@dexto/core 1.5.2 → 1.5.4

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.
Files changed (137) hide show
  1. package/dist/agent/DextoAgent.cjs +296 -2
  2. package/dist/agent/DextoAgent.d.ts +114 -0
  3. package/dist/agent/DextoAgent.d.ts.map +1 -1
  4. package/dist/agent/DextoAgent.js +287 -2
  5. package/dist/agent/schemas.d.ts +93 -21
  6. package/dist/agent/schemas.d.ts.map +1 -1
  7. package/dist/approval/manager.cjs +16 -0
  8. package/dist/approval/manager.d.ts +10 -0
  9. package/dist/approval/manager.d.ts.map +1 -1
  10. package/dist/approval/manager.js +16 -0
  11. package/dist/approval/types.d.ts +11 -0
  12. package/dist/approval/types.d.ts.map +1 -1
  13. package/dist/context/compaction/overflow.cjs +6 -10
  14. package/dist/context/compaction/overflow.d.ts +14 -11
  15. package/dist/context/compaction/overflow.d.ts.map +1 -1
  16. package/dist/context/compaction/overflow.js +6 -10
  17. package/dist/context/compaction/providers/reactive-overflow-provider.cjs +15 -0
  18. package/dist/context/compaction/providers/reactive-overflow-provider.d.ts +15 -0
  19. package/dist/context/compaction/providers/reactive-overflow-provider.d.ts.map +1 -1
  20. package/dist/context/compaction/providers/reactive-overflow-provider.js +15 -0
  21. package/dist/context/compaction/schemas.cjs +22 -2
  22. package/dist/context/compaction/schemas.d.ts +45 -0
  23. package/dist/context/compaction/schemas.d.ts.map +1 -1
  24. package/dist/context/compaction/schemas.js +22 -2
  25. package/dist/context/compaction/strategies/reactive-overflow.cjs +166 -26
  26. package/dist/context/compaction/strategies/reactive-overflow.d.ts +21 -0
  27. package/dist/context/compaction/strategies/reactive-overflow.d.ts.map +1 -1
  28. package/dist/context/compaction/strategies/reactive-overflow.js +166 -26
  29. package/dist/context/manager.cjs +278 -31
  30. package/dist/context/manager.d.ts +192 -5
  31. package/dist/context/manager.d.ts.map +1 -1
  32. package/dist/context/manager.js +285 -32
  33. package/dist/context/types.d.ts +6 -0
  34. package/dist/context/types.d.ts.map +1 -1
  35. package/dist/context/utils.cjs +77 -11
  36. package/dist/context/utils.d.ts +86 -8
  37. package/dist/context/utils.d.ts.map +1 -1
  38. package/dist/context/utils.js +71 -11
  39. package/dist/events/index.cjs +7 -1
  40. package/dist/events/index.d.ts +58 -7
  41. package/dist/events/index.d.ts.map +1 -1
  42. package/dist/events/index.js +7 -1
  43. package/dist/filesystem/filesystem-service.cjs +18 -15
  44. package/dist/filesystem/filesystem-service.d.ts +3 -3
  45. package/dist/filesystem/filesystem-service.d.ts.map +1 -1
  46. package/dist/filesystem/filesystem-service.js +18 -15
  47. package/dist/filesystem/path-validator.cjs +16 -7
  48. package/dist/filesystem/path-validator.d.ts +10 -3
  49. package/dist/filesystem/path-validator.d.ts.map +1 -1
  50. package/dist/filesystem/path-validator.js +16 -7
  51. package/dist/filesystem/types.d.ts +4 -0
  52. package/dist/filesystem/types.d.ts.map +1 -1
  53. package/dist/llm/executor/stream-processor.cjs +19 -1
  54. package/dist/llm/executor/stream-processor.d.ts +3 -0
  55. package/dist/llm/executor/stream-processor.d.ts.map +1 -1
  56. package/dist/llm/executor/stream-processor.js +19 -1
  57. package/dist/llm/executor/turn-executor.cjs +219 -30
  58. package/dist/llm/executor/turn-executor.d.ts +62 -10
  59. package/dist/llm/executor/turn-executor.d.ts.map +1 -1
  60. package/dist/llm/executor/turn-executor.js +219 -30
  61. package/dist/llm/executor/types.d.ts +28 -0
  62. package/dist/llm/executor/types.d.ts.map +1 -1
  63. package/dist/llm/formatters/vercel.cjs +36 -28
  64. package/dist/llm/formatters/vercel.d.ts.map +1 -1
  65. package/dist/llm/formatters/vercel.js +36 -28
  66. package/dist/llm/services/factory.cjs +3 -2
  67. package/dist/llm/services/factory.d.ts +3 -1
  68. package/dist/llm/services/factory.d.ts.map +1 -1
  69. package/dist/llm/services/factory.js +3 -2
  70. package/dist/llm/services/vercel.cjs +34 -6
  71. package/dist/llm/services/vercel.d.ts +23 -3
  72. package/dist/llm/services/vercel.d.ts.map +1 -1
  73. package/dist/llm/services/vercel.js +34 -6
  74. package/dist/logger/v2/schemas.cjs +4 -0
  75. package/dist/logger/v2/schemas.d.ts +16 -0
  76. package/dist/logger/v2/schemas.d.ts.map +1 -1
  77. package/dist/logger/v2/schemas.js +4 -0
  78. package/dist/logger/v2/transport-factory.cjs +4 -1
  79. package/dist/logger/v2/transport-factory.d.ts.map +1 -1
  80. package/dist/logger/v2/transport-factory.js +4 -1
  81. package/dist/logger/v2/transports/silent-transport.cjs +33 -0
  82. package/dist/logger/v2/transports/silent-transport.d.ts +15 -0
  83. package/dist/logger/v2/transports/silent-transport.d.ts.map +1 -0
  84. package/dist/logger/v2/transports/silent-transport.js +10 -0
  85. package/dist/session/chat-session.cjs +20 -11
  86. package/dist/session/chat-session.d.ts +9 -4
  87. package/dist/session/chat-session.d.ts.map +1 -1
  88. package/dist/session/chat-session.js +20 -11
  89. package/dist/session/compaction-service.cjs +139 -0
  90. package/dist/session/compaction-service.d.ts +81 -0
  91. package/dist/session/compaction-service.d.ts.map +1 -0
  92. package/dist/session/compaction-service.js +106 -0
  93. package/dist/session/session-manager.cjs +146 -0
  94. package/dist/session/session-manager.d.ts +50 -0
  95. package/dist/session/session-manager.d.ts.map +1 -1
  96. package/dist/session/session-manager.js +146 -0
  97. package/dist/session/title-generator.cjs +2 -2
  98. package/dist/session/title-generator.js +2 -2
  99. package/dist/systemPrompt/in-built-prompts.cjs +36 -0
  100. package/dist/systemPrompt/in-built-prompts.d.ts +18 -1
  101. package/dist/systemPrompt/in-built-prompts.d.ts.map +1 -1
  102. package/dist/systemPrompt/in-built-prompts.js +25 -0
  103. package/dist/systemPrompt/manager.cjs +22 -0
  104. package/dist/systemPrompt/manager.d.ts +10 -0
  105. package/dist/systemPrompt/manager.d.ts.map +1 -1
  106. package/dist/systemPrompt/manager.js +22 -0
  107. package/dist/systemPrompt/registry.cjs +2 -1
  108. package/dist/systemPrompt/registry.d.ts +1 -1
  109. package/dist/systemPrompt/registry.d.ts.map +1 -1
  110. package/dist/systemPrompt/registry.js +2 -1
  111. package/dist/systemPrompt/schemas.cjs +7 -0
  112. package/dist/systemPrompt/schemas.d.ts +13 -13
  113. package/dist/systemPrompt/schemas.d.ts.map +1 -1
  114. package/dist/systemPrompt/schemas.js +7 -0
  115. package/dist/tools/error-codes.cjs +1 -0
  116. package/dist/tools/error-codes.d.ts +1 -0
  117. package/dist/tools/error-codes.d.ts.map +1 -1
  118. package/dist/tools/error-codes.js +1 -0
  119. package/dist/tools/errors.cjs +17 -0
  120. package/dist/tools/errors.d.ts +9 -0
  121. package/dist/tools/errors.d.ts.map +1 -1
  122. package/dist/tools/errors.js +17 -0
  123. package/dist/tools/internal-tools/provider.cjs +3 -2
  124. package/dist/tools/internal-tools/provider.d.ts +1 -1
  125. package/dist/tools/internal-tools/provider.d.ts.map +1 -1
  126. package/dist/tools/internal-tools/provider.js +3 -2
  127. package/dist/tools/tool-manager.cjs +77 -4
  128. package/dist/tools/tool-manager.d.ts +18 -0
  129. package/dist/tools/tool-manager.d.ts.map +1 -1
  130. package/dist/tools/tool-manager.js +78 -5
  131. package/dist/tools/types.d.ts +5 -3
  132. package/dist/tools/types.d.ts.map +1 -1
  133. package/dist/utils/index.cjs +3 -1
  134. package/dist/utils/index.d.ts +1 -0
  135. package/dist/utils/index.d.ts.map +1 -1
  136. package/dist/utils/index.js +1 -0
  137. package/package.json +1 -1
