@love-moon/ai-sdk 0.17.1 → 0.17.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # @love-moon/ai-sdk
2
2
 
3
+ ## 0.17.2
4
+
5
+ ### Patch Changes
6
+
7
+ - b124565: Add QueuedChatSession with a shared local browser broker and a FIFO turn queue
8
+ across processes. Each task retains its own conversation tab; idle tasks no
9
+ longer hold exclusive access to the provider profile. The AI SDK uses queued
10
+ sessions when available and reports waiting positions through working status.
11
+
12
+ Cancel pending turns on disconnect, stop/close failed conversation tabs before
13
+ advancing the queue, and release the browser after its last client exits.
14
+ Keep the exclusive ChatSession API available for direct Playwright access.
15
+
16
+ Keep logical sessions usable after an active stop, timeout, or provider failure.
17
+ Restore the original provider conversation for the next explicit user operation;
18
+ require explicit clear when safe restoration is unavailable, and never replay a
19
+ failed prompt. Allow clear after a failed first submission. Correct the Windows
20
+ named-pipe address escaping.
21
+
22
+ - 0a9488e: Add Kimi website text conversations with persistent login, multi-turn context,
23
+ reply progress, new chats and browser diagnostics. Expose the provider through
24
+ ai-sdk's chat-web backend and the web-kimi Conductor configuration alias.
25
+
26
+ Explicit unknown chat-web providers now fail with a clear error instead of
27
+ silently opening ChatGPT. The existing kimi CLI backend is unchanged.
28
+
3
29
  ## 0.17.1
4
30
 
5
31
  ### Patch Changes
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * AI SDK provider that delegates to the chat-web runtime
3
3
  * (`@love-moon/chat-web`), which automates a real Chromium browser against
4
- * ChatGPT / Gemini / DeepSeek and ferries conversations through their web
4
+ * ChatGPT / Gemini / Kimi and ferries conversations through their web
5
5
  * UIs. Choose the underlying chat-web provider via:
6
6
  *
7
- * - `options.chatWebProvider`: "chatgpt" | "gemini" (preferred)
7
+ * - `options.chatWebProvider`: "chatgpt" | "gemini" | "kimi" (preferred)
8
8
  * - `options.provider`: same surface
9
9
  * - `options.model`: same surface (fallback for ergonomic
10
10
  * `createAiSession("chat-web", { model: "gemini" })`)
@@ -13,10 +13,10 @@
13
13
  *
14
14
  * Lifecycle:
15
15
  * - `boot()` lazily imports `@love-moon/chat-web`, registers its built-in
16
- * providers, and opens a long-lived `ChatSession` (headed by default).
16
+ * providers, and opens a queued conversation tab (headed by default).
17
17
  * - `runTurn(prompt)` calls `session.send(prompt)` and emits a single
18
18
  * `assistant_message` with the model's reply.
19
- * - `close()` tears the Chromium context down.
19
+ * - `close()` releases this task's tab; the broker closes an idle browser.
20
20
  *
21
21
  * Resume: chat-web's "session" is a Chromium browser context, not a
22
22
  * conversation ID — there is no native cross-process resume. We synthesise
@@ -50,6 +50,7 @@ export class ChatWebSession extends EventEmitter<[never]> {
50
50
  closed: boolean;
51
51
  currentTurn: {
52
52
  aborted: boolean;
53
+ controller: AbortController;
53
54
  } | null;
54
55
  currentTurnStatus: {
55
56
  source: string;
@@ -65,6 +66,7 @@ export class ChatWebSession extends EventEmitter<[never]> {
65
66
  session_file_path: undefined;
66
67
  updated_at: string;
67
68
  } | null;
69
+ turnAttempted: boolean;
68
70
  sessionMessageHandler: any;
69
71
  workingStatusHandler: any;
70
72
  activeReplyTarget: string;
@@ -4,7 +4,7 @@ import { PROVIDER_MEDIA_CAPABILITIES } from "../media-adapters.js";
4
4
  import { assertMediaCapabilities, resolveTurnMedia } from "../media-input.js";
5
5
  import { normalizeContextFiles } from "../context-files.js";
6
6
  import { emitLog, normalizeLogger } from "../shared.js";
7
- const SUPPORTED_CHAT_WEB_PROVIDERS = new Set(["chatgpt", "gemini"]);
7
+ const SUPPORTED_CHAT_WEB_PROVIDERS = new Set(["chatgpt", "gemini", "kimi"]);
8
8
  const DEFAULT_CHAT_WEB_PROVIDER = "chatgpt";
9
9
  const DEFAULT_TURN_TIMEOUT_MS = 5 * 60 * 1000;
10
10
  function normalizeChatWebProvider(value) {
@@ -22,9 +22,14 @@ function resolveChatWebProvider(options = {}) {
22
22
  const candidates = [options.chatWebProvider, options.provider, options.model];
23
23
  for (const candidate of candidates) {
24
24
  const normalized = normalizeChatWebProvider(candidate);
25
- if (SUPPORTED_CHAT_WEB_PROVIDERS.has(normalized)) {
26
- return normalized;
25
+ if (!normalized)
26
+ continue;
27
+ if (!SUPPORTED_CHAT_WEB_PROVIDERS.has(normalized)) {
28
+ const error = new Error(`Unknown chat-web provider "${normalized}". Supported: ${[...SUPPORTED_CHAT_WEB_PROVIDERS].join(", ")}`);
29
+ error.reason = "unknown_chat_web_provider";
30
+ throw error;
27
31
  }
32
+ return normalized;
28
33
  }
29
34
  return DEFAULT_CHAT_WEB_PROVIDER;
30
35
  }
@@ -93,10 +98,10 @@ function isPlaywrightMissingError(error) {
93
98
  /**
94
99
  * AI SDK provider that delegates to the chat-web runtime
95
100
  * (`@love-moon/chat-web`), which automates a real Chromium browser against
96
- * ChatGPT / Gemini / DeepSeek and ferries conversations through their web
101
+ * ChatGPT / Gemini / Kimi and ferries conversations through their web
97
102
  * UIs. Choose the underlying chat-web provider via:
98
103
  *
99
- * - `options.chatWebProvider`: "chatgpt" | "gemini" (preferred)
104
+ * - `options.chatWebProvider`: "chatgpt" | "gemini" | "kimi" (preferred)
100
105
  * - `options.provider`: same surface
101
106
  * - `options.model`: same surface (fallback for ergonomic
102
107
  * `createAiSession("chat-web", { model: "gemini" })`)
@@ -105,10 +110,10 @@ function isPlaywrightMissingError(error) {
105
110
  *
106
111
  * Lifecycle:
107
112
  * - `boot()` lazily imports `@love-moon/chat-web`, registers its built-in
108
- * providers, and opens a long-lived `ChatSession` (headed by default).
113
+ * providers, and opens a queued conversation tab (headed by default).
109
114
  * - `runTurn(prompt)` calls `session.send(prompt)` and emits a single
110
115
  * `assistant_message` with the model's reply.
111
- * - `close()` tears the Chromium context down.
116
+ * - `close()` releases this task's tab; the broker closes an idle browser.
112
117
  *
113
118
  * Resume: chat-web's "session" is a Chromium browser context, not a
114
119
  * conversation ID — there is no native cross-process resume. We synthesise
@@ -176,6 +181,7 @@ export class ChatWebSession extends EventEmitter {
176
181
  this.closed = false;
177
182
  this.currentTurn = null;
178
183
  this.currentTurnStatus = null;
184
+ this.turnAttempted = false;
179
185
  this.sessionMessageHandler = null;
180
186
  this.workingStatusHandler = null;
181
187
  this.activeReplyTarget = "";
@@ -316,7 +322,10 @@ export class ChatWebSession extends EventEmitter {
316
322
  if (typeof mod.registerBuiltinProviders === "function") {
317
323
  mod.registerBuiltinProviders();
318
324
  }
319
- if (typeof mod.ChatSession?.open !== "function") {
325
+ // New runtimes share a browser and schedule complete turns across workers.
326
+ // Keep the existing session surface for older optional chat-web versions.
327
+ const Session = mod.QueuedChatSession ?? mod.ChatSession;
328
+ if (typeof Session?.open !== "function") {
320
329
  throw new Error("Loaded @love-moon/chat-web is missing ChatSession.open");
321
330
  }
322
331
  // Forward optional browser-binary overrides to chat-web. The env vars
@@ -328,7 +337,7 @@ export class ChatWebSession extends EventEmitter {
328
337
  launch.channel = this.browserChannel;
329
338
  if (this.browserExecutablePath)
330
339
  launch.executablePath = this.browserExecutablePath;
331
- this.chatSession = await mod.ChatSession.open(this.chatWebProvider, {
340
+ this.chatSession = await Session.open(this.chatWebProvider, {
332
341
  headless: this.headless,
333
342
  // IMPORTANT: chat-web's logger contract is `{ error, warn, info, debug }`;
334
343
  // ai-sdk's normalised logger is `{ log }`. Passing the ai-sdk logger
@@ -376,7 +385,7 @@ export class ChatWebSession extends EventEmitter {
376
385
  }
377
386
  // Nothing has been said yet — fire boots the browser at announce time, so
378
387
  // "booted" alone does not mean there is a conversation to walk away from.
379
- if (!this.providerConversationId && this.history.length === 0) {
388
+ if (!this.providerConversationId && this.history.length === 0 && !this.turnAttempted) {
380
389
  return { clear: { status: "noop" }, usage: null, metadata: {} };
381
390
  }
382
391
  await this.boot();
@@ -394,6 +403,7 @@ export class ChatWebSession extends EventEmitter {
394
403
  providerUrl: undefined,
395
404
  };
396
405
  this.history = [];
406
+ this.turnAttempted = false;
397
407
  this.emit("session", this.getSessionInfo());
398
408
  // No id to report: the provider mints one on the next reply.
399
409
  return { clear: { status: "cleared" }, usage: null, metadata: {} };
@@ -438,6 +448,8 @@ export class ChatWebSession extends EventEmitter {
438
448
  switch (this.chatWebProvider) {
439
449
  case "chatgpt":
440
450
  return `https://chatgpt.com/c/${this.providerConversationId}`;
451
+ case "kimi":
452
+ return `https://www.kimi.com/chat/${encodeURIComponent(this.providerConversationId)}`;
441
453
  case "gemini":
442
454
  // chat-web's `gemini` provider targets AI Studio
443
455
  // (aistudio.google.com/prompts/new_chat) — that's the free web
@@ -476,7 +488,7 @@ export class ChatWebSession extends EventEmitter {
476
488
  error.reason = "turn_already_running";
477
489
  throw error;
478
490
  }
479
- this.currentTurn = { aborted: false };
491
+ this.currentTurn = { aborted: false, controller: new AbortController() };
480
492
  await this.emitWorkingStatus({
481
493
  phase: "turn_started",
482
494
  reply_in_progress: true,
@@ -486,8 +498,19 @@ export class ChatWebSession extends EventEmitter {
486
498
  await this.boot();
487
499
  if (this.closeRequested)
488
500
  throw this.createSessionClosedError();
501
+ this.turnAttempted = true;
489
502
  const result = await this.chatSession.send(prompt, {
490
503
  timeoutMs: this.turnTimeoutMs,
504
+ signal: this.currentTurn.controller.signal,
505
+ onQueue: (position) => {
506
+ void this.emitWorkingStatus({
507
+ phase: position > 0 ? "queued" : "turn_started",
508
+ reply_in_progress: true,
509
+ status_line: position > 0
510
+ ? `chat-web (${this.chatWebProvider}) queued: ${position} ahead`
511
+ : `chat-web (${this.chatWebProvider}) is working`,
512
+ }, onProgress);
513
+ },
491
514
  onProgress: (text) => {
492
515
  // chat-web's onProgress fires while streaming text grows; we
493
516
  // forward those as working_status updates with a short preview.
@@ -567,12 +590,11 @@ export class ChatWebSession extends EventEmitter {
567
590
  }
568
591
  }
569
592
  async interruptCurrentTurn() {
570
- // chat-web's ChatSession doesn't expose a turn abort — the underlying
571
- // Chromium tab is still busy with the model. We mark the turn as
572
- // aborted so the next status emission is "interrupted", but the
573
- // assistant_message may still arrive once the model finishes.
574
593
  if (this.currentTurn) {
575
594
  this.currentTurn.aborted = true;
595
+ // The shared broker removes a pending turn, or stops/closes the active
596
+ // task's tab before moving to the next task. No queued prompt is sent.
597
+ this.currentTurn.controller.abort();
576
598
  }
577
599
  }
578
600
  async close() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@love-moon/ai-sdk",
3
- "version": "0.17.1",
3
+ "version": "0.17.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/lovemoon-ai/conductor.git"
@@ -53,12 +53,12 @@
53
53
  "zod": "^4.1.5"
54
54
  },
55
55
  "optionalDependencies": {
56
- "@love-moon/chat-web": "^0.17.1"
56
+ "@love-moon/chat-web": "^0.17.2"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@types/node": "^22.10.2",
60
60
  "tsx": "^4.20.6",
61
61
  "typescript": "^5.6.3"
62
62
  },
63
- "gitCommitId": "e3dbbad"
63
+ "gitCommitId": "1671ee1"
64
64
  }