@love-moon/ai-sdk 0.2.41 → 0.3.1

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,19 @@
1
+ # @love-moon/ai-sdk
2
+
3
+ ## 0.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 4e8d4e5: Include `CHANGELOG.md` in published npm tarballs.
8
+
9
+ The `files` array in each package's `package.json` previously only
10
+ listed the build output (`bin`/`src` for the CLI, `dist` for the
11
+ modules). npm's `files` whitelist replaces the default include set,
12
+ and CHANGELOG is not one of the auto-included files (only
13
+ `package.json`, `README*`, `LICENSE*`, and `main` are unconditional).
14
+
15
+ As a result, every release through 0.3.0 published tarballs with no
16
+ CHANGELOG, so a consumer running `npm install` or unpacking the brew
17
+ artifact had no way to see what changed in the version they just
18
+ installed. The repo `cli/CHANGELOG.md` and the GitHub Release body
19
+ 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,13 +1,13 @@
1
1
  import { EventEmitter } from "node:events";
2
2
  import { existsSync } from "node:fs";
3
3
  import path from "node:path";
4
+ import { COPILOT_SDK_VARIANT as COPILOT_PROVIDER_VARIANT } from "../built-in-backends.js";
4
5
  import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, parseCommandParts, proxyToEnv, sanitizeForLog, withoutCopilotGithubTokenEnv, } 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
9
  const DEFAULT_CLOSE_TIMEOUT_MS = 5 * 1000;
9
10
  const SDK_SEND_AND_WAIT_TIMEOUT_GRACE_MS = 5 * 1000;
10
- const COPILOT_PROVIDER_VARIANT = "copilot-sdk";
11
11
  const LEGACY_COPILOT_CLI_ARGS = new Set(["--allow-all-paths", "--allow-all-tools"]);
12
12
  function waitForever() {
13
13
  return new Promise(() => { });
@@ -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
+ }
@@ -0,0 +1,14 @@
1
+ export function buildCliArgs(sessionId: any): string[];
2
+ export function findSessionPath(): Promise<null>;
3
+ export function resolveResumeContext(sessionId: any, options?: {}): Promise<{
4
+ provider: any;
5
+ sessionId: any;
6
+ sessionPath: null;
7
+ cwd: any;
8
+ debugMetadata: {
9
+ cwdSource: any;
10
+ sessionPath: null;
11
+ };
12
+ }>;
13
+ export const BACKEND: "copilot";
14
+ export const _COPILOT_GITHUB_TOKEN_ENV_KEYS: string[];