@framers/agentos 0.10.22 → 0.10.24

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 (204) hide show
  1. package/README.md +1 -1
  2. package/dist/api/AgentOS.d.ts.map +1 -1
  3. package/dist/api/AgentOS.js +3 -2
  4. package/dist/api/AgentOS.js.map +1 -1
  5. package/dist/api/agent.d.ts.map +1 -1
  6. package/dist/api/agent.js +25 -10
  7. package/dist/api/agent.js.map +1 -1
  8. package/dist/api/embedText.d.ts.map +1 -1
  9. package/dist/api/embedText.js +53 -17
  10. package/dist/api/embedText.js.map +1 -1
  11. package/dist/api/generateImage.d.ts.map +1 -1
  12. package/dist/api/generateImage.js +51 -7
  13. package/dist/api/generateImage.js.map +1 -1
  14. package/dist/api/generateText.d.ts +50 -7
  15. package/dist/api/generateText.d.ts.map +1 -1
  16. package/dist/api/generateText.js +265 -35
  17. package/dist/api/generateText.js.map +1 -1
  18. package/dist/api/model.d.ts +25 -1
  19. package/dist/api/model.d.ts.map +1 -1
  20. package/dist/api/model.js +108 -3
  21. package/dist/api/model.js.map +1 -1
  22. package/dist/api/runtime/SelfImprovementSessionManager.d.ts +34 -2
  23. package/dist/api/runtime/SelfImprovementSessionManager.d.ts.map +1 -1
  24. package/dist/api/runtime/SelfImprovementSessionManager.js +119 -33
  25. package/dist/api/runtime/SelfImprovementSessionManager.js.map +1 -1
  26. package/dist/api/runtime/global-default.d.ts +9 -1
  27. package/dist/api/runtime/global-default.d.ts.map +1 -1
  28. package/dist/api/runtime/global-default.js.map +1 -1
  29. package/dist/api/runtime/provider-defaults.d.ts.map +1 -1
  30. package/dist/api/runtime/provider-defaults.js +10 -4
  31. package/dist/api/runtime/provider-defaults.js.map +1 -1
  32. package/dist/api/runtime/tool-emulation/history.d.ts +16 -0
  33. package/dist/api/runtime/tool-emulation/history.d.ts.map +1 -0
  34. package/dist/api/runtime/tool-emulation/history.js +89 -0
  35. package/dist/api/runtime/tool-emulation/history.js.map +1 -0
  36. package/dist/api/runtime/tool-emulation/index.d.ts +1 -0
  37. package/dist/api/runtime/tool-emulation/index.d.ts.map +1 -1
  38. package/dist/api/runtime/tool-emulation/index.js +1 -0
  39. package/dist/api/runtime/tool-emulation/index.js.map +1 -1
  40. package/dist/api/runtime/tool-emulation/loop.d.ts +6 -0
  41. package/dist/api/runtime/tool-emulation/loop.d.ts.map +1 -1
  42. package/dist/api/runtime/tool-emulation/loop.js +1 -0
  43. package/dist/api/runtime/tool-emulation/loop.js.map +1 -1
  44. package/dist/api/sessionTranscript.d.ts +34 -0
  45. package/dist/api/sessionTranscript.d.ts.map +1 -1
  46. package/dist/api/sessionTranscript.js +61 -1
  47. package/dist/api/sessionTranscript.js.map +1 -1
  48. package/dist/api/streamText.d.ts.map +1 -1
  49. package/dist/api/streamText.js +120 -24
  50. package/dist/api/streamText.js.map +1 -1
  51. package/dist/api/types/AgentOSInput.d.ts +11 -0
  52. package/dist/api/types/AgentOSInput.d.ts.map +1 -1
  53. package/dist/api/types.d.ts +14 -6
  54. package/dist/api/types.d.ts.map +1 -1
  55. package/dist/api/types.js.map +1 -1
  56. package/dist/cognition/emergent/AdaptPersonalityTool.d.ts +13 -4
  57. package/dist/cognition/emergent/AdaptPersonalityTool.d.ts.map +1 -1
  58. package/dist/cognition/emergent/AdaptPersonalityTool.js +32 -23
  59. package/dist/cognition/emergent/AdaptPersonalityTool.js.map +1 -1
  60. package/dist/cognition/emergent/EmergentCapabilityEngine.d.ts +18 -6
  61. package/dist/cognition/emergent/EmergentCapabilityEngine.d.ts.map +1 -1
  62. package/dist/cognition/emergent/EmergentCapabilityEngine.js.map +1 -1
  63. package/dist/cognition/emergent/SelfEvaluateTool.d.ts +10 -3
  64. package/dist/cognition/emergent/SelfEvaluateTool.d.ts.map +1 -1
  65. package/dist/cognition/emergent/SelfEvaluateTool.js +3 -2
  66. package/dist/cognition/emergent/SelfEvaluateTool.js.map +1 -1
  67. package/dist/cognition/substrate/CognitiveMemoryBridge.d.ts +51 -2
  68. package/dist/cognition/substrate/CognitiveMemoryBridge.d.ts.map +1 -1
  69. package/dist/cognition/substrate/CognitiveMemoryBridge.js +84 -2
  70. package/dist/cognition/substrate/CognitiveMemoryBridge.js.map +1 -1
  71. package/dist/cognition/substrate/ConversationHistoryManager.d.ts +8 -4
  72. package/dist/cognition/substrate/ConversationHistoryManager.d.ts.map +1 -1
  73. package/dist/cognition/substrate/ConversationHistoryManager.js +17 -5
  74. package/dist/cognition/substrate/ConversationHistoryManager.js.map +1 -1
  75. package/dist/cognition/substrate/GMI.d.ts +92 -5
  76. package/dist/cognition/substrate/GMI.d.ts.map +1 -1
  77. package/dist/cognition/substrate/GMI.js +291 -65
  78. package/dist/cognition/substrate/GMI.js.map +1 -1
  79. package/dist/cognition/substrate/GMIManager.d.ts +12 -0
  80. package/dist/cognition/substrate/GMIManager.d.ts.map +1 -1
  81. package/dist/cognition/substrate/GMIManager.js +22 -16
  82. package/dist/cognition/substrate/GMIManager.js.map +1 -1
  83. package/dist/cognition/substrate/IGMI.d.ts +23 -0
  84. package/dist/cognition/substrate/IGMI.d.ts.map +1 -1
  85. package/dist/cognition/substrate/IGMI.js.map +1 -1
  86. package/dist/cognition/substrate/MetapromptExecutor.d.ts +183 -28
  87. package/dist/cognition/substrate/MetapromptExecutor.d.ts.map +1 -1
  88. package/dist/cognition/substrate/MetapromptExecutor.js +431 -108
  89. package/dist/cognition/substrate/MetapromptExecutor.js.map +1 -1
  90. package/dist/cognition/substrate/personas/IPersonaDefinition.d.ts +5 -0
  91. package/dist/cognition/substrate/personas/IPersonaDefinition.d.ts.map +1 -1
  92. package/dist/cognition/substrate/personas/PersonaValidation.d.ts.map +1 -1
  93. package/dist/cognition/substrate/personas/PersonaValidation.js +30 -0
  94. package/dist/cognition/substrate/personas/PersonaValidation.js.map +1 -1
  95. package/dist/cognition/substrate/personas/SoulLoader.d.ts +14 -1
  96. package/dist/cognition/substrate/personas/SoulLoader.d.ts.map +1 -1
  97. package/dist/cognition/substrate/personas/SoulLoader.js +45 -12
  98. package/dist/cognition/substrate/personas/SoulLoader.js.map +1 -1
  99. package/dist/cognition/substrate/personas/definitions/default_assistant_persona.json +3 -35
  100. package/dist/cognition/substrate/personas/hexaco.d.ts +34 -0
  101. package/dist/cognition/substrate/personas/hexaco.d.ts.map +1 -0
  102. package/dist/cognition/substrate/personas/hexaco.js +66 -0
  103. package/dist/cognition/substrate/personas/hexaco.js.map +1 -0
  104. package/dist/cognition/substrate/userFeedback.d.ts +69 -0
  105. package/dist/cognition/substrate/userFeedback.d.ts.map +1 -0
  106. package/dist/cognition/substrate/userFeedback.js +107 -0
  107. package/dist/cognition/substrate/userFeedback.js.map +1 -0
  108. package/dist/core/conversation/ConversationMessage.d.ts +9 -0
  109. package/dist/core/conversation/ConversationMessage.d.ts.map +1 -1
  110. package/dist/core/conversation/ConversationMessage.js.map +1 -1
  111. package/dist/core/llm/IPromptEngine.d.ts +9 -0
  112. package/dist/core/llm/IPromptEngine.d.ts.map +1 -1
  113. package/dist/core/llm/IPromptEngine.js.map +1 -1
  114. package/dist/core/llm/PromptEngine.d.ts +30 -1
  115. package/dist/core/llm/PromptEngine.d.ts.map +1 -1
  116. package/dist/core/llm/PromptEngine.js +229 -92
  117. package/dist/core/llm/PromptEngine.js.map +1 -1
  118. package/dist/core/llm/providers/AIModelProviderManager.d.ts +20 -0
  119. package/dist/core/llm/providers/AIModelProviderManager.d.ts.map +1 -1
  120. package/dist/core/llm/providers/AIModelProviderManager.js +35 -5
  121. package/dist/core/llm/providers/AIModelProviderManager.js.map +1 -1
  122. package/dist/core/llm/providers/IProvider.d.ts +28 -8
  123. package/dist/core/llm/providers/IProvider.d.ts.map +1 -1
  124. package/dist/core/llm/providers/implementations/AnthropicProvider.d.ts +135 -49
  125. package/dist/core/llm/providers/implementations/AnthropicProvider.d.ts.map +1 -1
  126. package/dist/core/llm/providers/implementations/AnthropicProvider.js +323 -114
  127. package/dist/core/llm/providers/implementations/AnthropicProvider.js.map +1 -1
  128. package/dist/core/llm/providers/implementations/ClaudeCodeProvider.d.ts.map +1 -1
  129. package/dist/core/llm/providers/implementations/ClaudeCodeProvider.js +15 -1
  130. package/dist/core/llm/providers/implementations/ClaudeCodeProvider.js.map +1 -1
  131. package/dist/core/llm/providers/implementations/GeminiCLIProvider.d.ts +1 -1
  132. package/dist/core/llm/providers/implementations/GeminiCLIProvider.d.ts.map +1 -1
  133. package/dist/core/llm/providers/implementations/GeminiCLIProvider.js +51 -24
  134. package/dist/core/llm/providers/implementations/GeminiCLIProvider.js.map +1 -1
  135. package/dist/core/llm/providers/implementations/GeminiProvider.d.ts +25 -6
  136. package/dist/core/llm/providers/implementations/GeminiProvider.d.ts.map +1 -1
  137. package/dist/core/llm/providers/implementations/GeminiProvider.js +108 -90
  138. package/dist/core/llm/providers/implementations/GeminiProvider.js.map +1 -1
  139. package/dist/core/llm/providers/implementations/MistralProvider.d.ts +11 -0
  140. package/dist/core/llm/providers/implementations/MistralProvider.d.ts.map +1 -1
  141. package/dist/core/llm/providers/implementations/MistralProvider.js +32 -2
  142. package/dist/core/llm/providers/implementations/MistralProvider.js.map +1 -1
  143. package/dist/core/llm/providers/implementations/OllamaProvider.js +7 -7
  144. package/dist/core/llm/providers/implementations/OllamaProvider.js.map +1 -1
  145. package/dist/core/llm/providers/implementations/OpenAIProvider.d.ts +53 -2
  146. package/dist/core/llm/providers/implementations/OpenAIProvider.d.ts.map +1 -1
  147. package/dist/core/llm/providers/implementations/OpenAIProvider.js +84 -17
  148. package/dist/core/llm/providers/implementations/OpenAIProvider.js.map +1 -1
  149. package/dist/core/llm/providers/implementations/OpenRouterProvider.d.ts.map +1 -1
  150. package/dist/core/llm/providers/implementations/OpenRouterProvider.js +8 -3
  151. package/dist/core/llm/providers/implementations/OpenRouterProvider.js.map +1 -1
  152. package/dist/core/llm/providers/implementations/TogetherProvider.d.ts +3 -3
  153. package/dist/core/llm/providers/implementations/TogetherProvider.d.ts.map +1 -1
  154. package/dist/core/llm/providers/implementations/TogetherProvider.js +25 -13
  155. package/dist/core/llm/providers/implementations/TogetherProvider.js.map +1 -1
  156. package/dist/core/llm/providers/implementations/cacheLeakDetector.d.ts +15 -7
  157. package/dist/core/llm/providers/implementations/cacheLeakDetector.d.ts.map +1 -1
  158. package/dist/core/llm/providers/implementations/cacheLeakDetector.js +12 -7
  159. package/dist/core/llm/providers/implementations/cacheLeakDetector.js.map +1 -1
  160. package/dist/core/llm/providers/model-cache-capabilities.d.ts +24 -9
  161. package/dist/core/llm/providers/model-cache-capabilities.d.ts.map +1 -1
  162. package/dist/core/llm/providers/model-cache-capabilities.js +59 -44
  163. package/dist/core/llm/providers/model-cache-capabilities.js.map +1 -1
  164. package/dist/core/llm/providers/model-effort.d.ts +14 -0
  165. package/dist/core/llm/providers/model-effort.d.ts.map +1 -1
  166. package/dist/core/llm/providers/model-effort.js +22 -0
  167. package/dist/core/llm/providers/model-effort.js.map +1 -1
  168. package/dist/core/llm/providers/model-forced-tool-choice.d.ts +13 -6
  169. package/dist/core/llm/providers/model-forced-tool-choice.d.ts.map +1 -1
  170. package/dist/core/llm/providers/model-forced-tool-choice.js +14 -7
  171. package/dist/core/llm/providers/model-forced-tool-choice.js.map +1 -1
  172. package/dist/core/llm/providers/model-thinking.d.ts +64 -13
  173. package/dist/core/llm/providers/model-thinking.d.ts.map +1 -1
  174. package/dist/core/llm/providers/model-thinking.js +52 -12
  175. package/dist/core/llm/providers/model-thinking.js.map +1 -1
  176. package/dist/core/llm/providers/openrouter-only-params.d.ts +26 -7
  177. package/dist/core/llm/providers/openrouter-only-params.d.ts.map +1 -1
  178. package/dist/core/llm/providers/openrouter-only-params.js +58 -9
  179. package/dist/core/llm/providers/openrouter-only-params.js.map +1 -1
  180. package/dist/core/llm/providers/url-secrets.d.ts +42 -0
  181. package/dist/core/llm/providers/url-secrets.d.ts.map +1 -0
  182. package/dist/core/llm/providers/url-secrets.js +64 -0
  183. package/dist/core/llm/providers/url-secrets.js.map +1 -0
  184. package/dist/core/llm/streaming/StreamingBatcher.d.ts +4 -3
  185. package/dist/core/llm/streaming/StreamingBatcher.d.ts.map +1 -1
  186. package/dist/core/llm/streaming/StreamingBatcher.js +8 -3
  187. package/dist/core/llm/streaming/StreamingBatcher.js.map +1 -1
  188. package/dist/core/safety/LLMProviderHealthRegistry.d.ts +4 -0
  189. package/dist/core/safety/LLMProviderHealthRegistry.d.ts.map +1 -1
  190. package/dist/core/safety/LLMProviderHealthRegistry.js +26 -0
  191. package/dist/core/safety/LLMProviderHealthRegistry.js.map +1 -1
  192. package/dist/core/utils/sha256.d.ts +19 -0
  193. package/dist/core/utils/sha256.d.ts.map +1 -0
  194. package/dist/core/utils/sha256.js +101 -0
  195. package/dist/core/utils/sha256.js.map +1 -0
  196. package/dist/index.d.ts +1 -1
  197. package/dist/index.d.ts.map +1 -1
  198. package/dist/index.js +1 -1
  199. package/dist/index.js.map +1 -1
  200. package/dist/orchestration/pipeline/query/types.js +3 -3
  201. package/dist/orchestration/pipeline/query/types.js.map +1 -1
  202. package/dist/orchestration/planning/ProviderAssignmentEngine.js +2 -2
  203. package/dist/orchestration/planning/ProviderAssignmentEngine.js.map +1 -1
  204. package/package.json +1 -1