@@ -398,7 +398,10 @@ Either:
398
398
  llmConfig.model
399
399
  );
400
400
  }
401
- const responseEvent = events.find((e) => e.name === "llm:response");
401
+ const responseEvents = events.filter(
402
+ (e) => e.name === "llm:response"
403
+ );
404
+ const responseEvent = responseEvents[responseEvents.length - 1];
402
405
  if (!responseEvent || responseEvent.name !== "llm:response") {
403
406
  const llmConfig = this.stateManager.getLLMConfig(sessionId);
404
407
  throw LLMError.generationFailed(
@@ -582,6 +585,30 @@ Either:
582
585
  signal: cleanupSignal
583
586
  });
584
587
  listeners.push({ event: "tool:running", listener: toolRunningListener });
588
+ const contextCompactingListener = (data) => {
589
+ if (data.sessionId !== sessionId) return;
590
+ eventQueue.push({ name: "context:compacting", ...data });
591
+ };
592
+ this.agentEventBus.on("context:compacting", contextCompactingListener, {
593
+ signal: cleanupSignal
594
+ });
595
+ listeners.push({ event: "context:compacting", listener: contextCompactingListener });
596
+ const contextCompactedListener = (data) => {
597
+ if (data.sessionId !== sessionId) return;
598
+ eventQueue.push({ name: "context:compacted", ...data });
599
+ };
600
+ this.agentEventBus.on("context:compacted", contextCompactedListener, {
601
+ signal: cleanupSignal
602
+ });
603
+ listeners.push({ event: "context:compacted", listener: contextCompactedListener });
604
+ const sessionContinuedListener = (data) => {
605
+ if (data.previousSessionId !== sessionId) return;
606
+ eventQueue.push({ name: "session:continued", ...data });
607
+ };
608
+ this.agentEventBus.on("session:continued", sessionContinuedListener, {
609
+ signal: cleanupSignal
610
+ });
611
+ listeners.push({ event: "session:continued", listener: sessionContinuedListener });
585
612
  const messageQueuedListener = (data) => {
586
613
  if (data.sessionId !== sessionId) return;
587
614
  eventQueue.push({ name: "message:queued", ...data });
@@ -598,6 +625,14 @@ Either:
598
625
  signal: cleanupSignal
599
626
  });
