Skip to content

feat(wire): native Google/Gemini wire codec (#317) - #318

Merged
ranxianglei merged 2 commits into
ranxianglei:masterfrom
joshwapohlmann:2026-09-16_317-google-wire
Sep 17, 2026
Merged

ranxianglei merged 2 commits into
ranxianglei:masterfrom
joshwapohlmann:2026-09-16_317-google-wire

Conversation

@joshwapohlmann

@joshwapohlmann joshwapohlmann commented Sep 16, 2026

Copy link
Copy Markdown

Solution

Native support for the Gemini wire in the kernel, so a Gemini conversation can be detected, folded and re-emitted instead of passing through the proxy verbatim.

  • src/wire/google.ts (new): googleToCore / coreToGoogle, googleSystemText, injectGoogleSystem, conversationSignalGoogle, with GooglePart, GoogleContent, GoogleFunctionDeclaration, GoogleTool, GoogleSystemInstruction, GoogleRequestBody types and a GoogleFlat view for the parts that differ per wire.
  • src/wire/formats.ts: WIRE_FORMATS gains google, and detectWireFormat tests an array contents before the input and messages shapes.
  • src/wire/bili-message.ts: two sidecars, rawGoogleParts?: unknown[] after rawResponsesItem and googleThoughtSignature?: string after thinkingSignature, so a fold round-trips the original part array and its signature.
  • src/wire/strip-images.ts: inlineData and fileData parts collapse to [{text:"[image]"}], matching the other wires.
  • src/wire/index.ts: re-export.
  • src/compress-tools.ts: the tool declarations for this wire, in the flat {name, description, parameters} shape Gemini requires. Descriptions are taken from the *_OPENAI declarations rather than duplicated.

Why native rather than the OpenAI-compatibility endpoint

/v1beta/openai/chat/completions would be a far smaller change, but that translation drops thoughtSignature and extra_content. Gemini 3 requires a signature on every replay of a function-call part, so a folded conversation re-emitted through the compatibility layer is rejected with INVALID_ARGUMENT as soon as the client does tool calls, which is exactly the case this proxy exists to serve. The native wire keeps every part intact.

Wire constraints encoded in the codec

  • Same-side runs merge: Gemini rejects two consecutive model contents or two consecutive user contents, and an omp-style conversation produces both after a tool round.
  • systemInstruction is the system channel, so system text is hoisted out of the fold space and re-injected there rather than being carried as a content.
  • functionCall and functionResponse pair by name; the id is optional and not always present.
  • thought: true parts are not visible text, so every text-shaped consumer skips them.
  • Usage totals come from usageMetadata, where the prompt count includes cached tokens.

Tests

tests/wire-google.test.ts (new, 14 tests): round-trips of text, thinking parts, signatures, guarded calls and responses, system hoisting and re-injection, same-side merging, the conversation signal, and malformed bodies. tests/wire-formats.test.ts: the pinned format list gains google.

Verification

npm test 806/806 on this branch, 820/820 on the tree merged with current master; npm run typecheck clean. A companion billion-context PR consumes the codec and was verified against gemini-3.8-flash end to end: detection from the URL, a proxy-owned compress call folding a 97k-token conversation, and a re-emitted request carrying systemInstruction with valid alternating contents.

Companion

The proxy side that consumes this codec is filed as billion-context PR #867 (issue #866); it needs a kernel release carrying the codec before its own CI can resolve acp-kernel/wire.

@ranxianglei

Copy link
Copy Markdown
Owner

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 Review started on feat(wire): native Google/Gemini wire codec.

Initial state check:

  • Head branch joshwapohlmann/acp-kernel@2026-09-16_317-google-wire (6b99de6) is behind current master (it branched at the v0.0.74 release merge; v0.0.75 landed since). No file overlap with the newer master commits (recommend.ts, package.json only), so a rebase should be trivially clean.
  • Diff scope looks clean: 8 files, +1011/−3, every line tied to this feature.

Now running typecheck + full test suite + build locally and reviewing the codec against the wire contracts. Full report to follow in this thread.

A Gemini request body (contents[], systemInstruction, generationConfig) had no
codec, so detectWireFormat returned null for it, the proxy forwarded the request
verbatim and the session never entered the core: no identity, no folding, no
compression. This adds the codec.

- src/wire/google.ts: googleToCore/coreToGoogle over contents[]. googleToCore
  hoists systemInstruction.parts into GoogleFlat.systemText, maps role:"model"
  to assistant and any other role to user, expands functionResponse parts into
  tool-result cores BEFORE that content's own user core (a content carrying only
  functionResponses yields no user core), maps {functionCall} to a tool-call and
  thought:true to reasoning. coreToGoogle merges same-side runs into one content
  because Gemini rejects non-alternating roles: an assistant run becomes one
  role:"model" part list ordered reasoning, text, tool-call; a user-side run
  (user + tool-result) becomes one role:"user" with the functionResponse parts
  first. A functionResponse without a wire id pairs with the earliest unmatched
  call of the same name in one forward pass, so pairing stays deterministic for
  content-derived messages.
