@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 +52 -7
- package/dist/client.js +245 -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 +9 -0
- package/dist/providers/claude-agent-sdk-session.js +78 -10
- package/dist/providers/codex-app-server-session.d.ts +12 -2
- package/dist/providers/codex-app-server-session.js +108 -11
- package/dist/providers/kimi-cli-session.d.ts +8 -0
- package/dist/providers/kimi-cli-session.js +80 -7
- package/dist/providers/opencode-sdk-session.d.ts +10 -0
- package/dist/providers/opencode-sdk-session.js +104 -13
- package/dist/session-factory.d.ts +5 -5
- package/dist/session-factory.js +42 -12
- package/dist/worker.js +1 -1
- package/package.json +2 -2
|
@@ -156,6 +156,9 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
156
156
|
this.currentTurn = null;
|
|
157
157
|
this.lastResult = null;
|
|
158
158
|
this.rateLimitInfo = null;
|
|
159
|
+
this.currentTurnStatus = null;
|
|
160
|
+
this.currentTurnActivityAt = 0;
|
|
161
|
+
this.now = typeof options.now === "function" ? options.now : () => Date.now();
|
|
159
162
|
this.turnDeadlineMs = getBoundedEnvInt("CONDUCTOR_TURN_DEADLINE_MS", DEFAULT_TURN_DEADLINE_MS, MIN_TURN_DEADLINE_MS, MAX_TURN_DEADLINE_MS);
|
|
160
163
|
this.sdkModulePromise = null;
|
|
161
164
|
const envConfig = loadEnvConfig(options.configFile);
|
|
@@ -179,10 +182,14 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
179
182
|
return this.sessionId;
|
|
180
183
|
}
|
|
181
184
|
get threadOptions() {
|
|
182
|
-
const model =
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
185
|
+
const model = this.sessionInfo?.model ||
|
|
186
|
+
(typeof this.options.model === "string" && this.options.model.trim()
|
|
187
|
+
? this.options.model.trim()
|
|
188
|
+
: this.backend);
|
|
189
|
+
return {
|
|
190
|
+
model,
|
|
191
|
+
modelProvider: this.sessionInfo?.modelProvider || undefined,
|
|
192
|
+
};
|
|
186
193
|
}
|
|
187
194
|
getSnapshot() {
|
|
188
195
|
return {
|
|
@@ -199,11 +206,15 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
199
206
|
command: `claude --resume ${this.sessionId}`,
|
|
200
207
|
}
|
|
201
208
|
: null,
|
|
209
|
+
currentTurnStatus: this.getCurrentTurnStatus(),
|
|
202
210
|
};
|
|
203
211
|
}
|
|
204
212
|
getSessionInfo() {
|
|
205
213
|
return this.sessionInfo ? { ...this.sessionInfo } : null;
|
|
206
214
|
}
|
|
215
|
+
getCurrentTurnStatus() {
|
|
216
|
+
return this.currentTurnStatus ? { ...this.currentTurnStatus } : null;
|
|
217
|
+
}
|
|
207
218
|
async ensureSessionInfo() {
|
|
208
219
|
return this.getSessionInfo();
|
|
209
220
|
}
|
|
@@ -244,6 +255,27 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
244
255
|
getCurrentReplyTarget() {
|
|
245
256
|
return this.activeReplyTarget || this.lastReplyTarget || undefined;
|
|
246
257
|
}
|
|
258
|
+
touchTurnActivity() {
|
|
259
|
+
this.currentTurnActivityAt = this.now();
|
|
260
|
+
}
|
|
261
|
+
updateCurrentTurnStatus(payload) {
|
|
262
|
+
const updatedAtMs = this.now();
|
|
263
|
+
this.currentTurnActivityAt = updatedAtMs;
|
|
264
|
+
this.currentTurnStatus = {
|
|
265
|
+
...payload,
|
|
266
|
+
updated_at: new Date(updatedAtMs).toISOString(),
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
markTurnStartedStatus() {
|
|
270
|
+
this.updateCurrentTurnStatus({
|
|
271
|
+
source: CLAUDE_PROVIDER_VARIANT,
|
|
272
|
+
reply_in_progress: true,
|
|
273
|
+
replyTo: this.getCurrentReplyTarget(),
|
|
274
|
+
phase: "turn_started",
|
|
275
|
+
status_line: "claude is working",
|
|
276
|
+
thread_id: this.sessionId || undefined,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
247
279
|
async emitWorkingStatus(payload, onProgress = null) {
|
|
248
280
|
const normalized = {
|
|
249
281
|
source: CLAUDE_PROVIDER_VARIANT,
|
|
@@ -256,15 +288,18 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
256
288
|
reply_preview: payload?.reply_preview,
|
|
257
289
|
thread_id: this.sessionId || undefined,
|
|
258
290
|
};
|
|
291
|
+
this.updateCurrentTurnStatus(normalized);
|
|
292
|
+
const snapshot = this.getCurrentTurnStatus();
|
|
259
293
|
if (typeof onProgress === "function") {
|
|
260
|
-
onProgress(
|
|
294
|
+
onProgress(snapshot);
|
|
261
295
|
}
|
|
262
296
|
if (typeof this.workingStatusHandler === "function") {
|
|
263
|
-
await this.workingStatusHandler(
|
|
297
|
+
await this.workingStatusHandler(snapshot);
|
|
264
298
|
}
|
|
265
|
-
this.emit("working_status",
|
|
299
|
+
this.emit("working_status", snapshot);
|
|
266
300
|
}
|
|
267
301
|
async emitAssistantMessage(text) {
|
|
302
|
+
this.touchTurnActivity();
|
|
268
303
|
const payload = {
|
|
269
304
|
text,
|
|
270
305
|
preserveWhitespace: true,
|
|
@@ -338,8 +373,27 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
338
373
|
};
|
|
339
374
|
}
|
|
340
375
|
let timer = null;
|
|
341
|
-
|
|
376
|
+
let settled = false;
|
|
377
|
+
const schedule = (reject) => {
|
|
378
|
+
const now = this.now();
|
|
379
|
+
const lastActivityAt = Number.isFinite(this.currentTurnActivityAt) && this.currentTurnActivityAt > 0
|
|
380
|
+
? this.currentTurnActivityAt
|
|
381
|
+
: now;
|
|
382
|
+
const elapsedMs = Math.max(0, now - lastActivityAt);
|
|
383
|
+
const waitMs = Math.max(1, this.turnDeadlineMs - elapsedMs);
|
|
342
384
|
timer = setTimeout(() => {
|
|
385
|
+
if (settled) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
const activityNow = this.now();
|
|
389
|
+
const latestActivityAt = Number.isFinite(this.currentTurnActivityAt) && this.currentTurnActivityAt > 0
|
|
390
|
+
? this.currentTurnActivityAt
|
|
391
|
+
: activityNow;
|
|
392
|
+
if (activityNow - latestActivityAt < this.turnDeadlineMs) {
|
|
393
|
+
schedule(reject);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
settled = true;
|
|
343
397
|
try {
|
|
344
398
|
onTimeout?.();
|
|
345
399
|
}
|
|
@@ -347,14 +401,18 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
347
401
|
// best effort
|
|
348
402
|
}
|
|
349
403
|
reject(this.createTurnTimeoutError(this.turnDeadlineMs));
|
|
350
|
-
},
|
|
351
|
-
if (typeof timer
|
|
404
|
+
}, waitMs);
|
|
405
|
+
if (typeof timer?.unref === "function") {
|
|
352
406
|
timer.unref();
|
|
353
407
|
}
|
|
408
|
+
};
|
|
409
|
+
const promise = new Promise((_, reject) => {
|
|
410
|
+
schedule(reject);
|
|
354
411
|
});
|
|
355
412
|
return {
|
|
356
413
|
promise,
|
|
357
414
|
cleanup: () => {
|
|
415
|
+
settled = true;
|
|
358
416
|
if (timer) {
|
|
359
417
|
clearTimeout(timer);
|
|
360
418
|
}
|
|
@@ -414,10 +472,19 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
414
472
|
const changed = this.sessionId !== normalizedSessionId;
|
|
415
473
|
this.sessionId = normalizedSessionId;
|
|
416
474
|
this.manualResumeReady = true;
|
|
475
|
+
const modelUsage = this.lastResult?.modelUsage && typeof this.lastResult.modelUsage === "object"
|
|
476
|
+
? this.lastResult.modelUsage
|
|
477
|
+
: null;
|
|
478
|
+
const resolvedModel = typeof modelUsage?.model === "string" && modelUsage.model.trim()
|
|
479
|
+
? modelUsage.model.trim()
|
|
480
|
+
: typeof this.options.model === "string" && this.options.model.trim()
|
|
481
|
+
? this.options.model.trim()
|
|
482
|
+
: undefined;
|
|
417
483
|
this.sessionInfo = {
|
|
418
484
|
...(this.sessionInfo || {}),
|
|
419
485
|
backend: this.backend,
|
|
420
486
|
sessionId: normalizedSessionId,
|
|
487
|
+
model: resolvedModel,
|
|
421
488
|
};
|
|
422
489
|
if (changed) {
|
|
423
490
|
this.trace(`session ready id=${normalizedSessionId}`);
|
|
@@ -668,6 +735,7 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
668
735
|
terminalWorkingStatusEmitted: false,
|
|
669
736
|
};
|
|
670
737
|
this.currentTurn = currentTurn;
|
|
738
|
+
this.markTurnStartedStatus();
|
|
671
739
|
const closeGuard = this.createCloseGuard(() => {
|
|
672
740
|
abortController.abort();
|
|
673
741
|
currentTurn.query?.close?.();
|
|
@@ -21,6 +21,9 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
|
|
|
21
21
|
nativeSessionId: string;
|
|
22
22
|
rateLimits: any;
|
|
23
23
|
tokenUsage: any;
|
|
24
|
+
currentTurnStatus: any;
|
|
25
|
+
currentTurnActivityAt: number;
|
|
26
|
+
now: any;
|
|
24
27
|
turnDeadlineMs: any;
|
|
25
28
|
currentTurn: {
|
|
26
29
|
turnId: string;
|
|
@@ -37,7 +40,8 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
|
|
|
37
40
|
trace(message: any): void;
|
|
38
41
|
get threadId(): any;
|
|
39
42
|
get threadOptions(): {
|
|
40
|
-
model:
|
|
43
|
+
model: any;
|
|
44
|
+
modelProvider: any;
|
|
41
45
|
};
|
|
42
46
|
getSnapshot(): {
|
|
43
47
|
backend: string;
|
|
@@ -51,9 +55,11 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
|
|
|
51
55
|
ready: boolean;
|
|
52
56
|
command: string;
|
|
53
57
|
} | null;
|
|
58
|
+
currentTurnStatus: any;
|
|
54
59
|
pid: number | undefined;
|
|
55
60
|
};
|
|
56
61
|
getSessionInfo(): any;
|
|
62
|
+
getCurrentTurnStatus(): any;
|
|
57
63
|
ensureSessionInfo(): Promise<any>;
|
|
58
64
|
getSessionUsageSummary(): Promise<{
|
|
59
65
|
sessionId: any;
|
|
@@ -72,9 +78,13 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
|
|
|
72
78
|
setWorkingStatusHandler(handler: any): void;
|
|
73
79
|
setSessionReplyTarget(replyTo: any): void;
|
|
74
80
|
getCurrentReplyTarget(): string | undefined;
|
|
81
|
+
touchTurnActivity(): void;
|
|
82
|
+
updateCurrentTurnStatus(payload: any): void;
|
|
83
|
+
markTurnStartedStatus(): void;
|
|
84
|
+
failPendingTurnStart(error: any): Promise<void>;
|
|
75
85
|
boot(): Promise<void>;
|
|
76
86
|
bootInternal(): Promise<void>;
|
|
77
|
-
applyThreadInfo(
|
|
87
|
+
applyThreadInfo(payload: any, { resumeReady }?: {
|
|
78
88
|
resumeReady?: boolean | undefined;
|
|
79
89
|
}): void;
|
|
80
90
|
applySessionConfigured(params: any): void;
|
|
@@ -186,6 +186,9 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
186
186
|
this.nativeSessionId = "";
|
|
187
187
|
this.rateLimits = null;
|
|
188
188
|
this.tokenUsage = null;
|
|
189
|
+
this.currentTurnStatus = null;
|
|
190
|
+
this.currentTurnActivityAt = 0;
|
|
191
|
+
this.now = typeof options.now === "function" ? options.now : () => Date.now();
|
|
189
192
|
this.turnDeadlineMs = getBoundedEnvInt("CONDUCTOR_TURN_DEADLINE_MS", DEFAULT_TURN_DEADLINE_MS, MIN_TURN_DEADLINE_MS, MAX_TURN_DEADLINE_MS);
|
|
190
193
|
this.currentTurn = null;
|
|
191
194
|
this.bootPromise = null;
|
|
@@ -224,7 +227,10 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
224
227
|
return this.sessionId;
|
|
225
228
|
}
|
|
226
229
|
get threadOptions() {
|
|
227
|
-
return {
|
|
230
|
+
return {
|
|
231
|
+
model: this.sessionInfo?.model || this.backend,
|
|
232
|
+
modelProvider: this.sessionInfo?.modelProvider || undefined,
|
|
233
|
+
};
|
|
228
234
|
}
|
|
229
235
|
getSnapshot() {
|
|
230
236
|
return {
|
|
@@ -241,12 +247,16 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
241
247
|
command: `codex resume ${this.sessionId}`,
|
|
242
248
|
}
|
|
243
249
|
: null,
|
|
250
|
+
currentTurnStatus: this.getCurrentTurnStatus(),
|
|
244
251
|
pid: this.transport.pid || undefined,
|
|
245
252
|
};
|
|
246
253
|
}
|
|
247
254
|
getSessionInfo() {
|
|
248
255
|
return this.sessionInfo ? { ...this.sessionInfo } : null;
|
|
249
256
|
}
|
|
257
|
+
getCurrentTurnStatus() {
|
|
258
|
+
return this.currentTurnStatus ? { ...this.currentTurnStatus } : null;
|
|
259
|
+
}
|
|
250
260
|
async ensureSessionInfo() {
|
|
251
261
|
await this.boot();
|
|
252
262
|
return this.getSessionInfo();
|
|
@@ -288,6 +298,37 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
288
298
|
getCurrentReplyTarget() {
|
|
289
299
|
return this.activeReplyTarget || this.lastReplyTarget || undefined;
|
|
290
300
|
}
|
|
301
|
+
touchTurnActivity() {
|
|
302
|
+
this.currentTurnActivityAt = this.now();
|
|
303
|
+
}
|
|
304
|
+
updateCurrentTurnStatus(payload) {
|
|
305
|
+
const updatedAtMs = this.now();
|
|
306
|
+
this.currentTurnActivityAt = updatedAtMs;
|
|
307
|
+
this.currentTurnStatus = {
|
|
308
|
+
...payload,
|
|
309
|
+
updated_at: new Date(updatedAtMs).toISOString(),
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
markTurnStartedStatus() {
|
|
313
|
+
this.updateCurrentTurnStatus({
|
|
314
|
+
source: "codex-app-server",
|
|
315
|
+
reply_in_progress: true,
|
|
316
|
+
replyTo: this.getCurrentReplyTarget(),
|
|
317
|
+
phase: "turn_started",
|
|
318
|
+
status_line: "codex is working",
|
|
319
|
+
thread_id: this.sessionId || undefined,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
async failPendingTurnStart(error) {
|
|
323
|
+
if (this.closeRequested || error?.reason === "session_closed") {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
await this.emitWorkingStatus({
|
|
327
|
+
phase: "turn_failed",
|
|
328
|
+
reply_in_progress: false,
|
|
329
|
+
status_done_line: error instanceof Error ? error.message : String(error),
|
|
330
|
+
});
|
|
331
|
+
}
|
|
291
332
|
async boot() {
|
|
292
333
|
if (this.booted) {
|
|
293
334
|
return;
|
|
@@ -326,9 +367,10 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
326
367
|
else {
|
|
327
368
|
result = await this.transport.request("thread/start", params);
|
|
328
369
|
}
|
|
329
|
-
this.applyThreadInfo(result
|
|
370
|
+
this.applyThreadInfo(result, { resumeReady: Boolean(this.resumeSessionId) });
|
|
330
371
|
}
|
|
331
|
-
applyThreadInfo(
|
|
372
|
+
applyThreadInfo(payload, { resumeReady = false } = {}) {
|
|
373
|
+
const thread = payload?.thread && typeof payload.thread === "object" ? payload.thread : payload;
|
|
332
374
|
const threadId = typeof thread?.id === "string" ? thread.id.trim() : "";
|
|
333
375
|
const threadPath = typeof thread?.path === "string" ? thread.path.trim() : "";
|
|
334
376
|
if (!threadId) {
|
|
@@ -336,15 +378,30 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
336
378
|
}
|
|
337
379
|
this.sessionId = threadId;
|
|
338
380
|
this.threadPath = threadPath;
|
|
381
|
+
const resolvedModel = typeof payload?.model === "string" && payload.model.trim()
|
|
382
|
+
? payload.model.trim()
|
|
383
|
+
: this.sessionInfo?.model || undefined;
|
|
384
|
+
const resolvedModelProvider = typeof payload?.modelProvider === "string" && payload.modelProvider.trim()
|
|
385
|
+
? payload.modelProvider.trim()
|
|
386
|
+
: typeof thread?.modelProvider === "string" && thread.modelProvider.trim()
|
|
387
|
+
? thread.modelProvider.trim()
|
|
388
|
+
: this.sessionInfo?.modelProvider || undefined;
|
|
389
|
+
const reasoningEffort = typeof payload?.reasoningEffort === "string" && payload.reasoningEffort.trim()
|
|
390
|
+
? payload.reasoningEffort.trim()
|
|
391
|
+
: this.sessionInfo?.reasoningEffort || undefined;
|
|
339
392
|
this.sessionInfo = {
|
|
393
|
+
...(this.sessionInfo || {}),
|
|
340
394
|
backend: this.backend,
|
|
341
395
|
sessionId: threadId,
|
|
342
396
|
sessionFilePath: threadPath || undefined,
|
|
397
|
+
model: resolvedModel,
|
|
398
|
+
modelProvider: resolvedModelProvider,
|
|
399
|
+
reasoningEffort,
|
|
343
400
|
};
|
|
344
401
|
if (resumeReady) {
|
|
345
402
|
this.manualResumeReady = true;
|
|
346
403
|
}
|
|
347
|
-
this.trace(`thread ready id=${threadId} path="${sanitizeForLog(threadPath, 180)}"`);
|
|
404
|
+
this.trace(`thread ready id=${threadId} path="${sanitizeForLog(threadPath, 180)}" model="${sanitizeForLog(resolvedModel || this.backend, 80)}" provider="${sanitizeForLog(resolvedModelProvider || this.backend, 80)}"`);
|
|
348
405
|
this.emit("session", this.getSessionInfo());
|
|
349
406
|
}
|
|
350
407
|
applySessionConfigured(params) {
|
|
@@ -387,12 +444,15 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
387
444
|
}
|
|
388
445
|
async emitWorkingStatus(payload) {
|
|
389
446
|
const normalized = this.normalizeWorkingStatusPayload(payload);
|
|
447
|
+
this.updateCurrentTurnStatus(normalized);
|
|
448
|
+
const snapshot = this.getCurrentTurnStatus();
|
|
390
449
|
if (typeof this.workingStatusHandler === "function") {
|
|
391
|
-
await this.workingStatusHandler(
|
|
450
|
+
await this.workingStatusHandler(snapshot);
|
|
392
451
|
}
|
|
393
|
-
this.emit("working_status",
|
|
452
|
+
this.emit("working_status", snapshot);
|
|
394
453
|
}
|
|
395
454
|
async emitAssistantMessage(text) {
|
|
455
|
+
this.touchTurnActivity();
|
|
396
456
|
const payload = {
|
|
397
457
|
text,
|
|
398
458
|
preserveWhitespace: true,
|
|
@@ -470,17 +530,40 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
470
530
|
};
|
|
471
531
|
}
|
|
472
532
|
let timer = null;
|
|
473
|
-
|
|
533
|
+
let settled = false;
|
|
534
|
+
const schedule = (reject) => {
|
|
535
|
+
const now = this.now();
|
|
536
|
+
const lastActivityAt = Number.isFinite(this.currentTurnActivityAt) && this.currentTurnActivityAt > 0
|
|
537
|
+
? this.currentTurnActivityAt
|
|
538
|
+
: now;
|
|
539
|
+
const elapsedMs = Math.max(0, now - lastActivityAt);
|
|
540
|
+
const waitMs = Math.max(1, this.turnDeadlineMs - elapsedMs);
|
|
474
541
|
timer = setTimeout(() => {
|
|
542
|
+
if (settled) {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
const activityNow = this.now();
|
|
546
|
+
const latestActivityAt = Number.isFinite(this.currentTurnActivityAt) && this.currentTurnActivityAt > 0
|
|
547
|
+
? this.currentTurnActivityAt
|
|
548
|
+
: activityNow;
|
|
549
|
+
if (activityNow - latestActivityAt < this.turnDeadlineMs) {
|
|
550
|
+
schedule(reject);
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
settled = true;
|
|
475
554
|
reject(this.createTurnTimeoutError(this.turnDeadlineMs));
|
|
476
|
-
},
|
|
477
|
-
if (typeof timer
|
|
555
|
+
}, waitMs);
|
|
556
|
+
if (typeof timer?.unref === "function") {
|
|
478
557
|
timer.unref();
|
|
479
558
|
}
|
|
559
|
+
};
|
|
560
|
+
const promise = new Promise((_, reject) => {
|
|
561
|
+
schedule(reject);
|
|
480
562
|
});
|
|
481
563
|
return {
|
|
482
564
|
promise,
|
|
483
565
|
cleanup: () => {
|
|
566
|
+
settled = true;
|
|
484
567
|
if (timer) {
|
|
485
568
|
clearTimeout(timer);
|
|
486
569
|
}
|
|
@@ -571,7 +654,7 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
571
654
|
}
|
|
572
655
|
switch (method) {
|
|
573
656
|
case "thread/started":
|
|
574
|
-
this.applyThreadInfo(params
|
|
657
|
+
this.applyThreadInfo(params, { resumeReady: Boolean(this.resumeSessionId) });
|
|
575
658
|
return;
|
|
576
659
|
case "sessionConfigured":
|
|
577
660
|
case "session_configured":
|
|
@@ -784,7 +867,6 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
784
867
|
if (this.closeRequested) {
|
|
785
868
|
throw this.createSessionClosedError();
|
|
786
869
|
}
|
|
787
|
-
await this.boot();
|
|
788
870
|
const effectivePrompt = this.buildPrompt(promptText, { useInitialImages });
|
|
789
871
|
if (!effectivePrompt) {
|
|
790
872
|
return {
|
|
@@ -799,6 +881,14 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
799
881
|
reason: "turn_already_running",
|
|
800
882
|
});
|
|
801
883
|
}
|
|
884
|
+
this.markTurnStartedStatus();
|
|
885
|
+
try {
|
|
886
|
+
await this.boot();
|
|
887
|
+
}
|
|
888
|
+
catch (error) {
|
|
889
|
+
await this.failPendingTurnStart(error);
|
|
890
|
+
throw error;
|
|
891
|
+
}
|
|
802
892
|
this.history.push({ role: "user", content: promptText });
|
|
803
893
|
const closeGuard = this.createCloseGuard();
|
|
804
894
|
const turnTimeoutGuard = this.createTurnTimeoutGuard();
|
|
@@ -860,6 +950,13 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
860
950
|
if (error?.reason === "turn_timeout") {
|
|
861
951
|
await this.interruptCurrentTurn();
|
|
862
952
|
}
|
|
953
|
+
if (!this.closeRequested && error?.reason !== "session_closed") {
|
|
954
|
+
await this.emitWorkingStatus({
|
|
955
|
+
phase: "turn_failed",
|
|
956
|
+
reply_in_progress: false,
|
|
957
|
+
status_done_line: error instanceof Error ? error.message : String(error),
|
|
958
|
+
});
|
|
959
|
+
}
|
|
863
960
|
this.maybeEmitAuthRequired(error);
|
|
864
961
|
throw error;
|
|
865
962
|
}
|
|
@@ -32,6 +32,8 @@ export class KimiCliSession extends EventEmitter<[never]> {
|
|
|
32
32
|
} | null;
|
|
33
33
|
lastTokenUsage: any;
|
|
34
34
|
lastContextUsagePercent: number | undefined;
|
|
35
|
+
currentTurnStatus: any;
|
|
36
|
+
currentTurnActivityAt: number;
|
|
35
37
|
turnDeadlineMs: any;
|
|
36
38
|
workingStatusDedupeMs: any;
|
|
37
39
|
workingStatusThrottleMs: any;
|
|
@@ -77,12 +79,14 @@ export class KimiCliSession extends EventEmitter<[never]> {
|
|
|
77
79
|
ready: boolean;
|
|
78
80
|
command: any;
|
|
79
81
|
} | null;
|
|
82
|
+
currentTurnStatus: any;
|
|
80
83
|
pid: any;
|
|
81
84
|
};
|
|
82
85
|
getSessionInfo(): {
|
|
83
86
|
backend: string;
|
|
84
87
|
sessionId: any;
|
|
85
88
|
} | null;
|
|
89
|
+
getCurrentTurnStatus(): any;
|
|
86
90
|
ensureSessionInfo(): Promise<{
|
|
87
91
|
backend: string;
|
|
88
92
|
sessionId: any;
|
|
@@ -104,6 +108,10 @@ export class KimiCliSession extends EventEmitter<[never]> {
|
|
|
104
108
|
setWorkingStatusHandler(handler: any): void;
|
|
105
109
|
setSessionReplyTarget(replyTo: any): void;
|
|
106
110
|
getCurrentReplyTarget(): string | undefined;
|
|
111
|
+
touchTurnActivity(): void;
|
|
112
|
+
updateCurrentTurnStatus(payload: any): void;
|
|
113
|
+
markTurnStartedStatus(): void;
|
|
114
|
+
failPendingTurnStart(error: any, onProgress?: null): Promise<void>;
|
|
107
115
|
buildWorkingStatusFingerprint(payload: any): string;
|
|
108
116
|
shouldSuppressWorkingStatus(payload: any): boolean;
|
|
109
117
|
recordWorkingStatusEmission(payload: any): void;
|
|
@@ -134,6 +134,8 @@ export class KimiCliSession extends EventEmitter {
|
|
|
134
134
|
this.currentTurn = null;
|
|
135
135
|
this.lastTokenUsage = null;
|
|
136
136
|
this.lastContextUsagePercent = undefined;
|
|
137
|
+
this.currentTurnStatus = null;
|
|
138
|
+
this.currentTurnActivityAt = 0;
|
|
137
139
|
this.turnDeadlineMs = getBoundedEnvInt("CONDUCTOR_TURN_DEADLINE_MS", DEFAULT_TURN_DEADLINE_MS, MIN_TURN_DEADLINE_MS, MAX_TURN_DEADLINE_MS);
|
|
138
140
|
this.workingStatusDedupeMs = getBoundedEnvInt("CONDUCTOR_KIMI_STATUS_DEDUPE_MS", DEFAULT_STATUS_DEDUPE_MS, 0, MAX_STATUS_TIMING_MS);
|
|
139
141
|
this.workingStatusThrottleMs = getBoundedEnvInt("CONDUCTOR_KIMI_STATUS_THROTTLE_MS", DEFAULT_STATUS_THROTTLE_MS, 0, MAX_STATUS_TIMING_MS);
|
|
@@ -216,12 +218,16 @@ export class KimiCliSession extends EventEmitter {
|
|
|
216
218
|
command: this.buildManualResumeCommand(),
|
|
217
219
|
}
|
|
218
220
|
: null,
|
|
221
|
+
currentTurnStatus: this.getCurrentTurnStatus(),
|
|
219
222
|
pid: this.transport.pid || undefined,
|
|
220
223
|
};
|
|
221
224
|
}
|
|
222
225
|
getSessionInfo() {
|
|
223
226
|
return this.sessionInfo ? { ...this.sessionInfo } : null;
|
|
224
227
|
}
|
|
228
|
+
getCurrentTurnStatus() {
|
|
229
|
+
return this.currentTurnStatus ? { ...this.currentTurnStatus } : null;
|
|
230
|
+
}
|
|
225
231
|
async ensureSessionInfo() {
|
|
226
232
|
await this.boot();
|
|
227
233
|
return this.getSessionInfo();
|
|
@@ -261,6 +267,39 @@ export class KimiCliSession extends EventEmitter {
|
|
|
261
267
|
getCurrentReplyTarget() {
|
|
262
268
|
return this.activeReplyTarget || this.lastReplyTarget || undefined;
|
|
263
269
|
}
|
|
270
|
+
touchTurnActivity() {
|
|
271
|
+
this.currentTurnActivityAt = this.now();
|
|
272
|
+
}
|
|
273
|
+
updateCurrentTurnStatus(payload) {
|
|
274
|
+
const updatedAtMs = this.now();
|
|
275
|
+
this.currentTurnActivityAt = updatedAtMs;
|
|
276
|
+
this.currentTurnStatus = {
|
|
277
|
+
...payload,
|
|
278
|
+
updated_at: new Date(updatedAtMs).toISOString(),
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
markTurnStartedStatus() {
|
|
282
|
+
this.updateCurrentTurnStatus({
|
|
283
|
+
source: KIMI_PROVIDER_VARIANT,
|
|
284
|
+
reply_in_progress: true,
|
|
285
|
+
replyTo: this.getCurrentReplyTarget(),
|
|
286
|
+
phase: "turn_started",
|
|
287
|
+
status_line: statusLineForPhase("turn_started"),
|
|
288
|
+
thread_id: this.sessionId || undefined,
|
|
289
|
+
session_id: this.sessionId || undefined,
|
|
290
|
+
session_file_path: undefined,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
async failPendingTurnStart(error, onProgress = null) {
|
|
294
|
+
if (this.closeRequested || error?.reason === "session_closed") {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
await this.emitWorkingStatus({
|
|
298
|
+
phase: "turn_failed",
|
|
299
|
+
reply_in_progress: false,
|
|
300
|
+
status_done_line: error instanceof Error ? error.message : String(error),
|
|
301
|
+
}, onProgress);
|
|
302
|
+
}
|
|
264
303
|
buildWorkingStatusFingerprint(payload) {
|
|
265
304
|
return JSON.stringify({
|
|
266
305
|
reply_in_progress: Boolean(payload?.reply_in_progress),
|
|
@@ -338,18 +377,22 @@ export class KimiCliSession extends EventEmitter {
|
|
|
338
377
|
session_file_path: undefined,
|
|
339
378
|
};
|
|
340
379
|
if (this.shouldSuppressWorkingStatus(normalized)) {
|
|
380
|
+
this.updateCurrentTurnStatus(normalized);
|
|
341
381
|
return;
|
|
342
382
|
}
|
|
383
|
+
this.updateCurrentTurnStatus(normalized);
|
|
384
|
+
const snapshot = this.getCurrentTurnStatus();
|
|
343
385
|
this.recordWorkingStatusEmission(normalized);
|
|
344
386
|
if (typeof onProgress === "function") {
|
|
345
|
-
onProgress(
|
|
387
|
+
onProgress(snapshot);
|
|
346
388
|
}
|
|
347
389
|
if (typeof this.workingStatusHandler === "function") {
|
|
348
|
-
await this.workingStatusHandler(
|
|
390
|
+
await this.workingStatusHandler(snapshot);
|
|
349
391
|
}
|
|
350
|
-
this.emit("working_status",
|
|
392
|
+
this.emit("working_status", snapshot);
|
|
351
393
|
}
|
|
352
394
|
async emitAssistantMessage(text) {
|
|
395
|
+
this.touchTurnActivity();
|
|
353
396
|
const payload = {
|
|
354
397
|
text,
|
|
355
398
|
preserveWhitespace: true,
|
|
@@ -423,8 +466,27 @@ export class KimiCliSession extends EventEmitter {
|
|
|
423
466
|
};
|
|
424
467
|
}
|
|
425
468
|
let timer = null;
|
|
426
|
-
|
|
469
|
+
let settled = false;
|
|
470
|
+
const schedule = (reject) => {
|
|
471
|
+
const now = this.now();
|
|
472
|
+
const lastActivityAt = Number.isFinite(this.currentTurnActivityAt) && this.currentTurnActivityAt > 0
|
|
473
|
+
? this.currentTurnActivityAt
|
|
474
|
+
: now;
|
|
475
|
+
const elapsedMs = Math.max(0, now - lastActivityAt);
|
|
476
|
+
const waitMs = Math.max(1, this.turnDeadlineMs - elapsedMs);
|
|
427
477
|
timer = setTimeout(() => {
|
|
478
|
+
if (settled) {
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
const activityNow = this.now();
|
|
482
|
+
const latestActivityAt = Number.isFinite(this.currentTurnActivityAt) && this.currentTurnActivityAt > 0
|
|
483
|
+
? this.currentTurnActivityAt
|
|
484
|
+
: activityNow;
|
|
485
|
+
if (activityNow - latestActivityAt < this.turnDeadlineMs) {
|
|
486
|
+
schedule(reject);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
settled = true;
|
|
428
490
|
try {
|
|
429
491
|
onTimeout?.();
|
|
430
492
|
}
|
|
@@ -432,14 +494,18 @@ export class KimiCliSession extends EventEmitter {
|
|
|
432
494
|
// best effort
|
|
433
495
|
}
|
|
434
496
|
reject(this.createTurnTimeoutError(this.turnDeadlineMs));
|
|
435
|
-
},
|
|
436
|
-
if (typeof timer
|
|
497
|
+
}, waitMs);
|
|
498
|
+
if (typeof timer?.unref === "function") {
|
|
437
499
|
timer.unref();
|
|
438
500
|
}
|
|
501
|
+
};
|
|
502
|
+
const promise = new Promise((_, reject) => {
|
|
503
|
+
schedule(reject);
|
|
439
504
|
});
|
|
440
505
|
return {
|
|
441
506
|
promise,
|
|
442
507
|
cleanup: () => {
|
|
508
|
+
settled = true;
|
|
443
509
|
if (timer) {
|
|
444
510
|
clearTimeout(timer);
|
|
445
511
|
}
|
|
@@ -763,12 +829,19 @@ export class KimiCliSession extends EventEmitter {
|
|
|
763
829
|
if (!effectivePrompt) {
|
|
764
830
|
return buildEmptyTurnResult();
|
|
765
831
|
}
|
|
766
|
-
await this.boot();
|
|
767
832
|
if (this.currentTurn) {
|
|
768
833
|
throw createTurnError("Kimi turn already running", {
|
|
769
834
|
reason: "turn_already_running",
|
|
770
835
|
});
|
|
771
836
|
}
|
|
837
|
+
this.markTurnStartedStatus();
|
|
838
|
+
try {
|
|
839
|
+
await this.boot();
|
|
840
|
+
}
|
|
841
|
+
catch (error) {
|
|
842
|
+
await this.failPendingTurnStart(error, onProgress);
|
|
843
|
+
throw error;
|
|
844
|
+
}
|
|
772
845
|
this.history.push({ role: "user", content: promptText });
|
|
773
846
|
const currentTurn = {
|
|
774
847
|
fullText: "",
|
|
@@ -46,6 +46,9 @@ export class OpencodeSdkSession extends EventEmitter<[never]> {
|
|
|
46
46
|
};
|
|
47
47
|
} | null;
|
|
48
48
|
lastAssistantInfo: any;
|
|
49
|
+
currentTurnStatus: any;
|
|
50
|
+
currentTurnActivityAt: number;
|
|
51
|
+
now: any;
|
|
49
52
|
turnDeadlineMs: any;
|
|
50
53
|
client: any;
|
|
51
54
|
sdkModulePromise: Promise<any> | Promise<typeof import("@opencode-ai/sdk/v2/client")> | null;
|
|
@@ -60,6 +63,7 @@ export class OpencodeSdkSession extends EventEmitter<[never]> {
|
|
|
60
63
|
get threadId(): any;
|
|
61
64
|
get threadOptions(): {
|
|
62
65
|
model: any;
|
|
66
|
+
modelProvider: any;
|
|
63
67
|
};
|
|
64
68
|
getSnapshot(): {
|
|
65
69
|
backend: string;
|
|
@@ -73,12 +77,14 @@ export class OpencodeSdkSession extends EventEmitter<[never]> {
|
|
|
73
77
|
useSessionFileReplyStream: boolean;
|
|
74
78
|
resumeReady: boolean;
|
|
75
79
|
manualResume: null;
|
|
80
|
+
currentTurnStatus: any;
|
|
76
81
|
pid: any;
|
|
77
82
|
};
|
|
78
83
|
getSessionInfo(): {
|
|
79
84
|
backend: string;
|
|
80
85
|
sessionId: any;
|
|
81
86
|
} | null;
|
|
87
|
+
getCurrentTurnStatus(): any;
|
|
82
88
|
ensureSessionInfo(): Promise<{
|
|
83
89
|
backend: string;
|
|
84
90
|
sessionId: any;
|
|
@@ -105,6 +111,10 @@ export class OpencodeSdkSession extends EventEmitter<[never]> {
|
|
|
105
111
|
setWorkingStatusHandler(handler: any): void;
|
|
106
112
|
setSessionReplyTarget(replyTo: any): void;
|
|
107
113
|
getCurrentReplyTarget(): string | undefined;
|
|
114
|
+
touchTurnActivity(): void;
|
|
115
|
+
updateCurrentTurnStatus(payload: any): void;
|
|
116
|
+
markTurnStartedStatus(): void;
|
|
117
|
+
failPendingTurnStart(error: any, onProgress?: null): Promise<void>;
|
|
108
118
|
emitWorkingStatus(payload: any, onProgress?: null): Promise<void>;
|
|
109
119
|
emitAssistantMessage(text: any): Promise<void>;
|
|
110
120
|
emitTerminalWorkingStatus(currentTurn: any, payload: any, onProgress?: null): Promise<void>;
|