600
627
  listeners.push({ event: "message:dequeued", listener: messageDequeuedListener });
628
+ const serviceEventListener = (data) => {
629
+ if (data.sessionId !== sessionId) return;
630
+ eventQueue.push({ name: "service:event", ...data });
631
+ };
632
+ this.agentEventBus.on("service:event", serviceEventListener, {
633
+ signal: cleanupSignal
634
+ });
635
+ listeners.push({ event: "service:event", listener: serviceEventListener });
601
636
  const runCompleteListener = (data) => {
602
637
  if (data.sessionId !== sessionId) return;
603
638
  eventQueue.push({ name: "run:complete", ...data });
@@ -733,12 +768,18 @@ Either:
733
768
  contentParts = [{ type: "text", text: textContent }];
734
769
  }
735
770
  const session = await this.sessionManager.getSession(sessionId) || await this.sessionManager.createSession(sessionId);
736
- await session.stream(contentParts, signal ? { signal } : void 0);
771
+ const streamResult = await session.stream(
772
+ contentParts,
773
+ signal ? { signal } : void 0
774
+ );
737
775
  this.sessionManager.incrementMessageCount(session.id).catch(
738
776
  (error) => this.logger.warn(
739
777
  `Failed to increment message count: ${error instanceof Error ? error.message : String(error)}`
740
778
  )
741
779
  );
