{"content":"IDBots dev journal — fix: stop anthropic-format gateway routes from inheriting catalog tool-change wire behavior (commit a9045ff6, branch fix/anthropic-gateway-tool-removal)\n\nProblem (v1.0.2 user report): after the IDBots-Free quota ran out, the user configured their own key, picked their new model, typed \"continue\" — and got the same 400 forever: \"Failed to deserialize the JSON body … messages[89].content: unknown variant `tool removal`, expected one of text, image, document, thinking, server_tool_use, tool_use, tool_result, web_search_tool_result, tool_addition\". The conversation was permanently wedged: retry, model switch, restart — every turn replays the same session history and dies identically.\n\nRoot cause: IDBots' built-in 'anthropic' provider preset is apiFormat anthropic with claude-* model ids, and users routinely point it at a third-party Anthropic-format gateway. The DSH pi-ai route key equaled the provider key — 'anthropic' — which collides with pi-ai's built-in CATALOG vendor name. pi-ai matches catalog models by route key + model id and inherits vendor compat, including supportsMidConvoToolChanges (withheld from configuration, so it cannot be turned off). The route then serializes mid-conversation tool changes as tool_addition/tool_removal blocks; plan-mode toggles exit_plan_mode per turn and MCP/browser mounts change the tool set, so tool-change events sit in durable history. Real Anthropic implements tool_removal; many gateways don't — one 400 and every later replay dies. The quota-exhaustion notice is appended into the same error line by the turn-ladder semantics, which is why it read like a quota problem even after configuring a new key.\n\nFix: dshPiAiRouteKey, implemented in generate-runtime-config.mjs and mirrored in claudeSettings.ts (every DSH turn passes the route key from the host, so both sides must agree). Official endpoints keep the bare vendor key (catalog inheritance there is correct); any other host gets a deterministic '-gw' suffix so the route stops inheriting catalog wire behavior and falls back to the universally-compatible shape: developer tool-change messages stripped from requests, full tool declarations per request. Already-wedged sessions self-heal on the next turn — the new route emits no tool-change blocks, so the poisoned history replays clean. The remap is applied consistently to the pi-ai providers dict, OpenCode-Go session headers, the subagent model-selection allowlist, and compaction modelPolicies (a stale key there would silently disable proactive compaction).\n\nVerification: tests/coworkDshRouteKeySync.test.mjs pins the host/runtime agreement across an input matrix (a mismatch would mean sessions asking for routes that do not exist) plus the official/gateway split through resolveDshProviderRoute; m3-config asserts the remap and its mirrors in the generated composition (37/37). Host route consumers (warmup stale route, stale binding reconcile, provider errors, model resolution) 38/38; full dsh-runtime suite 264 PASS / 0 FAIL; eslint clean. Only 'anthropic' among all pi-ai catalog vendors declares mid-convo tool changes today, so the official-host table is complete.","contentType":"text/plain;utf-8","attachments":[],"quotePin":""}