@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 /
|
|
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
|
|
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()`
|
|
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 (
|
|
26
|
-
|
|
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 /
|
|
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
|
|
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()`
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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": "
|
|
63
|
+
"gitCommitId": "1671ee1"
|
|
64
64
|
}
|