pennyrouter 0.3.5 → 0.3.6
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/package.json +1 -1
- package/src/harnesses/codex.js +10 -3
- package/src/harnesses/codex.test.js +4 -1
- package/src/launch.js +13 -6
- package/src/launch.test.js +3 -1
package/package.json
CHANGED
package/src/harnesses/codex.js
CHANGED
|
@@ -37,6 +37,7 @@ export const codexHarness = {
|
|
|
37
37
|
apiKey,
|
|
38
38
|
gatewayBaseUrl,
|
|
39
39
|
subscriptionAuth,
|
|
40
|
+
surface: "codex-terminal",
|
|
40
41
|
});
|
|
41
42
|
|
|
42
43
|
await atomicWrite(CONFIG_PATH, next);
|
|
@@ -98,7 +99,10 @@ export async function detectCodexChatgptAuth() {
|
|
|
98
99
|
* x-pennyrouter-key header authenticates the account to our gateway. In Penny-only mode
|
|
99
100
|
* the pr- key is the provider bearer, matching the isolated probe's existing behavior.
|
|
100
101
|
*/
|
|
101
|
-
export function renderCodexConfig(
|
|
102
|
+
export function renderCodexConfig(
|
|
103
|
+
text,
|
|
104
|
+
{ apiKey, gatewayBaseUrl, subscriptionAuth, surface = "codex-terminal" },
|
|
105
|
+
) {
|
|
102
106
|
const clean = removeManagedCodexConfig(text, { removeRootProvider: true }).trimEnd();
|
|
103
107
|
if (new RegExp(`^\\s*\\[model_providers\\.${escapeRegex(PROVIDER_ID)}\\]\\s*$`, "m").test(clean)) {
|
|
104
108
|
throw new Error(
|
|
@@ -111,9 +115,12 @@ export function renderCodexConfig(text, { apiKey, gatewayBaseUrl, subscriptionAu
|
|
|
111
115
|
const authLines = subscriptionAuth
|
|
112
116
|
? [
|
|
113
117
|
"requires_openai_auth = true",
|
|
114
|
-
`http_headers = { "x-pennyrouter-key" = ${escapedKey} }`,
|
|
118
|
+
`http_headers = { "x-pennyrouter-key" = ${escapedKey}, "x-pennyrouter-harness" = "codex", "x-pennyrouter-surface" = ${tomlString(surface)} }`,
|
|
115
119
|
]
|
|
116
|
-
: [
|
|
120
|
+
: [
|
|
121
|
+
`experimental_bearer_token = ${escapedKey}`,
|
|
122
|
+
`http_headers = { "x-pennyrouter-harness" = "codex", "x-pennyrouter-surface" = ${tomlString(surface)} }`,
|
|
123
|
+
];
|
|
117
124
|
const block = [
|
|
118
125
|
MANAGED_START,
|
|
119
126
|
`model_provider = "${PROVIDER_ID}"`,
|
|
@@ -29,7 +29,9 @@ function testSubscriptionProviderIsRootSafe() {
|
|
|
29
29
|
assert.ok(next.includes('[model_providers.pennyrouter]'));
|
|
30
30
|
assert.ok(next.includes('base_url = "http://localhost:8400/v1"'));
|
|
31
31
|
assert.ok(next.includes("requires_openai_auth = true"));
|
|
32
|
-
assert.ok(next.includes('
|
|
32
|
+
assert.ok(next.includes('"x-pennyrouter-key" = "pr-test-secret"'));
|
|
33
|
+
assert.ok(next.includes('"x-pennyrouter-harness" = "codex"'));
|
|
34
|
+
assert.ok(next.includes('"x-pennyrouter-surface" = "codex-terminal"'));
|
|
33
35
|
assert.ok(!next.includes("experimental_bearer_token"));
|
|
34
36
|
assert.equal((next.match(/# --- PennyRouter managed Codex provider ---/g) || []).length, 1);
|
|
35
37
|
}
|
|
@@ -51,6 +53,7 @@ function testLegacyMigrationAndIdempotence() {
|
|
|
51
53
|
assert.ok(!once.includes("openai_base_url"));
|
|
52
54
|
assert.ok(!once.includes("openai_api_key"));
|
|
53
55
|
assert.ok(once.includes('experimental_bearer_token = "pr-new"'));
|
|
56
|
+
assert.ok(once.includes('"x-pennyrouter-surface" = "codex-terminal"'));
|
|
54
57
|
assert.ok(!once.includes("requires_openai_auth"));
|
|
55
58
|
}
|
|
56
59
|
|
package/src/launch.js
CHANGED
|
@@ -295,11 +295,11 @@ export function renderCodexLaunchProfile({ gatewayBaseUrl, subscriptionAuth }) {
|
|
|
295
295
|
const authLines = subscriptionAuth
|
|
296
296
|
? [
|
|
297
297
|
"requires_openai_auth = true",
|
|
298
|
-
'env_http_headers = { "x-pennyrouter-key" = "PENNYROUTER_API_KEY", "x-pennyrouter-title-session" = "PENNYROUTER_TITLE_SESSION_ID", "x-pennyrouter-harness" = "codex" }',
|
|
298
|
+
'env_http_headers = { "x-pennyrouter-key" = "PENNYROUTER_API_KEY", "x-pennyrouter-title-session" = "PENNYROUTER_TITLE_SESSION_ID", "x-pennyrouter-harness" = "codex", "x-pennyrouter-surface" = "codex-terminal" }',
|
|
299
299
|
]
|
|
300
300
|
: [
|
|
301
301
|
'env_key = "PENNYROUTER_API_KEY"',
|
|
302
|
-
'http_headers = { "x-pennyrouter-title-session" = "PENNYROUTER_TITLE_SESSION_ID", "x-pennyrouter-harness" = "codex" }',
|
|
302
|
+
'http_headers = { "x-pennyrouter-title-session" = "PENNYROUTER_TITLE_SESSION_ID", "x-pennyrouter-harness" = "codex", "x-pennyrouter-surface" = "codex-terminal" }',
|
|
303
303
|
];
|
|
304
304
|
return [
|
|
305
305
|
CODEX_PROFILE_MARKER,
|
|
@@ -545,6 +545,7 @@ export async function createCodexDesktopHome({ config, realHome }) {
|
|
|
545
545
|
apiKey: config.api_key,
|
|
546
546
|
gatewayBaseUrl: config.gateway_base_url,
|
|
547
547
|
subscriptionAuth: Boolean(config.harnesses?.codex?.subscription_auth),
|
|
548
|
+
surface: "codex-desktop",
|
|
548
549
|
}));
|
|
549
550
|
await privateWrite(join(overlay, "config.toml"), desktopConfig);
|
|
550
551
|
return overlay;
|
|
@@ -971,7 +972,9 @@ export async function runPenny(argv = process.argv.slice(2)) {
|
|
|
971
972
|
stdio: "inherit",
|
|
972
973
|
shell: process.platform === "win32",
|
|
973
974
|
});
|
|
974
|
-
|
|
975
|
+
// Claude Code owns its own terminal title updates while a turn is running. Do not
|
|
976
|
+
// compete with it from the parent process; Codex and OpenCode remain Penny-owned.
|
|
977
|
+
const stopTerminalTitle = ["codex", "opencode"].includes(harness)
|
|
975
978
|
? startPennyTerminalTitle({
|
|
976
979
|
gatewayBaseUrl: config.gateway_base_url,
|
|
977
980
|
apiKey: config.api_key,
|
|
@@ -1019,7 +1022,10 @@ async function runCodexDesktop(config, { logs = false, mode = "gateway" } = {})
|
|
|
1019
1022
|
}
|
|
1020
1023
|
if (mode === "native") {
|
|
1021
1024
|
const child = spawn(codexDesktopExecutable(), [], {
|
|
1022
|
-
|
|
1025
|
+
// Desktop launches must not inherit a project directory. When PennyRouter itself is
|
|
1026
|
+
// started from ~/Documents, that makes macOS attribute Codex's access to Documents to
|
|
1027
|
+
// PennyRouter and trigger a protected-folder prompt.
|
|
1028
|
+
cwd: homeDir(),
|
|
1023
1029
|
env: process.env,
|
|
1024
1030
|
stdio: logs ? "inherit" : "ignore",
|
|
1025
1031
|
detached: !logs,
|
|
@@ -1042,7 +1048,8 @@ async function runCodexDesktop(config, { logs = false, mode = "gateway" } = {})
|
|
|
1042
1048
|
"__codex-desktop-supervisor",
|
|
1043
1049
|
overlay,
|
|
1044
1050
|
], {
|
|
1045
|
-
|
|
1051
|
+
// Keep the detached supervisor out of the caller's protected project directory too.
|
|
1052
|
+
cwd: homeDir(),
|
|
1046
1053
|
env: process.env,
|
|
1047
1054
|
stdio: "ignore",
|
|
1048
1055
|
detached: true,
|
|
@@ -1057,7 +1064,7 @@ async function runCodexDesktop(config, { logs = false, mode = "gateway" } = {})
|
|
|
1057
1064
|
|
|
1058
1065
|
export async function runCodexDesktopSupervisor(overlay) {
|
|
1059
1066
|
const child = spawn(codexDesktopExecutable(), [], {
|
|
1060
|
-
cwd:
|
|
1067
|
+
cwd: homeDir(),
|
|
1061
1068
|
env: { ...process.env, CODEX_HOME: overlay },
|
|
1062
1069
|
stdio: "inherit",
|
|
1063
1070
|
shell: process.platform === "win32",
|
package/src/launch.test.js
CHANGED
|
@@ -179,7 +179,8 @@ async function main() {
|
|
|
179
179
|
const desktopConfig = fs.readFileSync(path.join(desktopHome, "config.toml"), "utf8");
|
|
180
180
|
assert.match(desktopConfig, /^model_provider = "pennyrouter"/);
|
|
181
181
|
assert.match(desktopConfig, /\[model_providers\.pennyrouter\]/);
|
|
182
|
-
assert.match(desktopConfig, /
|
|
182
|
+
assert.match(desktopConfig, /"x-pennyrouter-key" = "pr-super-secret"/);
|
|
183
|
+
assert.match(desktopConfig, /"x-pennyrouter-surface" = "codex-desktop"/);
|
|
183
184
|
assert.equal(fs.readFileSync(path.join(home, ".codex", "config.toml"), "utf8"), nativeCodex);
|
|
184
185
|
fs.rmSync(desktopHome, { recursive: true, force: true });
|
|
185
186
|
const codexProfile = fs.readFileSync(codexLaunchProfilePath(), "utf8");
|
|
@@ -187,6 +188,7 @@ async function main() {
|
|
|
187
188
|
assert.ok(codexProfile.includes("[tui]\nterminal_title = []"));
|
|
188
189
|
assert.ok(codexProfile.includes('"x-pennyrouter-title-session" = "PENNYROUTER_TITLE_SESSION_ID"'));
|
|
189
190
|
assert.ok(codexProfile.includes('"x-pennyrouter-harness" = "codex"'));
|
|
191
|
+
assert.ok(codexProfile.includes('"x-pennyrouter-surface" = "codex-terminal"'));
|
|
190
192
|
assert.ok(!codexProfile.includes("pr-super-secret"));
|
|
191
193
|
assert.ok(!rc.includes("pr-super-secret"));
|
|
192
194
|
|