@cogitator-ai/core 0.23.0 → 0.24.0

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 (120) hide show
  1. package/README.md +79 -0
  2. package/dist/agent-tool.d.ts +7 -1
  3. package/dist/agent-tool.d.ts.map +1 -1
  4. package/dist/agent-tool.js +4 -1
  5. package/dist/agent-tool.js.map +1 -1
  6. package/dist/agent.d.ts +6 -25
  7. package/dist/agent.d.ts.map +1 -1
  8. package/dist/agent.js +18 -3
  9. package/dist/agent.js.map +1 -1
  10. package/dist/cogitator/handoffs.d.ts +14 -0
  11. package/dist/cogitator/handoffs.d.ts.map +1 -0
  12. package/dist/cogitator/handoffs.js +53 -0
  13. package/dist/cogitator/handoffs.js.map +1 -0
  14. package/dist/cogitator/prompts.d.ts +69 -0
  15. package/dist/cogitator/prompts.d.ts.map +1 -0
  16. package/dist/cogitator/prompts.js +149 -0
  17. package/dist/cogitator/prompts.js.map +1 -0
  18. package/dist/cogitator/run-checkpoints.d.ts +20 -0
  19. package/dist/cogitator/run-checkpoints.d.ts.map +1 -0
  20. package/dist/cogitator/run-checkpoints.js +67 -0
  21. package/dist/cogitator/run-checkpoints.js.map +1 -0
  22. package/dist/cogitator/streaming.d.ts +9 -7
  23. package/dist/cogitator/streaming.d.ts.map +1 -1
  24. package/dist/cogitator/streaming.js +12 -1
  25. package/dist/cogitator/streaming.js.map +1 -1
  26. package/dist/cogitator/tool-executor.d.ts +1 -1
  27. package/dist/cogitator/tool-executor.d.ts.map +1 -1
  28. package/dist/cogitator/tool-executor.js +2 -2
  29. package/dist/cogitator/tool-executor.js.map +1 -1
  30. package/dist/constitutional/constitutional-ai.d.ts +7 -1
  31. package/dist/constitutional/constitutional-ai.d.ts.map +1 -1
  32. package/dist/constitutional/constitutional-ai.js +6 -2
  33. package/dist/constitutional/constitutional-ai.js.map +1 -1
  34. package/dist/constitutional/tool-guard.d.ts +3 -1
  35. package/dist/constitutional/tool-guard.d.ts.map +1 -1
  36. package/dist/constitutional/tool-guard.js +2 -2
  37. package/dist/constitutional/tool-guard.js.map +1 -1
  38. package/dist/index.d.ts +5 -0
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +4 -0
  41. package/dist/index.js.map +1 -1
  42. package/dist/learning/postgres-trace-store.d.ts +5 -1
  43. package/dist/learning/postgres-trace-store.d.ts.map +1 -1
  44. package/dist/learning/postgres-trace-store.js +23 -0
  45. package/dist/learning/postgres-trace-store.js.map +1 -1
  46. package/dist/learning/prompt-stores.d.ts +26 -0
  47. package/dist/learning/prompt-stores.d.ts.map +1 -0
  48. package/dist/learning/prompt-stores.js +97 -0
  49. package/dist/learning/prompt-stores.js.map +1 -0
  50. package/dist/llm/anthropic-thinking.d.ts +29 -0
  51. package/dist/llm/anthropic-thinking.d.ts.map +1 -0
  52. package/dist/llm/anthropic-thinking.js +100 -0
  53. package/dist/llm/anthropic-thinking.js.map +1 -0
  54. package/dist/llm/anthropic.d.ts +12 -0
  55. package/dist/llm/anthropic.d.ts.map +1 -1
  56. package/dist/llm/anthropic.js +138 -85
  57. package/dist/llm/anthropic.js.map +1 -1
  58. package/dist/llm/bedrock.d.ts.map +1 -1
  59. package/dist/llm/bedrock.js +136 -39
  60. package/dist/llm/bedrock.js.map +1 -1
  61. package/dist/llm/claude-models.d.ts +23 -0
  62. package/dist/llm/claude-models.d.ts.map +1 -1
  63. package/dist/llm/claude-models.js +50 -0
  64. package/dist/llm/claude-models.js.map +1 -1
  65. package/dist/llm/google.d.ts +12 -1
  66. package/dist/llm/google.d.ts.map +1 -1
  67. package/dist/llm/google.js +84 -12
  68. package/dist/llm/google.js.map +1 -1
  69. package/dist/llm/ollama.d.ts +6 -1
  70. package/dist/llm/ollama.d.ts.map +1 -1
  71. package/dist/llm/ollama.js +26 -0
  72. package/dist/llm/ollama.js.map +1 -1
  73. package/dist/llm/openai-compatible-base.d.ts.map +1 -1
  74. package/dist/llm/openai-compatible-base.js +29 -21
  75. package/dist/llm/openai-compatible-base.js.map +1 -1
  76. package/dist/llm/openai-responses.d.ts.map +1 -1
  77. package/dist/llm/openai-responses.js +20 -3
  78. package/dist/llm/openai-responses.js.map +1 -1
  79. package/dist/logger.d.ts.map +1 -1
  80. package/dist/logger.js +3 -5
  81. package/dist/logger.js.map +1 -1
  82. package/dist/runtime.d.ts +48 -1
  83. package/dist/runtime.d.ts.map +1 -1
  84. package/dist/runtime.js +556 -172
  85. package/dist/runtime.js.map +1 -1
  86. package/dist/security/pii.d.ts +44 -0
  87. package/dist/security/pii.d.ts.map +1 -0
  88. package/dist/security/pii.js +285 -0
  89. package/dist/security/pii.js.map +1 -0
  90. package/dist/skill.d.ts.map +1 -1
  91. package/dist/skill.js +16 -10
  92. package/dist/skill.js.map +1 -1
  93. package/dist/tools/audio-generate.d.ts +1 -1
  94. package/dist/tools/audio-generate.d.ts.map +1 -1
  95. package/dist/tools/audio-generate.js +2 -1
  96. package/dist/tools/audio-generate.js.map +1 -1
  97. package/dist/tools/audio-transcribe.d.ts.map +1 -1
  98. package/dist/tools/audio-transcribe.js +2 -1
  99. package/dist/tools/audio-transcribe.js.map +1 -1
  100. package/dist/tools/email.d.ts.map +1 -1
  101. package/dist/tools/email.js +10 -9
  102. package/dist/tools/email.js.map +1 -1
  103. package/dist/tools/github.d.ts.map +1 -1
  104. package/dist/tools/github.js +2 -1
  105. package/dist/tools/github.js.map +1 -1
  106. package/dist/tools/image-generate.d.ts +2 -2
  107. package/dist/tools/image-generate.d.ts.map +1 -1
  108. package/dist/tools/image-generate.js +2 -1
  109. package/dist/tools/image-generate.js.map +1 -1
  110. package/dist/tools/vector-search.d.ts.map +1 -1
  111. package/dist/tools/vector-search.js +8 -7
  112. package/dist/tools/vector-search.js.map +1 -1
  113. package/dist/tools/web-search.d.ts.map +1 -1
  114. package/dist/tools/web-search.js +7 -6
  115. package/dist/tools/web-search.js.map +1 -1
  116. package/dist/utils/env.d.ts +9 -0
  117. package/dist/utils/env.d.ts.map +1 -0
  118. package/dist/utils/env.js +26 -0
  119. package/dist/utils/env.js.map +1 -0
  120. package/package.json +6 -6
package/dist/runtime.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { nanoid } from 'nanoid';
2
- import { getPrice } from '@cogitator-ai/models';
2
+ import { calculateCost as calculateModelCost } from '@cogitator-ai/models';
3
3
  import { ToolRegistry } from './registry.js';
4
4
  import { createLLMBackend, parseModel } from './llm/index.js';
5
5
  import { isLLMProvider } from './llm/providers.js';
6
6
  import { createLLMBackendFromPlugin, hasLLMPlugin } from './llm/plugin.js';
7
7
  import { withLLMRetry } from './llm/retry.js';
8
+ import { PiiMasker, withPiiMasking } from './security/pii.js';
8
9
  import { getLogger } from './logger.js';
9
10
  import { initializeMemory, initializeSandbox, initializeReflection, initializeGuardrails, initializeCostRouting, initializeSecurity, initializeContextManager, cleanupState, } from './cogitator/initializers.js';
10
11
  import { CogitatorError, ErrorCode } from '@cogitator-ai/types';
@@ -13,8 +14,12 @@ import { createSpan, getTextContent } from './cogitator/span-factory.js';
13
14
  import { executeTool, createToolMessage } from './cogitator/tool-executor.js';
14
15
  import { streamChat } from './cogitator/streaming.js';
15
16
  import { RunLimiter } from './cogitator/run-limiter.js';
17
+ import { findHandoffAgent, handoffTools } from './cogitator/handoffs.js';
18
+ import { PromptRegistry } from './cogitator/prompts.js';
19
+ import { InMemoryRunCheckpointStore, ThreadRunCheckpointStore } from './cogitator/run-checkpoints.js';
16
20
  import { parseStructuredOutput, toLLMResponseFormat } from './cogitator/response-format.js';
17
21
  import { CostEstimator } from './cost-routing/cost-estimator.js';
22
+ import { readEnv } from './utils/env.js';
18
23
  /** Run timeout when neither the run, the agent nor `limits.defaultTimeout` sets one. */
19
24
  const DEFAULT_RUN_TIMEOUT = 120_000;
20
25
  /**
@@ -68,6 +73,9 @@ const DEFAULT_RUN_TIMEOUT = 120_000;
68
73
  export class Cogitator {
69
74
  config;
70
75
  backends = new Map();
76
+ processCheckpoints;
77
+ promptRegistry;
78
+ threadCheckpoints;
71
79
  /** Global tool registry shared across all runs */
72
80
  tools = new ToolRegistry();
73
81
  state = {
@@ -131,7 +139,45 @@ export class Cogitator {
131
139
  * ```
132
140
  */
133
141
  async run(agent, options) {
134
- const runId = `run_${nanoid(12)}`;
142
+ return this.execute(agent, options);
143
+ }
144
+ /**
145
+ * Continue a run that paused for tool approvals (`status: 'paused'`) from its
146
+ * `checkpoint`. Approved calls run, declined ones answer the model with the
147
+ * reason, and the run goes on; calls without a decision pause it again.
148
+ *
149
+ * @example
150
+ * ```ts
151
+ * const paused = await cog.run(agent, { input: 'Refund order 42' });
152
+ * if (paused.status === 'paused') {
153
+ * await store.save(paused.checkpoint);
154
+ * // ... later, once someone approved it
155
+ * const result = await cog.resume(agent, checkpoint, {
156
+ * decisions: { [paused.pendingApprovals[0].toolCallId]: { approved: true } },
157
+ * });
158
+ * }
159
+ * ```
160
+ */
161
+ async resume(agent, target, options = {}) {
162
+ if (typeof target !== 'string' && target.version !== 1) {
163
+ throw new CogitatorError({
164
+ message: `Unsupported run checkpoint version: ${String(target.version)}`,
165
+ code: ErrorCode.VALIDATION_ERROR,
166
+ });
167
+ }
168
+ const { decisions = {}, defaultDecision, userId, ...rest } = options;
169
+ const threadId = typeof target === 'string' ? target : target.threadId;
170
+ return this.execute(agent, { ...rest, input: '', threadId, ...(userId !== undefined && { userId }) }, {
171
+ ...(typeof target === 'string' ? {} : { checkpoint: target }),
172
+ decisions,
173
+ ...(defaultDecision && { defaultDecision }),
174
+ checkUser: typeof target === 'string' || userId !== undefined,
175
+ });
176
+ }
177
+ async execute(agent, options, resumeFrom) {
178
+ let checkpoint = resumeFrom?.checkpoint;
179
+ let prompt;
180
+ let runId = checkpoint?.runId ?? `run_${nanoid(12)}`;
135
181
  const threadId = options.threadId ?? `thread_${nanoid(12)}`;
136
182
  const traceId = `trace_${nanoid(16)}`;
137
183
  const startTime = Date.now();
@@ -167,69 +213,109 @@ export class Cogitator {
167
213
  try {
168
214
  releaseRunSlot = await this.acquireRunSlot(abortController.signal);
169
215
  throwIfAborted(abortController.signal);
170
- options.onRunStart?.({ runId, agentId: agent.id, input: options.input, threadId });
216
+ if (!checkpoint) {
217
+ options.onRunStart?.({ runId, agentId: agent.id, input: options.input, threadId });
218
+ }
171
219
  const agentModel = this.resolveModel(agent);
172
220
  await this.initializeAll(agentModel);
173
- const input = await buildInputWithAudio(options.input, options.audio, {
174
- apiKey: this.config.llm?.providers?.openai?.apiKey ?? process.env.OPENAI_API_KEY,
175
- signal: abortController.signal,
176
- });
177
- const runOptions = input === options.input ? options : { ...options, input };
178
- const registry = new ToolRegistry();
179
- registry.registerMany(this.tools.getAll());
180
- if (agent.tools && agent.tools.length > 0) {
181
- registry.registerMany(agent.tools);
182
- }
183
- let effectiveModel = agentModel;
184
- let backend;
185
- let model;
186
- if (this.state.costRouter && this.config.costRouting?.autoSelectModel) {
187
- const recommendation = await this.state.costRouter.recommendModel(input);
188
- effectiveModel = `${recommendation.provider}/${recommendation.modelId}`;
189
- const budgetCheck = this.state.costRouter.checkBudget(recommendation.estimatedCost);
190
- if (!budgetCheck.allowed) {
191
- throw new Error(`Budget exceeded: ${budgetCheck.reason}`);
221
+ if (resumeFrom) {
222
+ checkpoint ??= (await this.runCheckpointStore().load(threadId)) ?? undefined;
223
+ if (!checkpoint) {
224
+ throw new CogitatorError({
225
+ message: `Thread ${threadId} has no paused run`,
226
+ code: ErrorCode.RUN_NOT_PAUSED,
227
+ });
192
228
  }
193
- backend = this.getBackend(effectiveModel, recommendation.provider);
194
- model = recommendation.modelId;
195
- }
196
- else {
197
- ({ backend, model } = this.route(effectiveModel, agent.config.provider));
198
- }
199
- const messages = await buildInitialMessages(agent, runOptions, threadId, this.state.memoryAdapter, this.state.contextBuilder);
200
- if (this.state.injectionDetector) {
201
- const injectionResult = await this.state.injectionDetector.analyze(input);
202
- if (injectionResult.action === 'blocked') {
203
- const threatTypes = injectionResult.threats.map((t) => t.type).join(', ');
229
+ if (resumeFrom.checkUser && checkpoint.userId !== options.userId) {
204
230
  throw new CogitatorError({
205
- message: `Prompt injection detected: ${threatTypes}`,
206
- code: ErrorCode.PROMPT_INJECTION_DETECTED,
207
- details: { threats: injectionResult.threats },
231
+ message: `The paused run in thread ${threadId} belongs to another user`,
232
+ code: ErrorCode.THREAD_ACCESS_DENIED,
208
233
  });
209
234
  }
235
+ runId = checkpoint.runId;
236
+ options = { ...options, userId: checkpoint.userId };
210
237
  }
211
- if (this.state.constitutionalAI?.config.filterInput) {
212
- const inputResult = await this.state.constitutionalAI.filterInput(input);
213
- if (!inputResult.allowed) {
214
- throw new Error(`Input blocked: ${inputResult.blockedReason ?? 'Policy violation'}`);
238
+ prompt = checkpoint?.prompt;
239
+ if (!checkpoint && (this.config.prompts || this.promptRegistry)) {
240
+ const resolution = await this.prompts.resolve(agent, threadId);
241
+ prompt = resolution.prompt;
242
+ if (resolution.instructions !== agent.instructions) {
243
+ agent = agent.clone({ id: agent.id, instructions: resolution.instructions });
215
244
  }
216
245
  }
217
- if (options.context) {
218
- addContextToMessages(messages, options.context);
246
+ let active = agent;
247
+ if (checkpoint?.activeAgent && checkpoint.activeAgent !== agent.name) {
248
+ const found = findHandoffAgent(agent, checkpoint.activeAgent);
249
+ if (!found) {
250
+ throw new CogitatorError({
251
+ message: `The paused run is in agent "${checkpoint.activeAgent}", which ${agent.name} cannot hand over to`,
252
+ code: ErrorCode.VALIDATION_ERROR,
253
+ });
254
+ }
255
+ active = found;
219
256
  }
220
- if (this.state.memoryAdapter &&
221
- options.saveHistory !== false &&
222
- options.useMemory !== false) {
223
- const currentUserMessage = messages[messages.length - 1];
224
- await saveEntry(threadId, agent.id, currentUserMessage, this.state.memoryAdapter, undefined, undefined, options.onMemoryError, options.userId);
257
+ const handoffs = [...(checkpoint?.handoffs ?? [])];
258
+ const buildRegistry = (owner) => {
259
+ const ownerRegistry = new ToolRegistry();
260
+ ownerRegistry.registerMany(this.tools.getAll());
261
+ if (owner.tools.length > 0)
262
+ ownerRegistry.registerMany(owner.tools);
263
+ const handoff = handoffTools(owner);
264
+ ownerRegistry.registerMany(handoff.tools);
265
+ return { registry: ownerRegistry, targets: handoff.targets };
266
+ };
267
+ let { registry, targets: handoffTargets } = buildRegistry(active);
268
+ let effectiveModel = agentModel;
269
+ let routeProvider = agent.config.provider;
270
+ let backend;
271
+ let model;
272
+ let input;
273
+ let messages;
274
+ if (checkpoint) {
275
+ input = checkpoint.input;
276
+ effectiveModel = checkpoint.model;
277
+ routeProvider = checkpoint.provider;
278
+ ({ backend, model } = this.route(effectiveModel, routeProvider));
279
+ messages = [...checkpoint.messages];
280
+ }
281
+ else {
282
+ input = await buildInputWithAudio(options.input, options.audio, {
283
+ apiKey: this.config.llm?.providers?.openai?.apiKey ??
284
+ (options.audio?.length ? readEnv('OPENAI_API_KEY') : undefined),
285
+ signal: abortController.signal,
286
+ });
287
+ const runOptions = input === options.input ? options : { ...options, input };
288
+ if (this.state.costRouter && this.config.costRouting?.autoSelectModel) {
289
+ const recommendation = await this.state.costRouter.recommendModel(input);
290
+ effectiveModel = `${recommendation.provider}/${recommendation.modelId}`;
291
+ routeProvider = recommendation.provider;
292
+ const budgetCheck = this.state.costRouter.checkBudget(recommendation.estimatedCost);
293
+ if (!budgetCheck.allowed) {
294
+ throw new Error(`Budget exceeded: ${budgetCheck.reason}`);
295
+ }
296
+ backend = this.getBackend(effectiveModel, recommendation.provider);
297
+ model = recommendation.modelId;
298
+ }
299
+ else {
300
+ ({ backend, model } = this.route(effectiveModel, agent.config.provider));
301
+ }
302
+ await this.abandonPausedRun(agent, runOptions, threadId);
303
+ messages = await this.prepareMessages(agent, runOptions, input, threadId);
225
304
  }
226
- const allToolCalls = [];
227
- let totalInputTokens = 0;
228
- let totalOutputTokens = 0;
229
- let iterations = 0;
305
+ const allToolCalls = [...(checkpoint?.toolCalls ?? [])];
306
+ let totalInputTokens = checkpoint?.usage.inputTokens ?? 0;
307
+ let totalOutputTokens = checkpoint?.usage.outputTokens ?? 0;
308
+ let cachedInputTokens = checkpoint?.usage.cachedInputTokens ?? 0;
309
+ let cacheWriteTokens = checkpoint?.usage.cacheWriteTokens ?? 0;
310
+ let reasoningTokens = checkpoint?.usage.reasoningTokens ?? 0;
311
+ const reasoningParts = [...(checkpoint?.reasoning ?? [])];
312
+ let reasoning = options.reasoning ?? active.config.reasoning;
313
+ const promptCache = this.config.llm?.promptCache ?? {};
314
+ let iterations = checkpoint?.iterations ?? 0;
230
315
  const maxIterations = agent.config?.maxIterations ?? 10;
231
- let lastToolCallSig = '';
232
- const responseFormat = toLLMResponseFormat(agent.config.responseFormat);
316
+ let lastToolCallSig = checkpoint?.lastToolCallSignature ?? '';
317
+ let pausedTurn;
318
+ let responseFormat = toLLMResponseFormat(active.config.responseFormat);
233
319
  const allReflections = [];
234
320
  const allActions = [];
235
321
  const agentContext = {
@@ -242,10 +328,189 @@ export class Cogitator {
242
328
  previousActions: [],
243
329
  availableTools: registry.getNames(),
244
330
  };
245
- if (this.state.reflectionEngine && this.config.reflection?.enabled) {
331
+ if (!checkpoint && this.state.reflectionEngine && this.config.reflection?.enabled) {
246
332
  await enrichMessagesWithInsights(messages, this.state.reflectionEngine, agentContext);
247
333
  }
248
- while (iterations < maxIterations) {
334
+ const switchTo = (target, reason) => {
335
+ const event = {
336
+ from: active.name,
337
+ to: target.name,
338
+ ...(typeof reason === 'string' && reason && { reason }),
339
+ };
340
+ handoffs.push(event);
341
+ options.onHandoff?.(event);
342
+ const system = messages[0];
343
+ const content = system?.role === 'system' &&
344
+ typeof system.content === 'string' &&
345
+ system.content.startsWith(active.instructions)
346
+ ? target.instructions + system.content.slice(active.instructions.length)
347
+ : target.instructions;
348
+ if (system?.role === 'system')
349
+ messages[0] = { role: 'system', content };
350
+ else
351
+ messages.unshift({ role: 'system', content });
352
+ spans.push(createSpan('agent.handoff', traceId, rootSpanId, Date.now(), Date.now(), { 'handoff.from': event.from, 'handoff.to': event.to }, 'ok', 'internal', options.onSpan));
353
+ active = target;
354
+ ({ registry, targets: handoffTargets } = buildRegistry(target));
355
+ effectiveModel = this.resolveModel(target);
356
+ routeProvider = target.config.provider;
357
+ ({ backend, model } = this.route(effectiveModel, routeProvider));
358
+ reasoning = options.reasoning ?? target.config.reasoning;
359
+ responseFormat = toLLMResponseFormat(target.config.responseFormat);
360
+ lastToolCallSig = '';
361
+ };
362
+ const handleToolTurn = async (toolCalls, resumed) => {
363
+ if (!resumed) {
364
+ const currentSig = toolCalls
365
+ .map((tc) => `${tc.name}:${JSON.stringify(tc.arguments)}`)
366
+ .join('|');
367
+ if (currentSig === lastToolCallSig) {
368
+ for (const tc of toolCalls) {
369
+ const errorResult = {
370
+ callId: tc.id,
371
+ name: tc.name,
372
+ result: null,
373
+ error: 'Duplicate tool call detected. Try a different approach.',
374
+ };
375
+ const duplicateMessage = createToolMessage(tc, errorResult);
376
+ messages.push(duplicateMessage);
377
+ if (this.state.memoryAdapter &&
378
+ options.saveHistory !== false &&
379
+ options.useMemory !== false) {
380
+ await saveEntry(threadId, active.id, duplicateMessage, this.state.memoryAdapter, undefined, [errorResult], options.onMemoryError, options.userId);
381
+ }
382
+ }
383
+ lastToolCallSig = '';
384
+ return undefined;
385
+ }
386
+ lastToolCallSig = currentSig;
387
+ for (const toolCall of toolCalls) {
388
+ allToolCalls.push(toolCall);
389
+ options.onToolCall?.(toolCall);
390
+ }
391
+ }
392
+ const decisions = new Map(Object.entries(resumed?.decisions ?? {}));
393
+ const pending = [];
394
+ for (const toolCall of toolCalls) {
395
+ const tool = registry.get(toolCall.name);
396
+ if (!tool || decisions.has(toolCall.id) || !needsApproval(tool, toolCall.arguments)) {
397
+ continue;
398
+ }
399
+ const request = {
400
+ toolCallId: toolCall.id,
401
+ toolName: toolCall.name,
402
+ arguments: toolCall.arguments,
403
+ description: tool.description,
404
+ ...(tool.sideEffects && { sideEffects: [...tool.sideEffects] }),
405
+ };
406
+ const decision = resumed?.fallback ?? (await this.decideApproval(request, options));
407
+ if (decision === 'pause')
408
+ pending.push(request);
409
+ else
410
+ decisions.set(toolCall.id, decision);
411
+ }
412
+ if (pending.length > 0) {
413
+ return { toolCalls, decisions: Object.fromEntries(decisions), pending };
414
+ }
415
+ if (resumed)
416
+ await this.runCheckpointStore().delete(threadId);
417
+ const executeToolCall = async (toolCall) => {
418
+ const toolSpanStart = Date.now();
419
+ const decision = decisions.get(toolCall.id);
420
+ if (decision?.approved === false) {
421
+ const declined = {
422
+ callId: toolCall.id,
423
+ name: toolCall.name,
424
+ result: null,
425
+ error: `The user declined this tool call${decision.reason ? `: ${decision.reason}` : ''}`,
426
+ };
427
+ return { toolCall, result: declined, toolSpanStart, toolSpanEnd: Date.now() };
428
+ }
429
+ const result = await waitForAbortable(executeTool(registry, toolCall, runId, active.id, this.state.sandboxManager, this.state.constitutionalAI, this.state.constitutionalAI?.config.filterToolCalls ?? false, () => initializeSandbox(this.config, this.state), abortController.signal, {
430
+ threadId,
431
+ userId: options.userId,
432
+ channelType: options.channelType,
433
+ channelId: options.channelId,
434
+ }, decision?.approved === true), abortController.signal);
435
+ const toolSpanEnd = Date.now();
436
+ return { toolCall, result, toolSpanStart, toolSpanEnd };
437
+ };
438
+ const toolResults = options.parallelToolCalls
439
+ ? await Promise.all(toolCalls.map(executeToolCall))
440
+ : await (async () => {
441
+ const results = [];
442
+ for (const toolCall of toolCalls) {
443
+ results.push(await executeToolCall(toolCall));
444
+ }
445
+ return results;
446
+ })();
447
+ const reflectionMessages = [];
448
+ for (const { toolCall, result, toolSpanStart, toolSpanEnd } of toolResults) {
449
+ const toolSpan = createSpan(`tool.${toolCall.name}`, traceId, rootSpanId, toolSpanStart, toolSpanEnd, {
450
+ 'tool.name': toolCall.name,
451
+ 'tool.call_id': toolCall.id,
452
+ 'tool.arguments': JSON.stringify(toolCall.arguments),
453
+ 'tool.success': !result.error,
454
+ 'tool.error': result.error,
455
+ }, result.error ? 'error' : 'ok', 'internal', options.onSpan);
456
+ spans.push(toolSpan);
457
+ options.onToolResult?.(result);
458
+ const toolMessage = createToolMessage(toolCall, result);
459
+ messages.push(toolMessage);
460
+ if (this.state.memoryAdapter &&
461
+ options.saveHistory !== false &&
462
+ options.useMemory !== false) {
463
+ await saveEntry(threadId, active.id, toolMessage, this.state.memoryAdapter, undefined, [result], options.onMemoryError, options.userId);
464
+ }
465
+ const action = {
466
+ type: 'tool_call',
467
+ toolName: toolCall.name,
468
+ input: toolCall.arguments,
469
+ output: result.result,
470
+ error: result.error,
471
+ duration: toolSpanEnd - toolSpanStart,
472
+ };
473
+ allActions.push(action);
474
+ const reflection = this.config.reflection;
475
+ const reflectOnFailure = action.error !== undefined && !!reflection?.reflectAfterError;
476
+ if (this.state.reflectionEngine &&
477
+ reflection?.enabled &&
478
+ (reflectOnFailure || reflection.reflectAfterToolCall)) {
479
+ try {
480
+ const reflectionResult = reflectOnFailure
481
+ ? await this.state.reflectionEngine.reflectOnError(action, agentContext)
482
+ : await this.state.reflectionEngine.reflectOnToolCall(action, agentContext);
483
+ allReflections.push(reflectionResult.reflection);
484
+ if (reflectionResult.shouldAdjustStrategy && reflectionResult.suggestedAction) {
485
+ reflectionMessages.push({
486
+ role: 'system',
487
+ content: `Reflection: ${reflectionResult.reflection.analysis.reasoning}. Consider: ${reflectionResult.suggestedAction}`,
488
+ });
489
+ }
490
+ }
491
+ catch (reflectionError) {
492
+ getLogger().warn('Reflection failed', {
493
+ error: reflectionError instanceof Error
494
+ ? reflectionError.message
495
+ : String(reflectionError),
496
+ });
497
+ }
498
+ }
499
+ }
500
+ messages.push(...reflectionMessages);
501
+ const handoff = toolCalls.find((call) => handoffTargets.has(call.name));
502
+ const target = handoff && handoffTargets.get(handoff.name);
503
+ if (handoff && target)
504
+ switchTo(target, handoff.arguments.reason);
505
+ return undefined;
506
+ };
507
+ if (checkpoint && resumeFrom) {
508
+ pausedTurn = await handleToolTurn(checkpoint.turn.toolCalls, {
509
+ decisions: { ...checkpoint.turn.decisions, ...resumeFrom.decisions },
510
+ fallback: resumeFrom.defaultDecision,
511
+ });
512
+ }
513
+ while (!pausedTurn && iterations < maxIterations) {
249
514
  throwIfAborted(abortController.signal);
250
515
  this.assertTokenBudget(totalInputTokens + totalOutputTokens);
251
516
  if (this.state.contextManager?.shouldCompress(messages, effectiveModel)) {
@@ -259,18 +524,20 @@ export class Cogitator {
259
524
  const llmSpanStart = Date.now();
260
525
  let response;
261
526
  if (options.stream && options.onToken) {
262
- response = await waitForAbortable(streamChat(backend, model, messages, registry, agent, options.onToken, abortController.signal, responseFormat), abortController.signal);
527
+ response = await waitForAbortable(streamChat(backend, model, messages, registry, active, options.onToken, abortController.signal, responseFormat, { reasoning, cache: promptCache, onReasoning: options.onReasoning }), abortController.signal);
263
528
  }
264
529
  else {
265
530
  response = await waitForAbortable(backend.chat({
266
531
  model,
267
532
  messages,
268
533
  tools: registry.getSchemas(),
269
- temperature: agent.config.temperature,
270
- topP: agent.config.topP,
271
- maxTokens: agent.config.maxTokens,
272
- stop: agent.config.stopSequences,
534
+ temperature: active.config.temperature,
535
+ topP: active.config.topP,
536
+ maxTokens: active.config.maxTokens,
537
+ stop: active.config.stopSequences,
273
538
  responseFormat,
539
+ reasoning,
540
+ cache: promptCache,
274
541
  signal: abortController.signal,
275
542
  }), abortController.signal);
276
543
  }
@@ -279,11 +546,22 @@ export class Cogitator {
279
546
  'llm.iteration': iterations,
280
547
  'llm.input_tokens': response.usage.inputTokens,
281
548
  'llm.output_tokens': response.usage.outputTokens,
549
+ ...(response.usage.cachedInputTokens && {
550
+ 'llm.cached_input_tokens': response.usage.cachedInputTokens,
551
+ }),
552
+ ...(response.usage.reasoningTokens && {
553
+ 'llm.reasoning_tokens': response.usage.reasoningTokens,
554
+ }),
282
555
  'llm.finish_reason': response.finishReason,
283
556
  }, 'ok', 'client', options.onSpan);
284
557
  spans.push(llmSpan);
285
558
  totalInputTokens += response.usage.inputTokens;
286
559
  totalOutputTokens += response.usage.outputTokens;
560
+ cachedInputTokens += response.usage.cachedInputTokens ?? 0;
561
+ cacheWriteTokens += response.usage.cacheWriteTokens ?? 0;
562
+ reasoningTokens += response.usage.reasoningTokens ?? 0;
563
+ if (response.reasoning)
564
+ reasoningParts.push(response.reasoning);
287
565
  let outputContent = response.content;
288
566
  if (this.state.constitutionalAI?.config.filterOutput) {
289
567
  const outputResult = await this.state.constitutionalAI.filterOutput(outputContent, messages);
@@ -307,122 +585,81 @@ export class Cogitator {
307
585
  if (this.state.memoryAdapter &&
308
586
  options.saveHistory !== false &&
309
587
  options.useMemory !== false) {
310
- await saveEntry(threadId, agent.id, assistantMessage, this.state.memoryAdapter, response.toolCalls, undefined, options.onMemoryError, options.userId);
588
+ await saveEntry(threadId, active.id, assistantMessage, this.state.memoryAdapter, response.toolCalls, undefined, options.onMemoryError, options.userId);
311
589
  }
312
590
  if (response.finishReason === 'tool_calls' &&
313
591
  response.toolCalls &&
314
592
  response.toolCalls.length > 0) {
315
- let toolCalls = response.toolCalls;
316
- const currentSig = toolCalls
317
- .map((tc) => `${tc.name}:${JSON.stringify(tc.arguments)}`)
318
- .join('|');
319
- if (currentSig === lastToolCallSig) {
320
- for (const tc of toolCalls) {
321
- const errorResult = {
322
- callId: tc.id,
323
- name: tc.name,
324
- result: null,
325
- error: 'Duplicate tool call detected. Try a different approach.',
326
- };
327
- const duplicateMessage = createToolMessage(tc, errorResult);
328
- messages.push(duplicateMessage);
329
- if (this.state.memoryAdapter &&
330
- options.saveHistory !== false &&
331
- options.useMemory !== false) {
332
- await saveEntry(threadId, agent.id, duplicateMessage, this.state.memoryAdapter, undefined, [errorResult], options.onMemoryError, options.userId);
333
- }
334
- }
335
- lastToolCallSig = '';
336
- continue;
337
- }
338
- lastToolCallSig = currentSig;
339
- for (const toolCall of toolCalls) {
340
- allToolCalls.push(toolCall);
341
- options.onToolCall?.(toolCall);
342
- }
343
- const executeToolCall = async (toolCall) => {
344
- const toolSpanStart = Date.now();
345
- const result = await waitForAbortable(executeTool(registry, toolCall, runId, agent.id, this.state.sandboxManager, this.state.constitutionalAI, this.state.constitutionalAI?.config.filterToolCalls ?? false, () => initializeSandbox(this.config, this.state), abortController.signal, {
346
- threadId,
347
- userId: options.userId,
348
- channelType: options.channelType,
349
- channelId: options.channelId,
350
- }), abortController.signal);
351
- const toolSpanEnd = Date.now();
352
- return { toolCall, result, toolSpanStart, toolSpanEnd };
353
- };
354
- const toolResults = options.parallelToolCalls
355
- ? await Promise.all(toolCalls.map(executeToolCall))
356
- : await (async () => {
357
- const results = [];
358
- for (const toolCall of toolCalls) {
359
- results.push(await executeToolCall(toolCall));
360
- }
361
- return results;
362
- })();
363
- const reflectionMessages = [];
364
- for (const { toolCall, result, toolSpanStart, toolSpanEnd } of toolResults) {
365
- const toolSpan = createSpan(`tool.${toolCall.name}`, traceId, rootSpanId, toolSpanStart, toolSpanEnd, {
366
- 'tool.name': toolCall.name,
367
- 'tool.call_id': toolCall.id,
368
- 'tool.arguments': JSON.stringify(toolCall.arguments),
369
- 'tool.success': !result.error,
370
- 'tool.error': result.error,
371
- }, result.error ? 'error' : 'ok', 'internal', options.onSpan);
372
- spans.push(toolSpan);
373
- options.onToolResult?.(result);
374
- const toolMessage = createToolMessage(toolCall, result);
375
- messages.push(toolMessage);
376
- if (this.state.memoryAdapter &&
377
- options.saveHistory !== false &&
378
- options.useMemory !== false) {
379
- await saveEntry(threadId, agent.id, toolMessage, this.state.memoryAdapter, undefined, [result], options.onMemoryError, options.userId);
380
- }
381
- const action = {
382
- type: 'tool_call',
383
- toolName: toolCall.name,
384
- input: toolCall.arguments,
385
- output: result.result,
386
- error: result.error,
387
- duration: toolSpanEnd - toolSpanStart,
388
- };
389
- allActions.push(action);
390
- const reflection = this.config.reflection;
391
- const reflectOnFailure = action.error !== undefined && !!reflection?.reflectAfterError;
392
- if (this.state.reflectionEngine &&
393
- reflection?.enabled &&
394
- (reflectOnFailure || reflection.reflectAfterToolCall)) {
395
- try {
396
- const reflectionResult = reflectOnFailure
397
- ? await this.state.reflectionEngine.reflectOnError(action, agentContext)
398
- : await this.state.reflectionEngine.reflectOnToolCall(action, agentContext);
399
- allReflections.push(reflectionResult.reflection);
400
- if (reflectionResult.shouldAdjustStrategy && reflectionResult.suggestedAction) {
401
- reflectionMessages.push({
402
- role: 'system',
403
- content: `Reflection: ${reflectionResult.reflection.analysis.reasoning}. Consider: ${reflectionResult.suggestedAction}`,
404
- });
405
- }
406
- }
407
- catch (reflectionError) {
408
- getLogger().warn('Reflection failed', {
409
- error: reflectionError instanceof Error
410
- ? reflectionError.message
411
- : String(reflectionError),
412
- });
413
- }
414
- }
415
- }
416
- messages.push(...reflectionMessages);
593
+ pausedTurn = await handleToolTurn(response.toolCalls);
594
+ if (pausedTurn)
595
+ break;
417
596
  }
418
597
  else {
419
598
  break;
420
599
  }
421
600
  }
601
+ if (pausedTurn) {
602
+ const pausedCheckpoint = {
603
+ version: 1,
604
+ runId,
605
+ agentId: active.id,
606
+ threadId,
607
+ ...(options.userId !== undefined && { userId: options.userId }),
608
+ model: effectiveModel,
609
+ ...(routeProvider !== undefined && { provider: routeProvider }),
610
+ input,
611
+ messages,
612
+ toolCalls: allToolCalls,
613
+ ...(prompt && { prompt }),
614
+ ...(active !== agent && { activeAgent: active.name }),
615
+ ...(handoffs.length > 0 && { handoffs }),
616
+ turn: { toolCalls: pausedTurn.toolCalls, decisions: pausedTurn.decisions },
617
+ iterations,
618
+ lastToolCallSignature: lastToolCallSig,
619
+ usage: {
620
+ inputTokens: totalInputTokens,
621
+ outputTokens: totalOutputTokens,
622
+ cachedInputTokens,
623
+ cacheWriteTokens,
624
+ reasoningTokens,
625
+ },
626
+ reasoning: reasoningParts,
627
+ startedAt: checkpoint?.startedAt ?? startTime,
628
+ };
629
+ const paused = {
630
+ output: getTextContent(messages[messages.length - 1]?.content ?? ''),
631
+ runId,
632
+ agentId: agent.id,
633
+ threadId,
634
+ status: 'paused',
635
+ pendingApprovals: pausedTurn.pending,
636
+ ...(handoffs.length > 0 && { handoffs, finalAgent: active.name }),
637
+ checkpoint: pausedCheckpoint,
638
+ usage: {
639
+ inputTokens: totalInputTokens,
640
+ outputTokens: totalOutputTokens,
641
+ totalTokens: totalInputTokens + totalOutputTokens,
642
+ cost: this.calculateCost(effectiveModel, {
643
+ inputTokens: totalInputTokens,
644
+ outputTokens: totalOutputTokens,
645
+ cachedInputTokens,
646
+ cacheWriteTokens,
647
+ }),
648
+ duration: Date.now() - startTime,
649
+ },
650
+ ...(reasoningParts.length > 0 && { reasoning: reasoningParts.join('\n\n') }),
651
+ toolCalls: allToolCalls,
652
+ messages,
653
+ trace: { traceId, spans },
654
+ };
655
+ await this.runCheckpointStore().save(pausedCheckpoint);
656
+ options.onRunComplete?.(paused);
657
+ return paused;
658
+ }
422
659
  const endTime = Date.now();
423
660
  const lastAssistantMessage = messages.filter((m) => m.role === 'assistant').pop();
424
661
  const finalOutput = lastAssistantMessage ? getTextContent(lastAssistantMessage.content) : '';
425
- const structured = parseStructuredOutput(agent.config.responseFormat, finalOutput);
662
+ const structured = parseStructuredOutput(active.config.responseFormat, finalOutput);
426
663
  if (this.state.reflectionEngine &&
427
664
  this.config.reflection?.enabled &&
428
665
  this.config.reflection.reflectAtEnd) {
@@ -448,7 +685,12 @@ export class Cogitator {
448
685
  'run.output_tokens': totalOutputTokens,
449
686
  }, 'ok', 'server', options.onSpan, rootSpanId);
450
687
  spans.unshift(rootSpan);
451
- const runCost = this.calculateCost(effectiveModel, totalInputTokens, totalOutputTokens);
688
+ const runCost = this.calculateCost(effectiveModel, {
689
+ inputTokens: totalInputTokens,
690
+ outputTokens: totalOutputTokens,
691
+ cachedInputTokens,
692
+ cacheWriteTokens,
693
+ });
452
694
  if (this.state.costRouter) {
453
695
  this.state.costRouter.recordCost({
454
696
  runId,
@@ -463,6 +705,9 @@ export class Cogitator {
463
705
  const result = {
464
706
  output: finalOutput,
465
707
  ...(structured !== undefined && { structured }),
708
+ status: 'completed',
709
+ ...(prompt && { prompt }),
710
+ ...(handoffs.length > 0 && { handoffs, finalAgent: active.name }),
466
711
  runId,
467
712
  agentId: agent.id,
468
713
  threadId,
@@ -473,7 +718,11 @@ export class Cogitator {
473
718
  totalTokens: totalInputTokens + totalOutputTokens,
474
719
  cost: runCost,
475
720
  duration: endTime - startTime,
721
+ ...(reasoningTokens > 0 && { reasoningTokens }),
722
+ ...(cachedInputTokens > 0 && { cachedInputTokens }),
723
+ ...(cacheWriteTokens > 0 && { cacheWriteTokens }),
476
724
  },
725
+ ...(reasoningParts.length > 0 && { reasoning: reasoningParts.join('\n\n') }),
477
726
  toolCalls: allToolCalls,
478
727
  messages,
479
728
  trace: {
@@ -485,10 +734,14 @@ export class Cogitator {
485
734
  ? await this.state.reflectionEngine.getSummary(agent.id)
486
735
  : undefined,
487
736
  };
737
+ if (prompt)
738
+ await this.recordPrompt(prompt, result, Date.now() - startTime);
488
739
  options.onRunComplete?.(result);
489
740
  return result;
490
741
  }
491
742
  catch (error) {
743
+ if (prompt)
744
+ await this.recordPrompt(prompt, undefined, Date.now() - startTime);
492
745
  const endTime = Date.now();
493
746
  const errorSpan = createSpan('agent.run', traceId, undefined, startTime, endTime, {
494
747
  'agent.id': agent.id,
@@ -509,6 +762,129 @@ export class Cogitator {
509
762
  removeParentAbortListener?.();
510
763
  }
511
764
  }
765
+ /**
766
+ * The messages a new run starts with: the thread's history and the input,
767
+ * once the input passed the injection and constitutional checks, with the
768
+ * input saved to memory.
769
+ */
770
+ async prepareMessages(agent, options, input, threadId) {
771
+ const messages = await buildInitialMessages(agent, options, threadId, this.state.memoryAdapter, this.state.contextBuilder);
772
+ const pii = this.config.security?.pii;
773
+ if (pii?.mode === 'block') {
774
+ const found = new PiiMasker(pii).find(input);
775
+ if (found.length > 0) {
776
+ throw new CogitatorError({
777
+ message: `The input contains personal data: ${[...new Set(found.map((f) => f.type))].join(', ')}`,
778
+ code: ErrorCode.PII_DETECTED,
779
+ details: { types: [...new Set(found.map((f) => f.type))] },
780
+ });
781
+ }
782
+ }
783
+ if (this.state.injectionDetector) {
784
+ const injectionResult = await this.state.injectionDetector.analyze(input);
785
+ if (injectionResult.action === 'blocked') {
786
+ const threatTypes = injectionResult.threats.map((t) => t.type).join(', ');
787
+ throw new CogitatorError({
788
+ message: `Prompt injection detected: ${threatTypes}`,
789
+ code: ErrorCode.PROMPT_INJECTION_DETECTED,
790
+ details: { threats: injectionResult.threats },
791
+ });
792
+ }
793
+ }
794
+ if (this.state.constitutionalAI?.config.filterInput) {
795
+ const inputResult = await this.state.constitutionalAI.filterInput(input);
796
+ if (!inputResult.allowed) {
797
+ throw new Error(`Input blocked: ${inputResult.blockedReason ?? 'Policy violation'}`);
798
+ }
799
+ }
800
+ if (options.context) {
801
+ addContextToMessages(messages, options.context);
802
+ }
803
+ if (this.state.memoryAdapter && options.saveHistory !== false && options.useMemory !== false) {
804
+ const currentUserMessage = messages[messages.length - 1];
805
+ await saveEntry(threadId, agent.id, currentUserMessage, this.state.memoryAdapter, undefined, undefined, options.onMemoryError, options.userId);
806
+ }
807
+ return messages;
808
+ }
809
+ /**
810
+ * Versioned instructions and A/B tests of this Cogitator's agents
811
+ * (`prompts` config, in process memory by default).
812
+ */
813
+ get prompts() {
814
+ this.promptRegistry ??= new PromptRegistry(this.config.prompts);
815
+ return this.promptRegistry;
816
+ }
817
+ async recordPrompt(prompt, result, durationMs) {
818
+ try {
819
+ await this.prompts.record(prompt, { result, durationMs });
820
+ }
821
+ catch (error) {
822
+ getLogger().warn('Could not record the outcome of a run against its instructions', {
823
+ prompt: prompt.key,
824
+ error: error instanceof Error ? error.message : String(error),
825
+ });
826
+ }
827
+ }
828
+ runCheckpointStore() {
829
+ if (this.config.runCheckpoints)
830
+ return this.config.runCheckpoints;
831
+ const memory = this.state.memoryAdapter;
832
+ if (memory) {
833
+ if (this.threadCheckpoints?.memory !== memory) {
834
+ this.threadCheckpoints = { memory, store: new ThreadRunCheckpointStore(memory) };
835
+ }
836
+ return this.threadCheckpoints.store;
837
+ }
838
+ this.processCheckpoints ??= new InMemoryRunCheckpointStore();
839
+ return this.processCheckpoints;
840
+ }
841
+ /**
842
+ * A new run on a thread whose run is waiting for approvals means the user
843
+ * moved on: the waiting calls are answered as declined, so the thread's
844
+ * history stays whole, and the pause is dropped.
845
+ */
846
+ async abandonPausedRun(agent, options, threadId) {
847
+ if (options.threadId === undefined)
848
+ return;
849
+ const store = this.runCheckpointStore();
850
+ const paused = await store.load(threadId).catch((error) => {
851
+ getLogger().warn('Could not read the paused run of a thread', {
852
+ threadId,
853
+ error: error instanceof Error ? error.message : String(error),
854
+ });
855
+ return null;
856
+ });
857
+ if (!paused)
858
+ return;
859
+ if (options.threadAccess !== 'shared' && paused.userId !== options.userId)
860
+ return;
861
+ if (this.state.memoryAdapter && options.useMemory !== false && options.saveHistory !== false) {
862
+ for (const toolCall of paused.turn.toolCalls) {
863
+ const result = {
864
+ callId: toolCall.id,
865
+ name: toolCall.name,
866
+ result: null,
867
+ error: 'The user moved on without approving this tool call',
868
+ };
869
+ await saveEntry(threadId, agent.id, createToolMessage(toolCall, result), this.state.memoryAdapter, undefined, [result], options.onMemoryError, options.userId);
870
+ }
871
+ }
872
+ await store.delete(threadId);
873
+ }
874
+ /**
875
+ * The decision for a call that needs approval: the run's `onApproval`, else
876
+ * `guardrails.onToolApproval`, else a pause.
877
+ */
878
+ async decideApproval(request, options) {
879
+ if (options.onApproval)
880
+ return options.onApproval(request);
881
+ const legacy = this.config.guardrails?.onToolApproval;
882
+ if (legacy) {
883
+ const approved = await legacy(request.toolName, request.arguments, request.sideEffects ?? []);
884
+ return approved ? { approved: true } : { approved: false };
885
+ }
886
+ return 'pause';
887
+ }
512
888
  async acquireRunSlot(signal) {
513
889
  const max = this.config.limits?.maxConcurrentRuns;
514
890
  if (max === undefined)
@@ -606,7 +982,7 @@ export class Cogitator {
606
982
  const cached = this.backends.get(name);
607
983
  if (cached)
608
984
  return cached;
609
- const backend = withLLMRetry(this.createBackend(name), this.config.llm?.retry);
985
+ const backend = withPiiMasking(withLLMRetry(this.createBackend(name), this.config.llm?.retry), this.config.security?.pii);
610
986
  this.backends.set(name, backend);
611
987
  return backend;
612
988
  }
@@ -624,13 +1000,9 @@ export class Cogitator {
624
1000
  code: ErrorCode.CONFIGURATION_ERROR,
625
1001
  });
626
1002
  }
627
- calculateCost(model, inputTokens, outputTokens) {
1003
+ calculateCost(model, usage) {
628
1004
  const { model: modelName } = parseModel(model);
629
- const price = getPrice(modelName);
630
- if (!price) {
631
- return 0;
632
- }
633
- return (inputTokens * price.input + outputTokens * price.output) / 1_000_000;
1005
+ return calculateModelCost(modelName, usage) ?? 0;
634
1006
  }
635
1007
  /**
636
1008
  * Get accumulated insights from reflection for an agent.
@@ -803,4 +1175,16 @@ function waitForAbortable(promise, signal) {
803
1175
  });
804
1176
  });
805
1177
  }
1178
+ /** Whether a call to `tool` with `args` needs approval; a check that throws counts as yes. */
1179
+ function needsApproval(tool, args) {
1180
+ const check = tool.requiresApproval;
1181
+ if (typeof check !== 'function')
1182
+ return check === true;
1183
+ try {
1184
+ return check(args);
1185
+ }
1186
+ catch {
1187
+ return true;
1188
+ }
1189
+ }
806
1190
  //# sourceMappingURL=runtime.js.map