dsh-codex-subscription 3.0.0-beta.6 → 3.0.0-beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -12,7 +12,7 @@ import { clientRequestSchema } from "@deepseek-ai/dsh-client-connection";
12
12
  import { lstat, mkdir, open, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
13
13
  import { constants, readFileSync, readdirSync, statSync } from "node:fs";
14
14
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
15
- import { pathToFileURL } from "node:url";
15
+ import { fileURLToPath, pathToFileURL } from "node:url";
16
16
  import { defineTool } from "@deepseek-ai/dsh-tools";
17
17
  import * as dshLlm from "@deepseek-ai/dsh-llm";
18
18
  import { createUserMessage } from "@deepseek-ai/dsh-llm";
@@ -1199,6 +1199,7 @@ const RPC_ENDPOINTS = Object.freeze([
1199
1199
  "account/select",
1200
1200
  "account/remove",
1201
1201
  "usage",
1202
+ "usage/accounts",
1202
1203
  "usage/history",
1203
1204
  "usage/profile",
1204
1205
  "diagnostics",
@@ -1400,7 +1401,9 @@ const SUPPORTED_RUNTIME_VERSIONS = Object.freeze([
1400
1401
  "0.2.0-rc.2",
1401
1402
  "0.2.1-alpha.1",
1402
1403
  "0.2.1-alpha.2",
1403
- "0.2.1-alpha.3"
1404
+ "0.2.1-alpha.3",
1405
+ "0.2.1-alpha.4",
1406
+ "0.2.1-alpha.5"
1404
1407
  ]);
1405
1408
  const require = createRequire(import.meta.url);
1406
1409
  const execute = promisify(execFile);
@@ -2158,9 +2161,36 @@ function inheritedOriginalImageRef(session, assetId) {
2158
2161
  }
2159
2162
  //#endregion
2160
2163
  //#region src/version.js
2161
- const PACKAGE_VERSION = "3.0.0-beta.6";
2164
+ const PACKAGE_VERSION = "3.0.0-beta.8";
2162
2165
  const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
2163
2166
  //#endregion
2167
+ //#region src/http-failure.js
2168
+ const SIGN_IN_RENEW = "ChatGPT sign-in needs to be renewed";
2169
+ const REQUEST_BLOCKED = "ChatGPT blocked this request before it reached your account (HTTP 403); your sign-in is fine, try again later or check your network, proxy or VPN";
2170
+ const CHALLENGE_MARKERS = /just a moment|cf-chl|_cf_chl_opt|attention required|enable javascript and cookies|challenge-platform/iu;
2171
+ /** Whether error text is a Cloudflare / network-protection page (pi-ai keeps only the body of a failed request, not its status). */
2172
+ const isChallengePage = (text) => typeof text === "string" && /^\s*<(?:!doctype|html)/iu.test(text) && CHALLENGE_MARKERS.test(text);
2173
+ /** Whether a 403 response is a Cloudflare / network-protection page rather than an answer from the ChatGPT backend. */
2174
+ async function isBlockedByNetwork(response) {
2175
+ if (response?.status !== 403) return false;
2176
+ try {
2177
+ const header = (name) => String(response.headers?.get?.(name) ?? "");
2178
+ if (/challenge/iu.test(header("cf-mitigated"))) return true;
2179
+ if (!/html/iu.test(header("content-type"))) return false;
2180
+ if (/cloudflare/iu.test(header("server"))) return true;
2181
+ const text = typeof response.clone === "function" ? (await response.clone().text()).slice(0, 8192) : "";
2182
+ return CHALLENGE_MARKERS.test(text);
2183
+ } catch {
2184
+ return false;
2185
+ }
2186
+ }
2187
+ /** The error message for a 401 or 403 answer, or undefined for any other status. */
2188
+ async function authFailureMessage(response) {
2189
+ if (response?.status === 401) return SIGN_IN_RENEW;
2190
+ if (response?.status !== 403) return void 0;
2191
+ return await isBlockedByNetwork(response) ? REQUEST_BLOCKED : SIGN_IN_RENEW;
2192
+ }
2193
+ //#endregion
2164
2194
  //#region src/codex-images.js
2165
2195
  const CODEX_IMAGE_TOOL_NAME = "codex_image_generate";
2166
2196
  const CODEX_IMAGE_GENERATION_URL = "https://chatgpt.com/backend-api/codex/images/generations";
@@ -2663,7 +2693,8 @@ function createCodexImageTool(options) {
2663
2693
  throw new Error(`Codex image ${editing ? "edit" : "generation"} request failed${requestFailureReason(error)}`, { cause: error });
2664
2694
  }
2665
2695
  if (!response.ok) {
2666
- if (response.status === 401 || response.status === 403) throw new Error("ChatGPT sign-in needs to be renewed");
2696
+ const authFailure = await authFailureMessage(response);
2697
+ if (authFailure !== void 0) throw new Error(authFailure);
2667
2698
  const requestId = supportId(response);
2668
2699
  if (response.status === 429) throw new Error(`${await limitMessage(response)}${requestId}`);
2669
2700
  throw new Error(`Codex image ${editing ? "edit" : "generation"} failed (HTTP ${response.status})${requestId}`);
@@ -3969,7 +4000,7 @@ const emptyTurn = () => ({
3969
4000
  completedAt: null,
3970
4001
  durationMs: null
3971
4002
  });
3972
- const textOf = (content) => (content ?? []).map((block) => block?.type === "text" ? block.text : "").filter(Boolean).join("\n");
4003
+ const textOf$1 = (content) => (content ?? []).map((block) => block?.type === "text" ? block.text : "").filter(Boolean).join("\n");
3973
4004
  const inputText = (input) => (Array.isArray(input) ? input : []).map((part) => part?.type === "text" ? part.text : "").filter(Boolean).join("\n");
3974
4005
  /** Skills the phone picked, written the way DSH invokes a skill: a /name gesture ahead of the text. */
3975
4006
  const skillGestures = (input) => (Array.isArray(input) ? input : []).filter((part) => part?.type === "skill" && /^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(String(part.name))).map((part) => `/${part.name}`);
@@ -3996,7 +4027,7 @@ function parseArguments(value) {
3996
4027
  }
3997
4028
  }
3998
4029
  /** What the person typed: DSH appends its own system reminders (skills, context) to user messages. */
3999
- const visibleText = (content) => textOf(content).replace(/<system-reminder>[\s\S]*?<\/system-reminder>/gu, "").trim();
4030
+ const visibleText = (content) => textOf$1(content).replace(/<system-reminder>[\s\S]*?<\/system-reminder>/gu, "").trim();
4000
4031
  /** Text of a user-role message the person wrote; DSH's own injected ones (runtime context, notices) carry another source kind. */
4001
4032
  const typedText = (message) => message.role === "user" && (message.source === void 0 || message.source?.kind === "user") ? visibleText(message.content) : "";
4002
4033
  const lines = (text) => String(text ?? "").split("\n");
@@ -4101,7 +4132,7 @@ function finishTool(item, message, imageUrls) {
4101
4132
  const failed = Boolean(message.isError);
4102
4133
  item.status = failed ? "failed" : "completed";
4103
4134
  if (item.type === "commandExecution") {
4104
- item.aggregatedOutput = textOf(message.content);
4135
+ item.aggregatedOutput = textOf$1(message.content);
4105
4136
  item.exitCode = failed ? 1 : 0;
4106
4137
  } else if (item.type === "imageGeneration") {
4107
4138
  const url = imageBlocks(message).map((block) => imageUrls.get(block.attachment.attachmentId)).find(Boolean);
@@ -4111,7 +4142,7 @@ function finishTool(item, message, imageUrls) {
4111
4142
  item.contentItems = toolOutput(message, imageUrls);
4112
4143
  item.success = !failed;
4113
4144
  } else if (item.type === "mcpToolCall") {
4114
- const text = textOf(message.content);
4145
+ const text = textOf$1(message.content);
4115
4146
  if (failed) item.error = { message: text.slice(0, 500) || "The connector tool failed" };
4116
4147
  else item.result = {
4117
4148
  content: [{
@@ -4125,7 +4156,7 @@ function finishTool(item, message, imageUrls) {
4125
4156
  const imageBlocks = (message) => (message.content ?? []).filter((block) => block?.type === "image" && block.attachment);
4126
4157
  const toolOutput = (message, imageUrls) => [{
4127
4158
  type: "inputText",
4128
- text: textOf(message.content)
4159
+ text: textOf$1(message.content)
4129
4160
  }, ...imageBlocks(message).map((block) => imageUrls.get(block.attachment.attachmentId)).filter(Boolean).map((imageUrl) => ({
4130
4161
  type: "inputImage",
4131
4162
  imageUrl
@@ -4242,7 +4273,7 @@ function thread({ id, title, preview, cwd, createdAt, updatedAt, running, turns
4242
4273
  */
4243
4274
  const PHONE_RETURN_MS = 12e4;
4244
4275
  const ADOPT_WITHIN_MS = 6e5;
4245
- function createDshRemoteControl({ controller, agents, permissions = () => void 0, workspaces = () => void 0, attachments = () => void 0, usage = () => void 0, usageProfile = () => void 0, projections = () => void 0, skills = () => void 0, llm = () => void 0, trace = () => {}, phoneReturnMs = PHONE_RETURN_MS, userAgent }) {
4276
+ function createDshRemoteControl({ controller, agents, permissions = () => void 0, workspaces = () => void 0, attachments = () => void 0, usage = () => void 0, usageProfile = () => void 0, projections = () => void 0, skills = () => void 0, commands = () => void 0, llm = () => void 0, trace = () => {}, phoneReturnMs = PHONE_RETURN_MS, userAgent }) {
4246
4277
  const subscribers = /* @__PURE__ */ new Map();
4247
4278
  const active = /* @__PURE__ */ new Map();
4248
4279
  const queues = /* @__PURE__ */ new Map();
@@ -4314,6 +4345,16 @@ function createDshRemoteControl({ controller, agents, permissions = () => void 0
4314
4345
  reasoningEffort: agent.options.reasoningEffort
4315
4346
  } : void 0;
4316
4347
  };
4348
+ const directoryOf = (session) => {
4349
+ try {
4350
+ const events = typeof session?.snapshotEvents === "function" ? session.snapshotEvents() : session?.eventsSnapshot;
4351
+ for (let index = (events?.length ?? 0) - 1; index >= 0; index -= 1) {
4352
+ const cwd = events[index]?.type === "working-directory/change" ? events[index].data?.cwd : void 0;
4353
+ if (typeof cwd === "string" && cwd !== "") return cwd;
4354
+ }
4355
+ } catch {}
4356
+ return session?.meta?.cwd;
4357
+ };
4317
4358
  const sameSelection = (a, b) => a?.provider === b?.provider && a?.model === b?.model && a?.reasoningEffort === b?.reasoningEffort;
4318
4359
  const selection = (agent) => {
4319
4360
  const saved = recorded(agent);
@@ -4435,16 +4476,17 @@ function createDshRemoteControl({ controller, agents, permissions = () => void 0
4435
4476
  const messages = session.deriveMessages();
4436
4477
  const first = messages.find((message) => typedText(message) !== "");
4437
4478
  const imageUrls = await imageUrlsOf(messages);
4479
+ const cwd = directoryOf(session);
4438
4480
  return {
4439
4481
  agent,
4440
4482
  thread: thread({
4441
4483
  id,
4442
- cwd: session.meta?.cwd,
4484
+ cwd,
4443
4485
  createdAt: session.meta?.createdAt,
4444
4486
  running: active.has(id),
4445
4487
  projectId: projectIdOf(id),
4446
4488
  preview: first ? visibleText(first.content).slice(0, 200) : "",
4447
- turns: withLiveTurn(id, projectTurns(messages, session.meta?.createdAt, imageUrls, session.meta?.cwd))
4489
+ turns: withLiveTurn(id, projectTurns(messages, session.meta?.createdAt, imageUrls, cwd))
4448
4490
  })
4449
4491
  };
4450
4492
  };
@@ -5051,6 +5093,92 @@ function createDshRemoteControl({ controller, agents, permissions = () => void 0
5051
5093
  "turn/start": (params, { notify }) => titles.has(params?.threadId) ? titleTurn(params, notify) : startTurn(params, notify),
5052
5094
  "turn/interrupt": (params) => {
5053
5095
  service().cancel({ sessionId: params?.threadId });
5096
+ active.get(params?.threadId)?.abort?.abort();
5097
+ return {};
5098
+ },
5099
+ "thread/compact/start": async (params, { notify }) => {
5100
+ const id = params?.threadId;
5101
+ const registry = commands();
5102
+ const agent = await agentFor(id);
5103
+ if (typeof registry?.execute !== "function" || registry.find?.(agent, "compact") === void 0) throw new RpcError(-32601, "This DSH version cannot compact a conversation on request");
5104
+ if (active.has(id)) throw new RpcError(-32602, "A turn is already running in this conversation");
5105
+ subscribe(id, notify);
5106
+ const turn = emptyTurn();
5107
+ const abort = new AbortController();
5108
+ active.set(id, {
5109
+ turn,
5110
+ ended: void 0,
5111
+ abort
5112
+ });
5113
+ emit(id, "turn/started", {
5114
+ threadId: id,
5115
+ turn
5116
+ });
5117
+ emit(id, "thread/status/changed", {
5118
+ threadId: id,
5119
+ status: {
5120
+ type: "active",
5121
+ activeFlags: []
5122
+ }
5123
+ });
5124
+ (async () => {
5125
+ try {
5126
+ const result = (await registry.execute(agent, "/compact", [], abort.signal))?.result;
5127
+ if (result?.kind === "error") throw new Error(result.text || "Compaction failed");
5128
+ if (result?.sourceEventSeq !== void 0 && turn.items.length === 0) {
5129
+ const item = {
5130
+ type: "contextCompaction",
5131
+ id: randomUUID()
5132
+ };
5133
+ turn.items.push(item);
5134
+ emit(id, "item/started", {
5135
+ threadId: id,
5136
+ turnId: turn.id,
5137
+ item,
5138
+ startedAtMs: Date.now()
5139
+ });
5140
+ emit(id, "item/completed", {
5141
+ threadId: id,
5142
+ turnId: turn.id,
5143
+ item,
5144
+ completedAtMs: Date.now()
5145
+ });
5146
+ }
5147
+ turn.status = "completed";
5148
+ } catch (error) {
5149
+ turn.status = abort.signal.aborted ? "interrupted" : "failed";
5150
+ if (turn.status === "failed") {
5151
+ turn.error = {
5152
+ message: String(error?.message ?? "Compaction failed").slice(0, 500),
5153
+ codexErrorInfo: null,
5154
+ additionalDetails: null
5155
+ };
5156
+ emit(id, "error", {
5157
+ threadId: id,
5158
+ turnId: turn.id,
5159
+ willRetry: false,
5160
+ error: turn.error
5161
+ });
5162
+ }
5163
+ }
5164
+ turn.completedAt = seconds(Date.now());
5165
+ active.delete(id);
5166
+ compactions.delete(id);
5167
+ const usageNow = tokenUsage(agent.session);
5168
+ if (usageNow) emit(id, "thread/tokenUsage/updated", {
5169
+ threadId: id,
5170
+ turnId: turn.id,
5171
+ tokenUsage: usageNow
5172
+ });
5173
+ emit(id, "turn/completed", {
5174
+ threadId: id,
5175
+ turn
5176
+ });
5177
+ emit(id, "thread/status/changed", {
5178
+ threadId: id,
5179
+ status: { type: "idle" }
5180
+ });
5181
+ })();
5054
5182
  return {};
5055
5183
  },
5056
5184
  "turn/steer": async (params) => {
@@ -5255,7 +5383,7 @@ function createDshRemoteControl({ controller, agents, permissions = () => void 0
5255
5383
  }) : askPhone(request, "item/commandExecution/requestApproval", {
5256
5384
  ...common,
5257
5385
  command: commandText(session, request),
5258
- cwd: session.meta?.cwd ?? null,
5386
+ cwd: directoryOf(session) ?? null,
5259
5387
  reason,
5260
5388
  commandActions: [],
5261
5389
  availableDecisions: [
@@ -5389,7 +5517,7 @@ function createDshRemoteControl({ controller, agents, permissions = () => void 0
5389
5517
  type: "text",
5390
5518
  text: "x"
5391
5519
  }]
5392
- }, message], void 0, void 0, session.meta?.cwd).flatMap((turn) => turn.items.slice(1));
5520
+ }, message], void 0, void 0, directoryOf(session)).flatMap((turn) => turn.items.slice(1));
5393
5521
  const stream = streams.get(session.id);
5394
5522
  streams.delete(session.id);
5395
5523
  let reuse = stream?.item;
@@ -5686,6 +5814,42 @@ var DshOAuthAccountVault = class {
5686
5814
  return clone$1(payload?.accounts.find((account) => account.id === payload.activeId)?.credential);
5687
5815
  });
5688
5816
  }
5817
+ /** One account's credential, whether or not it is the active one. */
5818
+ readAccount(id) {
5819
+ return this.#enqueue(async () => {
5820
+ const payload = await this.#ensurePayload();
5821
+ return clone$1(payload?.accounts.find((account) => account.id === id)?.credential);
5822
+ });
5823
+ }
5824
+ /** Update one account's credential (a token refresh) without changing which account is active. */
5825
+ modifyAccount(id, update) {
5826
+ return this.#enqueue(async () => {
5827
+ let result;
5828
+ await this.#modifyPayload(async (current) => {
5829
+ const index = current.accounts.findIndex((account) => account.id === id);
5830
+ if (index < 0) throw new Error("Unknown Codex account");
5831
+ const previous = clone$1(current.accounts[index].credential);
5832
+ const next = await update(previous);
5833
+ if (next === void 0) {
5834
+ result = previous;
5835
+ return current;
5836
+ }
5837
+ const credential = sanitizeOAuthCredential(next);
5838
+ if (credential.email === void 0 && previous.email !== void 0) credential.email = previous.email;
5839
+ const accounts = [...current.accounts];
5840
+ accounts[index] = {
5841
+ ...accounts[index],
5842
+ credential
5843
+ };
5844
+ result = clone$1(credential);
5845
+ return {
5846
+ ...current,
5847
+ accounts
5848
+ };
5849
+ });
5850
+ return result;
5851
+ });
5852
+ }
5689
5853
  activeId() {
5690
5854
  return this.#enqueue(async () => (await this.#ensurePayload())?.activeId);
5691
5855
  }
@@ -5785,11 +5949,11 @@ var DshOAuthAccountVault = class {
5785
5949
  };
5786
5950
  //#endregion
5787
5951
  //#region src/credential-store.js
5788
- const PROVIDER$1 = "openai-codex";
5952
+ const PROVIDER$2 = "openai-codex";
5789
5953
  const abortIfNeeded = (options) => options?.signal?.throwIfAborted();
5790
5954
  const clone = (value) => value === void 0 ? void 0 : structuredClone(value);
5791
- function assertProvider(providerId) {
5792
- if (providerId !== PROVIDER$1) throw new Error(`Codex credential store does not own provider ${JSON.stringify(providerId)}`);
5955
+ function assertProvider$1(providerId) {
5956
+ if (providerId !== PROVIDER$2) throw new Error(`Codex credential store does not own provider ${JSON.stringify(providerId)}`);
5793
5957
  }
5794
5958
  function assertOAuthCredential(value) {
5795
5959
  if (value === void 0) return void 0;
@@ -5822,7 +5986,7 @@ var DshOAuthCredentialStore = class {
5822
5986
  this.vault = options.vault;
5823
5987
  }
5824
5988
  #enqueue(providerId, operation, options) {
5825
- assertProvider(providerId);
5989
+ assertProvider$1(providerId);
5826
5990
  const current = (this.#chains.get(providerId) ?? Promise.resolve()).catch(() => void 0).then(async () => {
5827
5991
  abortIfNeeded(options);
5828
5992
  return operation();
@@ -5835,7 +5999,7 @@ var DshOAuthCredentialStore = class {
5835
5999
  return current;
5836
6000
  }
5837
6001
  async #read(providerId, options) {
5838
- assertProvider(providerId);
6002
+ assertProvider$1(providerId);
5839
6003
  abortIfNeeded(options);
5840
6004
  if (this.vault !== void 0) {
5841
6005
  const current = await this.vault.readActive();
@@ -5868,8 +6032,8 @@ var DshOAuthCredentialStore = class {
5868
6032
  }
5869
6033
  async list(options) {
5870
6034
  abortIfNeeded(options);
5871
- return await this.read(PROVIDER$1, options) === void 0 ? [] : [{
5872
- providerId: PROVIDER$1,
6035
+ return await this.read(PROVIDER$2, options) === void 0 ? [] : [{
6036
+ providerId: PROVIDER$2,
5873
6037
  type: "oauth"
5874
6038
  }];
5875
6039
  }
@@ -5919,16 +6083,16 @@ function createCodexAuthService(models, store, options = {}) {
5919
6083
  const createPendingStore = options.createPendingStore ?? (() => new PendingOAuthCredentialStore());
5920
6084
  return Object.freeze({
5921
6085
  async status(options) {
5922
- const current = await store.read(PROVIDER$1, options);
6086
+ const current = await store.read(PROVIDER$2, options);
5923
6087
  const accounts = await accountVault?.list();
5924
6088
  if (current === void 0) return {
5925
6089
  authenticated: false,
5926
- provider: PROVIDER$1,
6090
+ provider: PROVIDER$2,
5927
6091
  ...accounts === void 0 ? {} : { accounts }
5928
6092
  };
5929
6093
  return {
5930
6094
  authenticated: true,
5931
- provider: PROVIDER$1,
6095
+ provider: PROVIDER$2,
5932
6096
  type: "oauth",
5933
6097
  expiresAt: current.expires,
5934
6098
  ...accounts === void 0 ? {} : { accounts }
@@ -5939,13 +6103,13 @@ function createCodexAuthService(models, store, options = {}) {
5939
6103
  if (accountVault === void 0 || createLoginModels === void 0) throw new Error("Codex multi-account is unavailable");
5940
6104
  return runLogin(async () => {
5941
6105
  const pending = createPendingStore();
5942
- await createLoginModels(pending).login(PROVIDER$1, "oauth", interaction);
6106
+ await createLoginModels(pending).login(PROVIDER$2, "oauth", interaction);
5943
6107
  const credential = pending.credential();
5944
6108
  if (credential === void 0) throw new Error("Codex login did not return credentials");
5945
6109
  await accountVault.add(input.label, credential);
5946
6110
  });
5947
6111
  }
5948
- return runLogin(() => models.login(PROVIDER$1, "oauth", interaction));
6112
+ return runLogin(() => models.login(PROVIDER$2, "oauth", interaction));
5949
6113
  },
5950
6114
  async select(id) {
5951
6115
  if (accountVault === void 0) throw new Error("Codex multi-account is unavailable");
@@ -5958,7 +6122,7 @@ function createCodexAuthService(models, store, options = {}) {
5958
6122
  return this.status();
5959
6123
  },
5960
6124
  logout(options) {
5961
- return models.logout(PROVIDER$1, options);
6125
+ return models.logout(PROVIDER$2, options);
5962
6126
  }
5963
6127
  });
5964
6128
  }
@@ -6431,6 +6595,62 @@ function redactRequest(value) {
6431
6595
  return walk(value, "", 0);
6432
6596
  }
6433
6597
  //#endregion
6598
+ //#region src/pi-ai-compat.js
6599
+ const versionOf = (specifier) => {
6600
+ try {
6601
+ let path = fileURLToPath(import.meta.resolve(specifier));
6602
+ for (let depth = 0; depth < 6; depth += 1) {
6603
+ path = path.replace(/[\\/][^\\/]+$/u, "");
6604
+ try {
6605
+ const manifest = JSON.parse(readFileSync(`${path}/package.json`, "utf8"));
6606
+ if (typeof manifest.version === "string") return manifest.version;
6607
+ } catch {}
6608
+ }
6609
+ } catch {}
6610
+ };
6611
+ let cached;
6612
+ /** Versions of the pi-ai library and of DSH's adapter this plugin runs against. */
6613
+ function runtimeVersions() {
6614
+ cached ??= {
6615
+ piAi: versionOf("@earendil-works/pi-ai"),
6616
+ adapter: versionOf("@deepseek-ai/dsh-llm-pi-ai")
6617
+ };
6618
+ return cached;
6619
+ }
6620
+ const atLeast087 = (version) => {
6621
+ const match = /^(\d+)\.(\d+)/u.exec(version ?? "");
6622
+ return match !== null && (Number(match[1]) > 0 || Number(match[2]) >= 87);
6623
+ };
6624
+ /** Whether the installed pi-ai understands system messages inside the transcript. */
6625
+ const understandsSystemMessages = (version) => atLeast087(version);
6626
+ const textOf = (content) => typeof content === "string" ? content : Array.isArray(content) ? content.filter((block) => block?.type === "text" && typeof block.text === "string").map((block) => block.text).join("\n") : "";
6627
+ /**
6628
+ * Fold the transcript's leading system messages back into `systemPrompt` and `tools`,
6629
+ * the form every pi-ai release accepts. Messages after the first non-system one stay.
6630
+ */
6631
+ function hoistLeadingSystem(context) {
6632
+ const messages = context?.messages;
6633
+ if (!Array.isArray(messages) || messages[0]?.role !== "system") return context;
6634
+ let end = 0;
6635
+ const texts = context.systemPrompt ? [context.systemPrompt] : [];
6636
+ const tools = [...Array.isArray(context.tools) ? context.tools : []];
6637
+ while (messages[end]?.role === "system") {
6638
+ const message = messages[end];
6639
+ const text = [textOf(message.content), ...Object.values(message.sections ?? {}).filter((value) => typeof value === "string")].filter(Boolean).join("\n\n");
6640
+ if (text !== "") texts.push(text);
6641
+ if (Array.isArray(message.toolsAdded)) tools.push(...message.toolsAdded);
6642
+ end += 1;
6643
+ }
6644
+ const { systemPrompt: _systemPrompt, tools: _tools, ...rest } = context;
6645
+ const systemPrompt = texts.join("\n\n");
6646
+ return {
6647
+ ...rest,
6648
+ ...systemPrompt === "" ? {} : { systemPrompt },
6649
+ ...tools.length === 0 ? {} : { tools },
6650
+ messages: messages.slice(end)
6651
+ };
6652
+ }
6653
+ //#endregion
6434
6654
  //#region src/transport-failure.js
6435
6655
  const TRANSIENT_WEBSOCKET_CLOSE = /^WebSocket closed (?:1000|1006|1011|1012|1013)(?:\s|$)/;
6436
6656
  const GATEWAY_REPLAY_BUFFER_EXCEEDED = /^\s*exceeded request buffer limit while retrying upstream\s*$/;
@@ -6438,6 +6658,13 @@ function normalizeTransportEvent(event, signal) {
6438
6658
  if (signal?.aborted || event?.type !== "error" || event.reason !== "error") return event;
6439
6659
  const message = event.error?.errorMessage;
6440
6660
  if (typeof message !== "string") return event;
6661
+ if (isChallengePage(message)) return {
6662
+ ...event,
6663
+ error: {
6664
+ ...event.error,
6665
+ errorMessage: REQUEST_BLOCKED
6666
+ }
6667
+ };
6441
6668
  if (!TRANSIENT_WEBSOCKET_CLOSE.test(message) && !GATEWAY_REPLAY_BUFFER_EXCEEDED.test(message)) return event;
6442
6669
  return {
6443
6670
  ...event,
@@ -6724,7 +6951,8 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
6724
6951
  throw: (error) => iterator ? step("throw", error) : Promise.reject(error)
6725
6952
  };
6726
6953
  };
6727
- const routed = (method, model, context, options) => {
6954
+ const routed = (method, model, transcript, options) => {
6955
+ const context = understandsSystemMessages(runtimeVersions().piAi) ? transcript : hoistLeadingSystem(transcript);
6728
6956
  const route = reviewRoute(model, context);
6729
6957
  const source = {
6730
6958
  method,
@@ -6753,7 +6981,8 @@ Object.freeze([
6753
6981
  "0.85.1",
6754
6982
  "0.87.1",
6755
6983
  "1.1.0",
6756
- "1.2.0"
6984
+ "1.2.0",
6985
+ "1.3.0"
6757
6986
  ]);
6758
6987
  //#endregion
6759
6988
  //#region src/model-catalog.js
@@ -7160,7 +7389,10 @@ function createCodexSearchProvider(options) {
7160
7389
  if (signal?.aborted || error?.name === "AbortError") throw new WebError("Codex search aborted", "WEB_ABORTED", { cause: error });
7161
7390
  throw new WebError("Codex search request failed", "WEB_PROVIDER_ERROR", { cause: error });
7162
7391
  }
7163
- if (!response.ok) throw response.status === 401 || response.status === 403 ? new WebError("ChatGPT sign-in needs to be renewed", "WEB_PROVIDER_CREDENTIAL_MISSING") : new WebError(`Codex search request failed (HTTP ${response.status})`, "WEB_PROVIDER_ERROR");
7392
+ if (!response.ok) {
7393
+ const authFailure = await authFailureMessage(response);
7394
+ throw authFailure === "ChatGPT sign-in needs to be renewed" ? new WebError(authFailure, "WEB_PROVIDER_CREDENTIAL_MISSING") : authFailure !== void 0 ? new WebError(authFailure, "WEB_PROVIDER_ERROR") : new WebError(`Codex search request failed (HTTP ${response.status})`, "WEB_PROVIDER_ERROR");
7395
+ }
7164
7396
  let value;
7165
7397
  try {
7166
7398
  value = await response.json();
@@ -7691,6 +7923,15 @@ function safeRequests(network) {
7691
7923
  }
7692
7924
  return result;
7693
7925
  }
7926
+ /** pi-ai and DSH's adapter come from the host; a profile can pair an adapter with an older library, so the report names both. */
7927
+ function libraryVersions() {
7928
+ const { piAi, adapter } = runtimeVersions();
7929
+ const valid = (value) => typeof value === "string" && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]{1,32})?$/u.test(value);
7930
+ return {
7931
+ ...valid(piAi) ? { piAi } : {},
7932
+ ...valid(adapter) ? { adapter } : {}
7933
+ };
7934
+ }
7694
7935
  /** Build a support report that deliberately excludes OAuth and account metadata. */
7695
7936
  async function createSubscriptionDiagnostics({ auth, preferences, login = { phase: "idle" }, network, modelCatalog, connection, compaction, review, inspectionOptions, operations, runtimeManagement, storage, tools }) {
7696
7937
  const collected = {};
@@ -7869,7 +8110,8 @@ async function createSubscriptionDiagnostics({ auth, preferences, login = { phas
7869
8110
  node: process.version,
7870
8111
  platform: process.platform,
7871
8112
  arch: process.arch,
7872
- ...hostDshVersion() ? { dsh: hostDshVersion() } : {}
8113
+ ...hostDshVersion() ? { dsh: hostDshVersion() } : {},
8114
+ ...libraryVersions()
7873
8115
  },
7874
8116
  account,
7875
8117
  login,
@@ -8149,7 +8391,7 @@ function createCodexUsageReader(options) {
8149
8391
  signal: requestSignal$1(signal, timeoutMs)
8150
8392
  });
8151
8393
  if (!response.ok) {
8152
- const error = /* @__PURE__ */ new Error(response.status === 401 || response.status === 403 ? "ChatGPT sign-in needs to be renewed" : `ChatGPT usage request failed (HTTP ${response.status})`);
8394
+ const error = new Error(await authFailureMessage(response) ?? `ChatGPT usage request failed (HTTP ${response.status})`);
8153
8395
  Object.defineProperty(error, "retryAfterMs", { value: retryAfterMs(response, now, maxRetryAfterMs) });
8154
8396
  throw error;
8155
8397
  }
@@ -8200,6 +8442,102 @@ function createCodexUsageReader(options) {
8200
8442
  });
8201
8443
  }
8202
8444
  //#endregion
8445
+ //#region src/account-usage.js
8446
+ const PROVIDER$1 = "openai-codex";
8447
+ const MAX_PARALLEL = 4;
8448
+ const assertProvider = (providerId) => {
8449
+ if (providerId !== PROVIDER$1) throw new Error("Codex account usage received an unknown provider");
8450
+ };
8451
+ /** pi-ai's OAuth credential store, bound to one account of the vault. */
8452
+ function accountCredentialStore(vault, id, expirySkewMs = 0) {
8453
+ const shift = (credential) => credential === void 0 || expirySkewMs === 0 ? credential : {
8454
+ ...credential,
8455
+ expires: credential.expires - expirySkewMs
8456
+ };
8457
+ return {
8458
+ async read(providerId) {
8459
+ assertProvider(providerId);
8460
+ return shift(await vault.readAccount(id));
8461
+ },
8462
+ async list() {
8463
+ return await vault.readAccount(id) === void 0 ? [] : [{
8464
+ providerId: PROVIDER$1,
8465
+ type: "oauth"
8466
+ }];
8467
+ },
8468
+ async modify(providerId, update) {
8469
+ assertProvider(providerId);
8470
+ return vault.modifyAccount(id, async (current) => update(shift(current)));
8471
+ },
8472
+ async delete() {
8473
+ throw new Error("An account usage read cannot remove a sign-in");
8474
+ }
8475
+ };
8476
+ }
8477
+ /**
8478
+ * @param {object} options
8479
+ * @param {{ list(): Promise<object[]>, readAccount(id: string): Promise<object|undefined>, modifyAccount: Function }} options.vault
8480
+ * @param {(credentials: object) => object} options.createModels builds a pi-ai Models over a credential store, with the Codex provider set
8481
+ * @param {typeof fetch} options.fetch
8482
+ */
8483
+ function createAccountUsageReader({ vault, createModels, fetch, expirySkewMs = 0, now = Date.now, activeReader }) {
8484
+ const readers = /* @__PURE__ */ new Map();
8485
+ const readerFor = (id) => {
8486
+ let reader = readers.get(id);
8487
+ if (reader === void 0) {
8488
+ const models = createModels(accountCredentialStore(vault, id, expirySkewMs));
8489
+ reader = createCodexUsageReader({
8490
+ getAuth: () => models.getAuth(PROVIDER$1),
8491
+ readCredential: () => vault.readAccount(id).then((credential) => credential),
8492
+ fetch,
8493
+ now
8494
+ });
8495
+ readers.set(id, reader);
8496
+ }
8497
+ return reader;
8498
+ };
8499
+ const summarize = (error, signal) => {
8500
+ if (signal?.aborted) throw error;
8501
+ const message = error instanceof Error ? error.message : "";
8502
+ return ["ChatGPT subscription is not signed in", "ChatGPT sign-in needs to be renewed"].includes(message) || message.includes("blocked this request") ? message : "Could not read ChatGPT usage";
8503
+ };
8504
+ return Object.freeze({
8505
+ /** One entry per account: its usage reading, or the reason it could not be read. */
8506
+ async readAll({ force = false, signal } = {}) {
8507
+ const accounts = await vault.list();
8508
+ for (const id of [...readers.keys()]) if (!accounts.some((account) => account.id === id)) readers.delete(id);
8509
+ const entries = new Array(accounts.length);
8510
+ let next = 0;
8511
+ const worker = async () => {
8512
+ while (next < accounts.length) {
8513
+ const index = next++;
8514
+ const account = accounts[index];
8515
+ try {
8516
+ const reader = account.active && activeReader !== void 0 ? activeReader : readerFor(account.id);
8517
+ entries[index] = {
8518
+ id: account.id,
8519
+ usage: await reader.read({
8520
+ force,
8521
+ signal
8522
+ })
8523
+ };
8524
+ } catch (error) {
8525
+ entries[index] = {
8526
+ id: account.id,
8527
+ error: summarize(error, signal)
8528
+ };
8529
+ }
8530
+ }
8531
+ };
8532
+ await Promise.all(Array.from({ length: Math.min(MAX_PARALLEL, accounts.length) }, worker));
8533
+ return entries;
8534
+ },
8535
+ clear() {
8536
+ readers.clear();
8537
+ }
8538
+ });
8539
+ }
8540
+ //#endregion
8203
8541
  //#region src/plan-history.js
8204
8542
  const CODEX_PLAN_HISTORY_URL = "https://chatgpt.com/backend-api/wham/usage/plan_limit_history?days=7";
8205
8543
  const DIMENSIONS = Object.freeze([
@@ -8279,7 +8617,7 @@ function createReportReader({ url, parse, label, getAuth, readCredential, fetch:
8279
8617
  available: false,
8280
8618
  fetchedAt: now()
8281
8619
  };
8282
- if (!response.ok) throw new Error(response.status === 401 ? "ChatGPT sign-in needs to be renewed" : response.status === 403 ? `ChatGPT does not offer the ${label} to this account` : `ChatGPT ${label} request failed (HTTP ${response.status})`);
8620
+ if (!response.ok) throw new Error(response.status === 401 ? SIGN_IN_RENEW : response.status === 403 ? await isBlockedByNetwork(response) ? REQUEST_BLOCKED : `ChatGPT does not offer the ${label} to this account` : `ChatGPT ${label} request failed (HTTP ${response.status})`);
8283
8621
  let body;
8284
8622
  try {
8285
8623
  body = await response.json();
@@ -8460,8 +8798,8 @@ function createConnectorsClient({ getAuth, readCredential, fetch: fetchRpc = fet
8460
8798
  }),
8461
8799
  signal: signal === void 0 ? timeout : AbortSignal.any([signal, timeout])
8462
8800
  });
8463
- if (response.status === 401) throw new Error("ChatGPT sign-in needs to be renewed");
8464
- if (response.status === 403) throw new Error("ChatGPT does not offer connectors to this account");
8801
+ if (response.status === 401) throw new Error(SIGN_IN_RENEW);
8802
+ if (response.status === 403) throw new Error(await isBlockedByNetwork(response) ? REQUEST_BLOCKED : "ChatGPT does not offer connectors to this account");
8465
8803
  if (!response.ok) throw new Error(`ChatGPT connectors request failed (HTTP ${response.status})`);
8466
8804
  const message = await readRpc(response);
8467
8805
  if (record$1(message.error)) throw new Error(`Connector error: ${clip(oneLine(String(message.error.message ?? message.error.code ?? "failed")), 200)}`);
@@ -9392,7 +9730,7 @@ function createCodexResetCreditService(options) {
9392
9730
  },
9393
9731
  signal: requestSignal(signal, timeoutMs)
9394
9732
  });
9395
- if (!response.ok) throw new Error(response.status === 401 || response.status === 403 ? "ChatGPT sign-in needs to be renewed" : `ChatGPT quota reset request failed (HTTP ${response.status})`);
9733
+ if (!response.ok) throw new Error(await authFailureMessage(response) ?? `ChatGPT quota reset request failed (HTTP ${response.status})`);
9396
9734
  let raw;
9397
9735
  try {
9398
9736
  raw = await response.json();
@@ -9526,7 +9864,7 @@ function createCodexResetCreditService(options) {
9526
9864
  throw new Error(UNCERTAIN_RESET_RESULT);
9527
9865
  }
9528
9866
  if (response.status !== 401 && response.status !== 403) retryable = false;
9529
- throw new Error(response.status === 401 || response.status === 403 ? "ChatGPT sign-in needs to be renewed" : `ChatGPT quota reset request failed (HTTP ${response.status})`);
9867
+ throw new Error(await authFailureMessage(response) ?? `ChatGPT quota reset request failed (HTTP ${response.status})`);
9530
9868
  }
9531
9869
  let raw;
9532
9870
  try {
@@ -9582,7 +9920,7 @@ async function settlesLive(preferences, patch, signal, { attempts = 8, delayMs =
9582
9920
  }
9583
9921
  return live();
9584
9922
  }
9585
- function createSubscriptionRpcHandler({ authHandler, usageReader, planHistoryReader, usageProfileReader, resetCreditService, preferences, defaultModel, runtimeManagement, remoteControl, diagnosticsReader, modelCatalog, originalImages, resolveInheritedOriginal, closeConnections, onAccountChanged, onCleanupFailure }) {
9923
+ function createSubscriptionRpcHandler({ authHandler, usageReader, accountUsageReader, planHistoryReader, usageProfileReader, resetCreditService, preferences, defaultModel, runtimeManagement, remoteControl, diagnosticsReader, modelCatalog, originalImages, resolveInheritedOriginal, closeConnections, onAccountChanged, onCleanupFailure }) {
9586
9924
  return async (endpoint, payload, signal) => {
9587
9925
  if (endpoint === "default-model/status" || endpoint === "default-model/select") try {
9588
9926
  signal.throwIfAborted();
@@ -9751,7 +10089,11 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, planHistoryRea
9751
10089
  };
9752
10090
  } catch (error) {
9753
10091
  if (signal.aborted) throw error;
9754
- const known = /* @__PURE__ */ new Set(["ChatGPT subscription is not signed in", "ChatGPT sign-in needs to be renewed"]);
10092
+ const known = /* @__PURE__ */ new Set([
10093
+ "ChatGPT subscription is not signed in",
10094
+ "ChatGPT sign-in needs to be renewed",
10095
+ REQUEST_BLOCKED
10096
+ ]);
9755
10097
  const message = error instanceof Error && known.has(error.message) ? error.message : "Could not read ChatGPT usage";
9756
10098
  return publicError("internal", message);
9757
10099
  }
@@ -9766,9 +10108,26 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, planHistoryRea
9766
10108
  };
9767
10109
  } catch (error) {
9768
10110
  if (signal.aborted) throw error;
9769
- const known = /* @__PURE__ */ new Set(["ChatGPT subscription is not signed in", "ChatGPT sign-in needs to be renewed"]);
10111
+ const known = /* @__PURE__ */ new Set([
10112
+ "ChatGPT subscription is not signed in",
10113
+ "ChatGPT sign-in needs to be renewed",
10114
+ REQUEST_BLOCKED
10115
+ ]);
9770
10116
  return publicError("internal", error instanceof Error && known.has(error.message) ? error.message : "Could not read ChatGPT usage profile");
9771
10117
  }
10118
+ if (endpoint === "usage/accounts") try {
10119
+ signal.throwIfAborted();
10120
+ return {
10121
+ ok: true,
10122
+ value: { accounts: await accountUsageReader.readAll({
10123
+ force: payload?.force === true,
10124
+ signal
10125
+ }) }
10126
+ };
10127
+ } catch (error) {
10128
+ if (signal.aborted) throw error;
10129
+ return publicError("internal", "Could not read the usage of the signed-in accounts");
10130
+ }
9772
10131
  if (endpoint === "usage/history") try {
9773
10132
  signal.throwIfAborted();
9774
10133
  return {
@@ -9780,7 +10139,11 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, planHistoryRea
9780
10139
  };
9781
10140
  } catch (error) {
9782
10141
  if (signal.aborted) throw error;
9783
- const known = /* @__PURE__ */ new Set(["ChatGPT subscription is not signed in", "ChatGPT sign-in needs to be renewed"]);
10142
+ const known = /* @__PURE__ */ new Set([
10143
+ "ChatGPT subscription is not signed in",
10144
+ "ChatGPT sign-in needs to be renewed",
10145
+ REQUEST_BLOCKED
10146
+ ]);
9784
10147
  return publicError("internal", error instanceof Error && known.has(error.message) ? error.message : "Could not read ChatGPT usage history");
9785
10148
  }
9786
10149
  if (endpoint === "reset-credit/inspect" || endpoint === "reset-credit/prepare" || endpoint === "reset-credit/consume") try {
@@ -9801,6 +10164,7 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, planHistoryRea
9801
10164
  const known = /* @__PURE__ */ new Set([
9802
10165
  "ChatGPT subscription is not signed in",
9803
10166
  "ChatGPT sign-in needs to be renewed",
10167
+ REQUEST_BLOCKED,
9804
10168
  "No quota reset is available",
9805
10169
  "No usable quota reset is available",
9806
10170
  "The available quota reset expires too soon",
@@ -9825,7 +10189,7 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, planHistoryRea
9825
10189
  () => modelCatalog?.clear(),
9826
10190
  () => onAccountChanged?.()
9827
10191
  ];
9828
- if (endpoint === "logout") tasks.push(() => usageReader.clear());
10192
+ if (endpoint === "logout") tasks.push(() => usageReader.clear(), () => accountUsageReader?.clear());
9829
10193
  if (endpoint === "account/remove" && typeof payload?.id === "string") tasks.push(() => usageReader.clearScope(payload.id));
9830
10194
  if ((await Promise.allSettled(tasks.map((task) => Promise.resolve().then(task)))).some((value) => value.status === "rejected")) try {
9831
10195
  onCleanupFailure?.();
@@ -10227,6 +10591,7 @@ function apply(ctx, config = {}) {
10227
10591
  usageProfile: () => usageProfileReader,
10228
10592
  projections: () => ctx.get?.("sessionProjections"),
10229
10593
  skills: () => ctx.get?.("skills"),
10594
+ commands: () => ctx.get?.("commands"),
10230
10595
  llm: () => ctx.llm,
10231
10596
  trace: (entry) => {
10232
10597
  try {
@@ -10379,9 +10744,21 @@ function apply(ctx, config = {}) {
10379
10744
  sketchBridge.dispose();
10380
10745
  };
10381
10746
  }, "codex-subscription: native sketch tool");
10747
+ const accountUsageReader = createAccountUsageReader({
10748
+ vault: accountVault,
10749
+ createModels: (credentials) => {
10750
+ const accountModels = createModels({ credentials });
10751
+ accountModels.setProvider(provider);
10752
+ return accountModels;
10753
+ },
10754
+ fetch: (input, init) => network.fetch("quota", input, init),
10755
+ expirySkewMs: OAUTH_EXPIRY_SKEW_MS,
10756
+ activeReader: baseUsageReader
10757
+ });
10382
10758
  const subscriptionHandler = createSubscriptionRpcHandler({
10383
10759
  authHandler: createCodexRpcHandler(coordinator, { openExternal: openCodexAuthUrl }),
10384
10760
  usageReader,
10761
+ accountUsageReader,
10385
10762
  planHistoryReader,
10386
10763
  usageProfileReader,
10387
10764
  resetCreditService,