780
+ if (streamResult.didCompact && streamResult.compaction) {
781
+ await this.handleSessionContinuation(session, streamResult.compaction);
782
+ }
742
783
  } catch (err) {
743
784
  const error = err instanceof DextoRuntimeError || err instanceof DextoValidationError ? err : err instanceof Error ? err : AgentError.streamFailed(String(err));
744
785
  completed = true;
@@ -1112,6 +1153,173 @@ Either:
1112
1153
  sessionId
1113
1154
  });
1114
1155
  }
1156
+ /**
1157
+ * Manually compact the context using session-native compaction.
1158
+ *
1159
+ * Session-native compaction creates a NEW session with the summary as initial context,
1160
+ * providing clean session isolation while maintaining traceability via linking.
1161
+ *
1162
+ * The old session is marked as compacted with `continuedTo` pointing to the new session,
1163
+ * and the new session has `continuedFrom` pointing back to the old session.
1164
+ *
1165
+ * @param sessionId Session ID of the session to compact (required)
1166
+ * @returns Compaction result with new session info, or null if compaction was skipped
1167
+ */
1168
+ async compactContext(sessionId) {
1169
+ this.ensureStarted();
1170
+ if (!sessionId || typeof sessionId !== "string") {
1171
+ throw AgentError.apiValidationError(
1172
+ "sessionId is required and must be a non-empty string"
1173
+ );
1174
+ }
1175
+ const session = await this.sessionManager.getSession(sessionId);
1176
+ if (!session) {
1177
+ throw SessionError.notFound(sessionId);
1178
+ }
1179
+ const llmService = session.getLLMService();
1180
+ const compactionStrategy = llmService.getCompactionStrategy();
1181
+ if (!compactionStrategy) {
1182
+ this.logger.warn(
1183
+ `Compaction strategy not configured for session ${sessionId} - skipping manual compaction`
1184
+ );
1185
+ return null;
1186
+ }
1187
+ const { SessionCompactionService } = await import("../session/compaction-service.js");
1188
+ const compactionService = new SessionCompactionService(
1189
+ this.sessionManager,
1190
+ compactionStrategy,
1191
+ this.logger
1192
+ );
1193
+ const compactionResult = await compactionService.compact(session, {
1194
+ reason: "manual",
1195
+ eventBus: this.agentEventBus
1196
+ });
1197
+ if (!compactionResult) {
1198
+ this.logger.debug(`Compaction skipped for session ${sessionId} - nothing to compact`);
1199
+ return null;
1200
+ }
1201
+ this.logger.info(
1202
+ `Session-native compaction complete: ${sessionId} \u2192 ${compactionResult.newSessionId}, ${compactionResult.originalMessages} messages \u2192 ~${compactionResult.summaryTokens} token summary`
1203
+ );
1204
+ return {
1205
+ previousSessionId: compactionResult.previousSessionId,
1206
+ newSessionId: compactionResult.newSessionId,
1207
+ summaryTokens: compactionResult.summaryTokens,
1208
+ originalMessages: compactionResult.originalMessages
1209
+ };
1210
+ }
1211
+ /**
1212
+ * Get the chain of linked sessions (ancestors and descendants) for a session.
1213
+ * Useful for displaying session history and understanding compaction lineage.
1214
+ *
1215
+ * @param sessionId Any session ID in the chain
1216
+ * @returns Array of session data in the chain, ordered chronologically (oldest first)
1217
+ */
1218
+ async getSessionLineage(sessionId) {
1219
+ this.ensureStarted();
1220
+ if (!sessionId || typeof sessionId !== "string") {
1221
+ throw AgentError.apiValidationError(
1222
+ "sessionId is required and must be a non-empty string"
1223
+ );
1224
+ }
1225
+ const chain = await this.sessionManager.getSessionChain(sessionId);
1226
+ if (chain.length === 0) {
1227
+ throw SessionError.notFound(sessionId);
1228
+ }
1229
+ const currentIndex = chain.findIndex((s) => s.id === sessionId);
1230
+ if (currentIndex < 0) {
1231
+ throw SessionError.notFound(sessionId);
1232
+ }
1233
+ return {
1234
+ chain: chain.map((s) => {
1235
+ const item = {
1236
+ id: s.id,
1237
+ createdAt: s.createdAt
1238
+ };
1239
+ if (s.continuedFrom !== void 0) {
1240
+ item.continuedFrom = s.continuedFrom;
1241
+ }
1242
+ if (s.continuedTo !== void 0) {
1243
+ item.continuedTo = s.continuedTo;
1244
+ }
1245
+ if (s.compactedAt !== void 0) {
1246
+ item.compactedAt = s.compactedAt;
1247
+ }
1248
+ return item;
1249
+ }),
1250
+ currentIndex
1251
+ };
1252
+ }
1253
+ /**
1254
+ * Get context usage statistics for a session.
1255
+ * Useful for monitoring context window usage and compaction status.
1256
+ *
1257
+ * @param sessionId Session ID (required)
1258
+ * @returns Context statistics including token estimates and message counts
1259
+ */
1260
+ async getContextStats(sessionId) {
1261
+ this.ensureStarted();
1262
+ if (!sessionId || typeof sessionId !== "string") {
1263
+ throw AgentError.apiValidationError(
1264
+ "sessionId is required and must be a non-empty string"
1265
+ );
1266
+ }
1267
+ const session = await this.sessionManager.getSession(sessionId);
1268
+ if (!session) {
1269
+ throw SessionError.notFound(sessionId);
1270
+ }
1271
+ const contextManager = session.getContextManager();
1272
+ const contributorContext = { mcpManager: this.mcpManager };
1273
+ const llmService = session.getLLMService();
1274
+ const tools = await llmService.getAllTools();
1275
+ const tokenEstimate = await contextManager.getContextTokenEstimate(
1276
+ contributorContext,
1277
+ tools
1278
+ );
1279
+ const history = await contextManager.getHistory();
1280
+ const runtimeConfig = this.stateManager.getRuntimeConfig(sessionId);
1281
+ const compactionConfig = runtimeConfig.compaction;
1282
+ const modelContextWindow = contextManager.getMaxInputTokens();
1283
+ let maxContextTokens = modelContextWindow;
1284
+ if (compactionConfig?.maxContextTokens !== void 0) {
1285
+ maxContextTokens = Math.min(maxContextTokens, compactionConfig.maxContextTokens);
1286
+ }
1287
+ const thresholdPercent = compactionConfig?.thresholdPercent ?? 0.9;
1288
+ if (thresholdPercent < 1) {
1289
+ maxContextTokens = Math.floor(maxContextTokens * thresholdPercent);
1290
+ }
1291
+ const hasSummary = history.some(
1292
+ (msg) => msg.metadata?.isSummary === true || msg.metadata?.isSessionSummary === true
1293
+ );
1294
+ const llmConfig = runtimeConfig.llm;
1295
+ const { getModelDisplayName } = await import("../llm/registry.js");
1296
+ const modelDisplayName = getModelDisplayName(llmConfig.model, llmConfig.provider);
1297
+ const estimatedTokens = tokenEstimate.estimated;
1298
+ const compactionCount = await this.sessionManager.getCompactionCount(sessionId);
1299
+ return {
1300
+ estimatedTokens,
1301
+ actualTokens: tokenEstimate.actual,
1302
+ maxContextTokens,
1303
+ modelContextWindow,
1304
+ thresholdPercent,
1305
+ usagePercent: maxContextTokens > 0 ? Math.round(estimatedTokens / maxContextTokens * 100) : 0,
1306
+ messageCount: tokenEstimate.stats.originalMessageCount,
1307
+ filteredMessageCount: tokenEstimate.stats.filteredMessageCount,
1308
+ prunedToolCount: tokenEstimate.stats.prunedToolCount,
1309
+ hasSummary,
1310
+ compactionCount,
1311
+ model: llmConfig.model,
1312
+ modelDisplayName,
1313
+ breakdown: {
1314
+ systemPrompt: tokenEstimate.breakdown.systemPrompt,
1315
+ tools: tokenEstimate.breakdown.tools,
1316
+ messages: tokenEstimate.breakdown.messages
1317
+ },
1318
+ ...tokenEstimate.calculationBasis && {
1319
+ calculationBasis: tokenEstimate.calculationBasis
1320
+ }
1321
+ };
1322
+ }
1115
1323
  // ============= LLM MANAGEMENT =============
1116
1324
  /**
1117
1325
  * Gets the current LLM configuration with all defaults applied.
@@ -1187,6 +1395,83 @@ Either:
1187
1395
  }
1188
1396
  return validatedConfig;
1189
1397
  }
1398
+ /**
1399
+ * Handles session-native continuation after compaction.
1400
+ *
1401
+ * When context compaction occurs, this method:
1402
+ * 1. Receives compaction data directly (summary text + preserved messages)
1403
+ * 2. Creates a new continuation session
1404
+ * 3. Adds the summary as the first message in the new session
1405
+ * 4. Adds preserved messages to the new session
1406
+ * 5. Links the old and new sessions for traceability
1407
+ * 6. Emits session:continued event for UI to handle session switch
1408
+ *
1409
+ * IMPORTANT: The original session is NOT modified. The summary was never added to it.
1410
+ * This is the clean session-native compaction approach where:
1411
+ * - Original session: UNCHANGED (full history preserved)
1412
+ * - New session: [summary, ...preservedMessages]
1413
+ *
1414
+ * @param currentSession The session that triggered compaction
1415
+ * @param compactionData The compaction data with summary text and preserved messages
1416
+ */
1417
+ async handleSessionContinuation(currentSession, compactionData) {
1418
+ const currentSessionId = currentSession.id;
1419
+ try {
1420
+ const { summaryText, preservedMessages, summarizedCount } = compactionData;
1421
+ if (!summaryText) {
1422
+ this.logger.warn(
1423
+ `Session continuation skipped: empty summary text for session ${currentSessionId}`
1424
+ );
1425
+ return;
1426
+ }
1427
+ const { sessionId: newSessionId, session: newSession } = await this.sessionManager.createContinuationSession(currentSessionId);
1428
+ const sessionSummaryMessage = {
1429
+ role: "assistant",
1430
+ content: [{ type: "text", text: summaryText }],
1431
+ timestamp: Date.now(),
1432
+ metadata: {
1433
+ isSessionSummary: true,
1434
+ continuedFrom: currentSessionId,
1435
+ summarizedAt: Date.now(),
1436
+ originalMessageCount: summarizedCount,
1437
+ originalFirstTimestamp: compactionData.originalFirstTimestamp,
1438
+ originalLastTimestamp: compactionData.originalLastTimestamp
1439
+ }
1440
+ };
1441
+ const newContextManager = newSession.getContextManager();
1442
+ await newContextManager.addMessage(sessionSummaryMessage);
1443
+ for (const msg of preservedMessages) {
1444
+ await newContextManager.addMessage(msg);
1445
+ }
1446
+ await this.sessionManager.markSessionCompacted(currentSessionId, newSessionId);
1447
+ const { estimateMessagesTokens } = await import("../context/utils.js");
1448
+ const summaryTokens = estimateMessagesTokens([sessionSummaryMessage]);
1449
+ const totalOriginalMessages = summarizedCount + preservedMessages.length;
1450
+ const newSessionLLMConfig = this.stateManager.getRuntimeConfig(newSessionId).llm;
1451
+ const { getModelDisplayName } = await import("../llm/registry.js");
1452
+ const modelDisplayName = getModelDisplayName(
1453
+ newSessionLLMConfig.model,
1454
+ newSessionLLMConfig.provider
1455
+ );
1456
+ this.logger.info(
1457
+ `Session continuation: ${currentSessionId} \u2192 ${newSessionId} (${totalOriginalMessages} messages \u2192 summary + ${preservedMessages.length} preserved)`
1458
+ );
1459
+ this.agentEventBus.emit("session:continued", {
1460
+ previousSessionId: currentSessionId,
1461
+ newSessionId,
1462
+ summaryTokens,
1463
+ originalMessages: totalOriginalMessages,
1464
+ reason: "overflow",
1465
+ sessionId: newSessionId,
1466
+ model: newSessionLLMConfig.model,
1467
+ modelDisplayName
1468
+ });
1469
+ } catch (error) {
1470
+ this.logger.error(
1471
+ `Failed to handle session continuation for ${currentSessionId}: ${error instanceof Error ? error.message : String(error)}`
1472
+ );
1473
+ }
1474
+ }
1190
1475
  /**
1191
1476
  * Performs the actual LLM switch with a validated configuration.
1192
1477
  * This is a helper method that handles state management and session switching.