@velum-labs/routekit-tool-codex 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +20 -0
- package/dist/driver.d.ts +25 -0
- package/dist/driver.js +436 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +40 -0
- package/dist/install.d.ts +35 -0
- package/dist/install.js +185 -0
- package/dist/launch.d.ts +32 -0
- package/dist/launch.js +302 -0
- package/dist/test/driver.test.d.ts +1 -0
- package/dist/test/driver.test.js +115 -0
- package/dist/test/install.test.d.ts +1 -0
- package/dist/test/install.test.js +65 -0
- package/dist/test/launch.test.d.ts +1 -0
- package/dist/test/launch.test.js +200 -0
- package/package.json +49 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { chmodSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { test } from "node:test";
|
|
6
|
+
import { driverContractSuite } from "@velum-labs/routekit-harness-core/testing";
|
|
7
|
+
import { createCodexDriver } from "../driver.js";
|
|
8
|
+
/**
|
|
9
|
+
* A fake `codex` CLI: honors `--version`, and for `exec --json`
|
|
10
|
+
* reads the prompt from stdin and emits the JSONL event stream the codex-sdk
|
|
11
|
+
* parses (thread.started, turn.started, item, turn.completed). `resume <id>`
|
|
12
|
+
* reuses the given thread id so resume round-trips are observable.
|
|
13
|
+
*/
|
|
14
|
+
const FAKE_CODEX_CLI = `#!/usr/bin/env node
|
|
15
|
+
const args = process.argv.slice(2);
|
|
16
|
+
if (args[0] === "--version") { console.log("codex-cli 0.145.0"); process.exit(0); }
|
|
17
|
+
const resumeIdx = args.indexOf("resume");
|
|
18
|
+
const threadId = resumeIdx >= 0 ? args[resumeIdx + 1] : "thread_fake_1";
|
|
19
|
+
let input = "";
|
|
20
|
+
process.stdin.on("data", (c) => (input += c));
|
|
21
|
+
process.stdin.on("end", () => {
|
|
22
|
+
const emit = (obj) => process.stdout.write(JSON.stringify(obj) + "\\n");
|
|
23
|
+
emit({ type: "thread.started", thread_id: threadId });
|
|
24
|
+
emit({ type: "turn.started" });
|
|
25
|
+
emit({ type: "item.started", item: { id: "i1", type: "agent_message", text: "" } });
|
|
26
|
+
emit({ type: "item.completed", item: { id: "i1", type: "agent_message", text: "ARGS: " + args.join(" ") + "\\nOK: " + input.trim() } });
|
|
27
|
+
emit({ type: "turn.completed", usage: { input_tokens: 3, cached_input_tokens: 0, output_tokens: 2, reasoning_output_tokens: 0 } });
|
|
28
|
+
process.exit(0);
|
|
29
|
+
});
|
|
30
|
+
`;
|
|
31
|
+
function fakeCodexRepo() {
|
|
32
|
+
const dir = mkdtempSync(join(tmpdir(), "codex-driver-"));
|
|
33
|
+
const command = join(dir, "codex-fake.mjs");
|
|
34
|
+
writeFileSync(command, FAKE_CODEX_CLI);
|
|
35
|
+
chmodSync(command, 0o755);
|
|
36
|
+
return { command, cwd: dir, cleanup: () => rmSync(dir, { recursive: true, force: true }) };
|
|
37
|
+
}
|
|
38
|
+
const repo = fakeCodexRepo();
|
|
39
|
+
driverContractSuite({
|
|
40
|
+
name: "codex driver",
|
|
41
|
+
createInstance: async () => {
|
|
42
|
+
const driver = createCodexDriver();
|
|
43
|
+
const config = driver.configSchema.parse({ command: repo.command });
|
|
44
|
+
return driver.createInstance(config);
|
|
45
|
+
},
|
|
46
|
+
startOptions: () => ({ cwd: repo.cwd, model: "gpt-5.1-codex" }),
|
|
47
|
+
supportsResume: true,
|
|
48
|
+
turnTimeoutMs: 15_000
|
|
49
|
+
});
|
|
50
|
+
test("codex driver maps the CLI event stream into canonical events", async () => {
|
|
51
|
+
const driver = createCodexDriver();
|
|
52
|
+
const instance = await driver.createInstance(driver.configSchema.parse({ command: repo.command }));
|
|
53
|
+
try {
|
|
54
|
+
const session = await instance.startSession({ cwd: repo.cwd });
|
|
55
|
+
const events = [];
|
|
56
|
+
for await (const event of session.sendTurn({ prompt: "hello codex" })) {
|
|
57
|
+
events.push(event);
|
|
58
|
+
}
|
|
59
|
+
const types = events.map((event) => event.type);
|
|
60
|
+
assert.ok(types.includes("session.started"));
|
|
61
|
+
assert.ok(types.includes("turn.started"));
|
|
62
|
+
const delta = events.find((event) => event.type === "content.delta");
|
|
63
|
+
assert.ok(delta && delta.text.includes("hello codex"));
|
|
64
|
+
const completed = events.find((event) => event.type === "turn.completed");
|
|
65
|
+
assert.equal(completed?.endReason, "completed");
|
|
66
|
+
assert.equal(completed?.usage?.outputTokens, 2);
|
|
67
|
+
// The real thread id from thread.started becomes the resume cursor.
|
|
68
|
+
const cursor = session.resumeCursor();
|
|
69
|
+
assert.equal((cursor?.data).threadId, "thread_fake_1");
|
|
70
|
+
// Every event carries the codex kind and the raw envelope is preserved.
|
|
71
|
+
assert.ok(events.every((event) => event.kind === "codex"));
|
|
72
|
+
assert.ok(events.some((event) => event.raw?.source === "codex.exec.json"));
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
await instance.dispose();
|
|
76
|
+
repo.cleanup();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
test("codex driver forwards effort as the SDK CLI config", async () => {
|
|
80
|
+
const driver = createCodexDriver();
|
|
81
|
+
const effortRepo = fakeCodexRepo();
|
|
82
|
+
const instance = await driver.createInstance(driver.configSchema.parse({ command: effortRepo.command }));
|
|
83
|
+
try {
|
|
84
|
+
const session = await instance.startSession({
|
|
85
|
+
cwd: effortRepo.cwd,
|
|
86
|
+
reasoning: { mode: "effort", effort: "low" }
|
|
87
|
+
});
|
|
88
|
+
const events = [];
|
|
89
|
+
for await (const event of session.sendTurn({ prompt: "reason carefully" })) {
|
|
90
|
+
events.push(event);
|
|
91
|
+
}
|
|
92
|
+
const text = events
|
|
93
|
+
.flatMap((event) => (event.type === "content.delta" ? [event.text] : []))
|
|
94
|
+
.join("");
|
|
95
|
+
assert.match(text, /model_reasoning_effort="low"/);
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
await instance.dispose();
|
|
99
|
+
effortRepo.cleanup();
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
test("codex driver probe reports version and installed state", async () => {
|
|
103
|
+
const driver = createCodexDriver();
|
|
104
|
+
const repo2 = fakeCodexRepo();
|
|
105
|
+
try {
|
|
106
|
+
const status = await driver.probe({ env: { ...process.env } });
|
|
107
|
+
// The default command "codex" may or may not be installed on the host, so
|
|
108
|
+
// this only asserts the shape; the fake-command instance path is covered above.
|
|
109
|
+
assert.equal(status.kind, "codex");
|
|
110
|
+
assert.ok(typeof status.installed === "boolean");
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
repo2.cleanup();
|
|
114
|
+
}
|
|
115
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { test } from "node:test";
|
|
6
|
+
import { installCodexIntegration, uninstallCodexIntegration } from "../install.js";
|
|
7
|
+
const OWNER = {
|
|
8
|
+
id: "example-host",
|
|
9
|
+
displayName: "Example Host",
|
|
10
|
+
providerId: "example_route",
|
|
11
|
+
installCommand: "example install codex",
|
|
12
|
+
uninstallCommand: "example uninstall codex",
|
|
13
|
+
startCommand: "example serve"
|
|
14
|
+
};
|
|
15
|
+
test("Codex managed install updates and removes only owner-marked config", () => {
|
|
16
|
+
const home = mkdtempSync(join(tmpdir(), "routekit-codex-install-"));
|
|
17
|
+
const configPath = join(home, "config.toml");
|
|
18
|
+
writeFileSync(configPath, 'model = "user-default"\n');
|
|
19
|
+
try {
|
|
20
|
+
const installed = installCodexIntegration({
|
|
21
|
+
gatewayUrl: "http://127.0.0.1:9999/",
|
|
22
|
+
owner: OWNER,
|
|
23
|
+
profiles: [
|
|
24
|
+
{ modelId: "opaque-primary" },
|
|
25
|
+
{ modelId: "opaque-secondary", description: "Secondary route" }
|
|
26
|
+
],
|
|
27
|
+
codexHome: home
|
|
28
|
+
});
|
|
29
|
+
assert.equal(installed.action, "installed");
|
|
30
|
+
assert.match(readFileSync(configPath, "utf8"), /model = "user-default"/);
|
|
31
|
+
assert.match(readFileSync(configPath, "utf8"), /base_url = "http:\/\/127\.0\.0\.1:9999\/v1"/);
|
|
32
|
+
assert.equal(existsSync(join(home, "opaque-secondary.config.toml")), true);
|
|
33
|
+
const updated = installCodexIntegration({
|
|
34
|
+
gatewayUrl: "http://127.0.0.1:8888",
|
|
35
|
+
owner: OWNER,
|
|
36
|
+
profiles: [{ modelId: "opaque-primary" }],
|
|
37
|
+
codexHome: home
|
|
38
|
+
});
|
|
39
|
+
assert.equal(updated.action, "updated");
|
|
40
|
+
assert.equal(existsSync(join(home, "opaque-secondary.config.toml")), false);
|
|
41
|
+
assert.equal(uninstallCodexIntegration({ ownerId: OWNER.id, codexHome: home }).removed, true);
|
|
42
|
+
assert.equal(readFileSync(configPath, "utf8"), 'model = "user-default"\n');
|
|
43
|
+
assert.equal(existsSync(join(home, "opaque-primary.config.toml")), false);
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
rmSync(home, { recursive: true, force: true });
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
test("Codex profiles can preserve an opaque model id behind a safe selector", () => {
|
|
50
|
+
const home = mkdtempSync(join(tmpdir(), "routekit-codex-opaque-"));
|
|
51
|
+
try {
|
|
52
|
+
const result = installCodexIntegration({
|
|
53
|
+
gatewayUrl: "http://127.0.0.1:9999",
|
|
54
|
+
owner: OWNER,
|
|
55
|
+
profiles: [{ modelId: "provider/model", profileId: "route-1" }],
|
|
56
|
+
codexHome: home
|
|
57
|
+
});
|
|
58
|
+
assert.deepEqual(result.profiles, ["route-1"]);
|
|
59
|
+
assert.match(readFileSync(join(home, "route-1.config.toml"), "utf8"), /provider\/model/);
|
|
60
|
+
assert.equal(existsSync(join(home, "provider", "model.config.toml")), false);
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
rmSync(home, { recursive: true, force: true });
|
|
64
|
+
}
|
|
65
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { existsSync, mkdtempSync, rmSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { test } from "node:test";
|
|
6
|
+
import { codexAgentRoleToml, codexCatalogEntries, createIsolatedCodexHome, codexLaunchConfigToml, codexModelCatalogJson } from "../launch.js";
|
|
7
|
+
const SPEC = {
|
|
8
|
+
gatewayUrl: "http://127.0.0.1:9999",
|
|
9
|
+
defaultModel: "opaque-primary",
|
|
10
|
+
models: [
|
|
11
|
+
{
|
|
12
|
+
id: "opaque-primary",
|
|
13
|
+
label: "Primary",
|
|
14
|
+
aliases: ["primary-alias"],
|
|
15
|
+
reasoning: {
|
|
16
|
+
status: "supported",
|
|
17
|
+
efforts: [{ id: "quick" }, { id: "deep" }],
|
|
18
|
+
defaultEffort: "quick",
|
|
19
|
+
provenance: "provider"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: "opaque-secondary",
|
|
24
|
+
reasoning: { status: "unknown", provenance: "unknown" }
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
args: []
|
|
28
|
+
};
|
|
29
|
+
const PROFILE = {
|
|
30
|
+
id: "reviewer",
|
|
31
|
+
model: "opaque-secondary",
|
|
32
|
+
description: "Review changes.",
|
|
33
|
+
instructions: "Return concise findings."
|
|
34
|
+
};
|
|
35
|
+
test("Codex launcher serializes namespaced models without interpreting provider ids", () => {
|
|
36
|
+
const template = {
|
|
37
|
+
slug: "stock",
|
|
38
|
+
display_name: "Stock",
|
|
39
|
+
visibility: "list",
|
|
40
|
+
supported_reasoning_levels: [{ effort: "template" }],
|
|
41
|
+
default_reasoning_level: "template"
|
|
42
|
+
};
|
|
43
|
+
const entries = codexCatalogEntries(SPEC, template, [
|
|
44
|
+
template,
|
|
45
|
+
{ slug: "opaque-secondary", display_name: "duplicate" }
|
|
46
|
+
]);
|
|
47
|
+
assert.deepEqual(entries.map((entry) => entry.slug), ["opaque-primary", "primary-alias", "opaque-secondary", "stock"]);
|
|
48
|
+
assert.equal(entries[0]?.display_name, "Primary");
|
|
49
|
+
assert.deepEqual(entries[0]?.supported_reasoning_levels, [
|
|
50
|
+
{ effort: "quick", description: "quick" },
|
|
51
|
+
{ effort: "deep", description: "deep" }
|
|
52
|
+
]);
|
|
53
|
+
// Codex rejects the whole catalog file when any entry omits this field, so
|
|
54
|
+
// undiscovered models must serialize an explicit empty list.
|
|
55
|
+
assert.deepEqual(entries[2]?.supported_reasoning_levels, []);
|
|
56
|
+
assert.ok(entries
|
|
57
|
+
.slice(0, 3)
|
|
58
|
+
.every((entry) => Array.isArray(entry.supported_reasoning_levels)), "every gateway-routed entry carries supported_reasoning_levels");
|
|
59
|
+
assert.deepEqual(JSON.parse(codexModelCatalogJson(SPEC, template)).models, entries.slice(0, 3));
|
|
60
|
+
});
|
|
61
|
+
test("Codex launcher neutralizes stock-model behavior fields from the template", () => {
|
|
62
|
+
const template = {
|
|
63
|
+
slug: "gpt-stock",
|
|
64
|
+
display_name: "Stock",
|
|
65
|
+
visibility: "list",
|
|
66
|
+
supported_reasoning_levels: [{ effort: "medium" }],
|
|
67
|
+
default_reasoning_level: "medium",
|
|
68
|
+
// Real stock entries carry fields that change how Codex talks to the
|
|
69
|
+
// model; none of them may leak into gateway-routed entries.
|
|
70
|
+
tool_mode: "code_mode_only",
|
|
71
|
+
use_responses_lite: true,
|
|
72
|
+
additional_speed_tiers: ["fast"],
|
|
73
|
+
service_tiers: [{ id: "priority", name: "Fast" }],
|
|
74
|
+
default_service_tier: "priority",
|
|
75
|
+
base_instructions: "You are Codex, an agent based on GPT-5.",
|
|
76
|
+
model_messages: {
|
|
77
|
+
instructions_template: "You are Codex, an agent based on GPT-5.",
|
|
78
|
+
instructions_variables: null
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const [entry] = codexCatalogEntries(SPEC, template);
|
|
82
|
+
assert.ok(entry);
|
|
83
|
+
assert.equal("tool_mode" in entry, false);
|
|
84
|
+
assert.equal("default_service_tier" in entry, false);
|
|
85
|
+
assert.equal(entry.use_responses_lite, false);
|
|
86
|
+
assert.deepEqual(entry.additional_speed_tiers, []);
|
|
87
|
+
assert.deepEqual(entry.service_tiers, []);
|
|
88
|
+
// The developer message must not claim a stock model's identity.
|
|
89
|
+
assert.equal(entry.base_instructions, "You are a coding agent.");
|
|
90
|
+
assert.deepEqual(entry.model_messages, {
|
|
91
|
+
instructions_template: "You are a coding agent.",
|
|
92
|
+
instructions_variables: null
|
|
93
|
+
});
|
|
94
|
+
// A minimal template gains no wire-shape fields it never had.
|
|
95
|
+
const [minimal] = codexCatalogEntries(SPEC, { slug: "s", visibility: "list" });
|
|
96
|
+
assert.ok(minimal);
|
|
97
|
+
assert.equal("use_responses_lite" in minimal, false);
|
|
98
|
+
assert.equal("service_tiers" in minimal, false);
|
|
99
|
+
});
|
|
100
|
+
test("Codex launcher passes stock ModelInfo through for codex-native models only", () => {
|
|
101
|
+
const spec = {
|
|
102
|
+
gatewayUrl: "http://127.0.0.1:9999",
|
|
103
|
+
defaultModel: "codex/gpt-5.5",
|
|
104
|
+
models: [
|
|
105
|
+
{ id: "codex/gpt-5.5" },
|
|
106
|
+
// A foreign model that happens to collide with a stock slug must NOT
|
|
107
|
+
// inherit the stock entry: it is not the Codex-native model.
|
|
108
|
+
{ id: "claude-code/gpt-5.4" },
|
|
109
|
+
{ id: "claude-code/claude-sonnet-5" }
|
|
110
|
+
],
|
|
111
|
+
args: []
|
|
112
|
+
};
|
|
113
|
+
const template = { slug: "stock", display_name: "Stock", visibility: "list" };
|
|
114
|
+
const stock = [
|
|
115
|
+
{
|
|
116
|
+
slug: "gpt-5.5",
|
|
117
|
+
display_name: "GPT-5.5",
|
|
118
|
+
description: "Stock Codex model.",
|
|
119
|
+
base_instructions: "You are Codex, an agent based on GPT-5.",
|
|
120
|
+
tool_mode: "code_mode_only",
|
|
121
|
+
use_responses_lite: true,
|
|
122
|
+
supported_reasoning_levels: [{ effort: "xhigh" }],
|
|
123
|
+
default_reasoning_level: "xhigh",
|
|
124
|
+
visibility: "hidden"
|
|
125
|
+
},
|
|
126
|
+
{ slug: "gpt-5.4", display_name: "GPT-5.4" },
|
|
127
|
+
{ slug: "gpt-unrelated", display_name: "Unrelated" }
|
|
128
|
+
];
|
|
129
|
+
const entries = codexCatalogEntries(spec, template, stock, {
|
|
130
|
+
appendUnlistedStock: false
|
|
131
|
+
});
|
|
132
|
+
assert.deepEqual(entries.map((entry) => entry.slug), ["gpt-5.5", "claude-code/gpt-5.4", "claude-code/claude-sonnet-5"]);
|
|
133
|
+
const [native, foreignCollision, foreign] = entries;
|
|
134
|
+
// Native passthrough keeps the tuned stock behavior, pinned to list + HTTP.
|
|
135
|
+
assert.equal(native?.base_instructions, "You are Codex, an agent based on GPT-5.");
|
|
136
|
+
assert.equal(native?.tool_mode, "code_mode_only");
|
|
137
|
+
assert.equal(native?.use_responses_lite, true);
|
|
138
|
+
assert.equal(native?.default_reasoning_level, "xhigh");
|
|
139
|
+
assert.equal(native?.visibility, "list");
|
|
140
|
+
assert.equal(native?.prefer_websockets, false);
|
|
141
|
+
// Foreign models never inherit stock entries, colliding slug or not.
|
|
142
|
+
assert.equal(foreignCollision?.base_instructions, undefined);
|
|
143
|
+
assert.equal("tool_mode" in (foreignCollision ?? {}), false);
|
|
144
|
+
assert.equal("tool_mode" in (foreign ?? {}), false);
|
|
145
|
+
// Unlisted stock models stay out when appending is disabled.
|
|
146
|
+
assert.ok(!entries.some((entry) => entry.slug === "gpt-unrelated"));
|
|
147
|
+
});
|
|
148
|
+
test("Codex launcher serializes one gateway provider and generic agent profiles", () => {
|
|
149
|
+
const role = { ...PROFILE, configPath: "/tmp/reviewer.toml" };
|
|
150
|
+
const config = codexLaunchConfigToml(SPEC, "/tmp/catalog.json", [role]);
|
|
151
|
+
assert.match(config, /model = "opaque-primary"/);
|
|
152
|
+
assert.match(config, /base_url = "http:\/\/127\.0\.0\.1:9999\/v1"/);
|
|
153
|
+
assert.match(config, /config_file = "\/tmp\/reviewer\.toml"/);
|
|
154
|
+
const profile = codexAgentRoleToml(PROFILE);
|
|
155
|
+
assert.match(profile, /model = "opaque-secondary"/);
|
|
156
|
+
assert.match(profile, /developer_instructions = "Return concise findings\."/);
|
|
157
|
+
});
|
|
158
|
+
test("Codex launcher projects codex models to native picker ids", () => {
|
|
159
|
+
const spec = {
|
|
160
|
+
gatewayUrl: "http://127.0.0.1:9999",
|
|
161
|
+
defaultModel: "codex/gpt-5.5",
|
|
162
|
+
models: [
|
|
163
|
+
{ id: "codex/gpt-5.5", label: "GPT-5.5 subscription" },
|
|
164
|
+
{ id: "claude-code/claude-sonnet-4-6" }
|
|
165
|
+
],
|
|
166
|
+
args: []
|
|
167
|
+
};
|
|
168
|
+
const template = {
|
|
169
|
+
slug: "stock",
|
|
170
|
+
display_name: "Stock",
|
|
171
|
+
visibility: "list"
|
|
172
|
+
};
|
|
173
|
+
assert.deepEqual(codexCatalogEntries(spec, template).map((entry) => [
|
|
174
|
+
entry.slug,
|
|
175
|
+
entry.display_name
|
|
176
|
+
]), [
|
|
177
|
+
["gpt-5.5", "GPT-5.5 subscription"],
|
|
178
|
+
[
|
|
179
|
+
"claude-code/claude-sonnet-4-6",
|
|
180
|
+
"claude-code/claude-sonnet-4-6"
|
|
181
|
+
]
|
|
182
|
+
]);
|
|
183
|
+
assert.match(codexLaunchConfigToml(spec), /model = "gpt-5\.5"/);
|
|
184
|
+
assert.match(codexAgentRoleToml({
|
|
185
|
+
...PROFILE,
|
|
186
|
+
model: "codex/gpt-5.5"
|
|
187
|
+
}), /model = "gpt-5\.5"/);
|
|
188
|
+
});
|
|
189
|
+
test("isolated Codex homes live under the user cache instead of the system temp root", () => {
|
|
190
|
+
const root = mkdtempSync(join(tmpdir(), "routekit-codex-home-test-"));
|
|
191
|
+
const userHome = join(root, "home");
|
|
192
|
+
try {
|
|
193
|
+
const isolated = createIsolatedCodexHome("driver-", { HOME: userHome });
|
|
194
|
+
assert.ok(isolated.startsWith(join(userHome, ".cache", "routekit", "codex", "driver-")));
|
|
195
|
+
assert.equal(existsSync(isolated), true);
|
|
196
|
+
}
|
|
197
|
+
finally {
|
|
198
|
+
rmSync(root, { recursive: true, force: true });
|
|
199
|
+
}
|
|
200
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@velum-labs/routekit-tool-codex",
|
|
3
|
+
"private": false,
|
|
4
|
+
"version": "0.9.0",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/velum-labs/handoffkit.git",
|
|
8
|
+
"directory": "packages/tool-codex"
|
|
9
|
+
},
|
|
10
|
+
"description": "Product-neutral Codex launcher, serializer, and canonical harness driver.",
|
|
11
|
+
"license": "Apache-2.0",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"LICENSE"
|
|
22
|
+
],
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"registry": "https://registry.npmjs.org",
|
|
25
|
+
"access": "public",
|
|
26
|
+
"provenance": true
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@openai/codex-sdk": "0.145.0",
|
|
30
|
+
"smol-toml": "1.7.0",
|
|
31
|
+
"zod": "4.4.3",
|
|
32
|
+
"@velum-labs/routekit-harness-core": "0.9.0",
|
|
33
|
+
"@velum-labs/routekit-runtime": "0.9.0",
|
|
34
|
+
"@velum-labs/routekit-registry": "0.9.0",
|
|
35
|
+
"@velum-labs/routekit-tools": "0.9.0"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"llm",
|
|
39
|
+
"coding-agent",
|
|
40
|
+
"codex",
|
|
41
|
+
"harness",
|
|
42
|
+
"adapter"
|
|
43
|
+
],
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsc -b",
|
|
46
|
+
"clean": "tsc -b --clean",
|
|
47
|
+
"test": "node --test \"dist/test/*.test.js\""
|
|
48
|
+
}
|
|
49
|
+
}
|