@@ -17,7 +17,7 @@ import { attachGenAiAttributes, attachUsageAttributes, toTurnMetricUsage } from
17
17
  import { fireLlmUsageObserver } from './observers.js';
18
18
  import { hostPolicyToRouteParams, mergeRequiredCapabilities, } from './runtime/hostPolicy.js';
19
19
  import { adaptTools } from './runtime/toolAdapter.js';
20
- import { runEmulatedToolLoop } from './runtime/tool-emulation/index.js';
20
+ import { runEmulatedToolLoop, toShimMessages } from './runtime/tool-emulation/index.js';
21
21
  import { resolveDynamicToolCalls } from './runtime/dynamicToolCalling.js';
22
22
  import { recordAgentOSTurnMetrics, withAgentOSSpan } from '../safety/evaluation/observability/otel.js';
23
23
  import { createLogger } from '../core/logging/loggerFactory.js';
@@ -66,6 +66,7 @@ class LLMProviderCircuitOpenError extends Error {
66
66
  this.name = 'LLMProviderCircuitOpenError';
67
67
  }
68
68
  }
69
+ import { toProviderReplayMessage } from './sessionTranscript.js';
69
70
  async function recordAgentOSUsageLazy(input) {
70
71
  const { recordAgentOSUsage } = await import('./runtime/usageLedger.js');
71
72
  return recordAgentOSUsage(input);
@@ -168,6 +169,7 @@ export async function createPlan(provider, modelId, userMessages, toolNames, con
168
169
  // Inherited (or planning-specific) cache control: a cache:false root
169
170
  // call's planning sub-call must not auto-cache behind the caller's back.
170
171
  ...(config?.cache !== undefined ? { cache: config.cache } : {}),
172
+ ...(config?.thinking === false ? { thinking: false } : {}),
171
173
  });
