Repository navigation
[Feature]: CodeBuddy capture-only tool bridge and account-roster model discovery (direct merge requested) #5146
Description
Area
Provider adapter — src/adapters/codebuddy/, src/adapters/coding-agent/, src/codex/catalog/provider-models.ts
Summary
Two completed, live-verified capabilities for the CodeBuddy adapter, implemented on top of current dev:
-
Structured tool calling. A capture-only MCP bridge advertises the Codex tool catalog to the CLI, so routed models stop writing tool-call intent as DSML prose. This is the "control-protocol tool bridge" the adapter itself already names as a fast-follow:
"Codex's tool catalog is not advertised in v1 (the control-protocol tool bridge is a fast-follow)." —
src/adapters/codebuddy/adapter.ts -
Model catalog sync. Live model discovery reads the account-scoped
--helproster instead of relying on the static manifest-derived seed, so the proxy's CodeBuddy catalog matches what the CodeBuddy client offers the same account.
Both are implemented on a public reference branch (my fork, commit-by-commit chronology) with focused tests, typecheck, privacy scan, and an opt-in live acceptance run against the real CLI and a real subscription. I am asking for these to land as direct merges of those commits into dev (the line that promotes to main), sliced as below — not as a reimplementation of the approach.
Problem 1 — the model has no tool channel
The CLI is spawned with --tools "" and --strict-mcp-config, so the routed model has no tool channel at all. When a turn needs a tool, the model writes its call as DSML calls/invoke control lines in ordinary text. That has produced a patch loop of leak-and-refuse fixes: #4596 -> #4776 -> #4852 -> #4887. The guard work is correct fail-closed behavior, but it treats the symptom. The model keeps trying to call tools because it has no legitimate way to.
Problem 2 — the catalog cannot match the client
The static seed in src/providers/codebuddy-models.ts is transcribed from the manifest bundled with the vendor CLI, and CodeBuddy documents no third-party /v1/models endpoint. But the CLI's --help roster line is account-scoped and served fresh: the same binary prints a different list per subscription, and it already lists models the bundled manifest does not know — for example kimi-k3-2, glm-5.3, and deepseek-v4.1-flash appear on a CN account's roster, while the manifests shipped with both CLI 2.126.0 and 2.155.0 still describe the kimi-k3-1 generation. With liveModels: false on the presets, a fresh install advertises models the client cannot serve and hides models it can. That is exactly the mismatch users see between the proxy catalog and the CodeBuddy client.
Proposal 1 — capture-only MCP bridge (armed per request)
- Per turn, opencodex writes the tool schemas already present in the client's Responses request to a private temp dir and starts a capture-only MCP server for them.
- The CLI keeps
--tools ""(builtins off) and--strict-mcp-config, but receives--mcp-configpointing at only this server, plus an exact--allowedToolslist. User-configured MCP servers stay excluded. - The
system/initframe must report exactly that bridge server as connected, or the turn fails closed — the model must actually have seen the catalog. - The model's tool calls arrive as structured MCP calls. The server captures intent and never executes. opencodex emits them to the client as Responses
function_callitems (names mapped back to the request's wire names, at most 16 calls per assistant message). - The external Codex client performs approval, sandboxing, and execution, exactly as today. Tool results return as
function_call_outputon the next request and the conversation continues. Continuation is the stock Responses replay path — no bespoke registry. - Fail-closed stays: malformed or truncated NDJSON, an undeclared call name, oversize arguments, or exceeding the turn limit terminates the leg with an error. A model turn that may have been billed is never blindly replayed.
This is not prose-to-execution — the boundary maintained in #4515 ("turning model prose into tool execution is a security boundary, not a convenience") — because calls arrive through the MCP schema contract the bridge itself advertised, never by interpreting model text.
Proposal 2 — account-roster discovery
- On start/sync, run
codebuddy --helpwith the region's key and parse the "Currently supported" roster line (bounded output, strict line match,custom:*selectors excluded as per-user operator upstreams). - Bind the cached roster to an irreversible fingerprint of the key, so an account switch never reuses another account's roster.
- Degrade in order: stale account-scoped cache, then the static manifest seed; a discovery failure is a warning plus degradation, never a crash.
- Default both presets to
liveModels: true; the static lists stay as the documented fallback.
Implementation and evidence
Reference branch: mdwsk88/opencodex branch feat/codebuddy-tool-bridge, rebased on current dev (7 commits).
- 207 focused tests across the codebuddy adapter, tool bridge, roster discovery, registry parity, tool conformance, and test-layout suites;
bun x tsc --noEmitclean;bun run privacy:scanclean. - Opt-in live acceptance harness in-tree (
scripts/codebuddy-live-acceptance.ts): three real subscription turns through the real CLI — the model calls an advertised tool through the capture server, the executed tool result is replayed, and the final answer quotes it exactly. Both tool legs report real token usage (folded from pre-message_stopusage snapshots, because a terminated bridge leg has no result frame).
Slicing (two PRs, direct merge requested)
feat/codebuddy-roster-discovery— roster parser + key-fingerprint cache, presets default to discovery, static-seed header documentation, provider-guide docs. (3 commits)feat/codebuddy-tool-bridge— bridge + catalog advertisement + captured-call translation, pre-result usage accounting, opt-in live acceptance harness, provider-guide docs. (4 commits)
Both branches are cherry-picks of the reference-branch commits onto dev; they share two files but touch different lines, so either can land first and the other rebases routinely after the first merges.
Attribution
I designed and built the capture-only bridge and the account-roster discovery before the current coding-agent/ framework landed, and ported both onto dev for this submission. If any of this lands via carry or reimplementation rather than a direct merge, I ask for the Co-authored-by trailer per CREDITS.md — the repair path #3478/#3787 already established — and for this issue and the PR trail to remain the design record. The reference branch exists precisely so the chronology is verifiable without relying on anyone's goodwill.
Activity
Ingwannu commentedon Sep 20, 2026
Tracked by PR #5147 (account-scoped roster discovery) and PR #5148 (capture-only tool bridge). Neither is presently a direct-merge candidate: #5147 is draft and #5148 is conflicting/has outstanding review boundaries.
The two slices should remain independent. Roster discovery must stay credential-scoped and bounded; the MCP bridge must remain capture-only, require exact init/catalog acknowledgement, never execute tools, and fail closed on incomplete calls. Repository policy requires normal PR review and exact-head CI; the “direct merge requested” wording does not bypass those gates. Keeping this issue open until each slice reaches a terminal merged or rejected disposition.
fix(security): carry luvs01 service and security hardening stacks (#5610
devin-ai-integration commentedon Sep 24, 2026
Triage:
priority: P3— CodeBuddy tool bridge / roster discovery feature.Criteria (P3): Low: new provider/client integration, large or experimental feature (>2000 LOC or >50 files), RFC/roadmap, or long-stale branch.
Matched pull requests:
priority: P3]priority: P3]lidge-jun commentedon Sep 26, 2026
Fixed on
dev: #5147, carried in enhancement merge train batch 10B, #5992 (merge 35f267d), discovers the key-scoped CodeBuddy account roster through the vendor config endpoint. The capture-only bridge half landed earlier via #5610. Closing; please reopen if a build that includes 35f267d still shows the static catalog.