@love-moon/ai-sdk 0.2.42 → 0.3.2

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/CHANGELOG.md ADDED
@@ -0,0 +1,26 @@
1
+ # @love-moon/ai-sdk
2
+
3
+ ## 0.3.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 8e1d4a8: Prefer the bundled Copilot platform executable before the JS entrypoint so Node
8
+ 20 installs do not fail with `ERR_UNKNOWN_BUILTIN_MODULE: node:sqlite`.
9
+
10
+ ## 0.3.1
11
+
12
+ ### Patch Changes
13
+
14
+ - 4e8d4e5: Include `CHANGELOG.md` in published npm tarballs.
15
+
16
+ The `files` array in each package's `package.json` previously only
17
+ listed the build output (`bin`/`src` for the CLI, `dist` for the
18
+ modules). npm's `files` whitelist replaces the default include set,
19
+ and CHANGELOG is not one of the auto-included files (only
20
+ `package.json`, `README*`, `LICENSE*`, and `main` are unconditional).
21
+
22
+ As a result, every release through 0.3.0 published tarballs with no
23
+ CHANGELOG, so a consumer running `npm install` or unpacking the brew
24
+ artifact had no way to see what changed in the version they just
25
+ installed. The repo `cli/CHANGELOG.md` and the GitHub Release body
26
+ remain the canonical source until 0.3.1 ships with this fix.
@@ -0,0 +1,51 @@
1
+ export function listBuiltInBackends(): string[];
2
+ export function normalizeBuiltInBackend(backend: any): any;
3
+ export function isBuiltInBackend(backend: any): boolean;
4
+ export function getBuiltInBackendEntry(backend: any): BuiltInBackendEntry | null;
5
+ /**
6
+ * Canonical registry of built-in AI SDK backends.
7
+ *
8
+ * This is the single source of truth for:
9
+ * - backend canonical name (e.g. "codex", "claude")
10
+ * - accepted aliases (e.g. "code" → "codex", "kimi-cli" → "kimi")
11
+ * - session variants (default + structured-output variant, if any)
12
+ *
13
+ * Provider surfaces that need to know about built-in backends (session
14
+ * factory, resume dispatcher, etc.) import from here so that adding a new
15
+ * built-in backend is a one-line change in this file, plus the provider's own
16
+ * implementation module.
17
+ */
18
+ export const CODEX_APP_SERVER_VARIANT: "codex-app-server";
19
+ export const CODEX_EXEC_VARIANT: "codex-exec";
20
+ export const CLAUDE_AGENT_SDK_VARIANT: "claude-agent-sdk";
21
+ export const COPILOT_SDK_VARIANT: "copilot-sdk";
22
+ export const KIMI_CLI_WIRE_VARIANT: "kimi-cli-wire";
23
+ export const KIMI_CLI_PRINT_VARIANT: "kimi-cli-print";
24
+ export const OPENCODE_SDK_VARIANT: "opencode-sdk";
25
+ /**
26
+ * @typedef {Object} BuiltInBackendEntry
27
+ * @property {string} backend Canonical backend name.
28
+ * @property {string[]} aliases All accepted aliases (INCLUDING the canonical name).
29
+ * @property {string} defaultVariant Variant used when no structured-output preference is set.
30
+ * @property {string=} structuredVariant Optional variant used when structured output is requested.
31
+ */
32
+ /** @type {BuiltInBackendEntry[]} */
33
+ export const BUILT_IN_BACKENDS: BuiltInBackendEntry[];
34
+ export type BuiltInBackendEntry = {
35
+ /**
36
+ * Canonical backend name.
37
+ */
38
+ backend: string;
39
+ /**
40
+ * All accepted aliases (INCLUDING the canonical name).
41
+ */
42
+ aliases: string[];
43
+ /**
44
+ * Variant used when no structured-output preference is set.
45
+ */
46
+ defaultVariant: string;
47
+ /**
48
+ * Optional variant used when structured output is requested.
49
+ */
50
+ structuredVariant?: string | undefined;
51
+ };
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Canonical registry of built-in AI SDK backends.
3
+ *
4
+ * This is the single source of truth for:
5
+ * - backend canonical name (e.g. "codex", "claude")
6
+ * - accepted aliases (e.g. "code" → "codex", "kimi-cli" → "kimi")
7
+ * - session variants (default + structured-output variant, if any)
8
+ *
9
+ * Provider surfaces that need to know about built-in backends (session
10
+ * factory, resume dispatcher, etc.) import from here so that adding a new
11
+ * built-in backend is a one-line change in this file, plus the provider's own
12
+ * implementation module.
13
+ */
14
+ export const CODEX_APP_SERVER_VARIANT = "codex-app-server";
15
+ export const CODEX_EXEC_VARIANT = "codex-exec";
16
+ export const CLAUDE_AGENT_SDK_VARIANT = "claude-agent-sdk";
17
+ export const COPILOT_SDK_VARIANT = "copilot-sdk";
18
+ export const KIMI_CLI_WIRE_VARIANT = "kimi-cli-wire";
19
+ export const KIMI_CLI_PRINT_VARIANT = "kimi-cli-print";
20
+ export const OPENCODE_SDK_VARIANT = "opencode-sdk";
21
+ /**
22
+ * @typedef {Object} BuiltInBackendEntry
23
+ * @property {string} backend Canonical backend name.
24
+ * @property {string[]} aliases All accepted aliases (INCLUDING the canonical name).
25
+ * @property {string} defaultVariant Variant used when no structured-output preference is set.
26
+ * @property {string=} structuredVariant Optional variant used when structured output is requested.
27
+ */
28
+ /** @type {BuiltInBackendEntry[]} */
29
+ export const BUILT_IN_BACKENDS = [
30
+ {
31
+ backend: "codex",
32
+ aliases: ["codex", "code"],
33
+ defaultVariant: CODEX_APP_SERVER_VARIANT,
34
+ structuredVariant: CODEX_EXEC_VARIANT,
35
+ },
36
+ {
37
+ backend: "claude",
38
+ aliases: ["claude", "claude-code"],
39
+ defaultVariant: CLAUDE_AGENT_SDK_VARIANT,
40
+ },
41
+ {
42
+ backend: "copilot",
43
+ aliases: ["copilot"],
44
+ defaultVariant: COPILOT_SDK_VARIANT,
45
+ },
46
+ {
47
+ backend: "kimi",
48
+ aliases: ["kimi", "kimi-cli", "kimi-code"],
49
+ defaultVariant: KIMI_CLI_WIRE_VARIANT,
50
+ structuredVariant: KIMI_CLI_PRINT_VARIANT,
51
+ },
52
+ {
53
+ backend: "opencode",
54
+ aliases: ["opencode", "open-code", "open_code"],
55
+ defaultVariant: OPENCODE_SDK_VARIANT,
56
+ },
57
+ ];
58
+ const ALIAS_TO_BACKEND = new Map();
59
+ for (const entry of BUILT_IN_BACKENDS) {
60
+ for (const alias of entry.aliases) {
61
+ ALIAS_TO_BACKEND.set(alias, entry.backend);
62
+ }
63
+ }
64
+ const BACKEND_SET = new Set(BUILT_IN_BACKENDS.map((entry) => entry.backend));
65
+ export function listBuiltInBackends() {
66
+ return BUILT_IN_BACKENDS.map((entry) => entry.backend);
67
+ }
68
+ export function normalizeBuiltInBackend(backend) {
69
+ const normalized = String(backend || "").trim().toLowerCase();
70
+ return ALIAS_TO_BACKEND.get(normalized) || normalized;
71
+ }
72
+ export function isBuiltInBackend(backend) {
73
+ return BACKEND_SET.has(normalizeBuiltInBackend(backend));
74
+ }
75
+ export function getBuiltInBackendEntry(backend) {
76
+ const canonical = normalizeBuiltInBackend(backend);
77
+ return BUILT_IN_BACKENDS.find((entry) => entry.backend === canonical) || null;
78
+ }
@@ -113,12 +113,26 @@ function validateDescriptor(descriptor, sourcePath) {
113
113
  throw new Error(`External AI SDK provider "${backend}" from ${sourcePath} is missing provider.createSession().`);
114
114
  }
