mirror of
https://github.com/hanzoai/insights.git
synced 2026-10-30 16:15:31 +00:00
The five days of upstream evolution since the 2026-08-01 import land through bin/debrand: new query schema, billing counters, ducklake compaction, the desktop sandbox default, capture evolution. Ours' invariants hold everywhere they met it: IAM-native auth only, no ee, no upsell surfaces, one sessions model, org-is-the-tenant, v1-first serving, fail-closed capture. Crates and workflows the fork retired stay retired; the Lemon prefix finishes dissolving into elements.
442 lines
18 KiB
TypeScript
442 lines
18 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import {
|
|
EXEC_BUILT_PAYLOAD,
|
|
STRUCTURED_CONTENT_ONLY_TEXT,
|
|
estimateResponseTokens,
|
|
markExecPayload,
|
|
buildToolResultPayload,
|
|
isToolCallPayload,
|
|
} from '@/lib/build-tool-result'
|
|
import { estimateTokens } from '@/lib/estimate-tokens'
|
|
import { INSIGHTS_FORMATTED_RESULTS_OVERRIDE_KEY, INSIGHTS_META_KEY } from '@/tools/types'
|
|
import { APP_DATA_META_KEY } from '@/ui-apps/types'
|
|
|
|
// Simulates a `query-trends` handler return value: a UI-resource tool that
|
|
// carries both the raw `results` object and a pre-formatted pipe-delimited table
|
|
// surfaced via the override key.
|
|
const FORMATTED_TABLE = [
|
|
'Date|$pageview',
|
|
'2026-04-14|0',
|
|
'2026-04-15|0',
|
|
'2026-04-16|25',
|
|
'2026-04-17|0',
|
|
'2026-04-18|0',
|
|
'2026-04-19|0',
|
|
'2026-04-20|3',
|
|
'2026-04-21|0',
|
|
].join('\n')
|
|
|
|
function queryTrendsHandlerResult(withFormatted = true): Record<string, unknown> {
|
|
return {
|
|
results: [
|
|
{
|
|
data: [0, 0, 25, 0, 0, 0, 3, 0],
|
|
labels: [
|
|
'14-Apr-2026',
|
|
'15-Apr-2026',
|
|
'16-Apr-2026',
|
|
'17-Apr-2026',
|
|
'18-Apr-2026',
|
|
'19-Apr-2026',
|
|
'20-Apr-2026',
|
|
'21-Apr-2026',
|
|
],
|
|
count: 28,
|
|
label: '$pageview',
|
|
},
|
|
],
|
|
_insightsUrl: 'http://localhost:8010/project/1/insights/new#q=%7B...',
|
|
...(withFormatted ? { [INSIGHTS_FORMATTED_RESULTS_OVERRIDE_KEY]: FORMATTED_TABLE } : {}),
|
|
}
|
|
}
|
|
|
|
// Same `_meta` shape that `createQueryWrapper` produces for a UI-resource query tool.
|
|
const queryTrendsToolMeta = {
|
|
ui: { resourceUri: 'ui://insights/query-results.html' },
|
|
} as const
|
|
|
|
describe('buildToolResultPayload — query-trends for Claude Code', () => {
|
|
it('returns formatted table as text AND suppresses structuredContent for claude-code', () => {
|
|
const payload = buildToolResultPayload({
|
|
handlerResult: queryTrendsHandlerResult(),
|
|
toolMeta: queryTrendsToolMeta,
|
|
toolName: 'query-trends',
|
|
params: { series: [{ event: '$pageview', kind: 'EventsNode' }] },
|
|
suppressStructuredContentForFormattedResults: true,
|
|
distinctId: 'test-distinct-id',
|
|
})
|
|
|
|
// The model should see the formatted table — not a JSON dump.
|
|
expect(payload.content).toEqual([{ type: 'text', text: FORMATTED_TABLE }])
|
|
// No structuredContent: Claude Code would otherwise prefer it over text,
|
|
// defeating the purpose of the formatted_results override.
|
|
expect(payload).not.toHaveProperty('structuredContent')
|
|
})
|
|
|
|
it('keeps structuredContent when suppression is false', () => {
|
|
const payload = buildToolResultPayload({
|
|
handlerResult: queryTrendsHandlerResult(),
|
|
toolMeta: queryTrendsToolMeta,
|
|
toolName: 'query-trends',
|
|
params: {},
|
|
suppressStructuredContentForFormattedResults: false,
|
|
distinctId: 'test-distinct-id',
|
|
})
|
|
|
|
expect(payload.content[0]!.text).toBe(FORMATTED_TABLE)
|
|
expect(payload.structuredContent).toMatchObject({
|
|
results: expect.any(Array),
|
|
_insightsUrl: expect.any(String),
|
|
_analytics: { distinctId: 'test-distinct-id', toolName: 'query-trends' },
|
|
})
|
|
// Override key must not leak into structuredContent.
|
|
expect(payload.structuredContent).not.toHaveProperty(INSIGHTS_FORMATTED_RESULTS_OVERRIDE_KEY)
|
|
})
|
|
|
|
it('keeps structuredContent when suppression is omitted', () => {
|
|
const payload = buildToolResultPayload({
|
|
handlerResult: queryTrendsHandlerResult(),
|
|
toolMeta: queryTrendsToolMeta,
|
|
toolName: 'query-trends',
|
|
params: {},
|
|
distinctId: 'd',
|
|
})
|
|
|
|
expect(payload.content[0]!.text).toBe(FORMATTED_TABLE)
|
|
expect(payload.structuredContent).not.toBeUndefined()
|
|
})
|
|
|
|
it('keeps structuredContent when caller explicitly passes output_format=json (even on claude-code)', () => {
|
|
const payload = buildToolResultPayload({
|
|
handlerResult: queryTrendsHandlerResult(),
|
|
toolMeta: queryTrendsToolMeta,
|
|
toolName: 'query-trends',
|
|
params: { output_format: 'json' },
|
|
suppressStructuredContentForFormattedResults: true,
|
|
distinctId: 'd',
|
|
})
|
|
|
|
// Text still carries the formatted override (the override wins unconditionally)...
|
|
expect(payload.content[0]!.text).toBe(FORMATTED_TABLE)
|
|
// ...but structuredContent is no longer suppressed — the caller opted into JSON.
|
|
expect(payload.structuredContent).not.toBeUndefined()
|
|
expect(payload.structuredContent).toMatchObject({
|
|
results: expect.any(Array),
|
|
_insightsUrl: expect.any(String),
|
|
})
|
|
})
|
|
|
|
it('does NOT suppress structuredContent when there is no formatted_results override', () => {
|
|
// If the backend didn't return formatted_results (unsupported query type, EE unavailable,
|
|
// etc.), the wrapper has nothing to put in text besides the TOON-encoded raw object.
|
|
// In that case we must keep structuredContent so Claude Code at least sees the data.
|
|
const payload = buildToolResultPayload({
|
|
handlerResult: queryTrendsHandlerResult(/* withFormatted */ false),
|
|
toolMeta: queryTrendsToolMeta,
|
|
toolName: 'query-trends',
|
|
params: {},
|
|
suppressStructuredContentForFormattedResults: true,
|
|
distinctId: 'd',
|
|
})
|
|
|
|
expect(payload.structuredContent).not.toBeUndefined()
|
|
// Text is TOON-encoded rawResult, not JSON, not the formatted table.
|
|
expect(payload.content[0]!.text).not.toBe(FORMATTED_TABLE)
|
|
expect(payload.content[0]!.text).toContain('_insightsUrl')
|
|
})
|
|
|
|
it('embeds analytics metadata in structuredContent when present (non-suppressed case)', () => {
|
|
const payload = buildToolResultPayload({
|
|
handlerResult: queryTrendsHandlerResult(),
|
|
toolMeta: queryTrendsToolMeta,
|
|
toolName: 'query-trends',
|
|
params: {},
|
|
suppressStructuredContentForFormattedResults: false,
|
|
distinctId: 'user-abc-123',
|
|
})
|
|
|
|
expect(payload.structuredContent).toMatchObject({
|
|
_analytics: {
|
|
distinctId: 'user-abc-123',
|
|
toolName: 'query-trends',
|
|
},
|
|
})
|
|
})
|
|
|
|
it('omits structuredContent for tools without a UI resource', () => {
|
|
// Non-UI tools (e.g. a hypothetical `query-logs` without a UI app) must never emit
|
|
// structuredContent regardless of client or formatted_results.
|
|
const payload = buildToolResultPayload({
|
|
handlerResult: queryTrendsHandlerResult(),
|
|
toolMeta: undefined,
|
|
toolName: 'whatever',
|
|
params: {},
|
|
suppressStructuredContentForFormattedResults: false,
|
|
distinctId: 'd',
|
|
})
|
|
|
|
expect(payload).not.toHaveProperty('structuredContent')
|
|
expect(payload.content[0]!.text).toBe(FORMATTED_TABLE)
|
|
})
|
|
})
|
|
|
|
// Inline-exec UI-app hosts (Insights Desktop, Claude Code, Cowork) go through the exec
|
|
// wrapper, which sets `forceUiDataToMeta` + `includeUiResponseMeta`. The app payload
|
|
// should only move onto `_meta` when a compact formatted table takes structuredContent's
|
|
// place for the model — otherwise it stays in the standard structuredContent field so it
|
|
// isn't duplicated under a non-standard `_meta` key.
|
|
describe('buildToolResultPayload — inline-exec UI host (forceUiDataToMeta)', () => {
|
|
it('suppresses structuredContent and re-homes the payload onto _meta when a formatted table exists', () => {
|
|
const payload = buildToolResultPayload({
|
|
handlerResult: queryTrendsHandlerResult(/* withFormatted */ true),
|
|
toolMeta: queryTrendsToolMeta,
|
|
toolName: 'query-trends',
|
|
params: {},
|
|
forceUiDataToMeta: true,
|
|
includeUiResponseMeta: true,
|
|
distinctId: 'd',
|
|
})
|
|
|
|
// Model reads the compact table, not the verbose JSON.
|
|
expect(payload.content[0]!.text).toBe(FORMATTED_TABLE)
|
|
expect(payload).not.toHaveProperty('structuredContent')
|
|
// The UI app hydrates from _meta since structuredContent was dropped.
|
|
expect(payload._meta?.[APP_DATA_META_KEY]).toMatchObject({ results: expect.any(Array) })
|
|
})
|
|
|
|
it('keeps the payload in structuredContent and never emits _meta app-data when there is no formatted table', () => {
|
|
// Regression guard for the duplicated-results report: without a compact table the
|
|
// model reads the data as text anyway, so re-homing it onto `_meta` only duplicates
|
|
// the full payload under a non-standard key. Keep it in structuredContent instead.
|
|
const payload = buildToolResultPayload({
|
|
handlerResult: queryTrendsHandlerResult(/* withFormatted */ false),
|
|
toolMeta: queryTrendsToolMeta,
|
|
toolName: 'query-trends',
|
|
params: {},
|
|
forceUiDataToMeta: true,
|
|
includeUiResponseMeta: true,
|
|
distinctId: 'd',
|
|
})
|
|
|
|
expect(payload.structuredContent).toMatchObject({ results: expect.any(Array) })
|
|
expect(payload._meta?.[APP_DATA_META_KEY]).toBeUndefined()
|
|
// The UI resource URI is still exposed for single-exec clients.
|
|
expect(payload._meta?.ui).toEqual({ resourceUri: 'ui://insights/query-results.html' })
|
|
})
|
|
|
|
// Regression guard for the relapse report: after the payload moved back into
|
|
// `structuredContent`, the text channel still carried a full TOON copy of it, so an
|
|
// inline-exec UI host handed the agent the same rows twice. List and detail shapes
|
|
// are the two the report flagged (`experiment-list`, `experiment-get`).
|
|
it.each([
|
|
[
|
|
'list',
|
|
{
|
|
count: 2,
|
|
next: null,
|
|
results: [
|
|
{ id: 387170, name: 'Onboarding copy', feature_flag_key: 'onboarding-copy' },
|
|
{ id: 387171, name: 'Pricing page', feature_flag_key: 'pricing-page' },
|
|
],
|
|
},
|
|
],
|
|
[
|
|
'detail',
|
|
{
|
|
id: 387170,
|
|
name: 'Onboarding copy',
|
|
feature_flag: { key: 'onboarding-copy', filters: { groups: [{ rollout_percentage: 100 }] } },
|
|
metrics: [{ kind: 'ExperimentMetric', name: 'Signups' }],
|
|
},
|
|
],
|
|
])('serializes a %s payload exactly once when there is no formatted table', (_shape, handlerResult) => {
|
|
const payload = buildToolResultPayload({
|
|
handlerResult,
|
|
toolMeta: { ui: { resourceUri: 'ui://insights/experiment-list.html' } },
|
|
toolName: 'experiment-list',
|
|
params: {},
|
|
forceUiDataToMeta: true,
|
|
includeUiResponseMeta: true,
|
|
distinctId: 'd',
|
|
})
|
|
|
|
expect(payload.structuredContent).toMatchObject(handlerResult)
|
|
// The text channel points at structuredContent instead of repeating it.
|
|
expect(payload.content).toEqual([{ type: 'text', text: STRUCTURED_CONTENT_ONLY_TEXT }])
|
|
expect(payload.content[0]!.text).not.toContain('Onboarding copy')
|
|
expect(payload._meta?.[APP_DATA_META_KEY]).toBeUndefined()
|
|
})
|
|
|
|
it('counts the structured payload for token estimation when the text is only a pointer', () => {
|
|
// The estimate feeds `$mcp_tool_call.output_tokens`; without this the whole
|
|
// response would be billed as the one-line pointer.
|
|
const payload = buildToolResultPayload({
|
|
handlerResult: queryTrendsHandlerResult(/* withFormatted */ false),
|
|
toolMeta: queryTrendsToolMeta,
|
|
toolName: 'query-trends',
|
|
params: {},
|
|
forceUiDataToMeta: true,
|
|
includeUiResponseMeta: true,
|
|
distinctId: 'd',
|
|
})
|
|
|
|
expect(estimateResponseTokens(payload)).toBeGreaterThan(estimateTokens(STRUCTURED_CONTENT_ONLY_TEXT))
|
|
})
|
|
|
|
it('keeps the mirrored text when the caller asked for JSON output', () => {
|
|
const payload = buildToolResultPayload({
|
|
handlerResult: queryTrendsHandlerResult(/* withFormatted */ false),
|
|
toolMeta: queryTrendsToolMeta,
|
|
toolName: 'query-trends',
|
|
params: { output_format: 'json' },
|
|
forceUiDataToMeta: true,
|
|
includeUiResponseMeta: true,
|
|
distinctId: 'd',
|
|
})
|
|
|
|
expect(JSON.parse(payload.content[0]!.text)).toMatchObject({ results: expect.any(Array) })
|
|
expect(payload.structuredContent).toMatchObject({ results: expect.any(Array) })
|
|
})
|
|
})
|
|
|
|
describe('buildToolResultPayload — non-query use cases', () => {
|
|
it('preserves array handler results', () => {
|
|
const result = [{ id: 'template-1' }]
|
|
Object.defineProperty(result, INSIGHTS_FORMATTED_RESULTS_OVERRIDE_KEY, {
|
|
value: 'informational result',
|
|
enumerable: false,
|
|
})
|
|
|
|
const payload = buildToolResultPayload({
|
|
handlerResult: result,
|
|
toolMeta: undefined,
|
|
toolName: 'templates-list',
|
|
params: {},
|
|
})
|
|
|
|
expect(payload.content).toEqual([{ type: 'text', text: 'informational result' }])
|
|
})
|
|
|
|
it('passes string handler results through verbatim (no character-indexed expansion)', () => {
|
|
// Regression guard for the original bug: `execute-sql` and other
|
|
// string-returning handlers must not be object-rest-destructured.
|
|
const sqlResult = 'You are given a table...\n\nmarker|answer\nfix_applied|42'
|
|
|
|
const payload = buildToolResultPayload({
|
|
handlerResult: sqlResult,
|
|
toolMeta: undefined,
|
|
toolName: 'execute-sql',
|
|
params: {},
|
|
suppressStructuredContentForFormattedResults: true,
|
|
distinctId: undefined,
|
|
})
|
|
|
|
expect(payload.content[0]!.text).toBe(sqlResult)
|
|
expect(payload.content[0]!.text).not.toMatch(/"0":\s*"./)
|
|
expect(payload).not.toHaveProperty('structuredContent')
|
|
})
|
|
|
|
it('JSON-encodes rawResult when tool-level outputFormat=json is configured', () => {
|
|
// For tools like `query-llm-traces-list` that advertise JSON output at the tool level
|
|
// (via `_meta[INSIGHTS_META_KEY].outputFormat === 'json'`), text is JSON, not TOON.
|
|
const payload = buildToolResultPayload({
|
|
handlerResult: { results: [], _insightsUrl: 'http://...' },
|
|
toolMeta: { [INSIGHTS_META_KEY]: { outputFormat: 'json' } },
|
|
toolName: 'query-llm-traces-list',
|
|
params: {},
|
|
suppressStructuredContentForFormattedResults: true,
|
|
distinctId: undefined,
|
|
})
|
|
|
|
expect(() => JSON.parse(payload.content[0]!.text)).not.toThrow()
|
|
expect(JSON.parse(payload.content[0]!.text)).toEqual({
|
|
results: [],
|
|
_insightsUrl: 'http://...',
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('isToolCallPayload — nominal brand', () => {
|
|
it('matches only payloads carrying the exec brand', () => {
|
|
const branded = markExecPayload({
|
|
content: [{ type: 'text', text: 'hi' }],
|
|
structuredContent: { foo: 1 },
|
|
_meta: { ui: { resourceUri: 'ui://app' } },
|
|
})
|
|
expect(isToolCallPayload(branded)).toBe(true)
|
|
expect(branded[EXEC_BUILT_PAYLOAD]).toBe(true)
|
|
})
|
|
|
|
it('returns false for bare CallToolResult-shaped payloads without the brand', () => {
|
|
// Regression guard: `setActive` / `searchDocs` return this shape today, and
|
|
// a future tool returning a similar shape must not silently skip the
|
|
// buildToolResultPayload pipeline.
|
|
expect(
|
|
isToolCallPayload({
|
|
content: [{ type: 'text', text: 'switched' }],
|
|
})
|
|
).toBe(false)
|
|
expect(
|
|
isToolCallPayload({
|
|
content: [{ type: 'text', text: 'switched' }],
|
|
structuredContent: { foo: 1 },
|
|
})
|
|
).toBe(false)
|
|
expect(
|
|
isToolCallPayload({
|
|
content: [{ type: 'text', text: 'switched' }],
|
|
_meta: { ui: { resourceUri: 'ui://app' } },
|
|
})
|
|
).toBe(false)
|
|
})
|
|
|
|
it('returns false for non-object values', () => {
|
|
expect(isToolCallPayload(undefined)).toBe(false)
|
|
expect(isToolCallPayload(null)).toBe(false)
|
|
expect(isToolCallPayload('string-result')).toBe(false)
|
|
expect(isToolCallPayload(42)).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('buildToolResultPayload — confirmed-action prepare results', () => {
|
|
const prepareResult = {
|
|
confirmation_hash: 'signed-token-abc',
|
|
confirmation_word: 'confirm',
|
|
action: 'create loop',
|
|
message: "About to create the loop 'Open PR Summary'. Reply 'confirm' to create it.",
|
|
next_steps: 'Surface the message above to the user.',
|
|
}
|
|
|
|
it('carries the prepare payload on _meta so UI apps can read the hash', () => {
|
|
const payload = buildToolResultPayload({
|
|
handlerResult: prepareResult,
|
|
toolMeta: undefined,
|
|
toolName: 'loops-create-prepare',
|
|
params: { name: 'Open PR Summary' },
|
|
})
|
|
|
|
expect(payload._meta?.[APP_DATA_META_KEY]).toMatchObject({
|
|
confirmation_hash: 'signed-token-abc',
|
|
confirmation_word: 'confirm',
|
|
})
|
|
expect(payload).not.toHaveProperty('structuredContent')
|
|
expect(payload.content[0]!.text).toContain('signed-token-abc')
|
|
})
|
|
|
|
it.each([
|
|
['non-prepare object result', { results: [{ id: 1 }] }],
|
|
['confirmation_hash without the confirm word', { confirmation_hash: 'abc', confirmation_word: 'yes' }],
|
|
['non-string confirmation_hash', { confirmation_hash: 42, confirmation_word: 'confirm' }],
|
|
])('does not attach _meta for %s', (_label, handlerResult) => {
|
|
const payload = buildToolResultPayload({
|
|
handlerResult,
|
|
toolMeta: undefined,
|
|
toolName: 'whatever',
|
|
params: {},
|
|
})
|
|
|
|
expect(payload).not.toHaveProperty('_meta')
|
|
})
|
|
})
|