Skip to content

Commit 71400e8

Browse files
committed
feat(release): ship claude native structured outputs
Requested removing the legacy JSON prompt fallback so Claude structured outputs rely on the Agent SDK's native schema enforcement. The Claude adapter now always requests json_schema outputFormat, extracts structured_output from result frames, and the sample test asserts this behavior. Documented the change in the changelog, bumped every package to 0.19.0 (updating peer ranges and locks), and rebuilt the Claude adapter to ensure the new flow compiles.
1 parent a9caa27 commit 71400e8

File tree

13 files changed

+36
-60
lines changed

13 files changed

+36
-60
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
## [0.19.0] - 2025-11-19
4+
### ✨ Claude Native Structured Outputs
5+
- `@headless-coder-sdk/claude-adapter` now relies entirely on Claude Agent SDK's native `structured_output`, removing the legacy prompt-based JSON parsing fallback so schemas are enforced by Claude itself.
6+
- Claude structured output example asserts that `structured_output` is returned, ensuring future regressions are caught by CI.
7+
8+
### 📦 Versioning
9+
- Bumped every package to `0.19.0` to publish the native structured output change and keep dependencies aligned.
10+
311
## [0.18.0] - 2025-11-14
412
### 🚀 Codex & Model Updates
513
- Codex adapter is now built against `@openai/codex-sdk@0.58.0`, unlocking GPT-5.1 Codex and GPT-5.1 Modals support out of the box.