- Sidecars rawGoogleParts and googleThoughtSignature carry what the core does
  not model (inlineData, executableCode, call ids, thought signatures) so a
  rebuilt request stays faithful for Gemini 3, which validates thoughtSignature
  on every replayed part.
- formats: WIRE_FORMATS and detectWireFormat learn "google" (an array
  `contents`, checked before the input/messages branches).
- strip-images: inlineData/fileData parts are replaced by a text placeholder.
- compress-tools: the ACP tool declarations in Gemini's shape (flat
  `parameters`), so the compress/decompress/search_context/acp_status/absorb
  tools can be injected on this wire.
@joshwapohlmann
joshwapohlmann force-pushed the 2026-09-16_317-google-wire branch from 6b99de6 to a2ee01c Compare September 16, 2026 17:45
@joshwapohlmann

Copy link
Copy Markdown
Author

Rebased onto current master as you suggested: head is now a2ee01c on top of a679eca. npm run typecheck clean, npm test 818/818 (11 suites), npm run build OK on the rebased branch. Nothing in the change itself moved; the diff is still 8 files, +1011/−3.

@ranxianglei

Copy link
Copy Markdown
Owner

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 Re-reviewed the rebased head a2ee01c — all your numbers reproduce exactly:

Verified locally (head a2ee01c, base = current master a679eca, single commit):

  • npm run typecheck — clean
  • npm test818/818 pass, 11 suites, 0 fail
  • npm run build — success
  • diff vs master: still 8 files, +1011/−3, every line tied to the feature (no unrelated churn, no whitespace noise)
  • GitHub shows no check-runs recorded for the new sha yet (fork PR); local runs cover the same three checks CI performs

What I found solid in the codec itself:

  • The round-trip guarantee is the right design: raw parts ride the rawGoogleParts sidecar verbatim, scalar sidecars (googleThoughtSignature, image fields) rebuild when raw parts are gone, and the strongest test asserts the rebuilt contents are byte-equal to the request the client sent (assert.deepEqual(rebuilt, body.contents)), including videoMetadata/executableCode parts the core doesn't model.
  • Same-side run merging in coreToGoogle correctly enforces Gemini's alternating-role constraint, with part ordering (reasoning → text → calls; responses → content) pinned by tests.
  • Id-less functionResponse pairing (earliest unmatched call of the same name) is deterministic across passes — tested with structuredClone re-runs.
  • detectWireFormat testing contents first is unambiguous: no other dialect uses a top-level contents array.
  • Flat {name, description, parameters} tool declarations reusing the *_OPENAI descriptions avoids drift between copies — good call.

One cosmetic nit (non-blocking): the PR introduces prettier drift in 3 files that were clean before — src/wire/formats.ts:1 (the WIRE_FORMATS array wants multi-line), src/wire/strip-images.ts:13 (the StripProtocol union wants wrapping), tests/wire-formats.test.ts:10 (the deepEqual call wants wrapping). Context: CI doesn't run prettier and ~113 repo files already fail format:check, so this is optional — but since those lines are yours, npx prettier --write on those three files keeps the touched surface tool-clean. I can't push to your fork branch, so leaving it to you (or to be ignored).

Process notes: this is a feat: adding a new wire surface — per repo rules it stays human-gated regardless of green checks, so the merge decision is yours. And cross-repo sequencing applies: after merge, cut the next acp-kernel release, confirm npm view acp-kernel version, then bump the companion billion-context PR against it.

Conclusion: code reviewed line-by-line, all checks green on the current master base, no contract concerns (§7.3 untouched — additive wire only). Recommend merge.

中文摘要:逐行审查了 Google/Gemini 原生 wire codec 的 rebase 后版本(a2ee01c),typecheck/test(818/818)/build 全部本地复现通过,diff 干净无无关改动,round-trip 保真设计(raw parts sidecar + 字节级回环测试)扎实,仅发现 3 处可选的 prettier 换行问题(CI 不强制,不阻塞),建议合并。

@joshwapohlmann

Copy link
Copy Markdown
Author

human here: tested end 2 end in a live session, not just with mock-up. hope for quick merge and upstream integration because until then i have to maintain a weird local setup where i merge upstream changes with my local fixes.

@ranxianglei

Copy link
Copy Markdown
Owner

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 Thanks — live end-to-end confirmation against a real session is exactly the piece that mock tests can't cover, so that closes out the last open question on my side. My review stands as posted: all checks green on the current master base (a2ee01c), clean diff, no contract concerns.

One thing I can't do: I'm not allowed to merge PRs — that's a human-only operation under this repo's rules, even when asked directly. Please merge it yourself here:

#318

So your local fork-setup workaround can go away, the fastest path after the merge is: cut the next release branch (2026-XX-XX_release-v0.0.76, version bump only), let CI publish, confirm with npm view acp-kernel version, then point the companion billion-context PR at the new version. Happy to help with any of those steps once it's merged.

中文摘要:用户确认已做真实会话端到端验证;我的审查结论不变(全绿、建议合并),但按仓库规则我不能执行合并,请本人到 PR 页面自行合并,合并后我可以协助发版和适配端升级。

…anglei#317)

The raw-passthrough path was covered but not the changed-text rebuild
(kernel refs/truncation rewrite), which is the production compression
flow: text part re-renders from core text + sidecar signature, sibling
non-text raw parts preserved verbatim.
@joshwapohlmann

Copy link
Copy Markdown
Author

Both open items are confirmed on our side, so neither gates the merge.

2. URL detection. Correct, it lives in the proxy. googlePathKind(urlPath) and googleModelFromPath(urlPath) are in the companion proxy change (billion-context #867) and its e2e drives /v1beta/models/gemini-3.8-flash:streamGenerateContent?alt=sse, so the split you recommend is deliberate and already tested there rather than missing.

3. Mirror parity. Checked before answering, and it does not affect this PR. Nothing in either consumer calls a mirror* builder: mirrorOpenaiMessages, mirrorAnthropicMessages, mirrorResponsesInput and their *ToCore twins are referenced only by their own tests, and the proxy rebuilds a replayed conversation from the persisted raw body plus the rawGoogleParts / googleThoughtSignature sidecars, not through the mirror API. A google mirror would be a kernel-side addition for a consumer that needs one, not a gap in this change.

On the CI auth item: understood, and the per-PR prerelease is not consumed by anything of ours — the proxy pins a released kernel version and bumps it only after a release — so it does not gate us. The merge decision and the release path you sketched are with the maintainer.

Head 006f91a: I fast-forwarded locally onto your regression test and confirm npm run typecheck clean and npm test 819/819, matching your run. Thanks for closing that hole in the edited-rebuild path; a load-bearing branch with no test is exactly the kind of thing that survives review.

@ranxianglei

Copy link
Copy Markdown
Owner

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 Verified head 006f91a locally — your numbers match mine exactly: npm run typecheck clean, npm test 819/819 (0 fail), npm run build success, still sitting directly on current master (a679eca). The added test covers the right branch: core text mutated after decode (the ref-tag injection case) re-renders as a fresh text part while the sibling raw part (videoMetadata) and the googleThoughtSignature sidecar both survive — good catch that it had been load-bearing but untested.

And thanks for closing out the two design questions with evidence rather than assertion:

  • URL detection — accepted. googlePathKind/googleModelFromPath living in the proxy (billion-context #867) with e2e traffic against /v1beta/models/...:streamGenerateContent?alt=sse is the deliberate split; the kernel's body-shape contents check stays as the format discriminator. Nothing missing here.
  • Mirror parity — accepted. If no consumer calls the mirror* builders (tests only) and replay rebuilds from the persisted raw body plus the two sidecars, a google mirror would be speculative surface area. Correct call to leave it out; file it later if a consumer actually needs it.
  • CI auth / per-PR prerelease — understood, not consumed by your stack since the proxy pins released versions. Noted, no action.

So the PR-side work is done and green. What remains is entirely on the maintainer side: merge (human-only operation — I can't do it), then the release path I sketched before (release branch → CI publish → npm view acp-kernel version → bump billion-context against it). That unblocks getting off your local fork-setup workaround.

中文摘要:已本地验证新 head 006f91a(typecheck 干净、819/819、build 通过,与用户结果一致),新增测试正确覆盖了 contentParts 编辑文本重渲染分支;用户对 URL 检测在代理侧、mirror 无消费者两点均给出证据,予以接受,PR 侧工作已完成且全绿,剩余仅维护者合并及后续发版流程。

@ranxianglei

Copy link
Copy Markdown
Owner

继续

@ranxianglei
ranxianglei merged commit 8cff844 into ranxianglei:master Sep 17, 2026
3 of 4 checks passed
@ranxianglei ranxianglei mentioned this pull request Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants