@@ -70,20 +70,25 @@ class ClaudeLocalAgentAdapter implements LocalAgentAdapter {
7070 allowDangerouslySkipPermissions : true ,
7171 env : claudeCommandEnvironment ( process . env ) ,
7272 ...( claudeExecutable ? { pathToClaudeCodeExecutable : claudeExecutable } : { } ) ,
73+ ...claudeOutputFormatOptions ( input . schema ) ,
7374 } ,
7475 } ) ;
7576
7677 let providerSessionId = input . providerSessionId ?? null ;
7778 let finalResponse = "" ;
79+ let structured : unknown | undefined ;
7880 const items : unknown [ ] = [ ] ;
7981 for await ( const message of messages ) {
8082 items . push ( message ) ;
8183 const record = message as Record < string , unknown > ;
8284 if ( typeof record . session_id === "string" ) providerSessionId = record . session_id ;
83- if ( record . type === "result" && typeof record . result === "string" ) {
84- const resultError = claudeResultError ( record ) ;
85- if ( resultError ) throw new Error ( resultError ) ;
86- finalResponse = record . result ;
85+ if ( record . type !== "result" ) continue ;
86+ const resultError = claudeResultError ( record ) ;
87+ if ( resultError ) throw new Error ( resultError ) ;
88+ const extracted = extractClaudeResultPayload ( record ) ;
89+ if ( extracted ) {
90+ finalResponse = extracted . finalResponse ;
91+ structured = extracted . structured ;
8792 }
8893 }
8994
@@ -93,10 +98,52 @@ class ClaudeLocalAgentAdapter implements LocalAgentAdapter {
9398 providerSessionId,
9499 finalResponse,
95100 items,
101+ ...( structured !== undefined ? { structured } : { } ) ,
96102 } ;
97103 }
98104}
99105
106+ /** Build Claude SDK outputFormat when a JSON Schema is requested. */
107+ export function claudeOutputFormatOptions (
108+ schema : object | undefined ,
109+ ) : { outputFormat : { type : "json_schema" ; schema : Record < string , unknown > } } | Record < string , never > {
110+ if ( ! schema ) return { } ;
111+ return {
112+ outputFormat : {
113+ type : "json_schema" ,
114+ schema : schema as Record < string , unknown > ,
115+ } ,
116+ } ;
117+ }
118+
119+ /**
120+ * Prefer structured_output from a Claude result message; fall back to text result.
121+ * When only structured is present, stringify it for journal finalResponse.
122+ */
123+ export function extractClaudeResultPayload (
124+ record : Record < string , unknown > ,
125+ ) : { finalResponse : string ; structured ?: unknown } | undefined {
126+ const hasStructured = Object . prototype . hasOwnProperty . call ( record , "structured_output" ) ;
127+ const structured = hasStructured ? record . structured_output : undefined ;
128+ const text = typeof record . result === "string" ? record . result : undefined ;
129+
130+ if ( hasStructured && structured !== undefined ) {
131+ return {
132+ finalResponse :
133+ text && text . trim ( )
134+ ? text
135+ : typeof structured === "string"
136+ ? structured
137+ : JSON . stringify ( structured ) ,
138+ structured,
139+ } ;
140+ }
141+ if ( text !== undefined ) {
142+ return { finalResponse : text } ;
143+ }
144+ return undefined ;
145+ }
146+
100147function claudeResultError ( record : Record < string , unknown > ) : string | undefined {
101148 const subtype = typeof record . subtype === "string" ? record . subtype : undefined ;
102149 const isError = record . is_error === true || subtype ?. startsWith ( "error" ) ;
@@ -516,6 +563,9 @@ async function promptOpencodeSession(
516563 sessionId : string ,
517564 input : LocalAgentRunInput ,
518565) : Promise < unknown > {
566+ // OpenCode SessionPrompt accepts format: { type: 'json_schema', schema }, but
567+ // per-model support is not programmatically discoverable — workflow agent({ schema })
568+ // keeps the prompt+Ajv path for opencode (no native structured output here).
519569 const session = ( client as {
520570 session : {
521571 prompt ( parameters ?: unknown , options ?: unknown ) : Promise < unknown > ;
0 commit comments