@love-moon/ai-sdk 0.2.29 → 0.2.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +47 -7
- package/dist/client.js +214 -14
- package/dist/external-provider-registry.d.ts +4 -0
- package/dist/external-provider-registry.js +143 -0
- package/dist/providers/claude-agent-sdk-session.d.ts +1 -0
- package/dist/providers/claude-agent-sdk-session.js +17 -4
- package/dist/providers/codex-app-server-session.d.ts +3 -2
- package/dist/providers/codex-app-server-session.js +24 -5
- package/dist/providers/opencode-sdk-session.d.ts +1 -0
- package/dist/providers/opencode-sdk-session.js +22 -4
- package/dist/resume.d.ts +26 -0
- package/dist/resume.js +380 -0
- package/dist/session-factory.d.ts +5 -5
- package/dist/session-factory.js +42 -12
- package/dist/tui-session.d.ts +153 -0
- package/dist/tui-session.js +941 -0
- package/dist/worker.js +1 -1
- package/package.json +2 -2
|
@@ -200,10 +200,14 @@ export class OpencodeSdkSession extends EventEmitter {
|
|
|
200
200
|
return this.sessionId;
|
|
201
201
|
}
|
|
202
202
|
get threadOptions() {
|
|
203
|
-
const model =
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
203
|
+
const model = this.sessionInfo?.model ||
|
|
204
|
+
(typeof this.options.model === "string" && this.options.model.trim()
|
|
205
|
+
? this.options.model.trim()
|
|
206
|
+
: this.backend);
|
|
207
|
+
return {
|
|
208
|
+
model,
|
|
209
|
+
modelProvider: this.sessionInfo?.modelProvider || undefined,
|
|
210
|
+
};
|
|
207
211
|
}
|
|
208
212
|
getSnapshot() {
|
|
209
213
|
return {
|
|
@@ -536,9 +540,20 @@ export class OpencodeSdkSession extends EventEmitter {
|
|
|
536
540
|
}
|
|
537
541
|
const changed = this.sessionId !== normalizedSessionId;
|
|
538
542
|
this.sessionId = normalizedSessionId;
|
|
543
|
+
const resolvedModel = typeof this.lastAssistantInfo?.model?.modelID === "string" && this.lastAssistantInfo.model.modelID.trim()
|
|
544
|
+
? this.lastAssistantInfo.model.modelID.trim()
|
|
545
|
+
: typeof this.options.model === "string" && this.options.model.trim()
|
|
546
|
+
? this.options.model.trim()
|
|
547
|
+
: undefined;
|
|
548
|
+
const resolvedModelProvider = typeof this.lastAssistantInfo?.model?.providerID === "string" && this.lastAssistantInfo.model.providerID.trim()
|
|
549
|
+
? this.lastAssistantInfo.model.providerID.trim()
|
|
550
|
+
: undefined;
|
|
539
551
|
this.sessionInfo = {
|
|
552
|
+
...(this.sessionInfo || {}),
|
|
540
553
|
backend: this.backend,
|
|
541
554
|
sessionId: normalizedSessionId,
|
|
555
|
+
model: resolvedModel,
|
|
556
|
+
modelProvider: resolvedModelProvider,
|
|
542
557
|
};
|
|
543
558
|
if (changed) {
|
|
544
559
|
this.trace(`session ready id=${normalizedSessionId}`);
|
|
@@ -874,6 +889,9 @@ export class OpencodeSdkSession extends EventEmitter {
|
|
|
874
889
|
this.activeReplyTarget = "";
|
|
875
890
|
this.lastUsage = this.buildUsageFromAssistantInfo(currentTurn.lastAssistantInfo);
|
|
876
891
|
this.lastAssistantInfo = currentTurn.lastAssistantInfo || null;
|
|
892
|
+
this.applySessionInfo({
|
|
893
|
+
id: this.sessionId,
|
|
894
|
+
});
|
|
877
895
|
await this.emitTerminalWorkingStatus(currentTurn, {
|
|
878
896
|
phase: "turn_completed",
|
|
879
897
|
status_done_line: "opencode finished",
|
package/dist/resume.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function resumeProviderForBackend(backend: any): "codex" | "claude" | "copilot" | null;
|
|
2
|
+
export function findSessionPath(provider: any, sessionId: any, options?: {}): Promise<any>;
|
|
3
|
+
export function findCodexSessionPath(sessionId: any, options?: {}): Promise<string | null>;
|
|
4
|
+
export function findClaudeSessionPath(sessionId: any, options?: {}): Promise<any>;
|
|
5
|
+
export function findCopilotSessionPath(sessionId: any, options?: {}): Promise<string | null>;
|
|
6
|
+
export function resolveSessionRunDirectory(sessionPath: any): Promise<string>;
|
|
7
|
+
export function inspectResumeTarget(backend: any, sessionId: any, options?: {}): Promise<{
|
|
8
|
+
provider: string;
|
|
9
|
+
sessionId: string;
|
|
10
|
+
sessionPath: any;
|
|
11
|
+
cwd: string;
|
|
12
|
+
debugMetadata: {
|
|
13
|
+
cwdSource: string;
|
|
14
|
+
sessionPath: any;
|
|
15
|
+
};
|
|
16
|
+
}>;
|
|
17
|
+
export function resolveResumeContext(backend: any, sessionId: any, options?: {}): Promise<{
|
|
18
|
+
provider: string;
|
|
19
|
+
sessionId: string;
|
|
20
|
+
sessionPath: any;
|
|
21
|
+
cwd: string;
|
|
22
|
+
debugMetadata: {
|
|
23
|
+
cwdSource: string;
|
|
24
|
+
sessionPath: any;
|
|
25
|
+
};
|
|
26
|
+
}>;
|
package/dist/resume.js
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { promises as fsp } from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import readline from "node:readline";
|
|
6
|
+
import yaml from "js-yaml";
|
|
7
|
+
function normalizeBackend(backend) {
|
|
8
|
+
return String(backend || "").trim().toLowerCase();
|
|
9
|
+
}
|
|
10
|
+
function resolveHomeDir(options) {
|
|
11
|
+
if (options?.homeDir) {
|
|
12
|
+
return options.homeDir;
|
|
13
|
+
}
|
|
14
|
+
return os.homedir();
|
|
15
|
+
}
|
|
16
|
+
function normalizeSessionId(sessionId) {
|
|
17
|
+
return typeof sessionId === "string" ? sessionId.trim() : "";
|
|
18
|
+
}
|
|
19
|
+
export function resumeProviderForBackend(backend) {
|
|
20
|
+
const normalizedBackend = normalizeBackend(backend);
|
|
21
|
+
if (normalizedBackend === "codex" || normalizedBackend === "code") {
|
|
22
|
+
return "codex";
|
|
23
|
+
}
|
|
24
|
+
if (normalizedBackend === "claude" || normalizedBackend === "claude-code") {
|
|
25
|
+
return "claude";
|
|
26
|
+
}
|
|
27
|
+
if (normalizedBackend === "copilot") {
|
|
28
|
+
return "copilot";
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
export async function findSessionPath(provider, sessionId, options = {}) {
|
|
33
|
+
const normalizedProvider = String(provider || "").trim().toLowerCase();
|
|
34
|
+
if (normalizedProvider === "codex") {
|
|
35
|
+
return findCodexSessionPath(sessionId, options);
|
|
36
|
+
}
|
|
37
|
+
if (normalizedProvider === "claude") {
|
|
38
|
+
return findClaudeSessionPath(sessionId, options);
|
|
39
|
+
}
|
|
40
|
+
if (normalizedProvider === "copilot") {
|
|
41
|
+
return findCopilotSessionPath(sessionId, options);
|
|
42
|
+
}
|
|
43
|
+
throw new Error(`Unsupported provider: ${provider}`);
|
|
44
|
+
}
|
|
45
|
+
export async function findCodexSessionPath(sessionId, options = {}) {
|
|
46
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
47
|
+
if (!normalizedSessionId) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const homeDir = resolveHomeDir(options);
|
|
51
|
+
const sessionsDir = options.codexSessionsDir || path.join(homeDir, ".codex", "sessions");
|
|
52
|
+
return findCodexSessionFile(sessionsDir, normalizedSessionId);
|
|
53
|
+
}
|
|
54
|
+
export async function findClaudeSessionPath(sessionId, options = {}) {
|
|
55
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
56
|
+
if (!normalizedSessionId) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const homeDir = resolveHomeDir(options);
|
|
60
|
+
const projectsDir = options.claudeProjectsDir || path.join(homeDir, ".claude", "projects");
|
|
61
|
+
const sessionEntries = await findClaudeSessionEntries(projectsDir, normalizedSessionId);
|
|
62
|
+
if (sessionEntries.length > 0) {
|
|
63
|
+
return sessionEntries[0]?.source || null;
|
|
64
|
+
}
|
|
65
|
+
const tasksDir = options.claudeTasksDir || path.join(homeDir, ".claude", "tasks");
|
|
66
|
+
const directTaskDir = path.join(tasksDir, normalizedSessionId);
|
|
67
|
+
if (await pathExists(directTaskDir, "directory")) {
|
|
68
|
+
return directTaskDir;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
export async function findCopilotSessionPath(sessionId, options = {}) {
|
|
73
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
74
|
+
if (!normalizedSessionId) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const homeDir = resolveHomeDir(options);
|
|
78
|
+
const sessionStateDir = options.copilotSessionStateDir || path.join(homeDir, ".copilot", "session-state");
|
|
79
|
+
const directJsonlPath = path.join(sessionStateDir, `${normalizedSessionId}.jsonl`);
|
|
80
|
+
if (await pathExists(directJsonlPath, "file")) {
|
|
81
|
+
return directJsonlPath;
|
|
82
|
+
}
|
|
83
|
+
const directSessionDir = path.join(sessionStateDir, normalizedSessionId);
|
|
84
|
+
if (await pathExists(directSessionDir, "directory")) {
|
|
85
|
+
return directSessionDir;
|
|
86
|
+
}
|
|
87
|
+
return findPathByName(sessionStateDir, normalizedSessionId);
|
|
88
|
+
}
|
|
89
|
+
export async function resolveSessionRunDirectory(sessionPath) {
|
|
90
|
+
const normalizedPath = typeof sessionPath === "string" ? sessionPath.trim() : "";
|
|
91
|
+
if (!normalizedPath) {
|
|
92
|
+
throw new Error("Invalid session path");
|
|
93
|
+
}
|
|
94
|
+
let stats;
|
|
95
|
+
try {
|
|
96
|
+
stats = await fsp.stat(normalizedPath);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
throw new Error(`Session path does not exist: ${normalizedPath}`);
|
|
100
|
+
}
|
|
101
|
+
return stats.isDirectory() ? normalizedPath : path.dirname(normalizedPath);
|
|
102
|
+
}
|
|
103
|
+
export async function inspectResumeTarget(backend, sessionId, options = {}) {
|
|
104
|
+
return resolveResumeContext(backend, sessionId, options);
|
|
105
|
+
}
|
|
106
|
+
export async function resolveResumeContext(backend, sessionId, options = {}) {
|
|
107
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
108
|
+
if (!normalizedSessionId) {
|
|
109
|
+
throw new Error("--resume requires a session id");
|
|
110
|
+
}
|
|
111
|
+
const provider = resumeProviderForBackend(backend);
|
|
112
|
+
if (!provider) {
|
|
113
|
+
throw new Error(`--resume is not supported for backend "${backend}"`);
|
|
114
|
+
}
|
|
115
|
+
const sessionPath = await findSessionPath(provider, normalizedSessionId, options);
|
|
116
|
+
if (!sessionPath) {
|
|
117
|
+
throw new Error(`Invalid --resume session id for ${provider}: ${normalizedSessionId}`);
|
|
118
|
+
}
|
|
119
|
+
const cwdFromSession = await extractResumeCwdFromSession(provider, sessionPath, normalizedSessionId);
|
|
120
|
+
const fallbackCwd = await resolveSessionRunDirectory(sessionPath);
|
|
121
|
+
const cwd = cwdFromSession || fallbackCwd;
|
|
122
|
+
if (!(await isExistingDirectory(cwd))) {
|
|
123
|
+
throw new Error(`Resume workspace path does not exist: ${cwd}`);
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
provider,
|
|
127
|
+
sessionId: normalizedSessionId,
|
|
128
|
+
sessionPath,
|
|
129
|
+
cwd,
|
|
130
|
+
debugMetadata: {
|
|
131
|
+
cwdSource: cwdFromSession ? "session" : "session_path",
|
|
132
|
+
sessionPath,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
async function isExistingDirectory(targetPath) {
|
|
137
|
+
const normalizedPath = typeof targetPath === "string" ? targetPath.trim() : "";
|
|
138
|
+
if (!normalizedPath) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
const stats = await fsp.stat(normalizedPath);
|
|
143
|
+
return stats.isDirectory();
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async function extractCodexResumeCwd(sessionPath) {
|
|
150
|
+
if (!sessionPath.endsWith(".jsonl")) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
const rl = readline.createInterface({
|
|
154
|
+
input: fs.createReadStream(sessionPath),
|
|
155
|
+
crlfDelay: Infinity,
|
|
156
|
+
});
|
|
157
|
+
for await (const line of rl) {
|
|
158
|
+
const trimmed = line.trim();
|
|
159
|
+
if (!trimmed) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
let entry;
|
|
163
|
+
try {
|
|
164
|
+
entry = JSON.parse(trimmed);
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const maybeCwd = entry?.type === "session_meta" ? entry?.payload?.cwd : null;
|
|
170
|
+
if (typeof maybeCwd === "string" && maybeCwd.trim()) {
|
|
171
|
+
return maybeCwd.trim();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
async function extractClaudeResumeCwd(sessionPath, sessionId) {
|
|
177
|
+
if (!sessionPath.endsWith(".jsonl")) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
const rl = readline.createInterface({
|
|
181
|
+
input: fs.createReadStream(sessionPath),
|
|
182
|
+
crlfDelay: Infinity,
|
|
183
|
+
});
|
|
184
|
+
for await (const line of rl) {
|
|
185
|
+
const trimmed = line.trim();
|
|
186
|
+
if (!trimmed) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
let entry;
|
|
190
|
+
try {
|
|
191
|
+
entry = JSON.parse(trimmed);
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const idMatches = String(entry?.sessionId || "").trim() === sessionId;
|
|
197
|
+
const maybeCwd = entry?.cwd;
|
|
198
|
+
if (idMatches && typeof maybeCwd === "string" && maybeCwd.trim()) {
|
|
199
|
+
return maybeCwd.trim();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
async function extractCopilotResumeCwd(sessionPath) {
|
|
205
|
+
let stats;
|
|
206
|
+
try {
|
|
207
|
+
stats = await fsp.stat(sessionPath);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
if (stats.isDirectory()) {
|
|
213
|
+
const workspaceYamlPath = path.join(sessionPath, "workspace.yaml");
|
|
214
|
+
try {
|
|
215
|
+
const yamlContent = await fsp.readFile(workspaceYamlPath, "utf8");
|
|
216
|
+
const parsed = yaml.load(yamlContent);
|
|
217
|
+
const maybeCwd = parsed && typeof parsed === "object" ? parsed.cwd : null;
|
|
218
|
+
if (typeof maybeCwd === "string" && maybeCwd.trim()) {
|
|
219
|
+
return maybeCwd.trim();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
if (!sessionPath.endsWith(".jsonl")) {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
const rl = readline.createInterface({
|
|
231
|
+
input: fs.createReadStream(sessionPath),
|
|
232
|
+
crlfDelay: Infinity,
|
|
233
|
+
});
|
|
234
|
+
for await (const line of rl) {
|
|
235
|
+
const trimmed = line.trim();
|
|
236
|
+
if (!trimmed) {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
let entry;
|
|
240
|
+
try {
|
|
241
|
+
entry = JSON.parse(trimmed);
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const maybeCwd = entry?.data?.context?.cwd || entry?.data?.cwd;
|
|
247
|
+
if (typeof maybeCwd === "string" && maybeCwd.trim()) {
|
|
248
|
+
return maybeCwd.trim();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
async function extractResumeCwdFromSession(provider, sessionPath, sessionId) {
|
|
254
|
+
if (provider === "codex") {
|
|
255
|
+
return extractCodexResumeCwd(sessionPath);
|
|
256
|
+
}
|
|
257
|
+
if (provider === "claude") {
|
|
258
|
+
return extractClaudeResumeCwd(sessionPath, sessionId);
|
|
259
|
+
}
|
|
260
|
+
if (provider === "copilot") {
|
|
261
|
+
return extractCopilotResumeCwd(sessionPath);
|
|
262
|
+
}
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
async function findCodexSessionFile(rootDir, sessionId) {
|
|
266
|
+
const queue = [rootDir];
|
|
267
|
+
while (queue.length) {
|
|
268
|
+
const current = queue.pop();
|
|
269
|
+
let entries = [];
|
|
270
|
+
try {
|
|
271
|
+
entries = await fsp.readdir(current, { withFileTypes: true });
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
for (const entry of entries) {
|
|
277
|
+
const fullPath = path.join(current, entry.name);
|
|
278
|
+
if (entry.isDirectory()) {
|
|
279
|
+
queue.push(fullPath);
|
|
280
|
+
}
|
|
281
|
+
else if (entry.isFile() &&
|
|
282
|
+
entry.name.includes(sessionId) &&
|
|
283
|
+
entry.name.endsWith(".jsonl")) {
|
|
284
|
+
return fullPath;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
async function findClaudeSessionEntries(projectsDir, sessionId) {
|
|
291
|
+
const entries = [];
|
|
292
|
+
let projectDirs = [];
|
|
293
|
+
try {
|
|
294
|
+
projectDirs = await fsp.readdir(projectsDir, { withFileTypes: true });
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
return entries;
|
|
298
|
+
}
|
|
299
|
+
for (const projectDir of projectDirs) {
|
|
300
|
+
if (!projectDir.isDirectory()) {
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
const projectPath = path.join(projectsDir, projectDir.name);
|
|
304
|
+
let files = [];
|
|
305
|
+
try {
|
|
306
|
+
files = await fsp.readdir(projectPath, { withFileTypes: true });
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
for (const file of files) {
|
|
312
|
+
if (!file.isFile()) {
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
if (!file.name.endsWith(".jsonl") || file.name.startsWith("agent-")) {
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
const filePath = path.join(projectPath, file.name);
|
|
319
|
+
const rl = readline.createInterface({
|
|
320
|
+
input: fs.createReadStream(filePath),
|
|
321
|
+
crlfDelay: Infinity,
|
|
322
|
+
});
|
|
323
|
+
for await (const line of rl) {
|
|
324
|
+
const trimmed = line.trim();
|
|
325
|
+
if (!trimmed) {
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
let entry;
|
|
329
|
+
try {
|
|
330
|
+
entry = JSON.parse(trimmed);
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
if (entry.sessionId === sessionId) {
|
|
336
|
+
entries.push({ ...entry, source: filePath });
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return entries;
|
|
342
|
+
}
|
|
343
|
+
async function findPathByName(rootDir, sessionId) {
|
|
344
|
+
const queue = [rootDir];
|
|
345
|
+
while (queue.length) {
|
|
346
|
+
const current = queue.pop();
|
|
347
|
+
let entries = [];
|
|
348
|
+
try {
|
|
349
|
+
entries = await fsp.readdir(current, { withFileTypes: true });
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
for (const entry of entries) {
|
|
355
|
+
const fullPath = path.join(current, entry.name);
|
|
356
|
+
if (entry.name.includes(sessionId)) {
|
|
357
|
+
return fullPath;
|
|
358
|
+
}
|
|
359
|
+
if (entry.isDirectory()) {
|
|
360
|
+
queue.push(fullPath);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
async function pathExists(targetPath, expectedType) {
|
|
367
|
+
try {
|
|
368
|
+
const stats = await fsp.stat(targetPath);
|
|
369
|
+
if (expectedType === "file") {
|
|
370
|
+
return stats.isFile();
|
|
371
|
+
}
|
|
372
|
+
if (expectedType === "directory") {
|
|
373
|
+
return stats.isDirectory();
|
|
374
|
+
}
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export function normalizeBackend(backend: any):
|
|
2
|
-
export function isSupportedBackend(backend: any): boolean
|
|
3
|
-
export function providerVariantForBackend(backend: any):
|
|
4
|
-
export function assertSupportedBackend(backend: any):
|
|
5
|
-
export function createLocalAiSession(backend: any, options?: {}):
|
|
1
|
+
export function normalizeBackend(backend: any, options?: {}): Promise<any>;
|
|
2
|
+
export function isSupportedBackend(backend: any, options?: {}): Promise<boolean>;
|
|
3
|
+
export function providerVariantForBackend(backend: any, options?: {}): Promise<any>;
|
|
4
|
+
export function assertSupportedBackend(backend: any, options?: {}): Promise<any>;
|
|
5
|
+
export function createLocalAiSession(backend: any, options?: {}): Promise<any>;
|
|
6
6
|
export const DEFAULT_PROVIDER_VARIANT: "codex-app-server";
|
|
7
7
|
export const CLAUDE_PROVIDER_VARIANT: "claude-agent-sdk";
|
|
8
8
|
export const KIMI_PROVIDER_VARIANT: "kimi-cli-wire";
|
package/dist/session-factory.js
CHANGED
|
@@ -2,11 +2,12 @@ import { CodexAppServerSession } from "./providers/codex-app-server-session.js";
|
|
|
2
2
|
import { ClaudeAgentSdkSession } from "./providers/claude-agent-sdk-session.js";
|
|
3
3
|
import { KimiCliSession } from "./providers/kimi-cli-session.js";
|
|
4
4
|
import { OpencodeSdkSession } from "./providers/opencode-sdk-session.js";
|
|
5
|
+
import { getExternalProviderDescriptor, resolveExternalBackend, } from "./external-provider-registry.js";
|
|
5
6
|
export const DEFAULT_PROVIDER_VARIANT = "codex-app-server";
|
|
6
7
|
export const CLAUDE_PROVIDER_VARIANT = "claude-agent-sdk";
|
|
7
8
|
export const KIMI_PROVIDER_VARIANT = "kimi-cli-wire";
|
|
8
9
|
export const OPENCODE_PROVIDER_VARIANT = "opencode-sdk";
|
|
9
|
-
|
|
10
|
+
function normalizeBuiltInBackendName(backend) {
|
|
10
11
|
const normalized = String(backend || "").trim().toLowerCase();
|
|
11
12
|
if (normalized === "code") {
|
|
12
13
|
return "codex";
|
|
@@ -22,12 +23,23 @@ export function normalizeBackend(backend) {
|
|
|
22
23
|
}
|
|
23
24
|
return normalized;
|
|
24
25
|
}
|
|
25
|
-
export function
|
|
26
|
-
const normalized =
|
|
27
|
-
|
|
26
|
+
export async function normalizeBackend(backend, options = {}) {
|
|
27
|
+
const normalized = normalizeBuiltInBackendName(backend);
|
|
28
|
+
if (normalized === "codex" || normalized === "claude" || normalized === "kimi" || normalized === "opencode") {
|
|
29
|
+
return normalized;
|
|
30
|
+
}
|
|
31
|
+
return await resolveExternalBackend(normalized, options);
|
|
32
|
+
}
|
|
33
|
+
export async function isSupportedBackend(backend, options = {}) {
|
|
34
|
+
const normalized = await normalizeBackend(backend, options);
|
|
35
|
+
if (normalized === "codex" || normalized === "claude" || normalized === "kimi" || normalized === "opencode") {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
const descriptor = await getExternalProviderDescriptor(normalized, options);
|
|
39
|
+
return Boolean(descriptor);
|
|
28
40
|
}
|
|
29
|
-
export function providerVariantForBackend(backend) {
|
|
30
|
-
const normalized = normalizeBackend(backend);
|
|
41
|
+
export async function providerVariantForBackend(backend, options = {}) {
|
|
42
|
+
const normalized = await normalizeBackend(backend, options);
|
|
31
43
|
if (normalized === "claude") {
|
|
32
44
|
return CLAUDE_PROVIDER_VARIANT;
|
|
33
45
|
}
|
|
@@ -37,17 +49,28 @@ export function providerVariantForBackend(backend) {
|
|
|
37
49
|
if (normalized === "opencode") {
|
|
38
50
|
return OPENCODE_PROVIDER_VARIANT;
|
|
39
51
|
}
|
|
52
|
+
if (normalized === "codex") {
|
|
53
|
+
return DEFAULT_PROVIDER_VARIANT;
|
|
54
|
+
}
|
|
55
|
+
const descriptor = await getExternalProviderDescriptor(normalized, options);
|
|
56
|
+
if (descriptor?.variant) {
|
|
57
|
+
return descriptor.variant;
|
|
58
|
+
}
|
|
40
59
|
return DEFAULT_PROVIDER_VARIANT;
|
|
41
60
|
}
|
|
42
|
-
export function assertSupportedBackend(backend) {
|
|
43
|
-
const normalized = normalizeBackend(backend);
|
|
61
|
+
export async function assertSupportedBackend(backend, options = {}) {
|
|
62
|
+
const normalized = await normalizeBackend(backend, options);
|
|
44
63
|
if (normalized === "codex" || normalized === "claude" || normalized === "kimi" || normalized === "opencode") {
|
|
45
64
|
return normalized;
|
|
46
65
|
}
|
|
47
|
-
|
|
66
|
+
const descriptor = await getExternalProviderDescriptor(normalized, options);
|
|
67
|
+
if (descriptor) {
|
|
68
|
+
return normalized;
|
|
69
|
+
}
|
|
70
|
+
throw new Error(`Unsupported AI SDK backend "${backend}". Built-in backends are codex app-server, claude agent-sdk, kimi cli wire, and opencode sdk. Set AISDK_PROVIDER_PATH to load external providers.`);
|
|
48
71
|
}
|
|
49
|
-
export function createLocalAiSession(backend, options = {}) {
|
|
50
|
-
const normalized = assertSupportedBackend(backend);
|
|
72
|
+
export async function createLocalAiSession(backend, options = {}) {
|
|
73
|
+
const normalized = await assertSupportedBackend(backend, options);
|
|
51
74
|
if (normalized === "claude") {
|
|
52
75
|
return new ClaudeAgentSdkSession(normalized, options);
|
|
53
76
|
}
|
|
@@ -57,7 +80,14 @@ export function createLocalAiSession(backend, options = {}) {
|
|
|
57
80
|
if (normalized === "opencode") {
|
|
58
81
|
return new OpencodeSdkSession(normalized, options);
|
|
59
82
|
}
|
|
60
|
-
|
|
83
|
+
if (normalized === "codex") {
|
|
84
|
+
return new CodexAppServerSession(normalized, options);
|
|
85
|
+
}
|
|
86
|
+
const descriptor = await getExternalProviderDescriptor(normalized, options);
|
|
87
|
+
if (!descriptor) {
|
|
88
|
+
throw new Error(`External AI SDK provider "${normalized}" is unavailable.`);
|
|
89
|
+
}
|
|
90
|
+
return await descriptor.createSession(normalized, options);
|
|
61
91
|
}
|
|
62
92
|
export { CodexAppServerSession };
|
|
63
93
|
export { ClaudeAgentSdkSession };
|