-
Notifications
You must be signed in to change notification settings - Fork 1.3k
fix: integrate twelve open bug and compatibility PRs (sweep 260926) #5858
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
fd0ee5b
3ebf0fc
32f1de9
678c616
9672451
6199d37
107a258
516a4dc
f315bb1
0fb3dcb
acbb1f7
9130e00
e14dd7a
062ee79
e72ea73
b5426cc
62c3464
7acec81
6272f76
6243e37
9c5d964
b5617f7
e091bc8
7c6e1f6
7d4b492
f19ef3d
22c0c1a
e830d8a
076d886
125a605
4449334
9d5e7a3
76a52cb
2bda696
5468a9d
519f1c7
bd0f03e
11484c8
3a42ceb
ca685e6
a5e791f
78a1cd1
7e70729
43345e3
f679936
b603a5c
3ed77e9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| import { createHash } from "node:crypto"; | ||
| import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; | ||
| import { join, resolve } from "node:path"; | ||
| import { join, resolve, basename } from "node:path"; | ||
| import { isStandaloneTarget, standaloneExecutableName } from "./standalone-targets"; | ||
|
|
||
| function hostTarget(): string { | ||
|
|
@@ -28,17 +28,61 @@ if (!existsSync(join(guiDist, "index.html"))) { | |
| const output = resolve(argumentValue("--out") ?? join(repoRoot, "dist", "standalone", target)); | ||
| mkdirSync(output, { recursive: true }); | ||
| const executable = join(output, standaloneExecutableName(target)); | ||
| const result = Bun.spawnSync([ | ||
| process.execPath, | ||
| "build", | ||
| "--compile", | ||
| "--target", | ||
| target, | ||
| join(repoRoot, "src", "cli", "index.ts"), | ||
| "--outfile", | ||
| executable, | ||
| ], { stdout: "inherit", stderr: "inherit" }); | ||
| if (result.exitCode !== 0) process.exit(result.exitCode); | ||
|
|
||
| // Pre-bundle worker entrypoints so compiled binaries can spawn them from Blob | ||
| // URLs: oven-sh/bun#29124 breaks nested worker entrypoints resolved from | ||
| // $bunfs, so `new Worker(new URL(...))` dies with ModuleNotFound otherwise. | ||
| const WORKER_ENTRIES: Record<string, string> = { | ||
| "policy-worker": join(repoRoot, "src", "storage", "policy-worker.ts"), | ||
| "restore-worker": join(repoRoot, "src", "storage", "restore-worker.ts"), | ||
| "history-worker": join(repoRoot, "src", "codex", "history-worker.ts"), | ||
| }; | ||
| const GEN_FILE = join(repoRoot, "src", "generated", "worker-bundles.gen.ts"); | ||
| const GEN_PLACEHOLDER = `// Generated by scripts/build-standalone.ts — do not edit by hand.\n// Placeholder for source checkouts; standalone builds overwrite this file\n// with pre-bundled worker sources before compiling.\nexport const WORKER_BUNDLES: Record<string, string> = {};\n`; | ||
| const bundleLines: string[] = []; | ||
| const workerOut = join(output, ".worker-bundles"); | ||
| for (const [key, entry] of Object.entries(WORKER_ENTRIES)) { | ||
| const bundled = Bun.spawnSync([process.execPath, "build", entry, "--target", "bun", "--outdir", workerOut], { | ||
| stdout: "inherit", | ||
| stderr: "inherit", | ||
| }); | ||
| if (bundled.exitCode !== 0) process.exit(bundled.exitCode ?? 1); | ||
| const name = `${basename(entry, ".ts")}.js`; | ||
| bundleLines.push(` ${JSON.stringify(key)}: ${JSON.stringify(readFileSync(join(workerOut, name), "utf8"))},`); | ||
| } | ||
| writeFileSync( | ||
| GEN_FILE, | ||
| `// Generated by scripts/build-standalone.ts — do not edit by hand.\nexport const WORKER_BUNDLES: Record<string, string> = {\n${bundleLines.join("\n")}\n};\n`, | ||
| ); | ||
|
|
||
| // The generated bundles only exist while the compile below consumes them. | ||
| // Always restore the empty placeholder afterwards — success or failure — so | ||
| // source-checkout runs and tests keep spawning workers from source files | ||
| // instead of a stale committed bundle. | ||
| function restoreGenPlaceholder(): void { | ||
| writeFileSync(GEN_FILE, GEN_PLACEHOLDER); | ||
| } | ||
|
|
||
| const compileArgs = [process.execPath, "build", "--compile"]; | ||
| // Cross-compiling to the host target produces a binary the kernel kills on | ||
| // launch; only pass --target when it differs from the host. | ||
| if (target !== hostTarget()) compileArgs.push("--target", target); | ||
| compileArgs.push(join(repoRoot, "src", "cli", "index.ts"), "--outfile", executable); | ||
| // process.exit() skips `finally`, so exit only after the placeholder is back. | ||
| let compileExitCode: number; | ||
| try { | ||
| compileExitCode = Bun.spawnSync(compileArgs, { stdout: "inherit", stderr: "inherit" }).exitCode ?? 1; | ||
| } finally { | ||
| restoreGenPlaceholder(); | ||
| } | ||
| if (compileExitCode !== 0) process.exit(compileExitCode); | ||
|
|
||
| // bun's ad-hoc linker signature does not always cover the embedded payload; | ||
| // macOS kills the executable on launch (SIGKILL) unless it is re-signed. | ||
| if (process.platform === "darwin") { | ||
| const sign = Bun.spawnSync(["codesign", "--force", "--sign", "-", executable], { stdout: "inherit", stderr: "inherit" }); | ||
|
Comment on lines
+82
to
+83
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Sign only macOS-target executables. If a macOS host builds with 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| if (sign.exitCode !== 0) process.exit(sign.exitCode ?? 1); | ||
| } | ||
|
|
||
| cpSync(guiDist, join(output, "gui", "dist"), { recursive: true }); | ||
| const digest = createHash("sha256").update(readFileSync(executable)).digest("hex"); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -211,6 +211,27 @@ function toolCallsToItems( | |||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| function legacyFunctionCallToItem( | ||||||||||||||||||||||||||||
| value: unknown, | ||||||||||||||||||||||||||||
| input: Rec[], | ||||||||||||||||||||||||||||
| knownNameByCallId: Map<string, string>, | ||||||||||||||||||||||||||||
| awaitingToolResult: Set<string>, | ||||||||||||||||||||||||||||
| sequence: number, | ||||||||||||||||||||||||||||
| ): { callId: string; name: string } | null { | ||||||||||||||||||||||||||||
| if (value === undefined) return null; | ||||||||||||||||||||||||||||
| if (!isRec(value) || typeof value.name !== "string" || value.name.length === 0) { | ||||||||||||||||||||||||||||
| throw new ChatCompletionsRequestError("assistant function_call requires a name"); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| const args = typeof value.arguments === "string" | ||||||||||||||||||||||||||||
| ? value.arguments | ||||||||||||||||||||||||||||
| : JSON.stringify(value.arguments ?? {}); | ||||||||||||||||||||||||||||
| const callId = `call_legacy_${String(sequence).padStart(4, "0")}`; | ||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Keep synthetic call IDs distinct from client-supplied call IDs. A client can supply a modern 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| knownNameByCallId.set(callId, value.name); | ||||||||||||||||||||||||||||
| awaitingToolResult.add(callId); | ||||||||||||||||||||||||||||
| input.push({ type: "function_call", call_id: callId, name: value.name, arguments: args }); | ||||||||||||||||||||||||||||
| return { callId, name: value.name }; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| function toolsToResponses(tools: unknown): Rec[] | undefined { | ||||||||||||||||||||||||||||
| if (!Array.isArray(tools) || tools.length === 0) return undefined; | ||||||||||||||||||||||||||||
| const out: Rec[] = []; | ||||||||||||||||||||||||||||
|
|
@@ -243,6 +264,24 @@ function toolsToResponses(tools: unknown): Rec[] | undefined { | |||||||||||||||||||||||||||
| return out.length > 0 ? out : undefined; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| function legacyFunctionsToResponses(functions: unknown): Rec[] | undefined { | ||||||||||||||||||||||||||||
| if (functions === undefined) return undefined; | ||||||||||||||||||||||||||||
| if (!Array.isArray(functions)) throw new ChatCompletionsRequestError("functions must be an array"); | ||||||||||||||||||||||||||||
| const out: Rec[] = []; | ||||||||||||||||||||||||||||
| for (const raw of functions) { | ||||||||||||||||||||||||||||
| if (!isRec(raw) || typeof raw.name !== "string" || raw.name.length === 0) { | ||||||||||||||||||||||||||||
| throw new ChatCompletionsRequestError("functions entries require a name"); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| out.push({ | ||||||||||||||||||||||||||||
| type: "function", | ||||||||||||||||||||||||||||
| name: raw.name, | ||||||||||||||||||||||||||||
| ...(typeof raw.description === "string" ? { description: raw.description } : {}), | ||||||||||||||||||||||||||||
| ...(isRec(raw.parameters) ? { parameters: raw.parameters } : {}), | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
|
Comment on lines
+275
to
+280
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Preserve non-strict semantics for legacy function declarations. Legacy Chat functions are non-strict by default. Responses can normalize a function schema when Proposed change out.push({
type: "function",
name: raw.name,
+ strict: false,
...(typeof raw.description === "string" ? { description: raw.description } : {}),
...(isRec(raw.parameters) ? { parameters: raw.parameters } : {}),
});📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| return out.length > 0 ? out : undefined; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| function toolChoiceToResponses(choice: unknown, body: Rec): void { | ||||||||||||||||||||||||||||
| if (choice === undefined || choice === null) return; | ||||||||||||||||||||||||||||
| if (choice === "auto" || choice === "none" || choice === "required") { | ||||||||||||||||||||||||||||
|
|
@@ -269,6 +308,18 @@ function toolChoiceToResponses(choice: unknown, body: Rec): void { | |||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| function legacyFunctionChoiceToResponses(choice: unknown, body: Rec): void { | ||||||||||||||||||||||||||||
| if (choice === undefined || choice === null) return; | ||||||||||||||||||||||||||||
| if (choice === "auto" || choice === "none") { | ||||||||||||||||||||||||||||
| body.tool_choice = choice; | ||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| if (!isRec(choice) || typeof choice.name !== "string" || choice.name.length === 0) { | ||||||||||||||||||||||||||||
| throw new ChatCompletionsRequestError("function_call requires auto, none, or a function name"); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| body.tool_choice = { type: "function", name: choice.name }; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||
| * Chat Completions nests the subset under `allowed_tools`, Responses carries `mode`/`tools` | ||||||||||||||||||||||||||||
| * on the choice itself, and each entry names its tool under a member keyed by its own type | ||||||||||||||||||||||||||||
|
|
@@ -389,6 +440,8 @@ export function chatCompletionsToResponsesBody(raw: unknown): Rec { | |||||||||||||||||||||||||||
| // Recover replace-style tool calls incrementally instead of rebuilding the | ||||||||||||||||||||||||||||
| // call-id index from the entire translated transcript for every message. | ||||||||||||||||||||||||||||
| const knownNameByCallId = new Map<string, string>(); | ||||||||||||||||||||||||||||
| const legacyAwaiting: Array<{ callId: string; name: string }> = []; | ||||||||||||||||||||||||||||
| let legacyCallSequence = 0; | ||||||||||||||||||||||||||||
| // Tool calls whose result has not arrived yet. Several adapters need a call and its output | ||||||||||||||||||||||||||||
| // to stay adjacent — Kiro refuses an interrupted pair (src/adapters/kiro/payload.ts) and the | ||||||||||||||||||||||||||||
| // Anthropic and Google mappers synthesize a missing result — so an instruction that arrives | ||||||||||||||||||||||||||||
|
|
@@ -405,6 +458,7 @@ export function chatCompletionsToResponsesBody(raw: unknown): Rec { | |||||||||||||||||||||||||||
| const beginConversationTurn = (): void => { | ||||||||||||||||||||||||||||
| releaseHeldInstructions(); | ||||||||||||||||||||||||||||
| awaitingToolResult.clear(); | ||||||||||||||||||||||||||||
| legacyAwaiting.length = 0; | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| for (const msg of raw.messages) { | ||||||||||||||||||||||||||||
|
|
@@ -462,6 +516,16 @@ export function chatCompletionsToResponsesBody(raw: unknown): Rec { | |||||||||||||||||||||||||||
| if (msg.tool_calls !== undefined) { | ||||||||||||||||||||||||||||
| toolCallsToItems(msg.tool_calls, input, knownNameByCallId, awaitingToolResult); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| if (msg.function_call !== undefined && msg.function_call !== null) { | ||||||||||||||||||||||||||||
| const call = legacyFunctionCallToItem( | ||||||||||||||||||||||||||||
| msg.function_call, | ||||||||||||||||||||||||||||
| input, | ||||||||||||||||||||||||||||
| knownNameByCallId, | ||||||||||||||||||||||||||||
| awaitingToolResult, | ||||||||||||||||||||||||||||
| ++legacyCallSequence, | ||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||
| if (call) legacyAwaiting.push(call); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| case "function": { | ||||||||||||||||||||||||||||
|
|
@@ -472,6 +536,17 @@ export function chatCompletionsToResponsesBody(raw: unknown): Rec { | |||||||||||||||||||||||||||
| "Legacy function-result image translation is not implemented. Use tool_calls and role:tool with tool_call_id.", | ||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| const name = typeof msg.name === "string" ? msg.name : ""; | ||||||||||||||||||||||||||||
| if (!name) throw new ChatCompletionsRequestError("function messages require a name"); | ||||||||||||||||||||||||||||
| const pendingIndex = legacyAwaiting.findIndex(call => call.name === name); | ||||||||||||||||||||||||||||
| if (pendingIndex < 0) { | ||||||||||||||||||||||||||||
| throw new ChatCompletionsRequestError(`function result has no pending call named ${name}`); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| const [call] = legacyAwaiting.splice(pendingIndex, 1); | ||||||||||||||||||||||||||||
| const output = contentToText(msg.content); | ||||||||||||||||||||||||||||
| input.push({ type: "function_call_output", call_id: call!.callId, output }); | ||||||||||||||||||||||||||||
| awaitingToolResult.delete(call!.callId); | ||||||||||||||||||||||||||||
| if (awaitingToolResult.size === 0) releaseHeldInstructions(); | ||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| case "tool": { | ||||||||||||||||||||||||||||
|
|
@@ -507,9 +582,13 @@ export function chatCompletionsToResponsesBody(raw: unknown): Rec { | |||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if (systemParts.length > 0) body.instructions = systemParts.join("\n\n"); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const tools = toolsToResponses(raw.tools); | ||||||||||||||||||||||||||||
| if (tools) body.tools = tools; | ||||||||||||||||||||||||||||
| toolChoiceToResponses(raw.tool_choice, body); | ||||||||||||||||||||||||||||
| const tools = [ | ||||||||||||||||||||||||||||
| ...(toolsToResponses(raw.tools) ?? []), | ||||||||||||||||||||||||||||
| ...(legacyFunctionsToResponses(raw.functions) ?? []), | ||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||
| if (tools.length > 0) body.tools = tools; | ||||||||||||||||||||||||||||
| if (raw.tool_choice !== undefined) toolChoiceToResponses(raw.tool_choice, body); | ||||||||||||||||||||||||||||
| else legacyFunctionChoiceToResponses(raw.function_call, body); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const maxTokens = typeof raw.max_completion_tokens === "number" | ||||||||||||||||||||||||||||
| ? raw.max_completion_tokens | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Isolate the generated worker file for concurrent builds.
If two targets build from one checkout at the same time, both invocations write
src/generated/worker-bundles.gen.ts. One invocation can replace or clear that file while the other invocation compiles it. The resulting executable can contain the wrong worker bundles or the empty placeholder. Serialize these builds or give each build an isolated generated input; an atomic write alone does not prevent the cross-build race. As per coding guidelines, scripts must use “deterministic inputs.”🤖 Prompt for AI Agents
Source: Coding guidelines