Skip to content
Draft
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
3 changes: 0 additions & 3 deletions electron-builder.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,6 @@ export default {
electronVersion,
npmRebuild: false,
buildDependenciesFromSource: false,
extraMetadata: {
main: ".vite/build/bootstrap.js"
},
asar: true,
files: [
{
Expand Down
5 changes: 3 additions & 2 deletions scripts/lib/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const skippedLinuxResourceNames = new Set([
"app.asar",
"app.asar.unpacked",
"codex",
"codex-code-mode-host",
"codex_chronicle",
"cua_node",
"native",
Expand All @@ -44,10 +45,10 @@ const primaryRuntime = {
const codexCliRuntime = {
url:
process.env.CODEX_CLI_RUNTIME_URL ||
"https://github.com/openai/codex/releases/download/rust-v0.140.0/codex-package-x86_64-unknown-linux-musl.tar.gz",
"https://github.com/openai/codex/releases/download/rust-v0.144.1/codex-package-x86_64-unknown-linux-musl.tar.gz",
sha256:
process.env.CODEX_CLI_RUNTIME_SHA256 ||
"9620e798900c6fb289199a9e0a8ed0c3a8cb7e3561048498ebc2dac354a1627b",
"3fd50cf96809b1eea294bbfba0a5c3a576871b4876a1f0e91226e520c1923be1",
codexEntry: "bin/codex"
};

Expand Down
12 changes: 6 additions & 6 deletions scripts/lib/upstream-patches.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,7 @@ function patchDynamicToolStartResponse(source, patch = findDynamicToolStartRespo
const helper = source.includes(dynamicToolStartResponseMarker)
? ""
: [
`function ${dynamicToolStartResponseMarker}(e){if(!Array.isArray(e))return[];return e.map(e=>{if(e?.type!==\`namespace\`||!Array.isArray(e.tools))return e;let t=e.tools.flatMap(e=>{if(e?.type!==\`function\`)return[e];if(e.inputSchema!=null)return[e];if(e.input_schema!=null)return[{...e,inputSchema:e.input_schema}];if(e.parameters!=null)return[{...e,inputSchema:e.parameters}];return[]});return{...e,tools:t}})}`
`function ${dynamicToolStartResponseMarker}(e){return __codexLinuxCanonicalDynamicTools(e)}function __codexLinuxCanonicalDynamicToolFunction(e){if(e==null||typeof e!==\`object\`||typeof e.name!==\`string\`||e.type!=null&&e.type!==\`function\`)return null;let t=e.inputSchema??e.input_schema??e.parameters;if(t==null)return null;let n=e.deferLoading??(e.exposeToContext==null?!1:!e.exposeToContext);return{type:\`function\`,name:e.name,description:typeof e.description===\`string\`?e.description:\`\`,inputSchema:t,...n?{deferLoading:!0}:{}}}function __codexLinuxCanonicalDynamicTools(e){if(!Array.isArray(e))return[];let t=[],n=new Map,r=(e,o=null,i=\`\`)=>{if(e?.type===\`namespace\`&&Array.isArray(e.tools)){let a=typeof e.name===\`string\`?e.name:o;if(a==null)return;for(let t of e.tools)r(t,a,typeof e.description===\`string\`?e.description:\`\`);return}let a=__codexLinuxCanonicalDynamicToolFunction(e);if(a==null)return;let s=o??(typeof e.namespace===\`string\`?e.namespace:null);if(s==null){t.push(a);return}let l=n.get(s);if(l==null){l={type:\`namespace\`,name:s,description:i,tools:[]},n.set(s,l),t.push(l)}l.tools.push(a)};for(let n of e)r(n);return t}`
].join("");
const replacements = patch.patches.map(target => {
const argumentSource = source.slice(target.argumentStart, target.argumentEnd);
Expand Down Expand Up @@ -675,9 +675,9 @@ function patchDynamicToolThreadStartBridge(source, patch = findDynamicToolThread
const helper = source.includes(dynamicToolThreadStartBridgeMarker)
? ""
: [
`function ${dynamicToolThreadStartBridgeMarker}(e){if(e?.method!==\`thread/start\`||e.params==null)return e;let t=__codexLinuxNormalizeThreadStartBridgeValue(e.params);return{...e,params:{...t,...Array.isArray(t.dynamicTools)?{dynamicTools:t.dynamicTools.flatMap(e=>__codexLinuxNormalizeThreadStartBridgeDynamicTool(e))}:{}}}}`,
`function __codexLinuxNormalizeThreadStartBridgeValue(e){if(Array.isArray(e))return e.map(__codexLinuxNormalizeThreadStartBridgeValue);if(e==null||typeof e!==\`object\`)return e;let t={...e};for(let n of Object.keys(t))t[n]=__codexLinuxNormalizeThreadStartBridgeValue(t[n]);if(t.inputSchema==null){if(t.input_schema!=null)t.inputSchema=t.input_schema;else if(t.parameters!=null)t.inputSchema=t.parameters;else if(t.type===\`function\`)t.inputSchema={type:\`object\`,properties:{},additionalProperties:!1}}return t}`,
`function __codexLinuxNormalizeThreadStartBridgeDynamicTool(e,t=null){e=__codexLinuxNormalizeThreadStartBridgeValue(e);if(e==null||typeof e!==\`object\`)return[];if(e.type===\`namespace\`&&Array.isArray(e.tools)){let n=typeof e.name===\`string\`?e.name:t;return e.tools.flatMap(e=>__codexLinuxNormalizeThreadStartBridgeDynamicTool(e,n))}let n=t==null?e:{...e,namespace:e.namespace??t};if(n.type==null&&typeof n.name===\`string\`)return[{...n,type:\`function\`,inputSchema:n.inputSchema??{type:\`object\`,properties:{},additionalProperties:!1}}];return n.type===\`function\`?[n]:[]}`
`function ${dynamicToolThreadStartBridgeMarker}(e){if(e?.method!==\`thread/start\`||e.params==null||!Array.isArray(e.params.dynamicTools))return e;return{...e,params:{...e.params,dynamicTools:__codexLinuxCanonicalThreadStartBridgeDynamicTools(e.params.dynamicTools)}}}`,
`function __codexLinuxCanonicalThreadStartBridgeFunction(e){if(e==null||typeof e!==\`object\`||typeof e.name!==\`string\`||e.type!=null&&e.type!==\`function\`)return null;let t=e.inputSchema??e.input_schema??e.parameters??{type:\`object\`,properties:{},additionalProperties:!1},n=e.deferLoading??(e.exposeToContext==null?!1:!e.exposeToContext);return{type:\`function\`,name:e.name,description:typeof e.description===\`string\`?e.description:\`\`,inputSchema:t,...n?{deferLoading:!0}:{}}}`,
`function __codexLinuxCanonicalThreadStartBridgeDynamicTools(e){let t=[],n=new Map,r=(e,o=null,i=\`\`)=>{if(e?.type===\`namespace\`&&Array.isArray(e.tools)){let a=typeof e.name===\`string\`?e.name:o;if(a==null)return;for(let t of e.tools)r(t,a,typeof e.description===\`string\`?e.description:\`\`);return}let a=__codexLinuxCanonicalThreadStartBridgeFunction(e);if(a==null)return;let s=o??(typeof e.namespace===\`string\`?e.namespace:null);if(s==null){t.push(a);return}let l=n.get(s);if(l==null){l={type:\`namespace\`,name:s,description:i,tools:[]},n.set(s,l),t.push(l)}l.tools.push(a)};for(let t of e)r(t);return t}`
].join("");
const replacements = patch.patches.map(target => {
const requestSource = source.slice(target.requestStart, target.requestEnd);
Expand Down Expand Up @@ -824,7 +824,7 @@ function patchDynamicToolThreadStartRequest(source, patch = findDynamicToolThrea
const helper = source.includes(dynamicToolThreadStartRequestMarker)
? ""
: [
`function ${dynamicToolThreadStartRequestMarker}(e){if(e==null||!Array.isArray(e.dynamicTools))return e;return{...e,dynamicTools:e.dynamicTools.flatMap(e=>__codexLinuxNormalizeThreadStartRequestDynamicTool(e))}}function __codexLinuxNormalizeThreadStartRequestDynamicTool(e,t=null){if(e==null||typeof e!==\`object\`)return[];if(e.type===\`namespace\`&&Array.isArray(e.tools)){let n=typeof e.name===\`string\`?e.name:t;return e.tools.flatMap(e=>__codexLinuxNormalizeThreadStartRequestDynamicTool(e,n))}let n=t==null?e:{...e,namespace:e.namespace??t};if(n.type==null&&typeof n.name===\`string\`)return[{...n,type:\`function\`,inputSchema:n.inputSchema??n.input_schema??n.parameters??{type:\`object\`,properties:{},additionalProperties:!1}}];if(n.type!==\`function\`)return[];if(n.inputSchema!=null)return[n];if(n.input_schema!=null)return[{...n,inputSchema:n.input_schema}];if(n.parameters!=null)return[{...n,inputSchema:n.parameters}];return[{...n,inputSchema:{type:\`object\`,properties:{},additionalProperties:!1}}]}`
`function ${dynamicToolThreadStartRequestMarker}(e){if(e==null||!Array.isArray(e.dynamicTools))return e;return{...e,dynamicTools:__codexLinuxCanonicalThreadStartRequestDynamicTools(e.dynamicTools)}}function __codexLinuxCanonicalThreadStartRequestFunction(e){if(e==null||typeof e!==\`object\`||typeof e.name!==\`string\`||e.type!=null&&e.type!==\`function\`)return null;let t=e.inputSchema??e.input_schema??e.parameters??{type:\`object\`,properties:{},additionalProperties:!1},n=e.deferLoading??(e.exposeToContext==null?!1:!e.exposeToContext);return{type:\`function\`,name:e.name,description:typeof e.description===\`string\`?e.description:\`\`,inputSchema:t,...n?{deferLoading:!0}:{}}}function __codexLinuxCanonicalThreadStartRequestDynamicTools(e){let t=[],n=new Map,r=(e,o=null,i=\`\`)=>{if(e?.type===\`namespace\`&&Array.isArray(e.tools)){let a=typeof e.name===\`string\`?e.name:o;if(a==null)return;for(let t of e.tools)r(t,a,typeof e.description===\`string\`?e.description:\`\`);return}let a=__codexLinuxCanonicalThreadStartRequestFunction(e);if(a==null)return;let s=o??(typeof e.namespace===\`string\`?e.namespace:null);if(s==null){t.push(a);return}let l=n.get(s);if(l==null){l={type:\`namespace\`,name:s,description:i,tools:[]},n.set(s,l),t.push(l)}l.tools.push(a)};for(let t of e)r(t);return t}`
].join("");
const replacements = patch.patches.map(target => {
const paramsSource = source.slice(target.paramsStart, target.paramsEnd);
Expand Down Expand Up @@ -993,7 +993,7 @@ function patchDynamicToolSchemaContract(source, patch = findDynamicToolSchemaCon
const helper = source.includes(dynamicToolSchemaContractMarker)
? ""
: [
`function ${dynamicToolSchemaContractMarker}(e){if(e==null||e.type!==\`function\`)return e;if(e.inputSchema!=null)return e;if(e.input_schema!=null)return{...e,inputSchema:e.input_schema};if(e.parameters!=null)return{...e,inputSchema:e.parameters};return null}`
`function ${dynamicToolSchemaContractMarker}(e){if(e==null||e.type!==\`function\`||typeof e.name!==\`string\`)return null;let t=e.inputSchema??e.input_schema??e.parameters;if(t==null)return null;return{type:\`function\`,name:e.name,description:typeof e.description===\`string\`?e.description:\`\`,inputSchema:t,...e.deferLoading?{deferLoading:!0}:{}}}`
].join("");
const replacements = [];

Expand Down
2 changes: 2 additions & 0 deletions test/build.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ test("stagePackagedResources preserves Linux-safe upstream resources", async ()
"mach-o-arm64"
);
await fs.writeFile(path.join(resourcesDir, "codex"), "darwin-codex");
await fs.writeFile(path.join(resourcesDir, "codex-code-mode-host"), "darwin-code-mode-host");
await fs.mkdir(path.join(resourcesDir, "cua_node", "bin"), { recursive: true });
await fs.writeFile(path.join(resourcesDir, "cua_node", "bin", "node_repl"), "darwin-node-repl");
await fs.writeFile(path.join(resourcesDir, "node"), "darwin-node");
Expand All @@ -104,6 +105,7 @@ test("stagePackagedResources preserves Linux-safe upstream resources", async ()
await assert.rejects(fs.access(path.join(targetDir, "app.asar")));
await assert.rejects(fs.access(path.join(targetDir, "app.asar.unpacked")));
await assert.rejects(fs.access(path.join(targetDir, "codex")));
await assert.rejects(fs.access(path.join(targetDir, "codex-code-mode-host")));
await assert.rejects(fs.access(path.join(targetDir, "cua_node")));
await assert.rejects(fs.access(path.join(targetDir, "node")));
await assert.rejects(fs.access(path.join(targetDir, "rg")));
Expand Down
16 changes: 16 additions & 0 deletions test/electron-builder-config.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";

const __dirname = path.dirname(fileURLToPath(import.meta.url));

test("electron-builder preserves the upstream package entry point", async () => {
const config = await fs.readFile(
path.join(__dirname, "..", "electron-builder.config.mjs"),
"utf8"
);

assert.doesNotMatch(config, /extraMetadata\s*:\s*\{\s*main\s*:/);
});
51 changes: 37 additions & 14 deletions test/upstream-patches.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,25 @@ const openTargetResolverSource =
"function W(e){let t=which.default.sync(e,{nothrow:!0});return typeof t==`string`&&fs.existsSync(t)?t:null}";
const withOpenTargetResolver = parts => [openTargetResolverSource, ...parts].join(";");

function assertCanonicalDynamicTools(tools) {
for (const tool of tools) {
assert.equal(typeof tool.description, "string");
assert.equal("namespace" in tool, false);
assert.equal("exposeToContext" in tool, false);
assert.equal("input_schema" in tool, false);
assert.equal("parameters" in tool, false);

if (tool.type === "namespace") {
assert.equal(Array.isArray(tool.tools), true);
assertCanonicalDynamicTools(tool.tools);
continue;
}

assert.equal(tool.type, "function");
assert.equal(tool.inputSchema?.type, "object");
}
}

test("patchLinuxOpenTargetsSource adds Linux editor targets and exposes app paths", () => {
const source = withOpenTargetResolver([
"prefix",
Expand Down Expand Up @@ -461,11 +480,15 @@ test("patchDynamicToolStartResponseSource normalizes desktop thread-start dynami

vm.runInNewContext(patched, context);

const tools = context.globalThis.result[0].tools;
const dynamicTools = context.globalThis.result;
const tools = dynamicTools[0].tools;

assert.equal(hasUnguardedDynamicToolStartResponseSource(patched), false);
assert.equal(JSON.stringify(tools.map(tool => tool.name)), JSON.stringify(["good", "snake", "params", "untouched"]));
assert.equal(dynamicTools[0].type, "namespace");
assert.equal(dynamicTools[0].name, "codex_app");
assert.equal(JSON.stringify(tools.map(tool => tool.name)), JSON.stringify(["good", "snake", "params"]));
assert.equal(tools.filter(tool => tool.type === "function").every(tool => tool.inputSchema?.type === "object"), true);
assertCanonicalDynamicTools(dynamicTools);
});

test("patchDynamicToolStartResponseSource is idempotent", () => {
Expand Down Expand Up @@ -497,15 +520,14 @@ test("patchDynamicToolThreadStartBridgeSource normalizes final Electron bridge r
await vm.runInNewContext(`(async()=>{${patched}})()`, context);

const [normal, prewarm] = context.globalThis.bridge.requests;
const tools = normal.params.dynamicTools;
const dynamicTools = normal.params.dynamicTools;
const tools = dynamicTools[0].tools;

assert.equal(hasUnguardedDynamicToolThreadStartBridgeSource(patched), false);
assert.equal(JSON.stringify(tools.map(tool => tool.name)), JSON.stringify(["good", "plainSnake", "plainParams", "bad", "top", "topBad"]));
assert.equal(tools.every(tool => tool.type === "function"), true);
assert.equal(tools.every(tool => tool.inputSchema?.type === "object"), true);
assert.equal(tools.slice(0, 4).every(tool => tool.namespace === "codex_app"), true);
assert.equal(tools.slice(4).every(tool => tool.namespace == null), true);
assert.deepEqual(prewarm.params.dynamicTools, tools);
assert.equal(JSON.stringify(tools.map(tool => tool.name)), JSON.stringify(["good", "plainSnake", "plainParams", "bad"]));
assert.equal(JSON.stringify(dynamicTools.slice(1).map(tool => tool.name)), JSON.stringify(["top", "topBad"]));
assertCanonicalDynamicTools(dynamicTools);
assert.deepEqual(prewarm.params.dynamicTools, dynamicTools);
});

test("patchDynamicToolThreadStartBridgeSource is idempotent", () => {
Expand Down Expand Up @@ -539,12 +561,14 @@ test("patchDynamicToolSchemaContractSource normalizes thread-start dynamic tool

await vm.runInNewContext(`(async()=>{${patched}})()`, context);

const tools = context.globalThis.result[0].tools;
const dynamicTools = context.globalThis.result;
const tools = dynamicTools[0].tools;

assert.equal(hasUnguardedDynamicToolSchemaContractSource(patched), false);
assert.equal(JSON.stringify(tools.map(tool => tool.name)), JSON.stringify(["good", "snake", "parameters"]));
assert.equal(tools.every(tool => tool.inputSchema?.type === "object"), true);
assert.equal(tools.every(tool => tool.deferLoading === true), true);
assertCanonicalDynamicTools(dynamicTools);
});

test("patchDynamicToolThreadStartRequestSource normalizes final thread/start params", () => {
Expand All @@ -563,13 +587,12 @@ test("patchDynamicToolThreadStartRequestSource normalizes final thread/start par

vm.runInNewContext(patched, context);

const tools = context.globalThis.sent.payload.request.params.dynamicTools;
const dynamicTools = context.globalThis.sent.payload.request.params.dynamicTools;
const tools = dynamicTools[0].tools;

assert.equal(hasUnguardedDynamicToolThreadStartRequestSource(patched), false);
assert.equal(JSON.stringify(tools.map(tool => tool.name)), JSON.stringify(["good", "snake", "params", "bad"]));
assert.equal(tools.every(tool => tool.type === "function"), true);
assert.equal(tools.every(tool => tool.inputSchema?.type === "object"), true);
assert.equal(tools.every(tool => tool.namespace === "codex_app"), true);
assertCanonicalDynamicTools(dynamicTools);
});

test("patchDynamicToolThreadStartRequestSource is idempotent", () => {
Expand Down