Skip to content

fix(volcengine): align Coding Plan preset with native Responses API #5159

Closed

Description

@juzijia

Client or integration

Direct HTTP client (validated through OpenCodex's Responses endpoint; the original incompatibility was observed from a Pi/Codex-compatible client path).

Provider or upstream service

Volcengine Ark Coding Plan (volcengine-coding-plan)

OpenCodex version

2.59.0

Endpoint or capability

Volcengine Coding Plan native Responses endpoint and tool-call continuation:

  • Upstream base: https://ark.cn-beijing.volces.com/api/coding/v3
  • Expected Responses resource: /responses
  • Full upstream URL: https://ark.cn-beijing.volces.com/api/coding/v3/responses
  • Capabilities involved: Responses wire selection, streaming tool calls, function-call continuation, reasoning-item replay

Current behaviour

Volcengine's current Coding Plan Codex documentation specifies the Responses wire protocol, but OpenCodex 2.59.0 still presets volcengine-coding-plan to openai-chat.

Current built-in preset:

provider: volcengine-coding-plan
baseUrl: https://ark.cn-beijing.volces.com/api/coding/v3
adapter: openai-chat

On the existing Chat translation path, GLM-5.3 has produced real streamed tool-call failures such as:

upstream_server_error: upstream stream produced malformed tool call arguments

To isolate the wire protocol, I added a separate non-default test provider without modifying the production provider:

adapter: openai-responses
baseUrl: https://ark.cn-beijing.volces.com/api/coding/v3
responsesPath: /responses

Native Responses then succeeded for all three Coding Plan models used in this environment:

  • deepseek-v4-flash: HTTP 200, exact smoke response, sendCount=1, no recovery.
  • glm-5.3: HTTP 200 function call with valid JSON arguments; continuation HTTP 200; no malformed tool-call error.
  • glm-5.3-flash: HTTP 200 function call and continuation; valid JSON arguments; no malformed tool-call error.

A second compatibility issue was also reproduced on the native Responses path: replaying the full prior reasoning output item together with function_call_output caused Ark to return 400 InvalidParameter. Replaying only the required function_call plus function_call_output succeeded with HTTP 200.

Expected behaviour

The built-in volcengine-coding-plan preset should follow Volcengine's current Codex documentation and use native Responses by default.

Expected provider behaviour:

adapter: openai-responses
baseUrl: https://ark.cn-beijing.volces.com/api/coding/v3
responsesPath: /responses

This must resolve to:

https://ark.cn-beijing.volces.com/api/coding/v3/responses

Tool-call continuations should also apply provider-scoped reasoning replay handling compatible with Ark: do not forward a replayed reasoning item in the form that Ark rejects, while preserving the function_call + function_call_output continuation.

Chat should remain available as an explicit compatibility/per-model fallback where the existing OpenCodex routing model supports it, similar in design intent to the Z.AI Responses migration in #4307.

Minimal redacted request or reproduction

# 1. Current built-in OpenCodex provider (2.59.0)
#    volcengine-coding-plan:
#      baseUrl=https://ark.cn-beijing.volces.com/api/coding/v3
#      adapter=openai-chat
#
# 2. Add a separate TEST-only provider (not default; production provider unchanged):
#    adapter=openai-responses
#    baseUrl=https://ark.cn-beijing.volces.com/api/coding/v3
#    responsesPath=/responses
#
# 3. Minimal Responses smoke request through OpenCodex:
curl -sS http://127.0.0.1:10100/v1/responses \
  -H 'Authorization: Bearer <redacted>' \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "volcengine-coding-plan-responses-test/deepseek-v4-flash",
    "input": "Reply exactly: RESPONSE-SMOKE-OK"
  }'

# Result:
# HTTP 200
# resolved upstream:
# https://ark.cn-beijing.volces.com/api/coding/v3/responses
# output:
# RESPONSE-SMOKE-OK

# 4. GLM-5.3 function-call validation:
# echo({"value":"GLM-RESPONSES-OK"})
# Initial tool call: HTTP 200
# arguments: {"value":"GLM-RESPONSES-OK"}
# continuation without replayed reasoning item: HTTP 200
#
# Replaying the full prior reasoning item + function_call_output:
# HTTP 400 InvalidParameter

Actual response or error

# Existing Chat translation path, observed on GLM-5.3:
upstream_server_error: upstream stream produced malformed tool call arguments

# Native Responses compatibility test:
# full prior reasoning item + function_call_output
HTTP 400 InvalidParameter

# Same native Responses continuation after omitting the rejected reasoning item:
HTTP 200
completed

# Native Responses smoke/tool-call validation:
deepseek-v4-flash: HTTP 200
glm-5.3: HTTP 200 / HTTP 200 continuation
glm-5.3-flash: HTTP 200 / HTTP 200 continuation

Upstream documentation

Volcengine Coding Plan Codex documentation:

https://docs.volcengine.com/docs/ark/coding-plan-personal-ai-codex?lang=zh

The current documented Codex configuration specifies:

base_url = "https://ark.cn-beijing.volces.com/api/coding/v3"
wire_api = "responses"

Suggested mapping or implementation notes

The provider preset is now behind the upstream Codex integration documentation.

Suggested direction:

  1. Make native Responses the default wire for volcengine-coding-plan.
  2. Set responsesPath: "/responses".
  3. Keep the existing Chat wire available as an explicit compatibility/per-model fallback where supported.
  4. Use the Z.AI migration in feat(zai): default to the Responses protocol and keep Chat as a per-model opt-in #4307 as the design reference for Responses-by-default plus fallback, but do not copy its reasoning replay policy blindly: Z.AI accepts preserved plaintext reasoning replay, while Ark Coding Plan rejected the reproduced reasoning-item replay with 400 InvalidParameter.
  5. Apply a provider-scoped Responses replay sanitization rule for Ark rather than a GLM-model-specific hard-coded branch.
  6. Add focused regression coverage for preset wire selection, resolved URL, tool-call continuation, Ark reasoning replay handling, and explicit Chat override.

I am not claiming the Chat translation path is the sole cause of every previously observed malformed tool-call failure. The compatibility gap is independently established by the upstream wire change, and native Responses was validated locally for the three models above.

Additional context and attachments

Related OpenCodex design reference: #4307 (Z.AI Responses migration).

Local validation safety:

  • The production volcengine-coding-plan provider remained unchanged.
  • The test provider was additive and was not made default.
  • OpenCodex was not restarted.
  • No production source/configuration was overwritten.
  • No stress test or large-output reproduction was required.

Checks

  • I searched existing provider and compatibility issues.
  • The request and response were redacted.
  • The expected behaviour is based on an upstream specification or a concrete client requirement.

Activity

  1. github-actions commented on Sep 19, 2026

    @github-actions
    Contributor

    Issue reopened

    The report now contains the information required by the automated check. Thanks for updating it.

  2. added
    providerProvider adapters, OpenAI-compat presets, upstream API quirks
    toolstool_calls, MCP, web-search / sidecar tools
    on Sep 19, 2026
  3. lidge-jun commented on Sep 19, 2026

    @lidge-jun
    Owner

    Closed by #5198, squash-merged into dev as 98b9b344e1. Carried from #5173 with the author credited.

    The Volcengine Coding Plan preset now targets the native Responses wire. One part of the submitted change was deliberately dropped, and it is the part worth explaining.

    #5173 shipped a startup migration that would rewrite every stored canonical Volcengine Chat row to Responses on the next boot. It copies the shape of the Z.AI wire migration, but inverts what makes that one safe: zai-responses-migration.ts gates on providerMatchesRegistryTransport, so it only rewrites rows the router already canonicalizes at request time — its own comment calls that behaviour-preserving by construction. A Volcengine Chat row is not canonicalized, so migrating it changes a wire an operator is actually using, and a version marker added today cannot distinguish the old default from a deliberate pre-upgrade choice to stay on Chat.

    So the preset default applies to new rows only, and the documentation says how to switch an existing row by hand. Dropping the migration also removed a src/server/index.ts hunk that would have taken that file from 892 to 895 lines against its 893-line ratchet cap once merged with dev.

    Evidence is exact-head hosted CI at 6861bc3fd087f4...: test 1/4 through 4/4, gates, structure gate and macos 1/2 all successful. No local suite was run in this lane.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    providerProvider adapters, OpenAI-compat presets, upstream API quirksprovider-compatibilityProvider compatibility reportstoolstool_calls, MCP, web-search / sidecar tools

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions