Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion devlog/_plan/260905_external_image_roundtrip/000_plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
- Outcomes: DONE only with proof; external dependencies may be BLOCKED/NEEDS_HUMAN;
unsafe expansion is UNSAFE. No implementation-success claim from docs-only work.
- Scope: this managed checkout, read-only Aside official docs, GitHub stack/CI/admin
merge. Maximum four concurrent agents; reassess after 90 minutes; no token cap set.
merge. The user's follow-up permits unlimited useful parallel agents (subject to
actual tool capacity); reassess after 90 minutes; no token cap set.
- Escalation: reclaim a lane after two distinct failed agents; any delegated writes
must be planned with disjoint paths before B. No production credentials in artifacts.

Expand Down Expand Up @@ -55,6 +56,12 @@ converter and protects the integrated contract independently of unit-level asser

## Continuity

wp1 outcome: commit `1f1daa368` implements 010 with ten converter regression cases;
draft PR #3586 targets dev. Independent patch reviewer inspected both changed files
and returned PASS. Standalone request JSON changed from image/detail missing (exit 1)
to both retained (exit 0); node TypeScript and privacy scan passed. Suites are CI-only,
not claimed green yet. wp2 inherits this verified converter and adds wire/HTTP evidence.

Roadmap audit: independent gpt-6-astra high reviewer returned GO-WITH-FIXES,
two medium findings. Both folded: exact no-suite typecheck/push commands and actual
Claude converter export. Direct node tsc exits 0. Standalone reproduction at
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

Depends on wp1 and its corrected Chat converter. One full PABCD cycle.

Delegated B lanes (user reconfirmed unlimited useful parallelism): worker A exclusively
edits tests/responses/openai-responses-passthrough.test.ts; worker B exclusively adds
the HTTP regression in tests/responses/chat-completions-endpoint.test.ts. Main owns
public docs, structure, devlog and git/CI. C reviewers are read-only and independent.
All lanes prohibit local test suites, services, config/auth and git/FSM mutation.

User steering during B: all image representations must be audited before merge. This
cycle now publishes the wire-contract child; the original exact-head CI/merge/ancestry
criterion is unchanged and moves to appended wp3 after the expanded audit. No criterion
is dropped or marked met early. Only existing 020 implementation runs in this B.

## MODIFY tests/responses/openai-responses-passthrough.test.ts

Import real chatCompletionsToResponsesBody, anthropicToResponsesBody,
Expand Down Expand Up @@ -38,6 +49,10 @@ existing Chat-to-Responses HTTP regression (line 2834). POST a user image with h
detail and a paired tool screenshot to mock/grok-4.5; consume the stream and assert
one captured /responses body with unchanged ordered image parts. This is real HTTP
route proof in CI, not real-model OCR or canonical account authentication.
The manual HTTP probe showed that data-only mock Responses frames don't satisfy the
native event-name terminal observer. Add matching `event: response.output_text.delta`
and `event: response.completed` fields to mockDualWireUpstream's existing frames;
preserve all body assertions and require `[DONE]` on the new HTTP cases.

## MODIFY structure/04_transports-and-sidecars.md

Expand Down
39 changes: 39 additions & 0 deletions devlog/_plan/260905_external_image_roundtrip/060_delivery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Full-format closeout

Depends on030/040/050; wp6. No new runtime features. Main owns all external writes.

## Diagnosed CI prerequisite (test-only, no production storage changes)

PR3589 job101236091166 on7783355f9 fails the late-async-spill ordering test before
its overwrite assertions: outer fallback uses real Date.now despite frozen inner
clocks, so its80ms reserve can expire during real cleanup. Images aren't in this
isolated batch. Do not blindly retry or weaken budgets/assertions.

MODIFY only tests/responses/responses-state.test.ts: import existing spyOn and
awaitResponseSpillPublicationTailForTests; after `await started` in this single
late-completion test, capture Date.now and spy it to return that captured value.
Keep all assertions and40ms real drain timer. In finally restore spy FIRST, then
release the blocked writer and await the existing publication-tail barrier.
Do not freeze timers or other deadline/exhaustion tests. Existing superseded flag,
file-identity and replay assertions prove ordering independently of clock progression.
Publish correction on layer2, cascade all own higher branches with explicit leases,
and re-run exact-head CI; no new production clock hook/export. Independent reviewer
must verify scope and teardown. The earlier failed CI is the red evidence.

