@love-moon/ai-sdk 0.13.1 → 0.13.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.
@@ -56,6 +56,24 @@ ${schemaText}
56
56
 
57
57
  ${promptText}`;
58
58
  }
59
+ function readTokenCount(breakdown) {
60
+ const value = Number(breakdown?.totalTokens);
61
+ return Number.isFinite(value) ? value : null;
62
+ }
63
+ /** Adds tokens spent outside the reported attempt (an overflowed first try) to a turn result or error. */
64
+ function addTurnTokens(holder, extraTokens) {
65
+ if (extraTokens > 0 && holder && typeof holder === "object") {
66
+ const counted = Number(holder.usage?.turnTotalTokens);
67
+ holder.usage = { ...(holder.usage || {}), turnTotalTokens: (Number.isFinite(counted) ? counted : 0) + extraTokens };
68
+ }
69
+ return holder;
70
+ }
71
+ /** A failed or interrupted turn still spent tokens: expose them on the error. */
72
+ function attachTurnUsage(error, usage) {
73
+ if (error && typeof error === "object" && error.usage === undefined) {
74
+ error.usage = usage;
75
+ }
76
+ }
59
77
  function normalizeItemId(value) {
60
78
  return typeof value === "string" ? value.trim() : "";
61
79
  }
@@ -185,6 +203,7 @@ export class CodexAppServerSession extends EventEmitter {
185
203
  */
186
204
  static capabilities = Object.freeze({
187
205
  goal: true,
206
+ compact: true,
188
207
  media: PROVIDER_MEDIA_CAPABILITIES[CODEX_APP_SERVER_VARIANT],
189
208
  });
190
209
  /**
@@ -219,6 +238,10 @@ export class CodexAppServerSession extends EventEmitter {
219
238
  this.nativeSessionId = "";
220
239
  this.rateLimits = null;
221
240
  this.tokenUsage = null;
241
+ // Thread total when the current turn/goal/compaction started, and whether
242
+ // its own usage has arrived since; see snapshotTokenUsage().
243
+ this.turnTokenBaseline = null;
244
+ this.turnTokensSeen = false;
222
245
  this.currentTurnStatus = null;
223
246
  this.currentTurnActivityAt = 0;
224
247
  this.now = typeof options.now === "function" ? options.now : () => Date.now();
@@ -467,6 +490,29 @@ export class CodexAppServerSession extends EventEmitter {
467
490
  }
468
491
  }
469
492
  }
493
+ /** Start counting a turn's tokens from the thread total known right now. */
494
+ beginTurnTokenAccounting() {
495
+ this.turnTokenBaseline = readTokenCount(this.tokenUsage?.total);
496
+ this.turnTokensSeen = false;
497
+ }
498
+ /**
499
+ * Thread token usage plus `turnTotalTokens`: tokens spent since the current
500
+ * turn (or goal) started. Codex only reports thread-cumulative totals, and
501
+ * only when a response completes, so a turn that ended before any response
502
+ * did has unknown usage.
503
+ */
504
+ snapshotTokenUsage() {
505
+ if (!this.tokenUsage) {
506
+ return null;
507
+ }
508
+ const total = readTokenCount(this.tokenUsage.total);
509
+ return {
510
+ ...this.tokenUsage,
511
+ ...(total !== null && this.turnTokenBaseline !== null && this.turnTokensSeen
512
+ ? { turnTotalTokens: Math.max(0, total - this.turnTokenBaseline) }
513
+ : {}),
514
+ };
515
+ }
470
516
  resolveContextUsagePercent() {
471
517
  const totalTokens = Number(this.tokenUsage?.total?.totalTokens);
472
518
  const contextWindow = Number(this.tokenUsage?.modelContextWindow);
@@ -527,6 +573,8 @@ export class CodexAppServerSession extends EventEmitter {
527
573
  this.manualResumeReady = false;
528
574
  this.sessionInfo = null;
529
575
  this.tokenUsage = null;
576
+ this.turnTokenBaseline = null;
577
+ this.turnTokensSeen = false;
530
578
  this.booted = false;
531
579
  this.bootPromise = null;
532
580
  }
@@ -602,7 +650,7 @@ export class CodexAppServerSession extends EventEmitter {
602
650
  if (!finalizedMessageId || !activeMessageId || activeMessageId === finalizedMessageId) {
603
651
  currentTurn.activeAssistantMessageId = "";
604
652
  }
605
- if (!text) {
653
+ if (!text || currentTurn.suppressReply) {
606
654
  return false;
607
655
  }
608
656
  await this.emitAssistantMessage(text);
@@ -925,9 +973,28 @@ export class CodexAppServerSession extends EventEmitter {
925
973
  await this.queueAssistantDelta(delta, { messageId });
926
974
  return;
927
975
  }
928
- case "thread/tokenUsage/updated":
929
- this.tokenUsage = params?.tokenUsage && typeof params.tokenUsage === "object" ? { ...params.tokenUsage } : null;
976
+ case "thread/tokenUsage/updated": {
977
+ const tokenUsage = params?.tokenUsage && typeof params.tokenUsage === "object" ? { ...params.tokenUsage } : null;
978
+ if (this.currentTurn && tokenUsage) {
979
+ // Codex replays a resumed thread's total right after thread/resume,
980
+ // before this turn's own responses (no or another turn id).
981
+ const turnId = this.currentTurn.turnId;
982
+ const beforeTurn = !turnId || Boolean(params?.turnId && params.turnId !== turnId);
983
+ if (this.turnTokenBaseline === null) {
984
+ // No total was known when the turn started (fresh boot or resume):
985
+ // a replayed total is the baseline; this turn's first response
986
+ // has `total - last` before it.
987
+ const total = readTokenCount(tokenUsage.total);
988
+ const last = readTokenCount(tokenUsage.last);
989
+ this.turnTokenBaseline = total === null ? null : beforeTurn ? total : last === null ? null : total - last;
990
+ }
991
+ if (!beforeTurn) {
992
+ this.turnTokensSeen = true;
993
+ }
994
+ }
995
+ this.tokenUsage = tokenUsage;
930
996
  return;
997
+ }
931
998
  case "account/rateLimits/updated":
932
999
  this.rateLimits = params?.rateLimits && typeof params.rateLimits === "object" ? { ...params.rateLimits } : null;
933
1000
  return;
@@ -1023,7 +1090,7 @@ export class CodexAppServerSession extends EventEmitter {
1023
1090
  await this.emitTurnCompletedStatus(currentTurn);
1024
1091
  currentTurn.resolve({
1025
1092
  turn,
1026
- usage: this.tokenUsage ? { ...this.tokenUsage } : null,
1093
+ usage: this.snapshotTokenUsage(),
1027
1094
  });
1028
1095
  this.currentTurn = null;
1029
1096
  return;
@@ -1145,6 +1212,7 @@ export class CodexAppServerSession extends EventEmitter {
1145
1212
  resolve: resolveTurn,
1146
1213
  reject: rejectTurn,
1147
1214
  };
1215
+ this.beginTurnTokenAccounting();
1148
1216
  this.currentTurn = currentTurn;
1149
1217
  try {
1150
1218
  const startPromise = this.transport.request("turn/start", {
@@ -1203,6 +1271,8 @@ export class CodexAppServerSession extends EventEmitter {
1203
1271
  !this.closeRequested &&
1204
1272
  !currentTurn.goalMode &&
1205
1273
  this.isRecoverableContextOverflow(error)) {
1274
+ // The overflowed attempt already spent tokens; the fresh thread's totals won't include them.
1275
+ const failedAttemptTokens = this.snapshotTokenUsage()?.turnTotalTokens ?? 0;
1206
1276
  // Drop the user message pushed for the failed attempt; the retry
1207
1277
  // re-adds it (and folds prior history in as a preface via buildPrompt).
1208
1278
  if (this.history.length > 0 &&
@@ -1227,12 +1297,15 @@ export class CodexAppServerSession extends EventEmitter {
1227
1297
  status_line: "codex context full; retrying on a fresh thread",
1228
1298
  });
1229
1299
  this.emit("thread_recovered", { reason: "context_overflow", backend: this.backend });
1230
- return await this.runTurn(promptText, {
1300
+ return addTurnTokens(await this.runTurn(promptText, {
1231
1301
  useInitialImages,
1232
1302
  media: mediaInput,
1233
1303
  contextFiles,
1234
1304
  jsonSchema,
1235
- });
1305
+ }), failedAttemptTokens);
1306
+ }
1307
+ catch (retryError) {
1308
+ throw addTurnTokens(retryError, failedAttemptTokens);
1236
1309
  }
1237
1310
  finally {
1238
1311
  this._contextRecoveryInFlight = false;
@@ -1246,6 +1319,97 @@ export class CodexAppServerSession extends EventEmitter {
1246
1319
  });
1247
1320
  }
1248
1321
  this.maybeEmitAuthRequired(error);
1322
+ attachTurnUsage(error, this.snapshotTokenUsage());
1323
+ throw error;
1324
+ }
1325
+ finally {
1326
+ if (this.currentTurn === currentTurn) {
1327
+ this.currentTurn = null;
1328
+ }
1329
+ closeGuard.cleanup();
1330
+ turnTimeoutGuard.cleanup();
1331
+ }
1332
+ }
1333
+ /**
1334
+ * Manually compact the thread via `thread/compact/start`. The app-server
1335
+ * runs compaction as a regular turn, so completion is the usual
1336
+ * `turn/completed`. The protocol takes no focus instructions.
1337
+ *
1338
+ * @param {import("../shared.js").CompactRequest} [request]
1339
+ * @returns {Promise<import("../shared.js").CompactResult>}
1340
+ */
1341
+ async runCompact(request = {}) {
1342
+ if (this.closeRequested) {
1343
+ throw this.createSessionClosedError();
1344
+ }
1345
+ if (this.currentTurn || this.currentGoalRun) {
1346
+ throw createTurnError("Codex app-server turn already running", {
1347
+ reason: "turn_already_running",
1348
+ });
1349
+ }
1350
+ const instructions = typeof request?.instructions === "string" ? request.instructions.trim() : "";
1351
+ if (instructions) {
1352
+ this.trace("thread/compact/start takes no instructions; ignoring them");
1353
+ }
1354
+ if (this.pendingHistorySeed || (!this.resumeSessionId && this.history.length === 0)) {
1355
+ return { compact: { status: "noop", instructionsApplied: false }, usage: null, metadata: {} };
1356
+ }
1357
+ this.markTurnStartedStatus();
1358
+ try {
1359
+ await this.boot();
1360
+ }
1361
+ catch (error) {
1362
+ await this.failPendingTurnStart(error);
1363
+ throw error;
1364
+ }
1365
+ const closeGuard = this.createCloseGuard();
1366
+ const turnTimeoutGuard = this.createTurnTimeoutGuard();
1367
+ let resolveTurn = null;
1368
+ let rejectTurn = null;
1369
+ const completion = new Promise((resolve, reject) => {
1370
+ resolveTurn = resolve;
1371
+ rejectTurn = reject;
1372
+ });
1373
+ const currentTurn = {
1374
+ turnId: "",
1375
+ fullText: "",
1376
+ activeAssistantMessageId: "",
1377
+ activeAssistantMessageText: "",
1378
+ resolve: resolveTurn,
1379
+ reject: rejectTurn,
1380
+ suppressReply: true,
1381
+ };
1382
+ this.beginTurnTokenAccounting();
1383
+ this.currentTurn = currentTurn;
1384
+ const metadata = { source: CODEX_APP_SERVER_VARIANT, threadId: this.sessionId };
1385
+ try {
1386
+ const completionResult = await Promise.race([
1387
+ (async () => {
1388
+ await this.transport.request("thread/compact/start", { threadId: this.sessionId });
1389
+ return await completion;
1390
+ })(),
1391
+ closeGuard.promise,
1392
+ turnTimeoutGuard.promise,
1393
+ ]);
1394
+ await this.emitTurnCompletedStatus(currentTurn);
1395
+ return {
1396
+ compact: { status: "compacted", instructionsApplied: false },
1397
+ usage: completionResult?.usage || null,
1398
+ metadata,
1399
+ };
1400
+ }
1401
+ catch (error) {
1402
+ if (error?.reason === "turn_timeout") {
1403
+ await this.interruptCurrentTurn();
1404
+ }
1405
+ if (!this.closeRequested && error?.reason !== "session_closed") {
1406
+ await this.emitWorkingStatus({
1407
+ phase: "turn_failed",
1408
+ reply_in_progress: false,
1409
+ status_done_line: error instanceof Error ? error.message : String(error),
1410
+ });
1411
+ }
1412
+ attachTurnUsage(error, this.snapshotTokenUsage());
1249
1413
  throw error;
1250
1414
  }
1251
1415
  finally {
@@ -1368,7 +1532,7 @@ export class CodexAppServerSession extends EventEmitter {
1368
1532
  if (isTerminalGoalStatusLocal(payload.status)) {
1369
1533
  resolveGoal?.({
1370
1534
  goal: { ...goalRun.latestGoal },
1371
- usage: this.tokenUsage ? { ...this.tokenUsage } : null,
1535
+ usage: this.snapshotTokenUsage(),
1372
1536
  cleared: false,
1373
1537
  });
1374
1538
  }
@@ -1382,7 +1546,7 @@ export class CodexAppServerSession extends EventEmitter {
1382
1546
  };
1383
1547
  resolveGoal?.({
1384
1548
  goal: { ...finalGoal, status: finalGoal.status || "complete" },
1385
- usage: this.tokenUsage ? { ...this.tokenUsage } : null,
1549
+ usage: this.snapshotTokenUsage(),
1386
1550
  cleared: true,
1387
1551
  });
1388
1552
  },
@@ -1406,6 +1570,7 @@ export class CodexAppServerSession extends EventEmitter {
1406
1570
  },
1407
1571
  goalMode: true,
1408
1572
  };
1573
+ this.beginTurnTokenAccounting();
1409
1574
  this.currentTurn = currentTurn;
1410
1575
  try {
1411
1576
  let setResult;
@@ -1440,7 +1605,7 @@ export class CodexAppServerSession extends EventEmitter {
1440
1605
  if (isTerminalGoalStatusLocal(initialGoal.status)) {
1441
1606
  resolveGoal?.({
1442
1607
  goal: { ...initialGoal },
1443
- usage: this.tokenUsage ? { ...this.tokenUsage } : null,
1608
+ usage: this.snapshotTokenUsage(),
1444
1609
  cleared: false,
1445
1610
  });
1446
1611
  }
@@ -1480,7 +1645,8 @@ export class CodexAppServerSession extends EventEmitter {
1480
1645
  status: goalState.status,
1481
1646
  tokenBudget: goalState.tokenBudget !== undefined ? goalState.tokenBudget : tokenBudget,
1482
1647
  },
1483
- usage: goalResult?.usage || (this.tokenUsage ? { ...this.tokenUsage } : null),
1648
+ // Fresh snapshot: usage can still arrive after the terminal goal status.
1649
+ usage: this.snapshotTokenUsage() || goalResult?.usage || null,
1484
1650
  metadata: {
1485
1651
  source: CODEX_APP_SERVER_VARIANT,
1486
1652
  threadId: this.sessionId,
@@ -1502,6 +1668,7 @@ export class CodexAppServerSession extends EventEmitter {
1502
1668
  });
1503
1669
  }
1504
1670
  this.maybeEmitAuthRequired(error);
1671
+ attachTurnUsage(error, this.snapshotTokenUsage());
1505
1672
  throw error;
1506
1673
  }
1507
1674
  finally {
@@ -41,7 +41,7 @@ export class CopilotSdkSession extends EventEmitter<[never]> {
41
41
  abortRequested: boolean;
42
42
  terminalWorkingStatusEmitted: boolean;
43
43
  error: null;
44
- onProgress: null;
44
+ onProgress: any;
45
45
  } | null;
46
46
  lastUsage: any;
47
47
  currentTurnStatus: any;
@@ -84,6 +84,7 @@ export class CopilotSdkSession extends EventEmitter<[never]> {
84
84
  } | null;
85
85
  currentTurnStatus: any;
86
86
  capabilities: {
87
+ compact: boolean;
87
88
  media: Readonly<{
88
89
  image: "native";
89
90
  }>;
@@ -170,6 +171,33 @@ export class CopilotSdkSession extends EventEmitter<[never]> {
170
171
  handleCopilotEventFailure(error: any): void;
171
172
  handleCopilotEvent(event: any): Promise<void>;
172
173
  interruptCurrentTurn(): Promise<void>;
174
+ createTurnState(onProgress: any): {
175
+ items: never[];
176
+ events: never[];
177
+ fullText: string;
178
+ messageOrder: never[];
179
+ messageText: Map<any, any>;
180
+ emittedMessageIds: Set<any>;
181
+ activeToolName: string;
182
+ activeToolPhase: string;
183
+ abortRequested: boolean;
184
+ terminalWorkingStatusEmitted: boolean;
185
+ error: null;
186
+ onProgress: any;
187
+ };
188
+ /**
189
+ * Manually compact the session via the CLI's `session.history.compact` RPC
190
+ * (the same call its interactive `/compact` makes). `customInstructions`
191
+ * is not in the SDK's typed wrapper, so the request goes through the raw
192
+ * connection.
193
+ *
194
+ * @param {import("../shared.js").CompactRequest} [request]
195
+ * @param {{ onProgress?: Function }} [options]
196
+ * @returns {Promise<import("../shared.js").CompactResult>}
197
+ */
198
+ runCompact(request?: import("../shared.js").CompactRequest, { onProgress }?: {
199
+ onProgress?: Function;
200
+ }): Promise<import("../shared.js").CompactResult>;
173
201
  runTurn(promptText: any, { useInitialImages, media: mediaInput, contextFiles, onProgress }?: {
174
202
  useInitialImages?: boolean | undefined;
175
203
  onProgress?: null | undefined;
@@ -594,7 +594,7 @@ export class CopilotSdkSession extends EventEmitter {
594
594
  }
595
595
  : null,
596
596
  currentTurnStatus: this.getCurrentTurnStatus(),
597
- capabilities: { media: PROVIDER_MEDIA_CAPABILITIES[COPILOT_PROVIDER_VARIANT] },
597
+ capabilities: { compact: true, media: PROVIDER_MEDIA_CAPABILITIES[COPILOT_PROVIDER_VARIANT] },
598
598
  };
599
599
  }
600
600
  getSessionInfo() {
@@ -709,7 +709,7 @@ export class CopilotSdkSession extends EventEmitter {
709
709
  return false;
710
710
  }
711
711
  const text = currentTurn.messageText.get(messageId) || "";
712
- if (!text) {
712
+ if (!text || currentTurn.suppressReply) {
713
713
  return false;
714
714
  }
715
715
  currentTurn.emittedMessageIds.add(messageId);
@@ -1362,6 +1362,11 @@ export class CopilotSdkSession extends EventEmitter {
1362
1362
  }
1363
1363
  }
1364
1364
  async interruptCurrentTurn() {
1365
+ if (this.currentTurn?.abortCompaction) {
1366
+ this.currentTurn.abortRequested = true;
1367
+ this.currentTurn.abortCompaction();
1368
+ return;
1369
+ }
1365
1370
  if (!this.currentTurn || !this.session || typeof this.session.abort !== "function") {
1366
1371
  return;
1367
1372
  }
@@ -1374,6 +1379,109 @@ export class CopilotSdkSession extends EventEmitter {
1374
1379
  }
1375
1380
  }
1376
1381
  }
1382
+ createTurnState(onProgress) {
1383
+ return {
1384
+ items: [],
1385
+ events: [],
1386
+ fullText: "",
1387
+ messageOrder: [],
1388
+ messageText: new Map(),
1389
+ emittedMessageIds: new Set(),
1390
+ activeToolName: "",
1391
+ activeToolPhase: "",
1392
+ abortRequested: false,
1393
+ terminalWorkingStatusEmitted: false,
1394
+ error: null,
1395
+ onProgress,
1396
+ };
1397
+ }
1398
+ /**
1399
+ * Manually compact the session via the CLI's `session.history.compact` RPC
1400
+ * (the same call its interactive `/compact` makes). `customInstructions`
1401
+ * is not in the SDK's typed wrapper, so the request goes through the raw
1402
+ * connection.
1403
+ *
1404
+ * @param {import("../shared.js").CompactRequest} [request]
1405
+ * @param {{ onProgress?: Function }} [options]
1406
+ * @returns {Promise<import("../shared.js").CompactResult>}
1407
+ */
1408
+ async runCompact(request = {}, { onProgress = null } = {}) {
1409
+ if (this.currentTurn) {
1410
+ throw this.createTurnAlreadyRunningError();
1411
+ }
1412
+ const instructions = typeof request?.instructions === "string" ? request.instructions.trim() : "";
1413
+ const noop = { compact: { status: "noop", instructionsApplied: false }, usage: null, metadata: {} };
1414
+ if (this.pendingHistorySeed) {
1415
+ return noop;
1416
+ }
1417
+ const currentTurn = this.createTurnState(onProgress);
1418
+ currentTurn.suppressReply = true;
1419
+ this.currentTurn = currentTurn;
1420
+ this.touchTurnActivity();
1421
+ this.markTurnStartedStatus();
1422
+ // `session.abort()` does not stop a manual compaction; it has its own abort RPC.
1423
+ const abortCompaction = () => {
1424
+ Promise.resolve()
1425
+ .then(() => this.session?.connection?.sendRequest("session.history.abortManualCompaction", {
1426
+ sessionId: this.session.sessionId,
1427
+ }))
1428
+ .catch(() => { });
1429
+ };
1430
+ currentTurn.abortCompaction = abortCompaction;
1431
+ const closeGuard = this.createCloseGuard(abortCompaction);
1432
+ const turnTimeoutGuard = this.createTurnTimeoutGuard(abortCompaction);
1433
+ try {
1434
+ await Promise.race([this.boot(), closeGuard.promise, turnTimeoutGuard.promise]);
1435
+ this.applySessionInfo(this.session?.sessionId || this.sessionId);
1436
+ await this.emitWorkingStatus({ phase: "context_compaction", reply_in_progress: true, status_line: "copilot compacting context" }, onProgress);
1437
+ if (currentTurn.abortRequested) {
1438
+ throw createTurnError("Copilot compaction interrupted", { reason: "turn_interrupted" });
1439
+ }
1440
+ const result = await Promise.race([
1441
+ this.requestOrThrow(this.session.connection.sendRequest("session.history.compact", {
1442
+ sessionId: this.session.sessionId,
1443
+ // The CLI schema caps focus instructions at 4000 characters.
1444
+ ...(instructions ? { customInstructions: instructions.slice(0, 4000) } : {}),
1445
+ })),
1446
+ closeGuard.promise,
1447
+ turnTimeoutGuard.promise,
1448
+ ]);
1449
+ if (result?.success === false) {
1450
+ throw createTurnError("Copilot compaction failed", { reason: "compact_failed" });
1451
+ }
1452
+ await this.emitTerminalWorkingStatus(currentTurn, { phase: "turn_completed", status_done_line: "copilot compacted context" }, onProgress);
1453
+ const tokensRemoved = Number(result?.tokensRemoved);
1454
+ return {
1455
+ compact: {
1456
+ status: "compacted",
1457
+ instructionsApplied: Boolean(instructions),
1458
+ tokensRemoved: Number.isFinite(tokensRemoved) ? tokensRemoved : undefined,
1459
+ },
1460
+ usage: null,
1461
+ metadata: { source: COPILOT_PROVIDER_VARIANT, sessionId: this.sessionId || undefined },
1462
+ };
1463
+ }
1464
+ catch (error) {
1465
+ const message = extractErrorMessage(error);
1466
+ // The CLI refuses when the session holds nothing it can summarize yet.
1467
+ if (!currentTurn.abortRequested && /nothing to compact|no active agent context/i.test(message)) {
1468
+ await this.emitTerminalWorkingStatus(currentTurn, { phase: "turn_completed", status_done_line: "copilot finished" }, onProgress);
1469
+ return noop;
1470
+ }
1471
+ if (currentTurn.abortRequested && error?.reason !== "turn_timeout" && error?.reason !== "session_closed") {
1472
+ await this.emitTerminalWorkingStatus(currentTurn, { phase: "turn_interrupted", status_done_line: "copilot interrupted" }, onProgress);
1473
+ throw createTurnError("Copilot compaction interrupted", { reason: "turn_interrupted" });
1474
+ }
1475
+ await this.emitTerminalWorkingStatus(currentTurn, { phase: "turn_failed", status_done_line: message }, onProgress);
1476
+ throw error;
1477
+ }
1478
+ finally {
1479
+ closeGuard.cleanup();
1480
+ turnTimeoutGuard.cleanup();
1481
+ this.activeReplyTarget = "";
1482
+ this.currentTurn = null;
1483
+ }
1484
+ }
1377
1485
  async runTurn(promptText, { useInitialImages = false, media: mediaInput, contextFiles, onProgress = null } = {}) {
1378
1486
  if (this.currentTurn) {
1379
1487
  throw this.createTurnAlreadyRunningError();
@@ -1394,20 +1502,7 @@ export class CopilotSdkSession extends EventEmitter {
1394
1502
  },
1395
1503
  };
1396
1504
  }
1397
- const currentTurn = {
1398
- items: [],
1399
- events: [],
1400
- fullText: "",
1401
- messageOrder: [],
1402
- messageText: new Map(),
1403
- emittedMessageIds: new Set(),
1404
- activeToolName: "",
1405
- activeToolPhase: "",
1406
- abortRequested: false,
1407
- terminalWorkingStatusEmitted: false,
1408
- error: null,
1409
- onProgress,
1410
- };
1505
+ const currentTurn = this.createTurnState(onProgress);
1411
1506
  this.currentTurn = currentTurn;
1412
1507
  this.touchTurnActivity();
1413
1508
  this.markTurnStartedStatus();
@@ -1,4 +1,5 @@
1
1
  export function defaultDshSessionRoot(): string;
2
+ export const COMPACT_SUMMARY_PROMPT: string;
2
3
  /**
3
4
  * DeepSeek Harness (dsh) backend session.
4
5
  *
@@ -17,10 +18,12 @@ export function defaultDshSessionRoot(): string;
17
18
  export class DshSdkSession extends EventEmitter<[never]> {
18
19
  static capabilities: Readonly<{
19
20
  goal: false;
21
+ compact: true;
20
22
  }>;
21
23
  constructor(backend: any, options?: {});
22
24
  getCapabilities(): {
23
25
  goal: false;
26
+ compact: true;
24
27
  };
25
28
  backend: string;
26
29
  options: {};
@@ -52,6 +55,7 @@ export class DshSdkSession extends EventEmitter<[never]> {
52
55
  manualResumeReady: boolean;
53
56
  sessionAnnounced: boolean;
54
57
  currentTurn: {
58
+ suppressReply: boolean;
55
59
  emittedAssistantMessage: boolean;
56
60
  fullText: string;
57
61
  interrupted: boolean;
@@ -87,6 +91,7 @@ export class DshSdkSession extends EventEmitter<[never]> {
87
91
  currentTurnStatus: any;
88
92
  capabilities: {
89
93
  goal: false;
94
+ compact: true;
90
95
  };
91
96
  };
92
97
  getSessionInfo(): {
@@ -165,9 +170,23 @@ export class DshSdkSession extends EventEmitter<[never]> {
165
170
  onProgress: any;
166
171
  }): Promise<void>;
167
172
  interruptCurrentTurn(): Promise<boolean>;
168
- runTurn(promptText: any, { useInitialImages, onProgress }?: {
173
+ /**
174
+ * Manually compact the conversation. The dsh SDK wire exposes no compaction
175
+ * request (its engine's `compactNow` is internal), so the model summarizes
176
+ * the conversation in one silent turn and the chat continues on a fresh
177
+ * wire session seeded with only that summary.
178
+ *
179
+ * @param {import("../shared.js").CompactRequest} [request]
180
+ * @param {{ onProgress?: Function }} [options]
181
+ * @returns {Promise<import("../shared.js").CompactResult>}
182
+ */
183
+ runCompact(request?: import("../shared.js").CompactRequest, { onProgress }?: {
184
+ onProgress?: Function;
185
+ }): Promise<import("../shared.js").CompactResult>;
186
+ runTurn(promptText: any, { useInitialImages, onProgress, suppressReply }?: {
169
187
  useInitialImages?: boolean | undefined;
170
188
  onProgress?: null | undefined;
189
+ suppressReply?: boolean | undefined;
171
190
  }): Promise<{
172
191
  text: string;
173
192
  usage: null;