@love-moon/ai-sdk 0.2.30 → 0.2.32

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 CHANGED
@@ -1,19 +1,18 @@
1
- export function createAiSession(backend: any, options?: {}): import("./session-factory.js").CodexAppServerSession | import("./session-factory.js").ClaudeAgentSdkSession | import("./session-factory.js").KimiCliSession | import("./session-factory.js").OpencodeSdkSession | RemoteAiSession;
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;
11
9
  };
12
10
  useSessionFileReplyStreamValue: boolean;
13
11
  sessionInfo: any;
12
+ currentTurnStatus: any;
14
13
  snapshot: {
15
- backend: string;
16
- provider: string;
14
+ backend: undefined;
15
+ provider: undefined;
17
16
  sessionId: any;
18
17
  useSessionFileReplyStream: boolean;
19
18
  workerReady: boolean;
@@ -31,20 +30,26 @@ export class RemoteAiSession extends EventEmitter<[never]> {
31
30
  resolveReady: ((value: any) => void) | null;
32
31
  rejectReady: ((reason?: any) => void) | null;
33
32
  readyPromise: Promise<any>;
33
+ initializeSession(backend: any, options: any): Promise<void>;
34
+ backend: any;
35
+ variant: any;
34
36
  get threadId(): any;
35
37
  get threadOptions(): {
38
+ modelProvider?: any;
36
39
  model: any;
37
40
  };
38
41
  getSnapshot(): {
42
+ currentTurnStatus: any;
39
43
  sessionInfo: any;
40
- backend: string;
41
- provider: string;
44
+ backend: undefined;
45
+ provider: undefined;
42
46
  sessionId: any;
43
47
  useSessionFileReplyStream: boolean;
44
48
  workerReady: boolean;
45
49
  };
46
50
  usesSessionFileReplyStream(): boolean;
47
51
  getSessionInfo(): any;
52
+ getCurrentTurnStatus(): any;
48
53
  setSessionMessageHandler(handler: any): void;
49
54
  setWorkingStatusHandler(handler: any): void;
50
55
  setSessionReplyTarget(replyTo: any): void;
@@ -63,5 +68,45 @@ export class RemoteAiSession extends EventEmitter<[never]> {
63
68
  handleWorkerEvent(payload: any): Promise<void>;
64
69
  rejectPendingRequests(error: any): void;
65
70
  }
71
+ declare class LocalAiSessionProxy extends EventEmitter<[never]> {
72
+ constructor(backend: any, options?: {});
73
+ backend: any;
74
+ options: {};
75
+ threadIdValue: any;
76
+ threadOptionsValue: {
77
+ model: any;
78
+ };
79
+ useSessionFileReplyStreamValue: boolean;
80
+ sessionInfo: any;
81
+ currentTurnStatus: any;
82
+ session: any;
83
+ closed: boolean;
84
+ sessionMessageHandler: any;
85
+ workingStatusHandler: any;
86
+ replyTarget: any;
87
+ snapshot: {
88
+ backend: undefined;
89
+ provider: undefined;
90
+ sessionId: any;
91
+ useSessionFileReplyStream: boolean;
92
+ workerReady: boolean;
93
+ };
94
+ readyPromise: Promise<any>;
95
+ initializeSession(backend: any, options: any): Promise<any>;
96
+ get threadId(): any;
97
+ get threadOptions(): any;
98
+ getSnapshot(): any;
99
+ usesSessionFileReplyStream(): boolean;
100
+ getSessionInfo(): any;
101
+ getCurrentTurnStatus(): any;
102
+ setSessionMessageHandler(handler: any): void;
103
+ setWorkingStatusHandler(handler: any): void;
104
+ setSessionReplyTarget(replyTarget: any): void;
105
+ ensureSessionInfo(): Promise<any>;
106
+ getSessionUsageSummary(): Promise<any>;
107
+ runTurn(promptText: any, options?: {}): Promise<any>;
108
+ close(): Promise<void>;
109
+ }
66
110
  import { EventEmitter } from "node:events";
67
111
  import readline from "node:readline";
112
+ 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,14 @@ 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
- : this.backend || "unknown",
38
+ : String(backend || "unknown").trim() || "unknown",
41
39
  };
42
40
  this.useSessionFileReplyStreamValue = true;
43
41
  this.sessionInfo = null;
42
+ this.currentTurnStatus = null;
44
43
  this.snapshot = {
45
- backend: this.backend,
46
- provider: this.variant,
44
+ backend: undefined,
45
+ provider: undefined,
47
46
  sessionId: this.threadIdValue || undefined,
48
47
  useSessionFileReplyStream: this.useSessionFileReplyStreamValue,
49
48
  workerReady: false,
@@ -94,21 +93,50 @@ export class RemoteAiSession extends EventEmitter {
94
93
  this.rejectReady?.(error);
95
94
  this.rejectPendingRequests(error);
96
95
  });
97
- this.child.stdin.write(toSerializablePayload({
98
- type: "create",
99
- backend: this.backend,
100
- options: sanitizeOptionsForWorker(options),
101
- }));
96
+ void this.initializeSession(backend, options);
97
+ }
98
+ async initializeSession(backend, options) {
99
+ try {
100
+ this.backend = await assertSupportedBackend(backend, options);
101
+ this.variant = await providerVariantForBackend(backend, options);
102
+ this.snapshot.backend = this.backend;
103
+ this.snapshot.provider = this.variant;
104
+ this.threadOptionsValue.model =
105
+ typeof options.model === "string" && options.model.trim() ? options.model.trim() : this.backend || "unknown";
106
+ this.child.stdin.write(toSerializablePayload({
107
+ type: "create",
108
+ backend: this.backend,
109
+ options: sanitizeOptionsForWorker(options),
110
+ }));
111
+ }
112
+ catch (error) {
113
+ this.rejectReady?.(error);
114
+ this.resolveReady = null;
115
+ this.rejectReady = null;
116
+ this.rejectPendingRequests(error);
117
+ try {
118
+ this.child.kill("SIGTERM");
119
+ }
120
+ catch {
121
+ // ignore
122
+ }
123
+ }
102
124
  }
