@love-moon/ai-sdk 0.2.31 → 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 +5 -0
- package/dist/client.js +31 -0
- package/dist/providers/claude-agent-sdk-session.d.ts +8 -0
- package/dist/providers/claude-agent-sdk-session.js +61 -6
- package/dist/providers/codex-app-server-session.d.ts +9 -0
- package/dist/providers/codex-app-server-session.js +84 -6
- 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 +9 -0
- package/dist/providers/opencode-sdk-session.js +82 -9
- package/package.json +2 -2
package/dist/client.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export class RemoteAiSession extends EventEmitter<[never]> {
|
|
|
9
9
|
};
|
|
10
10
|
useSessionFileReplyStreamValue: boolean;
|
|
11
11
|
sessionInfo: any;
|
|
12
|
+
currentTurnStatus: any;
|
|
12
13
|
snapshot: {
|
|
13
14
|
backend: undefined;
|
|
14
15
|
provider: undefined;
|
|
@@ -38,6 +39,7 @@ export class RemoteAiSession extends EventEmitter<[never]> {
|
|
|
38
39
|
model: any;
|
|
39
40
|
};
|
|
40
41
|
getSnapshot(): {
|
|
42
|
+
currentTurnStatus: any;
|
|
41
43
|
sessionInfo: any;
|
|
42
44
|
backend: undefined;
|
|
43
45
|
provider: undefined;
|
|
@@ -47,6 +49,7 @@ export class RemoteAiSession extends EventEmitter<[never]> {
|
|
|
47
49
|
};
|
|
48
50
|
usesSessionFileReplyStream(): boolean;
|
|
49
51
|
getSessionInfo(): any;
|
|
52
|
+
getCurrentTurnStatus(): any;
|
|
50
53
|
setSessionMessageHandler(handler: any): void;
|
|
51
54
|
setWorkingStatusHandler(handler: any): void;
|
|
52
55
|
setSessionReplyTarget(replyTo: any): void;
|
|
@@ -75,6 +78,7 @@ declare class LocalAiSessionProxy extends EventEmitter<[never]> {
|
|
|
75
78
|
};
|
|
76
79
|
useSessionFileReplyStreamValue: boolean;
|
|
77
80
|
sessionInfo: any;
|
|
81
|
+
currentTurnStatus: any;
|
|
78
82
|
session: any;
|
|
79
83
|
closed: boolean;
|
|
80
84
|
sessionMessageHandler: any;
|
|
@@ -94,6 +98,7 @@ declare class LocalAiSessionProxy extends EventEmitter<[never]> {
|
|
|
94
98
|
getSnapshot(): any;
|
|
95
99
|
usesSessionFileReplyStream(): boolean;
|
|
96
100
|
getSessionInfo(): any;
|
|
101
|
+
getCurrentTurnStatus(): any;
|
|
97
102
|
setSessionMessageHandler(handler: any): void;
|
|
98
103
|
setWorkingStatusHandler(handler: any): void;
|
|
99
104
|
setSessionReplyTarget(replyTarget: any): void;
|
package/dist/client.js
CHANGED
|
@@ -39,6 +39,7 @@ export class RemoteAiSession extends EventEmitter {
|
|
|
39
39
|
};
|
|
40
40
|
this.useSessionFileReplyStreamValue = true;
|
|
41
41
|
this.sessionInfo = null;
|
|
42
|
+
this.currentTurnStatus = null;
|
|
42
43
|
this.snapshot = {
|
|
43
44
|
backend: undefined,
|
|
44
45
|
provider: undefined,
|
|
@@ -135,6 +136,7 @@ export class RemoteAiSession extends EventEmitter {
|
|
|
135
136
|
getSnapshot() {
|
|
136
137
|
return {
|
|
137
138
|
...this.snapshot,
|
|
139
|
+
currentTurnStatus: this.getCurrentTurnStatus(),
|
|
138
140
|
sessionInfo: this.sessionInfo ? { ...this.sessionInfo } : null,
|
|
139
141
|
};
|
|
140
142
|
}
|
|
@@ -144,6 +146,9 @@ export class RemoteAiSession extends EventEmitter {
|
|
|
144
146
|
getSessionInfo() {
|
|
145
147
|
return this.sessionInfo ? { ...this.sessionInfo } : null;
|
|
146
148
|
}
|
|
149
|
+
getCurrentTurnStatus() {
|
|
150
|
+
return this.currentTurnStatus ? { ...this.currentTurnStatus } : null;
|
|
151
|
+
}
|
|
147
152
|
setSessionMessageHandler(handler) {
|
|
148
153
|
this.sessionMessageHandler = typeof handler === "function" ? handler : null;
|
|
149
154
|
}
|
|
@@ -304,6 +309,9 @@ export class RemoteAiSession extends EventEmitter {
|
|
|
304
309
|
if (snapshot?.useSessionFileReplyStream !== undefined) {
|
|
305
310
|
this.useSessionFileReplyStreamValue = Boolean(snapshot.useSessionFileReplyStream);
|
|
306
311
|
}
|
|
312
|
+
if (snapshot?.currentTurnStatus && typeof snapshot.currentTurnStatus === "object") {
|
|
313
|
+
this.currentTurnStatus = { ...snapshot.currentTurnStatus };
|
|
314
|
+
}
|
|
307
315
|
}
|
|
308
316
|
handleWorkerResponse(payload) {
|
|
309
317
|
const pending = this.pendingRequests.get(payload.id);
|
|
@@ -331,6 +339,9 @@ export class RemoteAiSession extends EventEmitter {
|
|
|
331
339
|
}
|
|
332
340
|
handleWorkerProgress(payload) {
|
|
333
341
|
const pending = this.pendingRequests.get(payload.requestId);
|
|
342
|
+
if (payload?.payload && typeof payload.payload === "object") {
|
|
343
|
+
this.currentTurnStatus = { ...payload.payload };
|
|
344
|
+
}
|
|
334
345
|
if (!pending || typeof pending.progressHandler !== "function") {
|
|
335
346
|
return;
|
|
336
347
|
}
|
|
@@ -363,6 +374,11 @@ export class RemoteAiSession extends EventEmitter {
|
|
|
363
374
|
if (name === "assistant_message" && typeof this.sessionMessageHandler === "function") {
|
|
364
375
|
await this.sessionMessageHandler(eventPayload);
|
|
365
376
|
}
|
|
377
|
+
if (name === "working_status") {
|
|
378
|
+
if (eventPayload && typeof eventPayload === "object") {
|
|
379
|
+
this.currentTurnStatus = { ...eventPayload };
|
|
380
|
+
}
|
|
381
|
+
}
|
|
366
382
|
if (name === "working_status" && typeof this.workingStatusHandler === "function") {
|
|
367
383
|
await this.workingStatusHandler(eventPayload);
|
|
368
384
|
}
|
|
@@ -395,6 +411,7 @@ class LocalAiSessionProxy extends EventEmitter {
|
|
|
395
411
|
};
|
|
396
412
|
this.useSessionFileReplyStreamValue = true;
|
|
397
413
|
this.sessionInfo = null;
|
|
414
|
+
this.currentTurnStatus = null;
|
|
398
415
|
this.session = null;
|
|
399
416
|
this.closed = false;
|
|
400
417
|
this.sessionMessageHandler = null;
|
|
@@ -428,6 +445,9 @@ class LocalAiSessionProxy extends EventEmitter {
|
|
|
428
445
|
this.threadIdValue = String(payload.sessionId);
|
|
429
446
|
}
|
|
430
447
|
}
|
|
448
|
+
if (eventName === "working_status" && payload && typeof payload === "object") {
|
|
449
|
+
this.currentTurnStatus = { ...payload };
|
|
450
|
+
}
|
|
431
451
|
this.emit(eventName, payload);
|
|
432
452
|
});
|
|
433
453
|
}
|
|
@@ -463,6 +483,7 @@ class LocalAiSessionProxy extends EventEmitter {
|
|
|
463
483
|
backend: this.backend,
|
|
464
484
|
sessionId: this.threadIdValue || undefined,
|
|
465
485
|
sessionInfo: this.sessionInfo,
|
|
486
|
+
currentTurnStatus: typeof session.getCurrentTurnStatus === "function" ? session.getCurrentTurnStatus() : this.currentTurnStatus,
|
|
466
487
|
useSessionFileReplyStream: this.useSessionFileReplyStreamValue,
|
|
467
488
|
workerReady: true,
|
|
468
489
|
};
|
|
@@ -481,6 +502,9 @@ class LocalAiSessionProxy extends EventEmitter {
|
|
|
481
502
|
...this.snapshot,
|
|
482
503
|
...sessionSnapshot,
|
|
483
504
|
backend: sessionSnapshot.backend || this.snapshot.backend,
|
|
505
|
+
currentTurnStatus: typeof this.session?.getCurrentTurnStatus === "function"
|
|
506
|
+
? this.session.getCurrentTurnStatus()
|
|
507
|
+
: sessionSnapshot.currentTurnStatus || this.currentTurnStatus || null,
|
|
484
508
|
sessionId: sessionSnapshot.sessionId || this.threadIdValue || undefined,
|
|
485
509
|
sessionInfo: typeof this.session?.getSessionInfo === "function"
|
|
486
510
|
? this.session.getSessionInfo()
|
|
@@ -489,6 +513,7 @@ class LocalAiSessionProxy extends EventEmitter {
|
|
|
489
513
|
}
|
|
490
514
|
return {
|
|
491
515
|
...this.snapshot,
|
|
516
|
+
currentTurnStatus: this.getCurrentTurnStatus(),
|
|
492
517
|
sessionInfo: this.sessionInfo ? { ...this.sessionInfo } : null,
|
|
493
518
|
};
|
|
494
519
|
}
|
|
@@ -504,6 +529,12 @@ class LocalAiSessionProxy extends EventEmitter {
|
|
|
504
529
|
}
|
|
505
530
|
return this.sessionInfo ? { ...this.sessionInfo } : null;
|
|
506
531
|
}
|
|
532
|
+
getCurrentTurnStatus() {
|
|
533
|
+
if (typeof this.session?.getCurrentTurnStatus === "function") {
|
|
534
|
+
return this.session.getCurrentTurnStatus();
|
|
535
|
+
}
|
|
536
|
+
return this.currentTurnStatus ? { ...this.currentTurnStatus } : null;
|
|
537
|
+
}
|
|
507
538
|
setSessionMessageHandler(handler) {
|
|
508
539
|
this.sessionMessageHandler = typeof handler === "function" ? handler : null;
|
|
509
540
|
if (typeof this.session?.setSessionMessageHandler === "function") {
|
|
@@ -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;
|
|
@@ -56,11 +59,13 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
|
|
|
56
59
|
ready: boolean;
|
|
57
60
|
command: string;
|
|
58
61
|
} | null;
|
|
62
|
+
currentTurnStatus: any;
|
|
59
63
|
};
|
|
60
64
|
getSessionInfo(): {
|
|
61
65
|
backend: string;
|
|
62
66
|
sessionId: any;
|
|
63
67
|
} | null;
|
|
68
|
+
getCurrentTurnStatus(): any;
|
|
64
69
|
ensureSessionInfo(): Promise<{
|
|
65
70
|
backend: string;
|
|
66
71
|
sessionId: any;
|
|
@@ -82,6 +87,9 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
|
|
|
82
87
|
setWorkingStatusHandler(handler: any): void;
|
|
83
88
|
setSessionReplyTarget(replyTo: any): void;
|
|
84
89
|
getCurrentReplyTarget(): string | undefined;
|
|
90
|
+
touchTurnActivity(): void;
|
|
91
|
+
updateCurrentTurnStatus(payload: any): void;
|
|
92
|
+
markTurnStartedStatus(): void;
|
|
85
93
|
emitWorkingStatus(payload: any, onProgress?: null): Promise<void>;
|
|
86
94
|
emitAssistantMessage(text: any): Promise<void>;
|
|
87
95
|
emitTerminalWorkingStatus(currentTurn: any, payload: any, onProgress?: null): Promise<void>;
|
|
@@ -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);
|
|
@@ -203,11 +206,15 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
203
206
|
command: `claude --resume ${this.sessionId}`,
|
|
204
207
|
}
|
|
205
208
|
: null,
|
|
209
|
+
currentTurnStatus: this.getCurrentTurnStatus(),
|
|
206
210
|
};
|
|
207
211
|
}
|
|
208
212
|
getSessionInfo() {
|
|
209
213
|
return this.sessionInfo ? { ...this.sessionInfo } : null;
|
|
210
214
|
}
|
|
215
|
+
getCurrentTurnStatus() {
|
|
216
|
+
return this.currentTurnStatus ? { ...this.currentTurnStatus } : null;
|
|
217
|
+
}
|
|
211
218
|
async ensureSessionInfo() {
|
|
212
219
|
return this.getSessionInfo();
|
|
213
220
|
}
|
|
@@ -248,6 +255,27 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
248
255
|
getCurrentReplyTarget() {
|
|
249
256
|
return this.activeReplyTarget || this.lastReplyTarget || undefined;
|
|
250
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
|
+
}
|
|
251
279
|
async emitWorkingStatus(payload, onProgress = null) {
|
|
252
280
|
const normalized = {
|
|
253
281
|
source: CLAUDE_PROVIDER_VARIANT,
|
|
@@ -260,15 +288,18 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
260
288
|
reply_preview: payload?.reply_preview,
|
|
261
289
|
thread_id: this.sessionId || undefined,
|
|
262
290
|
};
|
|
291
|
+
this.updateCurrentTurnStatus(normalized);
|
|
292
|
+
const snapshot = this.getCurrentTurnStatus();
|
|
263
293
|
if (typeof onProgress === "function") {
|
|
264
|
-
onProgress(
|
|
294
|
+
onProgress(snapshot);
|
|
265
295
|
}
|
|
266
296
|
if (typeof this.workingStatusHandler === "function") {
|
|
267
|
-
await this.workingStatusHandler(
|
|
297
|
+
await this.workingStatusHandler(snapshot);
|
|
268
298
|
}
|
|
269
|
-
this.emit("working_status",
|
|
299
|
+
this.emit("working_status", snapshot);
|
|
270
300
|
}
|
|
271
301
|
async emitAssistantMessage(text) {
|
|
302
|
+
this.touchTurnActivity();
|
|
272
303
|
const payload = {
|
|
273
304
|
text,
|
|
274
305
|
preserveWhitespace: true,
|
|
@@ -342,8 +373,27 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
342
373
|
};
|
|
343
374
|
}
|
|
344
375
|
let timer = null;
|
|
345
|
-
|
|
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);
|
|
346
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;
|
|
347
397
|
try {
|
|
348
398
|
onTimeout?.();
|
|
349
399
|
}
|
|
@@ -351,14 +401,18 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
351
401
|
// best effort
|
|
352
402
|
}
|
|
353
403
|
reject(this.createTurnTimeoutError(this.turnDeadlineMs));
|
|
354
|
-
},
|
|
355
|
-
if (typeof timer
|
|
404
|
+
}, waitMs);
|
|
405
|
+
if (typeof timer?.unref === "function") {
|
|
356
406
|
timer.unref();
|
|
357
407
|
}
|
|
408
|
+
};
|
|
409
|
+
const promise = new Promise((_, reject) => {
|
|
410
|
+
schedule(reject);
|
|
358
411
|
});
|
|
359
412
|
return {
|
|
360
413
|
promise,
|
|
361
414
|
cleanup: () => {
|
|
415
|
+
settled = true;
|
|
362
416
|
if (timer) {
|
|
363
417
|
clearTimeout(timer);
|
|
364
418
|
}
|
|
@@ -681,6 +735,7 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
681
735
|
terminalWorkingStatusEmitted: false,
|
|
682
736
|
};
|
|
683
737
|
this.currentTurn = currentTurn;
|
|
738
|
+
this.markTurnStartedStatus();
|
|
684
739
|
const closeGuard = this.createCloseGuard(() => {
|
|
685
740
|
abortController.abort();
|
|
686
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;
|
|
@@ -52,9 +55,11 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
|
|
|
52
55
|
ready: boolean;
|
|
53
56
|
command: string;
|
|
54
57
|
} | null;
|
|
58
|
+
currentTurnStatus: any;
|
|
55
59
|
pid: number | undefined;
|
|
56
60
|
};
|
|
57
61
|
getSessionInfo(): any;
|
|
62
|
+
getCurrentTurnStatus(): any;
|
|
58
63
|
ensureSessionInfo(): Promise<any>;
|
|
59
64
|
getSessionUsageSummary(): Promise<{
|
|
60
65
|
sessionId: any;
|
|
@@ -73,6 +78,10 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
|
|
|
73
78
|
setWorkingStatusHandler(handler: any): void;
|
|
74
79
|
setSessionReplyTarget(replyTo: any): void;
|
|
75
80
|
getCurrentReplyTarget(): string | undefined;
|
|
81
|
+
touchTurnActivity(): void;
|
|
82
|
+
updateCurrentTurnStatus(payload: any): void;
|
|
83
|
+
markTurnStartedStatus(): void;
|
|
84
|
+
failPendingTurnStart(error: any): Promise<void>;
|
|
76
85
|
boot(): Promise<void>;
|
|
77
86
|
bootInternal(): Promise<void>;
|
|
78
87
|
applyThreadInfo(payload: any, { resumeReady }?: {
|
|
@@ -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;
|
|
@@ -244,12 +247,16 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
244
247
|
command: `codex resume ${this.sessionId}`,
|
|
245
248
|
}
|
|
246
249
|
: null,
|
|
250
|
+
currentTurnStatus: this.getCurrentTurnStatus(),
|
|
247
251
|
pid: this.transport.pid || undefined,
|
|
248
252
|
};
|
|
249
253
|
}
|
|
250
254
|
getSessionInfo() {
|
|
251
255
|
return this.sessionInfo ? { ...this.sessionInfo } : null;
|
|
252
256
|
}
|
|
257
|
+
getCurrentTurnStatus() {
|
|
258
|
+
return this.currentTurnStatus ? { ...this.currentTurnStatus } : null;
|
|
259
|
+
}
|
|
253
260
|
async ensureSessionInfo() {
|
|
254
261
|
await this.boot();
|
|
255
262
|
return this.getSessionInfo();
|
|
@@ -291,6 +298,37 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
291
298
|
getCurrentReplyTarget() {
|
|
292
299
|
return this.activeReplyTarget || this.lastReplyTarget || undefined;
|
|
293
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
|
+
}
|
|
294
332
|
async boot() {
|
|
295
333
|
if (this.booted) {
|
|
296
334
|
return;
|
|
@@ -406,12 +444,15 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
406
444
|
}
|
|
407
445
|
async emitWorkingStatus(payload) {
|
|
408
446
|
const normalized = this.normalizeWorkingStatusPayload(payload);
|
|
447
|
+
this.updateCurrentTurnStatus(normalized);
|
|
448
|
+
const snapshot = this.getCurrentTurnStatus();
|
|
409
449
|
if (typeof this.workingStatusHandler === "function") {
|
|
410
|
-
await this.workingStatusHandler(
|
|
450
|
+
await this.workingStatusHandler(snapshot);
|
|
411
451
|
}
|
|
412
|
-
this.emit("working_status",
|
|
452
|
+
this.emit("working_status", snapshot);
|
|
413
453
|
}
|
|
414
454
|
async emitAssistantMessage(text) {
|
|
455
|
+
this.touchTurnActivity();
|
|
415
456
|
const payload = {
|
|
416
457
|
text,
|
|
417
458
|
preserveWhitespace: true,
|
|
@@ -489,17 +530,40 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
489
530
|
};
|
|
490
531
|
}
|
|
491
532
|
let timer = null;
|
|
492
|
-
|
|
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);
|
|
493
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;
|
|
494
554
|
reject(this.createTurnTimeoutError(this.turnDeadlineMs));
|
|
495
|
-
},
|
|
496
|
-
if (typeof timer
|
|
555
|
+
}, waitMs);
|
|
556
|
+
if (typeof timer?.unref === "function") {
|
|
497
557
|
timer.unref();
|
|
498
558
|
}
|
|
559
|
+
};
|
|
560
|
+
const promise = new Promise((_, reject) => {
|
|
561
|
+
schedule(reject);
|
|
499
562
|
});
|
|
500
563
|
return {
|
|
501
564
|
promise,
|
|
502
565
|
cleanup: () => {
|
|
566
|
+
settled = true;
|
|
503
567
|
if (timer) {
|
|
504
568
|
clearTimeout(timer);
|
|
505
569
|
}
|
|
@@ -803,7 +867,6 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
803
867
|
if (this.closeRequested) {
|
|
804
868
|
throw this.createSessionClosedError();
|
|
805
869
|
}
|
|
806
|
-
await this.boot();
|
|
807
870
|
const effectivePrompt = this.buildPrompt(promptText, { useInitialImages });
|
|
808
871
|
if (!effectivePrompt) {
|
|
809
872
|
return {
|
|
@@ -818,6 +881,14 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
818
881
|
reason: "turn_already_running",
|
|
819
882
|
});
|
|
820
883
|
}
|
|
884
|
+
this.markTurnStartedStatus();
|
|
885
|
+
try {
|
|
886
|
+
await this.boot();
|
|
887
|
+
}
|
|
888
|
+
catch (error) {
|
|
889
|
+
await this.failPendingTurnStart(error);
|
|
890
|
+
throw error;
|
|
891
|
+
}
|
|
821
892
|
this.history.push({ role: "user", content: promptText });
|
|
822
893
|
const closeGuard = this.createCloseGuard();
|
|
823
894
|
const turnTimeoutGuard = this.createTurnTimeoutGuard();
|
|
@@ -879,6 +950,13 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
879
950
|
if (error?.reason === "turn_timeout") {
|
|
880
951
|
await this.interruptCurrentTurn();
|
|
881
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
|
+
}
|
|
882
960
|
this.maybeEmitAuthRequired(error);
|
|
883
961
|
throw error;
|
|
884
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;
|
|
@@ -74,12 +77,14 @@ export class OpencodeSdkSession extends EventEmitter<[never]> {
|
|
|
74
77
|
useSessionFileReplyStream: boolean;
|
|
75
78
|
resumeReady: boolean;
|
|
76
79
|
manualResume: null;
|
|
80
|
+
currentTurnStatus: any;
|
|
77
81
|
pid: any;
|
|
78
82
|
};
|
|
79
83
|
getSessionInfo(): {
|
|
80
84
|
backend: string;
|
|
81
85
|
sessionId: any;
|
|
82
86
|
} | null;
|
|
87
|
+
getCurrentTurnStatus(): any;
|
|
83
88
|
ensureSessionInfo(): Promise<{
|
|
84
89
|
backend: string;
|
|
85
90
|
sessionId: any;
|
|
@@ -106,6 +111,10 @@ export class OpencodeSdkSession extends EventEmitter<[never]> {
|
|
|
106
111
|
setWorkingStatusHandler(handler: any): void;
|
|
107
112
|
setSessionReplyTarget(replyTo: any): void;
|
|
108
113
|
getCurrentReplyTarget(): string | undefined;
|
|
114
|
+
touchTurnActivity(): void;
|
|
115
|
+
updateCurrentTurnStatus(payload: any): void;
|
|
116
|
+
markTurnStartedStatus(): void;
|
|
117
|
+
failPendingTurnStart(error: any, onProgress?: null): Promise<void>;
|
|
109
118
|
emitWorkingStatus(payload: any, onProgress?: null): Promise<void>;
|
|
110
119
|
emitAssistantMessage(text: any): Promise<void>;
|
|
111
120
|
emitTerminalWorkingStatus(currentTurn: any, payload: any, onProgress?: null): Promise<void>;
|
|
@@ -154,6 +154,9 @@ export class OpencodeSdkSession extends EventEmitter {
|
|
|
154
154
|
this.currentTurn = null;
|
|
155
155
|
this.lastUsage = null;
|
|
156
156
|
this.lastAssistantInfo = null;
|
|
157
|
+
this.currentTurnStatus = null;
|
|
158
|
+
this.currentTurnActivityAt = 0;
|
|
159
|
+
this.now = typeof options.now === "function" ? options.now : () => Date.now();
|
|
157
160
|
this.turnDeadlineMs = getBoundedEnvInt("CONDUCTOR_TURN_DEADLINE_MS", DEFAULT_TURN_DEADLINE_MS, MIN_TURN_DEADLINE_MS, MAX_TURN_DEADLINE_MS);
|
|
158
161
|
this.client = null;
|
|
159
162
|
this.sdkModulePromise = null;
|
|
@@ -219,12 +222,16 @@ export class OpencodeSdkSession extends EventEmitter {
|
|
|
219
222
|
useSessionFileReplyStream: this.usesSessionFileReplyStream(),
|
|
220
223
|
resumeReady: Boolean(this.sessionId),
|
|
221
224
|
manualResume: null,
|
|
225
|
+
currentTurnStatus: this.getCurrentTurnStatus(),
|
|
222
226
|
pid: this.transport.pid || undefined,
|
|
223
227
|
};
|
|
224
228
|
}
|
|
225
229
|
getSessionInfo() {
|
|
226
230
|
return this.sessionInfo ? { ...this.sessionInfo } : null;
|
|
227
231
|
}
|
|
232
|
+
getCurrentTurnStatus() {
|
|
233
|
+
return this.currentTurnStatus ? { ...this.currentTurnStatus } : null;
|
|
234
|
+
}
|
|
228
235
|
async ensureSessionInfo() {
|
|
229
236
|
await this.boot();
|
|
230
237
|
return this.getSessionInfo();
|
|
@@ -260,6 +267,39 @@ export class OpencodeSdkSession extends EventEmitter {
|
|
|
260
267
|
getCurrentReplyTarget() {
|
|
261
268
|
return this.activeReplyTarget || this.lastReplyTarget || undefined;
|
|
262
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: OPENCODE_PROVIDER_VARIANT,
|
|
284
|
+
reply_in_progress: true,
|
|
285
|
+
replyTo: this.getCurrentReplyTarget(),
|
|
286
|
+
phase: "turn_started",
|
|
287
|
+
status_line: "opencode is working",
|
|
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
|
+
}
|
|
263
303
|
async emitWorkingStatus(payload, onProgress = null) {
|
|
264
304
|
const normalized = {
|
|
265
305
|
source: OPENCODE_PROVIDER_VARIANT,
|
|
@@ -274,15 +314,18 @@ export class OpencodeSdkSession extends EventEmitter {
|
|
|
274
314
|
session_id: this.sessionId || undefined,
|
|
275
315
|
session_file_path: undefined,
|
|
276
316
|
};
|
|
317
|
+
this.updateCurrentTurnStatus(normalized);
|
|
318
|
+
const snapshot = this.getCurrentTurnStatus();
|
|
277
319
|
if (typeof onProgress === "function") {
|
|
278
|
-
onProgress(
|
|
320
|
+
onProgress(snapshot);
|
|
279
321
|
}
|
|
280
322
|
if (typeof this.workingStatusHandler === "function") {
|
|
281
|
-
await this.workingStatusHandler(
|
|
323
|
+
await this.workingStatusHandler(snapshot);
|
|
282
324
|
}
|
|
283
|
-
this.emit("working_status",
|
|
325
|
+
this.emit("working_status", snapshot);
|
|
284
326
|
}
|
|
285
327
|
async emitAssistantMessage(text) {
|
|
328
|
+
this.touchTurnActivity();
|
|
286
329
|
const payload = {
|
|
287
330
|
text,
|
|
288
331
|
preserveWhitespace: true,
|
|
@@ -356,8 +399,27 @@ export class OpencodeSdkSession extends EventEmitter {
|
|
|
356
399
|
};
|
|
357
400
|
}
|
|
358
401
|
let timer = null;
|
|
359
|
-
|
|
402
|
+
let settled = false;
|
|
403
|
+
const schedule = (reject) => {
|
|
404
|
+
const now = this.now();
|
|
405
|
+
const lastActivityAt = Number.isFinite(this.currentTurnActivityAt) && this.currentTurnActivityAt > 0
|
|
406
|
+
? this.currentTurnActivityAt
|
|
407
|
+
: now;
|
|
408
|
+
const elapsedMs = Math.max(0, now - lastActivityAt);
|
|
409
|
+
const waitMs = Math.max(1, this.turnDeadlineMs - elapsedMs);
|
|
360
410
|
timer = setTimeout(() => {
|
|
411
|
+
if (settled) {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
const activityNow = this.now();
|
|
415
|
+
const latestActivityAt = Number.isFinite(this.currentTurnActivityAt) && this.currentTurnActivityAt > 0
|
|
416
|
+
? this.currentTurnActivityAt
|
|
417
|
+
: activityNow;
|
|
418
|
+
if (activityNow - latestActivityAt < this.turnDeadlineMs) {
|
|
419
|
+
schedule(reject);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
settled = true;
|
|
361
423
|
try {
|
|
362
424
|
onTimeout?.();
|
|
363
425
|
}
|
|
@@ -365,14 +427,18 @@ export class OpencodeSdkSession extends EventEmitter {
|
|
|
365
427
|
// best effort
|
|
366
428
|
}
|
|
367
429
|
reject(this.createTurnTimeoutError(this.turnDeadlineMs));
|
|
368
|
-
},
|
|
369
|
-
if (typeof timer
|
|
430
|
+
}, waitMs);
|
|
431
|
+
if (typeof timer?.unref === "function") {
|
|
370
432
|
timer.unref();
|
|
371
433
|
}
|
|
434
|
+
};
|
|
435
|
+
const promise = new Promise((_, reject) => {
|
|
436
|
+
schedule(reject);
|
|
372
437
|
});
|
|
373
438
|
return {
|
|
374
439
|
promise,
|
|
375
440
|
cleanup: () => {
|
|
441
|
+
settled = true;
|
|
376
442
|
if (timer) {
|
|
377
443
|
clearTimeout(timer);
|
|
378
444
|
}
|
|
@@ -1076,14 +1142,21 @@ export class OpencodeSdkSession extends EventEmitter {
|
|
|
1076
1142
|
if (!effectivePrompt) {
|
|
1077
1143
|
return buildEmptyTurnResult();
|
|
1078
1144
|
}
|
|
1079
|
-
await this.boot();
|
|
1080
1145
|
if (this.currentTurn) {
|
|
1081
1146
|
throw createTurnError("Opencode turn already running", {
|
|
1082
1147
|
reason: "turn_already_running",
|
|
1083
1148
|
});
|
|
1084
1149
|
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1150
|
+
this.markTurnStartedStatus();
|
|
1151
|
+
try {
|
|
1152
|
+
await this.boot();
|
|
1153
|
+
if (!this.client?.session || typeof this.client.session.promptAsync !== "function") {
|
|
1154
|
+
throw new Error("Opencode session client is unavailable");
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
catch (error) {
|
|
1158
|
+
await this.failPendingTurnStart(error, onProgress);
|
|
1159
|
+
throw error;
|
|
1087
1160
|
}
|
|
1088
1161
|
this.history.push({ role: "user", content: promptText });
|
|
1089
1162
|
const abortController = new AbortController();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@love-moon/ai-sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.32",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -25,5 +25,5 @@
|
|
|
25
25
|
"@types/node": "^22.10.2",
|
|
26
26
|
"typescript": "^5.6.3"
|
|
27
27
|
},
|
|
28
|
-
"gitCommitId": "
|
|
28
|
+
"gitCommitId": "c749d4b"
|
|
29
29
|
}
|