115
115
  const aliases = Array.isArray(descriptor.aliases) ? descriptor.aliases.map((item) => normalizeName(item)).filter(Boolean) : [];
116
+ const optionalFn = (fieldName) => {
117
+ const value = descriptor[fieldName];
118
+ if (value === undefined || value === null) {
119
+ return null;
120
+ }
121
+ if (typeof value !== "function") {
122
+ throw new Error(`External AI SDK provider "${backend}" from ${sourcePath} has provider.${fieldName} `
123
+ + `but it is not a function (got ${typeof value}).`);
124
+ }
125
+ return value;
126
+ };
116
127
  return {
117
128
  backend,
118
129
  variant,
119
130
  aliases,
120
131
  createSession: descriptor.createSession,
121
- isSupported: typeof descriptor.isSupported === "function" ? descriptor.isSupported : null,
132
+ isSupported: optionalFn("isSupported"),
133
+ resolveResumeContext: optionalFn("resolveResumeContext"),
134
+ buildResumeArgs: optionalFn("buildResumeArgs"),
135
+ findSessionPath: optionalFn("findSessionPath"),
122
136
  sourcePath,
123
137
  };
124
138
  }
package/dist/index.d.ts CHANGED
@@ -1 +1,3 @@
1
+ export { BUILT_IN_BACKENDS } from "./built-in-backends.js";
1
2
  export { createAiSession, RemoteAiSession } from "./client.js";
3
+ export { resolveResumeContext, buildResumeArgsForBackend, resumeProviderForBackend, findSessionPath, resolveSessionRunDirectory, inspectResumeTarget } from "./resume/index.js";
package/dist/index.js CHANGED
@@ -1 +1,3 @@
1
1
  export { createAiSession, RemoteAiSession } from "./client.js";
2
+ export { BUILT_IN_BACKENDS } from "./built-in-backends.js";
3
+ export { resolveResumeContext, buildResumeArgsForBackend, resumeProviderForBackend, findSessionPath, resolveSessionRunDirectory, inspectResumeTarget, } from "./resume/index.js";
@@ -1,10 +1,10 @@
1
1
  import { EventEmitter } from "node:events";
2
+ import { CLAUDE_AGENT_SDK_VARIANT as CLAUDE_PROVIDER_VARIANT } from "../built-in-backends.js";
2
3
  import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, proxyToEnv, sanitizeForLog, } from "../shared.js";
3
4
  const DEFAULT_TURN_DEADLINE_MS = 12 * 60 * 1000;
4
5
  const MIN_TURN_DEADLINE_MS = 30 * 1000;
5
6
  const MAX_TURN_DEADLINE_MS = 30 * 60 * 1000;
6
7
  const DEFAULT_SETTING_SOURCES = ["user", "project", "local"];
7
- const CLAUDE_PROVIDER_VARIANT = "claude-agent-sdk";
8
8
  function waitForever() {
9
9
  return new Promise(() => { });
10
10
  }
@@ -1,4 +1,5 @@
1
1
  import { EventEmitter } from "node:events";
2
+ import { CODEX_APP_SERVER_VARIANT } from "../built-in-backends.js";
2
3
  import { CodexAppServerTransport } from "../transports/codex-app-server-transport.js";
3
4
  import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, proxyToEnv, sanitizeForLog, } from "../shared.js";
4
5
  const DEFAULT_TURN_DEADLINE_MS = 12 * 60 * 1000;