103
125
  get threadId() {
104
126
  return this.threadIdValue;
105
127
  }
106
128
  get threadOptions() {
107
- return { ...this.threadOptionsValue };
129
+ const sessionInfo = this.sessionInfo && typeof this.sessionInfo === "object" ? this.sessionInfo : null;
130
+ return {
131
+ ...this.threadOptionsValue,
132
+ ...(sessionInfo?.model ? { model: sessionInfo.model } : {}),
133
+ ...(sessionInfo?.modelProvider ? { modelProvider: sessionInfo.modelProvider } : {}),
134
+ };
108
135
  }
109
136
  getSnapshot() {
110
137
  return {
111
138
  ...this.snapshot,
139
+ currentTurnStatus: this.getCurrentTurnStatus(),
112
140
  sessionInfo: this.sessionInfo ? { ...this.sessionInfo } : null,
113
141
  };
114
142
  }
@@ -118,6 +146,9 @@ export class RemoteAiSession extends EventEmitter {
118
146
  getSessionInfo() {
119
147
  return this.sessionInfo ? { ...this.sessionInfo } : null;
120
148
  }
149
+ getCurrentTurnStatus() {
150
+ return this.currentTurnStatus ? { ...this.currentTurnStatus } : null;
151
+ }
121
152
  setSessionMessageHandler(handler) {
122
153
  this.sessionMessageHandler = typeof handler === "function" ? handler : null;
123
154
  }
@@ -278,6 +309,9 @@ export class RemoteAiSession extends EventEmitter {
278
309
  if (snapshot?.useSessionFileReplyStream !== undefined) {
279
310
  this.useSessionFileReplyStreamValue = Boolean(snapshot.useSessionFileReplyStream);
280
311
  }
312
+ if (snapshot?.currentTurnStatus && typeof snapshot.currentTurnStatus === "object") {
313
+ this.currentTurnStatus = { ...snapshot.currentTurnStatus };
314
+ }
281
315
  }
282
316
  handleWorkerResponse(payload) {
283
317
  const pending = this.pendingRequests.get(payload.id);
@@ -305,6 +339,9 @@ export class RemoteAiSession extends EventEmitter {
305
339
  }
306
340
  handleWorkerProgress(payload) {
307
341
  const pending = this.pendingRequests.get(payload.requestId);
342
+ if (payload?.payload && typeof payload.payload === "object") {
343
+ this.currentTurnStatus = { ...payload.payload };
344
+ }
308
345
  if (!pending || typeof pending.progressHandler !== "function") {
309
346
  return;
310
347
  }
@@ -337,6 +374,11 @@ export class RemoteAiSession extends EventEmitter {
337
374
  if (name === "assistant_message" && typeof this.sessionMessageHandler === "function") {
338
375
  await this.sessionMessageHandler(eventPayload);
339
376
  }
377
+ if (name === "working_status") {
378
+ if (eventPayload && typeof eventPayload === "object") {
379
+ this.currentTurnStatus = { ...eventPayload };
380
+ }
381
+ }
340
382
  if (name === "working_status" && typeof this.workingStatusHandler === "function") {
341
383
  await this.workingStatusHandler(eventPayload);
342
384
  }
@@ -352,10 +394,199 @@ export class RemoteAiSession extends EventEmitter {
352
394
  this.pendingRequests.clear();
353
395
  }
354
396
  }
397
+ const LOCAL_SESSION_EVENT_NAMES = ["session", "assistant_message", "working_status", "auth_required", "process.exited"];
398
+ class LocalAiSessionProxy extends EventEmitter {
399
+ constructor(backend, options = {}) {
400
+ super();
401
+ this.backend = undefined;
402
+ this.options = options;
403
+ this.threadIdValue =
404
+ typeof options.resumeSessionId === "string" && options.resumeSessionId.trim()
405
+ ? options.resumeSessionId.trim()
406
+ : "";
407
+ this.threadOptionsValue = {
408
+ model: typeof options.model === "string" && options.model.trim()
409
+ ? options.model.trim()
410
+ : String(backend || "unknown").trim() || "unknown",
411
+ };
412
+ this.useSessionFileReplyStreamValue = true;
413
+ this.sessionInfo = null;
414
+ this.currentTurnStatus = null;
415
+ this.session = null;
416
+ this.closed = false;
417
+ this.sessionMessageHandler = null;
418
+ this.workingStatusHandler = null;
419
+ this.replyTarget = undefined;
420
+ this.snapshot = {
421
+ backend: undefined,
422
+ provider: undefined,
423
+ sessionId: this.threadIdValue || undefined,
424
+ useSessionFileReplyStream: this.useSessionFileReplyStreamValue,
425
+ workerReady: false,
426
+ };
427
+ this.readyPromise = this.initializeSession(backend, options);
428
+ this.readyPromise.catch(() => {
429
+ // keep parity with RemoteAiSession and avoid unhandled rejections
430
+ });
431
+ }
432
+ async initializeSession(backend, options) {
433
+ const session = await createLocalAiSession(backend, options);
434
+ if (this.closed) {
435
+ await session.close?.();
436
+ return session;
437
+ }
438
+ this.session = session;
439
+ for (const eventName of LOCAL_SESSION_EVENT_NAMES) {
440
+ if (typeof session.on === "function") {
441
+ session.on(eventName, async (payload) => {
442
+ if (eventName === "session" && payload && typeof payload === "object") {
443
+ this.sessionInfo = { ...payload };
444
+ if (payload.sessionId) {
445
+ this.threadIdValue = String(payload.sessionId);
446
+ }
447
+ }
448
+ if (eventName === "working_status" && payload && typeof payload === "object") {
449
+ this.currentTurnStatus = { ...payload };
450
+ }
451
+ this.emit(eventName, payload);
452
+ });
453
+ }
454
+ }
455
+ if (this.sessionMessageHandler && typeof session.setSessionMessageHandler === "function") {
456
+ session.setSessionMessageHandler(this.sessionMessageHandler);
457
+ }
458
+ if (this.workingStatusHandler && typeof session.setWorkingStatusHandler === "function") {
459
+ session.setWorkingStatusHandler(this.workingStatusHandler);
460
+ }
461
+ if (this.replyTarget !== undefined && typeof session.setSessionReplyTarget === "function") {
462
+ session.setSessionReplyTarget(this.replyTarget);
463
+ }
464
+ const snapshot = typeof session.getSnapshot === "function" ? session.getSnapshot() : {};
465
+ this.backend = snapshot.backend || session.backend || this.backend;
466
+ this.threadIdValue = session.threadId || snapshot.sessionId || this.threadIdValue;
467
+ this.threadOptionsValue = session.threadOptions ? { ...session.threadOptions } : this.threadOptionsValue;
468
+ this.useSessionFileReplyStreamValue =
469
+ typeof session.usesSessionFileReplyStream === "function"
470
+ ? Boolean(session.usesSessionFileReplyStream())
471
+ : snapshot.useSessionFileReplyStream !== undefined
472
+ ? Boolean(snapshot.useSessionFileReplyStream)
473
+ : this.useSessionFileReplyStreamValue;
474
+ this.sessionInfo =
475
+ typeof session.getSessionInfo === "function"
476
+ ? session.getSessionInfo()
477
+ : snapshot.sessionInfo && typeof snapshot.sessionInfo === "object"
478
+ ? { ...snapshot.sessionInfo }
479
+ : this.sessionInfo;
480
+ this.snapshot = {
481
+ ...this.snapshot,
482
+ ...snapshot,
483
+ backend: this.backend,
484
+ sessionId: this.threadIdValue || undefined,
485
+ sessionInfo: this.sessionInfo,
486
+ currentTurnStatus: typeof session.getCurrentTurnStatus === "function" ? session.getCurrentTurnStatus() : this.currentTurnStatus,
487
+ useSessionFileReplyStream: this.useSessionFileReplyStreamValue,
488
+ workerReady: true,
489
+ };
490
+ return session;
491
+ }
492
+ get threadId() {
493
+ return this.session?.threadId || this.threadIdValue;
494
+ }
495
+ get threadOptions() {
496
+ return this.session?.threadOptions ? { ...this.session.threadOptions } : { ...this.threadOptionsValue };
497
+ }
498
+ getSnapshot() {
499
+ const sessionSnapshot = typeof this.session?.getSnapshot === "function" ? this.session.getSnapshot() : null;
500
+ if (sessionSnapshot) {
501
+ return {
502
+ ...this.snapshot,
503
+ ...sessionSnapshot,
504
+ backend: sessionSnapshot.backend || this.snapshot.backend,
505
+ currentTurnStatus: typeof this.session?.getCurrentTurnStatus === "function"
506
+ ? this.session.getCurrentTurnStatus()
507
+ : sessionSnapshot.currentTurnStatus || this.currentTurnStatus || null,
508
+ sessionId: sessionSnapshot.sessionId || this.threadIdValue || undefined,
509
+ sessionInfo: typeof this.session?.getSessionInfo === "function"
510
+ ? this.session.getSessionInfo()
511
+ : sessionSnapshot.sessionInfo || this.sessionInfo || null,
512
+ };
513
+ }
514
+ return {
515
+ ...this.snapshot,
516
+ currentTurnStatus: this.getCurrentTurnStatus(),
517
+ sessionInfo: this.sessionInfo ? { ...this.sessionInfo } : null,
518
+ };
519
+ }
520
+ usesSessionFileReplyStream() {
521
+ if (typeof this.session?.usesSessionFileReplyStream === "function") {
522
+ return Boolean(this.session.usesSessionFileReplyStream());
523
+ }
524
+ return Boolean(this.useSessionFileReplyStreamValue);
525
+ }
526
+ getSessionInfo() {
527
+ if (typeof this.session?.getSessionInfo === "function") {
528
+ return this.session.getSessionInfo();
529
+ }
530
+ return this.sessionInfo ? { ...this.sessionInfo } : null;
531
+ }
532
+ getCurrentTurnStatus() {
533
+ if (typeof this.session?.getCurrentTurnStatus === "function") {
534
+ return this.session.getCurrentTurnStatus();
535
+ }
536
+ return this.currentTurnStatus ? { ...this.currentTurnStatus } : null;
537
+ }
538
+ setSessionMessageHandler(handler) {
539
+ this.sessionMessageHandler = typeof handler === "function" ? handler : null;
540
+ if (typeof this.session?.setSessionMessageHandler === "function") {
541
+ this.session.setSessionMessageHandler(this.sessionMessageHandler);
542
+ }
543
+ }
544
+ setWorkingStatusHandler(handler) {
545
+ this.workingStatusHandler = typeof handler === "function" ? handler : null;
546
+ if (typeof this.session?.setWorkingStatusHandler === "function") {
547
+ this.session.setWorkingStatusHandler(this.workingStatusHandler);
548
+ }
549
+ }
550
+ setSessionReplyTarget(replyTarget) {
551
+ this.replyTarget = replyTarget;
552
+ if (typeof this.session?.setSessionReplyTarget === "function") {
553
+ this.session.setSessionReplyTarget(replyTarget);
554
+ }
555
+ }
556
+ async ensureSessionInfo() {
557
+ const session = await this.readyPromise;
558
+ const sessionInfo = await session.ensureSessionInfo();
559
+ this.sessionInfo = sessionInfo && typeof sessionInfo === "object" ? { ...sessionInfo } : sessionInfo;
560
+ if (sessionInfo?.sessionId) {
561
+ this.threadIdValue = String(sessionInfo.sessionId);
562
+ }
563
+ return sessionInfo;
564
+ }
565
+ async getSessionUsageSummary() {
566
+ const session = await this.readyPromise;
567
+ return await session.getSessionUsageSummary();
568
+ }
569
+ async runTurn(promptText, options = {}) {
570
+ const session = await this.readyPromise;
571
+ return await session.runTurn(promptText, options);
572
+ }
573
+ async close() {
574
+ if (this.closed) {
575
+ return;
576
+ }
577
+ this.closed = true;
578
+ try {
579
+ const session = await this.readyPromise;
580
+ await session.close?.();
581
+ }
582
+ catch {
583
+ // best effort
584
+ }
585
+ }
586
+ }
355
587
  export function createAiSession(backend, options = {}) {
356
- const normalizedBackend = assertSupportedBackend(backend);
357
588
  if (process.env.CONDUCTOR_AI_SDK_DISABLE_WORKER === "1") {
358
- return createLocalAiSession(normalizedBackend, options);
589
+ return new LocalAiSessionProxy(backend, options);
359
590
  }
360
- return new RemoteAiSession(normalizedBackend, options);
591
+ return new RemoteAiSession(backend, options);
361
592
  }
@@ -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
+ }
@@ -31,6 +31,9 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
31
31
  } | null;
32
32
  lastResult: any;
33
33
  rateLimitInfo: any;
34
+ currentTurnStatus: any;
35
+ currentTurnActivityAt: number;
36
+ now: any;
34
37
  turnDeadlineMs: any;
35
38
  sdkModulePromise: Promise<any> | Promise<typeof import("@anthropic-ai/claude-agent-sdk")> | null;
36
39
  env: any;
@@ -39,6 +42,7 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
39
42
  get threadId(): any;
40
43
  get threadOptions(): {
41
44
  model: any;
45
+ modelProvider: any;
42
46
  };
43
47
  getSnapshot(): {
44
48
  backend: string;
@@ -55,11 +59,13 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
55
59
  ready: boolean;
56
60
  command: string;
57
61
  } | null;
62
+ currentTurnStatus: any;
58
63
  };
59
64
  getSessionInfo(): {
60
65
  backend: string;
61
66
  sessionId: any;
62
67
  } | null;
68
+ getCurrentTurnStatus(): any;
63
69
  ensureSessionInfo(): Promise<{
64
70
  backend: string;
65
71
  sessionId: any;
@@ -81,6 +87,9 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
81
87
  setWorkingStatusHandler(handler: any): void;
82
88
  setSessionReplyTarget(replyTo: any): void;
83
89
  getCurrentReplyTarget(): string | undefined;
90
+ touchTurnActivity(): void;
91
+ updateCurrentTurnStatus(payload: any): void;
92
+ markTurnStartedStatus(): void;
84
93
  emitWorkingStatus(payload: any, onProgress?: null): Promise<void>;
85
94
  emitAssistantMessage(text: any): Promise<void>;
86
95
  emitTerminalWorkingStatus(currentTurn: any, payload: any, onProgress?: null): Promise<void>;