@love-moon/ai-sdk 0.2.31 → 0.2.33

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.
@@ -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(normalized);
387
+ onProgress(snapshot);
346
388
  }
347
389
  if (typeof this.workingStatusHandler === "function") {
348
- await this.workingStatusHandler(normalized);
390
+ await this.workingStatusHandler(snapshot);
349
391
  }
350
- this.emit("working_status", normalized);
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
- const promise = new Promise((_, reject) => {
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
- }, this.turnDeadlineMs);
436
- if (typeof timer.unref === "function") {
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(normalized);
320
+ onProgress(snapshot);
279
321
  }
280
322
  if (typeof this.workingStatusHandler === "function") {
281
- await this.workingStatusHandler(normalized);
323
+ await this.workingStatusHandler(snapshot);
282
324
  }
283
- this.emit("working_status", normalized);
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
- const promise = new Promise((_, reject) => {
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
- }, this.turnDeadlineMs);
369
- if (typeof timer.unref === "function") {
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
- if (!this.client?.session || typeof this.client.session.promptAsync !== "function") {
1086
- throw new Error("Opencode session client is unavailable");
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.31",
3
+ "version": "0.2.33",
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": "7e0bd83"
28
+ "gitCommitId": "db7f9bf"
29
29
  }
package/dist/resume.d.ts DELETED
@@ -1,26 +0,0 @@
1
- export function resumeProviderForBackend(backend: any): "codex" | "claude" | "copilot" | null;
2
- export function findSessionPath(provider: any, sessionId: any, options?: {}): Promise<any>;
3
- export function findCodexSessionPath(sessionId: any, options?: {}): Promise<string | null>;
4
- export function findClaudeSessionPath(sessionId: any, options?: {}): Promise<any>;
5
- export function findCopilotSessionPath(sessionId: any, options?: {}): Promise<string | null>;
6
- export function resolveSessionRunDirectory(sessionPath: any): Promise<string>;
7
- export function inspectResumeTarget(backend: any, sessionId: any, options?: {}): Promise<{
8
- provider: string;
9
- sessionId: string;
10
- sessionPath: any;
11
- cwd: string;
12
- debugMetadata: {
13
- cwdSource: string;
14
- sessionPath: any;
15
- };
16
- }>;
17
- export function resolveResumeContext(backend: any, sessionId: any, options?: {}): Promise<{
18
- provider: string;
19
- sessionId: string;
20
- sessionPath: any;
21
- cwd: string;
22
- debugMetadata: {
23
- cwdSource: string;
24
- sessionPath: any;
25
- };
26
- }>;