@jacobbd/relay-ai 0.9.5 → 0.9.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/dist/{chunk-SCW2TYSG.js → chunk-PYJQMEJD.js} +108 -30
- package/dist/{chunk-SCW2TYSG.js.map → chunk-PYJQMEJD.js.map} +1 -1
- package/dist/cli.js +574 -180
- package/dist/cli.js.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/{ui-command-Q6LBKVM3.js → ui-command-NYYLGTCD.js} +2 -2
- package/package.json +1 -1
- /package/dist/{ui-command-Q6LBKVM3.js.map → ui-command-NYYLGTCD.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -84,6 +84,7 @@ import {
|
|
|
84
84
|
getCodexProxyDebugLogPath,
|
|
85
85
|
getConfigPath,
|
|
86
86
|
getGeminiProxyDebugLogPath,
|
|
87
|
+
getLogsPath,
|
|
87
88
|
getProvidersPath,
|
|
88
89
|
getProxyDebugLogPath,
|
|
89
90
|
getReasoningCapabilities,
|
|
@@ -197,7 +198,7 @@ import {
|
|
|
197
198
|
validateCustomEndpointUrl,
|
|
198
199
|
writeSecureLogLine,
|
|
199
200
|
zenRegistryStub
|
|
200
|
-
} from "./chunk-
|
|
201
|
+
} from "./chunk-PYJQMEJD.js";
|
|
201
202
|
import {
|
|
202
203
|
filterTemplates,
|
|
203
204
|
getTemplateById,
|
|
@@ -1295,7 +1296,7 @@ ${pc4.bold("Subcommands:")}
|
|
|
1295
1296
|
(none) Provider hub wizard ${pc4.dim("[Phase 1.1]")}
|
|
1296
1297
|
add Add a provider (Groq, Mistral, Together AI, \u2026) ${pc4.dim("[Phase 1.1]")}
|
|
1297
1298
|
import Optional one-time import from OpenCode CLI ${pc4.dim("[Phase 1.0]")}
|
|
1298
|
-
auth Sign in with OAuth (GitHub Copilot, xAI, OpenAI, ClinePass)
|
|
1299
|
+
auth Sign in with OAuth (Antigravity, GitHub Copilot, xAI, OpenAI, ClinePass)
|
|
1299
1300
|
list Show configured providers ${pc4.dim("[Phase 1.0]")}
|
|
1300
1301
|
remove Remove a provider by id ${pc4.dim("[Phase 1.1]")}
|
|
1301
1302
|
refresh-models Update cached model lists ${pc4.dim("[Phase 1.2]")}`;
|
|
@@ -2233,7 +2234,7 @@ async function runProvidersCommand(args) {
|
|
|
2233
2234
|
// src/codex.ts
|
|
2234
2235
|
import pc7 from "picocolors";
|
|
2235
2236
|
import * as p8 from "@clack/prompts";
|
|
2236
|
-
import { join as
|
|
2237
|
+
import { join as join6 } from "path";
|
|
2237
2238
|
|
|
2238
2239
|
// src/codex-proxy.ts
|
|
2239
2240
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -3770,6 +3771,53 @@ async function resolveRoutedCollaborationInput(input, context) {
|
|
|
3770
3771
|
return normalizePlaintextCollaborationForExternal(out);
|
|
3771
3772
|
}
|
|
3772
3773
|
|
|
3774
|
+
// src/codex/route-audit.ts
|
|
3775
|
+
import { chmodSync, mkdirSync, writeFileSync } from "fs";
|
|
3776
|
+
import { join as join2 } from "path";
|
|
3777
|
+
var DIR_MODE = 448;
|
|
3778
|
+
var FILE_MODE = 384;
|
|
3779
|
+
var CODEX_ROUTE_AUDIT_LOG = "codex-route-audit.jsonl";
|
|
3780
|
+
function safeIdentifier(value) {
|
|
3781
|
+
if (value === void 0) return void 0;
|
|
3782
|
+
return value.replace(/[\u0000-\u001f\u007f]/g, "_").slice(0, 300);
|
|
3783
|
+
}
|
|
3784
|
+
function sanitizeCodexRouteAuditEvent(event) {
|
|
3785
|
+
return {
|
|
3786
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3787
|
+
transport: event.transport,
|
|
3788
|
+
requestedModel: safeIdentifier(event.requestedModel),
|
|
3789
|
+
dispatch: event.dispatch,
|
|
3790
|
+
phase: event.phase,
|
|
3791
|
+
...event.provider ? { provider: safeIdentifier(event.provider) } : {},
|
|
3792
|
+
...event.routeModel ? { routeModel: safeIdentifier(event.routeModel) } : {},
|
|
3793
|
+
...event.upstreamModel ? { upstreamModel: safeIdentifier(event.upstreamModel) } : {},
|
|
3794
|
+
...event.outcome ? { outcome: event.outcome } : {},
|
|
3795
|
+
...event.status !== void 0 ? { status: typeof event.status === "string" ? safeIdentifier(event.status) : event.status } : {}
|
|
3796
|
+
};
|
|
3797
|
+
}
|
|
3798
|
+
function getCodexRouteAuditLogPath() {
|
|
3799
|
+
const dir = getLogsPath();
|
|
3800
|
+
mkdirSync(dir, { recursive: true, mode: DIR_MODE });
|
|
3801
|
+
try {
|
|
3802
|
+
chmodSync(dir, DIR_MODE);
|
|
3803
|
+
} catch {
|
|
3804
|
+
}
|
|
3805
|
+
return join2(dir, CODEX_ROUTE_AUDIT_LOG);
|
|
3806
|
+
}
|
|
3807
|
+
function prepareCodexRouteAuditLog(path3 = getCodexRouteAuditLogPath()) {
|
|
3808
|
+
writeFileSync(path3, "", { mode: FILE_MODE });
|
|
3809
|
+
chmodSync(path3, FILE_MODE);
|
|
3810
|
+
return path3;
|
|
3811
|
+
}
|
|
3812
|
+
function appendCodexRouteAudit(path3, event) {
|
|
3813
|
+
try {
|
|
3814
|
+
writeFileSync(path3, `${JSON.stringify(sanitizeCodexRouteAuditEvent(event))}
|
|
3815
|
+
`, { flag: "a", mode: FILE_MODE });
|
|
3816
|
+
chmodSync(path3, FILE_MODE);
|
|
3817
|
+
} catch {
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3820
|
+
|
|
3773
3821
|
// src/codex-proxy.ts
|
|
3774
3822
|
function captureCompletedResponse(sseText) {
|
|
3775
3823
|
if (!sseText.includes("response.completed")) return void 0;
|
|
@@ -3777,11 +3825,34 @@ function captureCompletedResponse(sseText) {
|
|
|
3777
3825
|
if (!dataLine) return void 0;
|
|
3778
3826
|
try {
|
|
3779
3827
|
const obj = JSON.parse(dataLine.slice(5).trim());
|
|
3780
|
-
if (obj && obj.type === "response.completed"
|
|
3828
|
+
if (obj && obj.type === "response.completed" && obj.response && typeof obj.response === "object") {
|
|
3829
|
+
return obj.response;
|
|
3830
|
+
}
|
|
3781
3831
|
} catch {
|
|
3782
3832
|
}
|
|
3783
3833
|
return void 0;
|
|
3784
3834
|
}
|
|
3835
|
+
var MAX_EXTERNAL_RESPONSE_STATES = 8;
|
|
3836
|
+
var EXTERNAL_TOOL_OUTPUT_TYPES = /* @__PURE__ */ new Set([
|
|
3837
|
+
"function_call_output",
|
|
3838
|
+
"custom_tool_call_output",
|
|
3839
|
+
"tool_search_output"
|
|
3840
|
+
]);
|
|
3841
|
+
function responsesInputItems(input) {
|
|
3842
|
+
if (Array.isArray(input)) return input;
|
|
3843
|
+
if (typeof input === "string") {
|
|
3844
|
+
return [{ type: "message", role: "user", content: input }];
|
|
3845
|
+
}
|
|
3846
|
+
return [];
|
|
3847
|
+
}
|
|
3848
|
+
function isExternalToolOutputItem(item) {
|
|
3849
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return false;
|
|
3850
|
+
const type = item.type;
|
|
3851
|
+
return typeof type === "string" && EXTERNAL_TOOL_OUTPUT_TYPES.has(type);
|
|
3852
|
+
}
|
|
3853
|
+
function isExternalToolContinuation(input) {
|
|
3854
|
+
return Array.isArray(input) && input.length > 0 && input.every(isExternalToolOutputItem);
|
|
3855
|
+
}
|
|
3785
3856
|
function estimateCodexRequestChars(params) {
|
|
3786
3857
|
let chars = (params.system ?? "").length;
|
|
3787
3858
|
for (const msg of params.messages) {
|
|
@@ -3980,11 +4051,32 @@ async function prepareExternalCodexBody(body, context) {
|
|
|
3980
4051
|
);
|
|
3981
4052
|
return { ...externalBody, input: resolvedInput };
|
|
3982
4053
|
}
|
|
4054
|
+
function applyExternalCodexRuntimeIdentity(params, route) {
|
|
4055
|
+
const selectedModel = route.auditUpstreamModelId ?? route.upstreamModelId ?? route.modelId;
|
|
4056
|
+
const provider = route.providerId ?? "relay";
|
|
4057
|
+
const identity = [
|
|
4058
|
+
"<external-model-identity>",
|
|
4059
|
+
`The selected model for this turn is ${JSON.stringify(selectedModel)} through provider ${JSON.stringify(provider)}.`,
|
|
4060
|
+
"Codex is the host application and agent environment, not the model identity.",
|
|
4061
|
+
"Follow Codex host and tool instructions normally, but do not infer that you are an OpenAI or GPT model from host names, tool names, documentation, or conversation context.",
|
|
4062
|
+
"If asked what model you are, report the selected model and provider above; do not use self-identification as evidence of the network route.",
|
|
4063
|
+
"</external-model-identity>"
|
|
4064
|
+
].join("\n");
|
|
4065
|
+
return {
|
|
4066
|
+
...params,
|
|
4067
|
+
system: params.system?.trim() ? `${identity}
|
|
4068
|
+
|
|
4069
|
+
${params.system}` : identity
|
|
4070
|
+
};
|
|
4071
|
+
}
|
|
3983
4072
|
async function startCodexProxy(routes, options = {}) {
|
|
3984
4073
|
const opts = typeof options === "boolean" ? { debug: options } : options;
|
|
3985
4074
|
const debug = opts.debug ?? false;
|
|
3986
4075
|
const requireAuth = opts.requireAuth ?? true;
|
|
3987
4076
|
const mixedNative = opts.mixedNative;
|
|
4077
|
+
const audit = (event) => {
|
|
4078
|
+
if (opts.routeAuditPath) appendCodexRouteAudit(opts.routeAuditPath, event);
|
|
4079
|
+
};
|
|
3988
4080
|
const nativePayloadRelay = mixedNative ? createNativePayloadRelay({}) : void 0;
|
|
3989
4081
|
silenceSdkWarnings();
|
|
3990
4082
|
const models = /* @__PURE__ */ new Map();
|
|
@@ -4158,6 +4250,7 @@ async function startCodexProxy(routes, options = {}) {
|
|
|
4158
4250
|
log14(`subagent dispatch: requested=${modelId} route=${subagentRoute?.modelId ?? "(none)"}`);
|
|
4159
4251
|
}
|
|
4160
4252
|
if (mixedNative && markedSubagent && !subagentRoute) {
|
|
4253
|
+
audit({ transport: "http", requestedModel: modelId, dispatch: "relay-subagent", phase: "complete", outcome: "error", status: 503 });
|
|
4161
4254
|
sendJson(res, 503, {
|
|
4162
4255
|
error: {
|
|
4163
4256
|
message: "Codex marked this request as a Sub-agent, but no configured Codex Sub-agent route is available.",
|
|
@@ -4170,10 +4263,20 @@ async function startCodexProxy(routes, options = {}) {
|
|
|
4170
4263
|
if (!markedSubagent) {
|
|
4171
4264
|
const dispatch = classifyCodexDispatch(modelId, routes, mixedNative.nativeModelIds);
|
|
4172
4265
|
if (dispatch.kind === "unknown") {
|
|
4266
|
+
audit({ transport: "http", requestedModel: modelId, dispatch: "unknown", phase: "complete", outcome: "error", status: 404 });
|
|
4173
4267
|
sendJson(res, 404, { error: { message: `Unknown model: ${modelId}`, type: "invalid_request_error" } });
|
|
4174
4268
|
return;
|
|
4175
4269
|
}
|
|
4176
4270
|
if (dispatch.kind === "native") {
|
|
4271
|
+
audit({
|
|
4272
|
+
transport: "http",
|
|
4273
|
+
requestedModel: modelId,
|
|
4274
|
+
dispatch: "native",
|
|
4275
|
+
phase: "dispatch",
|
|
4276
|
+
provider: "openai-native",
|
|
4277
|
+
routeModel: modelId,
|
|
4278
|
+
upstreamModel: modelId
|
|
4279
|
+
});
|
|
4177
4280
|
const controller = new AbortController();
|
|
4178
4281
|
req.once("aborted", () => controller.abort());
|
|
4179
4282
|
try {
|
|
@@ -4187,7 +4290,29 @@ async function startCodexProxy(routes, options = {}) {
|
|
|
4187
4290
|
const contentType = nativeResponse.headers.get("content-type");
|
|
4188
4291
|
res.writeHead(nativeResponse.status, contentType ? { "content-type": contentType } : void 0);
|
|
4189
4292
|
res.end(Buffer.from(await nativeResponse.arrayBuffer()));
|
|
4293
|
+
audit({
|
|
4294
|
+
transport: "http",
|
|
4295
|
+
requestedModel: modelId,
|
|
4296
|
+
dispatch: "native",
|
|
4297
|
+
phase: "complete",
|
|
4298
|
+
provider: "openai-native",
|
|
4299
|
+
routeModel: modelId,
|
|
4300
|
+
upstreamModel: modelId,
|
|
4301
|
+
outcome: nativeResponse.ok ? "ok" : "error",
|
|
4302
|
+
status: nativeResponse.status
|
|
4303
|
+
});
|
|
4190
4304
|
} catch (err) {
|
|
4305
|
+
audit({
|
|
4306
|
+
transport: "http",
|
|
4307
|
+
requestedModel: modelId,
|
|
4308
|
+
dispatch: "native",
|
|
4309
|
+
phase: "complete",
|
|
4310
|
+
provider: "openai-native",
|
|
4311
|
+
routeModel: modelId,
|
|
4312
|
+
upstreamModel: modelId,
|
|
4313
|
+
outcome: "error",
|
|
4314
|
+
status: "forward-failed"
|
|
4315
|
+
});
|
|
4191
4316
|
if (!res.writableEnded) sendJson(res, 502, { error: { message: "Native Codex request failed", type: "upstream_error" } });
|
|
4192
4317
|
}
|
|
4193
4318
|
return;
|
|
@@ -4212,13 +4337,23 @@ async function startCodexProxy(routes, options = {}) {
|
|
|
4212
4337
|
}
|
|
4213
4338
|
}
|
|
4214
4339
|
const { route, languageModel } = resolved;
|
|
4340
|
+
const relayDispatch = markedSubagent ? "relay-subagent" : "relay";
|
|
4341
|
+
audit({
|
|
4342
|
+
transport: "http",
|
|
4343
|
+
requestedModel: modelId,
|
|
4344
|
+
dispatch: relayDispatch,
|
|
4345
|
+
phase: "dispatch",
|
|
4346
|
+
provider: route.providerId ?? "relay",
|
|
4347
|
+
routeModel: route.modelId,
|
|
4348
|
+
upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId
|
|
4349
|
+
});
|
|
4215
4350
|
try {
|
|
4216
4351
|
const routedBody = await prepareExternalCodexBody(body, {
|
|
4217
4352
|
relay: nativePayloadRelay,
|
|
4218
4353
|
mixedNative,
|
|
4219
4354
|
headers: req.headers
|
|
4220
4355
|
});
|
|
4221
|
-
let params = applyClaudeCodeOAuthIdentity(route, translateResponsesRequest(
|
|
4356
|
+
let params = applyClaudeCodeOAuthIdentity(route, applyExternalCodexRuntimeIdentity(translateResponsesRequest(
|
|
4222
4357
|
routedBody,
|
|
4223
4358
|
route.npm,
|
|
4224
4359
|
{
|
|
@@ -4230,7 +4365,7 @@ async function startCodexProxy(routes, options = {}) {
|
|
|
4230
4365
|
upstreamModelId: route.upstreamModelId
|
|
4231
4366
|
},
|
|
4232
4367
|
{ maxTools: maxToolsForNpm(route.npm) }
|
|
4233
|
-
));
|
|
4368
|
+
), route));
|
|
4234
4369
|
if (route.contextWindow && route.contextWindow > 0) {
|
|
4235
4370
|
const before = params.messages.length;
|
|
4236
4371
|
const estimatedChars = estimateCodexRequestChars(params);
|
|
@@ -4285,9 +4420,31 @@ async function startCodexProxy(routes, options = {}) {
|
|
|
4285
4420
|
log14(`response progress: model=${route.modelId} elapsedMs=${progress.elapsedMs} reasoningChars=${progress.reasoningChars} textChars=${progress.textChars} toolCalls=${progress.toolCallCount} reasoningTail=${JSON.stringify(progress.reasoningTail)}`);
|
|
4286
4421
|
}
|
|
4287
4422
|
});
|
|
4423
|
+
audit({
|
|
4424
|
+
transport: "http",
|
|
4425
|
+
requestedModel: modelId,
|
|
4426
|
+
dispatch: relayDispatch,
|
|
4427
|
+
phase: "complete",
|
|
4428
|
+
provider: route.providerId ?? "relay",
|
|
4429
|
+
routeModel: route.modelId,
|
|
4430
|
+
upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId,
|
|
4431
|
+
outcome: "ok",
|
|
4432
|
+
status: 200
|
|
4433
|
+
});
|
|
4288
4434
|
} catch (err) {
|
|
4289
4435
|
const msg = formatUpstreamError(err);
|
|
4290
4436
|
const status = upstreamHttpStatus(err, msg);
|
|
4437
|
+
audit({
|
|
4438
|
+
transport: "http",
|
|
4439
|
+
requestedModel: modelId,
|
|
4440
|
+
dispatch: relayDispatch,
|
|
4441
|
+
phase: "complete",
|
|
4442
|
+
provider: route.providerId ?? "relay",
|
|
4443
|
+
routeModel: route.modelId,
|
|
4444
|
+
upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId,
|
|
4445
|
+
outcome: "error",
|
|
4446
|
+
status
|
|
4447
|
+
});
|
|
4291
4448
|
if (debug) log14(`sdk error: ${route.modelId}: ${msg}`);
|
|
4292
4449
|
if (status === 429) {
|
|
4293
4450
|
writeResponsesRateLimitStream(modelId, msg, write);
|
|
@@ -4309,9 +4466,31 @@ async function startCodexProxy(routes, options = {}) {
|
|
|
4309
4466
|
});
|
|
4310
4467
|
}
|
|
4311
4468
|
sendJson(res, 200, response);
|
|
4469
|
+
audit({
|
|
4470
|
+
transport: "http",
|
|
4471
|
+
requestedModel: modelId,
|
|
4472
|
+
dispatch: relayDispatch,
|
|
4473
|
+
phase: "complete",
|
|
4474
|
+
provider: route.providerId ?? "relay",
|
|
4475
|
+
routeModel: route.modelId,
|
|
4476
|
+
upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId,
|
|
4477
|
+
outcome: "ok",
|
|
4478
|
+
status: 200
|
|
4479
|
+
});
|
|
4312
4480
|
} catch (err) {
|
|
4313
4481
|
const msg = formatUpstreamError(err);
|
|
4314
4482
|
const status = upstreamHttpStatus(err, msg);
|
|
4483
|
+
audit({
|
|
4484
|
+
transport: "http",
|
|
4485
|
+
requestedModel: modelId,
|
|
4486
|
+
dispatch: relayDispatch,
|
|
4487
|
+
phase: "complete",
|
|
4488
|
+
provider: route.providerId ?? "relay",
|
|
4489
|
+
routeModel: route.modelId,
|
|
4490
|
+
upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId,
|
|
4491
|
+
outcome: "error",
|
|
4492
|
+
status
|
|
4493
|
+
});
|
|
4315
4494
|
if (debug) log14(`sdk error: ${route.modelId}: ${msg}`);
|
|
4316
4495
|
if (status === 429) {
|
|
4317
4496
|
sendJson(res, 200, responsesRateLimitBody(modelId, msg));
|
|
@@ -4436,21 +4615,53 @@ Sec-WebSocket-Accept: ${wsAcceptKey(clientKey)}\r
|
|
|
4436
4615
|
`
|
|
4437
4616
|
);
|
|
4438
4617
|
let frameBuf = Buffer.alloc(0);
|
|
4439
|
-
let
|
|
4618
|
+
let externalActive = false;
|
|
4440
4619
|
let nativeActive = false;
|
|
4441
4620
|
let nativeUpstream;
|
|
4621
|
+
let nativeSendTurn;
|
|
4622
|
+
let socketClosing = false;
|
|
4623
|
+
const externalResponseStates = /* @__PURE__ */ new Map();
|
|
4624
|
+
let currentExternalCompletedResponse;
|
|
4625
|
+
let currentExternalStateInput;
|
|
4626
|
+
let currentExternalConsumedResponseId;
|
|
4442
4627
|
let currentRequestModel = "";
|
|
4443
|
-
const
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4628
|
+
const rememberExternalResponse = (response, input) => {
|
|
4629
|
+
const responseId = typeof response.id === "string" ? response.id : void 0;
|
|
4630
|
+
const output = Array.isArray(response.output) ? response.output : void 0;
|
|
4631
|
+
if (!responseId || !output || response.error) return;
|
|
4632
|
+
externalResponseStates.delete(responseId);
|
|
4633
|
+
externalResponseStates.set(responseId, { input: [...input], output: [...output] });
|
|
4634
|
+
while (externalResponseStates.size > MAX_EXTERNAL_RESPONSE_STATES) {
|
|
4635
|
+
const oldest = externalResponseStates.keys().next().value;
|
|
4636
|
+
if (!oldest) break;
|
|
4637
|
+
externalResponseStates.delete(oldest);
|
|
4447
4638
|
}
|
|
4448
4639
|
};
|
|
4640
|
+
const resolveExternalContinuation = (body) => {
|
|
4641
|
+
const previousResponseId = typeof body.previous_response_id === "string" ? body.previous_response_id : void 0;
|
|
4642
|
+
if (!previousResponseId || !isExternalToolContinuation(body.input)) return { body };
|
|
4643
|
+
const previous = externalResponseStates.get(previousResponseId);
|
|
4644
|
+
if (!previous) return { body, orphanedResponseId: previousResponseId };
|
|
4645
|
+
return {
|
|
4646
|
+
body: {
|
|
4647
|
+
...body,
|
|
4648
|
+
input: [...previous.input, ...previous.output, ...body.input]
|
|
4649
|
+
},
|
|
4650
|
+
consumedResponseId: previousResponseId
|
|
4651
|
+
};
|
|
4652
|
+
};
|
|
4653
|
+
const closeSocket = (code = 1e3) => {
|
|
4654
|
+
if (socketClosing || socket.destroyed) return;
|
|
4655
|
+
socketClosing = true;
|
|
4656
|
+
socket.write(wsCloseFrame(code));
|
|
4657
|
+
socket.end();
|
|
4658
|
+
};
|
|
4449
4659
|
const sendWsEvent = (sseChunk2) => {
|
|
4450
|
-
if (socket.destroyed) return;
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4660
|
+
if (socketClosing || socket.destroyed) return;
|
|
4661
|
+
const completed = captureCompletedResponse(sseChunk2);
|
|
4662
|
+
if (completed) {
|
|
4663
|
+
currentExternalCompletedResponse = completed;
|
|
4664
|
+
if (debug) {
|
|
4454
4665
|
appendCodexBodyDump({
|
|
4455
4666
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4456
4667
|
transport: "ws",
|
|
@@ -4468,7 +4679,6 @@ Sec-WebSocket-Accept: ${wsAcceptKey(clientKey)}\r
|
|
|
4468
4679
|
};
|
|
4469
4680
|
const onData = (chunk) => {
|
|
4470
4681
|
frameBuf = Buffer.concat([frameBuf, chunk]);
|
|
4471
|
-
if (handled && !nativeActive) return;
|
|
4472
4682
|
const frame = wsDecodeFrame(frameBuf);
|
|
4473
4683
|
if (!frame) return;
|
|
4474
4684
|
frameBuf = Buffer.alloc(0);
|
|
@@ -4490,7 +4700,10 @@ Sec-WebSocket-Accept: ${wsAcceptKey(clientKey)}\r
|
|
|
4490
4700
|
socket.end();
|
|
4491
4701
|
return;
|
|
4492
4702
|
}
|
|
4493
|
-
|
|
4703
|
+
if (externalActive) {
|
|
4704
|
+
closeSocket(1008);
|
|
4705
|
+
return;
|
|
4706
|
+
}
|
|
4494
4707
|
void (async () => {
|
|
4495
4708
|
let body;
|
|
4496
4709
|
try {
|
|
@@ -4531,6 +4744,7 @@ data: ${JSON.stringify({ error: { message: "Invalid JSON", type: "invalid_reques
|
|
|
4531
4744
|
log14(`WS subagent dispatch: requested=${modelId} route=${subagentRoute?.modelId ?? "(none)"}`);
|
|
4532
4745
|
}
|
|
4533
4746
|
if (mixedNative && markedSubagent && !subagentRoute) {
|
|
4747
|
+
audit({ transport: "ws", requestedModel: modelId, dispatch: "relay-subagent", phase: "complete", outcome: "error", status: 503 });
|
|
4534
4748
|
sendWsEvent(`event: error
|
|
4535
4749
|
data: ${JSON.stringify({ error: {
|
|
4536
4750
|
message: "Codex marked this request as a Sub-agent, but no configured Codex Sub-agent route is available.",
|
|
@@ -4545,6 +4759,7 @@ data: ${JSON.stringify({ error: {
|
|
|
4545
4759
|
if (!markedSubagent) {
|
|
4546
4760
|
const dispatch = classifyCodexDispatch(modelId, routes, mixedNative.nativeModelIds);
|
|
4547
4761
|
if (dispatch.kind === "unknown") {
|
|
4762
|
+
audit({ transport: "ws", requestedModel: modelId, dispatch: "unknown", phase: "complete", outcome: "error", status: 404 });
|
|
4548
4763
|
sendWsEvent(`event: error
|
|
4549
4764
|
data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "invalid_request_error" } })}
|
|
4550
4765
|
|
|
@@ -4553,6 +4768,15 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "i
|
|
|
4553
4768
|
return;
|
|
4554
4769
|
}
|
|
4555
4770
|
if (dispatch.kind === "native") {
|
|
4771
|
+
audit({
|
|
4772
|
+
transport: "ws",
|
|
4773
|
+
requestedModel: modelId,
|
|
4774
|
+
dispatch: "native",
|
|
4775
|
+
phase: "dispatch",
|
|
4776
|
+
provider: "openai-native",
|
|
4777
|
+
routeModel: modelId,
|
|
4778
|
+
upstreamModel: modelId
|
|
4779
|
+
});
|
|
4556
4780
|
const nativeBody = prepareNativeCodexBody(body);
|
|
4557
4781
|
if (debug && nativeBody !== body) {
|
|
4558
4782
|
log14(`WS native history normalized: model=${modelId} converted Relay compaction for native verification`);
|
|
@@ -4560,7 +4784,7 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "i
|
|
|
4560
4784
|
if (nativeActive && nativeUpstream) {
|
|
4561
4785
|
if (nativeUpstream.readyState === WebSocket.OPEN) {
|
|
4562
4786
|
if (debug) log14(`WS native forwarding next turn: model=${modelId}`);
|
|
4563
|
-
|
|
4787
|
+
nativeSendTurn?.(nativeBody, modelId);
|
|
4564
4788
|
} else if (debug) {
|
|
4565
4789
|
log14(`WS native cannot forward next turn: upstream_state=${nativeUpstream.readyState}`);
|
|
4566
4790
|
}
|
|
@@ -4571,6 +4795,7 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "i
|
|
|
4571
4795
|
let upstream;
|
|
4572
4796
|
let nativeOpened = false;
|
|
4573
4797
|
let nativeCompleted = false;
|
|
4798
|
+
let nativeTurnModelId = modelId;
|
|
4574
4799
|
let nativeFrameCount = 0;
|
|
4575
4800
|
let finished = false;
|
|
4576
4801
|
let connectTimer;
|
|
@@ -4590,10 +4815,24 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "i
|
|
|
4590
4815
|
if (finished) return;
|
|
4591
4816
|
finished = true;
|
|
4592
4817
|
nativeActive = false;
|
|
4818
|
+
nativeSendTurn = void 0;
|
|
4593
4819
|
if (nativeUpstream === upstream) nativeUpstream = void 0;
|
|
4594
4820
|
clearTimers();
|
|
4595
4821
|
if (debug && message) {
|
|
4596
|
-
log14(`WS native upstream failed: model=${
|
|
4822
|
+
log14(`WS native upstream failed: model=${nativeTurnModelId} opened=${nativeOpened} frames=${nativeFrameCount} message=${message}`);
|
|
4823
|
+
}
|
|
4824
|
+
if (message && !nativeCompleted) {
|
|
4825
|
+
audit({
|
|
4826
|
+
transport: "ws",
|
|
4827
|
+
requestedModel: nativeTurnModelId,
|
|
4828
|
+
dispatch: "native",
|
|
4829
|
+
phase: "complete",
|
|
4830
|
+
provider: "openai-native",
|
|
4831
|
+
routeModel: nativeTurnModelId,
|
|
4832
|
+
upstreamModel: nativeTurnModelId,
|
|
4833
|
+
outcome: "error",
|
|
4834
|
+
status: "upstream-failed"
|
|
4835
|
+
});
|
|
4597
4836
|
}
|
|
4598
4837
|
if (message && !nativeCompleted) sendNativeError(message);
|
|
4599
4838
|
try {
|
|
@@ -4602,20 +4841,31 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "i
|
|
|
4602
4841
|
}
|
|
4603
4842
|
closeSocket(closeCode);
|
|
4604
4843
|
};
|
|
4844
|
+
const sendNativeTurn = (turnBody, turnModelId) => {
|
|
4845
|
+
if (!upstream || upstream.readyState !== WebSocket.OPEN) {
|
|
4846
|
+
if (debug) log14(`WS native cannot send turn: model=${turnModelId} upstream_state=${upstream?.readyState ?? "missing"}`);
|
|
4847
|
+
return;
|
|
4848
|
+
}
|
|
4849
|
+
nativeTurnModelId = turnModelId;
|
|
4850
|
+
nativeCompleted = false;
|
|
4851
|
+
if (firstFrameTimer) clearTimeout(firstFrameTimer);
|
|
4852
|
+
upstream.send(JSON.stringify({ type: "response.create", ...turnBody }));
|
|
4853
|
+
firstFrameTimer = setTimeout(() => closeBoth("Native Codex WebSocket response timed out"), 6e4);
|
|
4854
|
+
};
|
|
4605
4855
|
try {
|
|
4606
4856
|
if (debug) {
|
|
4607
4857
|
log14(`WS native connecting: model=${modelId} url=${target.url} headers=[${Object.keys(target.headers).join(",")}]`);
|
|
4608
4858
|
}
|
|
4609
4859
|
upstream = new WebSocket(target.url, { headers: target.headers });
|
|
4610
4860
|
nativeUpstream = upstream;
|
|
4861
|
+
nativeSendTurn = sendNativeTurn;
|
|
4611
4862
|
nativeActive = true;
|
|
4612
4863
|
connectTimer = setTimeout(() => closeBoth("Native Codex WebSocket connection timed out"), 15e3);
|
|
4613
4864
|
upstream.once("open", () => {
|
|
4614
4865
|
nativeOpened = true;
|
|
4615
4866
|
if (connectTimer) clearTimeout(connectTimer);
|
|
4616
4867
|
if (debug) log14(`WS native upstream open: model=${modelId}`);
|
|
4617
|
-
|
|
4618
|
-
firstFrameTimer = setTimeout(() => closeBoth("Native Codex WebSocket response timed out"), 6e4);
|
|
4868
|
+
sendNativeTurn(nativeBody, modelId);
|
|
4619
4869
|
});
|
|
4620
4870
|
upstream.once("unexpected-response", (_request, response) => {
|
|
4621
4871
|
if (debug) log14(`WS native upstream HTTP rejection: model=${modelId} status=${response.statusCode}`);
|
|
@@ -4633,6 +4883,17 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "i
|
|
|
4633
4883
|
if (typeof parsed.type === "string") eventType = parsed.type;
|
|
4634
4884
|
if (eventType === "response.completed" || eventType === "response.failed" || eventType === "response.incomplete") {
|
|
4635
4885
|
nativeCompleted = true;
|
|
4886
|
+
audit({
|
|
4887
|
+
transport: "ws",
|
|
4888
|
+
requestedModel: modelId,
|
|
4889
|
+
dispatch: "native",
|
|
4890
|
+
phase: "complete",
|
|
4891
|
+
provider: "openai-native",
|
|
4892
|
+
routeModel: modelId,
|
|
4893
|
+
upstreamModel: modelId,
|
|
4894
|
+
outcome: eventType === "response.completed" ? "ok" : "error",
|
|
4895
|
+
status: eventType
|
|
4896
|
+
});
|
|
4636
4897
|
}
|
|
4637
4898
|
} catch {
|
|
4638
4899
|
}
|
|
@@ -4653,6 +4914,7 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "i
|
|
|
4653
4914
|
if (debug) log14(`WS native downstream close: model=${modelId} frames=${nativeFrameCount} completed=${nativeCompleted}`);
|
|
4654
4915
|
finished = true;
|
|
4655
4916
|
nativeActive = false;
|
|
4917
|
+
nativeSendTurn = void 0;
|
|
4656
4918
|
if (nativeUpstream === upstream) nativeUpstream = void 0;
|
|
4657
4919
|
clearTimers();
|
|
4658
4920
|
try {
|
|
@@ -4667,6 +4929,7 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "i
|
|
|
4667
4929
|
}
|
|
4668
4930
|
}
|
|
4669
4931
|
}
|
|
4932
|
+
externalActive = true;
|
|
4670
4933
|
let resolved = subagentRoute ? resolveModel(routes, models, subagentRoute.modelId) : resolveModel(routes, models, modelId);
|
|
4671
4934
|
if (!resolved) {
|
|
4672
4935
|
const fb = routes[0];
|
|
@@ -4685,13 +4948,35 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}` } })}
|
|
|
4685
4948
|
}
|
|
4686
4949
|
}
|
|
4687
4950
|
const { route, languageModel } = resolved;
|
|
4951
|
+
const relayDispatch = markedSubagent ? "relay-subagent" : "relay";
|
|
4952
|
+
audit({
|
|
4953
|
+
transport: "ws",
|
|
4954
|
+
requestedModel: modelId,
|
|
4955
|
+
dispatch: relayDispatch,
|
|
4956
|
+
phase: "dispatch",
|
|
4957
|
+
provider: route.providerId ?? "relay",
|
|
4958
|
+
routeModel: route.modelId,
|
|
4959
|
+
upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId
|
|
4960
|
+
});
|
|
4961
|
+
currentExternalCompletedResponse = void 0;
|
|
4962
|
+
currentExternalStateInput = void 0;
|
|
4963
|
+
currentExternalConsumedResponseId = void 0;
|
|
4964
|
+
const continuation = resolveExternalContinuation(body);
|
|
4965
|
+
if (continuation.orphanedResponseId) {
|
|
4966
|
+
if (debug) log14(`WS continuation rejected: unknown previous_response_id=${continuation.orphanedResponseId}`);
|
|
4967
|
+
writeResponsesErrorStream(modelId, "Unknown or expired previous_response_id", sendWsEvent, 400);
|
|
4968
|
+
externalActive = false;
|
|
4969
|
+
return;
|
|
4970
|
+
}
|
|
4688
4971
|
try {
|
|
4689
|
-
const routedBody = await prepareExternalCodexBody(body, {
|
|
4972
|
+
const routedBody = await prepareExternalCodexBody(continuation.body, {
|
|
4690
4973
|
relay: nativePayloadRelay,
|
|
4691
4974
|
mixedNative,
|
|
4692
4975
|
headers: req.headers
|
|
4693
4976
|
});
|
|
4694
|
-
|
|
4977
|
+
currentExternalStateInput = responsesInputItems(routedBody.input);
|
|
4978
|
+
currentExternalConsumedResponseId = continuation.consumedResponseId;
|
|
4979
|
+
let params = applyClaudeCodeOAuthIdentity(route, applyExternalCodexRuntimeIdentity(translateResponsesRequest(
|
|
4695
4980
|
routedBody,
|
|
4696
4981
|
route.npm,
|
|
4697
4982
|
{
|
|
@@ -4703,7 +4988,7 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}` } })}
|
|
|
4703
4988
|
upstreamModelId: route.upstreamModelId
|
|
4704
4989
|
},
|
|
4705
4990
|
{ maxTools: maxToolsForNpm(route.npm) }
|
|
4706
|
-
));
|
|
4991
|
+
), route));
|
|
4707
4992
|
if (route.contextWindow && route.contextWindow > 0) {
|
|
4708
4993
|
const before = params.messages.length;
|
|
4709
4994
|
const estimatedChars = estimateCodexRequestChars(params);
|
|
@@ -4736,9 +5021,37 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}` } })}
|
|
|
4736
5021
|
log14(`WS response progress: model=${route.modelId} elapsedMs=${progress.elapsedMs} reasoningChars=${progress.reasoningChars} textChars=${progress.textChars} toolCalls=${progress.toolCallCount} reasoningTail=${JSON.stringify(progress.reasoningTail)}`);
|
|
4737
5022
|
}
|
|
4738
5023
|
});
|
|
5024
|
+
if (currentExternalCompletedResponse && currentExternalStateInput) {
|
|
5025
|
+
if (currentExternalConsumedResponseId) {
|
|
5026
|
+
externalResponseStates.delete(currentExternalConsumedResponseId);
|
|
5027
|
+
}
|
|
5028
|
+
rememberExternalResponse(currentExternalCompletedResponse, currentExternalStateInput);
|
|
5029
|
+
}
|
|
5030
|
+
audit({
|
|
5031
|
+
transport: "ws",
|
|
5032
|
+
requestedModel: modelId,
|
|
5033
|
+
dispatch: relayDispatch,
|
|
5034
|
+
phase: "complete",
|
|
5035
|
+
provider: route.providerId ?? "relay",
|
|
5036
|
+
routeModel: route.modelId,
|
|
5037
|
+
upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId,
|
|
5038
|
+
outcome: "ok",
|
|
5039
|
+
status: "response.completed"
|
|
5040
|
+
});
|
|
4739
5041
|
} catch (err) {
|
|
4740
5042
|
const msg = formatUpstreamError(err);
|
|
4741
5043
|
const status = upstreamHttpStatus(err, msg);
|
|
5044
|
+
audit({
|
|
5045
|
+
transport: "ws",
|
|
5046
|
+
requestedModel: modelId,
|
|
5047
|
+
dispatch: relayDispatch,
|
|
5048
|
+
phase: "complete",
|
|
5049
|
+
provider: route.providerId ?? "relay",
|
|
5050
|
+
routeModel: route.modelId,
|
|
5051
|
+
upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId,
|
|
5052
|
+
outcome: "error",
|
|
5053
|
+
status
|
|
5054
|
+
});
|
|
4742
5055
|
if (debug) log14(`WS sdk error: ${route.modelId}: ${msg}`);
|
|
4743
5056
|
if (status === 429) {
|
|
4744
5057
|
writeResponsesRateLimitStream(modelId, msg, sendWsEvent);
|
|
@@ -4746,10 +5059,11 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}` } })}
|
|
|
4746
5059
|
writeResponsesErrorStream(modelId, msg, sendWsEvent, status);
|
|
4747
5060
|
}
|
|
4748
5061
|
}
|
|
4749
|
-
|
|
5062
|
+
externalActive = false;
|
|
4750
5063
|
})();
|
|
4751
5064
|
};
|
|
4752
5065
|
socket.on("error", () => socket.destroy());
|
|
5066
|
+
socket.once("close", () => externalResponseStates.clear());
|
|
4753
5067
|
socket.on("data", onData);
|
|
4754
5068
|
onData(head);
|
|
4755
5069
|
});
|
|
@@ -4774,44 +5088,44 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}` } })}
|
|
|
4774
5088
|
}
|
|
4775
5089
|
|
|
4776
5090
|
// src/codex/profile.ts
|
|
4777
|
-
import { join as
|
|
5091
|
+
import { join as join4 } from "path";
|
|
4778
5092
|
|
|
4779
5093
|
// src/codex/session.ts
|
|
4780
5094
|
import {
|
|
4781
5095
|
copyFileSync,
|
|
4782
|
-
chmodSync,
|
|
5096
|
+
chmodSync as chmodSync2,
|
|
4783
5097
|
existsSync as existsSync3,
|
|
4784
|
-
mkdirSync,
|
|
5098
|
+
mkdirSync as mkdirSync2,
|
|
4785
5099
|
readdirSync,
|
|
4786
5100
|
readFileSync as readFileSync2,
|
|
4787
5101
|
renameSync,
|
|
4788
5102
|
rmSync,
|
|
4789
5103
|
statSync,
|
|
4790
5104
|
unlinkSync,
|
|
4791
|
-
writeFileSync
|
|
5105
|
+
writeFileSync as writeFileSync2
|
|
4792
5106
|
} from "fs";
|
|
4793
5107
|
import { homedir as homedir3 } from "os";
|
|
4794
|
-
import { basename, dirname, join as
|
|
5108
|
+
import { basename, dirname, join as join3 } from "path";
|
|
4795
5109
|
var CODEX_PROFILE_NAME = "relay-ai-launch";
|
|
4796
5110
|
var STALE_SESSION_MS = 5 * 60 * 1e3;
|
|
4797
5111
|
var MAX_BACKUPS = 5;
|
|
4798
5112
|
function getCodexHome(env = process.env) {
|
|
4799
|
-
return env["CODEX_HOME"] ||
|
|
5113
|
+
return env["CODEX_HOME"] || join3(homedir3(), ".codex");
|
|
4800
5114
|
}
|
|
4801
5115
|
function getCodexProfilePath() {
|
|
4802
|
-
return
|
|
5116
|
+
return join3(getCodexHome(), `${CODEX_PROFILE_NAME}.config.toml`);
|
|
4803
5117
|
}
|
|
4804
5118
|
function getRelayAiCodexDir(env = process.env) {
|
|
4805
|
-
return
|
|
5119
|
+
return join3(getAppHome(env), "codex");
|
|
4806
5120
|
}
|
|
4807
5121
|
function getSessionLockPath(env = process.env) {
|
|
4808
|
-
return
|
|
5122
|
+
return join3(getRelayAiCodexDir(env), "session.json");
|
|
4809
5123
|
}
|
|
4810
5124
|
function getBackupsDir(env = process.env) {
|
|
4811
|
-
return
|
|
5125
|
+
return join3(getRelayAiCodexDir(env), "backups");
|
|
4812
5126
|
}
|
|
4813
5127
|
function getCatalogPath(providerId, env = process.env) {
|
|
4814
|
-
return
|
|
5128
|
+
return join3(getRelayAiCodexDir(env), `models-${providerId}.json`);
|
|
4815
5129
|
}
|
|
4816
5130
|
function ownedOverlayPaths(env = process.env) {
|
|
4817
5131
|
const paths = [getCodexProfilePath()];
|
|
@@ -4819,15 +5133,15 @@ function ownedOverlayPaths(env = process.env) {
|
|
|
4819
5133
|
if (existsSync3(codexDir)) {
|
|
4820
5134
|
for (const name of readdirSync(codexDir)) {
|
|
4821
5135
|
if (name.startsWith("models-") && name.endsWith(".json")) {
|
|
4822
|
-
paths.push(
|
|
5136
|
+
paths.push(join3(codexDir, name));
|
|
4823
5137
|
}
|
|
4824
5138
|
}
|
|
4825
5139
|
}
|
|
4826
|
-
const agentsDir =
|
|
5140
|
+
const agentsDir = join3(getCodexHome(env), "agents");
|
|
4827
5141
|
if (existsSync3(agentsDir)) {
|
|
4828
5142
|
for (const name of readdirSync(agentsDir)) {
|
|
4829
5143
|
if (/^relay-model-[a-z0-9-]+\.toml$/i.test(name)) {
|
|
4830
|
-
paths.push(
|
|
5144
|
+
paths.push(join3(agentsDir, name));
|
|
4831
5145
|
}
|
|
4832
5146
|
}
|
|
4833
5147
|
}
|
|
@@ -4835,27 +5149,27 @@ function ownedOverlayPaths(env = process.env) {
|
|
|
4835
5149
|
return paths;
|
|
4836
5150
|
}
|
|
4837
5151
|
function atomicWriteFile(path3, content) {
|
|
4838
|
-
|
|
5152
|
+
mkdirSync2(dirname(path3), { recursive: true });
|
|
4839
5153
|
const tmp = `${path3}.tmp.${process.pid}`;
|
|
4840
|
-
|
|
5154
|
+
writeFileSync2(tmp, content, { encoding: "utf8", mode: 384 });
|
|
4841
5155
|
renameSync(tmp, path3);
|
|
4842
5156
|
try {
|
|
4843
|
-
|
|
5157
|
+
chmodSync2(path3, 384);
|
|
4844
5158
|
} catch {
|
|
4845
5159
|
}
|
|
4846
5160
|
}
|
|
4847
5161
|
function rotateBackups(filePath, env = process.env) {
|
|
4848
5162
|
if (!existsSync3(filePath)) return;
|
|
4849
5163
|
const backupsDir = getBackupsDir(env);
|
|
4850
|
-
|
|
5164
|
+
mkdirSync2(backupsDir, { recursive: true });
|
|
4851
5165
|
const base = basename(filePath);
|
|
4852
5166
|
const stamp = Date.now();
|
|
4853
|
-
const backupPath =
|
|
5167
|
+
const backupPath = join3(backupsDir, `${base}.${stamp}.bak`);
|
|
4854
5168
|
copyFileSync(filePath, backupPath);
|
|
4855
|
-
const backups = readdirSync(backupsDir).filter((n) => n.startsWith(`${base}.`) && n.endsWith(".bak")).map((n) => ({ name: n, mtime: statSync(
|
|
5169
|
+
const backups = readdirSync(backupsDir).filter((n) => n.startsWith(`${base}.`) && n.endsWith(".bak")).map((n) => ({ name: n, mtime: statSync(join3(backupsDir, n)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
|
|
4856
5170
|
for (const old of backups.slice(MAX_BACKUPS)) {
|
|
4857
5171
|
try {
|
|
4858
|
-
unlinkSync(
|
|
5172
|
+
unlinkSync(join3(backupsDir, old.name));
|
|
4859
5173
|
} catch {
|
|
4860
5174
|
}
|
|
4861
5175
|
}
|
|
@@ -4876,7 +5190,7 @@ function readSessionLock(env = process.env) {
|
|
|
4876
5190
|
}
|
|
4877
5191
|
function writeSessionLock(lock, env = process.env) {
|
|
4878
5192
|
const path3 = getSessionLockPath(env);
|
|
4879
|
-
|
|
5193
|
+
mkdirSync2(getRelayAiCodexDir(env), { recursive: true });
|
|
4880
5194
|
atomicWriteFile(path3, `${JSON.stringify(lock, null, 2)}
|
|
4881
5195
|
`);
|
|
4882
5196
|
}
|
|
@@ -4999,10 +5313,10 @@ function getCatalogOutputPath(providerId) {
|
|
|
4999
5313
|
return getCatalogPath(providerId);
|
|
5000
5314
|
}
|
|
5001
5315
|
function getFavoritesCatalogPath() {
|
|
5002
|
-
return
|
|
5316
|
+
return join4(getRelayAiCodexDir(), "models-favorites.json");
|
|
5003
5317
|
}
|
|
5004
5318
|
function getFavoritesAppCatalogPath() {
|
|
5005
|
-
return
|
|
5319
|
+
return join4(getRelayAiCodexDir(), "app-models-favorites.json");
|
|
5006
5320
|
}
|
|
5007
5321
|
function profileName() {
|
|
5008
5322
|
return CODEX_PROFILE_NAME;
|
|
@@ -5013,7 +5327,7 @@ import { execSync as execSync2 } from "child_process";
|
|
|
5013
5327
|
import spawn2 from "cross-spawn";
|
|
5014
5328
|
import { existsSync as existsSync4 } from "fs";
|
|
5015
5329
|
import { homedir as homedir4 } from "os";
|
|
5016
|
-
import { join as
|
|
5330
|
+
import { join as join5 } from "path";
|
|
5017
5331
|
var isWindows2 = process.platform === "win32";
|
|
5018
5332
|
var CODEX_CI_ENV_VARS = [
|
|
5019
5333
|
"CI",
|
|
@@ -5034,11 +5348,11 @@ function stripCodexInheritedEnv(env) {
|
|
|
5034
5348
|
return out;
|
|
5035
5349
|
}
|
|
5036
5350
|
var CODEX_FALLBACK_PATHS = isWindows2 ? [
|
|
5037
|
-
|
|
5038
|
-
|
|
5351
|
+
join5(process.env["APPDATA"] ?? homedir4(), "npm", "codex.cmd"),
|
|
5352
|
+
join5(process.env["APPDATA"] ?? homedir4(), "npm", "codex")
|
|
5039
5353
|
] : [
|
|
5040
|
-
|
|
5041
|
-
|
|
5354
|
+
join5(homedir4(), ".local", "bin", "codex"),
|
|
5355
|
+
join5(homedir4(), ".npm", "bin", "codex"),
|
|
5042
5356
|
"/usr/local/bin/codex",
|
|
5043
5357
|
"/opt/homebrew/bin/codex"
|
|
5044
5358
|
];
|
|
@@ -5541,7 +5855,8 @@ async function resolveCodexMixedModels(input) {
|
|
|
5541
5855
|
subagents: subagentResult.resolved,
|
|
5542
5856
|
all,
|
|
5543
5857
|
providersById: new Map(input.compatible.map((provider) => [provider.id, provider])),
|
|
5544
|
-
dropped: [...visibleResult.droppedFavorites, ...subagentResult.droppedFavorites]
|
|
5858
|
+
dropped: [...visibleResult.droppedFavorites, ...subagentResult.droppedFavorites],
|
|
5859
|
+
capacitySkipped: [...visibleResult.capacitySkippedFavorites, ...subagentResult.capacitySkippedFavorites]
|
|
5545
5860
|
};
|
|
5546
5861
|
}
|
|
5547
5862
|
|
|
@@ -5741,6 +6056,7 @@ async function prepareCodexMixedRelayRoutes(models, trace = false) {
|
|
|
5741
6056
|
apiKey: backend.token,
|
|
5742
6057
|
baseURL: `http://127.0.0.1:${backend.port}`,
|
|
5743
6058
|
upstreamModelId: proxyRoute.aliasId,
|
|
6059
|
+
auditUpstreamModelId: original.model.upstreamModelId || original.model.id,
|
|
5744
6060
|
providerId: original.providerId,
|
|
5745
6061
|
authType: "oauth",
|
|
5746
6062
|
oauthAccountId: original.oauthAccountId,
|
|
@@ -6071,7 +6387,7 @@ async function writeFavoritesLaunchArtifacts(resolved, starting, proxyPort) {
|
|
|
6071
6387
|
return { profilePath, catalogPath };
|
|
6072
6388
|
}
|
|
6073
6389
|
async function writeMixedLaunchArtifacts(plan, proxyPort) {
|
|
6074
|
-
const catalogPath =
|
|
6390
|
+
const catalogPath = join6(getRelayAiCodexDir(), "models-mixed.json");
|
|
6075
6391
|
writeOverlayFile(catalogPath, serializeCatalog(plan.catalog));
|
|
6076
6392
|
const profilePath = getProfileOutputPath();
|
|
6077
6393
|
writeOverlayFile(profilePath, buildCodexMixedProfileToml({
|
|
@@ -6656,17 +6972,17 @@ import * as p10 from "@clack/prompts";
|
|
|
6656
6972
|
|
|
6657
6973
|
// src/gemini/launch.ts
|
|
6658
6974
|
import { spawn as spawn3 } from "child_process";
|
|
6659
|
-
import { existsSync as existsSync5, mkdirSync as
|
|
6975
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, mkdtempSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
6660
6976
|
import { homedir as homedir5, tmpdir } from "os";
|
|
6661
|
-
import { join as
|
|
6977
|
+
import { join as join7 } from "path";
|
|
6662
6978
|
var isWindows3 = process.platform === "win32";
|
|
6663
6979
|
var GEMINI_API_KEY_AUTH_TYPE = "gemini-api-key";
|
|
6664
6980
|
var GEMINI_FALLBACK_PATHS = isWindows3 ? [
|
|
6665
|
-
|
|
6666
|
-
|
|
6981
|
+
join7(process.env["APPDATA"] ?? homedir5(), "npm", "gemini.cmd"),
|
|
6982
|
+
join7(process.env["APPDATA"] ?? homedir5(), "npm", "gemini")
|
|
6667
6983
|
] : [
|
|
6668
|
-
|
|
6669
|
-
|
|
6984
|
+
join7(homedir5(), ".local", "bin", "gemini"),
|
|
6985
|
+
join7(homedir5(), ".npm", "bin", "gemini"),
|
|
6670
6986
|
"/usr/local/bin/gemini",
|
|
6671
6987
|
"/opt/homebrew/bin/gemini"
|
|
6672
6988
|
];
|
|
@@ -6687,7 +7003,7 @@ function buildGeminiChildEnv(proxyPort, proxyToken) {
|
|
|
6687
7003
|
return env;
|
|
6688
7004
|
}
|
|
6689
7005
|
function createGeminiCliHomeOverlay() {
|
|
6690
|
-
const cliHome = mkdtempSync(
|
|
7006
|
+
const cliHome = mkdtempSync(join7(tmpdir(), "relay-ai-gemini-"));
|
|
6691
7007
|
const settings = {
|
|
6692
7008
|
security: {
|
|
6693
7009
|
auth: {
|
|
@@ -6695,9 +7011,9 @@ function createGeminiCliHomeOverlay() {
|
|
|
6695
7011
|
}
|
|
6696
7012
|
}
|
|
6697
7013
|
};
|
|
6698
|
-
const geminiDir =
|
|
6699
|
-
|
|
6700
|
-
|
|
7014
|
+
const geminiDir = join7(cliHome, ".gemini");
|
|
7015
|
+
mkdirSync3(geminiDir);
|
|
7016
|
+
writeFileSync3(join7(geminiDir, "settings.json"), `${JSON.stringify(settings, null, 2)}
|
|
6701
7017
|
`, {
|
|
6702
7018
|
encoding: "utf8",
|
|
6703
7019
|
mode: 384
|
|
@@ -9939,15 +10255,15 @@ import { execFileSync, execSync as execSync3 } from "child_process";
|
|
|
9939
10255
|
import spawn4 from "cross-spawn";
|
|
9940
10256
|
import { existsSync as existsSync6 } from "fs";
|
|
9941
10257
|
import { homedir as homedir6 } from "os";
|
|
9942
|
-
import { join as
|
|
10258
|
+
import { join as join8 } from "path";
|
|
9943
10259
|
var isWindows4 = process.platform === "win32";
|
|
9944
10260
|
var FALLBACK_PATHS = isWindows4 ? [
|
|
9945
|
-
|
|
9946
|
-
|
|
9947
|
-
|
|
10261
|
+
join8(process.env["APPDATA"] ?? homedir6(), "npm", "agy.cmd"),
|
|
10262
|
+
join8(process.env["APPDATA"] ?? homedir6(), "npm", "agy"),
|
|
10263
|
+
join8(homedir6(), "AppData", "Roaming", "npm", "agy.cmd")
|
|
9948
10264
|
] : [
|
|
9949
|
-
|
|
9950
|
-
|
|
10265
|
+
join8(homedir6(), ".local", "bin", "agy"),
|
|
10266
|
+
join8(homedir6(), ".npm", "bin", "agy"),
|
|
9951
10267
|
"/usr/local/bin/agy",
|
|
9952
10268
|
"/opt/homebrew/bin/agy"
|
|
9953
10269
|
];
|
|
@@ -10025,7 +10341,7 @@ function launchAntigravityCli(env, extraArgs) {
|
|
|
10025
10341
|
import { execFileSync as execFileSync2, execSync as execSync4, spawn as spawn5 } from "child_process";
|
|
10026
10342
|
import { existsSync as existsSync7 } from "fs";
|
|
10027
10343
|
import { homedir as homedir7 } from "os";
|
|
10028
|
-
import { join as
|
|
10344
|
+
import { join as join9 } from "path";
|
|
10029
10345
|
|
|
10030
10346
|
// src/antigravity/ide-profile.ts
|
|
10031
10347
|
import fs from "fs";
|
|
@@ -10059,8 +10375,8 @@ function prepareIdeProfile(profileDir, gatewayUrl) {
|
|
|
10059
10375
|
}
|
|
10060
10376
|
|
|
10061
10377
|
// src/antigravity/launch-ide.ts
|
|
10062
|
-
var LINUX_APP_PROFILE_DIR =
|
|
10063
|
-
var LINUX_IDE_PROFILE_DIR =
|
|
10378
|
+
var LINUX_APP_PROFILE_DIR = join9(homedir7(), ".relay-ai", "antigravity", "app-profile");
|
|
10379
|
+
var LINUX_IDE_PROFILE_DIR = join9(homedir7(), ".relay-ai", "antigravity", "profile");
|
|
10064
10380
|
function sleep(ms) {
|
|
10065
10381
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
10066
10382
|
}
|
|
@@ -10068,7 +10384,7 @@ function linuxAntigravityBinary() {
|
|
|
10068
10384
|
const candidates = [
|
|
10069
10385
|
"/usr/share/antigravity/antigravity",
|
|
10070
10386
|
"/opt/antigravity/antigravity",
|
|
10071
|
-
|
|
10387
|
+
join9(homedir7(), ".local", "share", "antigravity", "antigravity")
|
|
10072
10388
|
];
|
|
10073
10389
|
for (const candidate of candidates) {
|
|
10074
10390
|
if (existsSync7(candidate)) return candidate;
|
|
@@ -10223,15 +10539,15 @@ function findAntigravityAppBinary() {
|
|
|
10223
10539
|
const override = getAppPathOverride("antigravity");
|
|
10224
10540
|
if (override) return existsSync7(override) ? override : null;
|
|
10225
10541
|
if (process.platform === "win32") {
|
|
10226
|
-
const localAppData = process.env["LOCALAPPDATA"] ??
|
|
10227
|
-
const winPath =
|
|
10542
|
+
const localAppData = process.env["LOCALAPPDATA"] ?? join9(homedir7(), "AppData", "Local");
|
|
10543
|
+
const winPath = join9(localAppData, "Programs", "Antigravity", "Antigravity.exe");
|
|
10228
10544
|
return existsSync7(winPath) ? winPath : null;
|
|
10229
10545
|
}
|
|
10230
10546
|
if (process.platform === "linux") return linuxAntigravityBinary();
|
|
10231
10547
|
if (process.platform !== "darwin") return null;
|
|
10232
10548
|
const defaultPath = "/Applications/Antigravity.app/Contents/MacOS/Antigravity";
|
|
10233
10549
|
if (existsSync7(defaultPath)) return defaultPath;
|
|
10234
|
-
const homePath =
|
|
10550
|
+
const homePath = join9(homedir7(), "Applications", "Antigravity.app", "Contents", "MacOS", "Antigravity");
|
|
10235
10551
|
if (existsSync7(homePath)) return homePath;
|
|
10236
10552
|
return null;
|
|
10237
10553
|
}
|
|
@@ -10239,15 +10555,15 @@ function findAntigravityIdeBinary() {
|
|
|
10239
10555
|
const override = getAppPathOverride("antigravity-ide");
|
|
10240
10556
|
if (override) return existsSync7(override) ? override : null;
|
|
10241
10557
|
if (process.platform === "win32") {
|
|
10242
|
-
const localAppData = process.env["LOCALAPPDATA"] ??
|
|
10243
|
-
const winPath =
|
|
10558
|
+
const localAppData = process.env["LOCALAPPDATA"] ?? join9(homedir7(), "AppData", "Local");
|
|
10559
|
+
const winPath = join9(localAppData, "Programs", "Antigravity IDE", "Antigravity IDE.exe");
|
|
10244
10560
|
return existsSync7(winPath) ? winPath : null;
|
|
10245
10561
|
}
|
|
10246
10562
|
if (process.platform === "linux") return linuxAntigravityBinary();
|
|
10247
10563
|
if (process.platform !== "darwin") return null;
|
|
10248
10564
|
const defaultPath = "/Applications/Antigravity IDE.app/Contents/Resources/app/bin/antigravity-ide";
|
|
10249
10565
|
if (existsSync7(defaultPath)) return defaultPath;
|
|
10250
|
-
const homePath =
|
|
10566
|
+
const homePath = join9(homedir7(), "Applications", "Antigravity IDE.app", "Contents", "Resources", "app", "bin", "antigravity-ide");
|
|
10251
10567
|
if (existsSync7(homePath)) return homePath;
|
|
10252
10568
|
return null;
|
|
10253
10569
|
}
|
|
@@ -10308,7 +10624,7 @@ function launchAntigravityIde(env, profileDir, gatewayUrl, extraArgs) {
|
|
|
10308
10624
|
return;
|
|
10309
10625
|
}
|
|
10310
10626
|
prepareIdeProfile(profileDir, gatewayUrl);
|
|
10311
|
-
const relayExtensionsDir =
|
|
10627
|
+
const relayExtensionsDir = join9(homedir7(), ".relay-ai", "antigravity", "extensions");
|
|
10312
10628
|
const args = [
|
|
10313
10629
|
`--user-data-dir=${profileDir}`,
|
|
10314
10630
|
`--extensions-dir=${relayExtensionsDir}`,
|
|
@@ -10338,7 +10654,7 @@ function launchAntigravityIde(env, profileDir, gatewayUrl, extraArgs) {
|
|
|
10338
10654
|
|
|
10339
10655
|
// src/antigravity.ts
|
|
10340
10656
|
import { homedir as homedir8 } from "os";
|
|
10341
|
-
import { join as
|
|
10657
|
+
import { join as join10 } from "path";
|
|
10342
10658
|
var SHUTDOWN_DRAIN_MS = 500;
|
|
10343
10659
|
var AGY_FAVORITES_PROVIDER_ID = "__relay_agy_favorites__";
|
|
10344
10660
|
var AGY_FAVORITES_PROVIDER_LABEL = "\u2605 Antigravity CLI Favorites";
|
|
@@ -10632,7 +10948,7 @@ async function runAntigravityAppCommand(childArgs, trace = false, boot) {
|
|
|
10632
10948
|
trace,
|
|
10633
10949
|
boot,
|
|
10634
10950
|
async (env, _routes, gatewayHandle) => {
|
|
10635
|
-
const profileDir =
|
|
10951
|
+
const profileDir = join10(homedir8(), ".relay-ai", "antigravity", "app-profile");
|
|
10636
10952
|
if (isAntigravityAppRunning(profileDir)) {
|
|
10637
10953
|
const restart = await p11.confirm({
|
|
10638
10954
|
message: "Restart Antigravity to apply this Relay gateway?",
|
|
@@ -10680,7 +10996,7 @@ async function runAntigravityIdeCommand(childArgs, trace = false, boot) {
|
|
|
10680
10996
|
trace,
|
|
10681
10997
|
boot,
|
|
10682
10998
|
async (env, _routes, gatewayHandle) => {
|
|
10683
|
-
const profileDir =
|
|
10999
|
+
const profileDir = join10(homedir8(), ".relay-ai", "antigravity", "profile");
|
|
10684
11000
|
if (isAntigravityIdeRunning(profileDir)) {
|
|
10685
11001
|
const restart = await p11.confirm({
|
|
10686
11002
|
message: "Restart Antigravity IDE to apply this Relay gateway?",
|
|
@@ -10725,7 +11041,7 @@ async function runAntigravityIdeCommand(childArgs, trace = false, boot) {
|
|
|
10725
11041
|
// src/codex-app.ts
|
|
10726
11042
|
import pc10 from "picocolors";
|
|
10727
11043
|
import * as p12 from "@clack/prompts";
|
|
10728
|
-
import { join as
|
|
11044
|
+
import { join as join13 } from "path";
|
|
10729
11045
|
|
|
10730
11046
|
// src/codex/app-provider-routes.ts
|
|
10731
11047
|
function codexRouteToProxyRoute(provider, model, apiKey) {
|
|
@@ -10814,14 +11130,14 @@ async function buildCodexAppProviderCatalogRoutes(provider, apiKey, selectedMode
|
|
|
10814
11130
|
}
|
|
10815
11131
|
|
|
10816
11132
|
// src/codex/app-config.ts
|
|
10817
|
-
import { existsSync as existsSync8, readFileSync as readFileSync3, rmSync as rmSync3, writeFileSync as
|
|
10818
|
-
import { dirname as dirname2, join as
|
|
11133
|
+
import { existsSync as existsSync8, readFileSync as readFileSync3, rmSync as rmSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
|
|
11134
|
+
import { dirname as dirname2, join as join11 } from "path";
|
|
10819
11135
|
import { parse, stringify } from "smol-toml";
|
|
10820
11136
|
function getCodexConfigPath() {
|
|
10821
|
-
return
|
|
11137
|
+
return join11(getCodexHome(), "config.toml");
|
|
10822
11138
|
}
|
|
10823
11139
|
function getCodexAppSidecarProfilePath() {
|
|
10824
|
-
return
|
|
11140
|
+
return join11(getCodexHome(), `${CODEX_APP_PROVIDER_ID}.config.toml`);
|
|
10825
11141
|
}
|
|
10826
11142
|
function asRecord(value) {
|
|
10827
11143
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
@@ -10996,8 +11312,13 @@ function applyAppConfigPatch(spec, configPath = getCodexConfigPath()) {
|
|
|
10996
11312
|
const text5 = `${stringify(merged)}
|
|
10997
11313
|
`;
|
|
10998
11314
|
validateAppConfigText(text5, spec);
|
|
10999
|
-
|
|
11000
|
-
|
|
11315
|
+
mkdirSync4(dirname2(configPath), { recursive: true });
|
|
11316
|
+
atomicWriteFile(configPath, text5);
|
|
11317
|
+
const written = readCodexConfigText(configPath);
|
|
11318
|
+
if (written !== text5) {
|
|
11319
|
+
throw new Error(`Codex config readback mismatch at ${configPath}`);
|
|
11320
|
+
}
|
|
11321
|
+
validateAppConfigText(written, spec);
|
|
11001
11322
|
return text5;
|
|
11002
11323
|
}
|
|
11003
11324
|
function applyRestoreKey(config, key, had, value) {
|
|
@@ -11053,7 +11374,7 @@ function restoreConfigFromState(state, configPath = getCodexConfigPath()) {
|
|
|
11053
11374
|
rmSync3(configPath, { force: true });
|
|
11054
11375
|
return true;
|
|
11055
11376
|
}
|
|
11056
|
-
|
|
11377
|
+
writeFileSync4(configPath, `${stringify(config)}
|
|
11057
11378
|
`, "utf8");
|
|
11058
11379
|
return true;
|
|
11059
11380
|
}
|
|
@@ -11064,31 +11385,69 @@ function previewAppConfigToml(spec) {
|
|
|
11064
11385
|
return text5;
|
|
11065
11386
|
}
|
|
11066
11387
|
|
|
11388
|
+
// src/codex/app-readiness.ts
|
|
11389
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
11390
|
+
function proxyRoot(spec) {
|
|
11391
|
+
const base = spec.proxyBaseUrl ?? `http://127.0.0.1:${spec.proxyPort}/v1`;
|
|
11392
|
+
if (!base.endsWith("/v1")) throw new Error("Codex App proxy base URL must end in /v1");
|
|
11393
|
+
return base.slice(0, -3);
|
|
11394
|
+
}
|
|
11395
|
+
async function checkedJson(url, fetchImpl) {
|
|
11396
|
+
const response = await fetchImpl(url);
|
|
11397
|
+
if (!response.ok) throw new Error(`Relay readiness check failed: GET ${url} returned HTTP ${response.status}`);
|
|
11398
|
+
return response.json();
|
|
11399
|
+
}
|
|
11400
|
+
async function verifyCodexAppReadiness(spec, options = {}) {
|
|
11401
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
11402
|
+
const root = proxyRoot(spec);
|
|
11403
|
+
const health = await checkedJson(`${root}/health`, fetchImpl);
|
|
11404
|
+
if (health.ok !== true) throw new Error("Relay proxy health check did not report ready");
|
|
11405
|
+
const catalog = JSON.parse(readFileSync4(spec.catalogPath, "utf8"));
|
|
11406
|
+
if (!Array.isArray(catalog.models) || catalog.models.length === 0) {
|
|
11407
|
+
throw new Error("Relay Codex model catalog is empty or invalid");
|
|
11408
|
+
}
|
|
11409
|
+
const catalogIds = catalog.models.map((model) => model?.slug).filter((id) => typeof id === "string" && id.length > 0);
|
|
11410
|
+
if (catalogIds.length !== catalog.models.length) throw new Error("Relay Codex model catalog contains an invalid model slug");
|
|
11411
|
+
if (!catalogIds.includes(spec.route.modelId)) {
|
|
11412
|
+
throw new Error(`Relay Codex model catalog is missing selected model ${spec.route.modelId}`);
|
|
11413
|
+
}
|
|
11414
|
+
const advertised = await checkedJson(`${root}/v1/models`, fetchImpl);
|
|
11415
|
+
const advertisedIds = new Set((advertised.data ?? []).map((model) => model.id).filter((id) => typeof id === "string"));
|
|
11416
|
+
for (const id of catalogIds) {
|
|
11417
|
+
if (!advertisedIds.has(id)) throw new Error(`Relay proxy does not advertise catalog model ${id}`);
|
|
11418
|
+
}
|
|
11419
|
+
validateAppConfigText(readCodexConfigText(options.configPath), spec);
|
|
11420
|
+
}
|
|
11421
|
+
|
|
11067
11422
|
// src/codex/app-session.ts
|
|
11068
11423
|
import {
|
|
11069
11424
|
copyFileSync as copyFileSync2,
|
|
11070
11425
|
existsSync as existsSync9,
|
|
11071
|
-
mkdirSync as
|
|
11426
|
+
mkdirSync as mkdirSync5,
|
|
11072
11427
|
readdirSync as readdirSync2,
|
|
11073
|
-
readFileSync as
|
|
11428
|
+
readFileSync as readFileSync5,
|
|
11074
11429
|
rmSync as rmSync4,
|
|
11075
11430
|
statSync as statSync2
|
|
11076
11431
|
} from "fs";
|
|
11077
|
-
import { basename as basename2, join as
|
|
11432
|
+
import { basename as basename2, join as join12 } from "path";
|
|
11433
|
+
import { createHash as createHash3 } from "crypto";
|
|
11078
11434
|
function getAppSessionLockPath(env = process.env) {
|
|
11079
|
-
return
|
|
11435
|
+
return join12(getRelayAiCodexDir(env), "session-app.json");
|
|
11080
11436
|
}
|
|
11081
11437
|
function getAppRestoreStatePath(env = process.env) {
|
|
11082
|
-
return
|
|
11438
|
+
return join12(getRelayAiCodexDir(env), "app-restore-state.json");
|
|
11083
11439
|
}
|
|
11084
11440
|
function getAppCatalogPath(providerId, env = process.env) {
|
|
11085
|
-
return
|
|
11441
|
+
return join12(getRelayAiCodexDir(env), `app-models-${providerId}.json`);
|
|
11442
|
+
}
|
|
11443
|
+
function fileSha256(path3) {
|
|
11444
|
+
return createHash3("sha256").update(readFileSync5(path3)).digest("hex");
|
|
11086
11445
|
}
|
|
11087
11446
|
function readAppSessionLock(env = process.env) {
|
|
11088
11447
|
const path3 = getAppSessionLockPath(env);
|
|
11089
11448
|
if (!existsSync9(path3)) return null;
|
|
11090
11449
|
try {
|
|
11091
|
-
const parsed = JSON.parse(
|
|
11450
|
+
const parsed = JSON.parse(readFileSync5(path3, "utf8"));
|
|
11092
11451
|
if (typeof parsed.pid === "number" && typeof parsed.startedAt === "string") return parsed;
|
|
11093
11452
|
} catch {
|
|
11094
11453
|
}
|
|
@@ -11106,7 +11465,7 @@ function readAppRestoreState(env = process.env) {
|
|
|
11106
11465
|
const path3 = getAppRestoreStatePath(env);
|
|
11107
11466
|
if (!existsSync9(path3)) return null;
|
|
11108
11467
|
try {
|
|
11109
|
-
return JSON.parse(
|
|
11468
|
+
return JSON.parse(readFileSync5(path3, "utf8"));
|
|
11110
11469
|
} catch {
|
|
11111
11470
|
return null;
|
|
11112
11471
|
}
|
|
@@ -11125,9 +11484,9 @@ function backupConfigToml(env = process.env) {
|
|
|
11125
11484
|
if (!existsSync9(configPath)) return void 0;
|
|
11126
11485
|
rotateBackups(configPath, env);
|
|
11127
11486
|
const backupsDir = getBackupsDir(env);
|
|
11128
|
-
|
|
11487
|
+
mkdirSync5(backupsDir, { recursive: true });
|
|
11129
11488
|
const base = basename2(configPath);
|
|
11130
|
-
const backupPath =
|
|
11489
|
+
const backupPath = join12(backupsDir, `${base}.${Date.now()}.bak`);
|
|
11131
11490
|
copyFileSync2(configPath, backupPath);
|
|
11132
11491
|
return backupPath;
|
|
11133
11492
|
}
|
|
@@ -11144,7 +11503,7 @@ function saveAppRestoreStateBeforePatch(env = process.env) {
|
|
|
11144
11503
|
function ownedAppCatalogPaths(env = process.env) {
|
|
11145
11504
|
const codexDir = getRelayAiCodexDir(env);
|
|
11146
11505
|
if (!existsSync9(codexDir)) return [];
|
|
11147
|
-
return readdirSync2(codexDir).filter((n) => n.startsWith("app-models-") && n.endsWith(".json")).map((n) =>
|
|
11506
|
+
return readdirSync2(codexDir).filter((n) => n.startsWith("app-models-") && n.endsWith(".json")).map((n) => join12(codexDir, n));
|
|
11148
11507
|
}
|
|
11149
11508
|
function removeAppCatalogs(env = process.env) {
|
|
11150
11509
|
const removed = [];
|
|
@@ -11162,7 +11521,7 @@ function newestConfigBackup(env = process.env) {
|
|
|
11162
11521
|
if (!existsSync9(backupDir)) return null;
|
|
11163
11522
|
const configBase = basename2(getCodexConfigPath());
|
|
11164
11523
|
const candidates = readdirSync2(backupDir).filter((name) => name.startsWith(`${configBase}.`) && name.endsWith(".bak")).map((name) => {
|
|
11165
|
-
const path3 =
|
|
11524
|
+
const path3 = join12(backupDir, name);
|
|
11166
11525
|
try {
|
|
11167
11526
|
return { path: path3, mtimeMs: statSync2(path3).mtimeMs };
|
|
11168
11527
|
} catch {
|
|
@@ -11188,7 +11547,12 @@ function restoreCodexAppOverlay(env = process.env) {
|
|
|
11188
11547
|
clearAppSessionLock(env);
|
|
11189
11548
|
return { restored: false, message: "Nothing to restore." };
|
|
11190
11549
|
}
|
|
11191
|
-
|
|
11550
|
+
const exactBackupIsSafe = Boolean(
|
|
11551
|
+
managed && lock?.backupPath && existsSync9(lock.backupPath) && lock.patchedConfigSha256 && lock.originalConfigSha256 && fileSha256(getCodexConfigPath()) === lock.patchedConfigSha256 && fileSha256(lock.backupPath) === lock.originalConfigSha256
|
|
11552
|
+
);
|
|
11553
|
+
if (exactBackupIsSafe) {
|
|
11554
|
+
copyFileSync2(lock.backupPath, getCodexConfigPath());
|
|
11555
|
+
} else if (restoreState) {
|
|
11192
11556
|
restoreConfigFromState(restoreState);
|
|
11193
11557
|
} else if (lock?.backupPath && existsSync9(lock.backupPath)) {
|
|
11194
11558
|
copyFileSync2(lock.backupPath, getCodexConfigPath());
|
|
@@ -11264,11 +11628,15 @@ function codexProxyRouteToCodexRoute(route, fallbackProviderId) {
|
|
|
11264
11628
|
refreshToken: route.refreshToken
|
|
11265
11629
|
};
|
|
11266
11630
|
}
|
|
11631
|
+
function codexAppUsesExplicitSelection(configOnly, launchProvider, launchModel) {
|
|
11632
|
+
void configOnly;
|
|
11633
|
+
return Boolean(launchProvider && launchModel);
|
|
11634
|
+
}
|
|
11267
11635
|
async function waitForShutdownWithConfirm(assumeYes = false) {
|
|
11268
11636
|
while (true) {
|
|
11269
11637
|
const signal = await waitForShutdown2();
|
|
11270
|
-
if (signal !== "sigint")
|
|
11271
|
-
if (assumeYes)
|
|
11638
|
+
if (signal !== "sigint") return signal;
|
|
11639
|
+
if (assumeYes) return signal;
|
|
11272
11640
|
console.log("");
|
|
11273
11641
|
const choice = await p12.select({
|
|
11274
11642
|
message: "Close ChatGPT Desktop and restore your Codex config?",
|
|
@@ -11277,9 +11645,12 @@ async function waitForShutdownWithConfirm(assumeYes = false) {
|
|
|
11277
11645
|
{ value: "no", label: "No, keep session running" }
|
|
11278
11646
|
]
|
|
11279
11647
|
});
|
|
11280
|
-
if (p12.isCancel(choice) || choice === "yes")
|
|
11648
|
+
if (p12.isCancel(choice) || choice === "yes") return signal;
|
|
11281
11649
|
}
|
|
11282
11650
|
}
|
|
11651
|
+
function unattendedShutdownClosesApp(assumeYes, signal) {
|
|
11652
|
+
return assumeYes && signal !== "sigint";
|
|
11653
|
+
}
|
|
11283
11654
|
async function maybeCloseRunningCodexApp(assumeYes = false) {
|
|
11284
11655
|
if (!isCodexAppRunning()) return;
|
|
11285
11656
|
if (assumeYes) {
|
|
@@ -11310,7 +11681,7 @@ ${pc10.bold("Options:")}
|
|
|
11310
11681
|
--vertex Use Claude models through Google Vertex AI
|
|
11311
11682
|
--with-native Load native Codex models beside Relay models for this launch
|
|
11312
11683
|
--relay-only Keep the current Relay-only launch behavior
|
|
11313
|
-
--yes, -y
|
|
11684
|
+
--yes, -y Approve a fully specified launch/restart without prompting
|
|
11314
11685
|
--restore Restore Codex config after an interrupted app session
|
|
11315
11686
|
--config Preview the generated Codex app configuration without launching
|
|
11316
11687
|
--trace Write proxy debug logs to ~/.relay-ai/logs/ and show errors on exit
|
|
@@ -11446,8 +11817,10 @@ async function runCodexAppVertexLaunch(configOnly, trace = false) {
|
|
|
11446
11817
|
catalogPath
|
|
11447
11818
|
};
|
|
11448
11819
|
saveAppRestoreStateBeforePatch();
|
|
11820
|
+
sessionActive = true;
|
|
11449
11821
|
const backupPath = backupConfigToml();
|
|
11450
11822
|
applyAppConfigPatch(spec);
|
|
11823
|
+
await verifyCodexAppReadiness(spec);
|
|
11451
11824
|
writeAppSessionLock({
|
|
11452
11825
|
pid: process.pid,
|
|
11453
11826
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -11455,9 +11828,10 @@ async function runCodexAppVertexLaunch(configOnly, trace = false) {
|
|
|
11455
11828
|
catalogPaths: [catalogPath],
|
|
11456
11829
|
restoreStatePath: getAppRestoreStatePath(),
|
|
11457
11830
|
backupPath,
|
|
11458
|
-
proxyPort
|
|
11831
|
+
proxyPort,
|
|
11832
|
+
patchedConfigSha256: fileSha256(getCodexConfigPath()),
|
|
11833
|
+
...backupPath ? { originalConfigSha256: fileSha256(backupPath) } : {}
|
|
11459
11834
|
});
|
|
11460
|
-
sessionActive = true;
|
|
11461
11835
|
p12.log.info(`Vertex AI \xB7 ${selectedEntry.display_name} \u2014 project: ${config.project} / location: ${config.location}`);
|
|
11462
11836
|
logProxy(proxyPort);
|
|
11463
11837
|
logActiveModel(selectedEntry.display_name, selectedEntry.id);
|
|
@@ -11466,6 +11840,7 @@ async function runCodexAppVertexLaunch(configOnly, trace = false) {
|
|
|
11466
11840
|
} catch (err) {
|
|
11467
11841
|
p12.log.warn(String(err instanceof Error ? err.message : err));
|
|
11468
11842
|
p12.log.info(codexAppInstallHint());
|
|
11843
|
+
throw err;
|
|
11469
11844
|
}
|
|
11470
11845
|
printCodexAppSessionPanel({
|
|
11471
11846
|
modelLabel: selectedEntry.display_name,
|
|
@@ -11576,7 +11951,7 @@ async function runCodexAppCommand(args, opts = {}) {
|
|
|
11576
11951
|
compatible.find((lp) => lp.id === prefs.lastCodexProvider) ?? compatible[0]
|
|
11577
11952
|
);
|
|
11578
11953
|
let selectedModel = activeProvider.models.find((m) => m.id === prefs.lastCodexModel) ?? activeProvider.models[0];
|
|
11579
|
-
if (
|
|
11954
|
+
if (codexAppUsesExplicitSelection(configOnly, opts.launchProvider, opts.launchModel)) {
|
|
11580
11955
|
const bootSelection = resolveBootSelection(
|
|
11581
11956
|
compatible,
|
|
11582
11957
|
opts.launchProvider,
|
|
@@ -11655,6 +12030,11 @@ async function runCodexAppCommand(args, opts = {}) {
|
|
|
11655
12030
|
generalFavorites: favorites,
|
|
11656
12031
|
subagentFavorites: prefs.codexSubagentModels ?? []
|
|
11657
12032
|
});
|
|
12033
|
+
if (mixedModels.capacitySkipped.length > 0) {
|
|
12034
|
+
p12.log.warn(
|
|
12035
|
+
`Skipped ${mixedModels.capacitySkipped.length} favorite(s) because the mixed catalog is full: ` + mixedModels.capacitySkipped.map((f) => `${f.providerId}:${f.modelId}`).join(", ")
|
|
12036
|
+
);
|
|
12037
|
+
}
|
|
11658
12038
|
assertConfiguredCodexSubagentsResolved(prefs.codexSubagentModels ?? [], mixedModels);
|
|
11659
12039
|
const multiAgentV2Supported = mixedModels.subagents.length === 0 || supportsMultiAgentV2(embeddedBinary);
|
|
11660
12040
|
if (!multiAgentV2Supported) {
|
|
@@ -11694,7 +12074,7 @@ Mixed Codex App mode is unavailable: ${err instanceof Error ? err.message : err}
|
|
|
11694
12074
|
let proxyHandle = null;
|
|
11695
12075
|
let sessionActive = false;
|
|
11696
12076
|
try {
|
|
11697
|
-
const catalogPath = mixedPlan ?
|
|
12077
|
+
const catalogPath = mixedPlan ? join13(getRelayAiCodexDir(), "app-models-mixed.json") : favoritesActive && resolvedFavorites.length > 0 ? getFavoritesAppCatalogPath() : getAppCatalogPath(route.providerId);
|
|
11698
12078
|
const activeRoute = mixedPlan ? {
|
|
11699
12079
|
tier: "proxy",
|
|
11700
12080
|
modelId: mixedPlan.selectedSlug,
|
|
@@ -11757,10 +12137,12 @@ Mixed Codex App mode is unavailable: ${err instanceof Error ? err.message : err}
|
|
|
11757
12137
|
return 0;
|
|
11758
12138
|
}
|
|
11759
12139
|
let proxyPort;
|
|
12140
|
+
const routeAuditPath = mixedPlan ? prepareCodexRouteAuditLog() : void 0;
|
|
11760
12141
|
if (mixedPlan) {
|
|
11761
12142
|
proxyHandle = await startCodexProxy(mixedPlan.relayRoutes, {
|
|
11762
12143
|
requireAuth: false,
|
|
11763
12144
|
debug: trace,
|
|
12145
|
+
routeAuditPath,
|
|
11764
12146
|
mixedNative: {
|
|
11765
12147
|
nativeModelIds: mixedPlan.nativeModelIds,
|
|
11766
12148
|
subagentRouteModelId: mixedPlan.subagentRouteModelId,
|
|
@@ -11769,6 +12151,7 @@ Mixed Codex App mode is unavailable: ${err instanceof Error ? err.message : err}
|
|
|
11769
12151
|
}
|
|
11770
12152
|
});
|
|
11771
12153
|
proxyPort = proxyHandle.port;
|
|
12154
|
+
p12.log.info(`Route audit (metadata only): ${routeAuditPath}`);
|
|
11772
12155
|
} else if (favoritesActive && resolvedFavorites.length > 0) {
|
|
11773
12156
|
const needsBackend = (r) => {
|
|
11774
12157
|
const m = r.model;
|
|
@@ -11828,8 +12211,10 @@ Mixed Codex App mode is unavailable: ${err instanceof Error ? err.message : err}
|
|
|
11828
12211
|
...mixedPlan ? { proxyBaseUrl: `${mixedProxyBaseUrl(proxyPort, mixedPlan.capability)}/v1` } : {}
|
|
11829
12212
|
};
|
|
11830
12213
|
saveAppRestoreStateBeforePatch();
|
|
12214
|
+
sessionActive = true;
|
|
11831
12215
|
const backupPath = backupConfigToml();
|
|
11832
12216
|
applyAppConfigPatch(spec);
|
|
12217
|
+
await verifyCodexAppReadiness(spec);
|
|
11833
12218
|
writeAppSessionLock({
|
|
11834
12219
|
pid: process.pid,
|
|
11835
12220
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -11837,9 +12222,10 @@ Mixed Codex App mode is unavailable: ${err instanceof Error ? err.message : err}
|
|
|
11837
12222
|
catalogPaths: [catalogPath],
|
|
11838
12223
|
restoreStatePath: getAppRestoreStatePath(),
|
|
11839
12224
|
backupPath,
|
|
11840
|
-
proxyPort
|
|
12225
|
+
proxyPort,
|
|
12226
|
+
patchedConfigSha256: fileSha256(getCodexConfigPath()),
|
|
12227
|
+
...backupPath ? { originalConfigSha256: fileSha256(backupPath) } : {}
|
|
11841
12228
|
});
|
|
11842
|
-
sessionActive = true;
|
|
11843
12229
|
const prevRecent = prefs.recentModelsByProvider?.[activeProvider.id] ?? [];
|
|
11844
12230
|
const updatedRecent = [selectedModel.id, ...prevRecent.filter((id) => id !== selectedModel.id)].slice(0, 3);
|
|
11845
12231
|
savePreferences({
|
|
@@ -11854,6 +12240,7 @@ Mixed Codex App mode is unavailable: ${err instanceof Error ? err.message : err}
|
|
|
11854
12240
|
} catch (err) {
|
|
11855
12241
|
p12.log.warn(String(err instanceof Error ? err.message : err));
|
|
11856
12242
|
p12.log.info(codexAppInstallHint());
|
|
12243
|
+
throw err;
|
|
11857
12244
|
}
|
|
11858
12245
|
printCodexAppSessionPanel({
|
|
11859
12246
|
modelLabel,
|
|
@@ -11862,14 +12249,21 @@ Mixed Codex App mode is unavailable: ${err instanceof Error ? err.message : err}
|
|
|
11862
12249
|
restoreCommand: "relay-ai codex-app --restore"
|
|
11863
12250
|
});
|
|
11864
12251
|
codexAppOutro(modelLabel);
|
|
11865
|
-
await waitForShutdownWithConfirm(opts.assumeYes);
|
|
12252
|
+
const shutdownSignal = await waitForShutdownWithConfirm(opts.assumeYes);
|
|
11866
12253
|
if (trace) printTraceLog(debugLogPath);
|
|
11867
12254
|
console.log("");
|
|
11868
12255
|
if (sessionActive) {
|
|
11869
12256
|
restoreCodexAppOverlay();
|
|
11870
12257
|
sessionActive = false;
|
|
11871
12258
|
}
|
|
11872
|
-
|
|
12259
|
+
if (unattendedShutdownClosesApp(Boolean(opts.assumeYes), shutdownSignal)) {
|
|
12260
|
+
if (isCodexAppRunning()) {
|
|
12261
|
+
p12.log.step("Stopping ChatGPT Desktop after unattended Relay shutdown...");
|
|
12262
|
+
quitCodexAppGracefully();
|
|
12263
|
+
}
|
|
12264
|
+
} else {
|
|
12265
|
+
await maybeCloseRunningCodexApp(opts.assumeYes);
|
|
12266
|
+
}
|
|
11873
12267
|
return 0;
|
|
11874
12268
|
} finally {
|
|
11875
12269
|
proxyHandle?.close();
|
|
@@ -11888,38 +12282,38 @@ import pc11 from "picocolors";
|
|
|
11888
12282
|
import * as p13 from "@clack/prompts";
|
|
11889
12283
|
|
|
11890
12284
|
// src/claude-desktop/app-config.ts
|
|
11891
|
-
import { existsSync as existsSync10, readFileSync as
|
|
12285
|
+
import { existsSync as existsSync10, readFileSync as readFileSync6, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6 } from "fs";
|
|
11892
12286
|
import { homedir as homedir9 } from "os";
|
|
11893
|
-
import { join as
|
|
12287
|
+
import { join as join14, dirname as dirname3 } from "path";
|
|
11894
12288
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
11895
12289
|
function getClaudeDesktopHome() {
|
|
11896
12290
|
if (process.platform === "win32") {
|
|
11897
|
-
return
|
|
12291
|
+
return join14(process.env.LOCALAPPDATA || join14(homedir9(), "AppData", "Local"), "Claude-3p");
|
|
11898
12292
|
}
|
|
11899
12293
|
if (process.platform === "linux") {
|
|
11900
|
-
return
|
|
12294
|
+
return join14(process.env.XDG_CONFIG_HOME || join14(homedir9(), ".config"), "Claude-3p");
|
|
11901
12295
|
}
|
|
11902
|
-
return
|
|
12296
|
+
return join14(homedir9(), "Library", "Application Support", "Claude-3p");
|
|
11903
12297
|
}
|
|
11904
12298
|
function getConfigLibraryPath() {
|
|
11905
|
-
return
|
|
12299
|
+
return join14(getClaudeDesktopHome(), "configLibrary");
|
|
11906
12300
|
}
|
|
11907
12301
|
function getMetaJsonPath() {
|
|
11908
|
-
return
|
|
12302
|
+
return join14(getConfigLibraryPath(), "_meta.json");
|
|
11909
12303
|
}
|
|
11910
12304
|
function readMetaJson() {
|
|
11911
12305
|
const metaPath = getMetaJsonPath();
|
|
11912
12306
|
if (!existsSync10(metaPath)) return null;
|
|
11913
12307
|
try {
|
|
11914
|
-
return JSON.parse(
|
|
12308
|
+
return JSON.parse(readFileSync6(metaPath, "utf8"));
|
|
11915
12309
|
} catch {
|
|
11916
12310
|
return null;
|
|
11917
12311
|
}
|
|
11918
12312
|
}
|
|
11919
12313
|
function writeMetaJson(meta) {
|
|
11920
12314
|
const metaPath = getMetaJsonPath();
|
|
11921
|
-
|
|
11922
|
-
|
|
12315
|
+
mkdirSync6(dirname3(metaPath), { recursive: true });
|
|
12316
|
+
writeFileSync5(metaPath, `${JSON.stringify(meta, null, 2)}
|
|
11923
12317
|
`, "utf8");
|
|
11924
12318
|
}
|
|
11925
12319
|
function buildRelayAiConfig(proxyPort) {
|
|
@@ -11933,10 +12327,10 @@ function buildRelayAiConfig(proxyPort) {
|
|
|
11933
12327
|
}
|
|
11934
12328
|
function writeRelayAiConfig(proxyPort) {
|
|
11935
12329
|
const uuid = randomUUID3();
|
|
11936
|
-
const configPath =
|
|
12330
|
+
const configPath = join14(getConfigLibraryPath(), `${uuid}.json`);
|
|
11937
12331
|
const config = buildRelayAiConfig(proxyPort);
|
|
11938
|
-
|
|
11939
|
-
|
|
12332
|
+
mkdirSync6(dirname3(configPath), { recursive: true });
|
|
12333
|
+
writeFileSync5(configPath, `${JSON.stringify(config, null, 2)}
|
|
11940
12334
|
`, "utf8");
|
|
11941
12335
|
const meta = readMetaJson() || { appliedId: "", entries: [] };
|
|
11942
12336
|
meta.appliedId = uuid;
|
|
@@ -12091,22 +12485,22 @@ async function buildClaudeAppServerCatalog(entries, providersById, trace) {
|
|
|
12091
12485
|
import {
|
|
12092
12486
|
copyFileSync as copyFileSync3,
|
|
12093
12487
|
existsSync as existsSync11,
|
|
12094
|
-
mkdirSync as
|
|
12095
|
-
readFileSync as
|
|
12488
|
+
mkdirSync as mkdirSync7,
|
|
12489
|
+
readFileSync as readFileSync7,
|
|
12096
12490
|
renameSync as renameSync2,
|
|
12097
12491
|
rmSync as rmSync5,
|
|
12098
12492
|
unlinkSync as unlinkSync2,
|
|
12099
|
-
writeFileSync as
|
|
12493
|
+
writeFileSync as writeFileSync6
|
|
12100
12494
|
} from "fs";
|
|
12101
|
-
import { dirname as dirname4, join as
|
|
12495
|
+
import { dirname as dirname4, join as join15 } from "path";
|
|
12102
12496
|
function getSessionLockPath2() {
|
|
12103
|
-
return
|
|
12497
|
+
return join15(getClaudeDesktopHome(), ".relay-ai.lock");
|
|
12104
12498
|
}
|
|
12105
12499
|
function inspectSessionLock() {
|
|
12106
12500
|
const path3 = getSessionLockPath2();
|
|
12107
12501
|
if (!existsSync11(path3)) return { status: "missing" };
|
|
12108
12502
|
try {
|
|
12109
|
-
const parsed = JSON.parse(
|
|
12503
|
+
const parsed = JSON.parse(readFileSync7(path3, "utf8"));
|
|
12110
12504
|
if (typeof parsed.pid === "number" && typeof parsed.startedAt === "string" && typeof parsed.uuid === "string" && typeof parsed.proxyPort === "number") {
|
|
12111
12505
|
return { status: "valid", lock: parsed };
|
|
12112
12506
|
}
|
|
@@ -12117,9 +12511,9 @@ function inspectSessionLock() {
|
|
|
12117
12511
|
function writeSessionLock2(lock) {
|
|
12118
12512
|
const path3 = getSessionLockPath2();
|
|
12119
12513
|
const tempPath = `${path3}.tmp.${process.pid}`;
|
|
12120
|
-
|
|
12514
|
+
mkdirSync7(dirname4(path3), { recursive: true });
|
|
12121
12515
|
try {
|
|
12122
|
-
|
|
12516
|
+
writeFileSync6(tempPath, `${JSON.stringify(lock, null, 2)}
|
|
12123
12517
|
`, "utf8");
|
|
12124
12518
|
renameSync2(tempPath, path3);
|
|
12125
12519
|
} finally {
|
|
@@ -12154,7 +12548,7 @@ function restoreMetaJson() {
|
|
|
12154
12548
|
}
|
|
12155
12549
|
}
|
|
12156
12550
|
function removeRelayAiConfig(uuid) {
|
|
12157
|
-
const configPath =
|
|
12551
|
+
const configPath = join15(getConfigLibraryPath(), `${uuid}.json`);
|
|
12158
12552
|
if (existsSync11(configPath)) {
|
|
12159
12553
|
try {
|
|
12160
12554
|
rmSync5(configPath, { force: true });
|
|
@@ -12472,17 +12866,17 @@ ${pc11.bold("Claude Desktop 3P Mode Active")}`);
|
|
|
12472
12866
|
}
|
|
12473
12867
|
|
|
12474
12868
|
// src/ai-doc.ts
|
|
12475
|
-
import { existsSync as existsSync12, mkdirSync as
|
|
12869
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
12476
12870
|
import { homedir as homedir10 } from "os";
|
|
12477
|
-
import { join as
|
|
12871
|
+
import { join as join16 } from "path";
|
|
12478
12872
|
var SKILL_DIR_NAME = "relay-ai-cli";
|
|
12479
12873
|
var SKILL_INSTALL_DIRS = [
|
|
12480
|
-
|
|
12481
|
-
|
|
12482
|
-
|
|
12483
|
-
|
|
12484
|
-
|
|
12485
|
-
|
|
12874
|
+
join16(getAppHome(), "skills"),
|
|
12875
|
+
join16(homedir10(), ".claude", "skills"),
|
|
12876
|
+
join16(homedir10(), ".agents", "skills"),
|
|
12877
|
+
join16(homedir10(), ".codex", "skills"),
|
|
12878
|
+
join16(homedir10(), ".cursor", "skills"),
|
|
12879
|
+
join16(homedir10(), ".cursor", "skills-cursor")
|
|
12486
12880
|
];
|
|
12487
12881
|
function parseSkillVersion(content) {
|
|
12488
12882
|
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
@@ -12496,10 +12890,10 @@ function parseSkillVersion(content) {
|
|
|
12496
12890
|
return null;
|
|
12497
12891
|
}
|
|
12498
12892
|
function readInstalledSkillVersion(skillDir) {
|
|
12499
|
-
const skillPath =
|
|
12893
|
+
const skillPath = join16(skillDir, "SKILL.md");
|
|
12500
12894
|
if (!existsSync12(skillPath)) return null;
|
|
12501
12895
|
try {
|
|
12502
|
-
const head =
|
|
12896
|
+
const head = readFileSync8(skillPath, "utf-8").slice(0, 1024);
|
|
12503
12897
|
return parseSkillVersion(head.includes("---", 4) ? head : `${head}
|
|
12504
12898
|
---
|
|
12505
12899
|
`);
|
|
@@ -12509,8 +12903,8 @@ function readInstalledSkillVersion(skillDir) {
|
|
|
12509
12903
|
}
|
|
12510
12904
|
function skillInstallTargets() {
|
|
12511
12905
|
return SKILL_INSTALL_DIRS.map((dir) => {
|
|
12512
|
-
const skillDir =
|
|
12513
|
-
return { skillDir, skillPath:
|
|
12906
|
+
const skillDir = join16(dir, SKILL_DIR_NAME);
|
|
12907
|
+
return { skillDir, skillPath: join16(skillDir, "SKILL.md") };
|
|
12514
12908
|
});
|
|
12515
12909
|
}
|
|
12516
12910
|
function formatProviderModels(provider) {
|
|
@@ -13014,8 +13408,8 @@ function installAiDoc(opts = {}) {
|
|
|
13014
13408
|
result.skipped.push(skillPath);
|
|
13015
13409
|
continue;
|
|
13016
13410
|
}
|
|
13017
|
-
|
|
13018
|
-
|
|
13411
|
+
mkdirSync8(skillDir, { recursive: true });
|
|
13412
|
+
writeFileSync7(skillPath, doc, "utf-8");
|
|
13019
13413
|
if (previous) {
|
|
13020
13414
|
result.updated.push({ path: skillPath, fromVersion: previous });
|
|
13021
13415
|
} else {
|
|
@@ -13175,16 +13569,16 @@ function buildHttpProxyChildEnv(baseEnv, proxyUrl, caCertPath) {
|
|
|
13175
13569
|
// src/http-proxy/ca.ts
|
|
13176
13570
|
import { randomBytes as randomBytes2, randomUUID as randomUUID4 } from "crypto";
|
|
13177
13571
|
import {
|
|
13178
|
-
chmodSync as
|
|
13572
|
+
chmodSync as chmodSync3,
|
|
13179
13573
|
existsSync as existsSync13,
|
|
13180
|
-
mkdirSync as
|
|
13181
|
-
readFileSync as
|
|
13574
|
+
mkdirSync as mkdirSync10,
|
|
13575
|
+
readFileSync as readFileSync9,
|
|
13182
13576
|
readdirSync as readdirSync3,
|
|
13183
13577
|
rmSync as rmSync6,
|
|
13184
13578
|
statSync as statSync3,
|
|
13185
|
-
writeFileSync as
|
|
13579
|
+
writeFileSync as writeFileSync9
|
|
13186
13580
|
} from "fs";
|
|
13187
|
-
import { dirname as dirname6, join as
|
|
13581
|
+
import { dirname as dirname6, join as join18, resolve } from "path";
|
|
13188
13582
|
import forge from "node-forge";
|
|
13189
13583
|
var SESSION_ROOT = "http-proxy-sessions";
|
|
13190
13584
|
var OWNER_FILE = "owner.pid";
|
|
@@ -13204,22 +13598,22 @@ function processIsRunning(pid) {
|
|
|
13204
13598
|
}
|
|
13205
13599
|
}
|
|
13206
13600
|
function cleanupStaleHttpProxySessions(appHome = getAppHome()) {
|
|
13207
|
-
const root =
|
|
13601
|
+
const root = join18(appHome, SESSION_ROOT);
|
|
13208
13602
|
if (!existsSync13(root)) return;
|
|
13209
13603
|
const now = Date.now();
|
|
13210
13604
|
for (const name of readdirSync3(root)) {
|
|
13211
|
-
const sessionDir =
|
|
13605
|
+
const sessionDir = join18(root, name);
|
|
13212
13606
|
try {
|
|
13213
13607
|
const stat = statSync3(sessionDir);
|
|
13214
13608
|
if (!stat.isDirectory()) continue;
|
|
13215
|
-
const ownerPath =
|
|
13609
|
+
const ownerPath = join18(sessionDir, OWNER_FILE);
|
|
13216
13610
|
if (!existsSync13(ownerPath)) {
|
|
13217
13611
|
if (now - stat.mtimeMs > MID_CREATION_GRACE_MS) {
|
|
13218
13612
|
rmSync6(sessionDir, { recursive: true, force: true });
|
|
13219
13613
|
}
|
|
13220
13614
|
continue;
|
|
13221
13615
|
}
|
|
13222
|
-
const pid = Number(
|
|
13616
|
+
const pid = Number(readFileSync9(ownerPath, "utf8").trim());
|
|
13223
13617
|
if (!Number.isSafeInteger(pid) || pid <= 0) {
|
|
13224
13618
|
const ownerStat = statSync3(ownerPath);
|
|
13225
13619
|
const newestMtimeMs = Math.max(stat.mtimeMs, ownerStat.mtimeMs);
|
|
@@ -13235,13 +13629,13 @@ function cleanupStaleHttpProxySessions(appHome = getAppHome()) {
|
|
|
13235
13629
|
}
|
|
13236
13630
|
function createHttpProxyCertificates(appHome = getAppHome()) {
|
|
13237
13631
|
cleanupStaleHttpProxySessions(appHome);
|
|
13238
|
-
const root =
|
|
13239
|
-
|
|
13240
|
-
|
|
13241
|
-
const sessionDir =
|
|
13242
|
-
|
|
13243
|
-
|
|
13244
|
-
|
|
13632
|
+
const root = join18(appHome, SESSION_ROOT);
|
|
13633
|
+
mkdirSync10(root, { recursive: true, mode: 448 });
|
|
13634
|
+
chmodSync3(root, 448);
|
|
13635
|
+
const sessionDir = join18(root, randomUUID4());
|
|
13636
|
+
mkdirSync10(sessionDir, { mode: 448 });
|
|
13637
|
+
chmodSync3(sessionDir, 448);
|
|
13638
|
+
writeFileSync9(join18(sessionDir, OWNER_FILE), `${process.pid}
|
|
13245
13639
|
`, { mode: 384 });
|
|
13246
13640
|
try {
|
|
13247
13641
|
const caKeys = forge.pki.rsa.generateKeyPair(2048);
|
|
@@ -13276,9 +13670,9 @@ function createHttpProxyCertificates(appHome = getAppHome()) {
|
|
|
13276
13670
|
]);
|
|
13277
13671
|
server.sign(caKeys.privateKey, forge.md.sha256.create());
|
|
13278
13672
|
const caCert = forge.pki.certificateToPem(ca);
|
|
13279
|
-
const caCertPath =
|
|
13280
|
-
|
|
13281
|
-
|
|
13673
|
+
const caCertPath = join18(sessionDir, "relay-ai-ca.pem");
|
|
13674
|
+
writeFileSync9(caCertPath, caCert, { encoding: "utf8", mode: 384 });
|
|
13675
|
+
chmodSync3(caCertPath, 384);
|
|
13282
13676
|
let cleaned = false;
|
|
13283
13677
|
const cleanupOnExit = () => {
|
|
13284
13678
|
if (cleaned) return;
|
|
@@ -13319,18 +13713,18 @@ function createHttpProxyCaBundle(relayCaCertPath, additionalCaCertPath) {
|
|
|
13319
13713
|
if (resolve(additionalCaCertPath) === resolve(relayCaCertPath)) {
|
|
13320
13714
|
return relayCaCertPath;
|
|
13321
13715
|
}
|
|
13322
|
-
const relayCa =
|
|
13323
|
-
const additionalCa =
|
|
13716
|
+
const relayCa = readFileSync9(relayCaCertPath, "utf8").trimEnd();
|
|
13717
|
+
const additionalCa = readFileSync9(additionalCaCertPath, "utf8").trim();
|
|
13324
13718
|
if (!additionalCa) return relayCaCertPath;
|
|
13325
|
-
const combinedPath =
|
|
13326
|
-
|
|
13719
|
+
const combinedPath = join18(dirname6(relayCaCertPath), "combined-ca.pem");
|
|
13720
|
+
writeFileSync9(
|
|
13327
13721
|
combinedPath,
|
|
13328
13722
|
`${relayCa}
|
|
13329
13723
|
${additionalCa}
|
|
13330
13724
|
`,
|
|
13331
13725
|
{ encoding: "utf8", mode: 384 }
|
|
13332
13726
|
);
|
|
13333
|
-
|
|
13727
|
+
chmodSync3(combinedPath, 384);
|
|
13334
13728
|
return combinedPath;
|
|
13335
13729
|
}
|
|
13336
13730
|
|
|
@@ -15345,7 +15739,7 @@ Options:
|
|
|
15345
15739
|
--trace Write debug logs under ~/.relay-ai/logs/`);
|
|
15346
15740
|
return 0;
|
|
15347
15741
|
}
|
|
15348
|
-
const { runUiCommand } = await import("./ui-command-
|
|
15742
|
+
const { runUiCommand } = await import("./ui-command-NYYLGTCD.js");
|
|
15349
15743
|
return runUiCommand({ trace: parsed.trace, serverMode: parsed.uiServerMode });
|
|
15350
15744
|
}
|
|
15351
15745
|
if (parsed.command === "models") {
|