172
174
  // Accumulate planning call usage
173
175
  if (response.usage) {
@@ -252,6 +254,124 @@ function formatPlanForPrompt(plan) {
252
254
  * @internal
253
255
  */
254
256
  const RETRYABLE_HTTP_STATUSES = new Set([401, 402, 403, 429, 500, 502, 503, 504]);
257
+ /**
258
+ * Whether the tool round a continuation answers was requested without
259
+ * thinking blocks, as it is when another provider's model ran it.
260
+ */
261
+ function toolTurnLacksThinking(messages) {
262
+ for (let i = messages.length - 1; i >= 0; i--) {
263
+ const message = messages[i];
264
+ if (message.role !== 'assistant' || !Array.isArray(message.tool_calls) || message.tool_calls.length === 0) {
265
+ continue;
266
+ }
267
+ return !Array.isArray(message.thinkingBlocks) || message.thinkingBlocks.length === 0;
268
+ }
269
+ return false;
270
+ }
271
+ /** Property key that marks an error thrown after the call's tools ran. */
272
+ const TOOLS_RAN = Symbol.for('agentos.generateText.toolsRan');
273
+ /**
274
+ * Marks `error` as thrown after the call's tools ran, so a fallback walker
275
+ * that called this call as a leg stops instead of restarting on another
276
+ * provider, which would run the tools again. A non-object is wrapped in an
277
+ * Error first.
278
+ *
279
+ * @returns The marked error.
280
+ */
281
+ function markToolsRan(error) {
282
+ const target = error !== null && typeof error === 'object' ? error : new Error(String(error));
283
+ try {
284
+ Object.defineProperty(target, TOOLS_RAN, { value: true, configurable: true });
285
+ }
286
+ catch {
287
+ // A frozen error cannot carry the mark.
288
+ }
289
+ return target;
290
+ }
291
+ /**
292
+ * Adds a provider's usage report (`ModelUsage`) to a call's running
293
+ * {@link TokenUsage}, as each completed step is added.
294
+ *
295
+ * @internal Shared with streamText.
296
+ */
297
+ export function addModelUsage(target, usage) {
298
+ if (!usage || typeof usage !== 'object')
299
+ return;
300
+ const u = usage;
301
+ const num = (value) => typeof value === 'number' && Number.isFinite(value) ? value : undefined;
302
+ target.promptTokens += num(u.promptTokens) ?? 0;
303
+ target.completionTokens += num(u.completionTokens) ?? 0;
304
+ target.totalTokens += num(u.totalTokens) ?? 0;
305
+ const cost = num(u.costUSD);
306
+ if (cost !== undefined)
307
+ target.costUSD = (target.costUSD ?? 0) + cost;
308
+ const cacheRead = num(u.cacheReadInputTokens);
309
+ if (cacheRead !== undefined)
310
+ target.cacheReadTokens = (target.cacheReadTokens ?? 0) + cacheRead;
311
+ const cacheWrite = num(u.cacheCreationInputTokens);
312
+ if (cacheWrite !== undefined)
313
+ target.cacheCreationTokens = (target.cacheCreationTokens ?? 0) + cacheWrite;
314
+ const inclusive = num(u.inclusiveInputTokens);
315
+ if (inclusive !== undefined)
316
+ target.inclusiveInputTokens = (target.inclusiveInputTokens ?? 0) + inclusive;
317
+ }
318
+ /**
319
+ * The usage a provider error reports for the request it ended, such as the
320
+ * billed tokens of a refused turn (`details.usage`).
321
+ *
322
+ * @internal Shared with streamText.
323
+ */
324
+ export function usageOfError(error) {
325
+ return error?.details?.usage;
326
+ }
327
+ /**
328
+ * Whether a usage report shows billable consumption.
329
+ *
330
+ * @internal Shared with streamText.
331
+ */
332
+ export function hasBillableUsage(usage) {
333
+ return (usage.promptTokens > 0 ||
334
+ usage.completionTokens > 0 ||
335
+ (usage.cacheReadTokens ?? 0) > 0 ||
336
+ (usage.cacheCreationTokens ?? 0) > 0);
337
+ }
338
+ /** The sum of two usage reports; an optional counter stays absent when neither has it. */
339
+ function sumTokenUsage(a, b) {
340
+ const optional = (x, y) => x === undefined && y === undefined ? undefined : (x ?? 0) + (y ?? 0);
341
+ const costUSD = optional(a.costUSD, b.costUSD);
342
+ const inclusiveInputTokens = optional(a.inclusiveInputTokens, b.inclusiveInputTokens);
343
+ const cacheReadTokens = optional(a.cacheReadTokens, b.cacheReadTokens);
344
+ const cacheCreationTokens = optional(a.cacheCreationTokens, b.cacheCreationTokens);
345
+ return {
346
+ promptTokens: a.promptTokens + b.promptTokens,
347
+ completionTokens: a.completionTokens + b.completionTokens,
348
+ totalTokens: a.totalTokens + b.totalTokens,
349
+ ...(costUSD !== undefined ? { costUSD } : {}),
350
+ ...(inclusiveInputTokens !== undefined ? { inclusiveInputTokens } : {}),
351
+ ...(cacheReadTokens !== undefined ? { cacheReadTokens } : {}),
352
+ ...(cacheCreationTokens !== undefined ? { cacheCreationTokens } : {}),
353
+ };
354
+ }
355
+ /** Whether `error` was marked by {@link markToolsRan}. */
356
+ function toolsRanBefore(error) {
357
+ return (error !== null &&
358
+ typeof error === 'object' &&
359
+ error[TOOLS_RAN] === true);
360
+ }
361
+ /**
362
+ * Provider error codes for request-level failures that another provider may
363
+ * not share: unreachable endpoints, request timeouts, and retries exhausted
364
+ * inside the provider. Mid-stream codes (STREAM_IDLE_TIMEOUT,
365
+ * STREAM_INCOMPLETE) are left out, because a stream that already delivered
366
+ * text must not be restarted on another provider.
367
+ */
368
+ const RETRYABLE_PROVIDER_ERROR_CODES = new Set([
369
+ 'NETWORK_ERROR',
370
+ 'REQUEST_TIMEOUT',
371
+ 'REQUEST_HARD_TIMEOUT',
372
+ 'TIMEOUT',
373
+ 'MAX_RETRIES_REACHED',
374
+ ]);
255
375
  /**
256
376
  * Detect content-policy refusals across providers so the fallback chain
257
377
  * can route them to a more permissive model (typically uncensored
@@ -325,19 +445,29 @@ export function isContentPolicyRefusal(error) {
325
445
  export function isRetryableError(error) {
326
446
  if (!(error instanceof Error))
327
447
  return false;
448
+ // A primary that cannot initialize (rejected key, unreachable endpoint)
449
+ // is unusable for this call whatever the cause; the next provider may not be.
450
+ if (error.name === 'ProviderInitializationError')
451
+ return true;
328
452
  // Typed provider errors carry the HTTP status as a numeric field. Prefer that
329
453
  // over message-grepping, since providers often substitute the body description
330
454
  // (e.g. "This request requires more credits...") for the status code.
331
455
  const status = error.httpStatus;
332
456
  if (typeof status === 'number' && RETRYABLE_HTTP_STATUSES.has(status))
333
457
  return true;
458
+ // Typed provider errors name request-level network failures and timeouts
459
+ // by code; their messages vary by provider (OpenAIProvider rewrites an
460
+ // exhausted network failure as "Network error: unable to reach ...").
461
+ const code = error.code;
462
+ if (typeof code === 'string' && RETRYABLE_PROVIDER_ERROR_CODES.has(code))
463
+ return true;
334
464
  const msg = error.message;
335
465
  // HTTP status codes that warrant a provider switch (string-grepped fallback
336
466
  // when the error type is not a typed provider error).
337
467
  if (/\b(402|429|500|502|503|504|401|403)\b/.test(msg))
338
468
  return true;
339
469
  // Network-level failures
340
- if (/fetch failed|ECONNREFUSED|ETIMEDOUT|ENOTFOUND/i.test(msg))
470
+ if (/fetch failed|ECONNREFUSED|ECONNRESET|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|socket hang up|network error/i.test(msg))
341
471
  return true;
342
472
  // Provider-specific phrases that always imply a retryable condition.
343
473
  // `credit balance` covers Anthropic's billing message ("Your credit
@@ -597,6 +727,19 @@ export async function generateText(opts) {
597
727
  let metricUsage;
598
728
  let metricProviderId;
599
729
  let metricModelId;
730
+ // What this attempt did that a failover must not repeat. Written inside
731
+ // the span callback and read by the fallback walk below, so it lives on an
732
+ // object rather than in narrowed locals.
733
+ // What this attempt consumed, planning and completed steps included. It
734
+ // lives outside the span callback so a failed attempt still reports the
735
+ // tokens it was billed for.
736
+ const attemptUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
737
+ const toolProgress = { shimRanTool: false };
738
+ // The tool run this call's tool execution contexts belong to, and the
739
+ // step it starts at. A fallback leg that continues the call keeps both,
740
+ // so tools see one session across providers and steps keep counting.
741
+ const helperToolRunId = opts._continuation?.helperToolRunId ?? randomUUID();
742
+ const stepOffset = opts._continuation?.stepOffset ?? 0;
600
743
  try {
601
744
  const successResult = await withAgentOSSpan('agentos.api.generate_text', async (span) => {
602
745
  let { providerId, modelId } = resolveModelOption(opts, 'text');
@@ -668,7 +811,6 @@ export async function generateText(opts) {
668
811
  const toolMap = new Map();
669
812
  for (const t of tools)
670
813
  toolMap.set(t.name, t);
671
- const helperToolRunId = randomUUID();
672
814
  // Build messages
673
815
  const messages = [];
674
816
  // --- Chain-of-thought injection ---
@@ -705,15 +847,27 @@ export async function generateText(opts) {
705
847
  }
706
848
  messages.push({ role: 'system', content: parts });
707
849
  }
850
+ // Everything above is built from opts (system prompt, chain-of-thought
851
+ // instruction); a failover continuation rebuilds it, so it is left out
852
+ // of the conversation a continuation carries.
853
+ const generatedSystemCount = messages.length;
708
854
  if (opts.messages) {
855
+ // Session history replays through here, so keep the tool pairing
856
+ // and thinking fields (see toProviderReplayMessage).
709
857
  for (const m of opts.messages)
710
- messages.push({ role: m.role, content: m.content });
858
+ messages.push(toProviderReplayMessage(m));
711
859
  }
712
860
  // Transcript delta capture (sessions, spec 2026-07-20 §1b): everything
713
861
  // from here on is THIS call's contribution. Callers that carry their
714
862
  // new user turn inside opts.messages mark how many trailing caller
715
863
  // messages belong to the delta.
716
- const transcriptDeltaStart = messages.length - (opts._transcriptIncludeTrailingCallerMessages ?? 0);
864
+ let transcriptDeltaStart = messages.length - (opts._transcriptIncludeTrailingCallerMessages ?? 0);
865
+ // Caller history (its system messages included) that precedes this
866
+ // call's contribution.
867
+ const callerHistoryCount = Math.max(0, transcriptDeltaStart - generatedSystemCount);
868
+ // The plan's system message, when planning adds one; a continuation
869
+ // leaves it out (the fallback plans again if planning is on).
870
+ let planMessage;
717
871
  if (opts.prompt)
718
872
  messages.push({ role: 'user', content: opts.prompt });
719
873
  span?.setAttribute('agentos.api.tool_count', tools.length);
@@ -724,7 +878,7 @@ export async function generateText(opts) {
724
878
  }))
725
879
  : undefined;
726
880
  const allToolCalls = [];
727
- const totalUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
881
+ const totalUsage = attemptUsage;
728
882
  // Provider-reported model id of the final step (spec batch-1 C1);
729
883
  // additive — the public `model` field keeps the resolved requested id.
730
884
  let lastResponseModelId;
@@ -755,6 +909,7 @@ export async function generateText(opts) {
755
909
  ...(planConfig?.cache !== undefined || opts.cache !== undefined
756
910
  ? { cache: planConfig?.cache ?? opts.cache }
757
911
  : {}),
912
+ ...(planConfig?.thinking === false || opts.thinking === false ? { thinking: false } : {}),
758
913
  }, totalUsage);
759
914
  if (resolvedPlan) {
760
915
  // Inject the plan as a system message right after any existing
@@ -762,7 +917,12 @@ export async function generateText(opts) {
762
917
  const planPrompt = formatPlanForPrompt(resolvedPlan);
763
918
  const firstNonSystem = messages.findIndex((m) => m.role !== 'system');
764
919
  const insertIdx = firstNonSystem === -1 ? messages.length : firstNonSystem;
765
- messages.splice(insertIdx, 0, { role: 'system', content: planPrompt });
920
+ planMessage = { role: 'system', content: planPrompt };
921
+ messages.splice(insertIdx, 0, planMessage);
922
+ // The plan lands ahead of this call's transcript delta; keep the
923
+ // delta starting at the same message.
924
+ if (insertIdx <= transcriptDeltaStart)
925
+ transcriptDeltaStart += 1;
766
926
  span?.setAttribute('agentos.api.plan_steps', resolvedPlan.steps.length);
767
927
  }
768
928
  }
@@ -776,10 +936,12 @@ export async function generateText(opts) {
776
936
  const runShim = async () => {
777
937
  const loopResult = await runEmulatedToolLoop({
778
938
  tools: Array.from(toolMap.values()),
779
- messages: messages.map((m) => ({
780
- role: String(m.role),
781
- content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content ?? ''),
782
- })),
939
+ onToolExecute: () => {
940
+ toolProgress.shimRanTool = true;
941
+ },
942
+ // Native tool turns (session history, a failover continuation)
943
+ // become the shim's own <tool_call> / <tool_response> text.
944
+ messages: toShimMessages(messages),
783
945
  maxRoundtrips: shimMaxRoundtrips,
784
946
  callModel: async (msgs) => {
785
947
  const r = await provider.generateCompletion(resolved.modelId, msgs, {
@@ -820,12 +982,13 @@ export async function generateText(opts) {
820
982
  ...(opts.requestTimeout !== undefined ? { requestTimeout: opts.requestTimeout } : {}),
821
983
  });
822
984
  // Aggregate the COMPLETE normalized usage from every shim
823
- // roundtrip (spec batch-1 review fold): the thin {totalTokens}
824
- // loop contract stays for the GMI helper (totalTokens flows via
825
- // loopResult below — NOT accumulated here, or it would double
826
- // count), while all richer counters land on totalUsage and the
827
- // final response identity is captured for telemetry.
985
+ // roundtrip (spec batch-1 review fold) as each call returns, so
986
+ // an attempt that fails on a later round still reports what its
987
+ // earlier rounds consumed. The loop's own {totalTokens} sum is
988
+ // left unused, or it would count twice.
828
989
  if (r.usage) {
990
+ if (typeof r.usage.totalTokens === 'number')
991
+ totalUsage.totalTokens += r.usage.totalTokens;
829
992
  if (typeof r.usage.promptTokens === 'number')
830
993
  totalUsage.promptTokens += r.usage.promptTokens;
831
994
  if (typeof r.usage.completionTokens === 'number')
@@ -853,10 +1016,7 @@ export async function generateText(opts) {
853
1016
  };
854
1017
  },
855
1018
  });
856
- const shimUsage = {
857
- ...totalUsage,
858
- totalTokens: (totalUsage.totalTokens ?? 0) + loopResult.totalTokens,
859
- };
1019
+ const shimUsage = { ...totalUsage };
860
1020
  metricUsage = shimUsage;
861
1021
  // Dual-emit the same root-span attribute pairs as the native
862
1022
  // terminals below — the shim early-return is a first-class chat
@@ -917,6 +1077,9 @@ export async function generateText(opts) {
917
1077
  let lastCacheDiagnostics;
918
1078
  try {
919
1079
  for (let step = 0; step < maxSteps; step++) {
1080
+ // The step's index in the whole call: a fallback leg that continues
1081
+ // after completed tool rounds numbers on from them.
1082
+ const runStep = stepOffset + step;
920
1083
  // --- onBeforeGeneration hook ---
921
1084
  let effectiveMessages = messages;
922
1085
  if (opts.onBeforeGeneration) {
@@ -927,8 +1090,8 @@ export async function generateText(opts) {
927
1090
  tools: Array.from(toolMap.values()),
928
1091
  model: resolved.modelId,
929
1092
  provider: resolved.providerId,
930
- step,
931
- prompt: opts.prompt,
1093
+ step: runStep,
1094
+ prompt: opts.prompt ?? opts._continuation?.prompt,
932
1095
  };
933
1096
  const modified = await opts.onBeforeGeneration(hookCtx);
934
1097
  if (modified) {
@@ -942,7 +1105,7 @@ export async function generateText(opts) {
942
1105
  const response = await withAgentOSSpan('agentos.api.generate_text.step', async (stepSpan) => {
943
1106
  stepSpan?.setAttribute('llm.provider', resolved.providerId);
944
1107
  stepSpan?.setAttribute('llm.model', resolved.modelId);
945
- stepSpan?.setAttribute('agentos.api.step', step + 1);
1108
+ stepSpan?.setAttribute('agentos.api.step', runStep + 1);
946
1109
  stepSpan?.setAttribute('agentos.api.tool_count', tools.length);
947
1110
  const stepResponse = await provider.generateCompletion(resolved.modelId, effectiveMessages, {
948
1111
  tools: toolSchemas,
@@ -1069,7 +1232,7 @@ export async function generateText(opts) {
1069
1232
  let textContent = typeof content === 'string' ? content : (content?.text ?? '');
1070
1233
  let toolCallsInChoice = resolveDynamicToolCalls(choice.message?.tool_calls, {
1071
1234
  text: textContent,
1072
- step,
1235
+ step: runStep,
1073
1236
  toolsAvailable: tools.length > 0,
1074
1237
  });
1075
1238
  // --- onAfterGeneration hook ---
@@ -1091,7 +1254,7 @@ export async function generateText(opts) {
1091
1254
  text: textContent,
1092
1255
  toolCalls: toolCallRecords,
1093
1256
  usage: stepUsage,
1094
- step,
1257
+ step: runStep,
1095
1258
  ...(response.cacheDiagnostics !== undefined
1096
1259
  ? { cacheDiagnostics: response.cacheDiagnostics }
1097
1260
  : {}),
@@ -1139,6 +1302,9 @@ export async function generateText(opts) {
1139
1302
  ...((choice.message?.thinking) !== undefined
1140
1303
  ? { thinking: choice.message.thinking }
1141
1304
  : {}),
1305
+ ...(choice.message?.thinkingBlocks?.length
1306
+ ? { thinkingBlocks: choice.message.thinkingBlocks }
1307
+ : {}),
1142
1308
  });
1143
1309
  return {
1144
1310
  transcriptDelta: messages.slice(transcriptDeltaStart),
@@ -1208,7 +1374,7 @@ export async function generateText(opts) {
1208
1374
  name: fnName,
1209
1375
  args: parsedArgs,
1210
1376
  id: tcId || '',
1211
- step,
1377
+ step: runStep,
1212
1378
  };
1213
1379
  const hookResult = await opts.onBeforeToolExecution(hookInfo);
1214
1380
  if (hookResult === null) {
@@ -1229,7 +1395,7 @@ export async function generateText(opts) {
1229
1395
  }
1230
1396
  if (tool) {
1231
1397
  try {
1232
- const result = await tool.execute(parsedArgs, buildHelperToolExecutionContext('generateText', helperToolRunId, step, tcId || undefined));
1398
+ const result = await tool.execute(parsedArgs, buildHelperToolExecutionContext('generateText', helperToolRunId, runStep, tcId || undefined));
1233
1399
  record.result = result.output;
1234
1400
  record.error = result.success ? undefined : result.error;
1235
1401
  messages.push({
@@ -1257,6 +1423,12 @@ export async function generateText(opts) {
1257
1423
  }
1258
1424
  allToolCalls.push(record);
1259
1425
  }
1426
+ toolProgress.completedToolRounds = {
1427
+ messages: messages.slice(generatedSystemCount).filter((m) => m !== planMessage),
1428
+ callerHistoryCount,
1429
+ toolCalls: [...allToolCalls],
1430
+ steps: step + 1,
1431
+ };
1260
1432
  continue;
1261
1433
  }
1262
1434
  metricUsage = totalUsage;
@@ -1285,6 +1457,9 @@ export async function generateText(opts) {
1285
1457
  ...((choice.message?.thinking) !== undefined
1286
1458
  ? { thinking: choice.message.thinking }
1287
1459
  : {}),
1460
+ ...(choice.message?.thinkingBlocks?.length
1461
+ ? { thinkingBlocks: choice.message.thinkingBlocks }
1462
+ : {}),
1288
1463
  });
1289
1464
  return {
1290
1465
  transcriptDelta: messages.slice(transcriptDeltaStart),
@@ -1372,8 +1547,25 @@ export async function generateText(opts) {
1372
1547
  if (metricProviderId && !(error instanceof LLMProviderCircuitOpenError)) {
1373
1548
  globalLLMProviderHealth.recordFailure(metricProviderId, error);
1374
1549
  }
1375
- // Capture the failed primary for the fallback trail before the loop below
1376
- // reassigns metricProviderId / metricModelId to the winning fallback.
1550
+ // The failed attempt is billed for its completed steps and for a step
1551
+ // the provider ended with usage attached (a refused turn). It is metered
1552
+ // here, once: this call's ledger row and a usage event of its own; a
1553
+ // fallback leg meters itself.
1554
+ addModelUsage(attemptUsage, usageOfError(error));
1555
+ metricUsage = attemptUsage;
1556
+ if (hasBillableUsage(attemptUsage)) {
1557
+ fireLlmUsageObserver({
1558
+ provider: metricProviderId ?? 'unknown',
1559
+ model: metricModelId ?? 'unknown',
1560
+ usage: { ...attemptUsage },
1561
+ source: opts.source,
1562
+ ...(opts.__fallbackDepth ? { fallbackDepth: opts.__fallbackDepth } : {}),
1563
+ finishReason: 'error',
1564
+ surface: 'generateText',
1565
+ durationMs: Date.now() - rootStartedAt,
1566
+ });
1567
+ }
1568
+ // The failed primary heads the fallback trail.
1377
1569
  const primaryProviderId = metricProviderId;
1378
1570
  const primaryModelId = metricModelId;
1379
1571
  const fallbackHops = [
@@ -1390,8 +1582,11 @@ export async function generateText(opts) {
1390
1582
  const effectiveFallbacks = opts.fallbackProviders === undefined
1391
1583
  ? buildPolicyAwareFallbackChain(opts.policyTier, metricProviderId)
1392
1584
  : opts.fallbackProviders;
1585
+ // A call whose prompt-shim tools already ran cannot be continued, and a
1586
+ // restart would run them again; it surfaces the error instead.
1393
1587
  if (effectiveFallbacks.length &&
1394
- isRetryableError(error)) {
1588
+ isRetryableError(error) &&
1589
+ !toolProgress.shimRanTool) {
1395
1590
  let lastError = error;
1396
1591
  let attempt = 0;
1397
1592
  for (const fb of effectiveFallbacks) {
@@ -1482,6 +1677,34 @@ export async function generateText(opts) {
1482
1677
  // end-to-end. The final entry passes [] -> explicit opt-out -> throw.
1483
1678
  fallbackProviders: effectiveFallbacks.slice(attempt),
1484
1679
  onFallback: undefined,
1680
+ // Continue after the tool rounds this attempt completed: the leg
1681
+ // receives the conversation so far (prompt included, so no new
1682
+ // prompt), counts all of it as this call's transcript delta, and
1683
+ // keeps the steps that remain.
1684
+ _continuation: {
1685
+ helperToolRunId,
1686
+ stepOffset: stepOffset + (toolProgress.completedToolRounds?.steps ?? 0),
1687
+ prompt: opts.prompt ?? opts._continuation?.prompt,
1688
+ },
1689
+ ...(toolProgress.completedToolRounds
1690
+ ? {
1691
+ messages: toolProgress.completedToolRounds.messages,
1692
+ prompt: undefined,
1693
+ _transcriptIncludeTrailingCallerMessages: toolProgress.completedToolRounds.messages.length - toolProgress.completedToolRounds.callerHistoryCount,
1694
+ maxSteps: Math.max(1, (opts.maxSteps ?? 1) - toolProgress.completedToolRounds.steps),
1695
+ // Claude's budgeted thinking requires the tool turn being
1696
+ // answered to open with its signed thinking, and a turn
1697
+ // another model ran has none, so the continuation runs
1698
+ // without a thinking budget. Adaptive thinking turns itself
1699
+ // off for such a turn. `thinking: false` stays as asked.
1700
+ ...(typeof opts.thinking === 'object' &&
1701
+ opts.thinking !== null &&
1702
+ (fb.provider === 'anthropic' || /claude/i.test(fb.model ?? '')) &&
1703
+ toolTurnLacksThinking(toolProgress.completedToolRounds.messages)
1704
+ ? { thinking: undefined }
1705
+ : {}),
1706
+ }
1707
+ : {}),
1485
1708
  });
1486
1709
  fallbackLogger.info('provider fallback succeeded', {
1487
1710
  event: 'fallback_succeeded',
@@ -1492,14 +1715,16 @@ export async function generateText(opts) {
1492
1715
  attempt,
1493
1716
  });
1494
1717
  metricStatus = 'ok';
1495
- metricUsage = fallbackResult.usage;
1496
- metricProviderId = fallbackResult.provider;
1497
- metricModelId = fallbackResult.model;
1498
1718
  fallbackHops.push(...(fallbackResult.fallback?.hops ?? [
1499
1719
  { provider: fallbackResult.provider, model: fallbackResult.model, ok: true },
1500
1720
  ]));
1501
1721
  return {
1502
1722
  ...fallbackResult,
1723
+ // The call's usage covers the failed attempt and the leg.
1724
+ usage: sumTokenUsage(attemptUsage, fallbackResult.usage),
1725
+ ...(toolProgress.completedToolRounds
1726
+ ? { toolCalls: [...toolProgress.completedToolRounds.toolCalls, ...fallbackResult.toolCalls] }
1727
+ : {}),
1503
1728
  fallback: {
1504
1729
  fired: true,
1505
1730
  finalProvider: fallbackResult.provider,
@@ -1511,6 +1736,10 @@ export async function generateText(opts) {
1511
1736
  catch (fbError) {
1512
1737
  lastError = fbError;
1513
1738
  fallbackHops.push({ provider: fb.provider, model: fb.model, ok: false });
1739
+ // The leg ran tools before it failed. A later leg would start
1740
+ // from a conversation that does not show them and run them again.
1741
+ if (toolsRanBefore(fbError))
1742
+ break;
1514
1743
  }
1515
1744
  }
1516
1745
  // All fallbacks exhausted: fall through to throw
@@ -1524,10 +1753,11 @@ export async function generateText(opts) {
1524
1753
  errorMessage: lastErr.message.slice(0, 200),
1525
1754
  });
1526
1755
  metricStatus = 'error';
1527
- throw lastError;
1756
+ throw toolProgress.shimRanTool || toolProgress.completedToolRounds ? markToolsRan(lastError) : lastError;
1528
1757
  }
1529
1758
  metricStatus = 'error';
1530
- throw error;
1759
+ // Marked so a walker that called this one as a fallback leg stops too.
1760
+ throw toolProgress.shimRanTool || toolProgress.completedToolRounds ? markToolsRan(error) : error;
1531
1761
  }
1532
1762
  finally {
1533
1763
  try {