@cogitator-ai/core 0.22.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.
- package/README.md +95 -0
- package/dist/agent-tool.d.ts +7 -1
- package/dist/agent-tool.d.ts.map +1 -1
- package/dist/agent-tool.js +4 -1
- package/dist/agent-tool.js.map +1 -1
- package/dist/agent.d.ts +6 -25
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +18 -3
- package/dist/agent.js.map +1 -1
- package/dist/cogitator/handoffs.d.ts +14 -0
- package/dist/cogitator/handoffs.d.ts.map +1 -0
- package/dist/cogitator/handoffs.js +53 -0
- package/dist/cogitator/handoffs.js.map +1 -0
- package/dist/cogitator/initializers.d.ts.map +1 -1
- package/dist/cogitator/initializers.js +14 -2
- package/dist/cogitator/initializers.js.map +1 -1
- package/dist/cogitator/message-builder.d.ts +1 -1
- package/dist/cogitator/message-builder.d.ts.map +1 -1
- package/dist/cogitator/message-builder.js +23 -8
- package/dist/cogitator/message-builder.js.map +1 -1
- package/dist/cogitator/prompts.d.ts +69 -0
- package/dist/cogitator/prompts.d.ts.map +1 -0
- package/dist/cogitator/prompts.js +149 -0
- package/dist/cogitator/prompts.js.map +1 -0
- package/dist/cogitator/run-checkpoints.d.ts +20 -0
- package/dist/cogitator/run-checkpoints.d.ts.map +1 -0
- package/dist/cogitator/run-checkpoints.js +67 -0
- package/dist/cogitator/run-checkpoints.js.map +1 -0
- package/dist/cogitator/streaming.d.ts +9 -7
- package/dist/cogitator/streaming.d.ts.map +1 -1
- package/dist/cogitator/streaming.js +12 -1
- package/dist/cogitator/streaming.js.map +1 -1
- package/dist/cogitator/threads.d.ts +27 -0
- package/dist/cogitator/threads.d.ts.map +1 -0
- package/dist/cogitator/threads.js +54 -0
- package/dist/cogitator/threads.js.map +1 -0
- package/dist/cogitator/tool-executor.d.ts +1 -1
- package/dist/cogitator/tool-executor.d.ts.map +1 -1
- package/dist/cogitator/tool-executor.js +2 -2
- package/dist/cogitator/tool-executor.js.map +1 -1
- package/dist/constitutional/constitutional-ai.d.ts +7 -1
- package/dist/constitutional/constitutional-ai.d.ts.map +1 -1
- package/dist/constitutional/constitutional-ai.js +6 -2
- package/dist/constitutional/constitutional-ai.js.map +1 -1
- package/dist/constitutional/tool-guard.d.ts +3 -1
- package/dist/constitutional/tool-guard.d.ts.map +1 -1
- package/dist/constitutional/tool-guard.js +2 -2
- package/dist/constitutional/tool-guard.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/learning/postgres-trace-store.d.ts +5 -1
- package/dist/learning/postgres-trace-store.d.ts.map +1 -1
- package/dist/learning/postgres-trace-store.js +23 -0
- package/dist/learning/postgres-trace-store.js.map +1 -1
- package/dist/learning/prompt-stores.d.ts +26 -0
- package/dist/learning/prompt-stores.d.ts.map +1 -0
- package/dist/learning/prompt-stores.js +97 -0
- package/dist/learning/prompt-stores.js.map +1 -0
- package/dist/llm/anthropic-thinking.d.ts +29 -0
- package/dist/llm/anthropic-thinking.d.ts.map +1 -0
- package/dist/llm/anthropic-thinking.js +100 -0
- package/dist/llm/anthropic-thinking.js.map +1 -0
- package/dist/llm/anthropic.d.ts +14 -0
- package/dist/llm/anthropic.d.ts.map +1 -1
- package/dist/llm/anthropic.js +139 -85
- package/dist/llm/anthropic.js.map +1 -1
- package/dist/llm/azure.d.ts +2 -0
- package/dist/llm/azure.d.ts.map +1 -1
- package/dist/llm/azure.js +1 -0
- package/dist/llm/azure.js.map +1 -1
- package/dist/llm/bedrock.d.ts +2 -0
- package/dist/llm/bedrock.d.ts.map +1 -1
- package/dist/llm/bedrock.js +139 -39
- package/dist/llm/bedrock.js.map +1 -1
- package/dist/llm/claude-models.d.ts +23 -0
- package/dist/llm/claude-models.d.ts.map +1 -1
- package/dist/llm/claude-models.js +50 -0
- package/dist/llm/claude-models.js.map +1 -1
- package/dist/llm/errors.d.ts +5 -0
- package/dist/llm/errors.d.ts.map +1 -1
- package/dist/llm/errors.js +36 -26
- package/dist/llm/errors.js.map +1 -1
- package/dist/llm/google.d.ts +12 -1
- package/dist/llm/google.d.ts.map +1 -1
- package/dist/llm/google.js +91 -15
- package/dist/llm/google.js.map +1 -1
- package/dist/llm/index.d.ts +7 -2
- package/dist/llm/index.d.ts.map +1 -1
- package/dist/llm/index.js +16 -2
- package/dist/llm/index.js.map +1 -1
- package/dist/llm/ollama.d.ts +6 -1
- package/dist/llm/ollama.d.ts.map +1 -1
- package/dist/llm/ollama.js +33 -3
- package/dist/llm/ollama.js.map +1 -1
- package/dist/llm/openai-compatible-base.d.ts.map +1 -1
- package/dist/llm/openai-compatible-base.js +29 -21
- package/dist/llm/openai-compatible-base.js.map +1 -1
- package/dist/llm/openai-responses.d.ts.map +1 -1
- package/dist/llm/openai-responses.js +20 -3
- package/dist/llm/openai-responses.js.map +1 -1
- package/dist/llm/openai.d.ts +2 -0
- package/dist/llm/openai.d.ts.map +1 -1
- package/dist/llm/openai.js +1 -0
- package/dist/llm/openai.js.map +1 -1
- package/dist/llm/retry.d.ts +32 -0
- package/dist/llm/retry.d.ts.map +1 -0
- package/dist/llm/retry.js +107 -0
- package/dist/llm/retry.js.map +1 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +3 -5
- package/dist/logger.js.map +1 -1
- package/dist/runtime.d.ts +49 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +571 -187
- package/dist/runtime.js.map +1 -1
- package/dist/security/pii.d.ts +44 -0
- package/dist/security/pii.d.ts.map +1 -0
- package/dist/security/pii.js +285 -0
- package/dist/security/pii.js.map +1 -0
- package/dist/skill.d.ts.map +1 -1
- package/dist/skill.js +16 -10
- package/dist/skill.js.map +1 -1
- package/dist/tools/audio-generate.d.ts +1 -1
- package/dist/tools/audio-generate.d.ts.map +1 -1
- package/dist/tools/audio-generate.js +2 -1
- package/dist/tools/audio-generate.js.map +1 -1
- package/dist/tools/audio-transcribe.d.ts.map +1 -1
- package/dist/tools/audio-transcribe.js +2 -1
- package/dist/tools/audio-transcribe.js.map +1 -1
- package/dist/tools/email.d.ts.map +1 -1
- package/dist/tools/email.js +10 -9
- package/dist/tools/email.js.map +1 -1
- package/dist/tools/github.d.ts.map +1 -1
- package/dist/tools/github.js +2 -1
- package/dist/tools/github.js.map +1 -1
- package/dist/tools/image-generate.d.ts +2 -2
- package/dist/tools/image-generate.d.ts.map +1 -1
- package/dist/tools/image-generate.js +2 -1
- package/dist/tools/image-generate.js.map +1 -1
- package/dist/tools/vector-search.d.ts.map +1 -1
- package/dist/tools/vector-search.js +8 -7
- package/dist/tools/vector-search.js.map +1 -1
- package/dist/tools/web-search.d.ts.map +1 -1
- package/dist/tools/web-search.js +7 -6
- package/dist/tools/web-search.js.map +1 -1
- package/dist/utils/env.d.ts +9 -0
- package/dist/utils/env.d.ts.map +1 -0
- package/dist/utils/env.js +26 -0
- package/dist/utils/env.js.map +1 -0
- package/package.json +6 -6
package/dist/runtime.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { nanoid } from 'nanoid';
|
|
2
|
-
import {
|
|
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
|
+
import { withLLMRetry } from './llm/retry.js';
|
|
8
|
+
import { PiiMasker, withPiiMasking } from './security/pii.js';
|
|
7
9
|
import { getLogger } from './logger.js';
|
|
8
10
|
import { initializeMemory, initializeSandbox, initializeReflection, initializeGuardrails, initializeCostRouting, initializeSecurity, initializeContextManager, cleanupState, } from './cogitator/initializers.js';
|
|
9
11
|
import { CogitatorError, ErrorCode } from '@cogitator-ai/types';
|
|
@@ -12,8 +14,12 @@ import { createSpan, getTextContent } from './cogitator/span-factory.js';
|
|
|
12
14
|
import { executeTool, createToolMessage } from './cogitator/tool-executor.js';
|
|
13
15
|
import { streamChat } from './cogitator/streaming.js';
|
|
14
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';
|
|
15
20
|
import { parseStructuredOutput, toLLMResponseFormat } from './cogitator/response-format.js';
|
|
16
21
|
import { CostEstimator } from './cost-routing/cost-estimator.js';
|
|
22
|
+
import { readEnv } from './utils/env.js';
|
|
17
23
|
/** Run timeout when neither the run, the agent nor `limits.defaultTimeout` sets one. */
|
|
18
24
|
const DEFAULT_RUN_TIMEOUT = 120_000;
|
|
19
25
|
/**
|
|
@@ -67,6 +73,9 @@ const DEFAULT_RUN_TIMEOUT = 120_000;
|
|
|
67
73
|
export class Cogitator {
|
|
68
74
|
config;
|
|
69
75
|
backends = new Map();
|
|
76
|
+
processCheckpoints;
|
|
77
|
+
promptRegistry;
|
|
78
|
+
threadCheckpoints;
|
|
70
79
|
/** Global tool registry shared across all runs */
|
|
71
80
|
tools = new ToolRegistry();
|
|
72
81
|
state = {
|
|
@@ -130,7 +139,45 @@ export class Cogitator {
|
|
|
130
139
|
* ```
|
|
131
140
|
*/
|
|
132
141
|
async run(agent, options) {
|
|
133
|
-
|
|
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)}`;
|
|
134
181
|
const threadId = options.threadId ?? `thread_${nanoid(12)}`;
|
|
135
182
|
const traceId = `trace_${nanoid(16)}`;
|
|
136
183
|
const startTime = Date.now();
|
|
@@ -166,69 +213,109 @@ export class Cogitator {
|
|
|
166
213
|
try {
|
|
167
214
|
releaseRunSlot = await this.acquireRunSlot(abortController.signal);
|
|
168
215
|
throwIfAborted(abortController.signal);
|
|
169
|
-
|
|
216
|
+
if (!checkpoint) {
|
|
217
|
+
options.onRunStart?.({ runId, agentId: agent.id, input: options.input, threadId });
|
|
218
|
+
}
|
|
170
219
|
const agentModel = this.resolveModel(agent);
|
|
171
220
|
await this.initializeAll(agentModel);
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (agent.tools && agent.tools.length > 0) {
|
|
180
|
-
registry.registerMany(agent.tools);
|
|
181
|
-
}
|
|
182
|
-
let effectiveModel = agentModel;
|
|
183
|
-
let backend;
|
|
184
|
-
let model;
|
|
185
|
-
if (this.state.costRouter && this.config.costRouting?.autoSelectModel) {
|
|
186
|
-
const recommendation = await this.state.costRouter.recommendModel(input);
|
|
187
|
-
effectiveModel = `${recommendation.provider}/${recommendation.modelId}`;
|
|
188
|
-
const budgetCheck = this.state.costRouter.checkBudget(recommendation.estimatedCost);
|
|
189
|
-
if (!budgetCheck.allowed) {
|
|
190
|
-
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
|
+
});
|
|
191
228
|
}
|
|
192
|
-
|
|
193
|
-
model = recommendation.modelId;
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
({ backend, model } = this.route(effectiveModel, agent.config.provider));
|
|
197
|
-
}
|
|
198
|
-
const messages = await buildInitialMessages(agent, runOptions, threadId, this.state.memoryAdapter, this.state.contextBuilder);
|
|
199
|
-
if (this.state.injectionDetector) {
|
|
200
|
-
const injectionResult = await this.state.injectionDetector.analyze(input);
|
|
201
|
-
if (injectionResult.action === 'blocked') {
|
|
202
|
-
const threatTypes = injectionResult.threats.map((t) => t.type).join(', ');
|
|
229
|
+
if (resumeFrom.checkUser && checkpoint.userId !== options.userId) {
|
|
203
230
|
throw new CogitatorError({
|
|
204
|
-
message: `
|
|
205
|
-
code: ErrorCode.
|
|
206
|
-
details: { threats: injectionResult.threats },
|
|
231
|
+
message: `The paused run in thread ${threadId} belongs to another user`,
|
|
232
|
+
code: ErrorCode.THREAD_ACCESS_DENIED,
|
|
207
233
|
});
|
|
208
234
|
}
|
|
235
|
+
runId = checkpoint.runId;
|
|
236
|
+
options = { ...options, userId: checkpoint.userId };
|
|
209
237
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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 });
|
|
214
244
|
}
|
|
215
245
|
}
|
|
216
|
-
|
|
217
|
-
|
|
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;
|
|
256
|
+
}
|
|
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];
|
|
218
280
|
}
|
|
219
|
-
|
|
220
|
-
options.
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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);
|
|
224
304
|
}
|
|
225
|
-
const allToolCalls = [];
|
|
226
|
-
let totalInputTokens = 0;
|
|
227
|
-
let totalOutputTokens = 0;
|
|
228
|
-
let
|
|
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;
|
|
229
315
|
const maxIterations = agent.config?.maxIterations ?? 10;
|
|
230
|
-
let lastToolCallSig = '';
|
|
231
|
-
|
|
316
|
+
let lastToolCallSig = checkpoint?.lastToolCallSignature ?? '';
|
|
317
|
+
let pausedTurn;
|
|
318
|
+
let responseFormat = toLLMResponseFormat(active.config.responseFormat);
|
|
232
319
|
const allReflections = [];
|
|
233
320
|
const allActions = [];
|
|
234
321
|
const agentContext = {
|
|
@@ -241,10 +328,189 @@ export class Cogitator {
|
|
|
241
328
|
previousActions: [],
|
|
242
329
|
availableTools: registry.getNames(),
|
|
243
330
|
};
|
|
244
|
-
if (this.state.reflectionEngine && this.config.reflection?.enabled) {
|
|
331
|
+
if (!checkpoint && this.state.reflectionEngine && this.config.reflection?.enabled) {
|
|
245
332
|
await enrichMessagesWithInsights(messages, this.state.reflectionEngine, agentContext);
|
|
246
333
|
}
|
|
247
|
-
|
|
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) {
|
|
248
514
|
throwIfAborted(abortController.signal);
|
|
249
515
|
this.assertTokenBudget(totalInputTokens + totalOutputTokens);
|
|
250
516
|
if (this.state.contextManager?.shouldCompress(messages, effectiveModel)) {
|
|
@@ -258,18 +524,20 @@ export class Cogitator {
|
|
|
258
524
|
const llmSpanStart = Date.now();
|
|
259
525
|
let response;
|
|
260
526
|
if (options.stream && options.onToken) {
|
|
261
|
-
response = await waitForAbortable(streamChat(backend, model, messages, registry,
|
|
527
|
+
response = await waitForAbortable(streamChat(backend, model, messages, registry, active, options.onToken, abortController.signal, responseFormat, { reasoning, cache: promptCache, onReasoning: options.onReasoning }), abortController.signal);
|
|
262
528
|
}
|
|
263
529
|
else {
|
|
264
530
|
response = await waitForAbortable(backend.chat({
|
|
265
531
|
model,
|
|
266
532
|
messages,
|
|
267
533
|
tools: registry.getSchemas(),
|
|
268
|
-
temperature:
|
|
269
|
-
topP:
|
|
270
|
-
maxTokens:
|
|
271
|
-
stop:
|
|
534
|
+
temperature: active.config.temperature,
|
|
535
|
+
topP: active.config.topP,
|
|
536
|
+
maxTokens: active.config.maxTokens,
|
|
537
|
+
stop: active.config.stopSequences,
|
|
272
538
|
responseFormat,
|
|
539
|
+
reasoning,
|
|
540
|
+
cache: promptCache,
|
|
273
541
|
signal: abortController.signal,
|
|
274
542
|
}), abortController.signal);
|
|
275
543
|
}
|
|
@@ -278,11 +546,22 @@ export class Cogitator {
|
|
|
278
546
|
'llm.iteration': iterations,
|
|
279
547
|
'llm.input_tokens': response.usage.inputTokens,
|
|
280
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
|
+
}),
|
|
281
555
|
'llm.finish_reason': response.finishReason,
|
|
282
556
|
}, 'ok', 'client', options.onSpan);
|
|
283
557
|
spans.push(llmSpan);
|
|
284
558
|
totalInputTokens += response.usage.inputTokens;
|
|
285
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);
|
|
286
565
|
let outputContent = response.content;
|
|
287
566
|
if (this.state.constitutionalAI?.config.filterOutput) {
|
|
288
567
|
const outputResult = await this.state.constitutionalAI.filterOutput(outputContent, messages);
|
|
@@ -306,122 +585,81 @@ export class Cogitator {
|
|
|
306
585
|
if (this.state.memoryAdapter &&
|
|
307
586
|
options.saveHistory !== false &&
|
|
308
587
|
options.useMemory !== false) {
|
|
309
|
-
await saveEntry(threadId,
|
|
588
|
+
await saveEntry(threadId, active.id, assistantMessage, this.state.memoryAdapter, response.toolCalls, undefined, options.onMemoryError, options.userId);
|
|
310
589
|
}
|
|
311
590
|
if (response.finishReason === 'tool_calls' &&
|
|
312
591
|
response.toolCalls &&
|
|
313
592
|
response.toolCalls.length > 0) {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
.join('|');
|
|
318
|
-
if (currentSig === lastToolCallSig) {
|
|
319
|
-
for (const tc of toolCalls) {
|
|
320
|
-
const errorResult = {
|
|
321
|
-
callId: tc.id,
|
|
322
|
-
name: tc.name,
|
|
323
|
-
result: null,
|
|
324
|
-
error: 'Duplicate tool call detected. Try a different approach.',
|
|
325
|
-
};
|
|
326
|
-
const duplicateMessage = createToolMessage(tc, errorResult);
|
|
327
|
-
messages.push(duplicateMessage);
|
|
328
|
-
if (this.state.memoryAdapter &&
|
|
329
|
-
options.saveHistory !== false &&
|
|
330
|
-
options.useMemory !== false) {
|
|
331
|
-
await saveEntry(threadId, agent.id, duplicateMessage, this.state.memoryAdapter, undefined, [errorResult], options.onMemoryError);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
lastToolCallSig = '';
|
|
335
|
-
continue;
|
|
336
|
-
}
|
|
337
|
-
lastToolCallSig = currentSig;
|
|
338
|
-
for (const toolCall of toolCalls) {
|
|
339
|
-
allToolCalls.push(toolCall);
|
|
340
|
-
options.onToolCall?.(toolCall);
|
|
341
|
-
}
|
|
342
|
-
const executeToolCall = async (toolCall) => {
|
|
343
|
-
const toolSpanStart = Date.now();
|
|
344
|
-
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, {
|
|
345
|
-
threadId,
|
|
346
|
-
userId: options.userId,
|
|
347
|
-
channelType: options.channelType,
|
|
348
|
-
channelId: options.channelId,
|
|
349
|
-
}), abortController.signal);
|
|
350
|
-
const toolSpanEnd = Date.now();
|
|
351
|
-
return { toolCall, result, toolSpanStart, toolSpanEnd };
|
|
352
|
-
};
|
|
353
|
-
const toolResults = options.parallelToolCalls
|
|
354
|
-
? await Promise.all(toolCalls.map(executeToolCall))
|
|
355
|
-
: await (async () => {
|
|
356
|
-
const results = [];
|
|
357
|
-
for (const toolCall of toolCalls) {
|
|
358
|
-
results.push(await executeToolCall(toolCall));
|
|
359
|
-
}
|
|
360
|
-
return results;
|
|
361
|
-
})();
|
|
362
|
-
const reflectionMessages = [];
|
|
363
|
-
for (const { toolCall, result, toolSpanStart, toolSpanEnd } of toolResults) {
|
|
364
|
-
const toolSpan = createSpan(`tool.${toolCall.name}`, traceId, rootSpanId, toolSpanStart, toolSpanEnd, {
|
|
365
|
-
'tool.name': toolCall.name,
|
|
366
|
-
'tool.call_id': toolCall.id,
|
|
367
|
-
'tool.arguments': JSON.stringify(toolCall.arguments),
|
|
368
|
-
'tool.success': !result.error,
|
|
369
|
-
'tool.error': result.error,
|
|
370
|
-
}, result.error ? 'error' : 'ok', 'internal', options.onSpan);
|
|
371
|
-
spans.push(toolSpan);
|
|
372
|
-
options.onToolResult?.(result);
|
|
373
|
-
const toolMessage = createToolMessage(toolCall, result);
|
|
374
|
-
messages.push(toolMessage);
|
|
375
|
-
if (this.state.memoryAdapter &&
|
|
376
|
-
options.saveHistory !== false &&
|
|
377
|
-
options.useMemory !== false) {
|
|
378
|
-
await saveEntry(threadId, agent.id, toolMessage, this.state.memoryAdapter, undefined, [result], options.onMemoryError);
|
|
379
|
-
}
|
|
380
|
-
const action = {
|
|
381
|
-
type: 'tool_call',
|
|
382
|
-
toolName: toolCall.name,
|
|
383
|
-
input: toolCall.arguments,
|
|
384
|
-
output: result.result,
|
|
385
|
-
error: result.error,
|
|
386
|
-
duration: toolSpanEnd - toolSpanStart,
|
|
387
|
-
};
|
|
388
|
-
allActions.push(action);
|
|
389
|
-
const reflection = this.config.reflection;
|
|
390
|
-
const reflectOnFailure = action.error !== undefined && !!reflection?.reflectAfterError;
|
|
391
|
-
if (this.state.reflectionEngine &&
|
|
392
|
-
reflection?.enabled &&
|
|
393
|
-
(reflectOnFailure || reflection.reflectAfterToolCall)) {
|
|
394
|
-
try {
|
|
395
|
-
const reflectionResult = reflectOnFailure
|
|
396
|
-
? await this.state.reflectionEngine.reflectOnError(action, agentContext)
|
|
397
|
-
: await this.state.reflectionEngine.reflectOnToolCall(action, agentContext);
|
|
398
|
-
allReflections.push(reflectionResult.reflection);
|
|
399
|
-
if (reflectionResult.shouldAdjustStrategy && reflectionResult.suggestedAction) {
|
|
400
|
-
reflectionMessages.push({
|
|
401
|
-
role: 'system',
|
|
402
|
-
content: `Reflection: ${reflectionResult.reflection.analysis.reasoning}. Consider: ${reflectionResult.suggestedAction}`,
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
catch (reflectionError) {
|
|
407
|
-
getLogger().warn('Reflection failed', {
|
|
408
|
-
error: reflectionError instanceof Error
|
|
409
|
-
? reflectionError.message
|
|
410
|
-
: String(reflectionError),
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
messages.push(...reflectionMessages);
|
|
593
|
+
pausedTurn = await handleToolTurn(response.toolCalls);
|
|
594
|
+
if (pausedTurn)
|
|
595
|
+
break;
|
|
416
596
|
}
|
|
417
597
|
else {
|
|
418
598
|
break;
|
|
419
599
|
}
|
|
420
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
|
+
}
|
|
421
659
|
const endTime = Date.now();
|
|
422
660
|
const lastAssistantMessage = messages.filter((m) => m.role === 'assistant').pop();
|
|
423
661
|
const finalOutput = lastAssistantMessage ? getTextContent(lastAssistantMessage.content) : '';
|
|
424
|
-
const structured = parseStructuredOutput(
|
|
662
|
+
const structured = parseStructuredOutput(active.config.responseFormat, finalOutput);
|
|
425
663
|
if (this.state.reflectionEngine &&
|
|
426
664
|
this.config.reflection?.enabled &&
|
|
427
665
|
this.config.reflection.reflectAtEnd) {
|
|
@@ -447,7 +685,12 @@ export class Cogitator {
|
|
|
447
685
|
'run.output_tokens': totalOutputTokens,
|
|
448
686
|
}, 'ok', 'server', options.onSpan, rootSpanId);
|
|
449
687
|
spans.unshift(rootSpan);
|
|
450
|
-
const runCost = this.calculateCost(effectiveModel,
|
|
688
|
+
const runCost = this.calculateCost(effectiveModel, {
|
|
689
|
+
inputTokens: totalInputTokens,
|
|
690
|
+
outputTokens: totalOutputTokens,
|
|
691
|
+
cachedInputTokens,
|
|
692
|
+
cacheWriteTokens,
|
|
693
|
+
});
|
|
451
694
|
if (this.state.costRouter) {
|
|
452
695
|
this.state.costRouter.recordCost({
|
|
453
696
|
runId,
|
|
@@ -462,6 +705,9 @@ export class Cogitator {
|
|
|
462
705
|
const result = {
|
|
463
706
|
output: finalOutput,
|
|
464
707
|
...(structured !== undefined && { structured }),
|
|
708
|
+
status: 'completed',
|
|
709
|
+
...(prompt && { prompt }),
|
|
710
|
+
...(handoffs.length > 0 && { handoffs, finalAgent: active.name }),
|
|
465
711
|
runId,
|
|
466
712
|
agentId: agent.id,
|
|
467
713
|
threadId,
|
|
@@ -472,7 +718,11 @@ export class Cogitator {
|
|
|
472
718
|
totalTokens: totalInputTokens + totalOutputTokens,
|
|
473
719
|
cost: runCost,
|
|
474
720
|
duration: endTime - startTime,
|
|
721
|
+
...(reasoningTokens > 0 && { reasoningTokens }),
|
|
722
|
+
...(cachedInputTokens > 0 && { cachedInputTokens }),
|
|
723
|
+
...(cacheWriteTokens > 0 && { cacheWriteTokens }),
|
|
475
724
|
},
|
|
725
|
+
...(reasoningParts.length > 0 && { reasoning: reasoningParts.join('\n\n') }),
|
|
476
726
|
toolCalls: allToolCalls,
|
|
477
727
|
messages,
|
|
478
728
|
trace: {
|
|
@@ -484,10 +734,14 @@ export class Cogitator {
|
|
|
484
734
|
? await this.state.reflectionEngine.getSummary(agent.id)
|
|
485
735
|
: undefined,
|
|
486
736
|
};
|
|
737
|
+
if (prompt)
|
|
738
|
+
await this.recordPrompt(prompt, result, Date.now() - startTime);
|
|
487
739
|
options.onRunComplete?.(result);
|
|
488
740
|
return result;
|
|
489
741
|
}
|
|
490
742
|
catch (error) {
|
|
743
|
+
if (prompt)
|
|
744
|
+
await this.recordPrompt(prompt, undefined, Date.now() - startTime);
|
|
491
745
|
const endTime = Date.now();
|
|
492
746
|
const errorSpan = createSpan('agent.run', traceId, undefined, startTime, endTime, {
|
|
493
747
|
'agent.id': agent.id,
|
|
@@ -508,6 +762,129 @@ export class Cogitator {
|
|
|
508
762
|
removeParentAbortListener?.();
|
|
509
763
|
}
|
|
510
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
|
+
}
|
|
511
888
|
async acquireRunSlot(signal) {
|
|
512
889
|
const max = this.config.limits?.maxConcurrentRuns;
|
|
513
890
|
if (max === undefined)
|
|
@@ -602,35 +979,30 @@ export class Cogitator {
|
|
|
602
979
|
hasLLMPlugin(name));
|
|
603
980
|
}
|
|
604
981
|
backendFor(name) {
|
|
605
|
-
const custom = this.config.llm?.backends;
|
|
606
|
-
if (custom && Object.hasOwn(custom, name))
|
|
607
|
-
return custom[name];
|
|
608
982
|
const cached = this.backends.get(name);
|
|
609
983
|
if (cached)
|
|
610
984
|
return cached;
|
|
611
|
-
|
|
612
|
-
if (isLLMProvider(name)) {
|
|
613
|
-
backend = createLLMBackend(name, this.config.llm);
|
|
614
|
-
}
|
|
615
|
-
else if (hasLLMPlugin(name)) {
|
|
616
|
-
backend = createLLMBackendFromPlugin(name, this.config.llm?.plugins?.[name]);
|
|
617
|
-
}
|
|
618
|
-
else {
|
|
619
|
-
throw new CogitatorError({
|
|
620
|
-
message: `Unknown LLM provider "${name}": not a built-in provider, a backend in llm.backends or a registered plugin`,
|
|
621
|
-
code: ErrorCode.CONFIGURATION_ERROR,
|
|
622
|
-
});
|
|
623
|
-
}
|
|
985
|
+
const backend = withPiiMasking(withLLMRetry(this.createBackend(name), this.config.llm?.retry), this.config.security?.pii);
|
|
624
986
|
this.backends.set(name, backend);
|
|
625
987
|
return backend;
|
|
626
988
|
}
|
|
627
|
-
|
|
628
|
-
const
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
989
|
+
createBackend(name) {
|
|
990
|
+
const custom = this.config.llm?.backends;
|
|
991
|
+
if (custom && Object.hasOwn(custom, name))
|
|
992
|
+
return custom[name];
|
|
993
|
+
if (isLLMProvider(name))
|
|
994
|
+
return createLLMBackend(name, this.config.llm);
|
|
995
|
+
if (hasLLMPlugin(name)) {
|
|
996
|
+
return createLLMBackendFromPlugin(name, this.config.llm?.plugins?.[name]);
|
|
632
997
|
}
|
|
633
|
-
|
|
998
|
+
throw new CogitatorError({
|
|
999
|
+
message: `Unknown LLM provider "${name}": not a built-in provider, a backend in llm.backends or a registered plugin`,
|
|
1000
|
+
code: ErrorCode.CONFIGURATION_ERROR,
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
calculateCost(model, usage) {
|
|
1004
|
+
const { model: modelName } = parseModel(model);
|
|
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
|