@nyssance/codex-acp-v2 0.7.2 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -21844,7 +21844,7 @@ var package_default = {
21844
21844
  publishConfig: {
21845
21845
  access: "public"
21846
21846
  },
21847
- version: "0.7.2",
21847
+ version: "0.7.4",
21848
21848
  description: "Native ACP v2 agent for the OpenAI Codex app-server",
21849
21849
  packageManager: "bun@1.4.0",
21850
21850
  engines: {
@@ -21916,7 +21916,7 @@ var package_default = {
21916
21916
  "vscode-jsonrpc": "^9.0.2"
21917
21917
  },
21918
21918
  optionalDependencies: {
21919
- "@openai/codex": "^0.158.0"
21919
+ "@openai/codex": "^0.159.2"
21920
21920
  }
21921
21921
  };
21922
21922
 
@@ -22076,6 +22076,8 @@ function classifyTurnError(info) {
22076
22076
  case "cyberPolicy":
22077
22077
  case "misalignmentPolicyViolation":
22078
22078
  return failure2("policy", false, "refusal");
22079
+ case "tooManyDenials":
22080
+ return failure2("too_many_denials");
22079
22081
  case "sessionBudgetExceeded":
22080
22082
  return failure2("budget");
22081
22083
  case "usageLimitExceeded":
@@ -24204,138 +24206,10 @@ function withWritableRoots(policy, roots) {
24204
24206
  return { ...policy, writableRoots: [...new Set([...policy.writableRoots, ...roots])] };
24205
24207
  }
24206
24208
 
24207
- // src/codex/models.ts
24208
- var MODEL_CONFIG_ID = "model";
24209
- var EFFORT_CONFIG_ID = "effort";
24210
- var FAST_MODE_CONFIG_ID = "fast_mode";
24211
- var COLLABORATION_MODE_CONFIG_ID = "collaboration_mode";
24212
- var DEFAULT_EFFORT = "medium";
24213
- var FAST_SERVICE_TIER = "fast";
24214
- function resolveModelSelection(catalog, modelId, effort) {
24215
- const listed = modelId === null ? undefined : catalog.find((model) => model.id === modelId);
24216
- if (listed)
24217
- return { model: listed.id, effort: effort ?? listed.defaultReasoningEffort };
24218
- if (modelId)
24219
- return { model: modelId, effort: effort ?? DEFAULT_EFFORT };
24220
- const fallback = catalog.find((model) => model.isDefault) ?? catalog[0];
24221
- if (!fallback)
24222
- throw new Error("Codex did not return any models");
24223
- return { model: fallback.id, effort: effort ?? fallback.defaultReasoningEffort };
24224
- }
24225
- function findModel(catalog, modelId) {
24226
- return catalog.find((model) => model.id === modelId);
24227
- }
24228
- function supportedEfforts(model) {
24229
- return model?.supportedReasoningEfforts ?? [];
24230
- }
24231
- function coerceEffort(model, effort) {
24232
- const options = supportedEfforts(model);
24233
- if (options.length === 0)
24234
- return effort;
24235
- if (options.some((option) => option.reasoningEffort === effort))
24236
- return effort;
24237
- return model?.defaultReasoningEffort ?? options[0].reasoningEffort;
24238
- }
24239
- function modelSupportsFast(model) {
24240
- if (!model)
24241
- return false;
24242
- return model.serviceTiers.some((tier) => tier.id === FAST_SERVICE_TIER) || model.additionalSpeedTiers.includes(FAST_SERVICE_TIER);
24243
- }
24244
- function modelSupportsImages(model) {
24245
- return model?.inputModalities.includes("image") ?? true;
24246
- }
24247
- function modelLacksReasoning(model) {
24248
- const options = supportedEfforts(model);
24249
- return options.length > 0 && options.every((option) => option.reasoningEffort === "none");
24250
- }
24251
- function modelConfigOption(catalog, current, gatewayGroups = []) {
24252
- const visible = catalog.filter((model) => !model.hidden || model.id === current);
24253
- const options = visible.map((model) => ({
24254
- value: model.id,
24255
- name: model.displayName,
24256
- description: model.description || null
24257
- }));
24258
- if (!options.some((option) => option.value === current)) {
24259
- options.unshift({ value: current, name: current, description: null });
24260
- }
24261
- const base = {
24262
- configId: MODEL_CONFIG_ID,
24263
- name: "Model",
24264
- description: "Model Codex uses for this session",
24265
- category: "model",
24266
- type: "select",
24267
- currentValue: current
24268
- };
24269
- if (gatewayGroups.length === 0)
24270
- return { ...base, options };
24271
- const owner = new Map;
24272
- for (const group of gatewayGroups)
24273
- for (const id of group.modelIds)
24274
- owner.set(id, group.id);
24275
- const native = options.filter((option) => !owner.has(option.value));
24276
- return {
24277
- ...base,
24278
- options: [
24279
- { groupId: "codex", name: "Codex", options: native },
24280
- ...gatewayGroups.map((group) => ({ groupId: group.id, name: group.name, options: options.filter((option) => owner.get(option.value) === group.id) }))
24281
- ]
24282
- };
24283
- }
24284
- function effortConfigOption(efforts, current) {
24285
- const options = efforts.map((option) => ({
24286
- value: option.reasoningEffort,
24287
- name: capitalize(option.reasoningEffort),
24288
- description: option.description || null
24289
- }));
24290
- if (!options.some((option) => option.value === current)) {
24291
- options.unshift({ value: current, name: capitalize(current), description: null });
24292
- }
24293
- return {
24294
- configId: EFFORT_CONFIG_ID,
24295
- name: "Reasoning effort",
24296
- description: "How much reasoning effort the model should use",
24297
- category: "thought_level",
24298
- type: "select",
24299
- currentValue: current,
24300
- options
24301
- };
24302
- }
24303
- function fastModeConfigOption(enabled) {
24304
- return {
24305
- configId: FAST_MODE_CONFIG_ID,
24306
- name: "Fast mode",
24307
- description: "1.5x speed, increased usage",
24308
- category: "model_config",
24309
- type: "boolean",
24310
- currentValue: enabled
24311
- };
24312
- }
24313
- var DEFAULT_COLLABORATION_MODE = "default";
24314
- var PLAN_COLLABORATION_MODE = "plan";
24315
- function collaborationModeConfigOption(current) {
24316
- return {
24317
- configId: COLLABORATION_MODE_CONFIG_ID,
24318
- name: "Collaboration mode",
24319
- description: "How Codex collaborates on subsequent turns",
24320
- category: "model_config",
24321
- type: "select",
24322
- currentValue: current,
24323
- options: [
24324
- { value: DEFAULT_COLLABORATION_MODE, name: "Default", description: "Make changes directly" },
24325
- { value: PLAN_COLLABORATION_MODE, name: "Plan", description: "Plan before making changes" }
24326
- ]
24327
- };
24328
- }
24329
- function parseCollaborationMode(value) {
24330
- if (value === DEFAULT_COLLABORATION_MODE)
24331
- return DEFAULT_COLLABORATION_MODE;
24332
- if (value === PLAN_COLLABORATION_MODE)
24333
- return PLAN_COLLABORATION_MODE;
24334
- return;
24335
- }
24336
- function capitalize(value) {
24337
- return value.length === 0 ? value : value[0].toUpperCase() + value.slice(1);
24338
- }
24209
+ // src/agent/providers.ts
24210
+ import { readFileSync } from "fs";
24211
+ import os from "os";
24212
+ import path4 from "path";
24339
24213
 
24340
24214
  // src/permissions/json.ts
24341
24215
  function isRecord2(value) {
@@ -24362,2289 +24236,2424 @@ function normalizeJsonObject(value) {
24362
24236
  return Object.fromEntries(Object.entries(value).filter(([, nested]) => nested !== undefined).map(([key, nested]) => [key, normalizeJsonValue(nested)]));
24363
24237
  }
24364
24238
 
24365
- // src/permissions/commandDecisions.ts
24366
- function availableCommandDecisions(params) {
24367
- if (params.availableDecisions === undefined || params.availableDecisions === null) {
24368
- return defaultDecisions(params);
24369
- }
24370
- if (!Array.isArray(params.availableDecisions) || params.availableDecisions.length === 0)
24371
- return;
24372
- const decisions = [];
24373
- for (const candidate of params.availableDecisions) {
24374
- const decision = parseDecision(candidate, params);
24375
- if (!decision)
24376
- return;
24377
- decisions.push(decision);
24378
- }
24379
- return decisions;
24239
+ // src/agent/providers.ts
24240
+ var OPENAI_PROVIDER_ID = "openai";
24241
+ var GATEWAY_MODEL_PROVIDER = "custom-gateway";
24242
+ var DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
24243
+ var NATIVE_GROUP_ID = "chatgpt";
24244
+ var NATIVE_GROUP_NAME = "ChatGPT";
24245
+ var GATEWAY_ID_PATTERN = /^[a-z0-9-]+$/;
24246
+ function gatewayModelId(connectionId, modelId) {
24247
+ return `gateway:${encodeURIComponent(connectionId)}:${encodeURIComponent(modelId)}`;
24380
24248
  }
24381
- function defaultDecisions(params) {
24382
- if (params.networkApprovalContext) {
24383
- const decisions2 = ["accept", "acceptForSession"];
24384
- for (const amendment of params.proposedNetworkPolicyAmendments ?? []) {
24385
- decisions2.push({ applyNetworkPolicyAmendment: { network_policy_amendment: amendment } });
24386
- }
24387
- decisions2.push("decline", "cancel");
24388
- return decisions2;
24389
- }
24390
- if (params.additionalPermissions)
24391
- return ["accept", "cancel"];
24392
- const decisions = ["accept", "acceptForSession"];
24393
- if (params.proposedExecpolicyAmendment && params.proposedExecpolicyAmendment.length > 0) {
24394
- decisions.push({ acceptWithExecpolicyAmendment: { execpolicy_amendment: params.proposedExecpolicyAmendment } });
24249
+
24250
+ class ProviderRouting {
24251
+ baseConfig;
24252
+ configuredProvider;
24253
+ gateways = new Map;
24254
+ constructor(baseConfig, configuredProvider) {
24255
+ this.baseConfig = baseConfig;
24256
+ this.configuredProvider = configuredProvider;
24395
24257
  }
24396
- decisions.push("decline", "cancel");
24397
- return decisions;
24398
- }
24399
- function parseDecision(candidate, params) {
24400
- if (candidate === "accept" || candidate === "acceptForSession" || candidate === "decline" || candidate === "cancel") {
24401
- return candidate;
24258
+ copy() {
24259
+ const copy = new ProviderRouting(this.baseConfig, this.configuredProvider);
24260
+ copy.gateways = new Map(this.gateways);
24261
+ return copy;
24402
24262
  }
24403
- if (!isRecord2(candidate))
24404
- return;
24405
- if ("acceptWithExecpolicyAmendment" in candidate) {
24406
- const value = candidate["acceptWithExecpolicyAmendment"];
24407
- if (!isRecord2(value))
24408
- return;
24409
- const amendment = value["execpolicy_amendment"];
24410
- if (!isStringArray(amendment) || amendment.length === 0)
24411
- return;
24412
- if (!sameStrings(amendment, params.proposedExecpolicyAmendment))
24413
- return;
24414
- return { acceptWithExecpolicyAmendment: { execpolicy_amendment: [...amendment] } };
24263
+ get active() {
24264
+ return this.gateways.values().next().value ?? null;
24415
24265
  }
24416
- if ("applyNetworkPolicyAmendment" in candidate) {
24417
- const value = candidate["applyNetworkPolicyAmendment"];
24418
- if (!isRecord2(value))
24419
- return;
24420
- const amendment = parseNetworkAmendment(value["network_policy_amendment"]);
24421
- if (!amendment || !params.networkApprovalContext)
24422
- return;
24423
- if (amendment.host !== params.networkApprovalContext.host)
24424
- return;
24425
- const proposed = params.proposedNetworkPolicyAmendments ?? [];
24426
- if (!proposed.some((entry) => entry.host === amendment.host && entry.action === amendment.action))
24427
- return;
24428
- return { applyNetworkPolicyAmendment: { network_policy_amendment: amendment } };
24266
+ gateway(id) {
24267
+ return id === null ? null : this.gateways.get(id) ?? null;
24429
24268
  }
24430
- return;
24431
- }
24432
- function parseNetworkAmendment(value) {
24433
- if (!isRecord2(value) || typeof value["host"] !== "string")
24434
- return;
24435
- const action = value["action"];
24436
- if (action !== "allow" && action !== "deny")
24437
- return;
24438
- return { host: value["host"], action };
24439
- }
24440
- function isStringArray(value) {
24441
- return Array.isArray(value) && value.every((entry) => typeof entry === "string");
24442
- }
24443
- function sameStrings(left, right) {
24444
- return !!right && left.length === right.length && left.every((value, index) => value === right[index]);
24445
- }
24446
- function renderExecPolicyPrefix(command) {
24447
- const script = extractWrappedScript(command);
24448
- if (script !== undefined)
24449
- return script;
24450
- if (command.some((value) => value.includes("\x00")))
24451
- return command.join(" ");
24452
- return command.map(shlexQuote).join(" ");
24453
- }
24454
- function extractWrappedScript(command) {
24455
- const executable = executableName(command[0]);
24456
- if ((executable === "bash" || executable === "zsh" || executable === "sh") && command.length === 3 && (command[1] === "-lc" || command[1] === "-c")) {
24457
- return command[2];
24269
+ hasGateway(id) {
24270
+ return id !== null && this.gateways.has(id);
24458
24271
  }
24459
- if (executable !== "pwsh" && executable !== "powershell")
24460
- return;
24461
- const allowedFlags = new Set(["-nologo", "-noprofile", "-command", "-c"]);
24462
- for (let index = 1;index + 1 < command.length; index++) {
24463
- const flag = command[index]?.toLowerCase();
24464
- if (flag === undefined || !allowedFlags.has(flag))
24465
- return;
24466
- if (flag === "-command" || flag === "-c")
24467
- return command[index + 1];
24272
+ list() {
24273
+ const first = this.active;
24274
+ const routed = first !== null && first.mode === "route";
24275
+ const summary = (gateway) => ({ id: gateway.id, name: gateway.name, baseUrl: gateway.baseUrl, models: gateway.models.map((model) => model.id) });
24276
+ return {
24277
+ providers: [{
24278
+ providerId: OPENAI_PROVIDER_ID,
24279
+ supported: ["openai"],
24280
+ required: false,
24281
+ current: routed && first ? { apiType: "openai", baseUrl: first.baseUrl } : { apiType: "openai", baseUrl: this.nativeBaseUrl() },
24282
+ ...first && !routed ? { _meta: { codex: { mode: "catalog", gateway: summary(first), gateways: [...this.gateways.values()].map(summary) } } } : {}
24283
+ }]
24284
+ };
24468
24285
  }
24469
- return;
24470
- }
24471
- function executableName(command) {
24472
- let filename = command?.replaceAll("\\", "/").split("/").at(-1);
24473
- while (filename !== undefined) {
24474
- if (["bash", "zsh", "sh", "pwsh", "powershell"].includes(filename))
24475
- return filename;
24476
- const dot = filename.lastIndexOf(".");
24477
- if (dot <= 0)
24478
- return;
24479
- filename = filename.slice(0, dot);
24286
+ set(request) {
24287
+ if (request.providerId !== OPENAI_PROVIDER_ID) {
24288
+ throw RequestError.invalidParams({ providerId: request.providerId }, `Unknown providerId "${request.providerId}"; only "${OPENAI_PROVIDER_ID}" is configurable`);
24289
+ }
24290
+ if (request.apiType !== "openai") {
24291
+ throw RequestError.invalidParams({ apiType: request.apiType }, `Codex only speaks the OpenAI protocol; got apiType "${request.apiType}"`);
24292
+ }
24293
+ const baseUrl = typeof request.baseUrl === "string" ? request.baseUrl.trim() : "";
24294
+ if (!/^https?:\/\//.test(baseUrl)) {
24295
+ throw RequestError.invalidParams({ baseUrl: request.baseUrl }, "baseUrl must be an http(s) URL");
24296
+ }
24297
+ const hints = readHints(request._meta);
24298
+ if (hints.id === OPENAI_PROVIDER_ID || hints.id === this.configuredProvider) {
24299
+ throw RequestError.invalidParams({ id: hints.id }, `_meta.codex.id "${hints.id}" is Codex's own provider; pick another gateway id`);
24300
+ }
24301
+ if (hints.mode === "route" || this.mode !== hints.mode)
24302
+ this.gateways.clear();
24303
+ this.gateways.set(hints.id, {
24304
+ id: hints.id,
24305
+ baseUrl,
24306
+ headers: { ...request.headers ?? {} },
24307
+ name: hints.name ?? "Client-configured gateway",
24308
+ models: hints.models,
24309
+ model: hints.model,
24310
+ bearerToken: hints.bearerToken,
24311
+ config: hints.config,
24312
+ mode: hints.mode,
24313
+ catalogEntries: hints.catalogEntries
24314
+ });
24480
24315
  }
24481
- return;
24482
- }
24483
- function shlexQuote(value) {
24484
- if (value.length === 0)
24485
- return "''";
24486
- if (/^[0-9A-Za-z+\-./:@_]+$/.test(value))
24487
- return value;
24488
- if (!value.includes("'"))
24489
- return `'${value}'`;
24490
- return `"${value.replace(/["\\$`!]/g, "\\$&")}"`;
24491
- }
24492
-
24493
- // src/permissions/options.ts
24494
- var OptionId = {
24495
- AllowOnce: "allow_once",
24496
- AllowForSession: "allow_for_session",
24497
- Decline: "decline",
24498
- Cancel: "cancel",
24499
- AcceptWithExecpolicyAmendment: "accept_execpolicy_amendment",
24500
- ApplyNetworkPolicyAmendment: "apply_network_policy_amendment",
24501
- AllowPermissionsForTurn: "allow_permissions_turn",
24502
- AllowPermissionsForTurnStrict: "allow_permissions_turn_strict_auto_review",
24503
- AllowPermissionsForSession: "allow_permissions_session",
24504
- RejectPermissions: "reject_permissions"
24505
- };
24506
- function commandDecisionOptions(params) {
24507
- const decisions = availableCommandDecisions(params);
24508
- if (!decisions)
24509
- return;
24510
- const options = [];
24511
- let networkIndex = 0;
24512
- for (const decision of decisions) {
24513
- if (decision === "accept") {
24514
- options.push(decisionOption(OptionId.AllowOnce, params.networkApprovalContext ? "Yes, just this once" : "Yes, proceed", "allow_once", decision));
24515
- } else if (decision === "acceptForSession") {
24516
- const name = params.networkApprovalContext ? "Yes, and allow this host for this conversation" : params.additionalPermissions ? "Yes, and allow these permissions for this session" : "Yes, and don't ask again for this command in this session";
24517
- options.push(decisionOption(OptionId.AllowForSession, name, "allow_always", decision));
24518
- } else if (decision === "decline") {
24519
- options.push(decisionOption(OptionId.Decline, "No, continue without running it", "reject_once", decision));
24520
- } else if (decision === "cancel") {
24521
- options.push(decisionOption(OptionId.Cancel, "No, and tell Codex what to do differently", "reject_once", decision));
24522
- } else if ("acceptWithExecpolicyAmendment" in decision) {
24523
- const prefix = renderExecPolicyPrefix(decision.acceptWithExecpolicyAmendment.execpolicy_amendment);
24524
- if (prefix.includes(`
24525
- `) || prefix.includes("\r"))
24526
- continue;
24527
- options.push(decisionOption(OptionId.AcceptWithExecpolicyAmendment, `Yes, and don't ask again for commands that start with \`${prefix}\``, "allow_always", decision));
24528
- } else {
24529
- const amendment = decision.applyNetworkPolicyAmendment.network_policy_amendment;
24530
- options.push(decisionOption(`${OptionId.ApplyNetworkPolicyAmendment}:${networkIndex++}`, amendment.action === "allow" ? "Yes, and allow this host in the future" : "No, and block this host in the future", amendment.action === "allow" ? "allow_always" : "reject_always", decision));
24316
+ get mode() {
24317
+ return this.active?.mode ?? "route";
24318
+ }
24319
+ gatewayFor(modelId) {
24320
+ for (const gateway of this.gateways.values()) {
24321
+ if (gateway.models.some((model) => (this.mode === "catalog" ? gatewayModelId(gateway.id, model.id) : model.id) === modelId))
24322
+ return gateway;
24531
24323
  }
24324
+ return null;
24532
24325
  }
24533
- const ordered = [...options].sort((left, right) => order(left.option) - order(right.option));
24534
- const hasAllow = ordered.some(({ option }) => option.kind === "allow_once" || option.kind === "allow_always");
24535
- const hasReject = ordered.some(({ option }) => option.kind === "reject_once" || option.kind === "reject_always");
24536
- const ids = ordered.map(({ option }) => option.optionId);
24537
- return hasAllow && hasReject && new Set(ids).size === ids.length ? ordered : undefined;
24538
- }
24539
- function order(option) {
24540
- if (option.kind === "allow_once")
24541
- return 0;
24542
- if (option.kind === "allow_always")
24543
- return 1;
24544
- return 2;
24545
- }
24546
- function fileChangeDecisionOptions() {
24547
- return [
24548
- decisionOption(OptionId.AllowOnce, "Yes, proceed", "allow_once", "accept"),
24549
- decisionOption(OptionId.AllowForSession, "Yes, and don't ask again for these files", "allow_always", "acceptForSession"),
24550
- decisionOption(OptionId.Cancel, "No, and tell Codex what to do differently", "reject_once", "cancel")
24551
- ];
24552
- }
24553
- function permissionProfileOptions() {
24554
- return [
24555
- permissionOption(OptionId.AllowPermissionsForTurn, "Yes, grant these permissions for this turn", "allow_once"),
24556
- permissionOption(OptionId.AllowPermissionsForTurnStrict, "Yes, grant for this turn with strict auto review", "allow_once"),
24557
- permissionOption(OptionId.AllowPermissionsForSession, "Yes, grant these permissions for this session", "allow_always"),
24558
- permissionOption(OptionId.RejectPermissions, "No, continue without permissions", "reject_once")
24559
- ];
24560
- }
24561
- function decisionOption(optionId, name, kind, decision) {
24562
- return { option: permissionOption(optionId, name, kind), decision };
24563
- }
24564
- function permissionOption(optionId, name, kind, description) {
24565
- return { optionId, name, kind, ...description ? { _meta: { codex: { description } } } : {} };
24566
- }
24567
- function selectedDecision(response, decisions) {
24568
- if (response.outcome.outcome !== "selected")
24569
- return;
24570
- const optionId = response.outcome.optionId;
24571
- return decisions.find(({ option }) => option.optionId === optionId)?.decision;
24572
- }
24573
-
24574
- // src/permissions/presentation.ts
24575
- var COMMAND_TITLE = "Run command?";
24576
- var NETWORK_TITLE = "Allow network access?";
24577
- var FILE_CHANGE_TITLE = "Make edits?";
24578
- var PERMISSIONS_TITLE = "Grant permissions?";
24579
- function commandSubject(params) {
24580
- const network = params.networkApprovalContext;
24581
- const rawInput = {
24582
- ...params.command ? { command: stripShellPrefix(params.command) } : {},
24583
- ...params.cwd ? { cwd: params.cwd } : {},
24584
- ...network?.protocol === "http" || network?.protocol === "https" ? { url: `${network.protocol}://${network.host}` } : {},
24585
- ...params.additionalPermissions ? { additionalPermissions: params.additionalPermissions } : {}
24586
- };
24587
- const content = [];
24588
- if (network)
24589
- content.push(text(`${network.protocol} access to ${network.host}`));
24590
- if (params.additionalPermissions)
24591
- content.push(...profileContent(params.additionalPermissions));
24592
- const locations = unique([...commandActionPaths(params.commandActions), ...profilePaths(params.additionalPermissions)]);
24593
- return {
24594
- toolCallId: params.itemId,
24595
- name: ToolName.Shell,
24596
- kind: "execute",
24597
- status: "pending",
24598
- title: network ? `${network.protocol} network access to ${network.host}` : commandTitle(params.command, params.commandActions),
24599
- ...Object.keys(rawInput).length > 0 ? { rawInput } : {},
24600
- ...locations.length > 0 ? { locations: locations.map((path4) => ({ path: path4 })) } : {},
24601
- ...content.length > 0 ? { content } : {}
24602
- };
24603
- }
24604
- function fileChangeSubject(params, item) {
24605
- const paths = item ? changePaths(item.changes) : [];
24606
- return {
24607
- toolCallId: params.itemId,
24608
- name: ToolName.ApplyPatch,
24609
- kind: "edit",
24610
- status: "pending",
24611
- title: paths.length === 1 ? `Edit ${paths[0]}` : paths.length > 1 ? `Edit ${paths.length} files` : "Edit files",
24612
- ...paths.length > 0 ? { locations: paths.map((path4) => ({ path: path4 })) } : {},
24613
- ...item ? { content: item.changes.map(diffContent) } : {},
24614
- ...params.grantRoot ? { rawInput: { grantRoot: params.grantRoot } } : {}
24615
- };
24616
- }
24617
- function permissionsSubject(itemId, cwd, environmentId, permissions) {
24618
- const content = profileContent(permissions);
24619
- const paths = profilePaths(permissions);
24620
- return {
24621
- toolCallId: itemId,
24622
- name: ToolName.Shell,
24623
- kind: "other",
24624
- status: "pending",
24625
- title: "Additional sandbox permissions",
24626
- rawInput: { permissions, cwd, environmentId },
24627
- ...paths.length > 0 ? { locations: paths.map((path4) => ({ path: path4 })) } : {},
24628
- ...content.length > 0 ? { content } : {}
24629
- };
24630
- }
24631
- function commandTitle(command, actions) {
24632
- const first = actions?.[0];
24633
- if (first?.type === "read")
24634
- return actions?.length === 1 ? `Read ${first.path}` : "Run command with file reads";
24635
- if (first?.type === "listFiles")
24636
- return "List files";
24637
- if (first?.type === "search")
24638
- return "Search files";
24639
- return command ? stripShellPrefix(command) : "Run command";
24640
- }
24641
- function profilePaths(permissions) {
24642
- const fileSystem = permissions?.fileSystem;
24643
- return unique([
24644
- ...fileSystem?.read ?? [],
24645
- ...fileSystem?.write ?? [],
24646
- ...(fileSystem?.entries ?? []).flatMap((entry) => entry.path.type === "path" ? [entry.path.path] : [])
24647
- ]);
24648
- }
24649
- function profileContent(permissions) {
24650
- const lines = [];
24651
- const networkEnabled = permissions.network?.enabled;
24652
- if (networkEnabled !== null && networkEnabled !== undefined) {
24653
- lines.push(networkEnabled ? "Enable network access" : "Disable network access");
24326
+ gatewayIdFor(modelId) {
24327
+ return this.gatewayFor(modelId)?.id ?? null;
24654
24328
  }
24655
- for (const entry of permissions.fileSystem?.entries ?? []) {
24656
- if (entry.path.type === "glob_pattern")
24657
- lines.push(`${entry.access} filesystem pattern ${entry.path.pattern}`);
24658
- else if (entry.path.type === "special")
24659
- lines.push(`${entry.access} Codex filesystem scope ${JSON.stringify(entry.path.value)}`);
24329
+ isGatewayModel(modelId) {
24330
+ return this.gatewayFor(modelId) !== null;
24660
24331
  }
24661
- return lines.length > 0 ? [text(lines.join(`
24662
- `))] : [];
24663
- }
24664
- function text(value) {
24665
- return { type: "content", content: { type: "text", text: value } };
24666
- }
24667
- function unique(values) {
24668
- return [...new Set(values)];
24669
- }
24670
-
24671
- // src/permissions/ApprovalHandler.ts
24672
- class CodexApprovalHandler {
24673
- client;
24674
- turn;
24675
- signal;
24676
- constructor(client, turn, signal = () => {
24677
- return;
24678
- }) {
24679
- this.client = client;
24680
- this.turn = turn;
24681
- this.signal = signal;
24332
+ gatewayGroups() {
24333
+ if (this.mode !== "catalog")
24334
+ return [];
24335
+ return [...this.gateways.values()].filter((gateway) => gateway.models.length > 0).map((gateway) => ({ id: gateway.id, name: gateway.name, modelIds: gateway.models.map((model) => gatewayModelId(gateway.id, model.id)) }));
24682
24336
  }
24683
- async handleCommandExecution(params) {
24684
- const approvalParams = params;
24685
- const decisions = commandDecisionOptions(approvalParams);
24686
- if (!decisions) {
24687
- logger.log("cancelling command approval: no complete decision set", { itemId: params.itemId });
24688
- return { decision: "cancel" };
24689
- }
24690
- try {
24691
- const response = await this.client.requestPermission({
24692
- title: params.networkApprovalContext ? NETWORK_TITLE : COMMAND_TITLE,
24693
- description: nonBlank(params.reason),
24694
- subject: { type: "tool_call", toolCall: commandSubject(approvalParams) },
24695
- options: decisions.map(({ option }) => option)
24696
- }, this.signal());
24697
- return { decision: selectedDecision(response, decisions) ?? "cancel" };
24698
- } catch (error40) {
24699
- logger.error("command approval failed", error40, { itemId: params.itemId });
24700
- return { decision: "cancel" };
24701
- }
24337
+ selectionId(modelId, provider) {
24338
+ return modelId !== null && this.mode === "catalog" && this.hasGateway(provider) ? gatewayModelId(provider, modelId) : modelId;
24339
+ }
24340
+ upstreamModel(selectionId) {
24341
+ if (selectionId === null || !selectionId.startsWith("gateway:"))
24342
+ return selectionId;
24343
+ const gateway = this.gatewayFor(selectionId);
24344
+ const model = gateway?.models.find((model2) => gatewayModelId(gateway.id, model2.id) === selectionId);
24345
+ if (!model)
24346
+ throw RequestError.invalidParams({ model: selectionId }, "The selected provider connection or model is unavailable");
24347
+ return model.id;
24348
+ }
24349
+ disable(request) {
24350
+ if (request.providerId !== OPENAI_PROVIDER_ID)
24351
+ return;
24352
+ const id = readGatewayId(request._meta);
24353
+ if (id === null)
24354
+ this.gateways.clear();
24355
+ else
24356
+ this.gateways.delete(id);
24357
+ }
24358
+ modelProvider() {
24359
+ return this.defaultGatewayId() ?? this.configuredProvider;
24702
24360
  }
24703
- async handleFileChange(params) {
24704
- const decisions = fileChangeDecisionOptions();
24705
- try {
24706
- const response = await this.client.requestPermission({
24707
- title: FILE_CHANGE_TITLE,
24708
- description: nonBlank(params.reason),
24709
- subject: { type: "tool_call", toolCall: fileChangeSubject(params, this.turn.fileChange(params.itemId)) },
24710
- options: decisions.map(({ option }) => option)
24711
- }, this.signal());
24712
- return { decision: selectedDecision(response, decisions) ?? "cancel" };
24713
- } catch (error40) {
24714
- logger.error("file change approval failed", error40, { itemId: params.itemId });
24715
- return { decision: "cancel" };
24716
- }
24361
+ routesByDefault() {
24362
+ return this.defaultGatewayId() !== null;
24717
24363
  }
24718
- async handlePermissionsRequest(params) {
24719
- try {
24720
- const response = await this.client.requestPermission({
24721
- title: PERMISSIONS_TITLE,
24722
- description: nonBlank(params.reason),
24723
- subject: { type: "tool_call", toolCall: permissionsSubject(params.itemId, params.cwd, params.environmentId, params.permissions) },
24724
- options: permissionProfileOptions()
24725
- }, this.signal());
24726
- return permissionsResponse(params.permissions, response);
24727
- } catch (error40) {
24728
- logger.error("permissions approval failed", error40, { itemId: params.itemId });
24729
- return rejected();
24730
- }
24364
+ defaultGatewayId() {
24365
+ const first = this.active;
24366
+ return first !== null && first.mode === "route" ? first.id : null;
24731
24367
  }
24732
- }
24733
- function permissionsResponse(permissions, response) {
24734
- if (response.outcome.outcome !== "selected")
24735
- return rejected();
24736
- switch (response.outcome.optionId) {
24737
- case OptionId.AllowPermissionsForTurn:
24738
- return { permissions: granted(permissions), scope: "turn", strictAutoReview: false };
24739
- case OptionId.AllowPermissionsForTurnStrict:
24740
- return { permissions: granted(permissions), scope: "turn", strictAutoReview: true };
24741
- case OptionId.AllowPermissionsForSession:
24742
- return { permissions: granted(permissions), scope: "session", strictAutoReview: false };
24743
- default:
24744
- return rejected();
24368
+ threadConfig(gatewayId = this.defaultGatewayId()) {
24369
+ const gateway = this.gateway(gatewayId);
24370
+ if (!gateway)
24371
+ return this.baseConfig;
24372
+ return this.applyGateway(this.baseConfig, gateway);
24745
24373
  }
24746
- }
24747
- function granted(permissions) {
24748
- return {
24749
- ...permissions.network ? { network: permissions.network } : {},
24750
- ...permissions.fileSystem ? { fileSystem: permissions.fileSystem } : {}
24751
- };
24752
- }
24753
- function rejected() {
24754
- return { permissions: {}, scope: "turn", strictAutoReview: false };
24755
- }
24756
- function nonBlank(value) {
24757
- const trimmed = value?.trim();
24758
- return trimmed ? trimmed : null;
24759
- }
24760
-
24761
- // src/permissions/mcp.ts
24762
- var McpOptionId = {
24763
- AllowOnce: "allow_once",
24764
- Accept: "accept",
24765
- AllowSession: "allow_session",
24766
- AllowAlways: "allow_always",
24767
- Decline: "decline",
24768
- Cancel: "cancel"
24769
- };
24770
- function parsePersistOptions(meta) {
24771
- const result = new Set;
24772
- if (!isRecord2(meta))
24773
- return result;
24774
- const persist = meta["persist"];
24775
- const values = Array.isArray(persist) ? persist : [persist];
24776
- if (values.includes("session"))
24777
- result.add("session");
24778
- if (values.includes("always"))
24779
- result.add("always");
24780
- return result;
24781
- }
24782
- function isMcpToolCallApproval(meta) {
24783
- return isRecord2(meta) && meta["codex_approval_kind"] === "mcp_tool_call";
24784
- }
24785
- function mcpPermissionOptions(isToolApproval, persist) {
24786
- const options = [permissionOption(isToolApproval ? McpOptionId.AllowOnce : McpOptionId.Accept, "Allow", "allow_once", isToolApproval ? "Run the tool and continue." : "Allow this request and continue.")];
24787
- if (persist.has("session")) {
24788
- options.push(permissionOption(McpOptionId.AllowSession, "Allow for this session", "allow_always", isToolApproval ? "Run the tool and remember this choice for this session." : "Allow this request and remember this choice for this session."));
24374
+ routeConfig(config2, gatewayId) {
24375
+ return this.rebind(config2, this, gatewayId);
24789
24376
  }
24790
- if (persist.has("always")) {
24791
- options.push(permissionOption(McpOptionId.AllowAlways, "Always allow", "allow_always", isToolApproval ? "Run the tool and remember this choice for future tool calls." : "Allow this request and remember this choice for future requests."));
24377
+ overrideKeys() {
24378
+ const keys = new Set;
24379
+ for (const gateway of this.gateways.values())
24380
+ for (const key of Object.keys(gateway.config))
24381
+ keys.add(key);
24382
+ return [...keys];
24792
24383
  }
24793
- if (!isToolApproval) {
24794
- options.push(permissionOption(McpOptionId.Decline, "Deny", "reject_once", "Decline this request and continue."));
24384
+ rebind(config2, previous, gatewayId = this.defaultGatewayId()) {
24385
+ const next = { ...config2 };
24386
+ for (const key of previous.overrideKeys()) {
24387
+ if (key in this.baseConfig)
24388
+ next[key] = this.baseConfig[key];
24389
+ else
24390
+ delete next[key];
24391
+ }
24392
+ delete next["model_providers"];
24393
+ if (isRecord2(this.baseConfig["model_providers"]))
24394
+ next["model_providers"] = this.baseConfig["model_providers"];
24395
+ const gateway = this.gateway(gatewayId);
24396
+ return gateway ? this.applyGateway(next, gateway) : next;
24795
24397
  }
24796
- options.push(permissionOption(McpOptionId.Cancel, "Cancel", "reject_once", isToolApproval ? "Cancel this tool call." : "Cancel this request."));
24797
- return options;
24798
- }
24799
- function mcpPermissionRequest(params, context, nextStandaloneToolCallId) {
24800
- const message = { type: "content", content: { type: "text", text: params.message } };
24801
- const options = mcpPermissionOptions(context.isToolApproval, context.persistOptions);
24802
- const title = context.isToolApproval ? `Allow MCP tool call on ${params.serverName}?` : `${params.serverName} requests approval`;
24803
- if (params.mode === "url") {
24804
- return {
24805
- title,
24806
- description: params.message,
24807
- subject: {
24808
- type: "tool_call",
24809
- toolCall: {
24810
- toolCallId: `elicitation:${params.elicitationId}`,
24811
- name: ToolName.Mcp,
24812
- kind: "fetch",
24813
- status: "pending",
24814
- title: `Open ${params.url}`,
24815
- content: [message],
24816
- rawInput: { serverName: params.serverName, url: params.url }
24817
- }
24818
- },
24819
- options
24398
+ applyGateway(config2, gateway) {
24399
+ const existing = isRecord2(config2["model_providers"]) ? config2["model_providers"] : {};
24400
+ const entry = {
24401
+ name: gateway.name,
24402
+ base_url: gateway.baseUrl,
24403
+ wire_api: "responses",
24404
+ http_headers: { "X-Client-Feature-ID": "codex", ...gateway.headers }
24820
24405
  };
24821
- }
24822
- if (context.correlatedCallId !== undefined) {
24406
+ if (gateway.bearerToken !== null)
24407
+ entry["experimental_bearer_token"] = gateway.bearerToken;
24823
24408
  return {
24824
- title,
24825
- description: params.message,
24826
- subject: { type: "tool_call", toolCall: { toolCallId: context.correlatedCallId, status: "pending" } },
24827
- options
24409
+ ...config2,
24410
+ ...gateway.config,
24411
+ model_providers: { ...existing, [gateway.id]: entry }
24828
24412
  };
24829
24413
  }
24830
- return {
24831
- title,
24832
- description: params.message,
24833
- subject: {
24834
- type: "tool_call",
24835
- toolCall: {
24836
- toolCallId: nextStandaloneToolCallId(),
24837
- name: ToolName.Mcp,
24838
- kind: context.isToolApproval ? "execute" : "other",
24839
- status: "pending",
24840
- title,
24841
- content: [message],
24842
- rawInput: { serverName: params.serverName, schema: params.requestedSchema }
24843
- }
24844
- },
24845
- options
24846
- };
24847
- }
24848
- function mcpPermissionResponse(response, isToolApproval, persist) {
24849
- if (response.outcome.outcome !== "selected")
24850
- return cancelled();
24851
- switch (response.outcome.optionId) {
24852
- case McpOptionId.AllowSession:
24853
- return persist.has("session") ? accepted({ persist: "session" }) : cancelled();
24854
- case McpOptionId.AllowAlways:
24855
- return persist.has("always") ? accepted({ persist: "always" }) : cancelled();
24856
- case McpOptionId.AllowOnce:
24857
- return isToolApproval ? accepted(null) : cancelled();
24858
- case McpOptionId.Accept:
24859
- return isToolApproval ? cancelled() : accepted(null);
24860
- case McpOptionId.Decline:
24861
- return isToolApproval ? cancelled() : { action: "decline", content: null, _meta: null };
24862
- default:
24863
- return cancelled();
24414
+ catalog(codexCatalog) {
24415
+ const first = this.active;
24416
+ if (first === null)
24417
+ return codexCatalog;
24418
+ if (first.mode === "route") {
24419
+ if (first.models.length === 0)
24420
+ return codexCatalog;
24421
+ return first.models.map((model, index) => gatewayModel(model, index === 0, codexCatalog[0], first.catalogEntries.get(model.id)));
24422
+ }
24423
+ const own = [];
24424
+ for (const gateway of this.gateways.values()) {
24425
+ for (const model of gateway.models)
24426
+ own.push({ ...gatewayModel(model, false, codexCatalog[0], gateway.catalogEntries.get(model.id)), id: gatewayModelId(gateway.id, model.id) });
24427
+ }
24428
+ return [...codexCatalog, ...own];
24864
24429
  }
24865
- }
24866
- function accepted(meta) {
24867
- return { action: "accept", content: null, _meta: meta };
24868
- }
24869
- function cancelled() {
24870
- return { action: "cancel", content: null, _meta: null };
24871
- }
24872
-
24873
- // src/permissions/ElicitationHandler.ts
24874
- var OTHER_SUFFIX = "__other";
24875
-
24876
- class CodexElicitationHandler {
24877
- client;
24878
- turn;
24879
- signal;
24880
- pendingUrlElicitations = new Set;
24881
- constructor(client, turn, signal = () => {
24882
- return;
24883
- }) {
24884
- this.client = client;
24885
- this.turn = turn;
24886
- this.signal = signal;
24430
+ nativeBaseUrl() {
24431
+ const providers = this.baseConfig["model_providers"];
24432
+ if (this.configuredProvider && isRecord2(providers)) {
24433
+ const entry = providers[this.configuredProvider];
24434
+ const baseUrl = isRecord2(entry) ? entry["base_url"] : undefined;
24435
+ if (typeof baseUrl === "string" && baseUrl.length > 0)
24436
+ return baseUrl;
24437
+ }
24438
+ return DEFAULT_OPENAI_BASE_URL;
24887
24439
  }
24888
- async observe(notification) {
24889
- if (notification.method !== "serverRequest/resolved")
24890
- return;
24891
- const ids = [...this.pendingUrlElicitations];
24892
- this.pendingUrlElicitations.clear();
24893
- for (const elicitationId of ids) {
24894
- await this.client.completeElicitation(elicitationId);
24440
+ }
24441
+ function readHints(meta) {
24442
+ const root = isRecord2(meta) ? meta : {};
24443
+ const alwith = isRecord2(root["alwith"]) ? root["alwith"] : {};
24444
+ const codex = isRecord2(root["codex"]) ? root["codex"] : {};
24445
+ const id = readGatewayId(meta) ?? GATEWAY_MODEL_PROVIDER;
24446
+ const models = [];
24447
+ if (Array.isArray(alwith["models"])) {
24448
+ for (const entry of alwith["models"]) {
24449
+ if (!isRecord2(entry) || typeof entry["id"] !== "string" || entry["id"].length === 0)
24450
+ continue;
24451
+ models.push({
24452
+ id: entry["id"],
24453
+ name: typeof entry["label"] === "string" && entry["label"].length > 0 ? entry["label"] : entry["id"],
24454
+ description: typeof entry["description"] === "string" ? entry["description"] : null
24455
+ });
24895
24456
  }
24896
24457
  }
24897
- async handleElicitation(params) {
24898
- try {
24899
- if (params.mode === "openai/userVerification") {
24900
- logger.log("MCP user verification is not supported over ACP", { server: params.serverName });
24901
- return cancelled();
24902
- }
24903
- const context = this.mcpContext(params);
24904
- const messageOnly = isMessageOnlyForm(params);
24905
- if (!messageOnly && this.useAcpElicitation(params)) {
24906
- const response2 = await this.client.createElicitation(this.elicitationRequest(params, context), this.signal());
24907
- const result2 = convertMcpElicitationResponse(response2, context);
24908
- if (params.mode === "url" && result2.action === "accept")
24909
- this.pendingUrlElicitations.add(params.elicitationId);
24910
- if (result2.action === "accept")
24911
- await this.markCorrelatedCallRunning(context);
24912
- return result2;
24913
- }
24914
- if (params.mode !== "url" && !messageOnly) {
24915
- return cancelled();
24916
- }
24917
- const request = mcpPermissionRequest(params, context, () => this.turn.nextStandaloneToolCallId(params.serverName));
24918
- const response = await this.client.requestPermission(request, this.signal());
24919
- const result = mcpPermissionResponse(response, context.isToolApproval, context.persistOptions);
24920
- if (result.action === "accept")
24921
- await this.markCorrelatedCallRunning(context);
24922
- return result;
24923
- } catch (error40) {
24924
- logger.error("MCP elicitation failed", error40, { server: params.serverName });
24925
- return cancelled();
24926
- }
24458
+ const model = typeof alwith["model"] === "string" && alwith["model"].length > 0 ? alwith["model"] : null;
24459
+ const name = typeof codex["name"] === "string" && codex["name"].length > 0 ? codex["name"] : null;
24460
+ const bearerToken = typeof codex["bearerToken"] === "string" && codex["bearerToken"].length > 0 ? codex["bearerToken"] : null;
24461
+ const rawConfig = codex["config"];
24462
+ if (rawConfig !== undefined && !isRecord2(rawConfig)) {
24463
+ throw RequestError.invalidParams({ config: rawConfig }, "_meta.codex.config must be an object of top-level Codex config keys");
24927
24464
  }
24928
- async handleUserInput(params) {
24929
- if (!this.client.capabilities.formElicitation)
24930
- return { answers: {} };
24931
- try {
24932
- const response = await this.client.createElicitation(userInputRequest(this.client.sessionId, params), this.signal());
24933
- return convertUserInputResponse(response, params);
24934
- } catch (error40) {
24935
- logger.error("user input request failed", error40, { itemId: params.itemId });
24936
- return { answers: {} };
24465
+ const config2 = {};
24466
+ if (isRecord2(rawConfig)) {
24467
+ for (const [key, value] of Object.entries(rawConfig)) {
24468
+ if (key === "model_providers")
24469
+ throw RequestError.invalidParams({ config: rawConfig }, "_meta.codex.config cannot set model_providers; that entry is derived from baseUrl and headers");
24470
+ config2[key] = value;
24937
24471
  }
24938
24472
  }
24939
- mcpContext(params) {
24940
- const isToolApproval = isMcpToolCallApproval(params._meta) && isMessageOnlyForm(params);
24941
- return {
24942
- isToolApproval,
24943
- persistOptions: parsePersistOptions(params._meta),
24944
- correlatedCallId: isToolApproval ? this.turn.popPendingMcpCall(params.serverName) : undefined
24945
- };
24473
+ const rawMode = codex["mode"];
24474
+ if (rawMode !== undefined && rawMode !== "route" && rawMode !== "catalog") {
24475
+ throw RequestError.invalidParams({ mode: rawMode }, '_meta.codex.mode must be "route" or "catalog"');
24946
24476
  }
24947
- useAcpElicitation(params) {
24948
- if (params.mode === "form")
24949
- return this.client.capabilities.formElicitation;
24950
- if (params.mode === "url")
24951
- return this.client.capabilities.urlElicitation;
24952
- return false;
24477
+ const mode = rawMode === "catalog" ? "catalog" : "route";
24478
+ if (mode === "catalog" && models.length === 0) {
24479
+ throw RequestError.invalidParams(undefined, `_meta.codex.mode "catalog" needs _meta.alwith.models: the models offered next to Codex's`);
24953
24480
  }
24954
- elicitationRequest(params, context) {
24955
- const base = {
24956
- sessionId: this.client.sessionId,
24957
- ...context.correlatedCallId ? { toolCallId: context.correlatedCallId } : {},
24958
- message: params.message,
24959
- _meta: recordOrNull(params._meta)
24960
- };
24961
- if (params.mode === "form") {
24962
- return { ...base, mode: "form", requestedSchema: normalizeSchema(params.requestedSchema) };
24481
+ const catalogPath = config2["model_catalog_json"];
24482
+ const catalogEntries = typeof catalogPath === "string" ? readCatalogFile(catalogPath) : new Map;
24483
+ return { id, models, model, name, bearerToken, config: config2, mode, catalogEntries };
24484
+ }
24485
+ function readGatewayId(meta) {
24486
+ const root = isRecord2(meta) ? meta : {};
24487
+ const codex = isRecord2(root["codex"]) ? root["codex"] : {};
24488
+ const raw = codex["id"];
24489
+ if (raw === undefined)
24490
+ return null;
24491
+ if (typeof raw !== "string" || !GATEWAY_ID_PATTERN.test(raw)) {
24492
+ throw RequestError.invalidParams({ id: raw }, "_meta.codex.id must match [a-z0-9-]+");
24493
+ }
24494
+ if (raw === NATIVE_GROUP_ID) {
24495
+ throw RequestError.invalidParams({ id: raw }, `_meta.codex.id "${NATIVE_GROUP_ID}" is the native model group`);
24496
+ }
24497
+ return raw;
24498
+ }
24499
+ function readCatalogFile(catalogPath) {
24500
+ const resolved = catalogPath.startsWith("~/") ? path4.join(os.homedir(), catalogPath.slice(2)) : catalogPath;
24501
+ let parsed;
24502
+ try {
24503
+ parsed = JSON.parse(readFileSync(resolved, "utf8"));
24504
+ } catch (error40) {
24505
+ throw RequestError.invalidParams({ model_catalog_json: catalogPath }, `model_catalog_json could not be read as JSON: ${error40 instanceof Error ? error40.message : String(error40)}`);
24506
+ }
24507
+ const models = isRecord2(parsed) && Array.isArray(parsed["models"]) ? parsed["models"] : null;
24508
+ if (models === null)
24509
+ throw RequestError.invalidParams({ model_catalog_json: catalogPath }, "model_catalog_json must be {models: [...]} as Codex expects");
24510
+ const entries = new Map;
24511
+ for (const entry of models) {
24512
+ if (!isRecord2(entry) || typeof entry["slug"] !== "string")
24513
+ continue;
24514
+ const levels = Array.isArray(entry["supported_reasoning_levels"]) ? entry["supported_reasoning_levels"] : [];
24515
+ const efforts = [];
24516
+ for (const level of levels) {
24517
+ if (!isRecord2(level) || typeof level["effort"] !== "string")
24518
+ continue;
24519
+ efforts.push({ reasoningEffort: level["effort"], description: typeof level["description"] === "string" ? level["description"] : "" });
24963
24520
  }
24964
- return { ...base, mode: "url", url: params.url, elicitationId: params.elicitationId };
24521
+ const modalities = Array.isArray(entry["input_modalities"]) ? entry["input_modalities"].filter((value) => value === "text" || value === "image") : null;
24522
+ entries.set(entry["slug"], {
24523
+ displayName: typeof entry["display_name"] === "string" ? entry["display_name"] : null,
24524
+ description: typeof entry["description"] === "string" ? entry["description"] : null,
24525
+ efforts,
24526
+ defaultEffort: typeof entry["default_reasoning_level"] === "string" ? entry["default_reasoning_level"] : null,
24527
+ inputModalities: modalities
24528
+ });
24965
24529
  }
24966
- async markCorrelatedCallRunning(context) {
24967
- if (context.correlatedCallId === undefined)
24968
- return;
24969
- await this.client.update({ sessionUpdate: "tool_call_update", toolCallId: context.correlatedCallId, status: "in_progress" });
24530
+ return entries;
24531
+ }
24532
+ function gatewayModel(model, isDefault, template, entry) {
24533
+ const efforts = entry && entry.efforts.length > 0 ? entry.efforts : template?.supportedReasoningEfforts ?? [];
24534
+ const defaultEffort = entry?.defaultEffort ?? template?.defaultReasoningEffort ?? "medium";
24535
+ return {
24536
+ id: model.id,
24537
+ model: model.id,
24538
+ upgrade: null,
24539
+ upgradeInfo: null,
24540
+ availabilityNux: null,
24541
+ displayName: model.name !== model.id ? model.name : entry?.displayName ?? model.name,
24542
+ description: model.description ?? entry?.description ?? "",
24543
+ modelSpecialty: null,
24544
+ hidden: false,
24545
+ supportedReasoningEfforts: efforts,
24546
+ defaultReasoningEffort: defaultEffort,
24547
+ inputModalities: entry?.inputModalities ?? ["text", "image"],
24548
+ supportsPersonality: false,
24549
+ multiAgentVersion: null,
24550
+ additionalSpeedTiers: [],
24551
+ serviceTiers: [],
24552
+ defaultServiceTier: null,
24553
+ availableAccessPrograms: null,
24554
+ isDefault
24555
+ };
24556
+ }
24557
+
24558
+ // src/codex/models.ts
24559
+ var MODEL_CONFIG_ID = "model";
24560
+ var EFFORT_CONFIG_ID = "effort";
24561
+ var FAST_MODE_CONFIG_ID = "fast_mode";
24562
+ var COLLABORATION_MODE_CONFIG_ID = "collaboration_mode";
24563
+ var DEFAULT_EFFORT = "medium";
24564
+ var FAST_SERVICE_TIER = "fast";
24565
+ function resolveModelSelection(catalog, modelId, effort) {
24566
+ const listed = modelId === null ? undefined : catalog.find((model) => model.id === modelId);
24567
+ if (listed)
24568
+ return { model: listed.id, effort: effort ?? listed.defaultReasoningEffort };
24569
+ if (modelId)
24570
+ return { model: modelId, effort: effort ?? DEFAULT_EFFORT };
24571
+ const fallback = catalog.find((model) => model.isDefault) ?? catalog[0];
24572
+ if (!fallback)
24573
+ throw new Error("Codex did not return any models");
24574
+ return { model: fallback.id, effort: effort ?? fallback.defaultReasoningEffort };
24575
+ }
24576
+ function findModel(catalog, modelId) {
24577
+ return catalog.find((model) => model.id === modelId);
24578
+ }
24579
+ function supportedEfforts(model) {
24580
+ return model?.supportedReasoningEfforts ?? [];
24581
+ }
24582
+ function coerceEffort(model, effort) {
24583
+ const options = supportedEfforts(model);
24584
+ if (options.length === 0)
24585
+ return effort;
24586
+ if (options.some((option) => option.reasoningEffort === effort))
24587
+ return effort;
24588
+ return model?.defaultReasoningEffort ?? options[0].reasoningEffort;
24589
+ }
24590
+ function modelSupportsFast(model) {
24591
+ if (!model)
24592
+ return false;
24593
+ return model.serviceTiers.some((tier) => tier.id === FAST_SERVICE_TIER) || model.additionalSpeedTiers.includes(FAST_SERVICE_TIER);
24594
+ }
24595
+ function modelSupportsImages(model) {
24596
+ return model?.inputModalities.includes("image") ?? true;
24597
+ }
24598
+ function modelLacksReasoning(model) {
24599
+ const options = supportedEfforts(model);
24600
+ return options.length > 0 && options.every((option) => option.reasoningEffort === "none");
24601
+ }
24602
+ function modelConfigOption(catalog, current, gatewayGroups = []) {
24603
+ const visible = catalog.filter((model) => !model.hidden || model.id === current);
24604
+ const options = visible.map((model) => ({
24605
+ value: model.id,
24606
+ name: model.displayName,
24607
+ description: model.description || null
24608
+ }));
24609
+ if (!options.some((option) => option.value === current)) {
24610
+ options.unshift({ value: current, name: current, description: null });
24611
+ }
24612
+ const base = {
24613
+ configId: MODEL_CONFIG_ID,
24614
+ name: "Model",
24615
+ description: "Model Codex uses for this session",
24616
+ category: "model",
24617
+ type: "select",
24618
+ currentValue: current
24619
+ };
24620
+ if (gatewayGroups.length === 0)
24621
+ return { ...base, options };
24622
+ const owner = new Map;
24623
+ for (const group of gatewayGroups)
24624
+ for (const id of group.modelIds)
24625
+ owner.set(id, group.id);
24626
+ const native = options.filter((option) => !owner.has(option.value));
24627
+ return {
24628
+ ...base,
24629
+ options: [
24630
+ { groupId: NATIVE_GROUP_ID, name: NATIVE_GROUP_NAME, options: native },
24631
+ ...gatewayGroups.map((group) => ({ groupId: group.id, name: group.name, options: options.filter((option) => owner.get(option.value) === group.id) }))
24632
+ ]
24633
+ };
24634
+ }
24635
+ function effortConfigOption(efforts, current) {
24636
+ const options = efforts.map((option) => ({
24637
+ value: option.reasoningEffort,
24638
+ name: capitalize(option.reasoningEffort),
24639
+ description: option.description || null
24640
+ }));
24641
+ if (!options.some((option) => option.value === current)) {
24642
+ options.unshift({ value: current, name: capitalize(current), description: null });
24970
24643
  }
24644
+ return {
24645
+ configId: EFFORT_CONFIG_ID,
24646
+ name: "Reasoning effort",
24647
+ description: "How much reasoning effort the model should use",
24648
+ category: "thought_level",
24649
+ type: "select",
24650
+ currentValue: current,
24651
+ options
24652
+ };
24971
24653
  }
24972
- function isMessageOnlyForm(params) {
24973
- if (params.mode !== "form" && params.mode !== "openai/form" && params.mode !== "openaiForm")
24974
- return false;
24975
- const schema = params.requestedSchema;
24976
- if (schema === null)
24977
- return true;
24978
- if (!isRecord2(schema))
24979
- return false;
24980
- return schema["type"] === "object" && isRecord2(schema["properties"]) && Object.keys(schema["properties"]).length === 0;
24654
+ function fastModeConfigOption(enabled) {
24655
+ return {
24656
+ configId: FAST_MODE_CONFIG_ID,
24657
+ name: "Fast mode",
24658
+ description: "1.5x speed, increased usage",
24659
+ category: "model_config",
24660
+ type: "boolean",
24661
+ currentValue: enabled
24662
+ };
24981
24663
  }
24982
- function normalizeSchema(value) {
24983
- const normalized = normalizeSchemaValue(value);
24984
- if (!isRecord2(normalized))
24985
- return { type: "object", properties: {} };
24986
- return { ...normalized, type: "object" };
24664
+ var DEFAULT_COLLABORATION_MODE = "default";
24665
+ var PLAN_COLLABORATION_MODE = "plan";
24666
+ function collaborationModeConfigOption(current) {
24667
+ return {
24668
+ configId: COLLABORATION_MODE_CONFIG_ID,
24669
+ name: "Collaboration mode",
24670
+ description: "How Codex collaborates on subsequent turns",
24671
+ category: "model_config",
24672
+ type: "select",
24673
+ currentValue: current,
24674
+ options: [
24675
+ { value: DEFAULT_COLLABORATION_MODE, name: "Default", description: "Make changes directly" },
24676
+ { value: PLAN_COLLABORATION_MODE, name: "Plan", description: "Plan before making changes" }
24677
+ ]
24678
+ };
24987
24679
  }
24988
- function normalizeSchemaValue(value) {
24989
- if (typeof value === "bigint")
24990
- return Number(value);
24991
- if (Array.isArray(value))
24992
- return value.map(normalizeSchemaValue);
24993
- if (!isRecord2(value))
24994
- return value;
24995
- const result = Object.fromEntries(Object.entries(value).filter(([, nested]) => nested !== undefined).map(([key, nested]) => [key, normalizeSchemaValue(nested)]));
24996
- if (result["type"] === "string" && Array.isArray(result["enum"]) && Array.isArray(result["enumNames"]) && !Array.isArray(result["oneOf"])) {
24997
- const values = result["enum"];
24998
- const names = result["enumNames"];
24999
- result["oneOf"] = values.map((entry, index) => ({ const: String(entry), title: String(names[index] ?? entry) }));
25000
- delete result["enum"];
25001
- delete result["enumNames"];
25002
- }
25003
- return result;
24680
+ function parseCollaborationMode(value) {
24681
+ if (value === DEFAULT_COLLABORATION_MODE)
24682
+ return DEFAULT_COLLABORATION_MODE;
24683
+ if (value === PLAN_COLLABORATION_MODE)
24684
+ return PLAN_COLLABORATION_MODE;
24685
+ return;
25004
24686
  }
25005
- function convertMcpElicitationResponse(response, context) {
25006
- if (CreateElicitationResponse.isAccept(response)) {
25007
- const content = isRecord2(response.content) ? { ...response.content } : {};
25008
- const persist = context.isToolApproval ? content["persist"] : undefined;
25009
- if (context.isToolApproval && !persistAllowed(persist, context.persistOptions))
25010
- return cancelled();
25011
- if (persist === "session" || persist === "always" || persist === "once")
25012
- delete content["persist"];
25013
- return { action: "accept", content: contentOrNull(content), _meta: responseMeta(response, context, persist) };
25014
- }
25015
- if (CreateElicitationResponse.isDecline(response)) {
25016
- return context.isToolApproval ? { action: "cancel", content: null, _meta: responseMeta(response, context) } : { action: "decline", content: null, _meta: responseMeta(response, context) };
24687
+ function capitalize(value) {
24688
+ return value.length === 0 ? value : value[0].toUpperCase() + value.slice(1);
24689
+ }
24690
+
24691
+ // src/permissions/commandDecisions.ts
24692
+ function availableCommandDecisions(params) {
24693
+ if (params.availableDecisions === undefined || params.availableDecisions === null) {
24694
+ return defaultDecisions(params);
25017
24695
  }
25018
- if (CreateElicitationResponse.isCancel(response)) {
25019
- return { action: "cancel", content: null, _meta: responseMeta(response, context) };
24696
+ if (!Array.isArray(params.availableDecisions) || params.availableDecisions.length === 0)
24697
+ return;
24698
+ const decisions = [];
24699
+ for (const candidate of params.availableDecisions) {
24700
+ const decision = parseDecision(candidate, params);
24701
+ if (!decision)
24702
+ return;
24703
+ decisions.push(decision);
25020
24704
  }
25021
- return cancelled();
25022
- }
25023
- function persistAllowed(persist, options) {
25024
- return persist === undefined || persist === "once" || persist === "session" && options.has("session") || persist === "always" && options.has("always");
25025
- }
25026
- function contentOrNull(content) {
25027
- const entries = Object.entries(content);
25028
- if (entries.length === 0)
25029
- return null;
25030
- return Object.fromEntries(entries.map(([key, value]) => [key, normalizeJsonValue(value)]));
25031
- }
25032
- function responseMeta(response, context, persist) {
25033
- const source = recordOrNull(response._meta);
25034
- const meta = source ? normalizeJsonObject(source) : {};
25035
- if (context.isToolApproval)
25036
- delete meta["persist"];
25037
- if (persist === "session" || persist === "always")
25038
- meta["persist"] = persist;
25039
- return Object.keys(meta).length === 0 ? null : meta;
24705
+ return decisions;
25040
24706
  }
25041
- function userInputRequest(sessionId, params) {
25042
- const properties = {};
25043
- const required2 = [];
25044
- const ids = new Set(params.questions.map((question) => question.id));
25045
- for (const question of params.questions) {
25046
- const options = question.options ?? [];
25047
- const hasOther = question.isOther && options.length > 0;
25048
- const base = {
25049
- title: question.header || question.id,
25050
- description: question.question,
25051
- _meta: { codex: { isOther: question.isOther, isSecret: question.isSecret } }
25052
- };
25053
- if (!hasOther)
25054
- required2.push(question.id);
25055
- properties[question.id] = options.length > 0 ? { ...base, type: "string", oneOf: options.map((option) => ({ const: option.label, title: option.label, description: option.description })) } : { ...base, type: "string" };
25056
- if (hasOther) {
25057
- properties[otherFieldId(question.id, ids)] = {
25058
- type: "string",
25059
- title: "Other",
25060
- description: "Type your own answer instead of choosing an option above.",
25061
- _meta: { codex: { questionId: question.id, isOtherAnswer: true, isSecret: question.isSecret } }
25062
- };
24707
+ function defaultDecisions(params) {
24708
+ if (params.networkApprovalContext) {
24709
+ const decisions2 = ["accept", "acceptForSession"];
24710
+ for (const amendment of params.proposedNetworkPolicyAmendments ?? []) {
24711
+ decisions2.push({ applyNetworkPolicyAmendment: { network_policy_amendment: amendment } });
25063
24712
  }
24713
+ decisions2.push("decline", "cancel");
24714
+ return decisions2;
25064
24715
  }
25065
- const first = params.questions[0];
25066
- return {
25067
- sessionId,
25068
- toolCallId: params.itemId,
25069
- mode: "form",
25070
- message: params.questions.length === 1 && first ? first.question : "Input requested",
25071
- requestedSchema: { type: "object", properties, required: required2 },
25072
- _meta: { codex: { isBlocking: params.isBlocking } }
25073
- };
24716
+ if (params.additionalPermissions)
24717
+ return ["accept", "cancel"];
24718
+ const decisions = ["accept", "acceptForSession"];
24719
+ if (params.proposedExecpolicyAmendment && params.proposedExecpolicyAmendment.length > 0) {
24720
+ decisions.push({ acceptWithExecpolicyAmendment: { execpolicy_amendment: params.proposedExecpolicyAmendment } });
24721
+ }
24722
+ decisions.push("decline", "cancel");
24723
+ return decisions;
25074
24724
  }
25075
- function convertUserInputResponse(response, params) {
25076
- if (!CreateElicitationResponse.isAccept(response))
25077
- return { answers: {} };
25078
- const content = isRecord2(response.content) ? response.content : {};
25079
- const ids = new Set(params.questions.map((question) => question.id));
25080
- const answers = {};
25081
- for (const question of params.questions) {
25082
- const hasOther = question.isOther && (question.options?.length ?? 0) > 0;
25083
- const value = (hasOther ? answerValue(content, otherFieldId(question.id, ids)) : undefined) ?? answerValue(content, question.id);
25084
- if (value === undefined)
25085
- continue;
25086
- answers[question.id] = { answers: Array.isArray(value) ? value.map(String) : [String(value)] };
24725
+ function parseDecision(candidate, params) {
24726
+ if (candidate === "accept" || candidate === "acceptForSession" || candidate === "decline" || candidate === "cancel") {
24727
+ return candidate;
25087
24728
  }
25088
- return { answers };
24729
+ if (!isRecord2(candidate))
24730
+ return;
24731
+ if ("acceptWithExecpolicyAmendment" in candidate) {
24732
+ const value = candidate["acceptWithExecpolicyAmendment"];
24733
+ if (!isRecord2(value))
24734
+ return;
24735
+ const amendment = value["execpolicy_amendment"];
24736
+ if (!isStringArray(amendment) || amendment.length === 0)
24737
+ return;
24738
+ if (!sameStrings(amendment, params.proposedExecpolicyAmendment))
24739
+ return;
24740
+ return { acceptWithExecpolicyAmendment: { execpolicy_amendment: [...amendment] } };
24741
+ }
24742
+ if ("applyNetworkPolicyAmendment" in candidate) {
24743
+ const value = candidate["applyNetworkPolicyAmendment"];
24744
+ if (!isRecord2(value))
24745
+ return;
24746
+ const amendment = parseNetworkAmendment(value["network_policy_amendment"]);
24747
+ if (!amendment || !params.networkApprovalContext)
24748
+ return;
24749
+ if (amendment.host !== params.networkApprovalContext.host)
24750
+ return;
24751
+ const proposed = params.proposedNetworkPolicyAmendments ?? [];
24752
+ if (!proposed.some((entry) => entry.host === amendment.host && entry.action === amendment.action))
24753
+ return;
24754
+ return { applyNetworkPolicyAmendment: { network_policy_amendment: amendment } };
24755
+ }
24756
+ return;
25089
24757
  }
25090
- function answerValue(content, fieldId) {
25091
- const value = content[fieldId];
25092
- if (typeof value === "string" && value.trim() === "")
24758
+ function parseNetworkAmendment(value) {
24759
+ if (!isRecord2(value) || typeof value["host"] !== "string")
25093
24760
  return;
25094
- if (Array.isArray(value) && value.length === 0)
24761
+ const action = value["action"];
24762
+ if (action !== "allow" && action !== "deny")
25095
24763
  return;
25096
- return value;
24764
+ return { host: value["host"], action };
25097
24765
  }
25098
- function otherFieldId(questionId, ids) {
25099
- const base = `${questionId}${OTHER_SUFFIX}`;
25100
- if (!ids.has(base))
25101
- return base;
25102
- let index = 1;
25103
- while (ids.has(`${base}${index}`))
25104
- index += 1;
25105
- return `${base}${index}`;
24766
+ function isStringArray(value) {
24767
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string");
25106
24768
  }
25107
-
25108
- // src/permissions/turnContext.ts
25109
- class TurnContext {
25110
- sessionId;
25111
- fileChanges = new Map;
25112
- pendingMcpCalls = new Map;
25113
- sequence = 0;
25114
- constructor(sessionId) {
25115
- this.sessionId = sessionId;
25116
- }
25117
- observe(notification) {
25118
- switch (notification.method) {
25119
- case "item/started":
25120
- this.itemStarted(notification.params.item);
25121
- return;
25122
- case "item/completed":
25123
- this.itemCompleted(notification.params.item);
25124
- return;
25125
- case "turn/completed":
25126
- this.fileChanges.clear();
25127
- this.pendingMcpCalls.clear();
25128
- return;
25129
- case "serverRequest/resolved":
25130
- this.pendingMcpCalls.clear();
25131
- return;
25132
- default:
25133
- return;
25134
- }
25135
- }
25136
- fileChange(itemId) {
25137
- return this.fileChanges.get(itemId);
24769
+ function sameStrings(left, right) {
24770
+ return !!right && left.length === right.length && left.every((value, index) => value === right[index]);
24771
+ }
24772
+ function renderExecPolicyPrefix(command) {
24773
+ const script = extractWrappedScript(command);
24774
+ if (script !== undefined)
24775
+ return script;
24776
+ if (command.some((value) => value.includes("\x00")))
24777
+ return command.join(" ");
24778
+ return command.map(shlexQuote).join(" ");
24779
+ }
24780
+ function extractWrappedScript(command) {
24781
+ const executable = executableName(command[0]);
24782
+ if ((executable === "bash" || executable === "zsh" || executable === "sh") && command.length === 3 && (command[1] === "-lc" || command[1] === "-c")) {
24783
+ return command[2];
25138
24784
  }
25139
- popPendingMcpCall(serverName) {
25140
- const pending = this.pendingMcpCalls.get(serverName);
25141
- if (pending?.length !== 1)
24785
+ if (executable !== "pwsh" && executable !== "powershell")
24786
+ return;
24787
+ const allowedFlags = new Set(["-nologo", "-noprofile", "-command", "-c"]);
24788
+ for (let index = 1;index + 1 < command.length; index++) {
24789
+ const flag = command[index]?.toLowerCase();
24790
+ if (flag === undefined || !allowedFlags.has(flag))
25142
24791
  return;
25143
- this.pendingMcpCalls.delete(serverName);
25144
- return pending[0];
25145
- }
25146
- nextStandaloneToolCallId(serverName) {
25147
- this.sequence += 1;
25148
- return `elicitation:${this.sessionId}:${serverName}:${this.sequence}`;
24792
+ if (flag === "-command" || flag === "-c")
24793
+ return command[index + 1];
25149
24794
  }
25150
- itemStarted(item) {
25151
- if (item.type === "fileChange") {
25152
- this.fileChanges.set(item.id, item);
25153
- } else if (item.type === "mcpToolCall") {
25154
- const pending = this.pendingMcpCalls.get(item.server) ?? [];
25155
- pending.push(item.id);
25156
- this.pendingMcpCalls.set(item.server, pending);
25157
- }
24795
+ return;
24796
+ }
24797
+ function executableName(command) {
24798
+ let filename = command?.replaceAll("\\", "/").split("/").at(-1);
24799
+ while (filename !== undefined) {
24800
+ if (["bash", "zsh", "sh", "pwsh", "powershell"].includes(filename))
24801
+ return filename;
24802
+ const dot = filename.lastIndexOf(".");
24803
+ if (dot <= 0)
24804
+ return;
24805
+ filename = filename.slice(0, dot);
25158
24806
  }
25159
- itemCompleted(item) {
25160
- if (item.type === "fileChange") {
25161
- this.fileChanges.delete(item.id);
25162
- } else if (item.type === "mcpToolCall") {
25163
- const pending = this.pendingMcpCalls.get(item.server);
25164
- if (!pending)
25165
- return;
25166
- const index = pending.indexOf(item.id);
25167
- if (index >= 0)
25168
- pending.splice(index, 1);
25169
- if (pending.length === 0)
25170
- this.pendingMcpCalls.delete(item.server);
24807
+ return;
24808
+ }
24809
+ function shlexQuote(value) {
24810
+ if (value.length === 0)
24811
+ return "''";
24812
+ if (/^[0-9A-Za-z+\-./:@_]+$/.test(value))
24813
+ return value;
24814
+ if (!value.includes("'"))
24815
+ return `'${value}'`;
24816
+ return `"${value.replace(/["\\$`!]/g, "\\$&")}"`;
24817
+ }
24818
+
24819
+ // src/permissions/options.ts
24820
+ var OptionId = {
24821
+ AllowOnce: "allow_once",
24822
+ AllowForSession: "allow_for_session",
24823
+ Decline: "decline",
24824
+ Cancel: "cancel",
24825
+ AcceptWithExecpolicyAmendment: "accept_execpolicy_amendment",
24826
+ ApplyNetworkPolicyAmendment: "apply_network_policy_amendment",
24827
+ AllowPermissionsForTurn: "allow_permissions_turn",
24828
+ AllowPermissionsForTurnStrict: "allow_permissions_turn_strict_auto_review",
24829
+ AllowPermissionsForSession: "allow_permissions_session",
24830
+ RejectPermissions: "reject_permissions"
24831
+ };
24832
+ function commandDecisionOptions(params) {
24833
+ const decisions = availableCommandDecisions(params);
24834
+ if (!decisions)
24835
+ return;
24836
+ const options = [];
24837
+ let networkIndex = 0;
24838
+ for (const decision of decisions) {
24839
+ if (decision === "accept") {
24840
+ options.push(decisionOption(OptionId.AllowOnce, params.networkApprovalContext ? "Yes, just this once" : "Yes, proceed", "allow_once", decision));
24841
+ } else if (decision === "acceptForSession") {
24842
+ const name = params.networkApprovalContext ? "Yes, and allow this host for this conversation" : params.additionalPermissions ? "Yes, and allow these permissions for this session" : "Yes, and don't ask again for this command in this session";
24843
+ options.push(decisionOption(OptionId.AllowForSession, name, "allow_always", decision));
24844
+ } else if (decision === "decline") {
24845
+ options.push(decisionOption(OptionId.Decline, "No, continue without running it", "reject_once", decision));
24846
+ } else if (decision === "cancel") {
24847
+ options.push(decisionOption(OptionId.Cancel, "No, and tell Codex what to do differently", "reject_once", decision));
24848
+ } else if ("acceptWithExecpolicyAmendment" in decision) {
24849
+ const prefix = renderExecPolicyPrefix(decision.acceptWithExecpolicyAmendment.execpolicy_amendment);
24850
+ if (prefix.includes(`
24851
+ `) || prefix.includes("\r"))
24852
+ continue;
24853
+ options.push(decisionOption(OptionId.AcceptWithExecpolicyAmendment, `Yes, and don't ask again for commands that start with \`${prefix}\``, "allow_always", decision));
24854
+ } else {
24855
+ const amendment = decision.applyNetworkPolicyAmendment.network_policy_amendment;
24856
+ options.push(decisionOption(`${OptionId.ApplyNetworkPolicyAmendment}:${networkIndex++}`, amendment.action === "allow" ? "Yes, and allow this host in the future" : "No, and block this host in the future", amendment.action === "allow" ? "allow_always" : "reject_always", decision));
25171
24857
  }
25172
24858
  }
24859
+ const ordered = [...options].sort((left, right) => order(left.option) - order(right.option));
24860
+ const hasAllow = ordered.some(({ option }) => option.kind === "allow_once" || option.kind === "allow_always");
24861
+ const hasReject = ordered.some(({ option }) => option.kind === "reject_once" || option.kind === "reject_always");
24862
+ const ids = ordered.map(({ option }) => option.optionId);
24863
+ return hasAllow && hasReject && new Set(ids).size === ids.length ? ordered : undefined;
24864
+ }
24865
+ function order(option) {
24866
+ if (option.kind === "allow_once")
24867
+ return 0;
24868
+ if (option.kind === "allow_always")
24869
+ return 1;
24870
+ return 2;
24871
+ }
24872
+ function fileChangeDecisionOptions() {
24873
+ return [
24874
+ decisionOption(OptionId.AllowOnce, "Yes, proceed", "allow_once", "accept"),
24875
+ decisionOption(OptionId.AllowForSession, "Yes, and don't ask again for these files", "allow_always", "acceptForSession"),
24876
+ decisionOption(OptionId.Cancel, "No, and tell Codex what to do differently", "reject_once", "cancel")
24877
+ ];
24878
+ }
24879
+ function permissionProfileOptions() {
24880
+ return [
24881
+ permissionOption(OptionId.AllowPermissionsForTurn, "Yes, grant these permissions for this turn", "allow_once"),
24882
+ permissionOption(OptionId.AllowPermissionsForTurnStrict, "Yes, grant for this turn with strict auto review", "allow_once"),
24883
+ permissionOption(OptionId.AllowPermissionsForSession, "Yes, grant these permissions for this session", "allow_always"),
24884
+ permissionOption(OptionId.RejectPermissions, "No, continue without permissions", "reject_once")
24885
+ ];
24886
+ }
24887
+ function decisionOption(optionId, name, kind, decision) {
24888
+ return { option: permissionOption(optionId, name, kind), decision };
24889
+ }
24890
+ function permissionOption(optionId, name, kind, description) {
24891
+ return { optionId, name, kind, ...description ? { _meta: { codex: { description } } } : {} };
24892
+ }
24893
+ function selectedDecision(response, decisions) {
24894
+ if (response.outcome.outcome !== "selected")
24895
+ return;
24896
+ const optionId = response.outcome.optionId;
24897
+ return decisions.find(({ option }) => option.optionId === optionId)?.decision;
25173
24898
  }
25174
24899
 
25175
- // node_modules/open/index.js
25176
- import process10 from "process";
25177
- import path5 from "path";
25178
- import { fileURLToPath } from "url";
25179
- import childProcess4 from "child_process";
25180
- import fs6, { constants as fsConstants2 } from "fs/promises";
25181
-
25182
- // node_modules/wsl-utils/index.js
25183
- import path4 from "path";
25184
- import { promisify as promisify2 } from "util";
25185
- import childProcess2 from "child_process";
25186
- import fs5, { constants as fsConstants } from "fs/promises";
25187
-
25188
- // node_modules/is-wsl/index.js
25189
- import process2 from "process";
25190
- import os from "os";
25191
- import fs4 from "fs";
25192
-
25193
- // node_modules/is-inside-container/index.js
25194
- import fs3 from "fs";
25195
-
25196
- // node_modules/is-docker/index.js
25197
- import fs2 from "fs";
25198
- var isDockerCached;
25199
- function hasDockerEnv() {
25200
- try {
25201
- fs2.statSync("/.dockerenv");
25202
- return true;
25203
- } catch {
25204
- return false;
25205
- }
24900
+ // src/permissions/presentation.ts
24901
+ var COMMAND_TITLE = "Run command?";
24902
+ var NETWORK_TITLE = "Allow network access?";
24903
+ var FILE_CHANGE_TITLE = "Make edits?";
24904
+ var PERMISSIONS_TITLE = "Grant permissions?";
24905
+ function commandSubject(params) {
24906
+ const network = params.networkApprovalContext;
24907
+ const rawInput = {
24908
+ ...params.command ? { command: stripShellPrefix(params.command) } : {},
24909
+ ...params.cwd ? { cwd: params.cwd } : {},
24910
+ ...network?.protocol === "http" || network?.protocol === "https" ? { url: `${network.protocol}://${network.host}` } : {},
24911
+ ...params.additionalPermissions ? { additionalPermissions: params.additionalPermissions } : {}
24912
+ };
24913
+ const content = [];
24914
+ if (network)
24915
+ content.push(text(`${network.protocol} access to ${network.host}`));
24916
+ if (params.additionalPermissions)
24917
+ content.push(...profileContent(params.additionalPermissions));
24918
+ const locations = unique([...commandActionPaths(params.commandActions), ...profilePaths(params.additionalPermissions)]);
24919
+ return {
24920
+ toolCallId: params.itemId,
24921
+ name: ToolName.Shell,
24922
+ kind: "execute",
24923
+ status: "pending",
24924
+ title: network ? `${network.protocol} network access to ${network.host}` : commandTitle(params.command, params.commandActions),
24925
+ ...Object.keys(rawInput).length > 0 ? { rawInput } : {},
24926
+ ...locations.length > 0 ? { locations: locations.map((path5) => ({ path: path5 })) } : {},
24927
+ ...content.length > 0 ? { content } : {}
24928
+ };
25206
24929
  }
25207
- function hasDockerCGroup() {
25208
- try {
25209
- return fs2.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
25210
- } catch {
25211
- return false;
25212
- }
24930
+ function fileChangeSubject(params, item) {
24931
+ const paths = item ? changePaths(item.changes) : [];
24932
+ return {
24933
+ toolCallId: params.itemId,
24934
+ name: ToolName.ApplyPatch,
24935
+ kind: "edit",
24936
+ status: "pending",
24937
+ title: paths.length === 1 ? `Edit ${paths[0]}` : paths.length > 1 ? `Edit ${paths.length} files` : "Edit files",
24938
+ ...paths.length > 0 ? { locations: paths.map((path5) => ({ path: path5 })) } : {},
24939
+ ...item ? { content: item.changes.map(diffContent) } : {},
24940
+ ...params.grantRoot ? { rawInput: { grantRoot: params.grantRoot } } : {}
24941
+ };
25213
24942
  }
25214
- function isDocker() {
25215
- if (isDockerCached === undefined) {
25216
- isDockerCached = hasDockerEnv() || hasDockerCGroup();
25217
- }
25218
- return isDockerCached;
24943
+ function permissionsSubject(itemId, cwd, environmentId, permissions) {
24944
+ const content = profileContent(permissions);
24945
+ const paths = profilePaths(permissions);
24946
+ return {
24947
+ toolCallId: itemId,
24948
+ name: ToolName.Shell,
24949
+ kind: "other",
24950
+ status: "pending",
24951
+ title: "Additional sandbox permissions",
24952
+ rawInput: { permissions, cwd, environmentId },
24953
+ ...paths.length > 0 ? { locations: paths.map((path5) => ({ path: path5 })) } : {},
24954
+ ...content.length > 0 ? { content } : {}
24955
+ };
24956
+ }
24957
+ function commandTitle(command, actions) {
24958
+ const first = actions?.[0];
24959
+ if (first?.type === "read")
24960
+ return actions?.length === 1 ? `Read ${first.path}` : "Run command with file reads";
24961
+ if (first?.type === "listFiles")
24962
+ return "List files";
24963
+ if (first?.type === "search")
24964
+ return "Search files";
24965
+ return command ? stripShellPrefix(command) : "Run command";
24966
+ }
24967
+ function profilePaths(permissions) {
24968
+ const fileSystem = permissions?.fileSystem;
24969
+ return unique([
24970
+ ...fileSystem?.read ?? [],
24971
+ ...fileSystem?.write ?? [],
24972
+ ...(fileSystem?.entries ?? []).flatMap((entry) => entry.path.type === "path" ? [entry.path.path] : [])
24973
+ ]);
25219
24974
  }
25220
-
25221
- // node_modules/is-inside-container/index.js
25222
- var cachedResult;
25223
- var hasContainerEnv = () => {
25224
- try {
25225
- fs3.statSync("/run/.containerenv");
25226
- return true;
25227
- } catch {
25228
- return false;
24975
+ function profileContent(permissions) {
24976
+ const lines = [];
24977
+ const networkEnabled = permissions.network?.enabled;
24978
+ if (networkEnabled !== null && networkEnabled !== undefined) {
24979
+ lines.push(networkEnabled ? "Enable network access" : "Disable network access");
25229
24980
  }
25230
- };
25231
- function isInsideContainer() {
25232
- if (cachedResult === undefined) {
25233
- cachedResult = hasContainerEnv() || isDocker();
24981
+ for (const entry of permissions.fileSystem?.entries ?? []) {
24982
+ if (entry.path.type === "glob_pattern")
24983
+ lines.push(`${entry.access} filesystem pattern ${entry.path.pattern}`);
24984
+ else if (entry.path.type === "special")
24985
+ lines.push(`${entry.access} Codex filesystem scope ${JSON.stringify(entry.path.value)}`);
25234
24986
  }
25235
- return cachedResult;
24987
+ return lines.length > 0 ? [text(lines.join(`
24988
+ `))] : [];
25236
24989
  }
25237
-
25238
- // node_modules/is-wsl/index.js
25239
- var isWsl = () => {
25240
- if (process2.platform !== "linux") {
25241
- return false;
25242
- }
25243
- if (os.release().toLowerCase().includes("microsoft")) {
25244
- if (isInsideContainer()) {
25245
- return false;
25246
- }
25247
- return true;
25248
- }
25249
- try {
25250
- if (fs4.readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft")) {
25251
- return !isInsideContainer();
25252
- }
25253
- } catch {}
25254
- if (fs4.existsSync("/proc/sys/fs/binfmt_misc/WSLInterop") || fs4.existsSync("/run/WSL")) {
25255
- return !isInsideContainer();
25256
- }
25257
- return false;
25258
- };
25259
- var is_wsl_default = process2.env.__IS_WSL_TEST__ ? isWsl : isWsl();
25260
-
25261
- // node_modules/wsl-utils/node_modules/powershell-utils/index.js
25262
- import process3 from "process";
25263
- import { Buffer as Buffer2 } from "buffer";
25264
- import { promisify } from "util";
25265
- import childProcess from "child_process";
25266
- var execFile = promisify(childProcess.execFile);
25267
- var powerShellPath = () => `${process3.env.SYSTEMROOT || process3.env.windir || String.raw`C:\Windows`}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`;
25268
- var executePowerShell = async (command, options = {}) => {
25269
- const {
25270
- powerShellPath: psPath,
25271
- ...execFileOptions
25272
- } = options;
25273
- const encodedCommand = executePowerShell.encodeCommand(command);
25274
- return execFile(psPath ?? powerShellPath(), [
25275
- ...executePowerShell.argumentsPrefix,
25276
- encodedCommand
25277
- ], {
25278
- encoding: "utf8",
25279
- ...execFileOptions
25280
- });
25281
- };
25282
- executePowerShell.argumentsPrefix = [
25283
- "-NoProfile",
25284
- "-NonInteractive",
25285
- "-ExecutionPolicy",
25286
- "Bypass",
25287
- "-EncodedCommand"
25288
- ];
25289
- executePowerShell.encodeCommand = (command) => Buffer2.from(command, "utf16le").toString("base64");
25290
- executePowerShell.escapeArgument = (value) => `'${String(value).replaceAll("'", "''")}'`;
25291
-
25292
- // node_modules/wsl-utils/utilities.js
25293
- function parseMountPointFromConfig(content) {
25294
- for (const line of content.split(`
25295
- `)) {
25296
- if (/^\s*#/.test(line)) {
25297
- continue;
25298
- }
25299
- const match = /^\s*root\s*=\s*(?<mountPoint>"[^"]*"|'[^']*'|[^#]*)/.exec(line);
25300
- if (!match) {
25301
- continue;
25302
- }
25303
- return match.groups.mountPoint.trim().replaceAll(/^["']|["']$/g, "");
25304
- }
24990
+ function text(value) {
24991
+ return { type: "content", content: { type: "text", text: value } };
24992
+ }
24993
+ function unique(values) {
24994
+ return [...new Set(values)];
25305
24995
  }
25306
24996
 
25307
- // node_modules/wsl-utils/index.js
25308
- var execFile2 = promisify2(childProcess2.execFile);
25309
- var wslDrivesMountPoint = (() => {
25310
- const defaultMountPoint = "/mnt/";
25311
- let mountPoint;
25312
- return async function() {
25313
- if (mountPoint) {
25314
- return mountPoint;
25315
- }
25316
- const configFilePath = "/etc/wsl.conf";
25317
- let isConfigFileExists = false;
25318
- try {
25319
- await fs5.access(configFilePath, fsConstants.F_OK);
25320
- isConfigFileExists = true;
25321
- } catch {}
25322
- if (!isConfigFileExists) {
25323
- return defaultMountPoint;
25324
- }
25325
- const configContent = await fs5.readFile(configFilePath, { encoding: "utf8" });
25326
- const parsedMountPoint = parseMountPointFromConfig(configContent);
25327
- if (parsedMountPoint === undefined) {
25328
- return defaultMountPoint;
24997
+ // src/permissions/ApprovalHandler.ts
24998
+ class CodexApprovalHandler {
24999
+ client;
25000
+ turn;
25001
+ signal;
25002
+ constructor(client, turn, signal = () => {
25003
+ return;
25004
+ }) {
25005
+ this.client = client;
25006
+ this.turn = turn;
25007
+ this.signal = signal;
25008
+ }
25009
+ async handleCommandExecution(params) {
25010
+ const approvalParams = params;
25011
+ const decisions = commandDecisionOptions(approvalParams);
25012
+ if (!decisions) {
25013
+ logger.log("cancelling command approval: no complete decision set", { itemId: params.itemId });
25014
+ return { decision: "cancel" };
25329
25015
  }
25330
- mountPoint = parsedMountPoint;
25331
- mountPoint = mountPoint.endsWith("/") ? mountPoint : `${mountPoint}/`;
25332
- return mountPoint;
25333
- };
25334
- })();
25335
- var powerShellPathFromWsl = async () => {
25336
- const mountPoint = await wslDrivesMountPoint();
25337
- return `${mountPoint}c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe`;
25338
- };
25339
- var powerShellPath2 = is_wsl_default ? powerShellPathFromWsl : powerShellPath;
25340
- var canAccessPowerShellPromise;
25341
- var canAccessPowerShell = async () => {
25342
- canAccessPowerShellPromise ??= (async () => {
25343
25016
  try {
25344
- const psPath = await powerShellPath2();
25345
- await fs5.access(psPath, fsConstants.X_OK);
25346
- return true;
25347
- } catch {
25348
- return false;
25349
- }
25350
- })();
25351
- return canAccessPowerShellPromise;
25352
- };
25353
- var wslDefaultBrowser = async () => {
25354
- const psPath = await powerShellPath2();
25355
- const command = String.raw`(Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice").ProgId`;
25356
- const { stdout } = await executePowerShell(command, {
25357
- powerShellPath: psPath,
25358
- cwd: path4.dirname(psPath)
25359
- });
25360
- return stdout.trim();
25361
- };
25362
- var isUrl = (path5) => /^[a-z]+:\/\//i.test(path5);
25363
- var convertWslPathToWindows = async (paths) => {
25364
- const isBatch = Array.isArray(paths);
25365
- const pathArray = isBatch ? paths : [paths];
25366
- const indicesToConvert = [];
25367
- const pathsToConvert = [];
25368
- for (const [index, path5] of pathArray.entries()) {
25369
- if (!isUrl(path5)) {
25370
- indicesToConvert.push(index);
25371
- pathsToConvert.push(path5);
25017
+ const response = await this.client.requestPermission({
25018
+ title: params.networkApprovalContext ? NETWORK_TITLE : COMMAND_TITLE,
25019
+ description: nonBlank(params.reason),
25020
+ subject: { type: "tool_call", toolCall: commandSubject(approvalParams) },
25021
+ options: decisions.map(({ option }) => option)
25022
+ }, this.signal());
25023
+ return { decision: selectedDecision(response, decisions) ?? "cancel" };
25024
+ } catch (error40) {
25025
+ logger.error("command approval failed", error40, { itemId: params.itemId });
25026
+ return { decision: "cancel" };
25372
25027
  }
25373
25028
  }
25374
- const results = [...pathArray];
25375
- if (pathsToConvert.length > 0) {
25029
+ async handleFileChange(params) {
25030
+ const decisions = fileChangeDecisionOptions();
25376
25031
  try {
25377
- const { stdout } = await execFile2("wslpath", ["-aw", ...pathsToConvert], { encoding: "utf8" });
25378
- const convertedPaths = stdout.split(/\r?\n/).filter(Boolean);
25379
- for (const [index, originalIndex] of indicesToConvert.entries()) {
25380
- results[originalIndex] = convertedPaths[index] ?? pathArray[originalIndex];
25381
- }
25382
- } catch {}
25383
- }
25384
- return isBatch ? results : results[0];
25385
- };
25386
-
25387
- // node_modules/powershell-utils/index.js
25388
- import process4 from "process";
25389
- import { Buffer as Buffer3 } from "buffer";
25390
- import { promisify as promisify3 } from "util";
25391
- import childProcess3 from "child_process";
25392
- var execFile3 = promisify3(childProcess3.execFile);
25393
- var powerShellPath3 = () => `${process4.env.SYSTEMROOT || process4.env.windir || String.raw`C:\Windows`}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`;
25394
- var argumentsPrefix = [
25395
- "-NoProfile",
25396
- "-NonInteractive",
25397
- "-ExecutionPolicy",
25398
- "Bypass",
25399
- "-EncodedCommand"
25400
- ];
25401
- var encodeCommand = (command) => Buffer3.from(command, "utf16le").toString("base64");
25402
- var escapeArgument = (value) => `'${String(value).replaceAll(/['\u2018\u2019\u201A\u201B]/g, (match) => match + match)}'`;
25403
- var createArguments = (command) => [...argumentsPrefix, encodeCommand(command)];
25404
- var createExecFileOptions = (options) => ({
25405
- encoding: "utf8",
25406
- ...options,
25407
- shell: false
25408
- });
25409
- var executePowerShell2 = async (command, options = {}) => {
25410
- const {
25411
- powerShellPath: psPath,
25412
- ...execFileOptions
25413
- } = options;
25414
- return execFile3(psPath ?? powerShellPath3(), createArguments(command), createExecFileOptions(execFileOptions));
25415
- };
25416
- executePowerShell2.argumentsPrefix = argumentsPrefix;
25417
- executePowerShell2.encodeCommand = encodeCommand;
25418
- executePowerShell2.escapeArgument = escapeArgument;
25419
- executePowerShell2.createArguments = createArguments;
25420
-
25421
- // node_modules/define-lazy-prop/index.js
25422
- function defineLazyProperty(object2, propertyName, valueGetter) {
25423
- const define = (value) => Object.defineProperty(object2, propertyName, { value, enumerable: true, writable: true });
25424
- Object.defineProperty(object2, propertyName, {
25425
- configurable: true,
25426
- enumerable: true,
25427
- get() {
25428
- const result = valueGetter();
25429
- define(result);
25430
- return result;
25431
- },
25432
- set(value) {
25433
- define(value);
25032
+ const response = await this.client.requestPermission({
25033
+ title: FILE_CHANGE_TITLE,
25034
+ description: nonBlank(params.reason),
25035
+ subject: { type: "tool_call", toolCall: fileChangeSubject(params, this.turn.fileChange(params.itemId)) },
25036
+ options: decisions.map(({ option }) => option)
25037
+ }, this.signal());
25038
+ return { decision: selectedDecision(response, decisions) ?? "cancel" };
25039
+ } catch (error40) {
25040
+ logger.error("file change approval failed", error40, { itemId: params.itemId });
25041
+ return { decision: "cancel" };
25434
25042
  }
25435
- });
25436
- return object2;
25437
- }
25438
-
25439
- // node_modules/default-browser/index.js
25440
- import { promisify as promisify7 } from "util";
25441
- import process8 from "process";
25442
- import { execFile as execFile7 } from "child_process";
25443
-
25444
- // node_modules/default-browser-id/index.js
25445
- import { promisify as promisify4 } from "util";
25446
- import process5 from "process";
25447
- import { execFile as execFile4 } from "child_process";
25448
- var execFileAsync = promisify4(execFile4);
25449
- async function defaultBrowserId() {
25450
- if (process5.platform !== "darwin") {
25451
- throw new Error("macOS only");
25452
25043
  }
25453
- const { stdout } = await execFileAsync("defaults", ["read", "com.apple.LaunchServices/com.apple.launchservices.secure", "LSHandlers"]);
25454
- const match = /LSHandlerRoleAll = "(?!-)(?<id>[^"]+?)";\s+?LSHandlerURLScheme = (?:http|https);/.exec(stdout);
25455
- const browserId = match?.groups.id ?? "com.apple.Safari";
25456
- if (browserId === "com.apple.safari") {
25457
- return "com.apple.Safari";
25044
+ async handlePermissionsRequest(params) {
25045
+ try {
25046
+ const response = await this.client.requestPermission({
25047
+ title: PERMISSIONS_TITLE,
25048
+ description: nonBlank(params.reason),
25049
+ subject: { type: "tool_call", toolCall: permissionsSubject(params.itemId, params.cwd, params.environmentId, params.permissions) },
25050
+ options: permissionProfileOptions()
25051
+ }, this.signal());
25052
+ return permissionsResponse(params.permissions, response);
25053
+ } catch (error40) {
25054
+ logger.error("permissions approval failed", error40, { itemId: params.itemId });
25055
+ return rejected();
25056
+ }
25458
25057
  }
25459
- return browserId;
25460
25058
  }
25461
-
25462
- // node_modules/run-applescript/index.js
25463
- import process6 from "process";
25464
- import { promisify as promisify5 } from "util";
25465
- import { execFile as execFile5, execFileSync } from "child_process";
25466
- var execFileAsync2 = promisify5(execFile5);
25467
- async function runAppleScript(script, { humanReadableOutput = true, signal } = {}) {
25468
- if (process6.platform !== "darwin") {
25469
- throw new Error("macOS only");
25470
- }
25471
- const outputArguments = humanReadableOutput ? [] : ["-ss"];
25472
- const execOptions = {};
25473
- if (signal) {
25474
- execOptions.signal = signal;
25059
+ function permissionsResponse(permissions, response) {
25060
+ if (response.outcome.outcome !== "selected")
25061
+ return rejected();
25062
+ switch (response.outcome.optionId) {
25063
+ case OptionId.AllowPermissionsForTurn:
25064
+ return { permissions: granted(permissions), scope: "turn", strictAutoReview: false };
25065
+ case OptionId.AllowPermissionsForTurnStrict:
25066
+ return { permissions: granted(permissions), scope: "turn", strictAutoReview: true };
25067
+ case OptionId.AllowPermissionsForSession:
25068
+ return { permissions: granted(permissions), scope: "session", strictAutoReview: false };
25069
+ default:
25070
+ return rejected();
25475
25071
  }
25476
- const { stdout } = await execFileAsync2("osascript", ["-e", script, outputArguments], execOptions);
25477
- return stdout.trim();
25478
25072
  }
25479
-
25480
- // node_modules/bundle-name/index.js
25481
- async function bundleName(bundleId) {
25482
- return runAppleScript(`tell application "Finder" to set app_path to application file id "${bundleId}" as string
25483
- tell application "System Events" to get value of property list item "CFBundleName" of property list file (app_path & ":Contents:Info.plist")`);
25073
+ function granted(permissions) {
25074
+ return {
25075
+ ...permissions.network ? { network: permissions.network } : {},
25076
+ ...permissions.fileSystem ? { fileSystem: permissions.fileSystem } : {}
25077
+ };
25078
+ }
25079
+ function rejected() {
25080
+ return { permissions: {}, scope: "turn", strictAutoReview: false };
25081
+ }
25082
+ function nonBlank(value) {
25083
+ const trimmed = value?.trim();
25084
+ return trimmed ? trimmed : null;
25484
25085
  }
25485
25086
 
25486
- // node_modules/default-browser/windows.js
25487
- import process7 from "process";
25488
- import { promisify as promisify6 } from "util";
25489
- import { execFile as execFile6 } from "child_process";
25490
- var execFileAsync3 = promisify6(execFile6);
25491
- var windowsBrowserProgIds = {
25492
- MSEdgeHTM: { name: "Edge", id: "com.microsoft.edge" },
25493
- MSEdgeBHTML: { name: "Edge Beta", id: "com.microsoft.edge.beta" },
25494
- MSEdgeDHTML: { name: "Edge Dev", id: "com.microsoft.edge.dev" },
25495
- AppXq0fevzme2pys62n3e0fbqa7peapykr8v: { name: "Edge", id: "com.microsoft.edge.old" },
25496
- ChromeHTML: { name: "Chrome", id: "com.google.chrome" },
25497
- ChromeBHTML: { name: "Chrome Beta", id: "com.google.chrome.beta" },
25498
- ChromeDHTML: { name: "Chrome Dev", id: "com.google.chrome.dev" },
25499
- ChromiumHTM: { name: "Chromium", id: "org.chromium.Chromium" },
25500
- BraveHTML: { name: "Brave", id: "com.brave.Browser" },
25501
- BraveBHTML: { name: "Brave Beta", id: "com.brave.Browser.beta" },
25502
- BraveDHTML: { name: "Brave Dev", id: "com.brave.Browser.dev" },
25503
- BraveSSHTM: { name: "Brave Nightly", id: "com.brave.Browser.nightly" },
25504
- FirefoxURL: { name: "Firefox", id: "org.mozilla.firefox" },
25505
- OperaStable: { name: "Opera", id: "com.operasoftware.Opera" },
25506
- VivaldiHTM: { name: "Vivaldi", id: "com.vivaldi.Vivaldi" },
25507
- "IE.HTTP": { name: "Internet Explorer", id: "com.microsoft.ie" }
25087
+ // src/permissions/mcp.ts
25088
+ var McpOptionId = {
25089
+ AllowOnce: "allow_once",
25090
+ Accept: "accept",
25091
+ AllowSession: "allow_session",
25092
+ AllowAlways: "allow_always",
25093
+ Decline: "decline",
25094
+ Cancel: "cancel"
25508
25095
  };
25509
- var _windowsBrowserProgIdMap = new Map(Object.entries(windowsBrowserProgIds));
25510
-
25511
- class UnknownBrowserError extends Error {
25096
+ function parsePersistOptions(meta) {
25097
+ const result = new Set;
25098
+ if (!isRecord2(meta))
25099
+ return result;
25100
+ const persist = meta["persist"];
25101
+ const values = Array.isArray(persist) ? persist : [persist];
25102
+ if (values.includes("session"))
25103
+ result.add("session");
25104
+ if (values.includes("always"))
25105
+ result.add("always");
25106
+ return result;
25512
25107
  }
25513
- async function defaultBrowser(_execFileAsync = execFileAsync3) {
25514
- const regPath = `${process7.env.SYSTEMROOT ?? process7.env.windir ?? "C:\\Windows"}\\System32\\reg.exe`;
25515
- const { stdout } = await _execFileAsync(regPath, [
25516
- "QUERY",
25517
- " HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\http\\UserChoice",
25518
- "/v",
25519
- "ProgId"
25520
- ]);
25521
- const match = /ProgId\s*REG_SZ\s*(?<id>\S+)/.exec(stdout);
25522
- if (!match) {
25523
- throw new UnknownBrowserError(`Cannot find Windows browser in stdout: ${JSON.stringify(stdout)}`);
25108
+ function isMcpToolCallApproval(meta) {
25109
+ return isRecord2(meta) && meta["codex_approval_kind"] === "mcp_tool_call";
25110
+ }
25111
+ function mcpPermissionOptions(isToolApproval, persist) {
25112
+ const options = [permissionOption(isToolApproval ? McpOptionId.AllowOnce : McpOptionId.Accept, "Allow", "allow_once", isToolApproval ? "Run the tool and continue." : "Allow this request and continue.")];
25113
+ if (persist.has("session")) {
25114
+ options.push(permissionOption(McpOptionId.AllowSession, "Allow for this session", "allow_always", isToolApproval ? "Run the tool and remember this choice for this session." : "Allow this request and remember this choice for this session."));
25524
25115
  }
25525
- const { id } = match.groups;
25526
- const dotIndex = id.lastIndexOf(".");
25527
- const hyphenIndex = id.lastIndexOf("-");
25528
- const baseIdByDot = dotIndex === -1 ? undefined : id.slice(0, dotIndex);
25529
- const baseIdByHyphen = hyphenIndex === -1 ? undefined : id.slice(0, hyphenIndex);
25530
- return windowsBrowserProgIds[id] ?? windowsBrowserProgIds[baseIdByDot] ?? windowsBrowserProgIds[baseIdByHyphen] ?? { name: id, id };
25116
+ if (persist.has("always")) {
25117
+ options.push(permissionOption(McpOptionId.AllowAlways, "Always allow", "allow_always", isToolApproval ? "Run the tool and remember this choice for future tool calls." : "Allow this request and remember this choice for future requests."));
25118
+ }
25119
+ if (!isToolApproval) {
25120
+ options.push(permissionOption(McpOptionId.Decline, "Deny", "reject_once", "Decline this request and continue."));
25121
+ }
25122
+ options.push(permissionOption(McpOptionId.Cancel, "Cancel", "reject_once", isToolApproval ? "Cancel this tool call." : "Cancel this request."));
25123
+ return options;
25531
25124
  }
25532
-
25533
- // node_modules/default-browser/index.js
25534
- var execFileAsync4 = promisify7(execFile7);
25535
- var titleize = (string4) => string4.toLowerCase().replaceAll(/(?:^|\s|-)\S/g, (x) => x.toUpperCase());
25536
- async function defaultBrowser2() {
25537
- if (process8.platform === "darwin") {
25538
- const id = await defaultBrowserId();
25539
- const name = await bundleName(id);
25540
- return { name, id };
25125
+ function mcpPermissionRequest(params, context, nextStandaloneToolCallId) {
25126
+ const message = { type: "content", content: { type: "text", text: params.message } };
25127
+ const options = mcpPermissionOptions(context.isToolApproval, context.persistOptions);
25128
+ const title = context.isToolApproval ? `Allow MCP tool call on ${params.serverName}?` : `${params.serverName} requests approval`;
25129
+ if (params.mode === "url") {
25130
+ return {
25131
+ title,
25132
+ description: params.message,
25133
+ subject: {
25134
+ type: "tool_call",
25135
+ toolCall: {
25136
+ toolCallId: `elicitation:${params.elicitationId}`,
25137
+ name: ToolName.Mcp,
25138
+ kind: "fetch",
25139
+ status: "pending",
25140
+ title: `Open ${params.url}`,
25141
+ content: [message],
25142
+ rawInput: { serverName: params.serverName, url: params.url }
25143
+ }
25144
+ },
25145
+ options
25146
+ };
25541
25147
  }
25542
- if (process8.platform === "linux") {
25543
- const { stdout } = await execFileAsync4("xdg-mime", ["query", "default", "x-scheme-handler/http"]);
25544
- const id = stdout.trim();
25545
- const name = titleize(id.replace(/.desktop$/, "").replace("-", " "));
25546
- return { name, id };
25148
+ if (context.correlatedCallId !== undefined) {
25149
+ return {
25150
+ title,
25151
+ description: params.message,
25152
+ subject: { type: "tool_call", toolCall: { toolCallId: context.correlatedCallId, status: "pending" } },
25153
+ options
25154
+ };
25547
25155
  }
25548
- if (process8.platform === "win32") {
25549
- return defaultBrowser();
25156
+ return {
25157
+ title,
25158
+ description: params.message,
25159
+ subject: {
25160
+ type: "tool_call",
25161
+ toolCall: {
25162
+ toolCallId: nextStandaloneToolCallId(),
25163
+ name: ToolName.Mcp,
25164
+ kind: context.isToolApproval ? "execute" : "other",
25165
+ status: "pending",
25166
+ title,
25167
+ content: [message],
25168
+ rawInput: { serverName: params.serverName, schema: params.requestedSchema }
25169
+ }
25170
+ },
25171
+ options
25172
+ };
25173
+ }
25174
+ function mcpPermissionResponse(response, isToolApproval, persist) {
25175
+ if (response.outcome.outcome !== "selected")
25176
+ return cancelled();
25177
+ switch (response.outcome.optionId) {
25178
+ case McpOptionId.AllowSession:
25179
+ return persist.has("session") ? accepted({ persist: "session" }) : cancelled();
25180
+ case McpOptionId.AllowAlways:
25181
+ return persist.has("always") ? accepted({ persist: "always" }) : cancelled();
25182
+ case McpOptionId.AllowOnce:
25183
+ return isToolApproval ? accepted(null) : cancelled();
25184
+ case McpOptionId.Accept:
25185
+ return isToolApproval ? cancelled() : accepted(null);
25186
+ case McpOptionId.Decline:
25187
+ return isToolApproval ? cancelled() : { action: "decline", content: null, _meta: null };
25188
+ default:
25189
+ return cancelled();
25550
25190
  }
25551
- throw new Error("Only macOS, Linux, and Windows are supported");
25191
+ }
25192
+ function accepted(meta) {
25193
+ return { action: "accept", content: null, _meta: meta };
25194
+ }
25195
+ function cancelled() {
25196
+ return { action: "cancel", content: null, _meta: null };
25552
25197
  }
25553
25198
 
25554
- // node_modules/is-in-ssh/index.js
25555
- import process9 from "process";
25556
- var isInSsh = Boolean(process9.env.SSH_CONNECTION || process9.env.SSH_CLIENT || process9.env.SSH_TTY);
25557
- var is_in_ssh_default = isInSsh;
25199
+ // src/permissions/ElicitationHandler.ts
25200
+ var OTHER_SUFFIX = "__other";
25558
25201
 
25559
- // node_modules/open/index.js
25560
- var fallbackAttemptSymbol = Symbol("fallbackAttempt");
25561
- var __dirname2 = import.meta.url ? path5.dirname(fileURLToPath(import.meta.url)) : "";
25562
- var localXdgOpenPath = path5.join(__dirname2, "xdg-open");
25563
- var { platform, arch } = process10;
25564
- var tryEachApp = async (apps, opener) => {
25565
- if (apps.length === 0) {
25202
+ class CodexElicitationHandler {
25203
+ client;
25204
+ turn;
25205
+ signal;
25206
+ pendingUrlElicitations = new Set;
25207
+ constructor(client, turn, signal = () => {
25566
25208
  return;
25209
+ }) {
25210
+ this.client = client;
25211
+ this.turn = turn;
25212
+ this.signal = signal;
25567
25213
  }
25568
- const errors3 = [];
25569
- for (const app of apps) {
25570
- try {
25571
- return await opener(app);
25572
- } catch (error40) {
25573
- errors3.push(error40);
25214
+ async observe(notification) {
25215
+ if (notification.method !== "serverRequest/resolved")
25216
+ return;
25217
+ const ids = [...this.pendingUrlElicitations];
25218
+ this.pendingUrlElicitations.clear();
25219
+ for (const elicitationId of ids) {
25220
+ await this.client.completeElicitation(elicitationId);
25574
25221
  }
25575
25222
  }
25576
- throw new AggregateError(errors3, "Failed to open in all supported apps");
25577
- };
25578
- var baseOpen = async (options) => {
25579
- options = {
25580
- wait: false,
25581
- background: false,
25582
- newInstance: false,
25583
- allowNonzeroExitCode: false,
25584
- ...options
25585
- };
25586
- const isFallbackAttempt = options[fallbackAttemptSymbol] === true;
25587
- delete options[fallbackAttemptSymbol];
25588
- if (Array.isArray(options.app)) {
25589
- return tryEachApp(options.app, (singleApp) => baseOpen({
25590
- ...options,
25591
- app: singleApp,
25592
- [fallbackAttemptSymbol]: true
25593
- }));
25594
- }
25595
- let { name: app, arguments: appArguments = [] } = options.app ?? {};
25596
- appArguments = [...appArguments];
25597
- if (Array.isArray(app)) {
25598
- return tryEachApp(app, (appName) => baseOpen({
25599
- ...options,
25600
- app: {
25601
- name: appName,
25602
- arguments: appArguments
25603
- },
25604
- [fallbackAttemptSymbol]: true
25605
- }));
25606
- }
25607
- if (app === "browser" || app === "browserPrivate") {
25608
- const ids = {
25609
- "com.google.chrome": "chrome",
25610
- "google-chrome.desktop": "chrome",
25611
- "com.brave.browser": "brave",
25612
- "org.mozilla.firefox": "firefox",
25613
- "firefox.desktop": "firefox",
25614
- "com.microsoft.msedge": "edge",
25615
- "com.microsoft.edge": "edge",
25616
- "com.microsoft.edgemac": "edge",
25617
- "microsoft-edge.desktop": "edge",
25618
- "com.apple.safari": "safari"
25619
- };
25620
- const flags = {
25621
- chrome: "--incognito",
25622
- brave: "--incognito",
25623
- firefox: "--private-window",
25624
- edge: "--inPrivate"
25625
- };
25626
- let browser;
25627
- if (is_wsl_default) {
25628
- const progId = await wslDefaultBrowser();
25629
- const browserInfo = _windowsBrowserProgIdMap.get(progId);
25630
- browser = browserInfo ?? {};
25631
- } else {
25632
- browser = await defaultBrowser2();
25633
- }
25634
- if (Object.hasOwn(ids, browser.id)) {
25635
- const browserName = ids[browser.id.toLowerCase()];
25636
- if (browserName === undefined) {
25637
- throw new Error(`${browser.name} is not supported as a default browser`);
25223
+ async handleElicitation(params) {
25224
+ try {
25225
+ if (params.mode === "openai/userVerification") {
25226
+ logger.log("MCP user verification is not supported over ACP", { server: params.serverName });
25227
+ return cancelled();
25638
25228
  }
25639
- if (app === "browserPrivate") {
25640
- if (browserName === "safari") {
25641
- throw new Error("Safari doesn't support opening in private mode via command line");
25642
- }
25643
- appArguments.push(flags[browserName]);
25229
+ const context = this.mcpContext(params);
25230
+ const messageOnly = isMessageOnlyForm(params);
25231
+ if (!messageOnly && this.useAcpElicitation(params)) {
25232
+ const response2 = await this.client.createElicitation(this.elicitationRequest(params, context), this.signal());
25233
+ const result2 = convertMcpElicitationResponse(response2, context);
25234
+ if (params.mode === "url" && result2.action === "accept")
25235
+ this.pendingUrlElicitations.add(params.elicitationId);
25236
+ if (result2.action === "accept")
25237
+ await this.markCorrelatedCallRunning(context);
25238
+ return result2;
25644
25239
  }
25645
- return baseOpen({
25646
- ...options,
25647
- app: {
25648
- name: apps[browserName],
25649
- arguments: appArguments
25650
- }
25651
- });
25652
- }
25653
- throw new Error(`${browser.name} is not supported as a default browser`);
25654
- }
25655
- let command;
25656
- const cliArguments = [];
25657
- const childProcessOptions = {};
25658
- let shouldUseWindowsInWsl = false;
25659
- if (is_wsl_default && !isInsideContainer() && !is_in_ssh_default && !app) {
25660
- shouldUseWindowsInWsl = await canAccessPowerShell();
25661
- }
25662
- if (platform === "darwin") {
25663
- command = "open";
25664
- if (options.wait) {
25665
- cliArguments.push("--wait-apps");
25666
- }
25667
- if (options.background) {
25668
- cliArguments.push("--background");
25669
- }
25670
- if (options.newInstance) {
25671
- cliArguments.push("--new");
25672
- }
25673
- if (app) {
25674
- cliArguments.push("-a", app);
25675
- }
25676
- } else if (platform === "win32" || shouldUseWindowsInWsl) {
25677
- command = await powerShellPath2();
25678
- cliArguments.push(...executePowerShell2.argumentsPrefix);
25679
- if (!is_wsl_default) {
25680
- childProcessOptions.windowsVerbatimArguments = true;
25681
- }
25682
- if (is_wsl_default && options.target) {
25683
- options.target = await convertWslPathToWindows(options.target);
25684
- }
25685
- const encodedArguments = ["$ProgressPreference = 'SilentlyContinue';", "Start"];
25686
- if (options.wait) {
25687
- encodedArguments.push("-Wait");
25688
- }
25689
- if (app) {
25690
- encodedArguments.push(executePowerShell2.escapeArgument(app));
25691
- if (options.target) {
25692
- appArguments.push(options.target);
25240
+ if (params.mode !== "url" && !messageOnly) {
25241
+ return cancelled();
25693
25242
  }
25694
- } else if (options.target) {
25695
- encodedArguments.push(executePowerShell2.escapeArgument(options.target));
25696
- }
25697
- if (appArguments.length > 0) {
25698
- appArguments = appArguments.map((argument) => executePowerShell2.escapeArgument(argument));
25699
- encodedArguments.push("-ArgumentList", appArguments.join(","));
25700
- }
25701
- options.target = executePowerShell2.encodeCommand(encodedArguments.join(" "));
25702
- if (!options.wait) {
25703
- childProcessOptions.stdio = "ignore";
25704
- }
25705
- if (is_wsl_default) {
25706
- childProcessOptions.cwd = path5.dirname(command);
25707
- }
25708
- } else {
25709
- if (app) {
25710
- command = app;
25711
- } else {
25712
- const isBundled = !__dirname2 || __dirname2 === "/";
25713
- let exeLocalXdgOpen = false;
25714
- try {
25715
- await fs6.access(localXdgOpenPath, fsConstants2.X_OK);
25716
- exeLocalXdgOpen = true;
25717
- } catch {}
25718
- const useSystemXdgOpen = process10.versions.electron ?? (platform === "android" || isBundled || !exeLocalXdgOpen);
25719
- command = useSystemXdgOpen ? "xdg-open" : localXdgOpenPath;
25720
- }
25721
- if (appArguments.length > 0) {
25722
- cliArguments.push(...appArguments);
25723
- }
25724
- if (!options.wait) {
25725
- childProcessOptions.stdio = "ignore";
25726
- childProcessOptions.detached = true;
25243
+ const request = mcpPermissionRequest(params, context, () => this.turn.nextStandaloneToolCallId(params.serverName));
25244
+ const response = await this.client.requestPermission(request, this.signal());
25245
+ const result = mcpPermissionResponse(response, context.isToolApproval, context.persistOptions);
25246
+ if (result.action === "accept")
25247
+ await this.markCorrelatedCallRunning(context);
25248
+ return result;
25249
+ } catch (error40) {
25250
+ logger.error("MCP elicitation failed", error40, { server: params.serverName });
25251
+ return cancelled();
25727
25252
  }
25728
25253
  }
25729
- if (platform === "darwin" && appArguments.length > 0) {
25730
- cliArguments.push("--args", ...appArguments);
25731
- }
25732
- if (options.target) {
25733
- cliArguments.push(options.target);
25734
- }
25735
- const subprocess = childProcess4.spawn(command, cliArguments, childProcessOptions);
25736
- if (options.wait) {
25737
- return new Promise((resolve, reject) => {
25738
- subprocess.once("error", reject);
25739
- subprocess.once("close", (exitCode) => {
25740
- if (!options.allowNonzeroExitCode && exitCode !== 0) {
25741
- reject(new Error(`Exited with code ${exitCode}`));
25742
- return;
25743
- }
25744
- resolve(subprocess);
25745
- });
25746
- });
25747
- }
25748
- if (isFallbackAttempt) {
25749
- return new Promise((resolve, reject) => {
25750
- subprocess.once("error", reject);
25751
- subprocess.once("spawn", () => {
25752
- subprocess.once("close", (exitCode) => {
25753
- subprocess.off("error", reject);
25754
- if (exitCode !== 0) {
25755
- reject(new Error(`Exited with code ${exitCode}`));
25756
- return;
25757
- }
25758
- subprocess.unref();
25759
- resolve(subprocess);
25760
- });
25761
- });
25762
- });
25763
- }
25764
- subprocess.unref();
25765
- return new Promise((resolve, reject) => {
25766
- subprocess.once("error", reject);
25767
- subprocess.once("spawn", () => {
25768
- subprocess.off("error", reject);
25769
- resolve(subprocess);
25770
- });
25771
- });
25772
- };
25773
- var open = (target, options) => {
25774
- if (typeof target !== "string") {
25775
- throw new TypeError("Expected a `target`");
25776
- }
25777
- return baseOpen({
25778
- ...options,
25779
- target
25780
- });
25781
- };
25782
- function detectArchBinary(binary) {
25783
- if (typeof binary === "string" || Array.isArray(binary)) {
25784
- return binary;
25254
+ async handleUserInput(params) {
25255
+ if (!this.client.capabilities.formElicitation)
25256
+ return { answers: {} };
25257
+ try {
25258
+ const response = await this.client.createElicitation(userInputRequest(this.client.sessionId, params), this.signal());
25259
+ return convertUserInputResponse(response, params);
25260
+ } catch (error40) {
25261
+ logger.error("user input request failed", error40, { itemId: params.itemId });
25262
+ return { answers: {} };
25263
+ }
25785
25264
  }
25786
- const { [arch]: archBinary } = binary;
25787
- if (!archBinary) {
25788
- throw new Error(`${arch} is not supported`);
25265
+ mcpContext(params) {
25266
+ const isToolApproval = isMcpToolCallApproval(params._meta) && isMessageOnlyForm(params);
25267
+ return {
25268
+ isToolApproval,
25269
+ persistOptions: parsePersistOptions(params._meta),
25270
+ correlatedCallId: isToolApproval ? this.turn.popPendingMcpCall(params.serverName) : undefined
25271
+ };
25789
25272
  }
25790
- return archBinary;
25791
- }
25792
- function detectPlatformBinary({ [platform]: platformBinary }, { wsl } = {}) {
25793
- if (wsl && is_wsl_default) {
25794
- return detectArchBinary(wsl);
25273
+ useAcpElicitation(params) {
25274
+ if (params.mode === "form")
25275
+ return this.client.capabilities.formElicitation;
25276
+ if (params.mode === "url")
25277
+ return this.client.capabilities.urlElicitation;
25278
+ return false;
25795
25279
  }
25796
- if (!platformBinary) {
25797
- throw new Error(`${platform} is not supported`);
25280
+ elicitationRequest(params, context) {
25281
+ const base = {
25282
+ sessionId: this.client.sessionId,
25283
+ ...context.correlatedCallId ? { toolCallId: context.correlatedCallId } : {},
25284
+ message: params.message,
25285
+ _meta: recordOrNull(params._meta)
25286
+ };
25287
+ if (params.mode === "form") {
25288
+ return { ...base, mode: "form", requestedSchema: normalizeSchema(params.requestedSchema) };
25289
+ }
25290
+ return { ...base, mode: "url", url: params.url, elicitationId: params.elicitationId };
25291
+ }
25292
+ async markCorrelatedCallRunning(context) {
25293
+ if (context.correlatedCallId === undefined)
25294
+ return;
25295
+ await this.client.update({ sessionUpdate: "tool_call_update", toolCallId: context.correlatedCallId, status: "in_progress" });
25798
25296
  }
25799
- return detectArchBinary(platformBinary);
25800
25297
  }
25801
- var apps = {
25802
- browser: "browser",
25803
- browserPrivate: "browserPrivate"
25804
- };
25805
- defineLazyProperty(apps, "chrome", () => detectPlatformBinary({
25806
- darwin: "google chrome",
25807
- win32: "chrome",
25808
- linux: ["google-chrome", "google-chrome-stable", "chromium", "chromium-browser"]
25809
- }, {
25810
- wsl: {
25811
- ia32: "/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe",
25812
- x64: ["/mnt/c/Program Files/Google/Chrome/Application/chrome.exe", "/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe"]
25298
+ function isMessageOnlyForm(params) {
25299
+ if (params.mode !== "form" && params.mode !== "openai/form" && params.mode !== "openaiForm")
25300
+ return false;
25301
+ const schema = params.requestedSchema;
25302
+ if (schema === null)
25303
+ return true;
25304
+ if (!isRecord2(schema))
25305
+ return false;
25306
+ return schema["type"] === "object" && isRecord2(schema["properties"]) && Object.keys(schema["properties"]).length === 0;
25307
+ }
25308
+ function normalizeSchema(value) {
25309
+ const normalized = normalizeSchemaValue(value);
25310
+ if (!isRecord2(normalized))
25311
+ return { type: "object", properties: {} };
25312
+ return { ...normalized, type: "object" };
25313
+ }
25314
+ function normalizeSchemaValue(value) {
25315
+ if (typeof value === "bigint")
25316
+ return Number(value);
25317
+ if (Array.isArray(value))
25318
+ return value.map(normalizeSchemaValue);
25319
+ if (!isRecord2(value))
25320
+ return value;
25321
+ const result = Object.fromEntries(Object.entries(value).filter(([, nested]) => nested !== undefined).map(([key, nested]) => [key, normalizeSchemaValue(nested)]));
25322
+ if (result["type"] === "string" && Array.isArray(result["enum"]) && Array.isArray(result["enumNames"]) && !Array.isArray(result["oneOf"])) {
25323
+ const values = result["enum"];
25324
+ const names = result["enumNames"];
25325
+ result["oneOf"] = values.map((entry, index) => ({ const: String(entry), title: String(names[index] ?? entry) }));
25326
+ delete result["enum"];
25327
+ delete result["enumNames"];
25813
25328
  }
25814
- }));
25815
- defineLazyProperty(apps, "brave", () => detectPlatformBinary({
25816
- darwin: "brave browser",
25817
- win32: "brave",
25818
- linux: ["brave-browser", "brave"]
25819
- }, {
25820
- wsl: {
25821
- ia32: "/mnt/c/Program Files (x86)/BraveSoftware/Brave-Browser/Application/brave.exe",
25822
- x64: ["/mnt/c/Program Files/BraveSoftware/Brave-Browser/Application/brave.exe", "/mnt/c/Program Files (x86)/BraveSoftware/Brave-Browser/Application/brave.exe"]
25329
+ return result;
25330
+ }
25331
+ function convertMcpElicitationResponse(response, context) {
25332
+ if (CreateElicitationResponse.isAccept(response)) {
25333
+ const content = isRecord2(response.content) ? { ...response.content } : {};
25334
+ const persist = context.isToolApproval ? content["persist"] : undefined;
25335
+ if (context.isToolApproval && !persistAllowed(persist, context.persistOptions))
25336
+ return cancelled();
25337
+ if (persist === "session" || persist === "always" || persist === "once")
25338
+ delete content["persist"];
25339
+ return { action: "accept", content: contentOrNull(content), _meta: responseMeta(response, context, persist) };
25823
25340
  }
25824
- }));
25825
- defineLazyProperty(apps, "firefox", () => detectPlatformBinary({
25826
- darwin: "firefox",
25827
- win32: String.raw`C:\Program Files\Mozilla Firefox\firefox.exe`,
25828
- linux: "firefox"
25829
- }, {
25830
- wsl: "/mnt/c/Program Files/Mozilla Firefox/firefox.exe"
25831
- }));
25832
- defineLazyProperty(apps, "edge", () => detectPlatformBinary({
25833
- darwin: "microsoft edge",
25834
- win32: "msedge",
25835
- linux: ["microsoft-edge", "microsoft-edge-dev"]
25836
- }, {
25837
- wsl: "/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe"
25838
- }));
25839
- defineLazyProperty(apps, "safari", () => detectPlatformBinary({
25840
- darwin: "Safari"
25841
- }));
25842
- var open_default = open;
25843
-
25844
- // src/agent/auth.ts
25845
- var AuthMethodId = {
25846
- ApiKey: "api-key",
25847
- ChatGpt: "chat-gpt",
25848
- ChatGptDeviceCode: "chat-gpt-device-code"
25849
- };
25850
- var CODEX_API_KEY_ENV = "CODEX_API_KEY";
25851
- var OPENAI_API_KEY_ENV = "OPENAI_API_KEY";
25852
- function authMethods(capabilities, env = process.env) {
25853
- const methods2 = [{
25854
- type: "agent",
25855
- methodId: AuthMethodId.ApiKey,
25856
- name: "API key",
25857
- description: `Use the API key from ${CODEX_API_KEY_ENV} or ${OPENAI_API_KEY_ENV}, or pass it in _meta["api-key"].apiKey`
25858
- }];
25859
- if (!env["NO_BROWSER"]) {
25860
- methods2.push({
25861
- type: "agent",
25862
- methodId: AuthMethodId.ChatGpt,
25863
- name: "ChatGPT",
25864
- description: "Sign in to ChatGPT in your browser"
25865
- });
25341
+ if (CreateElicitationResponse.isDecline(response)) {
25342
+ return context.isToolApproval ? { action: "cancel", content: null, _meta: responseMeta(response, context) } : { action: "decline", content: null, _meta: responseMeta(response, context) };
25866
25343
  }
25867
- if (capabilities.urlElicitation) {
25868
- methods2.push({
25869
- type: "agent",
25870
- methodId: AuthMethodId.ChatGptDeviceCode,
25871
- name: "ChatGPT (device code)",
25872
- description: "Sign in to ChatGPT by opening a verification page and entering a one-time code"
25873
- });
25344
+ if (CreateElicitationResponse.isCancel(response)) {
25345
+ return { action: "cancel", content: null, _meta: responseMeta(response, context) };
25874
25346
  }
25875
- return methods2;
25347
+ return cancelled();
25876
25348
  }
25877
- async function login(codex, link, request, requestId, env = process.env) {
25878
- switch (request.methodId) {
25879
- case AuthMethodId.ApiKey:
25880
- return await loginWithApiKey(codex, request, env);
25881
- case AuthMethodId.ChatGpt:
25882
- return await loginWithChatGpt(codex);
25883
- case AuthMethodId.ChatGptDeviceCode:
25884
- return await loginWithDeviceCode(codex, link, requestId);
25885
- default:
25886
- throw RequestError.invalidParams({ methodId: request.methodId }, `Unknown auth method "${request.methodId}"; expected one of ${Object.values(AuthMethodId).join(", ")}`);
25887
- }
25349
+ function persistAllowed(persist, options) {
25350
+ return persist === undefined || persist === "once" || persist === "session" && options.has("session") || persist === "always" && options.has("always");
25888
25351
  }
25889
- async function loginWithApiKey(codex, request, env) {
25890
- const meta = request._meta?.["api-key"];
25891
- const fromMeta = typeof meta === "object" && meta !== null ? meta.apiKey : undefined;
25892
- const apiKey = typeof fromMeta === "string" && fromMeta.trim().length > 0 ? fromMeta.trim() : env[CODEX_API_KEY_ENV]?.trim() || env[OPENAI_API_KEY_ENV]?.trim();
25893
- if (!apiKey) {
25894
- throw RequestError.invalidParams({ envVars: [CODEX_API_KEY_ENV, OPENAI_API_KEY_ENV] }, `No API key: set ${CODEX_API_KEY_ENV} or ${OPENAI_API_KEY_ENV}, or pass _meta["api-key"].apiKey`);
25352
+ function contentOrNull(content) {
25353
+ const entries = Object.entries(content);
25354
+ if (entries.length === 0)
25355
+ return null;
25356
+ return Object.fromEntries(entries.map(([key, value]) => [key, normalizeJsonValue(value)]));
25357
+ }
25358
+ function responseMeta(response, context, persist) {
25359
+ const source = recordOrNull(response._meta);
25360
+ const meta = source ? normalizeJsonObject(source) : {};
25361
+ if (context.isToolApproval)
25362
+ delete meta["persist"];
25363
+ if (persist === "session" || persist === "always")
25364
+ meta["persist"] = persist;
25365
+ return Object.keys(meta).length === 0 ? null : meta;
25366
+ }
25367
+ function userInputRequest(sessionId, params) {
25368
+ const properties = {};
25369
+ const required2 = [];
25370
+ const ids = new Set(params.questions.map((question) => question.id));
25371
+ for (const question of params.questions) {
25372
+ const options = question.options ?? [];
25373
+ const hasOther = question.isOther && options.length > 0;
25374
+ const base = {
25375
+ title: question.header || question.id,
25376
+ description: question.question,
25377
+ _meta: { codex: { isOther: question.isOther, isSecret: question.isSecret } }
25378
+ };
25379
+ if (!hasOther)
25380
+ required2.push(question.id);
25381
+ properties[question.id] = options.length > 0 ? { ...base, type: "string", oneOf: options.map((option) => ({ const: option.label, title: option.label, description: option.description })) } : { ...base, type: "string" };
25382
+ if (hasOther) {
25383
+ properties[otherFieldId(question.id, ids)] = {
25384
+ type: "string",
25385
+ title: "Other",
25386
+ description: "Type your own answer instead of choosing an option above.",
25387
+ _meta: { codex: { questionId: question.id, isOtherAnswer: true, isSecret: question.isSecret } }
25388
+ };
25389
+ }
25895
25390
  }
25896
- await codex.withNotification("account/login/completed", async (completed) => {
25897
- await codex.accountLogin({ type: "apiKey", apiKey });
25898
- const result = await completed;
25899
- if (!result.success)
25900
- throw RequestError.authRequired({ error: result.error }, result.error ?? "API key login failed");
25901
- });
25391
+ const first = params.questions[0];
25392
+ return {
25393
+ sessionId,
25394
+ toolCallId: params.itemId,
25395
+ mode: "form",
25396
+ message: params.questions.length === 1 && first ? first.question : "Input requested",
25397
+ requestedSchema: { type: "object", properties, required: required2 },
25398
+ _meta: { codex: { isBlocking: params.isBlocking } }
25399
+ };
25902
25400
  }
25903
- async function loginWithChatGpt(codex) {
25904
- const account = await codex.accountRead({ refreshToken: true });
25905
- if (account.account?.type === "chatgpt")
25906
- return;
25907
- await codex.withNotification("account/login/completed", async (completed) => {
25908
- const started = await codex.accountLogin({ type: "chatgpt" });
25909
- if (started.type !== "chatgpt")
25910
- throw RequestError.internalError({ type: started.type }, "Unexpected login response");
25911
- logger.log("opening browser for ChatGPT login", { loginId: started.loginId });
25912
- await open_default(started.authUrl);
25913
- const result = await completed;
25914
- if (!result.success)
25915
- throw RequestError.authRequired({ error: result.error }, result.error ?? "ChatGPT login failed");
25916
- });
25401
+ function convertUserInputResponse(response, params) {
25402
+ if (!CreateElicitationResponse.isAccept(response))
25403
+ return { answers: {} };
25404
+ const content = isRecord2(response.content) ? response.content : {};
25405
+ const ids = new Set(params.questions.map((question) => question.id));
25406
+ const answers = {};
25407
+ for (const question of params.questions) {
25408
+ const hasOther = question.isOther && (question.options?.length ?? 0) > 0;
25409
+ const value = (hasOther ? answerValue(content, otherFieldId(question.id, ids)) : undefined) ?? answerValue(content, question.id);
25410
+ if (value === undefined)
25411
+ continue;
25412
+ answers[question.id] = { answers: Array.isArray(value) ? value.map(String) : [String(value)] };
25413
+ }
25414
+ return { answers };
25917
25415
  }
25918
- async function loginWithDeviceCode(codex, link, requestId) {
25919
- const account = await codex.accountRead({ refreshToken: true });
25920
- if (account.account?.type === "chatgpt")
25416
+ function answerValue(content, fieldId) {
25417
+ const value = content[fieldId];
25418
+ if (typeof value === "string" && value.trim() === "")
25921
25419
  return;
25922
- await codex.withNotification("account/login/completed", async (completed) => {
25923
- const started = await codex.accountLogin({ type: "chatgptDeviceCode" });
25924
- if (started.type !== "chatgptDeviceCode")
25925
- throw RequestError.internalError({ type: started.type }, "Unexpected login response");
25926
- const elicitation = link.request(methods.client.elicitation.create, {
25927
- mode: "url",
25928
- requestId,
25929
- elicitationId: started.loginId,
25930
- url: started.verificationUrl,
25931
- message: `Sign in to ChatGPT and enter this code: ${started.userCode}`
25932
- });
25933
- elicitation.catch(() => {});
25934
- const first = await Promise.race([
25935
- completed.then((result2) => ({ kind: "completed", result: result2 })),
25936
- elicitation.then((response) => ({ kind: "elicitation", response }))
25937
- ]);
25938
- if (first.kind === "elicitation" && !CreateElicitationResponse.isAccept(first.response)) {
25939
- await codex.accountLoginCancel({ loginId: started.loginId });
25940
- throw RequestError.authRequired(undefined, "ChatGPT device code login was cancelled");
25941
- }
25942
- const result = first.kind === "completed" ? first.result : await completed;
25943
- await link.notify(methods.client.elicitation.complete, { elicitationId: started.loginId });
25944
- if (!result.success)
25945
- throw RequestError.authRequired({ error: result.error }, result.error ?? "ChatGPT login failed");
25946
- });
25420
+ if (Array.isArray(value) && value.length === 0)
25421
+ return;
25422
+ return value;
25947
25423
  }
25948
- async function logout(codex) {
25949
- await codex.withNotification("account/updated", async (updated) => {
25950
- await codex.accountLogout();
25951
- await updated;
25952
- });
25424
+ function otherFieldId(questionId, ids) {
25425
+ const base = `${questionId}${OTHER_SUFFIX}`;
25426
+ if (!ids.has(base))
25427
+ return base;
25428
+ let index = 1;
25429
+ while (ids.has(`${base}${index}`))
25430
+ index += 1;
25431
+ return `${base}${index}`;
25953
25432
  }
25954
25433
 
25955
- // src/agent/clientSession.ts
25956
- class ClientSession {
25434
+ // src/permissions/turnContext.ts
25435
+ class TurnContext {
25957
25436
  sessionId;
25958
- link;
25959
- capabilities;
25960
- waiting = 0;
25961
- turnActive = false;
25962
- turnGeneration = 0;
25963
- disposed = false;
25964
- output = new AbortController;
25965
- terminalAttempted = false;
25966
- messageId;
25967
- get signal() {
25968
- return this.output.signal;
25969
- }
25970
- constructor(sessionId, link, capabilities) {
25437
+ fileChanges = new Map;
25438
+ pendingMcpCalls = new Map;
25439
+ sequence = 0;
25440
+ constructor(sessionId) {
25971
25441
  this.sessionId = sessionId;
25972
- this.link = link;
25973
- this.capabilities = capabilities;
25974
25442
  }
25975
- async update(update) {
25976
- if (this.disposed)
25443
+ observe(notification) {
25444
+ switch (notification.method) {
25445
+ case "item/started":
25446
+ this.itemStarted(notification.params.item);
25447
+ return;
25448
+ case "item/completed":
25449
+ this.itemCompleted(notification.params.item);
25450
+ return;
25451
+ case "turn/completed":
25452
+ this.fileChanges.clear();
25453
+ this.pendingMcpCalls.clear();
25454
+ return;
25455
+ case "serverRequest/resolved":
25456
+ this.pendingMcpCalls.clear();
25457
+ return;
25458
+ default:
25459
+ return;
25460
+ }
25461
+ }
25462
+ fileChange(itemId) {
25463
+ return this.fileChanges.get(itemId);
25464
+ }
25465
+ popPendingMcpCall(serverName) {
25466
+ const pending = this.pendingMcpCalls.get(serverName);
25467
+ if (pending?.length !== 1)
25977
25468
  return;
25978
- await abortable(this.link.notify(methods.client.session.update, { sessionId: this.sessionId, update }), this.output.signal);
25469
+ this.pendingMcpCalls.delete(serverName);
25470
+ return pending[0];
25979
25471
  }
25980
- async updateAll(updates) {
25981
- for (const update of updates)
25982
- await this.update(update);
25472
+ nextStandaloneToolCallId(serverName) {
25473
+ this.sequence += 1;
25474
+ return `elicitation:${this.sessionId}:${serverName}:${this.sequence}`;
25983
25475
  }
25984
- async requestPermission(request, signal) {
25985
- signal = signal ? AbortSignal.any([signal, this.output.signal]) : this.output.signal;
25986
- signal.throwIfAborted();
25987
- return await this.waitingOnClient(() => {
25988
- signal?.throwIfAborted();
25989
- const pending = this.link.request(methods.client.session.requestPermission, { sessionId: this.sessionId, ...request }, signal ? { cancellationSignal: signal } : undefined);
25990
- return signal ? abortable(pending, signal) : pending;
25991
- }, signal);
25476
+ itemStarted(item) {
25477
+ if (item.type === "fileChange") {
25478
+ this.fileChanges.set(item.id, item);
25479
+ } else if (item.type === "mcpToolCall") {
25480
+ const pending = this.pendingMcpCalls.get(item.server) ?? [];
25481
+ pending.push(item.id);
25482
+ this.pendingMcpCalls.set(item.server, pending);
25483
+ }
25992
25484
  }
25993
- async requestTool(params, signal) {
25994
- return await this.link.request("_alwith/tool/call", params, { cancellationSignal: signal });
25485
+ itemCompleted(item) {
25486
+ if (item.type === "fileChange") {
25487
+ this.fileChanges.delete(item.id);
25488
+ } else if (item.type === "mcpToolCall") {
25489
+ const pending = this.pendingMcpCalls.get(item.server);
25490
+ if (!pending)
25491
+ return;
25492
+ const index = pending.indexOf(item.id);
25493
+ if (index >= 0)
25494
+ pending.splice(index, 1);
25495
+ if (pending.length === 0)
25496
+ this.pendingMcpCalls.delete(item.server);
25497
+ }
25995
25498
  }
25996
- async createElicitation(request, signal) {
25997
- signal = signal ? AbortSignal.any([signal, this.output.signal]) : this.output.signal;
25998
- signal.throwIfAborted();
25999
- return await this.waitingOnClient(() => {
26000
- signal?.throwIfAborted();
26001
- const pending = this.link.request(methods.client.elicitation.create, request, signal ? { cancellationSignal: signal } : undefined);
26002
- return signal ? abortable(pending, signal) : pending;
26003
- }, signal);
25499
+ }
25500
+
25501
+ // node_modules/open/index.js
25502
+ import process10 from "process";
25503
+ import path6 from "path";
25504
+ import { fileURLToPath } from "url";
25505
+ import childProcess4 from "child_process";
25506
+ import fs6, { constants as fsConstants2 } from "fs/promises";
25507
+
25508
+ // node_modules/wsl-utils/index.js
25509
+ import path5 from "path";
25510
+ import { promisify as promisify2 } from "util";
25511
+ import childProcess2 from "child_process";
25512
+ import fs5, { constants as fsConstants } from "fs/promises";
25513
+
25514
+ // node_modules/is-wsl/index.js
25515
+ import process2 from "process";
25516
+ import os2 from "os";
25517
+ import fs4 from "fs";
25518
+
25519
+ // node_modules/is-inside-container/index.js
25520
+ import fs3 from "fs";
25521
+
25522
+ // node_modules/is-docker/index.js
25523
+ import fs2 from "fs";
25524
+ var isDockerCached;
25525
+ function hasDockerEnv() {
25526
+ try {
25527
+ fs2.statSync("/.dockerenv");
25528
+ return true;
25529
+ } catch {
25530
+ return false;
26004
25531
  }
26005
- async completeElicitation(elicitationId) {
26006
- if (this.disposed)
26007
- return;
26008
- await abortable(this.link.notify(methods.client.elicitation.complete, { elicitationId }), this.output.signal);
25532
+ }
25533
+ function hasDockerCGroup() {
25534
+ try {
25535
+ return fs2.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
25536
+ } catch {
25537
+ return false;
26009
25538
  }
26010
- reportRunning(messageId) {
26011
- this.messageId = messageId;
26012
- this.terminalAttempted = false;
26013
- this.turnGeneration += 1;
26014
- this.turnActive = true;
26015
- this.waiting = 0;
26016
- this.state({ sessionUpdate: "state_update", state: "running" });
25539
+ }
25540
+ function isDocker() {
25541
+ if (isDockerCached === undefined) {
25542
+ isDockerCached = hasDockerEnv() || hasDockerCGroup();
26017
25543
  }
26018
- async reportIdle(stopReason, extra, messageId = this.messageId) {
26019
- if (messageId !== this.messageId)
26020
- return;
26021
- if (this.terminalAttempted)
26022
- return;
26023
- this.terminalAttempted = true;
26024
- this.turnActive = false;
26025
- this.waiting = 0;
26026
- await this.update(this.correlated({
26027
- sessionUpdate: "state_update",
26028
- state: "idle",
26029
- stopReason,
26030
- ...extra?.usage === undefined ? {} : { usage: extra.usage },
26031
- ...extra?._meta === undefined ? {} : { _meta: extra._meta }
26032
- }));
25544
+ return isDockerCached;
25545
+ }
25546
+
25547
+ // node_modules/is-inside-container/index.js
25548
+ var cachedResult;
25549
+ var hasContainerEnv = () => {
25550
+ try {
25551
+ fs3.statSync("/run/.containerenv");
25552
+ return true;
25553
+ } catch {
25554
+ return false;
25555
+ }
25556
+ };
25557
+ function isInsideContainer() {
25558
+ if (cachedResult === undefined) {
25559
+ cachedResult = hasContainerEnv() || isDocker();
25560
+ }
25561
+ return cachedResult;
25562
+ }
25563
+
25564
+ // node_modules/is-wsl/index.js
25565
+ var isWsl = () => {
25566
+ if (process2.platform !== "linux") {
25567
+ return false;
25568
+ }
25569
+ if (os2.release().toLowerCase().includes("microsoft")) {
25570
+ if (isInsideContainer()) {
25571
+ return false;
25572
+ }
25573
+ return true;
25574
+ }
25575
+ try {
25576
+ if (fs4.readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft")) {
25577
+ return !isInsideContainer();
25578
+ }
25579
+ } catch {}
25580
+ if (fs4.existsSync("/proc/sys/fs/binfmt_misc/WSLInterop") || fs4.existsSync("/run/WSL")) {
25581
+ return !isInsideContainer();
26033
25582
  }
26034
- correlated(update) {
26035
- if (this.messageId === undefined)
26036
- return update;
26037
- const meta = update._meta;
26038
- const alwith = meta?.["alwith"];
26039
- return { ...update, _meta: { ...meta, alwith: { ...alwith, messageId: this.messageId } } };
25583
+ return false;
25584
+ };
25585
+ var is_wsl_default = process2.env.__IS_WSL_TEST__ ? isWsl : isWsl();
25586
+
25587
+ // node_modules/wsl-utils/node_modules/powershell-utils/index.js
25588
+ import process3 from "process";
25589
+ import { Buffer as Buffer2 } from "buffer";
25590
+ import { promisify } from "util";
25591
+ import childProcess from "child_process";
25592
+ var execFile = promisify(childProcess.execFile);
25593
+ var powerShellPath = () => `${process3.env.SYSTEMROOT || process3.env.windir || String.raw`C:\Windows`}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`;
25594
+ var executePowerShell = async (command, options = {}) => {
25595
+ const {
25596
+ powerShellPath: psPath,
25597
+ ...execFileOptions
25598
+ } = options;
25599
+ const encodedCommand = executePowerShell.encodeCommand(command);
25600
+ return execFile(psPath ?? powerShellPath(), [
25601
+ ...executePowerShell.argumentsPrefix,
25602
+ encodedCommand
25603
+ ], {
25604
+ encoding: "utf8",
25605
+ ...execFileOptions
25606
+ });
25607
+ };
25608
+ executePowerShell.argumentsPrefix = [
25609
+ "-NoProfile",
25610
+ "-NonInteractive",
25611
+ "-ExecutionPolicy",
25612
+ "Bypass",
25613
+ "-EncodedCommand"
25614
+ ];
25615
+ executePowerShell.encodeCommand = (command) => Buffer2.from(command, "utf16le").toString("base64");
25616
+ executePowerShell.escapeArgument = (value) => `'${String(value).replaceAll("'", "''")}'`;
25617
+
25618
+ // node_modules/wsl-utils/utilities.js
25619
+ function parseMountPointFromConfig(content) {
25620
+ for (const line of content.split(`
25621
+ `)) {
25622
+ if (/^\s*#/.test(line)) {
25623
+ continue;
25624
+ }
25625
+ const match = /^\s*root\s*=\s*(?<mountPoint>"[^"]*"|'[^']*'|[^#]*)/.exec(line);
25626
+ if (!match) {
25627
+ continue;
25628
+ }
25629
+ return match.groups.mountPoint.trim().replaceAll(/^["']|["']$/g, "");
26040
25630
  }
26041
- async waitingOnClient(operation, signal) {
26042
- if (this.disposed)
26043
- throw new Error("Session is closed");
26044
- const generation = this.turnActive ? this.turnGeneration : null;
25631
+ }
25632
+
25633
+ // node_modules/wsl-utils/index.js
25634
+ var execFile2 = promisify2(childProcess2.execFile);
25635
+ var wslDrivesMountPoint = (() => {
25636
+ const defaultMountPoint = "/mnt/";
25637
+ let mountPoint;
25638
+ return async function() {
25639
+ if (mountPoint) {
25640
+ return mountPoint;
25641
+ }
25642
+ const configFilePath = "/etc/wsl.conf";
25643
+ let isConfigFileExists = false;
26045
25644
  try {
26046
- if (generation !== null && this.waiting++ === 0) {
26047
- const pending = this.state({ sessionUpdate: "state_update", state: "requires_action" });
26048
- await (signal ? abortable(pending, signal) : pending);
26049
- }
26050
- return await operation();
26051
- } finally {
26052
- if (this.turnActive && generation === this.turnGeneration && --this.waiting === 0) {
26053
- const pending = this.state({ sessionUpdate: "state_update", state: "running" });
26054
- await (signal ? abortable(pending, signal).catch(() => {}) : pending);
26055
- }
25645
+ await fs5.access(configFilePath, fsConstants.F_OK);
25646
+ isConfigFileExists = true;
25647
+ } catch {}
25648
+ if (!isConfigFileExists) {
25649
+ return defaultMountPoint;
26056
25650
  }
26057
- }
26058
- async state(update) {
25651
+ const configContent = await fs5.readFile(configFilePath, { encoding: "utf8" });
25652
+ const parsedMountPoint = parseMountPointFromConfig(configContent);
25653
+ if (parsedMountPoint === undefined) {
25654
+ return defaultMountPoint;
25655
+ }
25656
+ mountPoint = parsedMountPoint;
25657
+ mountPoint = mountPoint.endsWith("/") ? mountPoint : `${mountPoint}/`;
25658
+ return mountPoint;
25659
+ };
25660
+ })();
25661
+ var powerShellPathFromWsl = async () => {
25662
+ const mountPoint = await wslDrivesMountPoint();
25663
+ return `${mountPoint}c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe`;
25664
+ };
25665
+ var powerShellPath2 = is_wsl_default ? powerShellPathFromWsl : powerShellPath;
25666
+ var canAccessPowerShellPromise;
25667
+ var canAccessPowerShell = async () => {
25668
+ canAccessPowerShellPromise ??= (async () => {
26059
25669
  try {
26060
- await this.update(this.correlated(update));
26061
- } catch (error40) {
26062
- logger.error("Failed to publish session state", error40, { sessionId: this.sessionId });
25670
+ const psPath = await powerShellPath2();
25671
+ await fs5.access(psPath, fsConstants.X_OK);
25672
+ return true;
25673
+ } catch {
25674
+ return false;
25675
+ }
25676
+ })();
25677
+ return canAccessPowerShellPromise;
25678
+ };
25679
+ var wslDefaultBrowser = async () => {
25680
+ const psPath = await powerShellPath2();
25681
+ const command = String.raw`(Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice").ProgId`;
25682
+ const { stdout } = await executePowerShell(command, {
25683
+ powerShellPath: psPath,
25684
+ cwd: path5.dirname(psPath)
25685
+ });
25686
+ return stdout.trim();
25687
+ };
25688
+ var isUrl = (path6) => /^[a-z]+:\/\//i.test(path6);
25689
+ var convertWslPathToWindows = async (paths) => {
25690
+ const isBatch = Array.isArray(paths);
25691
+ const pathArray = isBatch ? paths : [paths];
25692
+ const indicesToConvert = [];
25693
+ const pathsToConvert = [];
25694
+ for (const [index, path6] of pathArray.entries()) {
25695
+ if (!isUrl(path6)) {
25696
+ indicesToConvert.push(index);
25697
+ pathsToConvert.push(path6);
26063
25698
  }
26064
25699
  }
26065
- dispose() {
26066
- this.disposed = true;
26067
- this.output.abort();
26068
- this.turnActive = false;
26069
- this.waiting = 0;
25700
+ const results = [...pathArray];
25701
+ if (pathsToConvert.length > 0) {
25702
+ try {
25703
+ const { stdout } = await execFile2("wslpath", ["-aw", ...pathsToConvert], { encoding: "utf8" });
25704
+ const convertedPaths = stdout.split(/\r?\n/).filter(Boolean);
25705
+ for (const [index, originalIndex] of indicesToConvert.entries()) {
25706
+ results[originalIndex] = convertedPaths[index] ?? pathArray[originalIndex];
25707
+ }
25708
+ } catch {}
26070
25709
  }
26071
- }
25710
+ return isBatch ? results : results[0];
25711
+ };
26072
25712
 
26073
- // src/agent/commands.ts
26074
- var BUILTIN_COMMANDS = [
26075
- { name: "plan", description: "Toggle plan mode: Codex proposes a plan before making changes" },
26076
- { name: "review", description: "Review uncommitted changes, or review with custom instructions", input: { type: "text", hint: "optional review instructions" } },
26077
- { name: "review-branch", description: "Review changes relative to a base branch", input: { type: "text", hint: "branch name" } },
26078
- { name: "review-commit", description: "Review a specific commit", input: { type: "text", hint: "commit sha" } },
26079
- { name: "compact", description: "Summarize the conversation to free up context" },
26080
- { name: "status", description: "Show session configuration, token usage, and rate limits" },
26081
- { name: "mcp", description: "List configured Model Context Protocol (MCP) servers" },
26082
- { name: "skills", description: "List available skills" },
26083
- { name: "logout", description: "Sign out of the Codex account" }
25713
+ // node_modules/powershell-utils/index.js
25714
+ import process4 from "process";
25715
+ import { Buffer as Buffer3 } from "buffer";
25716
+ import { promisify as promisify3 } from "util";
25717
+ import childProcess3 from "child_process";
25718
+ var execFile3 = promisify3(childProcess3.execFile);
25719
+ var powerShellPath3 = () => `${process4.env.SYSTEMROOT || process4.env.windir || String.raw`C:\Windows`}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`;
25720
+ var argumentsPrefix = [
25721
+ "-NoProfile",
25722
+ "-NonInteractive",
25723
+ "-ExecutionPolicy",
25724
+ "Bypass",
25725
+ "-EncodedCommand"
26084
25726
  ];
26085
- function parseCommand(prompt) {
26086
- const first = prompt[0];
26087
- if (!first || first.type !== "text")
26088
- return null;
26089
- const text2 = first.text.trim();
26090
- if (!text2.startsWith("/"))
26091
- return null;
26092
- const body = text2.slice(1).trim();
26093
- const [name] = body.split(/\s+/);
26094
- if (!name || name.startsWith("$"))
26095
- return null;
26096
- return { name: name.toLowerCase(), rest: body.slice(name.length).trim() };
26097
- }
26098
- function resolveCommand(command, session) {
26099
- switch (command.name) {
26100
- case "plan":
26101
- return {
26102
- kind: "config",
26103
- configId: COLLABORATION_MODE_CONFIG_ID,
26104
- value: session.collaborationMode === PLAN_COLLABORATION_MODE ? DEFAULT_COLLABORATION_MODE : PLAN_COLLABORATION_MODE
26105
- };
26106
- case "compact":
26107
- return { kind: "compact" };
26108
- case "review":
26109
- return { kind: "review", target: command.rest.length > 0 ? { type: "custom", instructions: command.rest } : { type: "uncommittedChanges" } };
26110
- case "review-branch":
26111
- return command.rest.length > 0 ? { kind: "review", target: { type: "baseBranch", branch: command.rest } } : { kind: "message", text: usage("review-branch", "<branch name>") };
26112
- case "review-commit":
26113
- return command.rest.length > 0 ? { kind: "review", target: { type: "commit", sha: command.rest, title: null } } : { kind: "message", text: usage("review-commit", "<commit sha>") };
26114
- case "logout":
26115
- return { kind: "logout" };
26116
- case "status":
26117
- case "mcp":
26118
- case "skills":
26119
- return { kind: "message", text: "" };
26120
- default:
26121
- return { kind: "prompt" };
26122
- }
26123
- }
26124
- function usage(name, hint) {
26125
- return `Usage: /${name} ${hint}`;
26126
- }
26127
- function availableCommands(skills) {
26128
- const commands = new Map;
26129
- for (const builtin of BUILTIN_COMMANDS)
26130
- commands.set(builtin.name, builtin);
26131
- for (const entry of skills) {
26132
- for (const skill of entry.skills) {
26133
- const name = `$${skill.name}`;
26134
- if (commands.has(name))
26135
- continue;
26136
- commands.set(name, { name, description: skill.shortDescription || skill.description || skill.name });
25727
+ var encodeCommand = (command) => Buffer3.from(command, "utf16le").toString("base64");
25728
+ var escapeArgument = (value) => `'${String(value).replaceAll(/['\u2018\u2019\u201A\u201B]/g, (match) => match + match)}'`;
25729
+ var createArguments = (command) => [...argumentsPrefix, encodeCommand(command)];
25730
+ var createExecFileOptions = (options) => ({
25731
+ encoding: "utf8",
25732
+ ...options,
25733
+ shell: false
25734
+ });
25735
+ var executePowerShell2 = async (command, options = {}) => {
25736
+ const {
25737
+ powerShellPath: psPath,
25738
+ ...execFileOptions
25739
+ } = options;
25740
+ return execFile3(psPath ?? powerShellPath3(), createArguments(command), createExecFileOptions(execFileOptions));
25741
+ };
25742
+ executePowerShell2.argumentsPrefix = argumentsPrefix;
25743
+ executePowerShell2.encodeCommand = encodeCommand;
25744
+ executePowerShell2.escapeArgument = escapeArgument;
25745
+ executePowerShell2.createArguments = createArguments;
25746
+
25747
+ // node_modules/define-lazy-prop/index.js
25748
+ function defineLazyProperty(object2, propertyName, valueGetter) {
25749
+ const define = (value) => Object.defineProperty(object2, propertyName, { value, enumerable: true, writable: true });
25750
+ Object.defineProperty(object2, propertyName, {
25751
+ configurable: true,
25752
+ enumerable: true,
25753
+ get() {
25754
+ const result = valueGetter();
25755
+ define(result);
25756
+ return result;
25757
+ },
25758
+ set(value) {
25759
+ define(value);
26137
25760
  }
25761
+ });
25762
+ return object2;
25763
+ }
25764
+
25765
+ // node_modules/default-browser/index.js
25766
+ import { promisify as promisify7 } from "util";
25767
+ import process8 from "process";
25768
+ import { execFile as execFile7 } from "child_process";
25769
+
25770
+ // node_modules/default-browser-id/index.js
25771
+ import { promisify as promisify4 } from "util";
25772
+ import process5 from "process";
25773
+ import { execFile as execFile4 } from "child_process";
25774
+ var execFileAsync = promisify4(execFile4);
25775
+ async function defaultBrowserId() {
25776
+ if (process5.platform !== "darwin") {
25777
+ throw new Error("macOS only");
26138
25778
  }
26139
- return [...commands.values()];
25779
+ const { stdout } = await execFileAsync("defaults", ["read", "com.apple.LaunchServices/com.apple.launchservices.secure", "LSHandlers"]);
25780
+ const match = /LSHandlerRoleAll = "(?!-)(?<id>[^"]+?)";\s+?LSHandlerURLScheme = (?:http|https);/.exec(stdout);
25781
+ const browserId = match?.groups.id ?? "com.apple.Safari";
25782
+ if (browserId === "com.apple.safari") {
25783
+ return "com.apple.Safari";
25784
+ }
25785
+ return browserId;
26140
25786
  }
26141
- function skillsMessage(skills) {
26142
- const lines = skills.flatMap((entry) => entry.skills).map((skill) => {
26143
- const description = skill.shortDescription || skill.description;
26144
- return description ? `- ${skill.name}: ${description}` : `- ${skill.name}`;
26145
- });
26146
- return lines.length > 0 ? ["Available skills:", ...lines].join(`
26147
- `) : "No skills configured.";
25787
+
25788
+ // node_modules/run-applescript/index.js
25789
+ import process6 from "process";
25790
+ import { promisify as promisify5 } from "util";
25791
+ import { execFile as execFile5, execFileSync } from "child_process";
25792
+ var execFileAsync2 = promisify5(execFile5);
25793
+ async function runAppleScript(script, { humanReadableOutput = true, signal } = {}) {
25794
+ if (process6.platform !== "darwin") {
25795
+ throw new Error("macOS only");
25796
+ }
25797
+ const outputArguments = humanReadableOutput ? [] : ["-ss"];
25798
+ const execOptions = {};
25799
+ if (signal) {
25800
+ execOptions.signal = signal;
25801
+ }
25802
+ const { stdout } = await execFileAsync2("osascript", ["-e", script, outputArguments], execOptions);
25803
+ return stdout.trim();
26148
25804
  }
26149
- async function mcpMessage(codex, session) {
26150
- const servers = await codex.mcpServerStatusList({});
26151
- const lines = servers.data.map((server) => {
26152
- const tools = Object.keys(server.tools ?? {}).length;
26153
- const resources = (server.resources ?? []).length;
26154
- return `- ${server.name}: ${tools} tools, ${resources} resources, auth=${server.authStatus}`;
26155
- });
26156
- const sessionLines = session.mcpServerNames.map((name) => `- ${name}`);
26157
- return [
26158
- lines.length > 0 ? "Configured MCP servers:" : "No MCP servers configured.",
26159
- ...lines,
26160
- ...sessionLines.length > 0 ? ["", "Session MCP servers:", ...sessionLines] : []
26161
- ].join(`
26162
- `);
25805
+
25806
+ // node_modules/bundle-name/index.js
25807
+ async function bundleName(bundleId) {
25808
+ return runAppleScript(`tell application "Finder" to set app_path to application file id "${bundleId}" as string
25809
+ tell application "System Events" to get value of property list item "CFBundleName" of property list file (app_path & ":Contents:Info.plist")`);
26163
25810
  }
26164
- function statusMessage(session, rateLimits) {
26165
- const usage2 = session.lastUsage;
26166
- const lines = [
26167
- "**Session**",
26168
- `**Session ID:** ${session.id}`,
26169
- `**Working directory:** ${session.cwd}`,
26170
- `**Model:** ${session.model.model} (${session.model.effort})`,
26171
- `**Mode:** ${session.mode.name}`,
26172
- `**Collaboration:** ${session.collaborationMode}`,
26173
- `**Fast mode:** ${session.fastMode ? "on" : "off"}`,
26174
- `**Account:** ${accountInfo(session)}`,
26175
- "",
26176
- "**Usage**",
26177
- `**Last turn:** ${usage2 ? `${formatTokenCount(usage2.totalTokens)} total (${formatTokenCount(usage2.inputTokens)} input + ${formatTokenCount(usage2.cachedInputTokens)} cached, ${formatTokenCount(usage2.outputTokens)} output)` : "not available yet"}`,
26178
- `**Context window:** ${usage2 && session.contextWindow ? `${Math.round((session.contextWindow - usage2.totalTokens) / session.contextWindow * 100)}% left (${formatTokenCount(usage2.totalTokens)} / ${formatTokenCount(session.contextWindow)})` : "not available yet"}`,
26179
- ...rateLimitLines(rateLimits)
26180
- ];
26181
- return lines.join(`
26182
- `);
25811
+
25812
+ // node_modules/default-browser/windows.js
25813
+ import process7 from "process";
25814
+ import { promisify as promisify6 } from "util";
25815
+ import { execFile as execFile6 } from "child_process";
25816
+ var execFileAsync3 = promisify6(execFile6);
25817
+ var windowsBrowserProgIds = {
25818
+ MSEdgeHTM: { name: "Edge", id: "com.microsoft.edge" },
25819
+ MSEdgeBHTML: { name: "Edge Beta", id: "com.microsoft.edge.beta" },
25820
+ MSEdgeDHTML: { name: "Edge Dev", id: "com.microsoft.edge.dev" },
25821
+ AppXq0fevzme2pys62n3e0fbqa7peapykr8v: { name: "Edge", id: "com.microsoft.edge.old" },
25822
+ ChromeHTML: { name: "Chrome", id: "com.google.chrome" },
25823
+ ChromeBHTML: { name: "Chrome Beta", id: "com.google.chrome.beta" },
25824
+ ChromeDHTML: { name: "Chrome Dev", id: "com.google.chrome.dev" },
25825
+ ChromiumHTM: { name: "Chromium", id: "org.chromium.Chromium" },
25826
+ BraveHTML: { name: "Brave", id: "com.brave.Browser" },
25827
+ BraveBHTML: { name: "Brave Beta", id: "com.brave.Browser.beta" },
25828
+ BraveDHTML: { name: "Brave Dev", id: "com.brave.Browser.dev" },
25829
+ BraveSSHTM: { name: "Brave Nightly", id: "com.brave.Browser.nightly" },
25830
+ FirefoxURL: { name: "Firefox", id: "org.mozilla.firefox" },
25831
+ OperaStable: { name: "Opera", id: "com.operasoftware.Opera" },
25832
+ VivaldiHTM: { name: "Vivaldi", id: "com.vivaldi.Vivaldi" },
25833
+ "IE.HTTP": { name: "Internet Explorer", id: "com.microsoft.ie" }
25834
+ };
25835
+ var _windowsBrowserProgIdMap = new Map(Object.entries(windowsBrowserProgIds));
25836
+
25837
+ class UnknownBrowserError extends Error {
26183
25838
  }
26184
- function accountInfo(session) {
26185
- const account = session.account;
26186
- if (!account)
26187
- return "not logged in";
26188
- switch (account.type) {
26189
- case "apiKey":
26190
- return "API key";
26191
- case "chatgpt":
26192
- return `ChatGPT ${account.planType}${account.email ? ` (${account.email})` : ""}`;
26193
- case "amazonBedrock":
26194
- return "Amazon Bedrock";
25839
+ async function defaultBrowser(_execFileAsync = execFileAsync3) {
25840
+ const regPath = `${process7.env.SYSTEMROOT ?? process7.env.windir ?? "C:\\Windows"}\\System32\\reg.exe`;
25841
+ const { stdout } = await _execFileAsync(regPath, [
25842
+ "QUERY",
25843
+ " HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\http\\UserChoice",
25844
+ "/v",
25845
+ "ProgId"
25846
+ ]);
25847
+ const match = /ProgId\s*REG_SZ\s*(?<id>\S+)/.exec(stdout);
25848
+ if (!match) {
25849
+ throw new UnknownBrowserError(`Cannot find Windows browser in stdout: ${JSON.stringify(stdout)}`);
26195
25850
  }
25851
+ const { id } = match.groups;
25852
+ const dotIndex = id.lastIndexOf(".");
25853
+ const hyphenIndex = id.lastIndexOf("-");
25854
+ const baseIdByDot = dotIndex === -1 ? undefined : id.slice(0, dotIndex);
25855
+ const baseIdByHyphen = hyphenIndex === -1 ? undefined : id.slice(0, hyphenIndex);
25856
+ return windowsBrowserProgIds[id] ?? windowsBrowserProgIds[baseIdByDot] ?? windowsBrowserProgIds[baseIdByHyphen] ?? { name: id, id };
26196
25857
  }
26197
- function rateLimitLines(rateLimits) {
26198
- const lines = [];
26199
- for (const snapshot of rateLimits.values()) {
26200
- const prefix = snapshot.limitName ? `${snapshot.limitName} ` : "";
26201
- for (const window of [snapshot.primary, snapshot.secondary]) {
26202
- if (!window)
26203
- continue;
26204
- const label = windowLabel(window.windowDurationMins);
26205
- const reset = window.resetsAt === null ? "" : ` (resets ${new Date(window.resetsAt * 1000).toLocaleString()})`;
26206
- lines.push(`**${prefix}${label}:** ${Math.round(100 - window.usedPercent)}% left${reset}`);
26207
- }
26208
- if (snapshot.credits?.unlimited)
26209
- lines.push(`**${prefix}Credits:** unlimited`);
26210
- else if (snapshot.credits?.balance)
26211
- lines.push(`**${prefix}Credits:** ${snapshot.credits.balance}`);
25858
+
25859
+ // node_modules/default-browser/index.js
25860
+ var execFileAsync4 = promisify7(execFile7);
25861
+ var titleize = (string4) => string4.toLowerCase().replaceAll(/(?:^|\s|-)\S/g, (x) => x.toUpperCase());
25862
+ async function defaultBrowser2() {
25863
+ if (process8.platform === "darwin") {
25864
+ const id = await defaultBrowserId();
25865
+ const name = await bundleName(id);
25866
+ return { name, id };
26212
25867
  }
26213
- return lines.length > 0 ? lines : ["**Limits:** not available yet"];
26214
- }
26215
- function windowLabel(minutes) {
26216
- if (minutes === null)
26217
- return "Limit";
26218
- if (minutes < 60)
26219
- return `${minutes}m limit`;
26220
- if (minutes < 1440)
26221
- return `${Math.round(minutes / 60)}h limit`;
26222
- if (minutes < 10080)
26223
- return `${Math.round(minutes / 1440)}d limit`;
26224
- return "Weekly limit";
25868
+ if (process8.platform === "linux") {
25869
+ const { stdout } = await execFileAsync4("xdg-mime", ["query", "default", "x-scheme-handler/http"]);
25870
+ const id = stdout.trim();
25871
+ const name = titleize(id.replace(/.desktop$/, "").replace("-", " "));
25872
+ return { name, id };
25873
+ }
25874
+ if (process8.platform === "win32") {
25875
+ return defaultBrowser();
25876
+ }
25877
+ throw new Error("Only macOS, Linux, and Windows are supported");
26225
25878
  }
26226
25879
 
26227
- // src/agent/configOptions.ts
26228
- function sessionConfigOptions(session) {
26229
- const model = findModel(session.catalog, session.model.model);
26230
- const options = [
26231
- modeConfigOption(session.mode),
26232
- modelConfigOption(session.catalog, session.model.model, session.gatewayGroups),
26233
- effortConfigOption(supportedEfforts(model), session.model.effort),
26234
- collaborationModeConfigOption(session.collaborationMode)
26235
- ];
26236
- if (modelSupportsFast(model))
26237
- options.push(fastModeConfigOption(session.fastMode));
26238
- return options;
26239
- }
26240
- async function applyConfigOption(session, codex, request) {
26241
- switch (request.configId) {
26242
- case MODE_CONFIG_ID: {
26243
- const mode = findAgentMode(stringValue(request));
26244
- if (!mode)
26245
- throw RequestError.invalidParams({ configId: request.configId }, `Unknown mode "${String(request.value)}"`);
26246
- session.mode = mode;
26247
- return;
26248
- }
26249
- case MODEL_CONFIG_ID: {
26250
- const modelId = stringValue(request);
26251
- const model = findModel(session.catalog, modelId);
26252
- session.model = { model: modelId, effort: coerceEffort(model, session.model.effort) };
26253
- if (!modelSupportsFast(model))
26254
- session.fastMode = false;
26255
- return;
25880
+ // node_modules/is-in-ssh/index.js
25881
+ import process9 from "process";
25882
+ var isInSsh = Boolean(process9.env.SSH_CONNECTION || process9.env.SSH_CLIENT || process9.env.SSH_TTY);
25883
+ var is_in_ssh_default = isInSsh;
25884
+
25885
+ // node_modules/open/index.js
25886
+ var fallbackAttemptSymbol = Symbol("fallbackAttempt");
25887
+ var __dirname2 = import.meta.url ? path6.dirname(fileURLToPath(import.meta.url)) : "";
25888
+ var localXdgOpenPath = path6.join(__dirname2, "xdg-open");
25889
+ var { platform, arch } = process10;
25890
+ var tryEachApp = async (apps, opener) => {
25891
+ if (apps.length === 0) {
25892
+ return;
25893
+ }
25894
+ const errors3 = [];
25895
+ for (const app of apps) {
25896
+ try {
25897
+ return await opener(app);
25898
+ } catch (error40) {
25899
+ errors3.push(error40);
26256
25900
  }
26257
- case EFFORT_CONFIG_ID: {
26258
- const effort = stringValue(request);
26259
- const model = findModel(session.catalog, session.model.model);
26260
- const efforts = supportedEfforts(model);
26261
- if (efforts.length > 0 && !efforts.some((option) => option.reasoningEffort === effort)) {
26262
- throw RequestError.invalidParams({ configId: request.configId }, `Model ${session.model.model} does not support effort "${effort}"`);
26263
- }
26264
- session.model = { ...session.model, effort };
26265
- return;
25901
+ }
25902
+ throw new AggregateError(errors3, "Failed to open in all supported apps");
25903
+ };
25904
+ var baseOpen = async (options) => {
25905
+ options = {
25906
+ wait: false,
25907
+ background: false,
25908
+ newInstance: false,
25909
+ allowNonzeroExitCode: false,
25910
+ ...options
25911
+ };
25912
+ const isFallbackAttempt = options[fallbackAttemptSymbol] === true;
25913
+ delete options[fallbackAttemptSymbol];
25914
+ if (Array.isArray(options.app)) {
25915
+ return tryEachApp(options.app, (singleApp) => baseOpen({
25916
+ ...options,
25917
+ app: singleApp,
25918
+ [fallbackAttemptSymbol]: true
25919
+ }));
25920
+ }
25921
+ let { name: app, arguments: appArguments = [] } = options.app ?? {};
25922
+ appArguments = [...appArguments];
25923
+ if (Array.isArray(app)) {
25924
+ return tryEachApp(app, (appName) => baseOpen({
25925
+ ...options,
25926
+ app: {
25927
+ name: appName,
25928
+ arguments: appArguments
25929
+ },
25930
+ [fallbackAttemptSymbol]: true
25931
+ }));
25932
+ }
25933
+ if (app === "browser" || app === "browserPrivate") {
25934
+ const ids = {
25935
+ "com.google.chrome": "chrome",
25936
+ "google-chrome.desktop": "chrome",
25937
+ "com.brave.browser": "brave",
25938
+ "org.mozilla.firefox": "firefox",
25939
+ "firefox.desktop": "firefox",
25940
+ "com.microsoft.msedge": "edge",
25941
+ "com.microsoft.edge": "edge",
25942
+ "com.microsoft.edgemac": "edge",
25943
+ "microsoft-edge.desktop": "edge",
25944
+ "com.apple.safari": "safari"
25945
+ };
25946
+ const flags = {
25947
+ chrome: "--incognito",
25948
+ brave: "--incognito",
25949
+ firefox: "--private-window",
25950
+ edge: "--inPrivate"
25951
+ };
25952
+ let browser;
25953
+ if (is_wsl_default) {
25954
+ const progId = await wslDefaultBrowser();
25955
+ const browserInfo = _windowsBrowserProgIdMap.get(progId);
25956
+ browser = browserInfo ?? {};
25957
+ } else {
25958
+ browser = await defaultBrowser2();
26266
25959
  }
26267
- case FAST_MODE_CONFIG_ID: {
26268
- if (request.type !== "boolean" || typeof request.value !== "boolean") {
26269
- throw RequestError.invalidParams({ configId: request.configId }, "fast_mode expects a boolean value");
25960
+ if (Object.hasOwn(ids, browser.id)) {
25961
+ const browserName = ids[browser.id.toLowerCase()];
25962
+ if (browserName === undefined) {
25963
+ throw new Error(`${browser.name} is not supported as a default browser`);
26270
25964
  }
26271
- if (request.value && !modelSupportsFast(findModel(session.catalog, session.model.model))) {
26272
- throw RequestError.invalidParams({ configId: request.configId }, `Model ${session.model.model} does not support fast mode`);
25965
+ if (app === "browserPrivate") {
25966
+ if (browserName === "safari") {
25967
+ throw new Error("Safari doesn't support opening in private mode via command line");
25968
+ }
25969
+ appArguments.push(flags[browserName]);
26273
25970
  }
26274
- session.fastMode = request.value;
26275
- return;
26276
- }
26277
- case COLLABORATION_MODE_CONFIG_ID: {
26278
- const mode = parseCollaborationMode(stringValue(request));
26279
- if (!mode)
26280
- throw RequestError.invalidParams({ configId: request.configId }, `Unknown collaboration mode "${String(request.value)}"`);
26281
- await codex.threadSettingsUpdate({
26282
- threadId: session.id,
26283
- collaborationMode: {
26284
- mode,
26285
- settings: { model: findModel(session.catalog, session.model.model)?.model ?? session.model.model, reasoning_effort: session.model.effort, developer_instructions: null }
25971
+ return baseOpen({
25972
+ ...options,
25973
+ app: {
25974
+ name: apps[browserName],
25975
+ arguments: appArguments
26286
25976
  }
26287
25977
  });
26288
- session.collaborationMode = mode;
26289
- return;
26290
25978
  }
26291
- default:
26292
- throw RequestError.invalidParams({ configId: request.configId }, `Unknown config option "${request.configId}"`);
25979
+ throw new Error(`${browser.name} is not supported as a default browser`);
26293
25980
  }
26294
- }
26295
- function stringValue(request) {
26296
- if (typeof request.value !== "string" || request.value.length === 0) {
26297
- throw RequestError.invalidParams({ configId: request.configId }, `Config option "${request.configId}" expects a string value`);
25981
+ let command;
25982
+ const cliArguments = [];
25983
+ const childProcessOptions = {};
25984
+ let shouldUseWindowsInWsl = false;
25985
+ if (is_wsl_default && !isInsideContainer() && !is_in_ssh_default && !app) {
25986
+ shouldUseWindowsInWsl = await canAccessPowerShell();
26298
25987
  }
26299
- return request.value;
26300
- }
26301
-
26302
- // src/agent/history.ts
26303
- function historyUpdates(turns) {
26304
- const updates = [];
26305
- for (const turn of turns) {
26306
- for (const item of turn.items) {
26307
- updates.push(...itemSnapshot(item));
25988
+ if (platform === "darwin") {
25989
+ command = "open";
25990
+ if (options.wait) {
25991
+ cliArguments.push("--wait-apps");
26308
25992
  }
26309
- }
26310
- return updates;
26311
- }
26312
- function historyTitle(thread, turns) {
26313
- const explicit = thread.name?.trim();
26314
- if (explicit)
26315
- return explicit;
26316
- for (const turn of turns) {
26317
- for (const item of turn.items) {
26318
- if (item.type !== "userMessage")
26319
- continue;
26320
- const text2 = item.content.filter((input) => input.type === "text").map((input) => input.text.trim()).find((part) => part.length > 0);
26321
- if (text2)
26322
- return firstLine(text2);
25993
+ if (options.background) {
25994
+ cliArguments.push("--background");
25995
+ }
25996
+ if (options.newInstance) {
25997
+ cliArguments.push("--new");
25998
+ }
25999
+ if (app) {
26000
+ cliArguments.push("-a", app);
26001
+ }
26002
+ } else if (platform === "win32" || shouldUseWindowsInWsl) {
26003
+ command = await powerShellPath2();
26004
+ cliArguments.push(...executePowerShell2.argumentsPrefix);
26005
+ if (!is_wsl_default) {
26006
+ childProcessOptions.windowsVerbatimArguments = true;
26007
+ }
26008
+ if (is_wsl_default && options.target) {
26009
+ options.target = await convertWslPathToWindows(options.target);
26010
+ }
26011
+ const encodedArguments = ["$ProgressPreference = 'SilentlyContinue';", "Start"];
26012
+ if (options.wait) {
26013
+ encodedArguments.push("-Wait");
26014
+ }
26015
+ if (app) {
26016
+ encodedArguments.push(executePowerShell2.escapeArgument(app));
26017
+ if (options.target) {
26018
+ appArguments.push(options.target);
26019
+ }
26020
+ } else if (options.target) {
26021
+ encodedArguments.push(executePowerShell2.escapeArgument(options.target));
26022
+ }
26023
+ if (appArguments.length > 0) {
26024
+ appArguments = appArguments.map((argument) => executePowerShell2.escapeArgument(argument));
26025
+ encodedArguments.push("-ArgumentList", appArguments.join(","));
26026
+ }
26027
+ options.target = executePowerShell2.encodeCommand(encodedArguments.join(" "));
26028
+ if (!options.wait) {
26029
+ childProcessOptions.stdio = "ignore";
26030
+ }
26031
+ if (is_wsl_default) {
26032
+ childProcessOptions.cwd = path6.dirname(command);
26033
+ }
26034
+ } else {
26035
+ if (app) {
26036
+ command = app;
26037
+ } else {
26038
+ const isBundled = !__dirname2 || __dirname2 === "/";
26039
+ let exeLocalXdgOpen = false;
26040
+ try {
26041
+ await fs6.access(localXdgOpenPath, fsConstants2.X_OK);
26042
+ exeLocalXdgOpen = true;
26043
+ } catch {}
26044
+ const useSystemXdgOpen = process10.versions.electron ?? (platform === "android" || isBundled || !exeLocalXdgOpen);
26045
+ command = useSystemXdgOpen ? "xdg-open" : localXdgOpenPath;
26046
+ }
26047
+ if (appArguments.length > 0) {
26048
+ cliArguments.push(...appArguments);
26049
+ }
26050
+ if (!options.wait) {
26051
+ childProcessOptions.stdio = "ignore";
26052
+ childProcessOptions.detached = true;
26323
26053
  }
26324
26054
  }
26325
- const preview = thread.preview.trim();
26326
- return preview.length > 0 ? firstLine(preview) : null;
26327
- }
26328
- function firstLine(text2) {
26329
- const line = text2.split(/\r?\n/).map((part) => part.trim()).find((part) => part.length > 0) ?? text2;
26330
- return line.length > 120 ? `${line.slice(0, 117)}...` : line;
26331
- }
26332
-
26333
- // src/agent/providers.ts
26334
- import { readFileSync } from "fs";
26335
- import os2 from "os";
26336
- import path6 from "path";
26337
- var OPENAI_PROVIDER_ID = "openai";
26338
- var GATEWAY_MODEL_PROVIDER = "custom-gateway";
26339
- var DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
26340
- var GATEWAY_ID_PATTERN = /^[a-z0-9-]+$/;
26341
- function gatewayModelId(connectionId, modelId) {
26342
- return `gateway:${encodeURIComponent(connectionId)}:${encodeURIComponent(modelId)}`;
26343
- }
26344
-
26345
- class ProviderRouting {
26346
- baseConfig;
26347
- configuredProvider;
26348
- gateways = new Map;
26349
- constructor(baseConfig, configuredProvider) {
26350
- this.baseConfig = baseConfig;
26351
- this.configuredProvider = configuredProvider;
26055
+ if (platform === "darwin" && appArguments.length > 0) {
26056
+ cliArguments.push("--args", ...appArguments);
26352
26057
  }
26353
- copy() {
26354
- const copy = new ProviderRouting(this.baseConfig, this.configuredProvider);
26355
- copy.gateways = new Map(this.gateways);
26356
- return copy;
26058
+ if (options.target) {
26059
+ cliArguments.push(options.target);
26357
26060
  }
26358
- get active() {
26359
- return this.gateways.values().next().value ?? null;
26061
+ const subprocess = childProcess4.spawn(command, cliArguments, childProcessOptions);
26062
+ if (options.wait) {
26063
+ return new Promise((resolve, reject) => {
26064
+ subprocess.once("error", reject);
26065
+ subprocess.once("close", (exitCode) => {
26066
+ if (!options.allowNonzeroExitCode && exitCode !== 0) {
26067
+ reject(new Error(`Exited with code ${exitCode}`));
26068
+ return;
26069
+ }
26070
+ resolve(subprocess);
26071
+ });
26072
+ });
26073
+ }
26074
+ if (isFallbackAttempt) {
26075
+ return new Promise((resolve, reject) => {
26076
+ subprocess.once("error", reject);
26077
+ subprocess.once("spawn", () => {
26078
+ subprocess.once("close", (exitCode) => {
26079
+ subprocess.off("error", reject);
26080
+ if (exitCode !== 0) {
26081
+ reject(new Error(`Exited with code ${exitCode}`));
26082
+ return;
26083
+ }
26084
+ subprocess.unref();
26085
+ resolve(subprocess);
26086
+ });
26087
+ });
26088
+ });
26089
+ }
26090
+ subprocess.unref();
26091
+ return new Promise((resolve, reject) => {
26092
+ subprocess.once("error", reject);
26093
+ subprocess.once("spawn", () => {
26094
+ subprocess.off("error", reject);
26095
+ resolve(subprocess);
26096
+ });
26097
+ });
26098
+ };
26099
+ var open = (target, options) => {
26100
+ if (typeof target !== "string") {
26101
+ throw new TypeError("Expected a `target`");
26360
26102
  }
26361
- gateway(id) {
26362
- return id === null ? null : this.gateways.get(id) ?? null;
26103
+ return baseOpen({
26104
+ ...options,
26105
+ target
26106
+ });
26107
+ };
26108
+ function detectArchBinary(binary) {
26109
+ if (typeof binary === "string" || Array.isArray(binary)) {
26110
+ return binary;
26363
26111
  }
26364
- hasGateway(id) {
26365
- return id !== null && this.gateways.has(id);
26112
+ const { [arch]: archBinary } = binary;
26113
+ if (!archBinary) {
26114
+ throw new Error(`${arch} is not supported`);
26366
26115
  }
26367
- list() {
26368
- const first = this.active;
26369
- const routed = first !== null && first.mode === "route";
26370
- const summary = (gateway) => ({ id: gateway.id, name: gateway.name, baseUrl: gateway.baseUrl, models: gateway.models.map((model) => model.id) });
26371
- return {
26372
- providers: [{
26373
- providerId: OPENAI_PROVIDER_ID,
26374
- supported: ["openai"],
26375
- required: false,
26376
- current: routed && first ? { apiType: "openai", baseUrl: first.baseUrl } : { apiType: "openai", baseUrl: this.nativeBaseUrl() },
26377
- ...first && !routed ? { _meta: { codex: { mode: "catalog", gateway: summary(first), gateways: [...this.gateways.values()].map(summary) } } } : {}
26378
- }]
26379
- };
26116
+ return archBinary;
26117
+ }
26118
+ function detectPlatformBinary({ [platform]: platformBinary }, { wsl } = {}) {
26119
+ if (wsl && is_wsl_default) {
26120
+ return detectArchBinary(wsl);
26380
26121
  }
26381
- set(request) {
26382
- if (request.providerId !== OPENAI_PROVIDER_ID) {
26383
- throw RequestError.invalidParams({ providerId: request.providerId }, `Unknown providerId "${request.providerId}"; only "${OPENAI_PROVIDER_ID}" is configurable`);
26384
- }
26385
- if (request.apiType !== "openai") {
26386
- throw RequestError.invalidParams({ apiType: request.apiType }, `Codex only speaks the OpenAI protocol; got apiType "${request.apiType}"`);
26387
- }
26388
- const baseUrl = typeof request.baseUrl === "string" ? request.baseUrl.trim() : "";
26389
- if (!/^https?:\/\//.test(baseUrl)) {
26390
- throw RequestError.invalidParams({ baseUrl: request.baseUrl }, "baseUrl must be an http(s) URL");
26391
- }
26392
- const hints = readHints(request._meta);
26393
- if (hints.id === OPENAI_PROVIDER_ID || hints.id === this.configuredProvider) {
26394
- throw RequestError.invalidParams({ id: hints.id }, `_meta.codex.id "${hints.id}" is Codex's own provider; pick another gateway id`);
26395
- }
26396
- if (hints.mode === "route" || this.mode !== hints.mode)
26397
- this.gateways.clear();
26398
- this.gateways.set(hints.id, {
26399
- id: hints.id,
26400
- baseUrl,
26401
- headers: { ...request.headers ?? {} },
26402
- name: hints.name ?? "Client-configured gateway",
26403
- models: hints.models,
26404
- model: hints.model,
26405
- bearerToken: hints.bearerToken,
26406
- config: hints.config,
26407
- mode: hints.mode,
26408
- catalogEntries: hints.catalogEntries
26409
- });
26122
+ if (!platformBinary) {
26123
+ throw new Error(`${platform} is not supported`);
26410
26124
  }
26411
- get mode() {
26412
- return this.active?.mode ?? "route";
26125
+ return detectArchBinary(platformBinary);
26126
+ }
26127
+ var apps = {
26128
+ browser: "browser",
26129
+ browserPrivate: "browserPrivate"
26130
+ };
26131
+ defineLazyProperty(apps, "chrome", () => detectPlatformBinary({
26132
+ darwin: "google chrome",
26133
+ win32: "chrome",
26134
+ linux: ["google-chrome", "google-chrome-stable", "chromium", "chromium-browser"]
26135
+ }, {
26136
+ wsl: {
26137
+ ia32: "/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe",
26138
+ x64: ["/mnt/c/Program Files/Google/Chrome/Application/chrome.exe", "/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe"]
26413
26139
  }
26414
- gatewayFor(modelId) {
26415
- for (const gateway of this.gateways.values()) {
26416
- if (gateway.models.some((model) => (this.mode === "catalog" ? gatewayModelId(gateway.id, model.id) : model.id) === modelId))
26417
- return gateway;
26418
- }
26419
- return null;
26140
+ }));
26141
+ defineLazyProperty(apps, "brave", () => detectPlatformBinary({
26142
+ darwin: "brave browser",
26143
+ win32: "brave",
26144
+ linux: ["brave-browser", "brave"]
26145
+ }, {
26146
+ wsl: {
26147
+ ia32: "/mnt/c/Program Files (x86)/BraveSoftware/Brave-Browser/Application/brave.exe",
26148
+ x64: ["/mnt/c/Program Files/BraveSoftware/Brave-Browser/Application/brave.exe", "/mnt/c/Program Files (x86)/BraveSoftware/Brave-Browser/Application/brave.exe"]
26420
26149
  }
26421
- gatewayIdFor(modelId) {
26422
- return this.gatewayFor(modelId)?.id ?? null;
26150
+ }));
26151
+ defineLazyProperty(apps, "firefox", () => detectPlatformBinary({
26152
+ darwin: "firefox",
26153
+ win32: String.raw`C:\Program Files\Mozilla Firefox\firefox.exe`,
26154
+ linux: "firefox"
26155
+ }, {
26156
+ wsl: "/mnt/c/Program Files/Mozilla Firefox/firefox.exe"
26157
+ }));
26158
+ defineLazyProperty(apps, "edge", () => detectPlatformBinary({
26159
+ darwin: "microsoft edge",
26160
+ win32: "msedge",
26161
+ linux: ["microsoft-edge", "microsoft-edge-dev"]
26162
+ }, {
26163
+ wsl: "/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe"
26164
+ }));
26165
+ defineLazyProperty(apps, "safari", () => detectPlatformBinary({
26166
+ darwin: "Safari"
26167
+ }));
26168
+ var open_default = open;
26169
+
26170
+ // src/agent/auth.ts
26171
+ var AuthMethodId = {
26172
+ ApiKey: "api-key",
26173
+ ChatGpt: "chat-gpt",
26174
+ ChatGptDeviceCode: "chat-gpt-device-code"
26175
+ };
26176
+ var CODEX_API_KEY_ENV = "CODEX_API_KEY";
26177
+ var OPENAI_API_KEY_ENV = "OPENAI_API_KEY";
26178
+ function authMethods(capabilities, env = process.env) {
26179
+ const methods2 = [{
26180
+ type: "agent",
26181
+ methodId: AuthMethodId.ApiKey,
26182
+ name: "API key",
26183
+ description: `Use the API key from ${CODEX_API_KEY_ENV} or ${OPENAI_API_KEY_ENV}, or pass it in _meta["api-key"].apiKey`
26184
+ }];
26185
+ if (!env["NO_BROWSER"]) {
26186
+ methods2.push({
26187
+ type: "agent",
26188
+ methodId: AuthMethodId.ChatGpt,
26189
+ name: "ChatGPT",
26190
+ description: "Sign in to ChatGPT in your browser"
26191
+ });
26423
26192
  }
26424
- isGatewayModel(modelId) {
26425
- return this.gatewayFor(modelId) !== null;
26193
+ if (capabilities.urlElicitation) {
26194
+ methods2.push({
26195
+ type: "agent",
26196
+ methodId: AuthMethodId.ChatGptDeviceCode,
26197
+ name: "ChatGPT (device code)",
26198
+ description: "Sign in to ChatGPT by opening a verification page and entering a one-time code"
26199
+ });
26426
26200
  }
26427
- gatewayGroups() {
26428
- if (this.mode !== "catalog")
26429
- return [];
26430
- return [...this.gateways.values()].filter((gateway) => gateway.models.length > 0).map((gateway) => ({ id: gateway.id, name: gateway.name, modelIds: gateway.models.map((model) => gatewayModelId(gateway.id, model.id)) }));
26201
+ return methods2;
26202
+ }
26203
+ async function login(codex, link, request, requestId, env = process.env) {
26204
+ switch (request.methodId) {
26205
+ case AuthMethodId.ApiKey:
26206
+ return await loginWithApiKey(codex, request, env);
26207
+ case AuthMethodId.ChatGpt:
26208
+ return await loginWithChatGpt(codex);
26209
+ case AuthMethodId.ChatGptDeviceCode:
26210
+ return await loginWithDeviceCode(codex, link, requestId);
26211
+ default:
26212
+ throw RequestError.invalidParams({ methodId: request.methodId }, `Unknown auth method "${request.methodId}"; expected one of ${Object.values(AuthMethodId).join(", ")}`);
26431
26213
  }
26432
- selectionId(modelId, provider) {
26433
- return modelId !== null && this.mode === "catalog" && this.hasGateway(provider) ? gatewayModelId(provider, modelId) : modelId;
26214
+ }
26215
+ async function loginWithApiKey(codex, request, env) {
26216
+ const meta = request._meta?.["api-key"];
26217
+ const fromMeta = typeof meta === "object" && meta !== null ? meta.apiKey : undefined;
26218
+ const apiKey = typeof fromMeta === "string" && fromMeta.trim().length > 0 ? fromMeta.trim() : env[CODEX_API_KEY_ENV]?.trim() || env[OPENAI_API_KEY_ENV]?.trim();
26219
+ if (!apiKey) {
26220
+ throw RequestError.invalidParams({ envVars: [CODEX_API_KEY_ENV, OPENAI_API_KEY_ENV] }, `No API key: set ${CODEX_API_KEY_ENV} or ${OPENAI_API_KEY_ENV}, or pass _meta["api-key"].apiKey`);
26434
26221
  }
26435
- upstreamModel(selectionId) {
26436
- if (selectionId === null || !selectionId.startsWith("gateway:"))
26437
- return selectionId;
26438
- const gateway = this.gatewayFor(selectionId);
26439
- const model = gateway?.models.find((model2) => gatewayModelId(gateway.id, model2.id) === selectionId);
26440
- if (!model)
26441
- throw RequestError.invalidParams({ model: selectionId }, "The selected provider connection or model is unavailable");
26442
- return model.id;
26222
+ await codex.withNotification("account/login/completed", async (completed) => {
26223
+ await codex.accountLogin({ type: "apiKey", apiKey });
26224
+ const result = await completed;
26225
+ if (!result.success)
26226
+ throw RequestError.authRequired({ error: result.error }, result.error ?? "API key login failed");
26227
+ });
26228
+ }
26229
+ async function loginWithChatGpt(codex) {
26230
+ const account = await codex.accountRead({ refreshToken: true });
26231
+ if (account.account?.type === "chatgpt")
26232
+ return;
26233
+ await codex.withNotification("account/login/completed", async (completed) => {
26234
+ const started = await codex.accountLogin({ type: "chatgpt" });
26235
+ if (started.type !== "chatgpt")
26236
+ throw RequestError.internalError({ type: started.type }, "Unexpected login response");
26237
+ logger.log("opening browser for ChatGPT login", { loginId: started.loginId });
26238
+ await open_default(started.authUrl);
26239
+ const result = await completed;
26240
+ if (!result.success)
26241
+ throw RequestError.authRequired({ error: result.error }, result.error ?? "ChatGPT login failed");
26242
+ });
26243
+ }
26244
+ async function loginWithDeviceCode(codex, link, requestId) {
26245
+ const account = await codex.accountRead({ refreshToken: true });
26246
+ if (account.account?.type === "chatgpt")
26247
+ return;
26248
+ await codex.withNotification("account/login/completed", async (completed) => {
26249
+ const started = await codex.accountLogin({ type: "chatgptDeviceCode" });
26250
+ if (started.type !== "chatgptDeviceCode")
26251
+ throw RequestError.internalError({ type: started.type }, "Unexpected login response");
26252
+ const elicitation = link.request(methods.client.elicitation.create, {
26253
+ mode: "url",
26254
+ requestId,
26255
+ elicitationId: started.loginId,
26256
+ url: started.verificationUrl,
26257
+ message: `Sign in to ChatGPT and enter this code: ${started.userCode}`
26258
+ });
26259
+ elicitation.catch(() => {});
26260
+ const first = await Promise.race([
26261
+ completed.then((result2) => ({ kind: "completed", result: result2 })),
26262
+ elicitation.then((response) => ({ kind: "elicitation", response }))
26263
+ ]);
26264
+ if (first.kind === "elicitation" && !CreateElicitationResponse.isAccept(first.response)) {
26265
+ await codex.accountLoginCancel({ loginId: started.loginId });
26266
+ throw RequestError.authRequired(undefined, "ChatGPT device code login was cancelled");
26267
+ }
26268
+ const result = first.kind === "completed" ? first.result : await completed;
26269
+ await link.notify(methods.client.elicitation.complete, { elicitationId: started.loginId });
26270
+ if (!result.success)
26271
+ throw RequestError.authRequired({ error: result.error }, result.error ?? "ChatGPT login failed");
26272
+ });
26273
+ }
26274
+ async function logout(codex) {
26275
+ await codex.withNotification("account/updated", async (updated) => {
26276
+ await codex.accountLogout();
26277
+ await updated;
26278
+ });
26279
+ }
26280
+
26281
+ // src/agent/clientSession.ts
26282
+ class ClientSession {
26283
+ sessionId;
26284
+ link;
26285
+ capabilities;
26286
+ waiting = 0;
26287
+ turnActive = false;
26288
+ turnGeneration = 0;
26289
+ disposed = false;
26290
+ output = new AbortController;
26291
+ terminalAttempted = false;
26292
+ messageId;
26293
+ get signal() {
26294
+ return this.output.signal;
26443
26295
  }
26444
- disable(request) {
26445
- if (request.providerId !== OPENAI_PROVIDER_ID)
26296
+ constructor(sessionId, link, capabilities) {
26297
+ this.sessionId = sessionId;
26298
+ this.link = link;
26299
+ this.capabilities = capabilities;
26300
+ }
26301
+ async update(update) {
26302
+ if (this.disposed)
26446
26303
  return;
26447
- const id = readGatewayId(request._meta);
26448
- if (id === null)
26449
- this.gateways.clear();
26450
- else
26451
- this.gateways.delete(id);
26304
+ await abortable(this.link.notify(methods.client.session.update, { sessionId: this.sessionId, update }), this.output.signal);
26452
26305
  }
26453
- modelProvider() {
26454
- return this.defaultGatewayId() ?? this.configuredProvider;
26306
+ async updateAll(updates) {
26307
+ for (const update of updates)
26308
+ await this.update(update);
26455
26309
  }
26456
- routesByDefault() {
26457
- return this.defaultGatewayId() !== null;
26310
+ async requestPermission(request, signal) {
26311
+ signal = signal ? AbortSignal.any([signal, this.output.signal]) : this.output.signal;
26312
+ signal.throwIfAborted();
26313
+ return await this.waitingOnClient(() => {
26314
+ signal?.throwIfAborted();
26315
+ const pending = this.link.request(methods.client.session.requestPermission, { sessionId: this.sessionId, ...request }, signal ? { cancellationSignal: signal } : undefined);
26316
+ return signal ? abortable(pending, signal) : pending;
26317
+ }, signal);
26458
26318
  }
26459
- defaultGatewayId() {
26460
- const first = this.active;
26461
- return first !== null && first.mode === "route" ? first.id : null;
26319
+ async requestTool(params, signal) {
26320
+ return await this.link.request("_alwith/tool/call", params, { cancellationSignal: signal });
26462
26321
  }
26463
- threadConfig(gatewayId = this.defaultGatewayId()) {
26464
- const gateway = this.gateway(gatewayId);
26465
- if (!gateway)
26466
- return this.baseConfig;
26467
- return this.applyGateway(this.baseConfig, gateway);
26322
+ async createElicitation(request, signal) {
26323
+ signal = signal ? AbortSignal.any([signal, this.output.signal]) : this.output.signal;
26324
+ signal.throwIfAborted();
26325
+ return await this.waitingOnClient(() => {
26326
+ signal?.throwIfAborted();
26327
+ const pending = this.link.request(methods.client.elicitation.create, request, signal ? { cancellationSignal: signal } : undefined);
26328
+ return signal ? abortable(pending, signal) : pending;
26329
+ }, signal);
26468
26330
  }
26469
- routeConfig(config2, gatewayId) {
26470
- return this.rebind(config2, this, gatewayId);
26331
+ async completeElicitation(elicitationId) {
26332
+ if (this.disposed)
26333
+ return;
26334
+ await abortable(this.link.notify(methods.client.elicitation.complete, { elicitationId }), this.output.signal);
26471
26335
  }
26472
- overrideKeys() {
26473
- const keys = new Set;
26474
- for (const gateway of this.gateways.values())
26475
- for (const key of Object.keys(gateway.config))
26476
- keys.add(key);
26477
- return [...keys];
26336
+ reportRunning(messageId) {
26337
+ this.messageId = messageId;
26338
+ this.terminalAttempted = false;
26339
+ this.turnGeneration += 1;
26340
+ this.turnActive = true;
26341
+ this.waiting = 0;
26342
+ this.state({ sessionUpdate: "state_update", state: "running" });
26478
26343
  }
26479
- rebind(config2, previous, gatewayId = this.defaultGatewayId()) {
26480
- const next = { ...config2 };
26481
- for (const key of previous.overrideKeys()) {
26482
- if (key in this.baseConfig)
26483
- next[key] = this.baseConfig[key];
26484
- else
26485
- delete next[key];
26486
- }
26487
- delete next["model_providers"];
26488
- if (isRecord2(this.baseConfig["model_providers"]))
26489
- next["model_providers"] = this.baseConfig["model_providers"];
26490
- const gateway = this.gateway(gatewayId);
26491
- return gateway ? this.applyGateway(next, gateway) : next;
26344
+ async reportIdle(stopReason, extra, messageId = this.messageId) {
26345
+ if (messageId !== this.messageId)
26346
+ return;
26347
+ if (this.terminalAttempted)
26348
+ return;
26349
+ this.terminalAttempted = true;
26350
+ this.turnActive = false;
26351
+ this.waiting = 0;
26352
+ await this.update(this.correlated({
26353
+ sessionUpdate: "state_update",
26354
+ state: "idle",
26355
+ stopReason,
26356
+ ...extra?.usage === undefined ? {} : { usage: extra.usage },
26357
+ ...extra?._meta === undefined ? {} : { _meta: extra._meta }
26358
+ }));
26492
26359
  }
26493
- applyGateway(config2, gateway) {
26494
- const existing = isRecord2(config2["model_providers"]) ? config2["model_providers"] : {};
26495
- const entry = {
26496
- name: gateway.name,
26497
- base_url: gateway.baseUrl,
26498
- wire_api: "responses",
26499
- http_headers: { "X-Client-Feature-ID": "codex", ...gateway.headers }
26500
- };
26501
- if (gateway.bearerToken !== null)
26502
- entry["experimental_bearer_token"] = gateway.bearerToken;
26503
- return {
26504
- ...config2,
26505
- ...gateway.config,
26506
- model_providers: { ...existing, [gateway.id]: entry }
26507
- };
26360
+ correlated(update) {
26361
+ if (this.messageId === undefined)
26362
+ return update;
26363
+ const meta = update._meta;
26364
+ const alwith = meta?.["alwith"];
26365
+ return { ...update, _meta: { ...meta, alwith: { ...alwith, messageId: this.messageId } } };
26508
26366
  }
26509
- catalog(codexCatalog) {
26510
- const first = this.active;
26511
- if (first === null)
26512
- return codexCatalog;
26513
- if (first.mode === "route") {
26514
- if (first.models.length === 0)
26515
- return codexCatalog;
26516
- return first.models.map((model, index) => gatewayModel(model, index === 0, codexCatalog[0], first.catalogEntries.get(model.id)));
26367
+ async waitingOnClient(operation, signal) {
26368
+ if (this.disposed)
26369
+ throw new Error("Session is closed");
26370
+ const generation = this.turnActive ? this.turnGeneration : null;
26371
+ try {
26372
+ if (generation !== null && this.waiting++ === 0) {
26373
+ const pending = this.state({ sessionUpdate: "state_update", state: "requires_action" });
26374
+ await (signal ? abortable(pending, signal) : pending);
26375
+ }
26376
+ return await operation();
26377
+ } finally {
26378
+ if (this.turnActive && generation === this.turnGeneration && --this.waiting === 0) {
26379
+ const pending = this.state({ sessionUpdate: "state_update", state: "running" });
26380
+ await (signal ? abortable(pending, signal).catch(() => {}) : pending);
26381
+ }
26517
26382
  }
26518
- const own = [];
26519
- for (const gateway of this.gateways.values()) {
26520
- for (const model of gateway.models)
26521
- own.push({ ...gatewayModel(model, false, codexCatalog[0], gateway.catalogEntries.get(model.id)), id: gatewayModelId(gateway.id, model.id) });
26383
+ }
26384
+ async state(update) {
26385
+ try {
26386
+ await this.update(this.correlated(update));
26387
+ } catch (error40) {
26388
+ logger.error("Failed to publish session state", error40, { sessionId: this.sessionId });
26522
26389
  }
26523
- return [...codexCatalog, ...own];
26524
26390
  }
26525
- nativeBaseUrl() {
26526
- const providers = this.baseConfig["model_providers"];
26527
- if (this.configuredProvider && isRecord2(providers)) {
26528
- const entry = providers[this.configuredProvider];
26529
- const baseUrl = isRecord2(entry) ? entry["base_url"] : undefined;
26530
- if (typeof baseUrl === "string" && baseUrl.length > 0)
26531
- return baseUrl;
26391
+ dispose() {
26392
+ this.disposed = true;
26393
+ this.output.abort();
26394
+ this.turnActive = false;
26395
+ this.waiting = 0;
26396
+ }
26397
+ }
26398
+
26399
+ // src/agent/commands.ts
26400
+ var BUILTIN_COMMANDS = [
26401
+ { name: "plan", description: "Toggle plan mode: Codex proposes a plan before making changes" },
26402
+ { name: "review", description: "Review uncommitted changes, or review with custom instructions", input: { type: "text", hint: "optional review instructions" } },
26403
+ { name: "review-branch", description: "Review changes relative to a base branch", input: { type: "text", hint: "branch name" } },
26404
+ { name: "review-commit", description: "Review a specific commit", input: { type: "text", hint: "commit sha" } },
26405
+ { name: "compact", description: "Summarize the conversation to free up context" },
26406
+ { name: "status", description: "Show session configuration, token usage, and rate limits" },
26407
+ { name: "mcp", description: "List configured Model Context Protocol (MCP) servers" },
26408
+ { name: "skills", description: "List available skills" },
26409
+ { name: "logout", description: "Sign out of the Codex account" }
26410
+ ];
26411
+ function parseCommand(prompt) {
26412
+ const first = prompt[0];
26413
+ if (!first || first.type !== "text")
26414
+ return null;
26415
+ const text2 = first.text.trim();
26416
+ if (!text2.startsWith("/"))
26417
+ return null;
26418
+ const body = text2.slice(1).trim();
26419
+ const [name] = body.split(/\s+/);
26420
+ if (!name || name.startsWith("$"))
26421
+ return null;
26422
+ return { name: name.toLowerCase(), rest: body.slice(name.length).trim() };
26423
+ }
26424
+ function resolveCommand(command, session) {
26425
+ switch (command.name) {
26426
+ case "plan":
26427
+ return {
26428
+ kind: "config",
26429
+ configId: COLLABORATION_MODE_CONFIG_ID,
26430
+ value: session.collaborationMode === PLAN_COLLABORATION_MODE ? DEFAULT_COLLABORATION_MODE : PLAN_COLLABORATION_MODE
26431
+ };
26432
+ case "compact":
26433
+ return { kind: "compact" };
26434
+ case "review":
26435
+ return { kind: "review", target: command.rest.length > 0 ? { type: "custom", instructions: command.rest } : { type: "uncommittedChanges" } };
26436
+ case "review-branch":
26437
+ return command.rest.length > 0 ? { kind: "review", target: { type: "baseBranch", branch: command.rest } } : { kind: "message", text: usage("review-branch", "<branch name>") };
26438
+ case "review-commit":
26439
+ return command.rest.length > 0 ? { kind: "review", target: { type: "commit", sha: command.rest, title: null } } : { kind: "message", text: usage("review-commit", "<commit sha>") };
26440
+ case "logout":
26441
+ return { kind: "logout" };
26442
+ case "status":
26443
+ case "mcp":
26444
+ case "skills":
26445
+ return { kind: "message", text: "" };
26446
+ default:
26447
+ return { kind: "prompt" };
26448
+ }
26449
+ }
26450
+ function usage(name, hint) {
26451
+ return `Usage: /${name} ${hint}`;
26452
+ }
26453
+ function availableCommands(skills) {
26454
+ const commands = new Map;
26455
+ for (const builtin of BUILTIN_COMMANDS)
26456
+ commands.set(builtin.name, builtin);
26457
+ for (const entry of skills) {
26458
+ for (const skill of entry.skills) {
26459
+ const name = `$${skill.name}`;
26460
+ if (commands.has(name))
26461
+ continue;
26462
+ commands.set(name, { name, description: skill.shortDescription || skill.description || skill.name });
26532
26463
  }
26533
- return DEFAULT_OPENAI_BASE_URL;
26464
+ }
26465
+ return [...commands.values()];
26466
+ }
26467
+ function skillsMessage(skills) {
26468
+ const lines = skills.flatMap((entry) => entry.skills).map((skill) => {
26469
+ const description = skill.shortDescription || skill.description;
26470
+ return description ? `- ${skill.name}: ${description}` : `- ${skill.name}`;
26471
+ });
26472
+ return lines.length > 0 ? ["Available skills:", ...lines].join(`
26473
+ `) : "No skills configured.";
26474
+ }
26475
+ async function mcpMessage(codex, session) {
26476
+ const servers = await codex.mcpServerStatusList({});
26477
+ const lines = servers.data.map((server) => {
26478
+ const tools = Object.keys(server.tools ?? {}).length;
26479
+ const resources = (server.resources ?? []).length;
26480
+ return `- ${server.name}: ${tools} tools, ${resources} resources, auth=${server.authStatus}`;
26481
+ });
26482
+ const sessionLines = session.mcpServerNames.map((name) => `- ${name}`);
26483
+ return [
26484
+ lines.length > 0 ? "Configured MCP servers:" : "No MCP servers configured.",
26485
+ ...lines,
26486
+ ...sessionLines.length > 0 ? ["", "Session MCP servers:", ...sessionLines] : []
26487
+ ].join(`
26488
+ `);
26489
+ }
26490
+ function statusMessage(session, rateLimits) {
26491
+ const usage2 = session.lastUsage;
26492
+ const lines = [
26493
+ "**Session**",
26494
+ `**Session ID:** ${session.id}`,
26495
+ `**Working directory:** ${session.cwd}`,
26496
+ `**Model:** ${session.model.model} (${session.model.effort})`,
26497
+ `**Mode:** ${session.mode.name}`,
26498
+ `**Collaboration:** ${session.collaborationMode}`,
26499
+ `**Fast mode:** ${session.fastMode ? "on" : "off"}`,
26500
+ `**Account:** ${accountInfo(session)}`,
26501
+ "",
26502
+ "**Usage**",
26503
+ `**Last turn:** ${usage2 ? `${formatTokenCount(usage2.totalTokens)} total (${formatTokenCount(usage2.inputTokens)} input + ${formatTokenCount(usage2.cachedInputTokens)} cached, ${formatTokenCount(usage2.outputTokens)} output)` : "not available yet"}`,
26504
+ `**Context window:** ${usage2 && session.contextWindow ? `${Math.round((session.contextWindow - usage2.totalTokens) / session.contextWindow * 100)}% left (${formatTokenCount(usage2.totalTokens)} / ${formatTokenCount(session.contextWindow)})` : "not available yet"}`,
26505
+ ...rateLimitLines(rateLimits)
26506
+ ];
26507
+ return lines.join(`
26508
+ `);
26509
+ }
26510
+ function accountInfo(session) {
26511
+ const account = session.account;
26512
+ if (!account)
26513
+ return "not logged in";
26514
+ switch (account.type) {
26515
+ case "apiKey":
26516
+ return "API key";
26517
+ case "chatgpt":
26518
+ return `ChatGPT ${account.planType}${account.email ? ` (${account.email})` : ""}`;
26519
+ case "amazonBedrock":
26520
+ return "Amazon Bedrock";
26534
26521
  }
26535
26522
  }
26536
- function readHints(meta) {
26537
- const root = isRecord2(meta) ? meta : {};
26538
- const alwith = isRecord2(root["alwith"]) ? root["alwith"] : {};
26539
- const codex = isRecord2(root["codex"]) ? root["codex"] : {};
26540
- const id = readGatewayId(meta) ?? GATEWAY_MODEL_PROVIDER;
26541
- const models = [];
26542
- if (Array.isArray(alwith["models"])) {
26543
- for (const entry of alwith["models"]) {
26544
- if (!isRecord2(entry) || typeof entry["id"] !== "string" || entry["id"].length === 0)
26523
+ function rateLimitLines(rateLimits) {
26524
+ const lines = [];
26525
+ for (const snapshot of rateLimits.values()) {
26526
+ const prefix = snapshot.limitName ? `${snapshot.limitName} ` : "";
26527
+ for (const window of [snapshot.primary, snapshot.secondary]) {
26528
+ if (!window)
26545
26529
  continue;
26546
- models.push({
26547
- id: entry["id"],
26548
- name: typeof entry["label"] === "string" && entry["label"].length > 0 ? entry["label"] : entry["id"],
26549
- description: typeof entry["description"] === "string" ? entry["description"] : null
26550
- });
26530
+ const label = windowLabel(window.windowDurationMins);
26531
+ const reset = window.resetsAt === null ? "" : ` (resets ${new Date(window.resetsAt * 1000).toLocaleString()})`;
26532
+ lines.push(`**${prefix}${label}:** ${Math.round(100 - window.usedPercent)}% left${reset}`);
26551
26533
  }
26534
+ if (snapshot.credits?.unlimited)
26535
+ lines.push(`**${prefix}Credits:** unlimited`);
26536
+ else if (snapshot.credits?.balance)
26537
+ lines.push(`**${prefix}Credits:** ${snapshot.credits.balance}`);
26552
26538
  }
26553
- const model = typeof alwith["model"] === "string" && alwith["model"].length > 0 ? alwith["model"] : null;
26554
- const name = typeof codex["name"] === "string" && codex["name"].length > 0 ? codex["name"] : null;
26555
- const bearerToken = typeof codex["bearerToken"] === "string" && codex["bearerToken"].length > 0 ? codex["bearerToken"] : null;
26556
- const rawConfig = codex["config"];
26557
- if (rawConfig !== undefined && !isRecord2(rawConfig)) {
26558
- throw RequestError.invalidParams({ config: rawConfig }, "_meta.codex.config must be an object of top-level Codex config keys");
26559
- }
26560
- const config2 = {};
26561
- if (isRecord2(rawConfig)) {
26562
- for (const [key, value] of Object.entries(rawConfig)) {
26563
- if (key === "model_providers")
26564
- throw RequestError.invalidParams({ config: rawConfig }, "_meta.codex.config cannot set model_providers; that entry is derived from baseUrl and headers");
26565
- config2[key] = value;
26539
+ return lines.length > 0 ? lines : ["**Limits:** not available yet"];
26540
+ }
26541
+ function windowLabel(minutes) {
26542
+ if (minutes === null)
26543
+ return "Limit";
26544
+ if (minutes < 60)
26545
+ return `${minutes}m limit`;
26546
+ if (minutes < 1440)
26547
+ return `${Math.round(minutes / 60)}h limit`;
26548
+ if (minutes < 10080)
26549
+ return `${Math.round(minutes / 1440)}d limit`;
26550
+ return "Weekly limit";
26551
+ }
26552
+
26553
+ // src/agent/configOptions.ts
26554
+ function sessionConfigOptions(session) {
26555
+ const model = findModel(session.catalog, session.model.model);
26556
+ const options = [
26557
+ modeConfigOption(session.mode),
26558
+ modelConfigOption(session.catalog, session.model.model, session.gatewayGroups),
26559
+ effortConfigOption(supportedEfforts(model), session.model.effort),
26560
+ collaborationModeConfigOption(session.collaborationMode)
26561
+ ];
26562
+ if (modelSupportsFast(model))
26563
+ options.push(fastModeConfigOption(session.fastMode));
26564
+ return options;
26565
+ }
26566
+ async function applyConfigOption(session, codex, request) {
26567
+ switch (request.configId) {
26568
+ case MODE_CONFIG_ID: {
26569
+ const mode = findAgentMode(stringValue(request));
26570
+ if (!mode)
26571
+ throw RequestError.invalidParams({ configId: request.configId }, `Unknown mode "${String(request.value)}"`);
26572
+ session.mode = mode;
26573
+ return;
26566
26574
  }
26575
+ case MODEL_CONFIG_ID: {
26576
+ const modelId = stringValue(request);
26577
+ const model = findModel(session.catalog, modelId);
26578
+ session.model = { model: modelId, effort: coerceEffort(model, session.model.effort) };
26579
+ if (!modelSupportsFast(model))
26580
+ session.fastMode = false;
26581
+ return;
26582
+ }
26583
+ case EFFORT_CONFIG_ID: {
26584
+ const effort = stringValue(request);
26585
+ const model = findModel(session.catalog, session.model.model);
26586
+ const efforts = supportedEfforts(model);
26587
+ if (efforts.length > 0 && !efforts.some((option) => option.reasoningEffort === effort)) {
26588
+ throw RequestError.invalidParams({ configId: request.configId }, `Model ${session.model.model} does not support effort "${effort}"`);
26589
+ }
26590
+ session.model = { ...session.model, effort };
26591
+ return;
26592
+ }
26593
+ case FAST_MODE_CONFIG_ID: {
26594
+ if (request.type !== "boolean" || typeof request.value !== "boolean") {
26595
+ throw RequestError.invalidParams({ configId: request.configId }, "fast_mode expects a boolean value");
26596
+ }
26597
+ if (request.value && !modelSupportsFast(findModel(session.catalog, session.model.model))) {
26598
+ throw RequestError.invalidParams({ configId: request.configId }, `Model ${session.model.model} does not support fast mode`);
26599
+ }
26600
+ session.fastMode = request.value;
26601
+ return;
26602
+ }
26603
+ case COLLABORATION_MODE_CONFIG_ID: {
26604
+ const mode = parseCollaborationMode(stringValue(request));
26605
+ if (!mode)
26606
+ throw RequestError.invalidParams({ configId: request.configId }, `Unknown collaboration mode "${String(request.value)}"`);
26607
+ await codex.threadSettingsUpdate({
26608
+ threadId: session.id,
26609
+ collaborationMode: {
26610
+ mode,
26611
+ settings: { model: findModel(session.catalog, session.model.model)?.model ?? session.model.model, reasoning_effort: session.model.effort, developer_instructions: null }
26612
+ }
26613
+ });
26614
+ session.collaborationMode = mode;
26615
+ return;
26616
+ }
26617
+ default:
26618
+ throw RequestError.invalidParams({ configId: request.configId }, `Unknown config option "${request.configId}"`);
26567
26619
  }
26568
- const rawMode = codex["mode"];
26569
- if (rawMode !== undefined && rawMode !== "route" && rawMode !== "catalog") {
26570
- throw RequestError.invalidParams({ mode: rawMode }, '_meta.codex.mode must be "route" or "catalog"');
26571
- }
26572
- const mode = rawMode === "catalog" ? "catalog" : "route";
26573
- if (mode === "catalog" && models.length === 0) {
26574
- throw RequestError.invalidParams(undefined, `_meta.codex.mode "catalog" needs _meta.alwith.models: the models offered next to Codex's`);
26575
- }
26576
- const catalogPath = config2["model_catalog_json"];
26577
- const catalogEntries = typeof catalogPath === "string" ? readCatalogFile(catalogPath) : new Map;
26578
- return { id, models, model, name, bearerToken, config: config2, mode, catalogEntries };
26579
26620
  }
26580
- function readGatewayId(meta) {
26581
- const root = isRecord2(meta) ? meta : {};
26582
- const codex = isRecord2(root["codex"]) ? root["codex"] : {};
26583
- const raw = codex["id"];
26584
- if (raw === undefined)
26585
- return null;
26586
- if (typeof raw !== "string" || !GATEWAY_ID_PATTERN.test(raw)) {
26587
- throw RequestError.invalidParams({ id: raw }, "_meta.codex.id must match [a-z0-9-]+");
26621
+ function stringValue(request) {
26622
+ if (typeof request.value !== "string" || request.value.length === 0) {
26623
+ throw RequestError.invalidParams({ configId: request.configId }, `Config option "${request.configId}" expects a string value`);
26588
26624
  }
26589
- return raw;
26625
+ return request.value;
26590
26626
  }
26591
- function readCatalogFile(catalogPath) {
26592
- const resolved = catalogPath.startsWith("~/") ? path6.join(os2.homedir(), catalogPath.slice(2)) : catalogPath;
26593
- let parsed;
26594
- try {
26595
- parsed = JSON.parse(readFileSync(resolved, "utf8"));
26596
- } catch (error40) {
26597
- throw RequestError.invalidParams({ model_catalog_json: catalogPath }, `model_catalog_json could not be read as JSON: ${error40 instanceof Error ? error40.message : String(error40)}`);
26627
+
26628
+ // src/agent/history.ts
26629
+ function historyUpdates(turns) {
26630
+ const updates = [];
26631
+ for (const turn of turns) {
26632
+ for (const item of turn.items) {
26633
+ updates.push(...itemSnapshot(item));
26634
+ }
26598
26635
  }
26599
- const models = isRecord2(parsed) && Array.isArray(parsed["models"]) ? parsed["models"] : null;
26600
- if (models === null)
26601
- throw RequestError.invalidParams({ model_catalog_json: catalogPath }, "model_catalog_json must be {models: [...]} as Codex expects");
26602
- const entries = new Map;
26603
- for (const entry of models) {
26604
- if (!isRecord2(entry) || typeof entry["slug"] !== "string")
26605
- continue;
26606
- const levels = Array.isArray(entry["supported_reasoning_levels"]) ? entry["supported_reasoning_levels"] : [];
26607
- const efforts = [];
26608
- for (const level of levels) {
26609
- if (!isRecord2(level) || typeof level["effort"] !== "string")
26636
+ return updates;
26637
+ }
26638
+ function historyTitle(thread, turns) {
26639
+ const explicit = thread.name?.trim();
26640
+ if (explicit)
26641
+ return explicit;
26642
+ for (const turn of turns) {
26643
+ for (const item of turn.items) {
26644
+ if (item.type !== "userMessage")
26610
26645
  continue;
26611
- efforts.push({ reasoningEffort: level["effort"], description: typeof level["description"] === "string" ? level["description"] : "" });
26646
+ const text2 = item.content.filter((input) => input.type === "text").map((input) => input.text.trim()).find((part) => part.length > 0);
26647
+ if (text2)
26648
+ return firstLine(text2);
26612
26649
  }
26613
- const modalities = Array.isArray(entry["input_modalities"]) ? entry["input_modalities"].filter((value) => value === "text" || value === "image") : null;
26614
- entries.set(entry["slug"], {
26615
- displayName: typeof entry["display_name"] === "string" ? entry["display_name"] : null,
26616
- description: typeof entry["description"] === "string" ? entry["description"] : null,
26617
- efforts,
26618
- defaultEffort: typeof entry["default_reasoning_level"] === "string" ? entry["default_reasoning_level"] : null,
26619
- inputModalities: modalities
26620
- });
26621
26650
  }
26622
- return entries;
26651
+ const preview = thread.preview.trim();
26652
+ return preview.length > 0 ? firstLine(preview) : null;
26623
26653
  }
26624
- function gatewayModel(model, isDefault, template, entry) {
26625
- const efforts = entry && entry.efforts.length > 0 ? entry.efforts : template?.supportedReasoningEfforts ?? [];
26626
- const defaultEffort = entry?.defaultEffort ?? template?.defaultReasoningEffort ?? "medium";
26627
- return {
26628
- id: model.id,
26629
- model: model.id,
26630
- upgrade: null,
26631
- upgradeInfo: null,
26632
- availabilityNux: null,
26633
- displayName: model.name !== model.id ? model.name : entry?.displayName ?? model.name,
26634
- description: model.description ?? entry?.description ?? "",
26635
- modelSpecialty: null,
26636
- hidden: false,
26637
- supportedReasoningEfforts: efforts,
26638
- defaultReasoningEffort: defaultEffort,
26639
- inputModalities: entry?.inputModalities ?? ["text", "image"],
26640
- supportsPersonality: false,
26641
- multiAgentVersion: null,
26642
- additionalSpeedTiers: [],
26643
- serviceTiers: [],
26644
- defaultServiceTier: null,
26645
- availableAccessPrograms: null,
26646
- isDefault
26647
- };
26654
+ function firstLine(text2) {
26655
+ const line = text2.split(/\r?\n/).map((part) => part.trim()).find((part) => part.length > 0) ?? text2;
26656
+ return line.length > 120 ? `${line.slice(0, 117)}...` : line;
26648
26657
  }
26649
26658
 
26650
26659
  // src/agent/session.ts
@@ -28597,7 +28606,8 @@ function threadIdOf(notification) {
28597
28606
  }
28598
28607
 
28599
28608
  // src/codex/process.ts
28600
- import { existsSync } from "fs";
28609
+ import { existsSync, mkdtempSync, readFileSync as readFileSync2, writeFileSync } from "fs";
28610
+ import os3 from "os";
28601
28611
  import path8 from "path";
28602
28612
  import { spawn, spawnSync } from "child_process";
28603
28613
  import { createRequire } from "module";
@@ -28909,7 +28919,9 @@ function startCodexProcess(codexPath, env = process.env) {
28909
28919
  if (probe.error !== undefined)
28910
28920
  throw new Error(`Could not run Codex (${launcher.command}): ${probe.error.message}`);
28911
28921
  assertCodexVersion(probe.stdout.length > 0 ? probe.stdout : probe.stderr);
28912
- const child = launcher.shell ? spawn(`${launcher.command} app-server`, { shell: true, env }) : spawn(launcher.command, [...launcher.prefixArgs, "app-server"], { env });
28922
+ const catalog = mergedModelCatalog(launcher, env);
28923
+ const catalogOverride = catalog === null ? null : `model_catalog_json='${catalog}'`;
28924
+ const child = launcher.shell ? spawn(`${launcher.command}${catalogOverride === null ? "" : ` -c "${catalogOverride}"`} app-server`, { shell: true, env }) : spawn(launcher.command, [...launcher.prefixArgs, ...catalogOverride === null ? [] : ["-c", catalogOverride], "app-server"], { env });
28913
28925
  let stderr = "";
28914
28926
  child.stderr.on("data", (data) => {
28915
28927
  stderr = (stderr + data.toString()).slice(-STDERR_RETAIN_BYTES);
@@ -28934,6 +28946,44 @@ function startCodexProcess(codexPath, env = process.env) {
28934
28946
  exitCode: () => child.exitCode
28935
28947
  };
28936
28948
  }
28949
+ var EXTRA_MODEL_CATALOGS_ENV = "CODEX_ACP_MODEL_CATALOGS";
28950
+ function mergedModelCatalog(launcher, env) {
28951
+ const raw = env[EXTRA_MODEL_CATALOGS_ENV];
28952
+ if (raw === undefined || raw.trim() === "")
28953
+ return null;
28954
+ const files = raw.split(path8.delimiter).filter((file2) => file2.length > 0);
28955
+ const dump = spawnSync(launcher.command, [...launcher.prefixArgs, "debug", "models"], {
28956
+ shell: launcher.shell,
28957
+ env,
28958
+ encoding: "utf8",
28959
+ maxBuffer: 64 * 1024 * 1024
28960
+ });
28961
+ if (dump.error !== undefined || dump.status !== 0) {
28962
+ throw new Error(`codex debug models failed: ${dump.error?.message ?? dump.stderr.trim()}`);
28963
+ }
28964
+ const base = JSON.parse(dump.stdout);
28965
+ if (!Array.isArray(base.models))
28966
+ throw new Error("codex debug models did not return {models: [...]}");
28967
+ const models = base.models;
28968
+ const slugs = new Set(models.map((model) => model["slug"]));
28969
+ for (const file2 of files) {
28970
+ const extra = JSON.parse(readFileSync2(file2, "utf8"));
28971
+ if (!Array.isArray(extra.models))
28972
+ throw new Error(`${EXTRA_MODEL_CATALOGS_ENV}: ${file2} is not {models: [...]}`);
28973
+ for (const entry of extra.models) {
28974
+ if (typeof entry["slug"] !== "string")
28975
+ throw new Error(`${EXTRA_MODEL_CATALOGS_ENV}: ${file2} has an entry without a slug`);
28976
+ if (slugs.has(entry["slug"]))
28977
+ continue;
28978
+ slugs.add(entry["slug"]);
28979
+ models.push({ ...entry, visibility: "hide" });
28980
+ }
28981
+ }
28982
+ const directory = mkdtempSync(path8.join(os3.tmpdir(), "codex-acp-v2-catalog-"));
28983
+ const target = path8.join(directory, "models.json");
28984
+ writeFileSync(target, JSON.stringify({ ...base, models }));
28985
+ return target;
28986
+ }
28937
28987
 
28938
28988
  // src/index.ts
28939
28989
  var CODEX_EXIT_GRACE_MS = 2000;