Repository navigation
[Feature]: Claude subscription provider — JSON-schema output and Responses tool round trips #6469
Description
Area
Provider adapters
What are you trying to accomplish?
Use the official Claude subscription runtime behind an application-managed Codex app-server:
Application → Codex app-server → OpenCodex /v1/responses → official Claude runtime
The application needs both ordinary text generation and a structured workflow: advertise application tools, receive a native function call, execute it in the application, return the result, and receive a final answer conforming to a supplied JSON Schema. Codex remains the tool/session host; the application retains control of tool execution and writes.
This request concerns the subscription provider, with authentication owned by the official Claude runtime. A direct Anthropic API-key adapter or copying subscription tokens into a custom HTTP client does not meet this use case.
What prevents this today?
On the inspected OpenCodex 2.73.0 source, claude-cli does not pass the caller's tool catalog or textFormat to the Claude process. Its argument builder has neither the tool bridge configuration nor --json-schema.
There is already relevant work: #5795 proposed the capture-only CLI bridge and was closed without merging; the open #5800 introduces the official Agent SDK route and capture-only tools, with a proposed rename to claude-agent-sdk. This request should build on that work, not duplicate or dictate its transport implementation.
At the reviewed #5800 head (ed0ffd9d537fb8033c5a028dd6ee3535712f0670), sdk-options.ts still does not map parsed.options.textFormat to the SDK's outputFormat. The shared result mapper also does not map the SDK's structured_output result field. These are source observations on that PR head, not a claim about a future merged version.
The remaining integration requirement is schema-constrained final output and a demonstrated Responses tool-result continuation, including their use together through Codex app-server. A successful text turn or a single tool call alone does not establish this workflow.
What should OpenCodex do?
- Honor Responses
text.format.type: "json_schema"on the Claude subscription provider. Preserve the caller's schema semantics and return a final result that validates against it. Surface schema incompatibility, refusal, incomplete output, and invalid structured output explicitly; do not silently ignore the format or weaken strict constraints. - Support caller-owned tool round trips: return native
function_callitems with the declared tool names, valid arguments and nonempty call IDs; accept matchingfunction_call_outputitems on the next request; use those results in the final schema-conforming answer. Cover Codex app-server dynamic tools as well as a minimal direct Responses fixture. - Keep execution with the external caller. The subscription runtime's bridge should capture intent only. Preserve isolation from built-in shell/file tools and unrelated user MCP servers, plugins and hooks. Unsupported tool-choice or schema combinations should fail explicitly.
- Preserve cancellation and failure semantics across tool and final-answer turns: stop owned runtime/bridge processes, never report interrupted or incomplete output as successful completion, and do not automatically replay a potentially billed turn.
- Document the supported schema subset and any continuation/context limits. Include focused offline tests plus an opt-in live acceptance path for the exact runtime versions tested.
The useful acceptance case is one complete tool request → caller execution → tool result → validated final JSON flow. Tool-free structured output should also work.
Example usage or interface
Illustrative first POST /v1/responses request; replace the model placeholder with the configured subscription route:
{
"model": "<configured-Claude-subscription-model>",
"stream": true,
"input": "Read record demo-1 using lookup_record, then return its id and title.",
"tools": [
{
"type": "function",
"name": "lookup_record",
"description": "Read one application record by id.",
"parameters": {
"type": "object",
"properties": { "id": { "type": "string" } },
"required": ["id"],
"additionalProperties": false
}
}
],
"text": {
"format": {
"type": "json_schema",
"name": "record_summary",
"strict": true,
"schema": {
"type": "object",
"properties": {
"id": { "type": "string" },
"title": { "type": "string" }
},
"required": ["id", "title"],
"additionalProperties": false
}
}
}
}Expected exchange:
- OpenCodex returns a
function_callforlookup_record({"id":"demo-1"}). - The application executes it and sends the prior conversation/call plus a matching
function_call_outputcontaining{"id":"demo-1","title":"Example record"}, retaining the output schema. - The final successful response contains
{"id":"demo-1","title":"Example record"}as schema-valid JSON. - The same behavior works when Codex app-server supplies the dynamic tool and output schema. No proxy/runtime process executes the application action itself.
Alternatives or workarounds
- Asking for JSON in prompt text alone does not provide the required schema contract.
- A separate hand-maintained Claude proxy/fork duplicates Responses translation and lifecycle handling.
- The direct Anthropic API route uses a different authentication/billing model and is outside this request.
Reusing the capture-only work in #5800 and the official runtime's structured-output facilities looks promising, but the implementation choice belongs with that adapter.
Additional context
Source references for the inspected baseline:
- OpenCodex 2.73.0 Claude CLI adapter
- Existing generic coding-agent tool bridge entry point
- fix(claude-agent-sdk): replace the ToS-violating claude -p turn with Anthropic's own harness #5800 is the related open subscription-runtime implementation, not an already released capability.
- Claude translated output schemas can falsely claim strict compatibility #6005 concerns Anthropic inbound schema translation; this request is about the Claude subscription outbound provider and its end-to-end Responses behavior.
Evidence for this report is source inspection and offline input-projection checks on 2.73.0. No live Claude request or Codex app-server acceptance run was performed for this report; tests/live results described in the linked PRs belong to their authors. This request makes no claim about third-party subscription entitlement or changes to authentication policy.
Checks
- I searched existing issues and documentation.
- This request describes a concrete OpenCodex workflow rather than merely naming a desired technology.
- I removed secrets and personal data.
Activity
Ingwannu commentedon Oct 3, 2026
The gap is still present at the latest #5800 head I inspected, 277d064: buildAgentSdkTurnOptions does not project parsed.options.textFormat into outputFormat. My new #5800 verification concerns two process-termination regressions, not schema-constrained final output or a Codex app-server acceptance flow. Please keep this feature request open and distinct from the adapter migration. The useful acceptance boundary is exactly your combined caller-owned tool request/result plus schema-valid final answer, with refusal/incomplete/invalid-output tests; a prompt-only JSON hint or text/tool happy-path claim should not close it. No live subscription or app-server acceptance test was performed here.
lidge-jun commentedon Oct 9, 2026
Keeping this open with
needs-design+help wanted. Status ondev(d17a9f2): roughly 20% — the claude-cli subscription row, isolation flags, and text-turn lifecycle exist, but caller tools are not bridged (notoolBridgeincreateClaudeCliAdapter),text.formatjson_schema is never passed through, andfunction_call_outputis not a native continuation. #5795/#5800 closed unmerged. A design note is needed first: transport (claude -pvs Agent SDK), schema subset, and fail-closed behavior. Contributions welcome once that is written down.adirwt852-cloud commentedon Oct 10, 2026
Hi, I'd like to contribute to this issue. I can help draft the initial technical design covering the transport approach, supported JSON Schema subset, tool-call continuation flow, and fail-closed behavior.
I'll first review the existing adapter and related work to avoid duplicating previous efforts, then propose a small, testable implementation plan with offline tests using mocks.
Would it be helpful if I posted a design proposal for review before starting implementation?
Ingwannu commentedon Oct 10, 2026
Yes—please post the technical design here before starting the implementation. A small offline-testable proposal is useful; this is not acceptance of a particular transport or a promise to merge it.
Please compare reuse of the existing
claude-cliand shared coding-agent protocol/continuation modules with an Agent SDK option, including dependency/install and lifecycle tradeoffs. #5795/#5800 closed unmerged, so do not assume their proposed contracts are the current implementation; credit any source work you carry.The first acceptance slice should be the complete caller-owned tool request → matching tool result → schema-valid final answer, plus tool-free structured output. Specify the supported schema subset and strictness, call-ID continuity, tool-choice rejection, refusal/incomplete/invalid structured output, cancellation/process cleanup, and a hard bound against hidden redispatch/replay. Mocked transports should prove that built-in shell/file tools, unrelated MCP/plugin/hook state and live subscription credentials are not used. Keep native app-server/live subscription acceptance explicitly separate from those offline tests.
A design and test matrix can be reviewed without changing anyone's live runtime or making provider calls. The existing
needs-design/help wantedstatus remains appropriate.adirwt852-cloud commentedon Oct 10, 2026
Design Proposal: Claude Subscription Provider JSON Schema and Tool Round Trips
1. Context and Scope
Issue #6469 needs an end-to-end workflow through the Claude subscription provider:
Application → Codex app-server → OpenCodex
/v1/responses→ Claude subscription runtimeThe application advertises its own tools, receives native Responses API
function_callitems, executes those calls itself, and sends the results back asfunction_call_output. The provider must then return a final JSON response that validates against the original JSON Schema.OpenCodex remains the protocol adapter. It must not execute application tools or take ownership of the application's writes.
The work should build on the capture-only tool bridge explored in #5795 and the Agent SDK approach in #5800, rather than reimplementing either effort independently.
2. Transport Choice
Compare the existing CLI path with the Agent SDK
Two approaches should be evaluated before selecting the implementation.
Option A: Reuse
claude-cliand the shared coding-agent protocol/continuation modulesThis may minimize new dependencies and reuse existing OpenCodex request translation, tool-bridge and lifecycle handling. However, the design must verify whether the existing CLI path can support schema-constrained output and faithful continuation from caller-supplied tool results without hidden redispatch or replay.
Option B: Use the official Claude Agent SDK
This may provide a more direct interface to the official runtime and its structured-output facilities. However, it introduces dependency, packaging, installation and runtime-lifecycle considerations that need maintainer review. The implementation must also verify that the SDK supports the required tool-call capture and continuation semantics.
Recommendation: Prefer the Agent SDK only if its dependency and packaging requirements are acceptable and it supports the complete required workflow. Otherwise, compare the existing CLI and shared protocol path against the same acceptance tests. Neither approach should be considered sufficient until the complete workflow is demonstrated.
The related PR #5800 documents the Agent SDK approach, but it was closed without merging. Its implementation and proposed contracts must not be treated as released functionality.
Existing work and attribution
Before implementation, inspect the current source and relevant changes from #5795 and #5800. Reuse suitable existing code and design decisions where compatible, and credit the corresponding prior work in implementation notes or the pull request. Both PRs were closed without merging, so their proposed contracts and code must not be assumed to exist in the current
devbranch.Runtime isolation
The selected runtime turn should:
The chosen transport must be documented with its dependency, installation, continuation and lifecycle trade-offs.
3. JSON Schema Contract
The provider must honor
text.format.type: "json_schema"instead of treating it as a prompt hint.The original schema should be retained at the Responses boundary. The adapter may translate it into the runtime's structured-output format only when the translation preserves its meaning.
Proposed initial supported subset
Start with a documented, deliberately limited subset:
string,number,integer,booleanandnull.propertiesandrequired.items.enumwhere supported by the runtime.additionalProperties: falsefor closed objects.Nested objects and arrays must obey the same supported-subset rules recursively.
This is a proposed initial contract, not a claim that the current runtime accepts every listed construct or combination. Before implementation, verify each construct against the actual selected SDK/runtime version and its documented structured-output behavior.
Schema composition, references, regular-expression constraints, numeric or string bounds, and other keywords should not be advertised as supported until their behavior is verified. Unsupported keywords or combinations must be rejected explicitly rather than silently removed.
Schema processing
Use an existing, suitable schema validator if the repository already provides one. Otherwise, select and review an appropriate dependency instead of writing an incomplete validator that silently accepts unsupported constructs.
The original schema remains the authority for final validation. Runtime-side constraints alone are not sufficient evidence that the returned value satisfies the caller's contract.
4. Responses Tool-Call Continuation
The application owns tool execution. OpenCodex must translate between Responses items and the Claude runtime without executing the requested actions itself.
First request
For a request containing tools and an optional JSON Schema:
function_callitems with the declared tool name, valid JSON arguments and a nonemptycall_id.The provider must not return a completed final answer when the runtime has requested a tool and the caller still needs to execute it.
Caller execution
The application receives the
function_callitems and executes the requested tools using its own authorization, approval, sandboxing and execution policies.OpenCodex must not execute these calls, invoke its own shell or file tools on their behalf, or forward them to unrelated MCP servers.
Follow-up request
The application sends the prior conversation and matching
function_call_outputitems in the next/v1/responsesrequest, retaining the original JSON Schema when a structured final answer is required.The provider must:
function_call_outputitems as results of externally executed calls.function_callby itscall_id.Tool results are untrusted application data. They must be treated as tool output, not as instructions that can override system behavior or relax schema requirements.
If the selected SDK or CLI cannot represent the required continuation faithfully, the adapter must not flatten the result into ordinary assistant text and claim native continuation support. That limitation must be resolved in the design first.
Response state and identifiers
Preserve the Responses API contract for response IDs, function-call IDs and continuation items.
If the implementation supports stateless reconstruction, it must verify the supplied history and identifiers on every continuation request. If it requires server-side state, that state must have an explicit lifetime and cleanup policy.
A continuation must not silently use a result from another call or another response. Unsupported or incomplete continuation context must fail explicitly.
Use
store: falsewhere supported by the existing Responses contract. This must not be treated as a guarantee that every runtime or intermediate layer is stateless; session and temporary-data behavior must be handled separately.5. Streaming and Completion Semantics
Streaming must not turn partial or invalid output into a successful response.
For tool calls, collect enough of each call to validate its identifier, name and complete JSON arguments before exposing it as a usable
function_call.For schema-constrained final output, buffer the final JSON until generation has completed, JSON parsing succeeds and validation against the original schema passes. Only then may the response be marked completed.
If progress events are emitted before validation, they must not imply that the final structured result is valid.
If a stream is cancelled, interrupted, truncated or terminated by a token limit before completion, the provider must not report a successful final response or emit a misleading completed event.
6. Fail-Closed Behavior
The adapter should fail explicitly for the following cases:
function_call_outputwith no matching prior call.Errors should use the repository's existing Responses error conventions and remain distinguishable enough for callers and tests to identify the failure.
Do not silently fall back to ordinary text generation, weaken strict constraints, fabricate a JSON result, or return partial structured output as a successful completion.
Cancellation, cleanup and retries
Cancellation must propagate to the owned runtime turn and bridge. Child processes must be stopped and reaped, and temporary resources must be cleaned up within bounded time.
A potentially billed turn must not be automatically replayed after an ambiguous timeout or process failure. If completion cannot be established safely, return an explicit error and leave retry decisions to the caller.
The implementation must have a hard bound against hidden redispatch or replay. A tool result or continuation request must not silently cause the original operation to execute again.
7. Testing Strategy
Offline unit tests
Use mocked runtime responses for deterministic tests of:
Isolation tests must prove that built-in shell/file tools, unrelated MCP servers, plugins, hooks and live subscription credentials are not used or exposed by the mocked provider flow. Tests should verify that the provider does not read, forward or depend on live subscription credentials through an unintended path.
Mocks should exercise the adapter's real request/response translation and lifecycle code rather than testing only standalone helper functions.
Responses protocol acceptance tests
Add a minimal direct
/v1/responsesfixture with alookup_recordtool and a strict schema for a record summary.The test should cover the complete exchange:
function_callforlookup_record.function_call_outputwith the original conversation and schema.Also test the tool-free structured-output case, where no tool call occurs and the first request returns schema-valid JSON.
Codex app-server acceptance test
Repeat the combined workflow using a dynamic tool supplied by Codex app-server, rather than relying only on a static direct Responses fixture.
This test must demonstrate that dynamic tool definitions and the output schema survive the full app-server → OpenCodex → Claude runtime flow. It must also demonstrate that the application, not the provider, executes the tool.
Opt-in live test
Provide a separate opt-in acceptance path for a real Claude subscription runtime. Document the exact runtime/SDK and Claude Code versions tested, configuration prerequisites, expected account usage and cleanup behavior.
The live test must exercise the combined tool request, caller execution, tool result and validated final JSON workflow. A successful text request or an isolated tool-call response is not sufficient evidence.
Offline tests remain the normal test-suite path and must not require a logged-in Claude account or network access.
Offline mocks must not use live subscription credentials or make real provider calls. Live acceptance testing must be explicitly opt-in and separate from normal unit and integration tests.
8. Implementation Plan
claude-cliadapter, Responses translation, tool-continuation path and related work in feat(claude-cli): serve a tool catalog through the capture-only bridge #5795 and fix(claude-agent-sdk): replace the ToS-violating claude -p turn with Anthropic's own harness #5800.dev.function_callmapping.function_call_outputcontinuation without provider-side tool execution.9. Acceptance Criteria
The implementation should demonstrate that:
text.format.type: "json_schema"requests and validates final output against the original schema.function_callitems contain valid arguments and nonempty call IDs.function_call_outputitems continue the same workflow without executing application tools inside OpenCodex.10. Open Decisions for Maintainers
claude-cliand shared coding-agent protocol/continuation modules, or use the Agent SDK? What dependency and packaging constraints apply?These decisions should be resolved before claiming end-to-end support. The goal is a small, testable adapter change that preserves the Responses contract, not a separate Claude proxy or a second tool-execution system.
The proposal describes intended behavior and tests, not a claim that implementation, live subscription calls or Codex app-server acceptance tests have already succeeded.
Ingwannu commentedon Oct 11, 2026
Thanks — this proposal now makes the combined caller-owned tool round trip plus schema-valid final answer explicit, and clearly labels offline, app-server and live acceptance as different evidence. It also correctly treats #5795/#5800 as closed, unmerged prior work rather than installed functionality.
The next actionable artifact is a small offline capability comparison, not another broad adapter rewrite: run the same direct Responses fixture through the existing CLI/shared continuation seam and the proposed SDK seam, recording where each can/cannot preserve the original schema and matching call IDs without executing caller tools or replaying an uncertain turn. Keep unsupported-schema/refusal/incomplete output as explicit errors, and record the session/history ownership required by each path. Do not make a live subscription test a normal-suite prerequisite.
@lidge-jun: transport/dependency acceptance and the supported schema/continuation contract are the remaining design decisions. This is a scoped design follow-up, not acceptance of a new SDK dependency or permission to run live subscriptions. The issue should remain open until the complete contract is implemented and verified.
adirwt852-cloud commentedon Oct 11, 2026
Thanks for clarifying the next steps. I completed an offline comparison of the current CLI/shared continuation path and the historical
pr-5800SDK adapter using mocked transports.Findings:
Verification:
These were offline adapter-level tests using mocked or injected transports, not a full direct Responses or Codex app-server acceptance test. No live subscription calls were made, and no tracked files were changed.
The next step is to run the same direct Responses fixture through both paths and verify the complete caller-owned tool round trip and schema-valid final response. This should help clarify which approach can support the required workflow and what gaps still need to be addressed.
adirwt852-cloud commentedon Oct 11, 2026
Follow-up: I ran the offline comparison you asked for, using the same
lookup_record/record_summaryfixture with mocked transports. No live calls. This is adapter-level only; the full/v1/responsesfixture is still the next step.What I found
--json-schema([adapter.ts](https://github.com/adirwt852-cloud/opencodex/blob/9b6f4b2632492b3df637dccc009d6a01ed47c85/src/adapters/claude-cli/adapter.ts)), and the old SDK path never setsoutputFormat([sdk-options.ts](https://github.com/adirwt852-cloud/opencodex/blob/c05a9a18638512d5e2f21bcfa939176f2635e7ba/src/adapters/claude-agent-sdk/sdk-options.ts#L106)).--tools "". The old SDK path capturedlookup_record(IDcall_demo_1) through its capture-only bridge ([sdk-bridge.ts](https://github.com/adirwt852-cloud/opencodex/blob/c05a9a18638512d5e2f21bcfa939176f2635e7ba/src/adapters/claude-agent-sdk/sdk-bridge.ts#L58)), but I haven't checked how that serializes as a Responsesfunction_call.tool_choicebehaves differently: the CLI path completed without error on an unknown named choice, while the SDK path errored on required-with-no-call and on an unselectable named choice.What's fine on both: built-in tools, settings and hooks are disabled, the adapter never executes the caller's tool, and no session is persisted. Cancellation, timeout and failure produced explicit errors in the mocked runs, with one spawn/query per invocation.
Tests: existing adapter tests pass (CLI 25, SDK 61 with 1 skipped). My comparison suite had 15 passing tests, but these confirm the gaps above rather than show the feature working. I deleted the scratch suite afterwards, so it isn't reproducible from this comment alone.
Not tested: the real
@anthropic-ai/claude-agent-sdk(I used an injected fake module), the full/v1/responsesserver and serializer, server-level retry/replay, a real refusal response, and Codex app-server.Baseline:
[9b6f4b2](https://github.com/adirwt852-cloud/opencodex/tree/9b6f4b2632492b3df637dccc009d6a01ed47c85)for current,[c05a9a1](https://github.com/adirwt852-cloud/opencodex/tree/c05a9a18638512d5e2f21bcfa939176f2635e7ba)for the historicalpr-5800ref.One question for the maintainers: can the intended SDK transport carry tool results as native state, or does it have to fall back to text history? That seems to decide whether the SDK path can meet the no-flattening requirement.
Ingwannu commentedon Oct 12, 2026
Thanks for the concrete comparison and for explicitly saying the 15-case scratch suite was deleted. Those passes document your observed gaps; they are not independently reproducible acceptance evidence yet, and a mocked SDK module cannot settle the native continuation capability question.
Please preserve the comparison as a small reviewable fixture/test patch with the exact two baseline SHAs and injected transports. Then carry the same lookup/schema case through the actual Responses request parser and serializer, including unknown/duplicate/missing call IDs and invalid final JSON. Keep it offline and keep historical #5800 code clearly separate from current dev; the closed, unmerged SDK proposal is not an accepted dependency.
Current dev
a0191a1b6fstill constructs the Claude CLI with built-in tools disabled and does not forward a final-output schema, so this remains a real feature gap, not a completed adapter migration. I am not asserting whether a real SDK session can faithfully represent the continuation from these fake-module results. @lidge-jun's transport/dependency and continuation-contract decision is still needed before implementation. No live subscription/app-server run or scan is authorized by this follow-up, and the issue stays open.adirwt852-cloud commentedon Oct 12, 2026
I’ve prepared an offline regression-test patch for the requested lookup/schema continuation case, keeping the current CLI baseline and historical SDK baseline explicitly separate.
Baselines
Regression coverage
The test file preserves the original 15 regression cases across Sections A–F and adds Section G coverage. The tests exercise the actual current Responses parser, continuation state, and response serializer. Coverage includes the lookup/schema scenario, tool-call outputs, unknown, duplicate, and missing call IDs, malformed requests, and invalid final JSON.
Section G executes the current Claude CLI adapter’s "runTurn()" through an injected fake process. It checks the generated CLI arguments and staged input, including that built-in tools are disabled and the requested output schema is not forwarded by the current CLI path.
Historical SDK comparison
I inspected the historical SDK implementation at the recorded baseline, but did not execute it. The patch does not treat historical source inspection or mocked results as proof of native SDK continuation compatibility. The historical SDK code remains separate from current dev, and no dependency from the closed, unmerged PR #5800 has been added.
Offline validation reported
The working-tree changes are limited to the new regression-test file and its two test-layout registration files. No production source, "package.json", or lockfile changes were reported. No live subscription/app-server run or scan was performed.
This patch provides reproducible offline evidence for the current Responses path and the current CLI adapter behavior. It does not claim that the adapter migration is complete.
The transport/dependency and continuation-contract decision remains with @lidge-jun. I have left production implementation unchanged pending that decision.
Ingwannu commentedon Oct 12, 2026
The distinction between current CLI evidence and historical SDK source inspection is useful. Please publish the offline patch as a reviewable Draft PR (or link an immutable patch/commit artifact) and record its exact implementation head, fixtures and commands there. This comment names baselines/counts but does not expose the test patch itself, so I cannot independently inspect or reproduce the18-test receipt yet.
Current dev has advanced to3bb7a5612c and testRunnerBun is1.4.3; refresh/revalidate the artifact against that code rather than treating the older9b6f4b26 baseline as current. Preserve the fake-process seam, no subscription/native app-server calls, and no dependency from closed #5800. No production migration or transport/dependency choice is authorized by a passing offline test. Owner decision remains separate; I have not executed the reported suite or approved that migration.