MODIFY003 audit table with each exact final disposition, test names and CI links;
MODIFY000 continuity with exact commit/PR/reviewer proof. Archive unit _plan -> _fin
only when it describes a public outcome. Tests/code may not be weakened for green CI.

Before each merge: refresh exact head, base, full status rollup, reviewer comments,
worktree identity and source ancestry. Resolve actual failures; never assume flakes.
Document user-authorized admin approval bypass. Merge bottom-up, prefer merge commits,
retain parent branches, retarget child to dev only after parent is public. Verify CI
against the exact child head and current base; restack with lease if necessary. Fetch
origin/dev and prove every merge SHA ancestor. No release, deployment or10100 restart.

Local suites remain prohibited. Inspect and stop actual local Bun suite processes as
authorized, not SSH commands merely mentioning a remote suite, dev servers or the proxy.
Success: c-all fully accounted + unchanged c2 CI/review/merge/ancestry criterion met.
Report remaining native file/remote URL/history limitations honestly, separate from
fixed silent losses. Report original ordinary-image OCR mismatch unproven if no new
evidence establishes its cause. Do not equate model tokens or a mock reply with OCR.
2 changes: 1 addition & 1 deletion structure/04_transports-and-sidecars.md
Original file line number Diff line number Diff line change
Expand Up @@ -1444,7 +1444,7 @@ surface is listed here so a maintainer can find the owner without grepping:
| Adapter execution support | `src/adapters/run-turn-queue.ts`, `src/adapters/tool-catalog-nudge.ts`, `src/adapters/identity.ts`, `src/adapters/image.ts`, `src/adapters/upstream-http-error.ts` | Shared machinery: turn ordering, tool-catalog nudging, client fingerprinting, image conversion, upstream error normalization. |
| Cursor (beyond the sections above) | `src/adapters/cursor/live-transport.ts`, `src/adapters/cursor/http1-bidi.ts`, `src/adapters/cursor/live-models.ts`, `src/adapters/cursor/transport-retry.ts`, `src/adapters/cursor/mcp-manager.ts`, `src/adapters/cursor/thread-continuity.ts`, `src/adapters/cursor/checkpoint-store.ts` | Thread continuity is the point: a retry must not start a new Cursor thread, and a validated checkpoint must not rebuild the full root history. HTTP/2 remains the default; an explicit `http1.1`/`h1` pin maps the bidi run onto Cursor's `RunSSE` receive stream plus sequenced `BidiAppend` sends, and applies to live discovery too. |
| Claude Messages | `src/server/claude-messages.ts` | Routed translation, a native Anthropic passthrough branch, and `count_tokens`. |
| Chat Completions inbound | `src/server/chat-completions.ts`, `src/chat/` | Inbound translation onto the same routing pipeline. |
| Chat Completions inbound | `src/server/chat-completions.ts`, `src/chat/` | Inbound translation onto the same routing pipeline. The content mapper preserves image URLs and supported detail, including screenshot-bearing tool results; target adapters own image placement on their wire. Image-free tool results stay strings. |
| Hosted search relay | `src/server/search.ts` | Direct relay; distinct from the web-search sidecar loop below. |
| Image/video generation loop | `src/images/loop.ts`, `src/images/plan.ts`, `src/images/fulfill.ts`, `src/images/xai-client.ts`, `src/images/xai-video-client.ts`, `src/images/artifacts.ts` | A provider-returned image URL is downloaded into a local artifact once, then served locally; warnings stay URL-free because provider CDN URLs may embed credentials. |
| GitHub Copilot | `src/providers/xai-transport.ts` (`resolveProviderTransport`), `src/providers/github-copilot-transport.ts` | `resolveProviderTransport` selects the Copilot transport when the routed provider name is `github-copilot`; the Copilot module then resolves its headers and base URL, and the registry seeds the provider row and model fallback. |
Expand Down
92 changes: 90 additions & 2 deletions tests/responses/chat-completions-endpoint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,8 @@ function mockDualWireUpstream() {

if (url.pathname.endsWith("/responses")) {
const frames = [
`data: ${JSON.stringify({ type: "response.output_text.delta", delta: "ok" })}\n\n`,
`data: ${JSON.stringify({
`event: response.output_text.delta\ndata: ${JSON.stringify({ type: "response.output_text.delta", delta: "ok" })}\n\n`,
`event: response.completed\ndata: ${JSON.stringify({
type: "response.completed",
response: {
id: "resp_1",
Expand Down Expand Up @@ -2934,6 +2934,94 @@ test("inbound chat-completions honors the override when stripping sampling (#404
}
});

test.each([
{ model: "grok-4.5", pathname: "/v1/responses" },
{ model: "gemini-3-pro", pathname: "/v1/chat/completions" },
])("inbound chat images and paired screenshots survive the $model wire", async ({ model, pathname }) => {
const png = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=";
const screenshot = "https://example.com/tool-screenshot.png";
const { server: upstream, captured } = mockDualWireUpstream();
let server: ReturnType<typeof startServer> | undefined;
try {
saveConfig(dualWireConfig(`${upstream.url.toString().replace(/\/$/, "")}/v1`));
server = startServer(0);
const response = await fetch(new URL("/v1/chat/completions", server.url), {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
model: `mock/${model}`,
stream: true,
// Force the Chat sibling through Responses translation, not native Chat passthrough.
store: true,
messages: [
{ role: "user", content: [
{ type: "text", text: "Inspect this image." },
{ type: "image_url", image_url: { url: png, detail: "high" } },
{ type: "text", text: "Compare it with the screenshot." },
] },
{ role: "assistant", content: null, tool_calls: [
{ id: "call_screenshot", type: "function", function: { name: "screenshot", arguments: "{}" } },
] },
{ role: "tool", tool_call_id: "call_screenshot", content: [
{ type: "text", text: "Before screenshot." },
{ type: "image_url", image_url: { url: screenshot, detail: "low" } },
{ type: "text", text: "After screenshot." },
{ type: "image_url", image_url: { url: png, detail: "auto" } },
] },
],
}),
});
expect(response.status).toBe(200);
expect(await response.text()).toContain("[DONE]");

expect(captured).toHaveLength(1);
expect(captured[0]!.pathname).toBe(pathname);
expect(captured[0]!.body.model).toBe(model);
if (pathname === "/v1/responses") {
expect(captured[0]!.body.input).toEqual([
{ type: "message", role: "user", content: [
{ type: "input_text", text: "Inspect this image." },
{ type: "input_image", image_url: png, detail: "high" },
{ type: "input_text", text: "Compare it with the screenshot." },
] },
{ type: "function_call", call_id: "call_screenshot", name: "screenshot", arguments: "{}" },
{ type: "function_call_output", call_id: "call_screenshot", output: [
{ type: "input_text", text: "Before screenshot." },
{ type: "input_image", image_url: screenshot, detail: "low" },
{ type: "input_text", text: "After screenshot." },
{ type: "input_image", image_url: png, detail: "auto" },
] },
]);
} else {
const messages = captured[0]!.body.messages as Array<Record<string, unknown>>;
const conversation = messages.filter(message => message.role !== "system");
expect(conversation.map(message => message.role)).toEqual(["user", "assistant", "tool", "user"]);
expect(conversation[0]).toEqual({ role: "user", content: [
{ type: "text", text: "Inspect this image." },
{ type: "image_url", image_url: { url: png, detail: "high" } },
{ type: "text", text: "Compare it with the screenshot." },
] });
expect(conversation[1]!.tool_calls).toEqual([
{ id: "call_screenshot", type: "function", function: { name: "screenshot", arguments: "{}" } },
]);
expect(conversation[2]).toEqual({
role: "tool", tool_call_id: "call_screenshot", content: "Before screenshot.After screenshot.",
});
expect(conversation[3]).toEqual({ role: "user", content: [
{ type: "text", text: "[ocx] image output from the preceding tool result(s):" },
{ type: "image_url", image_url: { url: screenshot, detail: "low" } },
{ type: "image_url", image_url: { url: png, detail: "auto" } },
] });
}
} finally {
try {
await server?.stop(true);
} finally {
await upstream.stop(true);
}
}
});

test("/v1/chat/completions non-OK upstream preserves top-level structured cyber_policy type", async () => {
const secret = `blocked by upstream policy Authorization: ${["Bear", "er"].join("")} chathttpsecret123456`;
const safeMessage = "blocked by upstream policy Authorization: Bearer [REDACTED]";
Expand Down
Loading
Loading