Summary
Multi-turn conversations against databricks-gpt-5-5 (and any other GPT-5 family model on the Databricks Responses path) fail on the second turn when driven through ucode + stock OpenCode. The Databricks Responses backend emits item.id values up to ~192 characters; OpenAI's Responses contract (which @ai-sdk/openai follows) validates incoming itemId at ≤64 characters. When OpenCode echoes the prior assistant message's itemId back in the next request, the gateway rejects it.
End-user symptom: first turn works, tool call appears. Second turn returns a 400 from /ai-gateway/codex/v1/responses and the conversation breaks.
This is the third in a series of blockers preventing ucode + stock OpenCode from substituting for a Databricks-aware OpenCode fork:
Reproduction (post-#97)
After the GPT/Codex provider is configured (the patch in #97 applied), select a databricks-gpt-5-5 model in OpenCode and run two turns that include a tool call:
ucode opencode run "list the files in $PWD then tell me how many python files there are"
Observed: turn 1 emits a bash/read tool call and the assistant inspects the directory. On turn 2 (the assistant's reply combining tool results into the final answer), OpenCode posts the assistant message from turn 1 with its providerOptions.openai.itemId echoed back. The gateway returns:
400 Bad Request
{"error":{"message":"Invalid value for 'input[N].id': string length must be <= 64",...}}
A direct curl reproduces the underlying server behaviour — fetch any Responses turn that produces a tool call and inspect output[].id:
DATABRICKS_CONFIG_PROFILE=<profile> bun -e '
import { Config } from "@databricks/sdk-experimental"
const cfg = new Config({ profile: process.env.DATABRICKS_CONFIG_PROFILE })
await cfg.ensureResolved()
const h = new Headers({ "content-type": "application/json" })
await cfg.authenticate(h)
const host = (await cfg.getHost()).origin
const r = await fetch(host + "/ai-gateway/codex/v1/responses", {
method: "POST", headers: h,
body: JSON.stringify({
model: "databricks-gpt-5-5",
input: "Use the get_weather tool for Melbourne.",
tools: [{ type: "function", name: "get_weather",
parameters: { type: "object", properties: { location: { type: "string" } }, required: ["location"] } }],
tool_choice: "auto",
max_output_tokens: 300,
}),
})
const j = await r.json()
console.log("output ids:")
for (const o of j.output ?? []) console.log(" ", o.id.length, o.id)
'
Typical output:
output ids:
180 rs_databricks_responses_018a73e6_4e10_7f4f_a3e2_2c8a4ed3b001_8f9b1c4d2e3a4f5b6c7d8e9f0a1b2c3d4e5f6789a0b1c2d3e4f5a6b7c8d9e0f1a
174 fc_databricks_responses_018a73e6_4e10_7f4f_a3e2_2c8a4ed3b001_a1b2c3d4e5f6789a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3
Two takeaways:
- Server-side root cause — Databricks should cap
output[].id length to OpenAI's documented 64-char contract on the /ai-gateway/codex/v1/responses route. Until that lands, every OpenAI-Responses-compatible client (not just OpenCode) hitting this gateway will break on multi-turn.
- Client-side mitigation — any client driving this gateway needs to strip oversized
itemId values before echoing the prior assistant turn back. Stock OpenCode does not do this; a Databricks-aware OpenCode fork does.
Where this leaves ucode users
Once #97 lands, every ucode user picking databricks-gpt-5-5 in OpenCode will hit this on turn 2. There are three places this can be fixed; ucode is the user-facing surface, so it's the one most likely to see the report:
(Recommended) Option A — ucode upstreams the truncation patch to OpenCode
The full patch (33 lines) is below. We've been running it on a fork (feat/databricks-provider) against databricks-gpt-5-5 since early May with zero regressions on Claude/Gemini and no multi-turn failures on GPT-5. Drop it into OpenCode's packages/opencode/src/provider/transform.ts inside the normalizeMessages function, then ucode can rely on a known-good OpenCode version range.
// transform.ts — inside normalizeMessages(), after the existing assistant
// message normalization loop and before the final `return msgs` (around line 334
// on a fresh checkout). Applies to both the model-serving path (via the bundled
// provider's providerOptions.databricks.itemId) and the AI Gateway path
// (via @ai-sdk/openai's providerOptions.openai.itemId).
const nativeApiSurface = (model as any).options?.nativeApiSurface
const skipItemIdTruncation =
nativeApiSurface === "anthropic" ||
nativeApiSurface === "gemini" ||
process.env["DATABRICKS_BARE_FETCH"] === "1"
if ((model as any).options?.useResponsesApi && !skipItemIdTruncation) {
msgs = msgs.map((msg) => {
if (msg.role !== "assistant" || !Array.isArray(msg.content)) return msg
return {
...msg,
content: msg.content.map((part) => {
const opts = (part as any).providerOptions
if (!opts) return part
let next = opts
for (const key of ["databricks", "openai"] as const) {
const itemId = next?.[key]?.itemId
if (typeof itemId === "string" && itemId.length > 64) {
const { itemId: _, ...rest } = next[key]
next = { ...next, [key]: rest }
}
}
return next === opts ? part : { ...part, providerOptions: next }
}) as typeof msg.content,
}
})
}
Design notes:
- Gated on
options.useResponsesApi so it only runs for the Responses path (Claude/Anthropic and Gemini paths skip via nativeApiSurface).
DATABRICKS_BARE_FETCH=1 escape hatch left in for diagnostic flows that want the raw IDs end-to-end.
- Strips both
providerOptions.databricks.itemId (model-serving path) and providerOptions.openai.itemId (AI Gateway path via @ai-sdk/openai).
- Does not mutate inputs; preserves the part if no oversize ID is present.
Option B — ucode pins OpenCode to a version that has the patch
If upstreaming is slow, ucode can pin opencode-ai in its installer prompts / package update checks to a known-good range. Today no published OpenCode version carries the truncation; this option only becomes viable after Option A lands.
Option C — server-side cap at the AI Gateway
The root-cause fix. Databricks should cap output[].id length to ≤64 on /ai-gateway/codex/v1/responses so that OpenAI's documented Responses contract is respected. Worth filing in parallel with the relevant service team, but client-side mitigation in OpenCode is the faster path to unblock ucode users.
Verification
- Fork carrying the patch (commit
bf6f2f996, branch feat/databricks-provider): 3-class smoke test passes on Claude, GPT-5.5, Gemini, including multi-turn tool use.
- Stock OpenCode without the patch: 3-class smoke test fails on GPT-5.5 turn 2 with the 400 above.
- Direct gateway
curl (no client in the loop): consistently returns id values >64 chars on output[] for tool-call responses.
Asks
- Confirm whether ucode is comfortable upstreaming Option A to OpenCode (we're happy to open the PR if that helps — let us know the preferred attribution).
- Track whether a server-side cap on
output[].id is on the gateway team's roadmap. If yes, Option A can ship as the interim fix until the server respects the contract.
- Until either lands, consider warning users in
ucode configure --agents opencode output that multi-turn GPT-5 conversations are known-broken on stock OpenCode.
Summary
Multi-turn conversations against
databricks-gpt-5-5(and any other GPT-5 family model on the Databricks Responses path) fail on the second turn when driven through ucode + stock OpenCode. The Databricks Responses backend emitsitem.idvalues up to ~192 characters; OpenAI's Responses contract (which@ai-sdk/openaifollows) validates incomingitemIdat ≤64 characters. When OpenCode echoes the prior assistant message'sitemIdback in the next request, the gateway rejects it.End-user symptom: first turn works, tool call appears. Second turn returns a 400 from
/ai-gateway/codex/v1/responsesand the conversation breaks.This is the third in a series of blockers preventing ucode + stock OpenCode from substituting for a Databricks-aware OpenCode fork:
1.4.11rejects the ucode-managed bearer config.Reproduction (post-#97)
After the GPT/Codex provider is configured (the patch in #97 applied), select a
databricks-gpt-5-5model in OpenCode and run two turns that include a tool call:ucode opencode run "list the files in $PWD then tell me how many python files there are"Observed: turn 1 emits a
bash/readtool call and the assistant inspects the directory. On turn 2 (the assistant's reply combining tool results into the final answer), OpenCode posts the assistant message from turn 1 with itsproviderOptions.openai.itemIdechoed back. The gateway returns:A direct
curlreproduces the underlying server behaviour — fetch any Responses turn that produces a tool call and inspectoutput[].id:Typical output:
Two takeaways:
output[].idlength to OpenAI's documented 64-char contract on the/ai-gateway/codex/v1/responsesroute. Until that lands, every OpenAI-Responses-compatible client (not just OpenCode) hitting this gateway will break on multi-turn.itemIdvalues before echoing the prior assistant turn back. Stock OpenCode does not do this; a Databricks-aware OpenCode fork does.Where this leaves ucode users
Once #97 lands, every ucode user picking
databricks-gpt-5-5in OpenCode will hit this on turn 2. There are three places this can be fixed; ucode is the user-facing surface, so it's the one most likely to see the report:(Recommended) Option A — ucode upstreams the truncation patch to OpenCode
The full patch (33 lines) is below. We've been running it on a fork (
feat/databricks-provider) againstdatabricks-gpt-5-5since early May with zero regressions on Claude/Gemini and no multi-turn failures on GPT-5. Drop it into OpenCode'spackages/opencode/src/provider/transform.tsinside thenormalizeMessagesfunction, then ucode can rely on a known-good OpenCode version range.Design notes:
options.useResponsesApiso it only runs for the Responses path (Claude/Anthropic and Gemini paths skip vianativeApiSurface).DATABRICKS_BARE_FETCH=1escape hatch left in for diagnostic flows that want the raw IDs end-to-end.providerOptions.databricks.itemId(model-serving path) andproviderOptions.openai.itemId(AI Gateway path via@ai-sdk/openai).Option B — ucode pins OpenCode to a version that has the patch
If upstreaming is slow, ucode can pin
opencode-aiin its installer prompts / package update checks to a known-good range. Today no published OpenCode version carries the truncation; this option only becomes viable after Option A lands.Option C — server-side cap at the AI Gateway
The root-cause fix. Databricks should cap
output[].idlength to ≤64 on/ai-gateway/codex/v1/responsesso that OpenAI's documented Responses contract is respected. Worth filing in parallel with the relevant service team, but client-side mitigation in OpenCode is the faster path to unblock ucode users.Verification
bf6f2f996, branchfeat/databricks-provider): 3-class smoke test passes on Claude, GPT-5.5, Gemini, including multi-turn tool use.curl(no client in the loop): consistently returnsidvalues >64 chars onoutput[]for tool-call responses.Asks
output[].idis on the gateway team's roadmap. If yes, Option A can ship as the interim fix until the server respects the contract.ucode configure --agents opencodeoutput that multi-turn GPT-5 conversations are known-broken on stock OpenCode.