@tryarcanist/cli 0.1.256 → 0.1.258
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/README.md +1 -75
- package/dist/index.js +824 -1168
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8207,856 +8207,201 @@ async function whoamiCommand(options, command) {
|
|
|
8207
8207
|
});
|
|
8208
8208
|
}
|
|
8209
8209
|
|
|
8210
|
-
// src/
|
|
8211
|
-
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
|
|
8216
|
-
|
|
8217
|
-
|
|
8218
|
-
|
|
8219
|
-
|
|
8220
|
-
|
|
8210
|
+
// src/commands/codex.ts
|
|
8211
|
+
import { readFile } from "fs/promises";
|
|
8212
|
+
import { join as join4 } from "path";
|
|
8213
|
+
|
|
8214
|
+
// src/vendor-login.ts
|
|
8215
|
+
import { spawn } from "child_process";
|
|
8216
|
+
import { rmSync } from "fs";
|
|
8217
|
+
import { mkdtemp, rm } from "fs/promises";
|
|
8218
|
+
import { tmpdir } from "os";
|
|
8219
|
+
import { join as join3 } from "path";
|
|
8220
|
+
function runVendorLoginProcess(spec) {
|
|
8221
|
+
return new Promise((resolve3, reject) => {
|
|
8222
|
+
const child = spawn(spec.binPath, spec.args, {
|
|
8223
|
+
stdio: "inherit",
|
|
8224
|
+
env: { ...process.env, ...spec.env }
|
|
8225
|
+
});
|
|
8226
|
+
child.on("error", (err) => {
|
|
8227
|
+
if (err.code === "ENOENT") {
|
|
8228
|
+
reject(
|
|
8229
|
+
new CliError("user", `Could not find the \`${spec.binPath}\` executable.`, {
|
|
8230
|
+
hint: spec.missingBinaryHint
|
|
8231
|
+
})
|
|
8232
|
+
);
|
|
8233
|
+
return;
|
|
8234
|
+
}
|
|
8235
|
+
reject(new CliError("user", `Failed to launch \`${spec.binPath} ${spec.args.join(" ")}\`: ${err.message}`));
|
|
8236
|
+
});
|
|
8237
|
+
child.on("close", (code) => {
|
|
8238
|
+
if (code === 0) {
|
|
8239
|
+
resolve3();
|
|
8240
|
+
return;
|
|
8241
|
+
}
|
|
8242
|
+
reject(
|
|
8243
|
+
new CliError("user", `\`${spec.binPath} ${spec.args.join(" ")}\` exited with code ${code ?? "unknown"}.`, {
|
|
8244
|
+
hint: `Complete the ${spec.displayName} login, then re-run \`${spec.retryCommand}\`.`
|
|
8245
|
+
})
|
|
8246
|
+
);
|
|
8247
|
+
});
|
|
8248
|
+
});
|
|
8249
|
+
}
|
|
8250
|
+
async function withIsolatedLoginDir(prefix, fn) {
|
|
8251
|
+
let tempDir;
|
|
8252
|
+
const handleSigint = () => {
|
|
8253
|
+
if (tempDir) {
|
|
8254
|
+
try {
|
|
8255
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
8256
|
+
} catch {
|
|
8257
|
+
}
|
|
8221
8258
|
}
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8259
|
+
process.exit(EXIT_CODE_INTERRUPTED);
|
|
8260
|
+
};
|
|
8261
|
+
process.on("SIGINT", handleSigint);
|
|
8262
|
+
try {
|
|
8263
|
+
tempDir = await mkdtemp(join3(tmpdir(), prefix));
|
|
8264
|
+
return await fn(tempDir);
|
|
8265
|
+
} finally {
|
|
8266
|
+
try {
|
|
8267
|
+
if (tempDir) {
|
|
8268
|
+
await rm(tempDir, { recursive: true, force: true }).catch(() => {
|
|
8269
|
+
});
|
|
8270
|
+
}
|
|
8271
|
+
} finally {
|
|
8272
|
+
process.off("SIGINT", handleSigint);
|
|
8273
|
+
}
|
|
8274
|
+
}
|
|
8228
8275
|
}
|
|
8229
8276
|
|
|
8230
|
-
//
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
if (
|
|
8236
|
-
|
|
8237
|
-
|
|
8238
|
-
|
|
8277
|
+
// src/commands/codex.ts
|
|
8278
|
+
var CODEX_SUBSCRIPTION_PATH = "/api/settings/codex-subscription";
|
|
8279
|
+
var CODEX_SUBSCRIPTION_AUTH_JSON_PATH = "/api/settings/codex-subscription/auth-json";
|
|
8280
|
+
var CODEX_SUBSCRIPTION_ENABLED_PATH = "/api/settings/codex-subscription/enabled";
|
|
8281
|
+
function describeCredentialStatus(credential) {
|
|
8282
|
+
if (!credential.isSet) return null;
|
|
8283
|
+
if (credential.lastValidationStatus === "invalid") {
|
|
8284
|
+
return "Expired. Upload a fresh auth.json in Settings, or run `arcanist codex login` if the Codex CLI is installed. `arcanist codex use off` falls back to your OpenAI key.";
|
|
8285
|
+
}
|
|
8286
|
+
if (credential.lastValidationStatus === "validated") return "Verified with OpenAI.";
|
|
8287
|
+
return "Saved, but not verified with OpenAI yet.";
|
|
8239
8288
|
}
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
function
|
|
8289
|
+
function resolveCodexPath(optionPath) {
|
|
8290
|
+
return optionPath?.trim() || process.env.ARCANIST_CODEX_BIN?.trim() || "codex";
|
|
8291
|
+
}
|
|
8292
|
+
function runCodexDeviceLogin(codexPath, codexHome) {
|
|
8293
|
+
return runVendorLoginProcess({
|
|
8294
|
+
displayName: "Codex",
|
|
8295
|
+
binPath: codexPath,
|
|
8296
|
+
args: ["login", "--device-auth"],
|
|
8297
|
+
env: { CODEX_HOME: codexHome },
|
|
8298
|
+
missingBinaryHint: "Install the Codex CLI, or point at it with --codex-path <path> or ARCANIST_CODEX_BIN.",
|
|
8299
|
+
retryCommand: "arcanist codex login"
|
|
8300
|
+
});
|
|
8301
|
+
}
|
|
8302
|
+
async function setCodexSubscriptionEnabled(config, enabled) {
|
|
8303
|
+
return apiFetch(config, CODEX_SUBSCRIPTION_ENABLED_PATH, {
|
|
8304
|
+
method: "PUT",
|
|
8305
|
+
body: JSON.stringify({ enabled })
|
|
8306
|
+
});
|
|
8307
|
+
}
|
|
8308
|
+
async function codexLoginCommand(options, command) {
|
|
8309
|
+
const { config } = resolveBusinessContext(command, options);
|
|
8310
|
+
const codexPath = resolveCodexPath(options.codexPath);
|
|
8311
|
+
const authJson = await withIsolatedLoginDir("arcanist-codex-", async (codexHome) => {
|
|
8312
|
+
await runCodexDeviceLogin(codexPath, codexHome);
|
|
8313
|
+
let raw;
|
|
8314
|
+
try {
|
|
8315
|
+
raw = await readFile(join4(codexHome, "auth.json"), "utf8");
|
|
8316
|
+
} catch {
|
|
8317
|
+
throw new CliError("user", "Codex login completed but no auth.json was written.", {
|
|
8318
|
+
hint: "Verify `codex login --device-auth` succeeds on its own, then re-run `arcanist codex login`."
|
|
8319
|
+
});
|
|
8320
|
+
}
|
|
8321
|
+
if (!raw.trim()) {
|
|
8322
|
+
throw new CliError("user", "Codex login produced an empty auth.json.");
|
|
8323
|
+
}
|
|
8324
|
+
return raw;
|
|
8325
|
+
});
|
|
8326
|
+
const state = await apiFetch(config, CODEX_SUBSCRIPTION_AUTH_JSON_PATH, {
|
|
8327
|
+
method: "PUT",
|
|
8328
|
+
body: JSON.stringify({ authJson })
|
|
8329
|
+
});
|
|
8330
|
+
let activated = false;
|
|
8331
|
+
let activationError;
|
|
8244
8332
|
try {
|
|
8245
|
-
|
|
8333
|
+
await setCodexSubscriptionEnabled(config, true);
|
|
8334
|
+
activated = true;
|
|
8246
8335
|
} catch (err) {
|
|
8247
|
-
|
|
8336
|
+
activationError = err instanceof Error ? err.message : String(err);
|
|
8248
8337
|
}
|
|
8338
|
+
emit(command, options, { ...state, useCodexSubscription: activated }, (payload) => {
|
|
8339
|
+
console.log(
|
|
8340
|
+
activated ? "Codex subscription auth saved and activated for OpenAI sessions." : "Codex subscription auth saved."
|
|
8341
|
+
);
|
|
8342
|
+
const status = describeCredentialStatus(payload);
|
|
8343
|
+
if (status) console.log(`Status: ${status}`);
|
|
8344
|
+
if (!activated) {
|
|
8345
|
+
console.log(`Could not activate it automatically${activationError ? ` (${activationError})` : ""}.`);
|
|
8346
|
+
console.log("Run `arcanist codex use on` to start using it.");
|
|
8347
|
+
}
|
|
8348
|
+
});
|
|
8249
8349
|
}
|
|
8250
|
-
function
|
|
8251
|
-
const
|
|
8252
|
-
if (
|
|
8253
|
-
|
|
8350
|
+
async function codexUseCommand(state, options, command) {
|
|
8351
|
+
const normalized = state.trim().toLowerCase();
|
|
8352
|
+
if (normalized !== "on" && normalized !== "off") {
|
|
8353
|
+
throw new CliError("user", "Usage: arcanist codex use <on|off>");
|
|
8354
|
+
}
|
|
8355
|
+
const enabled = normalized === "on";
|
|
8356
|
+
const { config } = resolveBusinessContext(command, options);
|
|
8357
|
+
const result = await setCodexSubscriptionEnabled(config, enabled);
|
|
8358
|
+
emit(
|
|
8359
|
+
command,
|
|
8360
|
+
options,
|
|
8361
|
+
result,
|
|
8362
|
+
() => console.log(
|
|
8363
|
+
result.useCodexSubscription ? "Codex subscription auth is now used for OpenAI sessions." : "Codex subscription auth is no longer used for OpenAI sessions."
|
|
8364
|
+
)
|
|
8365
|
+
);
|
|
8254
8366
|
}
|
|
8255
|
-
|
|
8256
|
-
|
|
8257
|
-
|
|
8258
|
-
|
|
8259
|
-
|
|
8260
|
-
|
|
8367
|
+
async function codexStatusCommand(options, command) {
|
|
8368
|
+
const { config } = resolveBusinessContext(command, options);
|
|
8369
|
+
const payload = await apiFetch(config, CODEX_SUBSCRIPTION_PATH);
|
|
8370
|
+
emit(command, options, payload, (state) => {
|
|
8371
|
+
console.log(`Eligible: ${state.eligible ? "yes" : "no"}`);
|
|
8372
|
+
console.log(`Auth.json saved: ${state.credential.isSet ? "yes" : "no"}`);
|
|
8373
|
+
const status = describeCredentialStatus(state.credential);
|
|
8374
|
+
if (status) console.log(`Status: ${status}`);
|
|
8375
|
+
});
|
|
8261
8376
|
}
|
|
8262
|
-
function
|
|
8263
|
-
|
|
8264
|
-
|
|
8377
|
+
async function codexLogoutCommand(options, command) {
|
|
8378
|
+
const { config } = resolveBusinessContext(command, options);
|
|
8379
|
+
await setCodexSubscriptionEnabled(config, false).catch(() => {
|
|
8380
|
+
});
|
|
8381
|
+
const payload = await apiFetch(config, CODEX_SUBSCRIPTION_AUTH_JSON_PATH, {
|
|
8382
|
+
method: "DELETE"
|
|
8383
|
+
});
|
|
8384
|
+
emit(command, options, payload, () => console.log("Codex subscription auth deactivated and cleared."));
|
|
8265
8385
|
}
|
|
8266
8386
|
|
|
8267
|
-
//
|
|
8268
|
-
|
|
8269
|
-
|
|
8387
|
+
// src/uploads.ts
|
|
8388
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
8389
|
+
import { basename as basename2, extname } from "path";
|
|
8270
8390
|
|
|
8271
|
-
// ../../shared/
|
|
8272
|
-
|
|
8273
|
-
|
|
8391
|
+
// ../../shared/constants/uploads.ts
|
|
8392
|
+
var MAX_UPLOADED_FILES = 5;
|
|
8393
|
+
var MAX_UPLOADED_FILE_SIZE_BYTES = 102400;
|
|
8394
|
+
var UPLOADED_FILE_EXTENSIONS = [".md", ".txt", ".csv", ".json", ".yaml", ".yml", ".xml", ".html"];
|
|
8395
|
+
var MAX_UPLOADED_IMAGE_SIZE_BYTES = 5 * 1024 * 1024;
|
|
8396
|
+
var ALLOWED_IMAGE_MEDIA_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
|
8397
|
+
|
|
8398
|
+
// ../../shared/utils/uploads.ts
|
|
8399
|
+
var ALLOWED_IMAGE_MEDIA_TYPE_SET = new Set(ALLOWED_IMAGE_MEDIA_TYPES);
|
|
8400
|
+
function ok(value) {
|
|
8401
|
+
return { ok: true, value };
|
|
8274
8402
|
}
|
|
8275
|
-
function
|
|
8276
|
-
|
|
8277
|
-
const trimmed = value.trim();
|
|
8278
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
8279
|
-
}
|
|
8280
|
-
|
|
8281
|
-
// ../../shared/constants/models.ts
|
|
8282
|
-
var OpenAIModel = {
|
|
8283
|
-
GPT56: "gpt-5.6",
|
|
8284
|
-
GPT56Sol: "gpt-5.6-sol",
|
|
8285
|
-
GPT56Terra: "gpt-5.6-terra",
|
|
8286
|
-
GPT56Luna: "gpt-5.6-luna",
|
|
8287
|
-
GPT55: "gpt-5.5",
|
|
8288
|
-
GPT54: "gpt-5.4",
|
|
8289
|
-
GPT54Pro: "gpt-5.4-pro",
|
|
8290
|
-
GPT54Mini: "gpt-5.4-mini",
|
|
8291
|
-
GPT54Nano: "gpt-5.4-nano",
|
|
8292
|
-
GPT53CodexSpark: "gpt-5.3-codex-spark",
|
|
8293
|
-
GPT53Codex: "gpt-5.3-codex",
|
|
8294
|
-
GPT52: "gpt-5.2",
|
|
8295
|
-
GPT52ChatLatest: "gpt-5.2-chat-latest",
|
|
8296
|
-
GPT52Codex: "gpt-5.2-codex"
|
|
8297
|
-
};
|
|
8298
|
-
var MODEL_PROVIDERS_SET = /* @__PURE__ */ new Set(["openai", "anthropic"]);
|
|
8299
|
-
var BACKEND_DESKTOP_IMAGE_FEEDBACK_CONFIGS = {
|
|
8300
|
-
[CODEX_AGENT_RUNTIME_BACKEND]: {
|
|
8301
|
-
backend: CODEX_AGENT_RUNTIME_BACKEND,
|
|
8302
|
-
fixture: "known_image_fixture",
|
|
8303
|
-
deliveryPath: "synthetic_image_context",
|
|
8304
|
-
verifiedAt: "2026-07-07"
|
|
8305
|
-
}
|
|
8306
|
-
};
|
|
8307
|
-
var MODEL_REGISTRY = [
|
|
8308
|
-
{
|
|
8309
|
-
id: OpenAIModel.GPT54,
|
|
8310
|
-
name: "GPT-5.4",
|
|
8311
|
-
provider: "openai",
|
|
8312
|
-
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
8313
|
-
capabilities: { codexToolSearch: true },
|
|
8314
|
-
contextWindow: 1e6,
|
|
8315
|
-
// Default codex model after the gpt-5.5 downgrade; carries gpt-5.5's prior
|
|
8316
|
-
// "medium" default so default sessions keep the same reasoning effort.
|
|
8317
|
-
reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: "medium" },
|
|
8318
|
-
// No cacheWritePerMillion: the pricing page shows a dash in the cache-writes
|
|
8319
|
-
// column for gpt-5.4 and gpt-5.5, so the absence is deliberate, not an
|
|
8320
|
-
// oversight (checked 2026-07-30). The 5.6 family does publish that rate.
|
|
8321
|
-
pricing: {
|
|
8322
|
-
inputPerMillion: 2.5,
|
|
8323
|
-
outputPerMillion: 15,
|
|
8324
|
-
cacheReadPerMillion: 0.25,
|
|
8325
|
-
longContext: {
|
|
8326
|
-
thresholdTokens: 272e3,
|
|
8327
|
-
inputPerMillion: 5,
|
|
8328
|
-
outputPerMillion: 22.5,
|
|
8329
|
-
cacheReadPerMillion: 0.5
|
|
8330
|
-
}
|
|
8331
|
-
},
|
|
8332
|
-
sessionStart: { eligible: true, isDefault: true }
|
|
8333
|
-
},
|
|
8334
|
-
{
|
|
8335
|
-
id: OpenAIModel.GPT56,
|
|
8336
|
-
name: "GPT-5.6",
|
|
8337
|
-
provider: "openai",
|
|
8338
|
-
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
8339
|
-
capabilities: { codexToolSearch: true },
|
|
8340
|
-
contextWindow: 105e4,
|
|
8341
|
-
// Verified 2026-07-09 against OpenAI's GPT-5.6 migration guide and model
|
|
8342
|
-
// catalog: the alias routes to gpt-5.6-sol and supports max effort. Pricing
|
|
8343
|
-
// mirrors gpt-5.6-sol since the alias resolves to it. Cache-write rates
|
|
8344
|
-
// verified 2026-07-30 against the Standard tier at
|
|
8345
|
-
// developers.openai.com/api/docs/pricing.
|
|
8346
|
-
reasoning: { efforts: ["none", "low", "medium", "high", "xhigh", "max"], default: "medium" },
|
|
8347
|
-
pricing: {
|
|
8348
|
-
inputPerMillion: 5,
|
|
8349
|
-
outputPerMillion: 30,
|
|
8350
|
-
cacheReadPerMillion: 0.5,
|
|
8351
|
-
cacheWritePerMillion: 6.25,
|
|
8352
|
-
longContext: {
|
|
8353
|
-
thresholdTokens: 272e3,
|
|
8354
|
-
inputPerMillion: 10,
|
|
8355
|
-
outputPerMillion: 45,
|
|
8356
|
-
cacheReadPerMillion: 1,
|
|
8357
|
-
cacheWritePerMillion: 12.5
|
|
8358
|
-
}
|
|
8359
|
-
},
|
|
8360
|
-
sessionStart: { eligible: true }
|
|
8361
|
-
},
|
|
8362
|
-
{
|
|
8363
|
-
id: OpenAIModel.GPT56Sol,
|
|
8364
|
-
name: "GPT-5.6 Sol",
|
|
8365
|
-
provider: "openai",
|
|
8366
|
-
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
8367
|
-
capabilities: { codexToolSearch: true },
|
|
8368
|
-
contextWindow: 105e4,
|
|
8369
|
-
reasoning: { efforts: ["none", "low", "medium", "high", "xhigh", "max"], default: "medium" },
|
|
8370
|
-
// Cache-write rates verified 2026-07-30 against the Standard tier at
|
|
8371
|
-
// developers.openai.com/api/docs/pricing.
|
|
8372
|
-
pricing: {
|
|
8373
|
-
inputPerMillion: 5,
|
|
8374
|
-
outputPerMillion: 30,
|
|
8375
|
-
cacheReadPerMillion: 0.5,
|
|
8376
|
-
cacheWritePerMillion: 6.25,
|
|
8377
|
-
longContext: {
|
|
8378
|
-
thresholdTokens: 272e3,
|
|
8379
|
-
inputPerMillion: 10,
|
|
8380
|
-
outputPerMillion: 45,
|
|
8381
|
-
cacheReadPerMillion: 1,
|
|
8382
|
-
cacheWritePerMillion: 12.5
|
|
8383
|
-
}
|
|
8384
|
-
},
|
|
8385
|
-
sessionStart: { eligible: true }
|
|
8386
|
-
},
|
|
8387
|
-
{
|
|
8388
|
-
id: OpenAIModel.GPT56Terra,
|
|
8389
|
-
name: "GPT-5.6 Terra",
|
|
8390
|
-
provider: "openai",
|
|
8391
|
-
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
8392
|
-
capabilities: { codexToolSearch: true },
|
|
8393
|
-
contextWindow: 105e4,
|
|
8394
|
-
reasoning: { efforts: ["none", "low", "medium", "high", "xhigh", "max"], default: "medium" },
|
|
8395
|
-
// Verified 2026-07-30 against the Standard tier at
|
|
8396
|
-
// developers.openai.com/api/docs/pricing: short and long context input,
|
|
8397
|
-
// cached input, cache writes, and output.
|
|
8398
|
-
pricing: {
|
|
8399
|
-
inputPerMillion: 2,
|
|
8400
|
-
outputPerMillion: 12,
|
|
8401
|
-
cacheReadPerMillion: 0.2,
|
|
8402
|
-
cacheWritePerMillion: 2.5,
|
|
8403
|
-
longContext: {
|
|
8404
|
-
thresholdTokens: 272e3,
|
|
8405
|
-
inputPerMillion: 4,
|
|
8406
|
-
outputPerMillion: 18,
|
|
8407
|
-
cacheReadPerMillion: 0.4,
|
|
8408
|
-
cacheWritePerMillion: 5
|
|
8409
|
-
}
|
|
8410
|
-
},
|
|
8411
|
-
sessionStart: { eligible: true }
|
|
8412
|
-
},
|
|
8413
|
-
{
|
|
8414
|
-
id: OpenAIModel.GPT56Luna,
|
|
8415
|
-
name: "GPT-5.6 Luna",
|
|
8416
|
-
provider: "openai",
|
|
8417
|
-
overloadFallback: OpenAIModel.GPT56Terra,
|
|
8418
|
-
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
8419
|
-
capabilities: { codexToolSearch: true },
|
|
8420
|
-
contextWindow: 105e4,
|
|
8421
|
-
reasoning: { efforts: ["none", "low", "medium", "high", "xhigh", "max"], default: "medium" },
|
|
8422
|
-
// Verified 2026-07-30 against the Standard tier at
|
|
8423
|
-
// developers.openai.com/api/docs/pricing: short and long context input,
|
|
8424
|
-
// cached input, cache writes, and output.
|
|
8425
|
-
pricing: {
|
|
8426
|
-
inputPerMillion: 0.2,
|
|
8427
|
-
outputPerMillion: 1.2,
|
|
8428
|
-
cacheReadPerMillion: 0.02,
|
|
8429
|
-
cacheWritePerMillion: 0.25,
|
|
8430
|
-
longContext: {
|
|
8431
|
-
thresholdTokens: 272e3,
|
|
8432
|
-
inputPerMillion: 0.4,
|
|
8433
|
-
outputPerMillion: 1.8,
|
|
8434
|
-
cacheReadPerMillion: 0.04,
|
|
8435
|
-
cacheWritePerMillion: 0.5
|
|
8436
|
-
}
|
|
8437
|
-
},
|
|
8438
|
-
sessionStart: { eligible: true }
|
|
8439
|
-
},
|
|
8440
|
-
{
|
|
8441
|
-
id: OpenAIModel.GPT55,
|
|
8442
|
-
name: "GPT-5.5",
|
|
8443
|
-
provider: "openai",
|
|
8444
|
-
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
8445
|
-
capabilities: { codexToolSearch: true },
|
|
8446
|
-
contextWindow: 1e6,
|
|
8447
|
-
reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: "medium" },
|
|
8448
|
-
pricing: {
|
|
8449
|
-
inputPerMillion: 5,
|
|
8450
|
-
outputPerMillion: 30,
|
|
8451
|
-
cacheReadPerMillion: 0.5,
|
|
8452
|
-
longContext: {
|
|
8453
|
-
thresholdTokens: 272e3,
|
|
8454
|
-
inputPerMillion: 10,
|
|
8455
|
-
outputPerMillion: 45,
|
|
8456
|
-
cacheReadPerMillion: 1
|
|
8457
|
-
}
|
|
8458
|
-
},
|
|
8459
|
-
sessionStart: { eligible: true }
|
|
8460
|
-
},
|
|
8461
|
-
{
|
|
8462
|
-
id: OpenAIModel.GPT54Mini,
|
|
8463
|
-
name: "GPT-5.4 Mini",
|
|
8464
|
-
provider: "openai",
|
|
8465
|
-
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
8466
|
-
capabilities: { codexToolSearch: true },
|
|
8467
|
-
contextWindow: 4e5,
|
|
8468
|
-
reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: void 0 },
|
|
8469
|
-
pricing: {
|
|
8470
|
-
inputPerMillion: 0.75,
|
|
8471
|
-
outputPerMillion: 4.5,
|
|
8472
|
-
cacheReadPerMillion: 0.075,
|
|
8473
|
-
// Flex bills at Batch rates: a flat 50% off standard across input,
|
|
8474
|
-
// cached input, and output (verified against the OpenAI pricing page,
|
|
8475
|
-
// June 2026). Bridge cost estimates deliberately strip this gateway-only
|
|
8476
|
-
// axis when deriving MODEL_PRICING.
|
|
8477
|
-
flex: { inputPerMillion: 0.375, outputPerMillion: 2.25, cacheReadPerMillion: 0.0375 }
|
|
8478
|
-
},
|
|
8479
|
-
sessionStart: { eligible: true }
|
|
8480
|
-
},
|
|
8481
|
-
{
|
|
8482
|
-
id: OpenAIModel.GPT54Nano,
|
|
8483
|
-
name: "GPT-5.4 Nano",
|
|
8484
|
-
provider: "openai",
|
|
8485
|
-
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
8486
|
-
capabilities: { codexToolSearch: false },
|
|
8487
|
-
contextWindow: 4e5,
|
|
8488
|
-
reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: void 0 },
|
|
8489
|
-
pricing: { inputPerMillion: 0.2, outputPerMillion: 1.25, cacheReadPerMillion: 0.02 },
|
|
8490
|
-
sessionStart: { eligible: true }
|
|
8491
|
-
},
|
|
8492
|
-
{
|
|
8493
|
-
id: OpenAIModel.GPT53CodexSpark,
|
|
8494
|
-
name: "GPT-5.3 Codex Spark",
|
|
8495
|
-
provider: "openai",
|
|
8496
|
-
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
8497
|
-
capabilities: { codexToolSearch: true },
|
|
8498
|
-
contextWindow: 128e3,
|
|
8499
|
-
reasoning: { efforts: ["low", "medium", "high", "xhigh"], default: "high" },
|
|
8500
|
-
costTracked: false,
|
|
8501
|
-
requiresCodexSubscriptionAuth: true,
|
|
8502
|
-
sessionStart: { eligible: true },
|
|
8503
|
-
visibility: "internal_probe"
|
|
8504
|
-
},
|
|
8505
|
-
{
|
|
8506
|
-
id: OpenAIModel.GPT53Codex,
|
|
8507
|
-
name: "GPT-5.3 Codex",
|
|
8508
|
-
provider: "openai",
|
|
8509
|
-
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
8510
|
-
capabilities: { codexToolSearch: true },
|
|
8511
|
-
contextWindow: 4e5,
|
|
8512
|
-
reasoning: { efforts: ["low", "medium", "high", "xhigh"], default: "high" },
|
|
8513
|
-
pricing: { inputPerMillion: 1.75, outputPerMillion: 14, cacheReadPerMillion: 0.175 }
|
|
8514
|
-
},
|
|
8515
|
-
{
|
|
8516
|
-
id: OpenAIModel.GPT52,
|
|
8517
|
-
name: "GPT-5.2",
|
|
8518
|
-
provider: "openai",
|
|
8519
|
-
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
8520
|
-
capabilities: { codexToolSearch: true },
|
|
8521
|
-
contextWindow: 4e5,
|
|
8522
|
-
reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: void 0 },
|
|
8523
|
-
pricing: { inputPerMillion: 1.75, outputPerMillion: 14, cacheReadPerMillion: 0.175 }
|
|
8524
|
-
},
|
|
8525
|
-
{
|
|
8526
|
-
id: OpenAIModel.GPT52ChatLatest,
|
|
8527
|
-
name: "GPT-5.2 Chat",
|
|
8528
|
-
provider: "openai",
|
|
8529
|
-
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
8530
|
-
capabilities: { codexToolSearch: true },
|
|
8531
|
-
contextWindow: 128e3,
|
|
8532
|
-
reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: void 0 },
|
|
8533
|
-
pricing: { inputPerMillion: 1.75, outputPerMillion: 14, cacheReadPerMillion: 0.175 }
|
|
8534
|
-
},
|
|
8535
|
-
{
|
|
8536
|
-
id: OpenAIModel.GPT52Codex,
|
|
8537
|
-
name: "GPT-5.2 Codex",
|
|
8538
|
-
provider: "openai",
|
|
8539
|
-
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
8540
|
-
capabilities: { codexToolSearch: true },
|
|
8541
|
-
contextWindow: 4e5,
|
|
8542
|
-
reasoning: { efforts: ["low", "medium", "high", "xhigh"], default: "high" },
|
|
8543
|
-
pricing: { inputPerMillion: 1.75, outputPerMillion: 14, cacheReadPerMillion: 0.175 }
|
|
8544
|
-
}
|
|
8545
|
-
];
|
|
8546
|
-
var MODEL_PROVIDER_NAMES = {
|
|
8547
|
-
openai: "OpenAI",
|
|
8548
|
-
anthropic: "Anthropic"
|
|
8549
|
-
};
|
|
8550
|
-
function buildSessionStartModelIdsByBackend() {
|
|
8551
|
-
const byBackend = Object.fromEntries(AGENT_RUNTIME_BACKENDS.map((backend) => [backend, []]));
|
|
8552
|
-
for (const model of MODEL_REGISTRY) {
|
|
8553
|
-
if (!model.sessionStart?.eligible) continue;
|
|
8554
|
-
for (const backend of model.backends) {
|
|
8555
|
-
if (!modelSupportsRequiredSessionStartCapabilities(model, backend)) continue;
|
|
8556
|
-
byBackend[backend].push(model.id);
|
|
8557
|
-
}
|
|
8558
|
-
}
|
|
8559
|
-
return byBackend;
|
|
8560
|
-
}
|
|
8561
|
-
function modelSupportsRequiredSessionStartCapabilities(model, backend) {
|
|
8562
|
-
if (backend !== CODEX_AGENT_RUNTIME_BACKEND) return true;
|
|
8563
|
-
return model.capabilities?.codexToolSearch === true;
|
|
8564
|
-
}
|
|
8565
|
-
function buildDefaultSessionStartModelIdByBackend() {
|
|
8566
|
-
const defaults = {};
|
|
8567
|
-
for (const backend of AGENT_RUNTIME_BACKENDS) {
|
|
8568
|
-
const backendDefaults = MODEL_REGISTRY.filter(
|
|
8569
|
-
(model) => model.sessionStart?.eligible && model.sessionStart.isDefault === true && model.backends.includes(backend)
|
|
8570
|
-
);
|
|
8571
|
-
if (backendDefaults.length !== 1) {
|
|
8572
|
-
throw new Error(`Expected exactly one default session-start model for ${backend}, got ${backendDefaults.length}`);
|
|
8573
|
-
}
|
|
8574
|
-
defaults[backend] = backendDefaults[0].id;
|
|
8575
|
-
}
|
|
8576
|
-
return defaults;
|
|
8577
|
-
}
|
|
8578
|
-
var SESSION_START_MODEL_IDS_BY_BACKEND = buildSessionStartModelIdsByBackend();
|
|
8579
|
-
var DEFAULT_SESSION_START_MODEL_ID_BY_BACKEND = buildDefaultSessionStartModelIdByBackend();
|
|
8580
|
-
var DEFAULT_SESSION_START_MODEL_ID = DEFAULT_SESSION_START_MODEL_ID_BY_BACKEND[CODEX_AGENT_RUNTIME_BACKEND];
|
|
8581
|
-
var VALID_MODEL_IDS = new Set(MODEL_REGISTRY.map((model) => model.id));
|
|
8582
|
-
var VALID_SESSION_START_MODEL_IDS_BY_BACKEND = {
|
|
8583
|
-
[CODEX_AGENT_RUNTIME_BACKEND]: new Set(SESSION_START_MODEL_IDS_BY_BACKEND[CODEX_AGENT_RUNTIME_BACKEND])
|
|
8584
|
-
};
|
|
8585
|
-
var MODEL_CONTEXT_WINDOWS = {
|
|
8586
|
-
...Object.fromEntries(
|
|
8587
|
-
MODEL_REGISTRY.flatMap((model) => model.contextWindow === void 0 ? [] : [[model.id, model.contextWindow]])
|
|
8588
|
-
)
|
|
8589
|
-
};
|
|
8590
|
-
var MODEL_PROVIDERS = {
|
|
8591
|
-
...Object.fromEntries(MODEL_REGISTRY.map((model) => [model.id, model.provider]))
|
|
8592
|
-
};
|
|
8593
|
-
var MODEL_REASONING_CONFIG = Object.fromEntries(
|
|
8594
|
-
MODEL_REGISTRY.flatMap((model) => model.reasoning ? [[model.id, model.reasoning]] : [])
|
|
8595
|
-
);
|
|
8596
|
-
var MODEL_DEFINITIONS_BY_ID = Object.fromEntries(MODEL_REGISTRY.map((model) => [model.id, model]));
|
|
8597
|
-
function splitModelIdentifier(value) {
|
|
8598
|
-
let hasSeparator = false;
|
|
8599
|
-
for (const separator of [":", "/"]) {
|
|
8600
|
-
const index = value.indexOf(separator);
|
|
8601
|
-
if (index <= 0 || index >= value.length - 1) continue;
|
|
8602
|
-
hasSeparator = true;
|
|
8603
|
-
const providerID = value.slice(0, index);
|
|
8604
|
-
const modelID = value.slice(index + 1);
|
|
8605
|
-
if (MODEL_PROVIDERS_SET.has(providerID) && modelID.length > 0) {
|
|
8606
|
-
return { providerID, modelID };
|
|
8607
|
-
}
|
|
8608
|
-
}
|
|
8609
|
-
if (hasSeparator) return void 0;
|
|
8610
|
-
return value.length > 0 ? { modelID: value } : void 0;
|
|
8611
|
-
}
|
|
8612
|
-
function getRawModelValue(raw) {
|
|
8613
|
-
if (typeof raw === "string") return asNonEmptyString(raw);
|
|
8614
|
-
if (!raw || typeof raw !== "object") return void 0;
|
|
8615
|
-
const value = raw;
|
|
8616
|
-
return asNonEmptyString(value.modelID) ?? asNonEmptyString(value.modelId) ?? asNonEmptyString(value.id);
|
|
8617
|
-
}
|
|
8618
|
-
function getExplicitProvider(raw) {
|
|
8619
|
-
if (raw && typeof raw === "object") {
|
|
8620
|
-
const value = raw;
|
|
8621
|
-
const providerID = asNonEmptyString(value.providerID) ?? asNonEmptyString(value.providerId);
|
|
8622
|
-
if (providerID && MODEL_PROVIDERS_SET.has(providerID)) {
|
|
8623
|
-
return providerID;
|
|
8624
|
-
}
|
|
8625
|
-
}
|
|
8626
|
-
const modelValue = getRawModelValue(raw);
|
|
8627
|
-
if (!modelValue) return void 0;
|
|
8628
|
-
return splitModelIdentifier(modelValue)?.providerID;
|
|
8629
|
-
}
|
|
8630
|
-
function extractModelId(raw) {
|
|
8631
|
-
const value = getRawModelValue(raw);
|
|
8632
|
-
if (!value) return void 0;
|
|
8633
|
-
return splitModelIdentifier(value)?.modelID;
|
|
8634
|
-
}
|
|
8635
|
-
function getSessionStartModelIdsForBackend(backend) {
|
|
8636
|
-
return SESSION_START_MODEL_IDS_BY_BACKEND[backend];
|
|
8637
|
-
}
|
|
8638
|
-
function isSessionStartModelAllowedForBackend(modelId, backend) {
|
|
8639
|
-
return VALID_SESSION_START_MODEL_IDS_BY_BACKEND[backend].has(modelId);
|
|
8640
|
-
}
|
|
8641
|
-
function getProviderForModel(modelId) {
|
|
8642
|
-
return MODEL_PROVIDERS[modelId] ?? "openai";
|
|
8643
|
-
}
|
|
8644
|
-
function toModelSelection(raw) {
|
|
8645
|
-
const modelID = extractModelId(raw);
|
|
8646
|
-
if (!modelID) return void 0;
|
|
8647
|
-
return {
|
|
8648
|
-
providerID: getExplicitProvider(raw) ?? getProviderForModel(modelID),
|
|
8649
|
-
modelID
|
|
8650
|
-
};
|
|
8651
|
-
}
|
|
8652
|
-
function getModelDefinition(raw) {
|
|
8653
|
-
const modelID = extractModelId(raw);
|
|
8654
|
-
if (!modelID) return void 0;
|
|
8655
|
-
return MODEL_DEFINITIONS_BY_ID[modelID];
|
|
8656
|
-
}
|
|
8657
|
-
function formatModelLabel(raw, options = {}) {
|
|
8658
|
-
const includeProvider = options.includeProvider ?? false;
|
|
8659
|
-
const definition = getModelDefinition(raw);
|
|
8660
|
-
if (definition) {
|
|
8661
|
-
return includeProvider ? `${MODEL_PROVIDER_NAMES[definition.provider]} / ${definition.name}` : definition.name;
|
|
8662
|
-
}
|
|
8663
|
-
const selection = toModelSelection(raw);
|
|
8664
|
-
if (!selection) return void 0;
|
|
8665
|
-
const providerName = MODEL_PROVIDER_NAMES[selection.providerID];
|
|
8666
|
-
return includeProvider ? `${providerName} / ${selection.modelID}` : selection.modelID;
|
|
8667
|
-
}
|
|
8668
|
-
function buildModelProviderGroups(models2) {
|
|
8669
|
-
const groups = /* @__PURE__ */ new Map();
|
|
8670
|
-
for (const model of models2) {
|
|
8671
|
-
let group = groups.get(model.provider);
|
|
8672
|
-
if (!group) {
|
|
8673
|
-
group = { id: model.provider, name: MODEL_PROVIDER_NAMES[model.provider], models: [] };
|
|
8674
|
-
groups.set(model.provider, group);
|
|
8675
|
-
}
|
|
8676
|
-
group.models.push({
|
|
8677
|
-
id: model.id,
|
|
8678
|
-
name: model.name,
|
|
8679
|
-
label: formatModelLabel(model, { includeProvider: true }) ?? model.name,
|
|
8680
|
-
backends: model.backends,
|
|
8681
|
-
reasoning: model.reasoning
|
|
8682
|
-
});
|
|
8683
|
-
}
|
|
8684
|
-
const defaultModelIds = new Set(
|
|
8685
|
-
models2.filter((model) => model.sessionStart?.isDefault === true).map((model) => model.id)
|
|
8686
|
-
);
|
|
8687
|
-
for (const group of groups.values()) {
|
|
8688
|
-
group.models.sort((a, b) => Number(defaultModelIds.has(b.id)) - Number(defaultModelIds.has(a.id)));
|
|
8689
|
-
}
|
|
8690
|
-
return [...groups.values()];
|
|
8691
|
-
}
|
|
8692
|
-
var SESSION_START_MODEL_ID_SET_ANY_BACKEND = new Set(
|
|
8693
|
-
AGENT_RUNTIME_BACKENDS.flatMap((backend) => [...SESSION_START_MODEL_IDS_BY_BACKEND[backend]])
|
|
8694
|
-
);
|
|
8695
|
-
var PUBLIC_SESSION_START_MODEL_PROVIDER_GROUPS = buildModelProviderGroups(
|
|
8696
|
-
MODEL_REGISTRY.filter(
|
|
8697
|
-
(model) => SESSION_START_MODEL_ID_SET_ANY_BACKEND.has(model.id) && model.visibility !== "internal_probe"
|
|
8698
|
-
)
|
|
8699
|
-
);
|
|
8700
|
-
var CODEX_SUBSCRIPTION_SESSION_START_MODEL_PROVIDER_GROUPS = buildModelProviderGroups(
|
|
8701
|
-
MODEL_REGISTRY.filter(
|
|
8702
|
-
(model) => SESSION_START_MODEL_ID_SET_ANY_BACKEND.has(model.id) && (model.visibility !== "internal_probe" || model.requiresCodexSubscriptionAuth === true)
|
|
8703
|
-
)
|
|
8704
|
-
);
|
|
8705
|
-
|
|
8706
|
-
// src/commands/model-options.ts
|
|
8707
|
-
function resolveModelAndBackend(options) {
|
|
8708
|
-
let agentRuntimeBackend = CODEX_AGENT_RUNTIME_BACKEND;
|
|
8709
|
-
const normalizedModel = options.model !== void 0 ? extractModelId(options.model) : void 0;
|
|
8710
|
-
if (options.model !== void 0) {
|
|
8711
|
-
if (normalizedModel === void 0 || !isSessionStartModelAllowedForBackend(normalizedModel, agentRuntimeBackend)) {
|
|
8712
|
-
const allowed = getSessionStartModelIdsForBackend(agentRuntimeBackend).join(", ");
|
|
8713
|
-
throw new CliError(
|
|
8714
|
-
"user",
|
|
8715
|
-
`Model '${options.model}' is not selectable for backend '${agentRuntimeBackend}'. Allowed: ${allowed}.`
|
|
8716
|
-
);
|
|
8717
|
-
}
|
|
8718
|
-
}
|
|
8719
|
-
return { agentRuntimeBackend, modelId: normalizedModel };
|
|
8720
|
-
}
|
|
8721
|
-
|
|
8722
|
-
// src/commands/automations.ts
|
|
8723
|
-
var AUTOMATION_ERROR_HINTS = {
|
|
8724
|
-
invalid_cron: "Use a standard five-field cron expression, for example: */15 * * * *",
|
|
8725
|
-
invalid_model: "Model is unknown or not selectable for its backend. Run `arcanist automations create --help` for allowed models.",
|
|
8726
|
-
repo_not_available: "Check that the token owner has GitHub access and Arcanist is installed for that repo.",
|
|
8727
|
-
duplicate_rule: "An enabled automation already exists for this repo, cron, and prompt.",
|
|
8728
|
-
invalid_skill: "Use a leading slash skill that exists in the selected repo, for example: /your-repo-skill",
|
|
8729
|
-
invalid_slack_target: "Pass both --slack-team-id and --slack-channel-id to enable Slack delivery.",
|
|
8730
|
-
slack_workspace_not_connected: "Connect that Slack workspace to Arcanist before creating the automation.",
|
|
8731
|
-
slack_channel_unavailable: "Check the Slack channel ID and confirm the bot can see the channel.",
|
|
8732
|
-
slack_bot_not_in_channel: "Invite the Arcanist bot to the channel, then retry.",
|
|
8733
|
-
rule_cap_reached: "Delete or disable an existing automation before creating another one. The cap is 20 enabled rules.",
|
|
8734
|
-
model_not_available: "That model is not available for this business; use the codex default or a codex model.",
|
|
8735
|
-
repo_skills_unavailable: "Arcanist could not verify the repo's skills right now. Retry once GitHub skill discovery is healthy.",
|
|
8736
|
-
unknown_skill: "That leading slash skill does not exist in the selected repository."
|
|
8737
|
-
};
|
|
8738
|
-
async function createAutomationCommand(repoUrl, promptArg, options, command) {
|
|
8739
|
-
const repo = parseRepoArg(repoUrl, "repo-url");
|
|
8740
|
-
const prompt = await resolvePromptInput(promptArg, options);
|
|
8741
|
-
const slackTeamId = options.slackTeamId?.trim();
|
|
8742
|
-
const slackChannelId = options.slackChannelId?.trim();
|
|
8743
|
-
const slackDeliveryRequested = options.slackTeamId !== void 0 || options.slackChannelId !== void 0;
|
|
8744
|
-
if (slackDeliveryRequested && (!slackTeamId || !slackChannelId)) {
|
|
8745
|
-
throw new CliError(
|
|
8746
|
-
"user",
|
|
8747
|
-
"Slack delivery requires both --slack-team-id and --slack-channel-id with non-empty values."
|
|
8748
|
-
);
|
|
8749
|
-
}
|
|
8750
|
-
const runtime = getRuntimeOptions(command, options);
|
|
8751
|
-
const config = requireConfig(runtime);
|
|
8752
|
-
const { modelId: normalizedModel } = resolveModelAndBackend(options);
|
|
8753
|
-
const body = {
|
|
8754
|
-
repoOwner: repo.owner,
|
|
8755
|
-
repoName: repo.repo,
|
|
8756
|
-
cron: options.cron,
|
|
8757
|
-
prompt
|
|
8758
|
-
};
|
|
8759
|
-
if (options.name !== void 0) body.name = options.name;
|
|
8760
|
-
if (options.model !== void 0) body.modelId = normalizedModel;
|
|
8761
|
-
if (slackDeliveryRequested) {
|
|
8762
|
-
body.slackTeamId = slackTeamId;
|
|
8763
|
-
body.slackChannelId = slackChannelId;
|
|
8764
|
-
}
|
|
8765
|
-
const payload = await automationApiFetch(config, "/api/automation/schedules", {
|
|
8766
|
-
method: "POST",
|
|
8767
|
-
body: JSON.stringify(body)
|
|
8768
|
-
});
|
|
8769
|
-
if (isJson(command, options)) {
|
|
8770
|
-
writeJson(payload.data);
|
|
8771
|
-
return;
|
|
8772
|
-
}
|
|
8773
|
-
printAutomationRule(payload.data);
|
|
8774
|
-
}
|
|
8775
|
-
async function listAutomationsCommand(options, command) {
|
|
8776
|
-
const runtime = getRuntimeOptions(command, options);
|
|
8777
|
-
const config = requireConfig(runtime);
|
|
8778
|
-
const { items, nextCursor } = await fetchPages(
|
|
8779
|
-
"automations list --all",
|
|
8780
|
-
options.all === true,
|
|
8781
|
-
options.cursor,
|
|
8782
|
-
async (cursor) => {
|
|
8783
|
-
const query = new URLSearchParams();
|
|
8784
|
-
if (options.limit) query.set("limit", options.limit);
|
|
8785
|
-
if (cursor) query.set("cursor", cursor);
|
|
8786
|
-
const payload = await automationApiFetch(
|
|
8787
|
-
config,
|
|
8788
|
-
`/api/automation/schedules${query.size ? `?${query.toString()}` : ""}`
|
|
8789
|
-
);
|
|
8790
|
-
return { items: payload.data.items, nextCursor: payload.data.nextCursor };
|
|
8791
|
-
}
|
|
8792
|
-
);
|
|
8793
|
-
const output = { items, nextCursor };
|
|
8794
|
-
if (isJson(command, options)) {
|
|
8795
|
-
writeJson(output);
|
|
8796
|
-
return;
|
|
8797
|
-
}
|
|
8798
|
-
if (items.length === 0) {
|
|
8799
|
-
console.log("No automations found.");
|
|
8800
|
-
return;
|
|
8801
|
-
}
|
|
8802
|
-
for (const rule of items) {
|
|
8803
|
-
console.log(
|
|
8804
|
-
`${rule.id} ${rule.repoOwner}/${rule.repoName} ${rule.normalizedCron} ${String(rule.enabled)} ${formatTime(rule.nextFireAt)}`
|
|
8805
|
-
);
|
|
8806
|
-
}
|
|
8807
|
-
if (output.nextCursor) console.log(`Next cursor: ${output.nextCursor}`);
|
|
8808
|
-
}
|
|
8809
|
-
async function deleteAutomationCommand(id, options, command) {
|
|
8810
|
-
if (isJson(command, options) && options.yes !== true) {
|
|
8811
|
-
throw new CliError("user", "`automations delete --json` requires --yes.");
|
|
8812
|
-
}
|
|
8813
|
-
if (options.yes !== true) {
|
|
8814
|
-
await confirmOrThrow(`Delete automation ${id}?`);
|
|
8815
|
-
}
|
|
8816
|
-
const runtime = getRuntimeOptions(command, options);
|
|
8817
|
-
const config = requireConfig(runtime);
|
|
8818
|
-
await automationApiFetchText(config, `/api/automation/schedules/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
8819
|
-
if (isJson(command, options)) {
|
|
8820
|
-
writeJson({ ok: true, id });
|
|
8821
|
-
return;
|
|
8822
|
-
}
|
|
8823
|
-
console.log(`Deleted automation ${id}.`);
|
|
8824
|
-
}
|
|
8825
|
-
async function automationApiFetch(config, path, init) {
|
|
8826
|
-
try {
|
|
8827
|
-
return await apiFetch(config, path, init);
|
|
8828
|
-
} catch (err) {
|
|
8829
|
-
throw mapAutomationApiError(err);
|
|
8830
|
-
}
|
|
8831
|
-
}
|
|
8832
|
-
async function automationApiFetchText(config, path, init) {
|
|
8833
|
-
try {
|
|
8834
|
-
return await apiFetchText(config, path, init);
|
|
8835
|
-
} catch (err) {
|
|
8836
|
-
throw mapAutomationApiError(err);
|
|
8837
|
-
}
|
|
8838
|
-
}
|
|
8839
|
-
function mapAutomationApiError(err) {
|
|
8840
|
-
if (!(err instanceof ApiError)) {
|
|
8841
|
-
return err instanceof CliError ? err : new CliError("server", stringifyError(err));
|
|
8842
|
-
}
|
|
8843
|
-
const parsed = parseApiErrorBody(err.body);
|
|
8844
|
-
const serverCode = parsed?.rawError ?? parsed?.serverCode;
|
|
8845
|
-
return new CliError(codeForHttpStatus(err.status), parsed?.message || serverCode || err.message, {
|
|
8846
|
-
exitCode: err.exitCode,
|
|
8847
|
-
hint: serverCode ? AUTOMATION_ERROR_HINTS[serverCode] : void 0,
|
|
8848
|
-
requestId: err.requestId
|
|
8849
|
-
});
|
|
8850
|
-
}
|
|
8851
|
-
function printAutomationRule(rule) {
|
|
8852
|
-
console.log(`ID: ${rule.id}`);
|
|
8853
|
-
console.log(`Repo: ${rule.repoOwner}/${rule.repoName}`);
|
|
8854
|
-
console.log(`Cron: ${rule.normalizedCron}`);
|
|
8855
|
-
if (rule.modelId) console.log(`Model: ${rule.modelId}`);
|
|
8856
|
-
if (rule.slackTeamId && rule.slackChannelId) {
|
|
8857
|
-
console.log(`Slack delivery: ${rule.slackTeamId}/${rule.slackChannelId}`);
|
|
8858
|
-
}
|
|
8859
|
-
console.log(`Next fire: ${formatTime(rule.nextFireAt)}`);
|
|
8860
|
-
}
|
|
8861
|
-
function formatTime(value) {
|
|
8862
|
-
return typeof value === "number" ? new Date(value).toISOString() : "none";
|
|
8863
|
-
}
|
|
8864
|
-
|
|
8865
|
-
// src/commands/codex.ts
|
|
8866
|
-
import { readFile } from "fs/promises";
|
|
8867
|
-
import { join as join4 } from "path";
|
|
8868
|
-
|
|
8869
|
-
// src/vendor-login.ts
|
|
8870
|
-
import { spawn } from "child_process";
|
|
8871
|
-
import { rmSync } from "fs";
|
|
8872
|
-
import { mkdtemp, rm } from "fs/promises";
|
|
8873
|
-
import { tmpdir } from "os";
|
|
8874
|
-
import { join as join3 } from "path";
|
|
8875
|
-
function runVendorLoginProcess(spec) {
|
|
8876
|
-
return new Promise((resolve3, reject) => {
|
|
8877
|
-
const child = spawn(spec.binPath, spec.args, {
|
|
8878
|
-
stdio: "inherit",
|
|
8879
|
-
env: { ...process.env, ...spec.env }
|
|
8880
|
-
});
|
|
8881
|
-
child.on("error", (err) => {
|
|
8882
|
-
if (err.code === "ENOENT") {
|
|
8883
|
-
reject(
|
|
8884
|
-
new CliError("user", `Could not find the \`${spec.binPath}\` executable.`, {
|
|
8885
|
-
hint: spec.missingBinaryHint
|
|
8886
|
-
})
|
|
8887
|
-
);
|
|
8888
|
-
return;
|
|
8889
|
-
}
|
|
8890
|
-
reject(new CliError("user", `Failed to launch \`${spec.binPath} ${spec.args.join(" ")}\`: ${err.message}`));
|
|
8891
|
-
});
|
|
8892
|
-
child.on("close", (code) => {
|
|
8893
|
-
if (code === 0) {
|
|
8894
|
-
resolve3();
|
|
8895
|
-
return;
|
|
8896
|
-
}
|
|
8897
|
-
reject(
|
|
8898
|
-
new CliError("user", `\`${spec.binPath} ${spec.args.join(" ")}\` exited with code ${code ?? "unknown"}.`, {
|
|
8899
|
-
hint: `Complete the ${spec.displayName} login, then re-run \`${spec.retryCommand}\`.`
|
|
8900
|
-
})
|
|
8901
|
-
);
|
|
8902
|
-
});
|
|
8903
|
-
});
|
|
8904
|
-
}
|
|
8905
|
-
async function withIsolatedLoginDir(prefix, fn) {
|
|
8906
|
-
let tempDir;
|
|
8907
|
-
const handleSigint = () => {
|
|
8908
|
-
if (tempDir) {
|
|
8909
|
-
try {
|
|
8910
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
8911
|
-
} catch {
|
|
8912
|
-
}
|
|
8913
|
-
}
|
|
8914
|
-
process.exit(EXIT_CODE_INTERRUPTED);
|
|
8915
|
-
};
|
|
8916
|
-
process.on("SIGINT", handleSigint);
|
|
8917
|
-
try {
|
|
8918
|
-
tempDir = await mkdtemp(join3(tmpdir(), prefix));
|
|
8919
|
-
return await fn(tempDir);
|
|
8920
|
-
} finally {
|
|
8921
|
-
try {
|
|
8922
|
-
if (tempDir) {
|
|
8923
|
-
await rm(tempDir, { recursive: true, force: true }).catch(() => {
|
|
8924
|
-
});
|
|
8925
|
-
}
|
|
8926
|
-
} finally {
|
|
8927
|
-
process.off("SIGINT", handleSigint);
|
|
8928
|
-
}
|
|
8929
|
-
}
|
|
8930
|
-
}
|
|
8931
|
-
|
|
8932
|
-
// src/commands/codex.ts
|
|
8933
|
-
var CODEX_SUBSCRIPTION_PATH = "/api/settings/codex-subscription";
|
|
8934
|
-
var CODEX_SUBSCRIPTION_AUTH_JSON_PATH = "/api/settings/codex-subscription/auth-json";
|
|
8935
|
-
var CODEX_SUBSCRIPTION_ENABLED_PATH = "/api/settings/codex-subscription/enabled";
|
|
8936
|
-
function describeCredentialStatus(credential) {
|
|
8937
|
-
if (!credential.isSet) return null;
|
|
8938
|
-
if (credential.lastValidationStatus === "invalid") {
|
|
8939
|
-
return "Expired. Upload a fresh auth.json in Settings, or run `arcanist codex login` if the Codex CLI is installed. `arcanist codex use off` falls back to your OpenAI key.";
|
|
8940
|
-
}
|
|
8941
|
-
if (credential.lastValidationStatus === "validated") return "Verified with OpenAI.";
|
|
8942
|
-
return "Saved, but not verified with OpenAI yet.";
|
|
8943
|
-
}
|
|
8944
|
-
function resolveCodexPath(optionPath) {
|
|
8945
|
-
return optionPath?.trim() || process.env.ARCANIST_CODEX_BIN?.trim() || "codex";
|
|
8946
|
-
}
|
|
8947
|
-
function runCodexDeviceLogin(codexPath, codexHome) {
|
|
8948
|
-
return runVendorLoginProcess({
|
|
8949
|
-
displayName: "Codex",
|
|
8950
|
-
binPath: codexPath,
|
|
8951
|
-
args: ["login", "--device-auth"],
|
|
8952
|
-
env: { CODEX_HOME: codexHome },
|
|
8953
|
-
missingBinaryHint: "Install the Codex CLI, or point at it with --codex-path <path> or ARCANIST_CODEX_BIN.",
|
|
8954
|
-
retryCommand: "arcanist codex login"
|
|
8955
|
-
});
|
|
8956
|
-
}
|
|
8957
|
-
async function setCodexSubscriptionEnabled(config, enabled) {
|
|
8958
|
-
return apiFetch(config, CODEX_SUBSCRIPTION_ENABLED_PATH, {
|
|
8959
|
-
method: "PUT",
|
|
8960
|
-
body: JSON.stringify({ enabled })
|
|
8961
|
-
});
|
|
8962
|
-
}
|
|
8963
|
-
async function codexLoginCommand(options, command) {
|
|
8964
|
-
const { config } = resolveBusinessContext(command, options);
|
|
8965
|
-
const codexPath = resolveCodexPath(options.codexPath);
|
|
8966
|
-
const authJson = await withIsolatedLoginDir("arcanist-codex-", async (codexHome) => {
|
|
8967
|
-
await runCodexDeviceLogin(codexPath, codexHome);
|
|
8968
|
-
let raw;
|
|
8969
|
-
try {
|
|
8970
|
-
raw = await readFile(join4(codexHome, "auth.json"), "utf8");
|
|
8971
|
-
} catch {
|
|
8972
|
-
throw new CliError("user", "Codex login completed but no auth.json was written.", {
|
|
8973
|
-
hint: "Verify `codex login --device-auth` succeeds on its own, then re-run `arcanist codex login`."
|
|
8974
|
-
});
|
|
8975
|
-
}
|
|
8976
|
-
if (!raw.trim()) {
|
|
8977
|
-
throw new CliError("user", "Codex login produced an empty auth.json.");
|
|
8978
|
-
}
|
|
8979
|
-
return raw;
|
|
8980
|
-
});
|
|
8981
|
-
const state = await apiFetch(config, CODEX_SUBSCRIPTION_AUTH_JSON_PATH, {
|
|
8982
|
-
method: "PUT",
|
|
8983
|
-
body: JSON.stringify({ authJson })
|
|
8984
|
-
});
|
|
8985
|
-
let activated = false;
|
|
8986
|
-
let activationError;
|
|
8987
|
-
try {
|
|
8988
|
-
await setCodexSubscriptionEnabled(config, true);
|
|
8989
|
-
activated = true;
|
|
8990
|
-
} catch (err) {
|
|
8991
|
-
activationError = err instanceof Error ? err.message : String(err);
|
|
8992
|
-
}
|
|
8993
|
-
emit(command, options, { ...state, useCodexSubscription: activated }, (payload) => {
|
|
8994
|
-
console.log(
|
|
8995
|
-
activated ? "Codex subscription auth saved and activated for OpenAI sessions." : "Codex subscription auth saved."
|
|
8996
|
-
);
|
|
8997
|
-
const status = describeCredentialStatus(payload);
|
|
8998
|
-
if (status) console.log(`Status: ${status}`);
|
|
8999
|
-
if (!activated) {
|
|
9000
|
-
console.log(`Could not activate it automatically${activationError ? ` (${activationError})` : ""}.`);
|
|
9001
|
-
console.log("Run `arcanist codex use on` to start using it.");
|
|
9002
|
-
}
|
|
9003
|
-
});
|
|
9004
|
-
}
|
|
9005
|
-
async function codexUseCommand(state, options, command) {
|
|
9006
|
-
const normalized = state.trim().toLowerCase();
|
|
9007
|
-
if (normalized !== "on" && normalized !== "off") {
|
|
9008
|
-
throw new CliError("user", "Usage: arcanist codex use <on|off>");
|
|
9009
|
-
}
|
|
9010
|
-
const enabled = normalized === "on";
|
|
9011
|
-
const { config } = resolveBusinessContext(command, options);
|
|
9012
|
-
const result = await setCodexSubscriptionEnabled(config, enabled);
|
|
9013
|
-
emit(
|
|
9014
|
-
command,
|
|
9015
|
-
options,
|
|
9016
|
-
result,
|
|
9017
|
-
() => console.log(
|
|
9018
|
-
result.useCodexSubscription ? "Codex subscription auth is now used for OpenAI sessions." : "Codex subscription auth is no longer used for OpenAI sessions."
|
|
9019
|
-
)
|
|
9020
|
-
);
|
|
9021
|
-
}
|
|
9022
|
-
async function codexStatusCommand(options, command) {
|
|
9023
|
-
const { config } = resolveBusinessContext(command, options);
|
|
9024
|
-
const payload = await apiFetch(config, CODEX_SUBSCRIPTION_PATH);
|
|
9025
|
-
emit(command, options, payload, (state) => {
|
|
9026
|
-
console.log(`Eligible: ${state.eligible ? "yes" : "no"}`);
|
|
9027
|
-
console.log(`Auth.json saved: ${state.credential.isSet ? "yes" : "no"}`);
|
|
9028
|
-
const status = describeCredentialStatus(state.credential);
|
|
9029
|
-
if (status) console.log(`Status: ${status}`);
|
|
9030
|
-
});
|
|
9031
|
-
}
|
|
9032
|
-
async function codexLogoutCommand(options, command) {
|
|
9033
|
-
const { config } = resolveBusinessContext(command, options);
|
|
9034
|
-
await setCodexSubscriptionEnabled(config, false).catch(() => {
|
|
9035
|
-
});
|
|
9036
|
-
const payload = await apiFetch(config, CODEX_SUBSCRIPTION_AUTH_JSON_PATH, {
|
|
9037
|
-
method: "DELETE"
|
|
9038
|
-
});
|
|
9039
|
-
emit(command, options, payload, () => console.log("Codex subscription auth deactivated and cleared."));
|
|
9040
|
-
}
|
|
9041
|
-
|
|
9042
|
-
// src/uploads.ts
|
|
9043
|
-
import { readFile as readFile2 } from "fs/promises";
|
|
9044
|
-
import { basename as basename2, extname } from "path";
|
|
9045
|
-
|
|
9046
|
-
// ../../shared/constants/uploads.ts
|
|
9047
|
-
var MAX_UPLOADED_FILES = 5;
|
|
9048
|
-
var MAX_UPLOADED_FILE_SIZE_BYTES = 102400;
|
|
9049
|
-
var UPLOADED_FILE_EXTENSIONS = [".md", ".txt", ".csv", ".json", ".yaml", ".yml", ".xml", ".html"];
|
|
9050
|
-
var MAX_UPLOADED_IMAGE_SIZE_BYTES = 5 * 1024 * 1024;
|
|
9051
|
-
var ALLOWED_IMAGE_MEDIA_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
|
9052
|
-
|
|
9053
|
-
// ../../shared/utils/uploads.ts
|
|
9054
|
-
var ALLOWED_IMAGE_MEDIA_TYPE_SET = new Set(ALLOWED_IMAGE_MEDIA_TYPES);
|
|
9055
|
-
function ok(value) {
|
|
9056
|
-
return { ok: true, value };
|
|
9057
|
-
}
|
|
9058
|
-
function fail(error) {
|
|
9059
|
-
return { ok: false, error };
|
|
8403
|
+
function fail(error) {
|
|
8404
|
+
return { ok: false, error };
|
|
9060
8405
|
}
|
|
9061
8406
|
function validateUploadedName(name, kind) {
|
|
9062
8407
|
const trimmed = name.trim();
|
|
@@ -9228,6 +8573,16 @@ function noChangeOutcomeCopy(reason) {
|
|
|
9228
8573
|
}
|
|
9229
8574
|
}
|
|
9230
8575
|
|
|
8576
|
+
// ../../shared/utils/type-guards.ts
|
|
8577
|
+
function isRecord(value) {
|
|
8578
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
8579
|
+
}
|
|
8580
|
+
function asNonEmptyString(value) {
|
|
8581
|
+
if (typeof value !== "string") return void 0;
|
|
8582
|
+
const trimmed = value.trim();
|
|
8583
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
8584
|
+
}
|
|
8585
|
+
|
|
9231
8586
|
// ../../shared/transcript/malformed-search.ts
|
|
9232
8587
|
var MALFORMED_SEARCH_BLOCKED_TRANSCRIPT_PREFIX = "Arcanist blocked this malformed search command before execution.";
|
|
9233
8588
|
var BASH_UNMATCHED_QUOTE_EOF_RE = /\/bin\/bash:\s+-c:\s+line\s+\d+:\s+unexpected EOF while looking for matching [`'"][`'"]?/i;
|
|
@@ -10366,9 +9721,12 @@ ${event.answer ? `**Answer:** ${event.answer}
|
|
|
10366
9721
|
}
|
|
10367
9722
|
return `*[${event.tool} output truncated]*
|
|
10368
9723
|
`;
|
|
9724
|
+
// Historical sessions may still carry `memory_usage`. Memory subsystem is
|
|
9725
|
+
// gone; keep the case for ActivityEvent exhaustiveness and render nothing.
|
|
10369
9726
|
case "memory_usage":
|
|
10370
|
-
return
|
|
10371
|
-
`
|
|
9727
|
+
return "";
|
|
9728
|
+
// Despite the name, `memory_recall_usage` is Zeus's telemetry transport for
|
|
9729
|
+
// some events and still appears in projected transcripts. Keep handling.
|
|
10372
9730
|
case "memory_recall_usage":
|
|
10373
9731
|
return event.returnedMemoryIds.length > 0 ? `*[recalled ${event.returnedMemoryIds.length === 1 ? "1 memory" : `${event.returnedMemoryIds.length} memories`}: ${event.returnedMemories.map((memory) => `\`${memory.title || memory.id}\``).join(", ")}]*
|
|
10374
9732
|
` : "";
|
|
@@ -10532,149 +9890,594 @@ function parseSsePayload(payload) {
|
|
|
10532
9890
|
flush();
|
|
10533
9891
|
continue;
|
|
10534
9892
|
}
|
|
10535
|
-
if (line.startsWith("event:")) {
|
|
10536
|
-
currentEvent = line.slice("event:".length).trim();
|
|
10537
|
-
continue;
|
|
9893
|
+
if (line.startsWith("event:")) {
|
|
9894
|
+
currentEvent = line.slice("event:".length).trim();
|
|
9895
|
+
continue;
|
|
9896
|
+
}
|
|
9897
|
+
if (line.startsWith("id:")) {
|
|
9898
|
+
const parsed = Number.parseInt(line.slice("id:".length).trim(), 10);
|
|
9899
|
+
currentId = Number.isFinite(parsed) ? parsed : void 0;
|
|
9900
|
+
continue;
|
|
9901
|
+
}
|
|
9902
|
+
if (line.startsWith("data:")) {
|
|
9903
|
+
currentData.push(line.slice("data:".length).trimStart());
|
|
9904
|
+
}
|
|
9905
|
+
}
|
|
9906
|
+
flush();
|
|
9907
|
+
let status = null;
|
|
9908
|
+
const events = [];
|
|
9909
|
+
for (const message of messages) {
|
|
9910
|
+
const data = message.data ? parseJsonObject(message.data) : {};
|
|
9911
|
+
if (message.event === "status") {
|
|
9912
|
+
const phaseRaw = data.phase;
|
|
9913
|
+
const phase = typeof phaseRaw === "string" && VALID_PHASES.has(phaseRaw) ? phaseRaw : null;
|
|
9914
|
+
const entry = {
|
|
9915
|
+
phase
|
|
9916
|
+
};
|
|
9917
|
+
const sandboxSubstate = validateEnumField(data.sandboxSubstate, VALID_SANDBOX_SUBSTATES);
|
|
9918
|
+
if (sandboxSubstate !== void 0) entry.sandboxSubstate = sandboxSubstate;
|
|
9919
|
+
const stopMode = validateEnumField(data.stopMode, VALID_STOP_MODES);
|
|
9920
|
+
if (stopMode !== void 0) entry.stopMode = stopMode;
|
|
9921
|
+
const finalizingStep = validateEnumField(data.finalizingStep, VALID_FINALIZING_STEPS);
|
|
9922
|
+
if (finalizingStep !== void 0) entry.finalizingStep = finalizingStep;
|
|
9923
|
+
if (typeof data.title === "string") entry.title = data.title;
|
|
9924
|
+
if (typeof data.spawnDurationMs === "number" || data.spawnDurationMs === null) {
|
|
9925
|
+
entry.spawnDurationMs = data.spawnDurationMs;
|
|
9926
|
+
}
|
|
9927
|
+
status = entry;
|
|
9928
|
+
continue;
|
|
9929
|
+
}
|
|
9930
|
+
events.push({
|
|
9931
|
+
type: message.event,
|
|
9932
|
+
...message.id !== void 0 ? { id: message.id } : {},
|
|
9933
|
+
data
|
|
9934
|
+
});
|
|
9935
|
+
}
|
|
9936
|
+
return { status, events };
|
|
9937
|
+
}
|
|
9938
|
+
function validateEnumField(value, validSet) {
|
|
9939
|
+
return typeof value === "string" && validSet.has(value) ? value : void 0;
|
|
9940
|
+
}
|
|
9941
|
+
function parseJsonObject(value) {
|
|
9942
|
+
try {
|
|
9943
|
+
const parsed = JSON.parse(value);
|
|
9944
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
9945
|
+
} catch (err) {
|
|
9946
|
+
throw new Error(`Malformed SSE JSON payload: ${stringifyError(err)}`);
|
|
9947
|
+
}
|
|
9948
|
+
}
|
|
9949
|
+
function formatPromptLabel(promptId, promptLabels) {
|
|
9950
|
+
if (!promptId) return "";
|
|
9951
|
+
const prompt = promptLabels.get(promptId);
|
|
9952
|
+
return prompt ? ` - ${prompt}` : "";
|
|
9953
|
+
}
|
|
9954
|
+
function buildPromptLabelMap(prompts) {
|
|
9955
|
+
const labels = /* @__PURE__ */ new Map();
|
|
9956
|
+
for (const prompt of prompts) {
|
|
9957
|
+
const text = derivePromptDisplayText({ prompt: prompt.prompt, replyToText: prompt.replyToText });
|
|
9958
|
+
labels.set(prompt.promptId, text.replace(/\s+/g, " ").trim().slice(0, PROMPT_LABEL_MAX_CHARS));
|
|
9959
|
+
}
|
|
9960
|
+
return labels;
|
|
9961
|
+
}
|
|
9962
|
+
function renderWatchEvent(event, state) {
|
|
9963
|
+
const data = event.data;
|
|
9964
|
+
switch (event.type) {
|
|
9965
|
+
case "text":
|
|
9966
|
+
return { kind: "text", text: String(data.text ?? "") };
|
|
9967
|
+
case "prompt_enqueued": {
|
|
9968
|
+
const promptId = typeof data.promptId === "string" ? data.promptId : void 0;
|
|
9969
|
+
return {
|
|
9970
|
+
kind: "line",
|
|
9971
|
+
line: `[queued] ${promptId ?? "unknown"}${formatPromptLabel(promptId, state.promptLabels)}`
|
|
9972
|
+
};
|
|
9973
|
+
}
|
|
9974
|
+
case "prompt_processing": {
|
|
9975
|
+
const promptId = typeof data.promptId === "string" ? data.promptId : void 0;
|
|
9976
|
+
return {
|
|
9977
|
+
kind: "line",
|
|
9978
|
+
line: `[prompt] ${promptId ?? "unknown"} started${formatPromptLabel(promptId, state.promptLabels)}`
|
|
9979
|
+
};
|
|
10538
9980
|
}
|
|
10539
|
-
|
|
10540
|
-
const
|
|
10541
|
-
|
|
10542
|
-
continue;
|
|
9981
|
+
case "prompt_completed": {
|
|
9982
|
+
const promptId = typeof data.promptId === "string" ? data.promptId : void 0;
|
|
9983
|
+
return { kind: "line", line: `[prompt] ${promptId ?? "unknown"} completed` };
|
|
10543
9984
|
}
|
|
10544
|
-
|
|
10545
|
-
|
|
9985
|
+
case "prompt_failed": {
|
|
9986
|
+
const promptId = typeof data.promptId === "string" ? data.promptId : void 0;
|
|
9987
|
+
const error = typeof data.error === "string" ? data.error : "unknown error";
|
|
9988
|
+
return { kind: "line", line: `[prompt] ${promptId ?? "unknown"} failed: ${error}` };
|
|
10546
9989
|
}
|
|
10547
|
-
|
|
10548
|
-
|
|
10549
|
-
|
|
10550
|
-
|
|
10551
|
-
|
|
10552
|
-
|
|
10553
|
-
|
|
10554
|
-
|
|
10555
|
-
const
|
|
10556
|
-
const
|
|
10557
|
-
|
|
9990
|
+
case "tool_call": {
|
|
9991
|
+
const toolId = String(data.id ?? "");
|
|
9992
|
+
const tool = String(data.tool ?? "unknown");
|
|
9993
|
+
const summary = String(data.summary ?? "");
|
|
9994
|
+
state.toolCalls.set(toolId, { tool, summary });
|
|
9995
|
+
return { kind: "line", line: `[tool] ${tool}${summary ? ` - ${summary}` : ""}` };
|
|
9996
|
+
}
|
|
9997
|
+
case "tool_update": {
|
|
9998
|
+
const toolId = String(data.id ?? "");
|
|
9999
|
+
const status = typeof data.status === "string" ? data.status : null;
|
|
10000
|
+
if (!status) return null;
|
|
10001
|
+
const tool = state.toolCalls.get(toolId);
|
|
10002
|
+
if (!tool) return { kind: "line", line: `[tool] ${toolId} ${status}` };
|
|
10003
|
+
return { kind: "line", line: `[tool ${status}] ${tool.tool}${tool.summary ? ` - ${tool.summary}` : ""}` };
|
|
10004
|
+
}
|
|
10005
|
+
case "question":
|
|
10006
|
+
return { kind: "line", line: `[question] ${String(data.question ?? "")}` };
|
|
10007
|
+
case "patch": {
|
|
10008
|
+
const files = Array.isArray(data.files) ? data.files.filter((item) => typeof item === "string") : [];
|
|
10009
|
+
return { kind: "line", line: `[patch] ${files.join(", ")}` };
|
|
10010
|
+
}
|
|
10011
|
+
case "agent_timeline": {
|
|
10012
|
+
const eventType = String(data.eventType ?? "timeline");
|
|
10013
|
+
const status = typeof data.status === "string" ? ` ${data.status}` : "";
|
|
10014
|
+
return { kind: "line", line: `[agent] ${eventType}${status}: ${String(data.summary ?? "")}` };
|
|
10015
|
+
}
|
|
10016
|
+
case "session_error":
|
|
10017
|
+
return {
|
|
10018
|
+
kind: "line",
|
|
10019
|
+
line: `[error] ${formatSessionErrorMessage(String(data.error ?? "Unknown error"), typeof data.code === "string" ? data.code : null)}`
|
|
10558
10020
|
};
|
|
10559
|
-
|
|
10560
|
-
|
|
10561
|
-
|
|
10562
|
-
|
|
10563
|
-
|
|
10564
|
-
|
|
10565
|
-
|
|
10566
|
-
|
|
10567
|
-
|
|
10021
|
+
case "pr_created":
|
|
10022
|
+
case "pr_updated":
|
|
10023
|
+
return { kind: "line", line: `[pr] ${String(data.prUrl ?? "")}` };
|
|
10024
|
+
case "pr_failed":
|
|
10025
|
+
return { kind: "line", line: `[pr error] ${String(data.error ?? "Unknown error")}` };
|
|
10026
|
+
case "session_idle":
|
|
10027
|
+
return { kind: "line", line: "[idle] waiting for next prompt" };
|
|
10028
|
+
default:
|
|
10029
|
+
return null;
|
|
10030
|
+
}
|
|
10031
|
+
}
|
|
10032
|
+
|
|
10033
|
+
// src/utils/session-payload.ts
|
|
10034
|
+
function unwrapSessionState(payload) {
|
|
10035
|
+
return payload.session && typeof payload.session === "object" ? payload.session : payload;
|
|
10036
|
+
}
|
|
10037
|
+
|
|
10038
|
+
// ../../shared/agent/agent-runtime-backend.ts
|
|
10039
|
+
var CODEX_AGENT_RUNTIME_BACKEND = "codex";
|
|
10040
|
+
var AGENT_RUNTIME_BACKENDS = [CODEX_AGENT_RUNTIME_BACKEND];
|
|
10041
|
+
|
|
10042
|
+
// ../../shared/constants/models.ts
|
|
10043
|
+
var OpenAIModel = {
|
|
10044
|
+
GPT56: "gpt-5.6",
|
|
10045
|
+
GPT56Sol: "gpt-5.6-sol",
|
|
10046
|
+
GPT56Terra: "gpt-5.6-terra",
|
|
10047
|
+
GPT56Luna: "gpt-5.6-luna",
|
|
10048
|
+
GPT55: "gpt-5.5",
|
|
10049
|
+
GPT54: "gpt-5.4",
|
|
10050
|
+
GPT54Pro: "gpt-5.4-pro",
|
|
10051
|
+
GPT54Mini: "gpt-5.4-mini",
|
|
10052
|
+
GPT54Nano: "gpt-5.4-nano",
|
|
10053
|
+
GPT53CodexSpark: "gpt-5.3-codex-spark",
|
|
10054
|
+
GPT53Codex: "gpt-5.3-codex",
|
|
10055
|
+
GPT52: "gpt-5.2",
|
|
10056
|
+
GPT52ChatLatest: "gpt-5.2-chat-latest",
|
|
10057
|
+
GPT52Codex: "gpt-5.2-codex"
|
|
10058
|
+
};
|
|
10059
|
+
var MODEL_PROVIDERS_SET = /* @__PURE__ */ new Set(["openai", "anthropic"]);
|
|
10060
|
+
var BACKEND_DESKTOP_IMAGE_FEEDBACK_CONFIGS = {
|
|
10061
|
+
[CODEX_AGENT_RUNTIME_BACKEND]: {
|
|
10062
|
+
backend: CODEX_AGENT_RUNTIME_BACKEND,
|
|
10063
|
+
fixture: "known_image_fixture",
|
|
10064
|
+
deliveryPath: "synthetic_image_context",
|
|
10065
|
+
verifiedAt: "2026-07-07"
|
|
10066
|
+
}
|
|
10067
|
+
};
|
|
10068
|
+
var MODEL_REGISTRY = [
|
|
10069
|
+
{
|
|
10070
|
+
id: OpenAIModel.GPT54,
|
|
10071
|
+
name: "GPT-5.4",
|
|
10072
|
+
provider: "openai",
|
|
10073
|
+
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
10074
|
+
capabilities: { codexToolSearch: true },
|
|
10075
|
+
contextWindow: 1e6,
|
|
10076
|
+
// Default codex model after the gpt-5.5 downgrade; carries gpt-5.5's prior
|
|
10077
|
+
// "medium" default so default sessions keep the same reasoning effort.
|
|
10078
|
+
reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: "medium" },
|
|
10079
|
+
// No cacheWritePerMillion: the pricing page shows a dash in the cache-writes
|
|
10080
|
+
// column for gpt-5.4 and gpt-5.5, so the absence is deliberate, not an
|
|
10081
|
+
// oversight (checked 2026-07-30). The 5.6 family does publish that rate.
|
|
10082
|
+
pricing: {
|
|
10083
|
+
inputPerMillion: 2.5,
|
|
10084
|
+
outputPerMillion: 15,
|
|
10085
|
+
cacheReadPerMillion: 0.25,
|
|
10086
|
+
longContext: {
|
|
10087
|
+
thresholdTokens: 272e3,
|
|
10088
|
+
inputPerMillion: 5,
|
|
10089
|
+
outputPerMillion: 22.5,
|
|
10090
|
+
cacheReadPerMillion: 0.5
|
|
10091
|
+
}
|
|
10092
|
+
},
|
|
10093
|
+
sessionStart: { eligible: true, isDefault: true }
|
|
10094
|
+
},
|
|
10095
|
+
{
|
|
10096
|
+
id: OpenAIModel.GPT56,
|
|
10097
|
+
name: "GPT-5.6",
|
|
10098
|
+
provider: "openai",
|
|
10099
|
+
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
10100
|
+
capabilities: { codexToolSearch: true },
|
|
10101
|
+
contextWindow: 105e4,
|
|
10102
|
+
// Verified 2026-07-09 against OpenAI's GPT-5.6 migration guide and model
|
|
10103
|
+
// catalog: the alias routes to gpt-5.6-sol and supports max effort. Pricing
|
|
10104
|
+
// mirrors gpt-5.6-sol since the alias resolves to it. Cache-write rates
|
|
10105
|
+
// verified 2026-07-30 against the Standard tier at
|
|
10106
|
+
// developers.openai.com/api/docs/pricing.
|
|
10107
|
+
reasoning: { efforts: ["none", "low", "medium", "high", "xhigh", "max"], default: "medium" },
|
|
10108
|
+
pricing: {
|
|
10109
|
+
inputPerMillion: 5,
|
|
10110
|
+
outputPerMillion: 30,
|
|
10111
|
+
cacheReadPerMillion: 0.5,
|
|
10112
|
+
cacheWritePerMillion: 6.25,
|
|
10113
|
+
longContext: {
|
|
10114
|
+
thresholdTokens: 272e3,
|
|
10115
|
+
inputPerMillion: 10,
|
|
10116
|
+
outputPerMillion: 45,
|
|
10117
|
+
cacheReadPerMillion: 1,
|
|
10118
|
+
cacheWritePerMillion: 12.5
|
|
10119
|
+
}
|
|
10120
|
+
},
|
|
10121
|
+
sessionStart: { eligible: true }
|
|
10122
|
+
},
|
|
10123
|
+
{
|
|
10124
|
+
id: OpenAIModel.GPT56Sol,
|
|
10125
|
+
name: "GPT-5.6 Sol",
|
|
10126
|
+
provider: "openai",
|
|
10127
|
+
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
10128
|
+
capabilities: { codexToolSearch: true },
|
|
10129
|
+
contextWindow: 105e4,
|
|
10130
|
+
reasoning: { efforts: ["none", "low", "medium", "high", "xhigh", "max"], default: "medium" },
|
|
10131
|
+
// Cache-write rates verified 2026-07-30 against the Standard tier at
|
|
10132
|
+
// developers.openai.com/api/docs/pricing.
|
|
10133
|
+
pricing: {
|
|
10134
|
+
inputPerMillion: 5,
|
|
10135
|
+
outputPerMillion: 30,
|
|
10136
|
+
cacheReadPerMillion: 0.5,
|
|
10137
|
+
cacheWritePerMillion: 6.25,
|
|
10138
|
+
longContext: {
|
|
10139
|
+
thresholdTokens: 272e3,
|
|
10140
|
+
inputPerMillion: 10,
|
|
10141
|
+
outputPerMillion: 45,
|
|
10142
|
+
cacheReadPerMillion: 1,
|
|
10143
|
+
cacheWritePerMillion: 12.5
|
|
10144
|
+
}
|
|
10145
|
+
},
|
|
10146
|
+
sessionStart: { eligible: true }
|
|
10147
|
+
},
|
|
10148
|
+
{
|
|
10149
|
+
id: OpenAIModel.GPT56Terra,
|
|
10150
|
+
name: "GPT-5.6 Terra",
|
|
10151
|
+
provider: "openai",
|
|
10152
|
+
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
10153
|
+
capabilities: { codexToolSearch: true },
|
|
10154
|
+
contextWindow: 105e4,
|
|
10155
|
+
reasoning: { efforts: ["none", "low", "medium", "high", "xhigh", "max"], default: "medium" },
|
|
10156
|
+
// Verified 2026-07-30 against the Standard tier at
|
|
10157
|
+
// developers.openai.com/api/docs/pricing: short and long context input,
|
|
10158
|
+
// cached input, cache writes, and output.
|
|
10159
|
+
pricing: {
|
|
10160
|
+
inputPerMillion: 2,
|
|
10161
|
+
outputPerMillion: 12,
|
|
10162
|
+
cacheReadPerMillion: 0.2,
|
|
10163
|
+
cacheWritePerMillion: 2.5,
|
|
10164
|
+
longContext: {
|
|
10165
|
+
thresholdTokens: 272e3,
|
|
10166
|
+
inputPerMillion: 4,
|
|
10167
|
+
outputPerMillion: 18,
|
|
10168
|
+
cacheReadPerMillion: 0.4,
|
|
10169
|
+
cacheWritePerMillion: 5
|
|
10170
|
+
}
|
|
10171
|
+
},
|
|
10172
|
+
sessionStart: { eligible: true }
|
|
10173
|
+
},
|
|
10174
|
+
{
|
|
10175
|
+
id: OpenAIModel.GPT56Luna,
|
|
10176
|
+
name: "GPT-5.6 Luna",
|
|
10177
|
+
provider: "openai",
|
|
10178
|
+
overloadFallback: OpenAIModel.GPT56Terra,
|
|
10179
|
+
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
10180
|
+
capabilities: { codexToolSearch: true },
|
|
10181
|
+
contextWindow: 105e4,
|
|
10182
|
+
reasoning: { efforts: ["none", "low", "medium", "high", "xhigh", "max"], default: "medium" },
|
|
10183
|
+
// Verified 2026-07-30 against the Standard tier at
|
|
10184
|
+
// developers.openai.com/api/docs/pricing: short and long context input,
|
|
10185
|
+
// cached input, cache writes, and output.
|
|
10186
|
+
pricing: {
|
|
10187
|
+
inputPerMillion: 0.2,
|
|
10188
|
+
outputPerMillion: 1.2,
|
|
10189
|
+
cacheReadPerMillion: 0.02,
|
|
10190
|
+
cacheWritePerMillion: 0.25,
|
|
10191
|
+
longContext: {
|
|
10192
|
+
thresholdTokens: 272e3,
|
|
10193
|
+
inputPerMillion: 0.4,
|
|
10194
|
+
outputPerMillion: 1.8,
|
|
10195
|
+
cacheReadPerMillion: 0.04,
|
|
10196
|
+
cacheWritePerMillion: 0.5
|
|
10197
|
+
}
|
|
10198
|
+
},
|
|
10199
|
+
sessionStart: { eligible: true }
|
|
10200
|
+
},
|
|
10201
|
+
{
|
|
10202
|
+
id: OpenAIModel.GPT55,
|
|
10203
|
+
name: "GPT-5.5",
|
|
10204
|
+
provider: "openai",
|
|
10205
|
+
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
10206
|
+
capabilities: { codexToolSearch: true },
|
|
10207
|
+
contextWindow: 1e6,
|
|
10208
|
+
reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: "medium" },
|
|
10209
|
+
pricing: {
|
|
10210
|
+
inputPerMillion: 5,
|
|
10211
|
+
outputPerMillion: 30,
|
|
10212
|
+
cacheReadPerMillion: 0.5,
|
|
10213
|
+
longContext: {
|
|
10214
|
+
thresholdTokens: 272e3,
|
|
10215
|
+
inputPerMillion: 10,
|
|
10216
|
+
outputPerMillion: 45,
|
|
10217
|
+
cacheReadPerMillion: 1
|
|
10568
10218
|
}
|
|
10569
|
-
|
|
10570
|
-
|
|
10219
|
+
},
|
|
10220
|
+
sessionStart: { eligible: true }
|
|
10221
|
+
},
|
|
10222
|
+
{
|
|
10223
|
+
id: OpenAIModel.GPT54Mini,
|
|
10224
|
+
name: "GPT-5.4 Mini",
|
|
10225
|
+
provider: "openai",
|
|
10226
|
+
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
10227
|
+
capabilities: { codexToolSearch: true },
|
|
10228
|
+
contextWindow: 4e5,
|
|
10229
|
+
reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: void 0 },
|
|
10230
|
+
pricing: {
|
|
10231
|
+
inputPerMillion: 0.75,
|
|
10232
|
+
outputPerMillion: 4.5,
|
|
10233
|
+
cacheReadPerMillion: 0.075,
|
|
10234
|
+
// Flex bills at Batch rates: a flat 50% off standard across input,
|
|
10235
|
+
// cached input, and output (verified against the OpenAI pricing page,
|
|
10236
|
+
// June 2026). Bridge cost estimates deliberately strip this gateway-only
|
|
10237
|
+
// axis when deriving MODEL_PRICING.
|
|
10238
|
+
flex: { inputPerMillion: 0.375, outputPerMillion: 2.25, cacheReadPerMillion: 0.0375 }
|
|
10239
|
+
},
|
|
10240
|
+
sessionStart: { eligible: true }
|
|
10241
|
+
},
|
|
10242
|
+
{
|
|
10243
|
+
id: OpenAIModel.GPT54Nano,
|
|
10244
|
+
name: "GPT-5.4 Nano",
|
|
10245
|
+
provider: "openai",
|
|
10246
|
+
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
10247
|
+
capabilities: { codexToolSearch: false },
|
|
10248
|
+
contextWindow: 4e5,
|
|
10249
|
+
reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: void 0 },
|
|
10250
|
+
pricing: { inputPerMillion: 0.2, outputPerMillion: 1.25, cacheReadPerMillion: 0.02 },
|
|
10251
|
+
sessionStart: { eligible: true }
|
|
10252
|
+
},
|
|
10253
|
+
{
|
|
10254
|
+
id: OpenAIModel.GPT53CodexSpark,
|
|
10255
|
+
name: "GPT-5.3 Codex Spark",
|
|
10256
|
+
provider: "openai",
|
|
10257
|
+
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
10258
|
+
capabilities: { codexToolSearch: true },
|
|
10259
|
+
contextWindow: 128e3,
|
|
10260
|
+
reasoning: { efforts: ["low", "medium", "high", "xhigh"], default: "high" },
|
|
10261
|
+
costTracked: false,
|
|
10262
|
+
requiresCodexSubscriptionAuth: true,
|
|
10263
|
+
sessionStart: { eligible: true },
|
|
10264
|
+
visibility: "internal_probe"
|
|
10265
|
+
},
|
|
10266
|
+
{
|
|
10267
|
+
id: OpenAIModel.GPT53Codex,
|
|
10268
|
+
name: "GPT-5.3 Codex",
|
|
10269
|
+
provider: "openai",
|
|
10270
|
+
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
10271
|
+
capabilities: { codexToolSearch: true },
|
|
10272
|
+
contextWindow: 4e5,
|
|
10273
|
+
reasoning: { efforts: ["low", "medium", "high", "xhigh"], default: "high" },
|
|
10274
|
+
pricing: { inputPerMillion: 1.75, outputPerMillion: 14, cacheReadPerMillion: 0.175 }
|
|
10275
|
+
},
|
|
10276
|
+
{
|
|
10277
|
+
id: OpenAIModel.GPT52,
|
|
10278
|
+
name: "GPT-5.2",
|
|
10279
|
+
provider: "openai",
|
|
10280
|
+
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
10281
|
+
capabilities: { codexToolSearch: true },
|
|
10282
|
+
contextWindow: 4e5,
|
|
10283
|
+
reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: void 0 },
|
|
10284
|
+
pricing: { inputPerMillion: 1.75, outputPerMillion: 14, cacheReadPerMillion: 0.175 }
|
|
10285
|
+
},
|
|
10286
|
+
{
|
|
10287
|
+
id: OpenAIModel.GPT52ChatLatest,
|
|
10288
|
+
name: "GPT-5.2 Chat",
|
|
10289
|
+
provider: "openai",
|
|
10290
|
+
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
10291
|
+
capabilities: { codexToolSearch: true },
|
|
10292
|
+
contextWindow: 128e3,
|
|
10293
|
+
reasoning: { efforts: ["none", "low", "medium", "high", "xhigh"], default: void 0 },
|
|
10294
|
+
pricing: { inputPerMillion: 1.75, outputPerMillion: 14, cacheReadPerMillion: 0.175 }
|
|
10295
|
+
},
|
|
10296
|
+
{
|
|
10297
|
+
id: OpenAIModel.GPT52Codex,
|
|
10298
|
+
name: "GPT-5.2 Codex",
|
|
10299
|
+
provider: "openai",
|
|
10300
|
+
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
10301
|
+
capabilities: { codexToolSearch: true },
|
|
10302
|
+
contextWindow: 4e5,
|
|
10303
|
+
reasoning: { efforts: ["low", "medium", "high", "xhigh"], default: "high" },
|
|
10304
|
+
pricing: { inputPerMillion: 1.75, outputPerMillion: 14, cacheReadPerMillion: 0.175 }
|
|
10305
|
+
}
|
|
10306
|
+
];
|
|
10307
|
+
var MODEL_PROVIDER_NAMES = {
|
|
10308
|
+
openai: "OpenAI",
|
|
10309
|
+
anthropic: "Anthropic"
|
|
10310
|
+
};
|
|
10311
|
+
function buildSessionStartModelIdsByBackend() {
|
|
10312
|
+
const byBackend = Object.fromEntries(AGENT_RUNTIME_BACKENDS.map((backend) => [backend, []]));
|
|
10313
|
+
for (const model of MODEL_REGISTRY) {
|
|
10314
|
+
if (!model.sessionStart?.eligible) continue;
|
|
10315
|
+
for (const backend of model.backends) {
|
|
10316
|
+
if (!modelSupportsRequiredSessionStartCapabilities(model, backend)) continue;
|
|
10317
|
+
byBackend[backend].push(model.id);
|
|
10571
10318
|
}
|
|
10572
|
-
events.push({
|
|
10573
|
-
type: message.event,
|
|
10574
|
-
...message.id !== void 0 ? { id: message.id } : {},
|
|
10575
|
-
data
|
|
10576
|
-
});
|
|
10577
10319
|
}
|
|
10578
|
-
return
|
|
10320
|
+
return byBackend;
|
|
10579
10321
|
}
|
|
10580
|
-
function
|
|
10581
|
-
|
|
10322
|
+
function modelSupportsRequiredSessionStartCapabilities(model, backend) {
|
|
10323
|
+
if (backend !== CODEX_AGENT_RUNTIME_BACKEND) return true;
|
|
10324
|
+
return model.capabilities?.codexToolSearch === true;
|
|
10582
10325
|
}
|
|
10583
|
-
function
|
|
10584
|
-
|
|
10585
|
-
|
|
10586
|
-
|
|
10587
|
-
|
|
10588
|
-
|
|
10326
|
+
function buildDefaultSessionStartModelIdByBackend() {
|
|
10327
|
+
const defaults = {};
|
|
10328
|
+
for (const backend of AGENT_RUNTIME_BACKENDS) {
|
|
10329
|
+
const backendDefaults = MODEL_REGISTRY.filter(
|
|
10330
|
+
(model) => model.sessionStart?.eligible && model.sessionStart.isDefault === true && model.backends.includes(backend)
|
|
10331
|
+
);
|
|
10332
|
+
if (backendDefaults.length !== 1) {
|
|
10333
|
+
throw new Error(`Expected exactly one default session-start model for ${backend}, got ${backendDefaults.length}`);
|
|
10334
|
+
}
|
|
10335
|
+
defaults[backend] = backendDefaults[0].id;
|
|
10589
10336
|
}
|
|
10337
|
+
return defaults;
|
|
10590
10338
|
}
|
|
10591
|
-
|
|
10592
|
-
|
|
10593
|
-
|
|
10594
|
-
|
|
10595
|
-
|
|
10596
|
-
|
|
10597
|
-
|
|
10598
|
-
|
|
10599
|
-
|
|
10600
|
-
|
|
10339
|
+
var SESSION_START_MODEL_IDS_BY_BACKEND = buildSessionStartModelIdsByBackend();
|
|
10340
|
+
var DEFAULT_SESSION_START_MODEL_ID_BY_BACKEND = buildDefaultSessionStartModelIdByBackend();
|
|
10341
|
+
var DEFAULT_SESSION_START_MODEL_ID = DEFAULT_SESSION_START_MODEL_ID_BY_BACKEND[CODEX_AGENT_RUNTIME_BACKEND];
|
|
10342
|
+
var VALID_MODEL_IDS = new Set(MODEL_REGISTRY.map((model) => model.id));
|
|
10343
|
+
var VALID_SESSION_START_MODEL_IDS_BY_BACKEND = {
|
|
10344
|
+
[CODEX_AGENT_RUNTIME_BACKEND]: new Set(SESSION_START_MODEL_IDS_BY_BACKEND[CODEX_AGENT_RUNTIME_BACKEND])
|
|
10345
|
+
};
|
|
10346
|
+
var MODEL_CONTEXT_WINDOWS = {
|
|
10347
|
+
...Object.fromEntries(
|
|
10348
|
+
MODEL_REGISTRY.flatMap((model) => model.contextWindow === void 0 ? [] : [[model.id, model.contextWindow]])
|
|
10349
|
+
)
|
|
10350
|
+
};
|
|
10351
|
+
var MODEL_PROVIDERS = {
|
|
10352
|
+
...Object.fromEntries(MODEL_REGISTRY.map((model) => [model.id, model.provider]))
|
|
10353
|
+
};
|
|
10354
|
+
var MODEL_REASONING_CONFIG = Object.fromEntries(
|
|
10355
|
+
MODEL_REGISTRY.flatMap((model) => model.reasoning ? [[model.id, model.reasoning]] : [])
|
|
10356
|
+
);
|
|
10357
|
+
var MODEL_DEFINITIONS_BY_ID = Object.fromEntries(MODEL_REGISTRY.map((model) => [model.id, model]));
|
|
10358
|
+
function splitModelIdentifier(value) {
|
|
10359
|
+
let hasSeparator = false;
|
|
10360
|
+
for (const separator of [":", "/"]) {
|
|
10361
|
+
const index = value.indexOf(separator);
|
|
10362
|
+
if (index <= 0 || index >= value.length - 1) continue;
|
|
10363
|
+
hasSeparator = true;
|
|
10364
|
+
const providerID = value.slice(0, index);
|
|
10365
|
+
const modelID = value.slice(index + 1);
|
|
10366
|
+
if (MODEL_PROVIDERS_SET.has(providerID) && modelID.length > 0) {
|
|
10367
|
+
return { providerID, modelID };
|
|
10368
|
+
}
|
|
10601
10369
|
}
|
|
10602
|
-
return
|
|
10370
|
+
if (hasSeparator) return void 0;
|
|
10371
|
+
return value.length > 0 ? { modelID: value } : void 0;
|
|
10603
10372
|
}
|
|
10604
|
-
function
|
|
10605
|
-
|
|
10606
|
-
|
|
10607
|
-
|
|
10608
|
-
|
|
10609
|
-
|
|
10610
|
-
|
|
10611
|
-
|
|
10612
|
-
|
|
10613
|
-
|
|
10614
|
-
|
|
10615
|
-
|
|
10616
|
-
case "prompt_processing": {
|
|
10617
|
-
const promptId = typeof data.promptId === "string" ? data.promptId : void 0;
|
|
10618
|
-
return {
|
|
10619
|
-
kind: "line",
|
|
10620
|
-
line: `[prompt] ${promptId ?? "unknown"} started${formatPromptLabel(promptId, state.promptLabels)}`
|
|
10621
|
-
};
|
|
10622
|
-
}
|
|
10623
|
-
case "prompt_completed": {
|
|
10624
|
-
const promptId = typeof data.promptId === "string" ? data.promptId : void 0;
|
|
10625
|
-
return { kind: "line", line: `[prompt] ${promptId ?? "unknown"} completed` };
|
|
10626
|
-
}
|
|
10627
|
-
case "prompt_failed": {
|
|
10628
|
-
const promptId = typeof data.promptId === "string" ? data.promptId : void 0;
|
|
10629
|
-
const error = typeof data.error === "string" ? data.error : "unknown error";
|
|
10630
|
-
return { kind: "line", line: `[prompt] ${promptId ?? "unknown"} failed: ${error}` };
|
|
10631
|
-
}
|
|
10632
|
-
case "tool_call": {
|
|
10633
|
-
const toolId = String(data.id ?? "");
|
|
10634
|
-
const tool = String(data.tool ?? "unknown");
|
|
10635
|
-
const summary = String(data.summary ?? "");
|
|
10636
|
-
state.toolCalls.set(toolId, { tool, summary });
|
|
10637
|
-
return { kind: "line", line: `[tool] ${tool}${summary ? ` - ${summary}` : ""}` };
|
|
10638
|
-
}
|
|
10639
|
-
case "tool_update": {
|
|
10640
|
-
const toolId = String(data.id ?? "");
|
|
10641
|
-
const status = typeof data.status === "string" ? data.status : null;
|
|
10642
|
-
if (!status) return null;
|
|
10643
|
-
const tool = state.toolCalls.get(toolId);
|
|
10644
|
-
if (!tool) return { kind: "line", line: `[tool] ${toolId} ${status}` };
|
|
10645
|
-
return { kind: "line", line: `[tool ${status}] ${tool.tool}${tool.summary ? ` - ${tool.summary}` : ""}` };
|
|
10646
|
-
}
|
|
10647
|
-
case "question":
|
|
10648
|
-
return { kind: "line", line: `[question] ${String(data.question ?? "")}` };
|
|
10649
|
-
case "patch": {
|
|
10650
|
-
const files = Array.isArray(data.files) ? data.files.filter((item) => typeof item === "string") : [];
|
|
10651
|
-
return { kind: "line", line: `[patch] ${files.join(", ")}` };
|
|
10373
|
+
function getRawModelValue(raw) {
|
|
10374
|
+
if (typeof raw === "string") return asNonEmptyString(raw);
|
|
10375
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
10376
|
+
const value = raw;
|
|
10377
|
+
return asNonEmptyString(value.modelID) ?? asNonEmptyString(value.modelId) ?? asNonEmptyString(value.id);
|
|
10378
|
+
}
|
|
10379
|
+
function getExplicitProvider(raw) {
|
|
10380
|
+
if (raw && typeof raw === "object") {
|
|
10381
|
+
const value = raw;
|
|
10382
|
+
const providerID = asNonEmptyString(value.providerID) ?? asNonEmptyString(value.providerId);
|
|
10383
|
+
if (providerID && MODEL_PROVIDERS_SET.has(providerID)) {
|
|
10384
|
+
return providerID;
|
|
10652
10385
|
}
|
|
10653
|
-
|
|
10654
|
-
|
|
10655
|
-
|
|
10656
|
-
|
|
10386
|
+
}
|
|
10387
|
+
const modelValue = getRawModelValue(raw);
|
|
10388
|
+
if (!modelValue) return void 0;
|
|
10389
|
+
return splitModelIdentifier(modelValue)?.providerID;
|
|
10390
|
+
}
|
|
10391
|
+
function extractModelId(raw) {
|
|
10392
|
+
const value = getRawModelValue(raw);
|
|
10393
|
+
if (!value) return void 0;
|
|
10394
|
+
return splitModelIdentifier(value)?.modelID;
|
|
10395
|
+
}
|
|
10396
|
+
function getSessionStartModelIdsForBackend(backend) {
|
|
10397
|
+
return SESSION_START_MODEL_IDS_BY_BACKEND[backend];
|
|
10398
|
+
}
|
|
10399
|
+
function isSessionStartModelAllowedForBackend(modelId, backend) {
|
|
10400
|
+
return VALID_SESSION_START_MODEL_IDS_BY_BACKEND[backend].has(modelId);
|
|
10401
|
+
}
|
|
10402
|
+
function getProviderForModel(modelId) {
|
|
10403
|
+
return MODEL_PROVIDERS[modelId] ?? "openai";
|
|
10404
|
+
}
|
|
10405
|
+
function toModelSelection(raw) {
|
|
10406
|
+
const modelID = extractModelId(raw);
|
|
10407
|
+
if (!modelID) return void 0;
|
|
10408
|
+
return {
|
|
10409
|
+
providerID: getExplicitProvider(raw) ?? getProviderForModel(modelID),
|
|
10410
|
+
modelID
|
|
10411
|
+
};
|
|
10412
|
+
}
|
|
10413
|
+
function getModelDefinition(raw) {
|
|
10414
|
+
const modelID = extractModelId(raw);
|
|
10415
|
+
if (!modelID) return void 0;
|
|
10416
|
+
return MODEL_DEFINITIONS_BY_ID[modelID];
|
|
10417
|
+
}
|
|
10418
|
+
function formatModelLabel(raw, options = {}) {
|
|
10419
|
+
const includeProvider = options.includeProvider ?? false;
|
|
10420
|
+
const definition = getModelDefinition(raw);
|
|
10421
|
+
if (definition) {
|
|
10422
|
+
return includeProvider ? `${MODEL_PROVIDER_NAMES[definition.provider]} / ${definition.name}` : definition.name;
|
|
10423
|
+
}
|
|
10424
|
+
const selection = toModelSelection(raw);
|
|
10425
|
+
if (!selection) return void 0;
|
|
10426
|
+
const providerName = MODEL_PROVIDER_NAMES[selection.providerID];
|
|
10427
|
+
return includeProvider ? `${providerName} / ${selection.modelID}` : selection.modelID;
|
|
10428
|
+
}
|
|
10429
|
+
function buildModelProviderGroups(models2) {
|
|
10430
|
+
const groups = /* @__PURE__ */ new Map();
|
|
10431
|
+
for (const model of models2) {
|
|
10432
|
+
let group = groups.get(model.provider);
|
|
10433
|
+
if (!group) {
|
|
10434
|
+
group = { id: model.provider, name: MODEL_PROVIDER_NAMES[model.provider], models: [] };
|
|
10435
|
+
groups.set(model.provider, group);
|
|
10657
10436
|
}
|
|
10658
|
-
|
|
10659
|
-
|
|
10660
|
-
|
|
10661
|
-
|
|
10662
|
-
|
|
10663
|
-
|
|
10664
|
-
|
|
10665
|
-
|
|
10666
|
-
|
|
10667
|
-
|
|
10668
|
-
|
|
10669
|
-
|
|
10670
|
-
|
|
10671
|
-
return null;
|
|
10437
|
+
group.models.push({
|
|
10438
|
+
id: model.id,
|
|
10439
|
+
name: model.name,
|
|
10440
|
+
label: formatModelLabel(model, { includeProvider: true }) ?? model.name,
|
|
10441
|
+
backends: model.backends,
|
|
10442
|
+
reasoning: model.reasoning
|
|
10443
|
+
});
|
|
10444
|
+
}
|
|
10445
|
+
const defaultModelIds = new Set(
|
|
10446
|
+
models2.filter((model) => model.sessionStart?.isDefault === true).map((model) => model.id)
|
|
10447
|
+
);
|
|
10448
|
+
for (const group of groups.values()) {
|
|
10449
|
+
group.models.sort((a, b) => Number(defaultModelIds.has(b.id)) - Number(defaultModelIds.has(a.id)));
|
|
10672
10450
|
}
|
|
10451
|
+
return [...groups.values()];
|
|
10673
10452
|
}
|
|
10453
|
+
var SESSION_START_MODEL_ID_SET_ANY_BACKEND = new Set(
|
|
10454
|
+
AGENT_RUNTIME_BACKENDS.flatMap((backend) => [...SESSION_START_MODEL_IDS_BY_BACKEND[backend]])
|
|
10455
|
+
);
|
|
10456
|
+
var PUBLIC_SESSION_START_MODEL_PROVIDER_GROUPS = buildModelProviderGroups(
|
|
10457
|
+
MODEL_REGISTRY.filter(
|
|
10458
|
+
(model) => SESSION_START_MODEL_ID_SET_ANY_BACKEND.has(model.id) && model.visibility !== "internal_probe"
|
|
10459
|
+
)
|
|
10460
|
+
);
|
|
10461
|
+
var CODEX_SUBSCRIPTION_SESSION_START_MODEL_PROVIDER_GROUPS = buildModelProviderGroups(
|
|
10462
|
+
MODEL_REGISTRY.filter(
|
|
10463
|
+
(model) => SESSION_START_MODEL_ID_SET_ANY_BACKEND.has(model.id) && (model.visibility !== "internal_probe" || model.requiresCodexSubscriptionAuth === true)
|
|
10464
|
+
)
|
|
10465
|
+
);
|
|
10674
10466
|
|
|
10675
|
-
// src/
|
|
10676
|
-
function
|
|
10677
|
-
|
|
10467
|
+
// src/commands/model-options.ts
|
|
10468
|
+
function resolveModelAndBackend(options) {
|
|
10469
|
+
let agentRuntimeBackend = CODEX_AGENT_RUNTIME_BACKEND;
|
|
10470
|
+
const normalizedModel = options.model !== void 0 ? extractModelId(options.model) : void 0;
|
|
10471
|
+
if (options.model !== void 0) {
|
|
10472
|
+
if (normalizedModel === void 0 || !isSessionStartModelAllowedForBackend(normalizedModel, agentRuntimeBackend)) {
|
|
10473
|
+
const allowed = getSessionStartModelIdsForBackend(agentRuntimeBackend).join(", ");
|
|
10474
|
+
throw new CliError(
|
|
10475
|
+
"user",
|
|
10476
|
+
`Model '${options.model}' is not selectable for backend '${agentRuntimeBackend}'. Allowed: ${allowed}.`
|
|
10477
|
+
);
|
|
10478
|
+
}
|
|
10479
|
+
}
|
|
10480
|
+
return { agentRuntimeBackend, modelId: normalizedModel };
|
|
10678
10481
|
}
|
|
10679
10482
|
|
|
10680
10483
|
// ../../shared/session/transient-disconnect.ts
|
|
@@ -10978,16 +10781,6 @@ async function createCommand(repoUrl, promptArg, options, command) {
|
|
|
10978
10781
|
if (promptPreview) body.prompt = promptPreview;
|
|
10979
10782
|
if (baseBranch) body.baseBranch = baseBranch;
|
|
10980
10783
|
if (startBranch) body.startBranch = startBranch;
|
|
10981
|
-
const linearIssue = options.linearIssue?.trim();
|
|
10982
|
-
if (options.linearIssue !== void 0 && !linearIssue) {
|
|
10983
|
-
throw new CliError("user", "--linear-issue must not be empty.");
|
|
10984
|
-
}
|
|
10985
|
-
if (linearIssue) body.linearIssue = linearIssue;
|
|
10986
|
-
const jiraIssue = options.jiraIssue?.trim();
|
|
10987
|
-
if (options.jiraIssue !== void 0 && !jiraIssue) {
|
|
10988
|
-
throw new CliError("user", "--jira-issue must not be empty.");
|
|
10989
|
-
}
|
|
10990
|
-
if (jiraIssue) body.jiraIssue = jiraIssue;
|
|
10991
10784
|
if (continuePr) body.continuePrUrl = continuePr;
|
|
10992
10785
|
if (continueMode) body.continueMode = continueMode;
|
|
10993
10786
|
if (options.onboarding) body.onboarding = true;
|
|
@@ -11062,8 +10855,6 @@ async function createCommand(repoUrl, promptArg, options, command) {
|
|
|
11062
10855
|
if (options.autoVerify) output.autoVerify = true;
|
|
11063
10856
|
if (baseBranch) output.baseBranch = baseBranch;
|
|
11064
10857
|
if (startBranch) output.startBranch = startBranch;
|
|
11065
|
-
if (linearIssue) output.linearIssue = linearIssue;
|
|
11066
|
-
if (jiraIssue) output.jiraIssue = jiraIssue;
|
|
11067
10858
|
if (continuePr) output.continuePrUrl = continuePr;
|
|
11068
10859
|
if (continueMode) output.continueMode = continueMode;
|
|
11069
10860
|
if (options.onboarding) output.onboarding = true;
|
|
@@ -11119,6 +10910,43 @@ function parseEgressAllowlistSourceFile(content, key = EGRESS_ALLOWLIST_SOURCE_P
|
|
|
11119
10910
|
return normalizeEgressDomains(domains, key);
|
|
11120
10911
|
}
|
|
11121
10912
|
|
|
10913
|
+
// ../../shared/github/repo-url.ts
|
|
10914
|
+
function parseGithubRepoFullName(value) {
|
|
10915
|
+
const shorthand = value.match(/^([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)$/);
|
|
10916
|
+
if (shorthand) return { owner: shorthand[1], repo: shorthand[2] };
|
|
10917
|
+
const ssh = value.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
10918
|
+
if (ssh) return { owner: ssh[1], repo: ssh[2] };
|
|
10919
|
+
const https = value.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
|
|
10920
|
+
if (https) return { owner: https[1], repo: https[2] };
|
|
10921
|
+
return null;
|
|
10922
|
+
}
|
|
10923
|
+
|
|
10924
|
+
// src/git.ts
|
|
10925
|
+
import { execFileSync } from "child_process";
|
|
10926
|
+
function git(args) {
|
|
10927
|
+
try {
|
|
10928
|
+
return execFileSync("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
10929
|
+
} catch (err) {
|
|
10930
|
+
throw new CliError("user", `git ${args.join(" ")} failed: ${stringifyError(err)}`);
|
|
10931
|
+
}
|
|
10932
|
+
}
|
|
10933
|
+
function currentRepo() {
|
|
10934
|
+
const parsed = parseGithubRepoFullName(git(["remote", "get-url", "origin"]));
|
|
10935
|
+
if (!parsed) throw new CliError("user", "origin remote must point at a GitHub repository");
|
|
10936
|
+
return { owner: parsed.owner, repo: parsed.repo };
|
|
10937
|
+
}
|
|
10938
|
+
|
|
10939
|
+
// src/utils/repo-arg.ts
|
|
10940
|
+
function parseRepoArg(value, argName = "repo") {
|
|
10941
|
+
const parsed = parseGithubRepoFullName(value);
|
|
10942
|
+
if (!parsed) throw new CliError("user", `${argName} must be owner/name or a GitHub URL`);
|
|
10943
|
+
return { owner: parsed.owner, repo: parsed.repo.replace(/\.git$/, "") };
|
|
10944
|
+
}
|
|
10945
|
+
function parseRepoArgOrCurrent(value, argName = "repo") {
|
|
10946
|
+
if (value === void 0) return currentRepo();
|
|
10947
|
+
return parseRepoArg(value, argName);
|
|
10948
|
+
}
|
|
10949
|
+
|
|
11122
10950
|
// src/commands/egress.ts
|
|
11123
10951
|
async function egressSourceSetCommand(sourceRepoArg, options = {}, command) {
|
|
11124
10952
|
const { config } = resolveBusinessContext(command, options);
|
|
@@ -11312,142 +11140,6 @@ async function modelsListCommand(options = {}, command) {
|
|
|
11312
11140
|
});
|
|
11313
11141
|
}
|
|
11314
11142
|
|
|
11315
|
-
// ../../shared/agent/verify-directive.ts
|
|
11316
|
-
var MAX_TARGET_PR_URL_LENGTH = 500;
|
|
11317
|
-
function normalizeGithubPullRequestUrl(rawUrl) {
|
|
11318
|
-
if (typeof rawUrl !== "string") return null;
|
|
11319
|
-
const trimmed = rawUrl.trim();
|
|
11320
|
-
if (!trimmed || trimmed.length > MAX_TARGET_PR_URL_LENGTH) return null;
|
|
11321
|
-
let url;
|
|
11322
|
-
try {
|
|
11323
|
-
url = new URL(trimmed);
|
|
11324
|
-
} catch {
|
|
11325
|
-
return null;
|
|
11326
|
-
}
|
|
11327
|
-
if (url.protocol !== "https:" || url.hostname.toLowerCase() !== "github.com") return null;
|
|
11328
|
-
const pathParts = url.pathname.split("/").filter(Boolean);
|
|
11329
|
-
if (pathParts.length !== 4 || pathParts[2] !== "pull" || !/^\d+$/.test(pathParts[3])) return null;
|
|
11330
|
-
if (!/^[A-Za-z0-9_.-]+$/.test(pathParts[0]) || !/^[A-Za-z0-9_.-]+$/.test(pathParts[1])) return null;
|
|
11331
|
-
return `https://github.com/${pathParts[0]}/${pathParts[1]}/pull/${pathParts[3]}`;
|
|
11332
|
-
}
|
|
11333
|
-
|
|
11334
|
-
// src/commands/qa.ts
|
|
11335
|
-
var QA_PROMPT = "Verify this pull request.";
|
|
11336
|
-
function parseCanonicalPrUrl(prUrl) {
|
|
11337
|
-
const targetPrUrl = normalizeGithubPullRequestUrl(prUrl);
|
|
11338
|
-
if (!targetPrUrl) {
|
|
11339
|
-
throw new CliError(
|
|
11340
|
-
"user",
|
|
11341
|
-
`Invalid pull request URL: "${prUrl}". Expected https://github.com/<owner>/<repo>/pull/<number>.`
|
|
11342
|
-
);
|
|
11343
|
-
}
|
|
11344
|
-
const url = new URL(targetPrUrl);
|
|
11345
|
-
const [owner, repo] = url.pathname.split("/").filter(Boolean);
|
|
11346
|
-
return {
|
|
11347
|
-
targetPrUrl,
|
|
11348
|
-
repoUrl: `https://github.com/${owner}/${repo}`
|
|
11349
|
-
};
|
|
11350
|
-
}
|
|
11351
|
-
function parseCreateConflict(error) {
|
|
11352
|
-
let message = error.message;
|
|
11353
|
-
let sessionId;
|
|
11354
|
-
let sessionUrl;
|
|
11355
|
-
const parsed = parseConflictBody(error);
|
|
11356
|
-
if (parsed) {
|
|
11357
|
-
const rawError = parsed.error;
|
|
11358
|
-
const parsedMessage = typeof rawError === "string" ? rawError : rawError && typeof rawError === "object" && typeof rawError.message === "string" ? rawError.message : void 0;
|
|
11359
|
-
if (parsedMessage) message = parsedMessage;
|
|
11360
|
-
if (typeof parsed.sessionId === "string") sessionId = parsed.sessionId;
|
|
11361
|
-
if (typeof parsed.sessionUrl === "string") sessionUrl = parsed.sessionUrl;
|
|
11362
|
-
}
|
|
11363
|
-
const location = sessionUrl ? ` (${sessionUrl})` : "";
|
|
11364
|
-
const existing = sessionId ? ` Existing verifier session: ${sessionId}${location}.` : "";
|
|
11365
|
-
return new CliError("conflict", `${message}${existing}`, {
|
|
11366
|
-
hint: error.hint,
|
|
11367
|
-
requestId: error.requestId
|
|
11368
|
-
});
|
|
11369
|
-
}
|
|
11370
|
-
async function qaCommand(prUrl, options, command) {
|
|
11371
|
-
const runtime = getRuntimeOptions(command, options);
|
|
11372
|
-
assertArcanistSessionMutationAllowed("qa");
|
|
11373
|
-
const config = requireConfig(runtime);
|
|
11374
|
-
const { targetPrUrl, repoUrl } = parseCanonicalPrUrl(prUrl);
|
|
11375
|
-
resolveModelAndBackend(options);
|
|
11376
|
-
const idempotencyKey = options.idempotencyKey ?? randomIdempotencyKey();
|
|
11377
|
-
const sessionIdempotencyKey = `${idempotencyKey}:session`;
|
|
11378
|
-
const promptIdempotencyKey = `${idempotencyKey}:prompt`;
|
|
11379
|
-
const body = {
|
|
11380
|
-
context: { repoUrl },
|
|
11381
|
-
qa: true,
|
|
11382
|
-
targetPrUrl
|
|
11383
|
-
};
|
|
11384
|
-
if (options.model) body.model = options.model;
|
|
11385
|
-
if (options.reasoningEffort) body.reasoningEffort = options.reasoningEffort;
|
|
11386
|
-
let sessionData;
|
|
11387
|
-
try {
|
|
11388
|
-
sessionData = await apiFetch(config, "/api/sessions", {
|
|
11389
|
-
method: "POST",
|
|
11390
|
-
headers: { "Idempotency-Key": sessionIdempotencyKey },
|
|
11391
|
-
body: JSON.stringify(body)
|
|
11392
|
-
});
|
|
11393
|
-
} catch (err) {
|
|
11394
|
-
if (err instanceof ApiError && err.status === 409) throw parseCreateConflict(err);
|
|
11395
|
-
throw err;
|
|
11396
|
-
}
|
|
11397
|
-
const sessionId = sessionData.sessionId;
|
|
11398
|
-
let promptId;
|
|
11399
|
-
if (!sessionData.promptAlreadyEnqueued) {
|
|
11400
|
-
try {
|
|
11401
|
-
const promptData = await apiFetch(config, `/api/sessions/${sessionId}/prompts`, {
|
|
11402
|
-
method: "POST",
|
|
11403
|
-
headers: { "Idempotency-Key": promptIdempotencyKey },
|
|
11404
|
-
body: JSON.stringify({ prompt: QA_PROMPT })
|
|
11405
|
-
});
|
|
11406
|
-
promptId = promptData.prompt?.promptId ?? promptData.prompt?.id;
|
|
11407
|
-
} catch (err) {
|
|
11408
|
-
throw new CliError(
|
|
11409
|
-
err instanceof CliError ? err.code : "server",
|
|
11410
|
-
`QA session created (${sessionId}) but prompt enqueue failed: ${stringifyError(err)}`,
|
|
11411
|
-
{
|
|
11412
|
-
exitCode: err instanceof CliError ? err.exitCode : void 0,
|
|
11413
|
-
hint: `Retry with: arcanist sessions qa ${targetPrUrl} --idempotency-key ${idempotencyKey}`,
|
|
11414
|
-
requestId: err instanceof CliError ? err.requestId : void 0,
|
|
11415
|
-
data: {
|
|
11416
|
-
...err instanceof CliError && err.data ? err.data : {},
|
|
11417
|
-
sessionId,
|
|
11418
|
-
...sessionData.sessionUrl ? { sessionUrl: sessionData.sessionUrl } : {}
|
|
11419
|
-
}
|
|
11420
|
-
}
|
|
11421
|
-
);
|
|
11422
|
-
}
|
|
11423
|
-
}
|
|
11424
|
-
if (options.wait) {
|
|
11425
|
-
const waitPollIntervalMs = parsePollInterval(options.pollInterval);
|
|
11426
|
-
if (!isJson(command, options)) {
|
|
11427
|
-
console.log(`Session: ${sessionId}`);
|
|
11428
|
-
if (sessionData.sessionUrl) console.log(`URL: ${sessionData.sessionUrl}`);
|
|
11429
|
-
}
|
|
11430
|
-
await waitForCreatedPrompt(sessionId, promptId, sessionData.sessionUrl, waitPollIntervalMs, runtime, command);
|
|
11431
|
-
if (!isJson(command, options)) {
|
|
11432
|
-
console.log(`Target PR: ${targetPrUrl}`);
|
|
11433
|
-
}
|
|
11434
|
-
}
|
|
11435
|
-
if (isJson(command, options)) {
|
|
11436
|
-
const output = { sessionId, repoUrl, targetPrUrl };
|
|
11437
|
-
if (sessionData.sessionUrl) output.sessionUrl = sessionData.sessionUrl;
|
|
11438
|
-
if (options.model) output.model = options.model;
|
|
11439
|
-
if (options.reasoningEffort) output.reasoningEffort = options.reasoningEffort;
|
|
11440
|
-
if (promptId) output.promptId = promptId;
|
|
11441
|
-
writeJson(output);
|
|
11442
|
-
return;
|
|
11443
|
-
}
|
|
11444
|
-
if (options.wait) return;
|
|
11445
|
-
console.log(`Session: ${sessionId}`);
|
|
11446
|
-
if (sessionData.sessionUrl) console.log(`URL: ${sessionData.sessionUrl}`);
|
|
11447
|
-
console.log(`Target PR: ${targetPrUrl}`);
|
|
11448
|
-
console.log(`Follow with: arcanist sessions events ${sessionId} --follow --json`);
|
|
11449
|
-
}
|
|
11450
|
-
|
|
11451
11143
|
// src/commands/repos.ts
|
|
11452
11144
|
async function reposListCommand(options = {}, command) {
|
|
11453
11145
|
const { config } = resolveBusinessContext(command, options);
|
|
@@ -12657,6 +12349,26 @@ async function sandboxUnassignRepoCommand(targetRepoArg, options = {}, command)
|
|
|
12657
12349
|
emit(command, options, payload, () => console.log(`Cleared sandbox layer assignment for ${repoPath(targetRepo)}.`));
|
|
12658
12350
|
}
|
|
12659
12351
|
|
|
12352
|
+
// src/utils/pagination.ts
|
|
12353
|
+
var MAX_ALL_PAGES = 1e3;
|
|
12354
|
+
async function fetchPages(label, all, initialCursor, fetchPage) {
|
|
12355
|
+
const items = [];
|
|
12356
|
+
let cursor = initialCursor;
|
|
12357
|
+
let nextCursor = null;
|
|
12358
|
+
let pageCount = 0;
|
|
12359
|
+
do {
|
|
12360
|
+
pageCount += 1;
|
|
12361
|
+
if (all && pageCount > MAX_ALL_PAGES) {
|
|
12362
|
+
throw new CliError("user", `${label} exceeded ${MAX_ALL_PAGES} pages without reaching the end.`);
|
|
12363
|
+
}
|
|
12364
|
+
const page = await fetchPage(cursor);
|
|
12365
|
+
items.push(...page.items);
|
|
12366
|
+
nextCursor = page.nextCursor;
|
|
12367
|
+
cursor = nextCursor ?? void 0;
|
|
12368
|
+
} while (all && nextCursor);
|
|
12369
|
+
return { items, nextCursor: all ? null : nextCursor };
|
|
12370
|
+
}
|
|
12371
|
+
|
|
12660
12372
|
// src/commands/sessions.ts
|
|
12661
12373
|
async function listSessionsCommand(options, command) {
|
|
12662
12374
|
const runtime = getRuntimeOptions(command, options);
|
|
@@ -13043,7 +12755,7 @@ function addCreateOptions(cmd) {
|
|
|
13043
12755
|
return cmd.argument("<repo-url>", "Repository URL").argument("[prompt]", "Prompt to send, or '-' to read stdin").option("--model <model>", "Model to use").option("--reasoning-effort <effort>", "Reasoning effort to use for models that support it").option("--auto-verify", "Opt this session into automatic QA verification after PR creation").option("--base-branch <branch>", "Base branch to create the session against (defaults to the repo default branch)").option(
|
|
13044
12756
|
"--start-branch <branch>",
|
|
13045
12757
|
"Resume an existing branch with its history instead of forking a new one off base"
|
|
13046
|
-
).option("--
|
|
12758
|
+
).option("--continue-pr <url>", "Continue from an existing same-repo pull request").option("--continue-mode <mode>", "Continuation mode: auto (default), update-pr, or new-pr").option("--browser-identity <id|none>", "Attach a business browser identity, or explicitly attach none").option("--prompt-stdin", "Read prompt from stdin").option(
|
|
13047
12759
|
"--uploaded-file <path>",
|
|
13048
12760
|
"Attach a local text file to the prompt as uploadedFiles; repeat for multiple files",
|
|
13049
12761
|
collectUploadedFileOption
|
|
@@ -13089,25 +12801,6 @@ JSON mode returns {sessionId, promptId?}
|
|
|
13089
12801
|
`
|
|
13090
12802
|
);
|
|
13091
12803
|
}
|
|
13092
|
-
function addQaOptions(cmd) {
|
|
13093
|
-
return cmd.argument("<pr-url>", "GitHub pull request URL to QA").option("--model <model>", "Model to use").option("--reasoning-effort <effort>", "Reasoning effort to use for models that support it").option("--wait", "Wait for the QA prompt to finish and exit non-zero if it fails").option(
|
|
13094
|
-
"--poll-interval <ms>",
|
|
13095
|
-
"Polling interval in milliseconds while waiting",
|
|
13096
|
-
String(DEFAULT_WATCH_POLL_INTERVAL_MS)
|
|
13097
|
-
).option("--idempotency-key <uuid>", "Request idempotency key for safe manual retries").addHelpText(
|
|
13098
|
-
"after",
|
|
13099
|
-
`
|
|
13100
|
-
Examples:
|
|
13101
|
-
arcanist sessions qa https://github.com/org/repo/pull/123
|
|
13102
|
-
arcanist sessions qa https://github.com/org/repo/pull/123 --model gpt-5.4 --wait
|
|
13103
|
-
arcanist sessions qa https://github.com/org/repo/pull/123 --idempotency-key 1f0e6f1a-...
|
|
13104
|
-
|
|
13105
|
-
JSON:
|
|
13106
|
-
JSON mode returns {sessionId, sessionUrl?, repoUrl, targetPrUrl, model?, reasoningEffort?, promptId?}
|
|
13107
|
-
Inspect progress with the session URL or the session events stream.
|
|
13108
|
-
`
|
|
13109
|
-
);
|
|
13110
|
-
}
|
|
13111
12804
|
var auth = program.command("auth").description("Authentication commands");
|
|
13112
12805
|
auth.command("login").description("Authenticate with a personal access token").option("--token-stdin", "Read token from stdin instead of interactive prompt").option("--api-url <url>", "Set custom API URL").addHelpText(
|
|
13113
12806
|
"after",
|
|
@@ -13177,9 +12870,6 @@ addCreateOptions(sessions.command("create").description("Create a session and se
|
|
|
13177
12870
|
addSendOptions(sessions.command("send").description("Send a message to an existing session")).action(
|
|
13178
12871
|
(sessionId, prompt, options, command) => messageCommand(sessionId, prompt, options, command)
|
|
13179
12872
|
);
|
|
13180
|
-
addQaOptions(sessions.command("qa").description("Start a QA verification session for a GitHub pull request")).action(
|
|
13181
|
-
(prUrl, options, command) => qaCommand(prUrl, options, command)
|
|
13182
|
-
);
|
|
13183
12873
|
sessions.command("respond").description("Answer a pending session question").argument("<session-id>", "Session ID").argument("[answer]", "Answer text, or '-' to read stdin").option("--answer-stdin", "Read answer from stdin").requiredOption("--question-id <id>", "Question ID from the question event").addHelpText(
|
|
13184
12874
|
"after",
|
|
13185
12875
|
`
|
|
@@ -13312,40 +13002,6 @@ JSON:
|
|
|
13312
13002
|
JSON mode returns {models}
|
|
13313
13003
|
`
|
|
13314
13004
|
).action((options, command) => modelsListCommand(options, command));
|
|
13315
|
-
var automations = program.command("automations").description("Automation commands");
|
|
13316
|
-
automations.command("create").description("Create a scheduled automation").argument("<repo-url>", "Repository URL").argument("[prompt]", "Prompt to run, or '-' to read stdin").requiredOption("--cron <expr>", "Cron expression").option("--name <name>", "Automation display name").option(
|
|
13317
|
-
"--model <model>",
|
|
13318
|
-
"Model to pin for this automation's sessions (e.g. gpt-5.5). Defaults to the codex backend default."
|
|
13319
|
-
).option(
|
|
13320
|
-
"--slack-team-id <team-id>",
|
|
13321
|
-
"Slack workspace/team ID for final digest delivery (requires --slack-channel-id)"
|
|
13322
|
-
).option("--slack-channel-id <channel-id>", "Slack channel ID for final digest delivery (requires --slack-team-id)").option("--prompt-stdin", "Read prompt from stdin").addHelpText(
|
|
13323
|
-
"after",
|
|
13324
|
-
`
|
|
13325
|
-
Examples:
|
|
13326
|
-
arcanist automations create tryarcanist/arcanist "summarize regressions" --cron "*/15 * * * *"
|
|
13327
|
-
arcanist automations create tryarcanist/arcanist "audit N+1s" --cron "0 13 * * 5" --model gpt-5.5
|
|
13328
|
-
arcanist automations create tryarcanist/arcanist "/audit-zeus-day yesterday" --cron "0 8 * * *" --slack-team-id T0123ABC --slack-channel-id C0456DEF
|
|
13329
|
-
printf "summarize regressions" | arcanist automations create tryarcanist/arcanist --prompt-stdin --cron "*/15 * * * *" --json
|
|
13330
|
-
`
|
|
13331
|
-
).action((repoUrl, prompt, options, command) => createAutomationCommand(repoUrl, prompt, options, command));
|
|
13332
|
-
automations.command("list").description("List scheduled automations").option("--limit <n>", "Maximum automations to return").option("--cursor <cursor>", "Pagination cursor").option("--all", "Fetch all pages").addHelpText(
|
|
13333
|
-
"after",
|
|
13334
|
-
`
|
|
13335
|
-
Examples:
|
|
13336
|
-
arcanist automations list
|
|
13337
|
-
arcanist automations list --limit 20 --json
|
|
13338
|
-
arcanist automations list --all --json
|
|
13339
|
-
`
|
|
13340
|
-
).action((options, command) => listAutomationsCommand(options, command));
|
|
13341
|
-
automations.command("delete").description("Delete a scheduled automation").argument("<id>", "Automation ID").option("--yes", "Confirm deletion without prompting").addHelpText(
|
|
13342
|
-
"after",
|
|
13343
|
-
`
|
|
13344
|
-
Examples:
|
|
13345
|
-
arcanist automations delete auto_123
|
|
13346
|
-
arcanist automations delete auto_123 --yes --json
|
|
13347
|
-
`
|
|
13348
|
-
).action((id, options, command) => deleteAutomationCommand(id, options, command));
|
|
13349
13005
|
var sandbox = program.command("sandbox").description("Sandbox layer commands");
|
|
13350
13006
|
sandbox.command("init").description("Create sandbox layer source files").action((options, command) => sandboxInitCommand(options, command));
|
|
13351
13007
|
sandbox.command("validate").description("Validate local sandbox layer source files").option("--manifest <path>", "Manifest path", ".arcanist/sandbox.yaml").action((options, command) => sandboxValidateCommand(options, command));
|