diff --git a/src/result-utils.ts b/src/result-utils.ts index 32d35e8..b7e34cd 100644 --- a/src/result-utils.ts +++ b/src/result-utils.ts @@ -158,17 +158,39 @@ function collectImages(images: ImageContent[]): ImageContent[] | null { return images; } +function unwrapJsonEnvelope(record: Record, fallback: unknown): unknown { + if ('json' in record) { + return record.json ?? null; + } + if ('data' in record) { + return Object.keys(record).length === 1 ? (record.data ?? null) : fallback; + } + return null; +} + +function parseStructuredContent(value: unknown): unknown { + if (value === undefined || value === null) { + return null; + } + if (typeof value === 'string') { + return tryParseJson(value); + } + if (typeof value !== 'object') { + return null; + } + + return unwrapJsonEnvelope(value as Record, value); +} + // tryParseJson pulls JSON payloads out of structured responses or raw strings. function tryParseJson(value: unknown): unknown { if (value === undefined || value === null) { return null; } if (typeof value === 'object') { - if ('json' in (value as Record)) { - return (value as Record).json ?? null; - } - if ('data' in (value as Record)) { - return (value as Record).data ?? null; + const unwrapped = unwrapJsonEnvelope(value as Record, value); + if (unwrapped !== null) { + return unwrapped; } } if (typeof value === 'string') { @@ -222,7 +244,7 @@ export function createCallResult(raw: T): CallResult { }, json() { const collected = getCollectedContent(); - const parsedStructured = tryParseJson(collected.structuredContent); + const parsedStructured = parseStructuredContent(collected.structuredContent); if (parsedStructured !== null) { return parsedStructured as J; } diff --git a/tests/cli-output-utils.test.ts b/tests/cli-output-utils.test.ts index 4fa1c0f..e60d134 100644 --- a/tests/cli-output-utils.test.ts +++ b/tests/cli-output-utils.test.ts @@ -18,6 +18,32 @@ describe('printCallOutput format selection', () => { expect(JSON.parse(String(logged))).toEqual({ source: 'json' }); }, ], + [ + 'auto prints the full structuredContent object instead of only its data field', + 'auto', + { + structuredContent: { + status: 'error', + summary: 'Failed to create base: name is required', + data: {}, + meta: {}, + }, + content: [ + { + type: 'text', + text: '{"status":"error","summary":"Failed to create base: name is required","data":{},"meta":{}}', + }, + ], + }, + (logged: unknown) => { + expect(JSON.parse(String(logged))).toEqual({ + status: 'error', + summary: 'Failed to create base: name is required', + data: {}, + meta: {}, + }); + }, + ], [ 'text prefers text over markdown/json', 'text', diff --git a/tests/result-utils.test.ts b/tests/result-utils.test.ts index 0c61d26..823a625 100644 --- a/tests/result-utils.test.ts +++ b/tests/result-utils.test.ts @@ -215,6 +215,77 @@ describe('createCallResult json extraction', () => { const result = createCallResult(response); expect(result.json()).toEqual({ nested: true }); }); + + it('returns the full structuredContent object when data is only one field among many', () => { + const response = { + raw: { + structuredContent: { + error: { + type: 'USER_ERROR', + message: 'Base name is required and cannot be empty or only whitespace', + retryable: false, + code: 'INVALID_NAME', + }, + status: 'error', + summary: 'Failed to create base: name is required', + data: {}, + meta: {}, + trace_id: 'trace-123', + }, + }, + }; + const result = createCallResult(response); + expect(result.json()).toEqual({ + error: { + type: 'USER_ERROR', + message: 'Base name is required and cannot be empty or only whitespace', + retryable: false, + code: 'INVALID_NAME', + }, + status: 'error', + summary: 'Failed to create base: name is required', + data: {}, + meta: {}, + trace_id: 'trace-123', + }); + }); + + it('returns the full parsed json object when text content includes data plus error fields', () => { + const response = { + content: [ + { + type: 'text', + text: JSON.stringify({ + error: { + type: 'USER_ERROR', + message: 'Base name is required and cannot be empty or only whitespace', + retryable: false, + code: 'INVALID_NAME', + }, + status: 'error', + summary: 'Failed to create base: name is required', + data: {}, + meta: {}, + trace_id: 'trace-123', + }), + }, + ], + }; + const result = createCallResult(response); + expect(result.json()).toEqual({ + error: { + type: 'USER_ERROR', + message: 'Base name is required and cannot be empty or only whitespace', + retryable: false, + code: 'INVALID_NAME', + }, + status: 'error', + summary: 'Failed to create base: name is required', + data: {}, + meta: {}, + trace_id: 'trace-123', + }); + }); }); describe('createCallResult structured accessors', () => {