@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
package/dist/client.d.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
export function createAiSession(backend: any, options?: {}):
|
|
1
|
+
export function createAiSession(backend: any, options?: {}): RemoteAiSession | LocalAiSessionProxy;
|
|
2
2
|
export class RemoteAiSession extends EventEmitter<[never]> {
|
|
3
3
|
constructor(backend: any, options?: {});
|
|
4
|
-
backend: string;
|
|
5
4
|
options: {};
|
|
6
5
|
logger: any;
|
|
7
|
-
variant: string;
|
|
8
6
|
threadIdValue: any;
|
|
9
7
|
threadOptionsValue: {
|
|
10
8
|
model: any;
|
|
@@ -12,8 +10,8 @@ export class RemoteAiSession extends EventEmitter<[never]> {
|
|
|
12
10
|
useSessionFileReplyStreamValue: boolean;
|
|
13
11
|
sessionInfo: any;
|
|
14
12
|
snapshot: {
|
|
15
|
-
backend:
|
|
16
|
-
provider:
|
|
13
|
+
backend: undefined;
|
|
14
|
+
provider: undefined;
|
|
17
15
|
sessionId: any;
|
|
18
16
|
useSessionFileReplyStream: boolean;
|
|
19
17
|
workerReady: boolean;
|
|
@@ -31,14 +29,18 @@ export class RemoteAiSession extends EventEmitter<[never]> {
|
|
|
31
29
|
resolveReady: ((value: any) => void) | null;
|
|
32
30
|
rejectReady: ((reason?: any) => void) | null;
|
|
33
31
|
readyPromise: Promise<any>;
|
|
32
|
+
initializeSession(backend: any, options: any): Promise<void>;
|
|
33
|
+
backend: any;
|
|
34
|
+
variant: any;
|
|
34
35
|
get threadId(): any;
|
|
35
36
|
get threadOptions(): {
|
|
37
|
+
modelProvider?: any;
|
|
36
38
|
model: any;
|
|
37
39
|
};
|
|
38
40
|
getSnapshot(): {
|
|
39
41
|
sessionInfo: any;
|
|
40
|
-
backend:
|
|
41
|
-
provider:
|
|
42
|
+
backend: undefined;
|
|
43
|
+
provider: undefined;
|
|
42
44
|
sessionId: any;
|
|
43
45
|
useSessionFileReplyStream: boolean;
|
|
44
46
|
workerReady: boolean;
|
|
@@ -63,5 +65,43 @@ export class RemoteAiSession extends EventEmitter<[never]> {
|
|
|
63
65
|
handleWorkerEvent(payload: any): Promise<void>;
|
|
64
66
|
rejectPendingRequests(error: any): void;
|
|
65
67
|
}
|
|
68
|
+
declare class LocalAiSessionProxy extends EventEmitter<[never]> {
|
|
69
|
+
constructor(backend: any, options?: {});
|
|
70
|
+
backend: any;
|
|
71
|
+
options: {};
|
|
72
|
+
threadIdValue: any;
|
|
73
|
+
threadOptionsValue: {
|
|
74
|
+
model: any;
|
|
75
|
+
};
|
|
76
|
+
useSessionFileReplyStreamValue: boolean;
|
|
77
|
+
sessionInfo: any;
|
|
78
|
+
session: any;
|
|
79
|
+
closed: boolean;
|
|
80
|
+
sessionMessageHandler: any;
|
|
81
|
+
workingStatusHandler: any;
|
|
82
|
+
replyTarget: any;
|
|
83
|
+
snapshot: {
|
|
84
|
+
backend: undefined;
|
|
85
|
+
provider: undefined;
|
|
86
|
+
sessionId: any;
|
|
87
|
+
useSessionFileReplyStream: boolean;
|
|
88
|
+
workerReady: boolean;
|
|
89
|
+
};
|
|
90
|
+
readyPromise: Promise<any>;
|
|
91
|
+
initializeSession(backend: any, options: any): Promise<any>;
|
|
92
|
+
get threadId(): any;
|
|
93
|
+
get threadOptions(): any;
|
|
94
|
+
getSnapshot(): any;
|
|
95
|
+
usesSessionFileReplyStream(): boolean;
|
|
96
|
+
getSessionInfo(): any;
|
|
97
|
+
setSessionMessageHandler(handler: any): void;
|
|
98
|
+
setWorkingStatusHandler(handler: any): void;
|
|
99
|
+
setSessionReplyTarget(replyTarget: any): void;
|
|
100
|
+
ensureSessionInfo(): Promise<any>;
|
|
101
|
+
getSessionUsageSummary(): Promise<any>;
|
|
102
|
+
runTurn(promptText: any, options?: {}): Promise<any>;
|
|
103
|
+
close(): Promise<void>;
|
|
104
|
+
}
|
|
66
105
|
import { EventEmitter } from "node:events";
|
|
67
106
|
import readline from "node:readline";
|
|
107
|
+
export {};
|
package/dist/client.js
CHANGED
|
@@ -26,10 +26,8 @@ function toSerializablePayload(payload) {
|
|
|
26
26
|
export class RemoteAiSession extends EventEmitter {
|
|
27
27
|
constructor(backend, options = {}) {
|
|
28
28
|
super();
|
|
29
|
-
this.backend = assertSupportedBackend(backend);
|
|
30
29
|
this.options = options;
|
|
31
30
|
this.logger = normalizeLogger(options.logger);
|
|
32
|
-
this.variant = providerVariantForBackend(this.backend);
|
|
33
31
|
this.threadIdValue =
|
|
34
32
|
typeof options.resumeSessionId === "string" && options.resumeSessionId.trim()
|
|
35
33
|
? options.resumeSessionId.trim()
|
|
@@ -37,13 +35,13 @@ export class RemoteAiSession extends EventEmitter {
|
|
|
37
35
|
this.threadOptionsValue = {
|
|
38
36
|
model: typeof options.model === "string" && options.model.trim()
|
|
39
37
|
? options.model.trim()
|
|
40
|
-
:
|
|
38
|
+
: String(backend || "unknown").trim() || "unknown",
|
|
41
39
|
};
|
|
42
40
|
this.useSessionFileReplyStreamValue = true;
|
|
43
41
|
this.sessionInfo = null;
|
|
44
42
|
this.snapshot = {
|
|
45
|
-
backend:
|
|
46
|
-
provider:
|
|
43
|
+
backend: undefined,
|
|
44
|
+
provider: undefined,
|
|
47
45
|
sessionId: this.threadIdValue || undefined,
|
|
48
46
|
useSessionFileReplyStream: this.useSessionFileReplyStreamValue,
|
|
49
47
|
workerReady: false,
|
|
@@ -94,17 +92,45 @@ export class RemoteAiSession extends EventEmitter {
|
|
|
94
92
|
this.rejectReady?.(error);
|
|
95
93
|
this.rejectPendingRequests(error);
|
|
96
94
|
});
|
|
97
|
-
this.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
95
|
+
void this.initializeSession(backend, options);
|
|
96
|
+
}
|
|
97
|
+
async initializeSession(backend, options) {
|
|
98
|
+
try {
|
|
99
|
+
this.backend = await assertSupportedBackend(backend, options);
|
|
100
|
+
this.variant = await providerVariantForBackend(backend, options);
|
|
101
|
+
this.snapshot.backend = this.backend;
|
|
102
|
+
this.snapshot.provider = this.variant;
|
|
103
|
+
this.threadOptionsValue.model =
|
|
104
|
+
typeof options.model === "string" && options.model.trim() ? options.model.trim() : this.backend || "unknown";
|
|
105
|
+
this.child.stdin.write(toSerializablePayload({
|
|
106
|
+
type: "create",
|
|
107
|
+
backend: this.backend,
|
|
108
|
+
options: sanitizeOptionsForWorker(options),
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
this.rejectReady?.(error);
|
|
113
|
+
this.resolveReady = null;
|
|
114
|
+
this.rejectReady = null;
|
|
115
|
+
this.rejectPendingRequests(error);
|
|
116
|
+
try {
|
|
117
|
+
this.child.kill("SIGTERM");
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// ignore
|
|
121
|
+
}
|
|
122
|
+
}
|
|
102
123
|
}
|
|
103
124
|
get threadId() {
|
|
104
125
|
return this.threadIdValue;
|
|
105
126
|
}
|
|
106
127
|
get threadOptions() {
|
|
107
|
-
|
|
128
|
+
const sessionInfo = this.sessionInfo && typeof this.sessionInfo === "object" ? this.sessionInfo : null;
|
|
129
|
+
return {
|
|
130
|
+
...this.threadOptionsValue,
|
|
131
|
+
...(sessionInfo?.model ? { model: sessionInfo.model } : {}),
|
|
132
|
+
...(sessionInfo?.modelProvider ? { modelProvider: sessionInfo.modelProvider } : {}),
|
|
133
|
+
};
|
|
108
134
|
}
|
|
109
135
|
getSnapshot() {
|
|
110
136
|
return {
|
|
@@ -352,10 +378,184 @@ export class RemoteAiSession extends EventEmitter {
|
|
|
352
378
|
this.pendingRequests.clear();
|
|
353
379
|
}
|
|
354
380
|
}
|
|
381
|
+
const LOCAL_SESSION_EVENT_NAMES = ["session", "assistant_message", "working_status", "auth_required", "process.exited"];
|
|
382
|
+
class LocalAiSessionProxy extends EventEmitter {
|
|
383
|
+
constructor(backend, options = {}) {
|
|
384
|
+
super();
|
|
385
|
+
this.backend = undefined;
|
|
386
|
+
this.options = options;
|
|
387
|
+
this.threadIdValue =
|
|
388
|
+
typeof options.resumeSessionId === "string" && options.resumeSessionId.trim()
|
|
389
|
+
? options.resumeSessionId.trim()
|
|
390
|
+
: "";
|
|
391
|
+
this.threadOptionsValue = {
|
|
392
|
+
model: typeof options.model === "string" && options.model.trim()
|
|
393
|
+
? options.model.trim()
|
|
394
|
+
: String(backend || "unknown").trim() || "unknown",
|
|
395
|
+
};
|
|
396
|
+
this.useSessionFileReplyStreamValue = true;
|
|
397
|
+
this.sessionInfo = null;
|
|
398
|
+
this.session = null;
|
|
399
|
+
this.closed = false;
|
|
400
|
+
this.sessionMessageHandler = null;
|
|
401
|
+
this.workingStatusHandler = null;
|
|
402
|
+
this.replyTarget = undefined;
|
|
403
|
+
this.snapshot = {
|
|
404
|
+
backend: undefined,
|
|
405
|
+
provider: undefined,
|
|
406
|
+
sessionId: this.threadIdValue || undefined,
|
|
407
|
+
useSessionFileReplyStream: this.useSessionFileReplyStreamValue,
|
|
408
|
+
workerReady: false,
|
|
409
|
+
};
|
|
410
|
+
this.readyPromise = this.initializeSession(backend, options);
|
|
411
|
+
this.readyPromise.catch(() => {
|
|
412
|
+
// keep parity with RemoteAiSession and avoid unhandled rejections
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
async initializeSession(backend, options) {
|
|
416
|
+
const session = await createLocalAiSession(backend, options);
|
|
417
|
+
if (this.closed) {
|
|
418
|
+
await session.close?.();
|
|
419
|
+
return session;
|
|
420
|
+
}
|
|
421
|
+
this.session = session;
|
|
422
|
+
for (const eventName of LOCAL_SESSION_EVENT_NAMES) {
|
|
423
|
+
if (typeof session.on === "function") {
|
|
424
|
+
session.on(eventName, async (payload) => {
|
|
425
|
+
if (eventName === "session" && payload && typeof payload === "object") {
|
|
426
|
+
this.sessionInfo = { ...payload };
|
|
427
|
+
if (payload.sessionId) {
|
|
428
|
+
this.threadIdValue = String(payload.sessionId);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
this.emit(eventName, payload);
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (this.sessionMessageHandler && typeof session.setSessionMessageHandler === "function") {
|
|
436
|
+
session.setSessionMessageHandler(this.sessionMessageHandler);
|
|
437
|
+
}
|
|
438
|
+
if (this.workingStatusHandler && typeof session.setWorkingStatusHandler === "function") {
|
|
439
|
+
session.setWorkingStatusHandler(this.workingStatusHandler);
|
|
440
|
+
}
|
|
441
|
+
if (this.replyTarget !== undefined && typeof session.setSessionReplyTarget === "function") {
|
|
442
|
+
session.setSessionReplyTarget(this.replyTarget);
|
|
443
|
+
}
|
|
444
|
+
const snapshot = typeof session.getSnapshot === "function" ? session.getSnapshot() : {};
|
|
445
|
+
this.backend = snapshot.backend || session.backend || this.backend;
|
|
446
|
+
this.threadIdValue = session.threadId || snapshot.sessionId || this.threadIdValue;
|
|
447
|
+
this.threadOptionsValue = session.threadOptions ? { ...session.threadOptions } : this.threadOptionsValue;
|
|
448
|
+
this.useSessionFileReplyStreamValue =
|
|
449
|
+
typeof session.usesSessionFileReplyStream === "function"
|
|
450
|
+
? Boolean(session.usesSessionFileReplyStream())
|
|
451
|
+
: snapshot.useSessionFileReplyStream !== undefined
|
|
452
|
+
? Boolean(snapshot.useSessionFileReplyStream)
|
|
453
|
+
: this.useSessionFileReplyStreamValue;
|
|
454
|
+
this.sessionInfo =
|
|
455
|
+
typeof session.getSessionInfo === "function"
|
|
456
|
+
? session.getSessionInfo()
|
|
457
|
+
: snapshot.sessionInfo && typeof snapshot.sessionInfo === "object"
|
|
458
|
+
? { ...snapshot.sessionInfo }
|
|
459
|
+
: this.sessionInfo;
|
|
460
|
+
this.snapshot = {
|
|
461
|
+
...this.snapshot,
|
|
462
|
+
...snapshot,
|
|
463
|
+
backend: this.backend,
|
|
464
|
+
sessionId: this.threadIdValue || undefined,
|
|
465
|
+
sessionInfo: this.sessionInfo,
|
|
466
|
+
useSessionFileReplyStream: this.useSessionFileReplyStreamValue,
|
|
467
|
+
workerReady: true,
|
|
468
|
+
};
|
|
469
|
+
return session;
|
|
470
|
+
}
|
|
471
|
+
get threadId() {
|
|
472
|
+
return this.session?.threadId || this.threadIdValue;
|
|
473
|
+
}
|
|
474
|
+
get threadOptions() {
|
|
475
|
+
return this.session?.threadOptions ? { ...this.session.threadOptions } : { ...this.threadOptionsValue };
|
|
476
|
+
}
|
|
477
|
+
getSnapshot() {
|
|
478
|
+
const sessionSnapshot = typeof this.session?.getSnapshot === "function" ? this.session.getSnapshot() : null;
|
|
479
|
+
if (sessionSnapshot) {
|
|
480
|
+
return {
|
|
481
|
+
...this.snapshot,
|
|
482
|
+
...sessionSnapshot,
|
|
483
|
+
backend: sessionSnapshot.backend || this.snapshot.backend,
|
|
484
|
+
sessionId: sessionSnapshot.sessionId || this.threadIdValue || undefined,
|
|
485
|
+
sessionInfo: typeof this.session?.getSessionInfo === "function"
|
|
486
|
+
? this.session.getSessionInfo()
|
|
487
|
+
: sessionSnapshot.sessionInfo || this.sessionInfo || null,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
return {
|
|
491
|
+
...this.snapshot,
|
|
492
|
+
sessionInfo: this.sessionInfo ? { ...this.sessionInfo } : null,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
usesSessionFileReplyStream() {
|
|
496
|
+
if (typeof this.session?.usesSessionFileReplyStream === "function") {
|
|
497
|
+
return Boolean(this.session.usesSessionFileReplyStream());
|
|
498
|
+
}
|
|
499
|
+
return Boolean(this.useSessionFileReplyStreamValue);
|
|
500
|
+
}
|
|
501
|
+
getSessionInfo() {
|
|
502
|
+
if (typeof this.session?.getSessionInfo === "function") {
|
|
503
|
+
return this.session.getSessionInfo();
|
|
504
|
+
}
|
|
505
|
+
return this.sessionInfo ? { ...this.sessionInfo } : null;
|
|
506
|
+
}
|
|
507
|
+
setSessionMessageHandler(handler) {
|
|
508
|
+
this.sessionMessageHandler = typeof handler === "function" ? handler : null;
|
|
509
|
+
if (typeof this.session?.setSessionMessageHandler === "function") {
|
|
510
|
+
this.session.setSessionMessageHandler(this.sessionMessageHandler);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
setWorkingStatusHandler(handler) {
|
|
514
|
+
this.workingStatusHandler = typeof handler === "function" ? handler : null;
|
|
515
|
+
if (typeof this.session?.setWorkingStatusHandler === "function") {
|
|
516
|
+
this.session.setWorkingStatusHandler(this.workingStatusHandler);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
setSessionReplyTarget(replyTarget) {
|
|
520
|
+
this.replyTarget = replyTarget;
|
|
521
|
+
if (typeof this.session?.setSessionReplyTarget === "function") {
|
|
522
|
+
this.session.setSessionReplyTarget(replyTarget);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
async ensureSessionInfo() {
|
|
526
|
+
const session = await this.readyPromise;
|
|
527
|
+
const sessionInfo = await session.ensureSessionInfo();
|
|
528
|
+
this.sessionInfo = sessionInfo && typeof sessionInfo === "object" ? { ...sessionInfo } : sessionInfo;
|
|
529
|
+
if (sessionInfo?.sessionId) {
|
|
530
|
+
this.threadIdValue = String(sessionInfo.sessionId);
|
|
531
|
+
}
|
|
532
|
+
return sessionInfo;
|
|
533
|
+
}
|
|
534
|
+
async getSessionUsageSummary() {
|
|
535
|
+
const session = await this.readyPromise;
|
|
536
|
+
return await session.getSessionUsageSummary();
|
|
537
|
+
}
|
|
538
|
+
async runTurn(promptText, options = {}) {
|
|
539
|
+
const session = await this.readyPromise;
|
|
540
|
+
return await session.runTurn(promptText, options);
|
|
541
|
+
}
|
|
542
|
+
async close() {
|
|
543
|
+
if (this.closed) {
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
this.closed = true;
|
|
547
|
+
try {
|
|
548
|
+
const session = await this.readyPromise;
|
|
549
|
+
await session.close?.();
|
|
550
|
+
}
|
|
551
|
+
catch {
|
|
552
|
+
// best effort
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
355
556
|
export function createAiSession(backend, options = {}) {
|
|
356
|
-
const normalizedBackend = assertSupportedBackend(backend);
|
|
357
557
|
if (process.env.CONDUCTOR_AI_SDK_DISABLE_WORKER === "1") {
|
|
358
|
-
return
|
|
558
|
+
return new LocalAiSessionProxy(backend, options);
|
|
359
559
|
}
|
|
360
|
-
return new RemoteAiSession(
|
|
560
|
+
return new RemoteAiSession(backend, options);
|
|
361
561
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export function getExternalProviderRegistry(options?: {}): Promise<any>;
|
|
2
|
+
export function resolveExternalBackend(backend: any, options?: {}): Promise<any>;
|
|
3
|
+
export function getExternalProviderDescriptor(backend: any, options?: {}): Promise<any>;
|
|
4
|
+
export function resetExternalProviderRegistryForTests(): void;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
import { loadEnvConfig } from "./shared.js";
|
|
4
|
+
const BUILT_IN_BACKENDS = new Set(["codex", "claude", "kimi", "opencode"]);
|
|
5
|
+
const registryPromises = new Map();
|
|
6
|
+
let externalProviderImportNonce = 0;
|
|
7
|
+
function normalizeProviderPathEnv(value) {
|
|
8
|
+
return String(value || "").trim();
|
|
9
|
+
}
|
|
10
|
+
function listProviderModulePathsFromValue(rawValue) {
|
|
11
|
+
const raw = normalizeProviderPathEnv(rawValue);
|
|
12
|
+
if (!raw) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
const parts = raw
|
|
16
|
+
.split(process.platform === "win32" ? ";" : ":")
|
|
17
|
+
.map((item) => item.trim())
|
|
18
|
+
.filter(Boolean);
|
|
19
|
+
return [...new Set(parts)];
|
|
20
|
+
}
|
|
21
|
+
function resolveProviderPathEnv(options = {}) {
|
|
22
|
+
const envValue = normalizeProviderPathEnv(process.env.AISDK_PROVIDER_PATH);
|
|
23
|
+
if (envValue) {
|
|
24
|
+
return envValue;
|
|
25
|
+
}
|
|
26
|
+
const envConfig = loadEnvConfig(options.configFile);
|
|
27
|
+
return normalizeProviderPathEnv(envConfig?.AISDK_PROVIDER_PATH);
|
|
28
|
+
}
|
|
29
|
+
function normalizeName(value) {
|
|
30
|
+
return String(value || "").trim().toLowerCase();
|
|
31
|
+
}
|
|
32
|
+
function createRegistry() {
|
|
33
|
+
return {
|
|
34
|
+
descriptors: [],
|
|
35
|
+
byBackend: new Map(),
|
|
36
|
+
aliasToBackend: new Map(),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function registerAlias(registry, alias, backend, sourcePath) {
|
|
40
|
+
const normalizedAlias = normalizeName(alias);
|
|
41
|
+
if (!normalizedAlias) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (BUILT_IN_BACKENDS.has(normalizedAlias) && normalizedAlias !== backend) {
|
|
45
|
+
throw new Error(`External AI SDK provider alias "${normalizedAlias}" from ${sourcePath} conflicts with built-in backend "${normalizedAlias}".`);
|
|
46
|
+
}
|
|
47
|
+
const existingBackend = registry.aliasToBackend.get(normalizedAlias);
|
|
48
|
+
if (existingBackend && existingBackend !== backend) {
|
|
49
|
+
throw new Error(`External AI SDK provider alias "${normalizedAlias}" from ${sourcePath} conflicts with backend "${existingBackend}".`);
|
|
50
|
+
}
|
|
51
|
+
registry.aliasToBackend.set(normalizedAlias, backend);
|
|
52
|
+
}
|
|
53
|
+
function validateDescriptor(descriptor, sourcePath) {
|
|
54
|
+
if (!descriptor || typeof descriptor !== "object") {
|
|
55
|
+
throw new Error(`External AI SDK provider module ${sourcePath} contains an invalid provider descriptor.`);
|
|
56
|
+
}
|
|
57
|
+
const backend = normalizeName(descriptor.backend);
|
|
58
|
+
if (!backend) {
|
|
59
|
+
throw new Error(`External AI SDK provider module ${sourcePath} is missing provider.backend.`);
|
|
60
|
+
}
|
|
61
|
+
if (BUILT_IN_BACKENDS.has(backend)) {
|
|
62
|
+
throw new Error(`External AI SDK provider backend "${backend}" from ${sourcePath} conflicts with a built-in backend.`);
|
|
63
|
+
}
|
|
64
|
+
const variant = String(descriptor.variant || "").trim();
|
|
65
|
+
if (!variant) {
|
|
66
|
+
throw new Error(`External AI SDK provider "${backend}" from ${sourcePath} is missing provider.variant.`);
|
|
67
|
+
}
|
|
68
|
+
if (typeof descriptor.createSession !== "function") {
|
|
69
|
+
throw new Error(`External AI SDK provider "${backend}" from ${sourcePath} is missing provider.createSession().`);
|
|
70
|
+
}
|
|
71
|
+
const aliases = Array.isArray(descriptor.aliases) ? descriptor.aliases.map((item) => normalizeName(item)).filter(Boolean) : [];
|
|
72
|
+
return {
|
|
73
|
+
backend,
|
|
74
|
+
variant,
|
|
75
|
+
aliases,
|
|
76
|
+
createSession: descriptor.createSession,
|
|
77
|
+
isSupported: typeof descriptor.isSupported === "function" ? descriptor.isSupported : null,
|
|
78
|
+
sourcePath,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
async function loadRegistry(modulePaths) {
|
|
82
|
+
const registry = createRegistry();
|
|
83
|
+
for (const modulePath of modulePaths) {
|
|
84
|
+
let importedModule;
|
|
85
|
+
try {
|
|
86
|
+
const resolvedPath = path.isAbsolute(modulePath) ? modulePath : path.resolve(modulePath);
|
|
87
|
+
const moduleUrl = pathToFileURL(resolvedPath);
|
|
88
|
+
moduleUrl.searchParams.set("conductor-ai-sdk-provider-attempt", String(++externalProviderImportNonce));
|
|
89
|
+
importedModule = await import(moduleUrl.href);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
throw new Error(`Failed to load external AI SDK provider module ${modulePath}: ${error?.message || error}`);
|
|
93
|
+
}
|
|
94
|
+
const providers = Array.isArray(importedModule?.providers) ? importedModule.providers : [];
|
|
95
|
+
if (providers.length === 0) {
|
|
96
|
+
throw new Error(`External AI SDK provider module ${modulePath} must export a non-empty providers array.`);
|
|
97
|
+
}
|
|
98
|
+
for (const rawDescriptor of providers) {
|
|
99
|
+
const descriptor = validateDescriptor(rawDescriptor, modulePath);
|
|
100
|
+
if (registry.byBackend.has(descriptor.backend)) {
|
|
101
|
+
throw new Error(`External AI SDK provider backend "${descriptor.backend}" is declared more than once (latest: ${modulePath}).`);
|
|
102
|
+
}
|
|
103
|
+
registry.descriptors.push(descriptor);
|
|
104
|
+
registry.byBackend.set(descriptor.backend, descriptor);
|
|
105
|
+
registerAlias(registry, descriptor.backend, descriptor.backend, modulePath);
|
|
106
|
+
for (const alias of descriptor.aliases) {
|
|
107
|
+
registerAlias(registry, alias, descriptor.backend, modulePath);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return registry;
|
|
112
|
+
}
|
|
113
|
+
export async function getExternalProviderRegistry(options = {}) {
|
|
114
|
+
const providerPathEnv = resolveProviderPathEnv(options);
|
|
115
|
+
if (!registryPromises.has(providerPathEnv)) {
|
|
116
|
+
const loadPromise = loadRegistry(listProviderModulePathsFromValue(providerPathEnv)).catch((error) => {
|
|
117
|
+
registryPromises.delete(providerPathEnv);
|
|
118
|
+
throw error;
|
|
119
|
+
});
|
|
120
|
+
registryPromises.set(providerPathEnv, loadPromise);
|
|
121
|
+
}
|
|
122
|
+
return registryPromises.get(providerPathEnv);
|
|
123
|
+
}
|
|
124
|
+
export async function resolveExternalBackend(backend, options = {}) {
|
|
125
|
+
const normalized = normalizeName(backend);
|
|
126
|
+
if (!normalized) {
|
|
127
|
+
return "";
|
|
128
|
+
}
|
|
129
|
+
const registry = await getExternalProviderRegistry(options);
|
|
130
|
+
return registry.aliasToBackend.get(normalized) || normalized;
|
|
131
|
+
}
|
|
132
|
+
export async function getExternalProviderDescriptor(backend, options = {}) {
|
|
133
|
+
const normalized = normalizeName(backend);
|
|
134
|
+
if (!normalized) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
const registry = await getExternalProviderRegistry(options);
|
|
138
|
+
const resolvedBackend = registry.aliasToBackend.get(normalized) || normalized;
|
|
139
|
+
return registry.byBackend.get(resolvedBackend) || null;
|
|
140
|
+
}
|
|
141
|
+
export function resetExternalProviderRegistryForTests() {
|
|
142
|
+
registryPromises.clear();
|
|
143
|
+
}
|
|
@@ -179,10 +179,14 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
179
179
|
return this.sessionId;
|
|
180
180
|
}
|
|
181
181
|
get threadOptions() {
|
|
182
|
-
const model =
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
182
|
+
const model = this.sessionInfo?.model ||
|
|
183
|
+
(typeof this.options.model === "string" && this.options.model.trim()
|
|
184
|
+
? this.options.model.trim()
|
|
185
|
+
: this.backend);
|
|
186
|
+
return {
|
|
187
|
+
model,
|
|
188
|
+
modelProvider: this.sessionInfo?.modelProvider || undefined,
|
|
189
|
+
};
|
|
186
190
|
}
|
|
187
191
|
getSnapshot() {
|
|
188
192
|
return {
|
|
@@ -414,10 +418,19 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
414
418
|
const changed = this.sessionId !== normalizedSessionId;
|
|
415
419
|
this.sessionId = normalizedSessionId;
|
|
416
420
|
this.manualResumeReady = true;
|
|
421
|
+
const modelUsage = this.lastResult?.modelUsage && typeof this.lastResult.modelUsage === "object"
|
|
422
|
+
? this.lastResult.modelUsage
|
|
423
|
+
: null;
|
|
424
|
+
const resolvedModel = typeof modelUsage?.model === "string" && modelUsage.model.trim()
|
|
425
|
+
? modelUsage.model.trim()
|
|
426
|
+
: typeof this.options.model === "string" && this.options.model.trim()
|
|
427
|
+
? this.options.model.trim()
|
|
428
|
+
: undefined;
|
|
417
429
|
this.sessionInfo = {
|
|
418
430
|
...(this.sessionInfo || {}),
|
|
419
431
|
backend: this.backend,
|
|
420
432
|
sessionId: normalizedSessionId,
|
|
433
|
+
model: resolvedModel,
|
|
421
434
|
};
|
|
422
435
|
if (changed) {
|
|
423
436
|
this.trace(`session ready id=${normalizedSessionId}`);
|
|
@@ -37,7 +37,8 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
|
|
|
37
37
|
trace(message: any): void;
|
|
38
38
|
get threadId(): any;
|
|
39
39
|
get threadOptions(): {
|
|
40
|
-
model:
|
|
40
|
+
model: any;
|
|
41
|
+
modelProvider: any;
|
|
41
42
|
};
|
|
42
43
|
getSnapshot(): {
|
|
43
44
|
backend: string;
|
|
@@ -74,7 +75,7 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
|
|
|
74
75
|
getCurrentReplyTarget(): string | undefined;
|
|
75
76
|
boot(): Promise<void>;
|
|
76
77
|
bootInternal(): Promise<void>;
|
|
77
|
-
applyThreadInfo(
|
|
78
|
+
applyThreadInfo(payload: any, { resumeReady }?: {
|
|
78
79
|
resumeReady?: boolean | undefined;
|
|
79
80
|
}): void;
|
|
80
81
|
applySessionConfigured(params: any): void;
|
|
@@ -224,7 +224,10 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
224
224
|
return this.sessionId;
|
|
225
225
|
}
|
|
226
226
|
get threadOptions() {
|
|
227
|
-
return {
|
|
227
|
+
return {
|
|
228
|
+
model: this.sessionInfo?.model || this.backend,
|
|
229
|
+
modelProvider: this.sessionInfo?.modelProvider || undefined,
|
|
230
|
+
};
|
|
228
231
|
}
|
|
229
232
|
getSnapshot() {
|
|
230
233
|
return {
|
|
@@ -326,9 +329,10 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
326
329
|
else {
|
|
327
330
|
result = await this.transport.request("thread/start", params);
|
|
328
331
|
}
|
|
329
|
-
this.applyThreadInfo(result
|
|
332
|
+
this.applyThreadInfo(result, { resumeReady: Boolean(this.resumeSessionId) });
|
|
330
333
|
}
|
|
331
|
-
applyThreadInfo(
|
|
334
|
+
applyThreadInfo(payload, { resumeReady = false } = {}) {
|
|
335
|
+
const thread = payload?.thread && typeof payload.thread === "object" ? payload.thread : payload;
|
|
332
336
|
const threadId = typeof thread?.id === "string" ? thread.id.trim() : "";
|
|
333
337
|
const threadPath = typeof thread?.path === "string" ? thread.path.trim() : "";
|
|
334
338
|
if (!threadId) {
|
|
@@ -336,15 +340,30 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
336
340
|
}
|
|
337
341
|
this.sessionId = threadId;
|
|
338
342
|
this.threadPath = threadPath;
|
|
343
|
+
const resolvedModel = typeof payload?.model === "string" && payload.model.trim()
|
|
344
|
+
? payload.model.trim()
|
|
345
|
+
: this.sessionInfo?.model || undefined;
|
|
346
|
+
const resolvedModelProvider = typeof payload?.modelProvider === "string" && payload.modelProvider.trim()
|
|
347
|
+
? payload.modelProvider.trim()
|
|
348
|
+
: typeof thread?.modelProvider === "string" && thread.modelProvider.trim()
|
|
349
|
+
? thread.modelProvider.trim()
|
|
350
|
+
: this.sessionInfo?.modelProvider || undefined;
|
|
351
|
+
const reasoningEffort = typeof payload?.reasoningEffort === "string" && payload.reasoningEffort.trim()
|
|
352
|
+
? payload.reasoningEffort.trim()
|
|
353
|
+
: this.sessionInfo?.reasoningEffort || undefined;
|
|
339
354
|
this.sessionInfo = {
|
|
355
|
+
...(this.sessionInfo || {}),
|
|
340
356
|
backend: this.backend,
|
|
341
357
|
sessionId: threadId,
|
|
342
358
|
sessionFilePath: threadPath || undefined,
|
|
359
|
+
model: resolvedModel,
|
|
360
|
+
modelProvider: resolvedModelProvider,
|
|
361
|
+
reasoningEffort,
|
|
343
362
|
};
|
|
344
363
|
if (resumeReady) {
|
|
345
364
|
this.manualResumeReady = true;
|
|
346
365
|
}
|
|
347
|
-
this.trace(`thread ready id=${threadId} path="${sanitizeForLog(threadPath, 180)}"`);
|
|
366
|
+
this.trace(`thread ready id=${threadId} path="${sanitizeForLog(threadPath, 180)}" model="${sanitizeForLog(resolvedModel || this.backend, 80)}" provider="${sanitizeForLog(resolvedModelProvider || this.backend, 80)}"`);
|
|
348
367
|
this.emit("session", this.getSessionInfo());
|
|
349
368
|
}
|
|
350
369
|
applySessionConfigured(params) {
|
|
@@ -571,7 +590,7 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
571
590
|
}
|
|
572
591
|
switch (method) {
|
|
573
592
|
case "thread/started":
|
|
574
|
-
this.applyThreadInfo(params
|
|
593
|
+
this.applyThreadInfo(params, { resumeReady: Boolean(this.resumeSessionId) });
|
|
575
594
|
return;
|
|
576
595
|
case "sessionConfigured":
|
|
577
596
|
case "session_configured":
|