@nyssance/codex-acp-v2 0.7.1 → 0.7.3

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