@@ -248,7 +249,7 @@ export class CodexAppServerSession extends EventEmitter {
248
249
  getSnapshot() {
249
250
  return {
250
251
  backend: this.backend,
251
- provider: "codex-app-server",
252
+ provider: CODEX_APP_SERVER_VARIANT,
252
253
  cwd: this.cwd,
253
254
  sessionId: this.sessionId || undefined,
254
255
  sessionInfo: this.getSessionInfo(),
@@ -324,7 +325,7 @@ export class CodexAppServerSession extends EventEmitter {
324
325
  }
325
326
  markTurnStartedStatus() {
326
327
  this.updateCurrentTurnStatus({
327
- source: "codex-app-server",
328
+ source: CODEX_APP_SERVER_VARIANT,
328
329
  reply_in_progress: true,
329
330
  replyTo: this.getCurrentReplyTarget(),
330
331
  phase: "turn_started",
@@ -444,7 +445,7 @@ export class CodexAppServerSession extends EventEmitter {
444
445
  }
445
446
  normalizeWorkingStatusPayload(payload) {
446
447
  return {
447
- source: "codex-app-server",
448
+ source: CODEX_APP_SERVER_VARIANT,
448
449
  reply_in_progress: Boolean(payload?.reply_in_progress),
449
450
  replyTo: payload?.replyTo || this.getCurrentReplyTarget(),
450
451
  state: payload?.state,
@@ -469,7 +470,7 @@ export class CodexAppServerSession extends EventEmitter {
469
470
  const payload = {
470
471
  text,
471
472
  preserveWhitespace: true,
472
- source: "codex-app-server",
473
+ source: CODEX_APP_SERVER_VARIANT,
473
474
  replyTo: this.getCurrentReplyTarget(),
474
475
  sessionId: this.sessionId || undefined,
475
476
  sessionFilePath: this.threadPath || undefined,
@@ -957,7 +958,7 @@ export class CodexAppServerSession extends EventEmitter {
957
958
  events: [],
958
959
  provider: this.backend,
959
960
  metadata: {
960
- source: "codex-app-server",
961
+ source: CODEX_APP_SERVER_VARIANT,
961
962
  threadId: this.sessionId,
962
963
  threadPath: this.threadPath || undefined,
963
964
  nativeSessionId: this.nativeSessionId || undefined,
@@ -5,11 +5,11 @@ import { spawn } from "node:child_process";
5
5
  import { randomUUID } from "node:crypto";
6
6
  import { EventEmitter } from "node:events";
7
7
  import readline from "node:readline";
8
+ import { CODEX_EXEC_VARIANT as CODEX_EXEC_PROVIDER_VARIANT } from "../built-in-backends.js";
8
9
  import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, parseCommandParts, proxyToEnv, sanitizeForLog, } from "../shared.js";
9
10
  const DEFAULT_TURN_DEADLINE_MS = 12 * 60 * 1000;
10
11
  const MIN_TURN_DEADLINE_MS = 30 * 1000;
11
12
  const MAX_TURN_DEADLINE_MS = 30 * 60 * 1000;
12
- const CODEX_EXEC_PROVIDER_VARIANT = "codex-exec";
13
13
  const DEFAULT_CODEX_EXEC_COMMAND = "codex";
14
14
  function createTurnError(message, extras = {}) {
15
15
  const error = new Error(message);
@@ -1,3 +1,10 @@
1
+ export function resolveBundledCopilotCliPath({ platform, arch, resolvePackage, resolvePackagePaths, existsSyncFn, }?: {
2
+ platform?: NodeJS.Platform | undefined;
3
+ arch?: NodeJS.Architecture | undefined;
4
+ resolvePackage?: ((packageName: any) => string) | undefined;
5
+ resolvePackagePaths?: ((packageName: any) => string[]) | undefined;
6
+ existsSyncFn?: typeof existsSync | undefined;
7
+ }): string | null;
1
8
  export class CopilotSdkSession extends EventEmitter<[never]> {
2
9
  constructor(backend: any, options?: {});
3
10
  backend: string;
@@ -190,4 +197,5 @@ export class CopilotSdkSession extends EventEmitter<[never]> {
190
197
  }>;
191
198
  close(): Promise<void>;
192
199
  }
200
+ import { existsSync } from "node:fs";
193
201
  import { EventEmitter } from "node:events";
@@ -1,14 +1,16 @@
1
1
  import { EventEmitter } from "node:events";
2
2
  import { existsSync } from "node:fs";
3
+ import { createRequire } from "node:module";
3
4
  import path from "node:path";
5
+ import { COPILOT_SDK_VARIANT as COPILOT_PROVIDER_VARIANT } from "../built-in-backends.js";
4
6
  import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, parseCommandParts, proxyToEnv, sanitizeForLog, withoutCopilotGithubTokenEnv, } from "../shared.js";
5
7
  const DEFAULT_TURN_DEADLINE_MS = 12 * 60 * 1000;
6
8
  const MIN_TURN_DEADLINE_MS = 30 * 1000;
7
9
  const MAX_TURN_DEADLINE_MS = 30 * 60 * 1000;
8
10
  const DEFAULT_CLOSE_TIMEOUT_MS = 5 * 1000;
9
11
  const SDK_SEND_AND_WAIT_TIMEOUT_GRACE_MS = 5 * 1000;
10
- const COPILOT_PROVIDER_VARIANT = "copilot-sdk";
11
12
  const LEGACY_COPILOT_CLI_ARGS = new Set(["--allow-all-paths", "--allow-all-tools"]);
13
+ const moduleRequire = createRequire(import.meta.url);
12
14
  function waitForever() {
13
15
  return new Promise(() => { });
14
16
  }
@@ -192,6 +194,44 @@ function unwrapEnvironmentCommand(command, args) {
192
194
  function hasOwnEnumerableKeys(value) {
193
195
  return value && typeof value === "object" && Object.keys(value).length > 0;
194
196
  }
197
+ function resolveCopilotPlatformPackageName(platform = process.platform, arch = process.arch) {
198
+ if (!["darwin", "linux", "win32"].includes(platform)) {
199
+ return null;
200
+ }
201
+ if (!["arm64", "x64"].includes(arch)) {
202
+ return null;
203
+ }
204
+ return `@github/copilot-${platform}-${arch}`;
205
+ }
206
+ function resolvePackageFileFromSearchPaths(packageName, relativePath, resolvePackagePaths, existsSyncFn) {
207
+ const searchPaths = resolvePackagePaths(packageName) || [];
208
+ const packageParts = packageName.split("/");
209
+ for (const basePath of searchPaths) {
210
+ const candidate = path.join(basePath, ...packageParts, relativePath);
211
+ if (existsSyncFn(candidate)) {
212
+ return candidate;
213
+ }
214
+ }
215
+ return null;
216
+ }
217
+ export function resolveBundledCopilotCliPath({ platform = process.platform, arch = process.arch, resolvePackage = (packageName) => moduleRequire.resolve(packageName), resolvePackagePaths = (packageName) => moduleRequire.resolve.paths(packageName) || [], existsSyncFn = existsSync, } = {}) {
218
+ const platformPackageName = resolveCopilotPlatformPackageName(platform, arch);
219
+ if (platformPackageName) {
220
+ try {
221
+ const platformExecutablePath = resolvePackage(platformPackageName);
222
+ if (platformExecutablePath && existsSyncFn(platformExecutablePath)) {
223
+ return platformExecutablePath;
224
+ }
225
+ }
226
+ catch {
227
+ // Optional platform packages may be absent when optional dependencies are disabled.
228
+ }
229
+ }
230
+ return resolvePackageFileFromSearchPaths("@github/copilot", "npm-loader.js", resolvePackagePaths, existsSyncFn);
231
+ }
232
+ function hasExplicitCopilotCliPathEnv(env) {
233
+ return typeof env?.COPILOT_CLI_PATH === "string" && env.COPILOT_CLI_PATH.trim();
234
+ }
195
235
  function resolveCopilotCliLaunch(commandLine, env = process.env) {
196
236
  const normalized = typeof commandLine === "string" ? commandLine.trim() : "";
197
237
  if (!normalized) {
@@ -395,6 +435,14 @@ function buildCopilotClientOptions(options, cwd, env) {
395
435
  clientOptions.env = hasExplicitGithubToken
396
436
  ? resolvedEnv
397
437
  : withoutCopilotGithubTokenEnv(resolvedEnv);
438
+ if (clientOptions.cliPath === undefined &&
439
+ clientOptions.cliUrl === undefined &&
440
+ !hasExplicitCopilotCliPathEnv(clientOptions.env)) {
441
+ const bundledCliPath = resolveBundledCopilotCliPath();
442
+ if (bundledCliPath) {
443
+ clientOptions.cliPath = bundledCliPath;
444
+ }
445
+ }
398
446
  if (!hasExplicitGithubToken && clientOptions.useLoggedInUser === undefined) {
399
447
  clientOptions.useLoggedInUser = true;
400
448
  }
@@ -1,11 +1,11 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { EventEmitter } from "node:events";
3
+ import { KIMI_CLI_WIRE_VARIANT as KIMI_PROVIDER_VARIANT } from "../built-in-backends.js";
3
4
  import { KimiWireTransport } from "../transports/kimi-wire-transport.js";
4
5
  import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, proxyToEnv, sanitizeForLog, } from "../shared.js";
5
6
  const DEFAULT_TURN_DEADLINE_MS = 12 * 60 * 1000;
6
7
  const MIN_TURN_DEADLINE_MS = 30 * 1000;
7
8
  const MAX_TURN_DEADLINE_MS = 30 * 60 * 1000;
8
- const KIMI_PROVIDER_VARIANT = "kimi-cli-wire";
9
9
  const DEFAULT_STATUS_DEDUPE_MS = 120;
10
10
  const DEFAULT_STATUS_THROTTLE_MS = 450;
11
11
  const DEFAULT_REASONING_STATUS_THROTTLE_MS = 2500;
@@ -4,11 +4,11 @@ import { spawn } from "node:child_process";
4
4
  import { randomUUID } from "node:crypto";
5
5
  import { EventEmitter } from "node:events";
6
6
  import readline from "node:readline";
7
+ import { KIMI_CLI_PRINT_VARIANT as KIMI_PRINT_PROVIDER_VARIANT } from "../built-in-backends.js";
7
8
  import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, parseCommandParts, proxyToEnv, sanitizeForLog, } from "../shared.js";
8
9
  const DEFAULT_TURN_DEADLINE_MS = 12 * 60 * 1000;
9
10
  const MIN_TURN_DEADLINE_MS = 30 * 1000;
10
11
  const MAX_TURN_DEADLINE_MS = 30 * 60 * 1000;
11
- const KIMI_PRINT_PROVIDER_VARIANT = "kimi-cli-print";
12
12
  const DEFAULT_KIMI_COMMAND = "kimi";
13
13
  function createTurnError(message, extras = {}) {
14
14
  const error = new Error(message);
@@ -1,10 +1,10 @@
1
1
  import { EventEmitter } from "node:events";
2
+ import { OPENCODE_SDK_VARIANT as OPENCODE_PROVIDER_VARIANT } from "../built-in-backends.js";
2
3
  import { OpencodeServerTransport } from "../transports/opencode-server-transport.js";
3
4
  import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, proxyToEnv, sanitizeForLog, } from "../shared.js";
4
5
  const DEFAULT_TURN_DEADLINE_MS = 12 * 60 * 1000;
5
6
  const MIN_TURN_DEADLINE_MS = 30 * 1000;
6
7
  const MAX_TURN_DEADLINE_MS = 30 * 60 * 1000;
7
- const OPENCODE_PROVIDER_VARIANT = "opencode-sdk";
8
8
  function waitForever() {
9
9
  return new Promise(() => { });
10
10
  }
@@ -0,0 +1,14 @@
1
+ export function buildCliArgs(sessionId: any): string[];
2
+ export function findSessionPath(sessionId: any, options?: {}): Promise<any>;
3
+ export function extractResumeCwd(sessionPath: any, sessionId: any): Promise<string | null>;
4
+ export function resolveResumeContext(sessionId: any, options?: {}): Promise<{
5
+ provider: any;
6
+ sessionId: any;
7
+ sessionPath: null;
8
+ cwd: any;
9
+ debugMetadata: {
10
+ cwdSource: any;
11
+ sessionPath: null;
12
+ };
13
+ }>;
14
+ export const BACKEND: "claude";
@@ -0,0 +1,138 @@
1
+ import fs from "node:fs";
2
+ import { promises as fsp } from "node:fs";
3
+ import path from "node:path";
4
+ import readline from "node:readline";
5
+ import { buildResumeContext, isExistingDirectory, normalizeSessionId, pathExists, resolveHomeDir, resolveSessionRunDirectory, } from "./shared.js";
6
+ export const BACKEND = "claude";
7
+ export function buildCliArgs(sessionId) {
8
+ const normalizedSessionId = normalizeSessionId(sessionId);
9
+ if (!normalizedSessionId) {
10
+ return [];
11
+ }
12
+ return ["--resume", normalizedSessionId];
13
+ }
14
+ export async function findSessionPath(sessionId, options = {}) {
15
+ const normalizedSessionId = normalizeSessionId(sessionId);
16
+ if (!normalizedSessionId) {
17
+ return null;
18
+ }
19
+ const homeDir = resolveHomeDir(options);
20
+ const projectsDir = options.claudeProjectsDir || path.join(homeDir, ".claude", "projects");
21
+ const sessionEntries = await findClaudeSessionEntries(projectsDir, normalizedSessionId);
22
+ if (sessionEntries.length > 0) {
23
+ return sessionEntries[0]?.source || null;
24
+ }
25
+ const tasksDir = options.claudeTasksDir || path.join(homeDir, ".claude", "tasks");
26
+ const directTaskDir = path.join(tasksDir, normalizedSessionId);
27
+ if (await pathExists(directTaskDir, "directory")) {
28
+ return directTaskDir;
29
+ }
30
+ return null;
31
+ }
32
+ export async function extractResumeCwd(sessionPath, sessionId) {
33
+ if (!sessionPath || !sessionPath.endsWith(".jsonl")) {
34
+ return null;
35
+ }
36
+ const rl = readline.createInterface({
37
+ input: fs.createReadStream(sessionPath),
38
+ crlfDelay: Infinity,
39
+ });
40
+ for await (const line of rl) {
41
+ const trimmed = line.trim();
42
+ if (!trimmed) {
43
+ continue;
44
+ }
45
+ let entry;
46
+ try {
47
+ entry = JSON.parse(trimmed);
48
+ }
49
+ catch {
50
+ continue;
51
+ }
52
+ const idMatches = String(entry?.sessionId || "").trim() === sessionId;
53
+ const maybeCwd = entry?.cwd;
54
+ if (idMatches && typeof maybeCwd === "string" && maybeCwd.trim()) {
55
+ return maybeCwd.trim();
56
+ }
57
+ }
58
+ return null;
59
+ }
60
+ export async function resolveResumeContext(sessionId, options = {}) {
61
+ const normalizedSessionId = normalizeSessionId(sessionId);
62
+ if (!normalizedSessionId) {
63
+ throw new Error("--resume requires a session id");
64
+ }
65
+ const sessionPath = await findSessionPath(normalizedSessionId, options);
66
+ if (!sessionPath) {
67
+ throw new Error(`Invalid --resume session id for claude: ${normalizedSessionId}`);
68
+ }
69
+ const cwdFromSession = await extractResumeCwd(sessionPath, normalizedSessionId);
70
+ const fallbackCwd = await resolveSessionRunDirectory(sessionPath);
71
+ const cwd = cwdFromSession || fallbackCwd;
72
+ if (!cwd) {
73
+ throw new Error(`Could not resolve workspace for claude session ${normalizedSessionId}`);
74
+ }
75
+ if (!(await isExistingDirectory(cwd))) {
76
+ throw new Error(`Resume workspace path does not exist: ${cwd}`);
77
+ }
78
+ return buildResumeContext({
79
+ provider: "claude",
80
+ sessionId: normalizedSessionId,
81
+ sessionPath,
82
+ cwd,
83
+ cwdSource: cwdFromSession ? "session" : "session_path",
84
+ });
85
+ }
86
+ async function findClaudeSessionEntries(projectsDir, sessionId) {
87
+ const entries = [];
88
+ let projectDirs = [];
89
+ try {
90
+ projectDirs = await fsp.readdir(projectsDir, { withFileTypes: true });
91
+ }
92
+ catch {
93
+ return entries;
94
+ }
95
+ for (const projectDir of projectDirs) {
96
+ if (!projectDir.isDirectory()) {
97
+ continue;
98
+ }
99
+ const projectPath = path.join(projectsDir, projectDir.name);
100
+ let files = [];
101
+ try {
102
+ files = await fsp.readdir(projectPath, { withFileTypes: true });
103
+ }
104
+ catch {
105
+ continue;
106
+ }
107
+ for (const file of files) {
108
+ if (!file.isFile()) {
109
+ continue;
110
+ }
111
+ if (!file.name.endsWith(".jsonl") || file.name.startsWith("agent-")) {
112
+ continue;
113
+ }
114
+ const filePath = path.join(projectPath, file.name);
115
+ const rl = readline.createInterface({
116
+ input: fs.createReadStream(filePath),
117
+ crlfDelay: Infinity,
118
+ });
119
+ for await (const line of rl) {
120
+ const trimmed = line.trim();
121
+ if (!trimmed) {
122
+ continue;
123
+ }
124
+ let entry;
125
+ try {
126
+ entry = JSON.parse(trimmed);
127
+ }
128
+ catch {
129
+ continue;
130
+ }
131
+ if (entry.sessionId === sessionId) {
132
+ entries.push({ ...entry, source: filePath });
133
+ }
134
+ }
135
+ }
136
+ }
137
+ return entries;
138
+ }
@@ -0,0 +1,14 @@
1
+ export function buildCliArgs(sessionId: any): string[];
2
+ export function findSessionPath(sessionId: any, options?: {}): Promise<string | null>;
3
+ export function extractResumeCwd(sessionPath: any): Promise<string | null>;
4
+ export function resolveResumeContext(sessionId: any, options?: {}): Promise<{
5
+ provider: any;
6
+ sessionId: any;
7
+ sessionPath: null;
8
+ cwd: any;
9
+ debugMetadata: {
10
+ cwdSource: any;
11
+ sessionPath: null;
12
+ };
13
+ }>;
14
+ export const BACKEND: "codex";
@@ -0,0 +1,81 @@
1
+ import { promises as fsp } from "node:fs";
2
+ import path from "node:path";
3
+ import { buildResumeContext, isExistingDirectory, iterateJsonlEntries, normalizeSessionId, resolveHomeDir, resolveSessionRunDirectory, } from "./shared.js";
4
+ export const BACKEND = "codex";
5
+ export function buildCliArgs(sessionId) {
6
+ const normalizedSessionId = normalizeSessionId(sessionId);
7
+ if (!normalizedSessionId) {
8
+ return [];
9
+ }
10
+ return ["resume", normalizedSessionId];
11
+ }
12
+ export async function findSessionPath(sessionId, options = {}) {
13
+ const normalizedSessionId = normalizeSessionId(sessionId);
14
+ if (!normalizedSessionId) {
15
+ return null;
16
+ }
17
+ const homeDir = resolveHomeDir(options);
18
+ const sessionsDir = options.codexSessionsDir || path.join(homeDir, ".codex", "sessions");
19
+ return findCodexSessionFile(sessionsDir, normalizedSessionId);
20
+ }
21
+ export async function extractResumeCwd(sessionPath) {
22
+ if (!sessionPath || !sessionPath.endsWith(".jsonl")) {
23
+ return null;
24
+ }
25
+ for await (const entry of iterateJsonlEntries(sessionPath)) {
26
+ const maybeCwd = entry?.type === "session_meta" ? entry?.payload?.cwd : null;
27
+ if (typeof maybeCwd === "string" && maybeCwd.trim()) {
28
+ return maybeCwd.trim();
29
+ }
30
+ }
31
+ return null;
32
+ }
33
+ export async function resolveResumeContext(sessionId, options = {}) {
34
+ const normalizedSessionId = normalizeSessionId(sessionId);
35
+ if (!normalizedSessionId) {
36
+ throw new Error("--resume requires a session id");
37
+ }
38
+ const sessionPath = await findSessionPath(normalizedSessionId, options);
39
+ if (!sessionPath) {
40
+ throw new Error(`Invalid --resume session id for codex: ${normalizedSessionId}`);
41
+ }
42
+ const cwdFromSession = await extractResumeCwd(sessionPath);
43
+ const fallbackCwd = await resolveSessionRunDirectory(sessionPath);
44
+ const cwd = cwdFromSession || fallbackCwd;
45
+ if (!cwd) {
46
+ throw new Error(`Could not resolve workspace for codex session ${normalizedSessionId}`);
47
+ }
48
+ if (!(await isExistingDirectory(cwd))) {
49
+ throw new Error(`Resume workspace path does not exist: ${cwd}`);
50
+ }
51
+ return buildResumeContext({
52
+ provider: "codex",
53
+ sessionId: normalizedSessionId,
54
+ sessionPath,
55
+ cwd,
56
+ cwdSource: cwdFromSession ? "session" : "session_path",
57
+ });
58
+ }
59
+ async function findCodexSessionFile(rootDir, sessionId) {
60
+ const queue = [rootDir];
61
+ while (queue.length) {
62
+ const current = queue.pop();
63
+ let entries = [];
64
+ try {
65
+ entries = await fsp.readdir(current, { withFileTypes: true });
66
+ }
67
+ catch {
68
+ continue;
69
+ }
70
+ for (const entry of entries) {
71
+ const fullPath = path.join(current, entry.name);
72
+ if (entry.isDirectory()) {
73
+ queue.push(fullPath);
74
+ }
75
+ else if (entry.isFile() && entry.name.includes(sessionId) && entry.name.endsWith(".jsonl")) {
76
+ return fullPath;
77
+ }
78
+ }
79
+ }
80
+ return null;
81
+ }