examples/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@headless-coder-sdk/examples-tests",
33
"private": true,
4-
"version": "0.1.0",
4+
"version": "0.19.0",
55
"type": "module",
66
"main": "src/codex-web-calculator.test.ts",
77
"files": [

examples/src/claude-structured-output.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ async function runClaudeStructured(t: TestContext): Promise<void> {
7373
assert.equal(typeof structured.summary, 'string');
7474
assert.ok(Array.isArray(structured.risks));
7575
assert.ok(structured.risks.length >= 1);
76+
assert.ok(result.raw?.structured_output, 'Claude should return native structured output payloads.');
77+
assert.deepEqual(result.raw?.structured_output, structured);
7678
}
7779

7880
test('claude returns structured output', runClaudeStructured);

package-lock.json

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "headless-coder-sdk",
33
"private": true,
4-
"version": "0.1.0",
4+
"version": "0.19.0",
55
"type": "module",
66
"workspaces": [
77
"packages/*",

packages/acp-server/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@headless-coder-sdk/acp-server",
3-
"version": "0.12.0",
3+
"version": "0.19.0",
44
"type": "module",
55
"scripts": {
66
"dev": "next dev -p 8000",

packages/claude-adapter/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@headless-coder-sdk/claude-adapter",
3-
"version": "0.18.0",
3+
"version": "0.19.0",
44
"type": "module",
55
"main": "./dist/index.cjs",
66
"module": "./dist/index.js",
@@ -19,7 +19,7 @@
1919
},
2020
"peerDependencies": {
2121
"@anthropic-ai/claude-agent-sdk": ">=0.1.46",
22-
"@headless-coder-sdk/core": "^0.18.0"
22+
"@headless-coder-sdk/core": "^0.19.0"
2323
},
2424
"devDependencies": {
2525
"typescript": "^5.4.0",

packages/claude-adapter/src/index.ts

Lines changed: 3 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -61,28 +61,12 @@ interface ActiveClaudeRun {
6161
abortReason?: string;
6262
}
6363

64-
const STRUCTURED_OUTPUT_SUFFIX =
65-
'You must respond with valid JSON that satisfies the provided schema. Do not include prose before or after the JSON.';
66-
6764
function ensureNodeRuntime(action: string): void {
6865
if (!isNodeRuntime) {
6966
throw new Error(`@headless-coder-sdk/claude-adapter can only ${action} inside Node.js.`);
7067
}
7168
}
7269

73-
function applyOutputSchemaPrompt(input: PromptInput, schema?: object): PromptInput {
74-
if (!schema) return input;
75-
const schemaSnippet = JSON.stringify(schema, null, 2);
76-
const systemPrompt = `${STRUCTURED_OUTPUT_SUFFIX}\nSchema:\n${schemaSnippet}`;
77-
if (typeof input === 'string') {
78-
return `${input}\n\n${systemPrompt}`;
79-
}
80-
return [
81-
{ role: 'system', content: systemPrompt },
82-
...input,
83-
];
84-
}
85-
8670
function shouldUseNativeStructuredOutput(schema?: object): boolean {
8771
return !!schema;
8872
}
@@ -98,20 +82,6 @@ function extractNativeStructuredOutput(result: any): unknown | undefined {
9882
return undefined;
9983
}
10084

101-
function extractJsonPayload(text: string | undefined): unknown | undefined {
102-
if (!text) return undefined;
103-
const fenced = text.match(/```json\s*([\s\S]+?)```/i);
104-
const candidate = (fenced ? fenced[1] : text).trim();
105-
const start = candidate.indexOf('{');
106-
const end = candidate.lastIndexOf('}');
107-
if (start === -1 || end === -1 || end < start) return undefined;
108-
try {
109-
return JSON.parse(candidate.slice(start, end + 1));
110-
} catch {
111-
return undefined;
112-
}
113-
}
114-
11585
/**
11686
* Normalises prompt input into Claude's string format.
11787
*
@@ -237,8 +207,7 @@ export class ClaudeAdapter implements HeadlessCoder {
237207
const state = thread.internal as ClaudeThreadState;
238208
this.assertIdle(state);
239209
const useNativeStructuredOutput = shouldUseNativeStructuredOutput(runOpts?.outputSchema);
240-
const promptInput = useNativeStructuredOutput ? input : applyOutputSchemaPrompt(input, runOpts?.outputSchema);
241-
const prompt = toPrompt(promptInput);
210+
const prompt = toPrompt(input);
242211
const options = this.buildOptions(state, runOpts, useNativeStructuredOutput);
243212
const generator = query({ prompt, options });
244213
const active = this.registerRun(state, generator, runOpts?.signal);
@@ -279,9 +248,7 @@ export class ClaudeAdapter implements HeadlessCoder {
279248
if (finalResult && claudeResultIndicatesError(finalResult)) {
280249
throw new Error(buildClaudeResultErrorMessage(finalResult));
281250
}
282-
const structured = runOpts?.outputSchema
283-
? extractNativeStructuredOutput(finalResult) ?? extractJsonPayload(lastAssistant)
284-
: undefined;
251+
const structured = runOpts?.outputSchema ? extractNativeStructuredOutput(finalResult) : undefined;
285252
return { threadId: state.sessionId, text: lastAssistant, raw: finalResult, json: structured };
286253
}
287254

@@ -308,8 +275,7 @@ export class ClaudeAdapter implements HeadlessCoder {
308275
const state = thread.internal as ClaudeThreadState;
309276
this.assertIdle(state);
310277
const useNativeStructuredOutput = shouldUseNativeStructuredOutput(runOpts?.outputSchema);
311-
const promptInput = useNativeStructuredOutput ? input : applyOutputSchemaPrompt(input, runOpts?.outputSchema);
312-
const prompt = toPrompt(promptInput);
278+
const prompt = toPrompt(input);
313279
const options = this.buildOptions(state, runOpts, useNativeStructuredOutput);
314280
const generator = query({ prompt, options });
315281
const adapter = this;

packages/codex-adapter/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@headless-coder-sdk/codex-adapter",
3-
"version": "0.18.0",
3+
"version": "0.19.0",
44
"type": "module",
55
"main": "./dist/index.cjs",
66
"module": "./dist/index.js",
@@ -27,7 +27,7 @@
2727
"build": "tsup --config tsup.config.ts"
2828
},
2929
"peerDependencies": {
30-
"@headless-coder-sdk/core": "^0.18.0",
30+
"@headless-coder-sdk/core": "^0.19.0",
3131
"@openai/codex-sdk": "^0.58.0"
3232
},
3333
"devDependencies": {

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@headless-coder-sdk/core",
3-
"version": "0.18.0",
3+
"version": "0.19.0",
44
"description": "Unified SDK for headless AI coders (Codex, Claude, Gemini) with standardized threading, streaming, and sandboxing.",
55
"type": "module",
66
"main": "./dist/index.cjs",

0 commit comments

Comments
 (0)