@cogitator-ai/core 0.23.0 → 0.25.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 +79 -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/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/response-format.d.ts +7 -2
- package/dist/cogitator/response-format.d.ts.map +1 -1
- package/dist/cogitator/response-format.js +45 -7
- package/dist/cogitator/response-format.js.map +1 -1
- 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/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 +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -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 +12 -0
- package/dist/llm/anthropic.d.ts.map +1 -1
- package/dist/llm/anthropic.js +138 -85
- package/dist/llm/anthropic.js.map +1 -1
- package/dist/llm/bedrock.d.ts.map +1 -1
- package/dist/llm/bedrock.js +136 -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/google.d.ts +12 -1
- package/dist/llm/google.d.ts.map +1 -1
- package/dist/llm/google.js +84 -12
- package/dist/llm/google.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 +26 -0
- 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/logger.d.ts.map +1 -1
- package/dist/logger.js +3 -5
- package/dist/logger.js.map +1 -1
- package/dist/runtime.d.ts +58 -3
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +594 -185
- 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/tool.d.ts +7 -0
- package/dist/tool.d.ts.map +1 -1
- package/dist/tool.js +9 -0
- package/dist/tool.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/memory-tools.d.ts +4 -4
- package/dist/tools/memory-tools.d.ts.map +1 -1
- package/dist/tools/memory-tools.js +2 -2
- package/dist/tools/memory-tools.js.map +1 -1
- package/dist/tools/scheduler-tools.d.ts +4 -4
- package/dist/tools/scheduler-tools.d.ts.map +1 -1
- package/dist/tools/scheduler-tools.js +2 -2
- package/dist/tools/scheduler-tools.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 +7 -7
package/dist/runtime.js
CHANGED
|
@@ -1,10 +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
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';
|
|
16
|
-
import {
|
|
17
|
+
import { findHandoffAgent, handoffTools } from './cogitator/handoffs.js';
|
|
18
|
+
import { PromptRegistry } from './cogitator/prompts.js';
|
|
19
|
+
import { InMemoryRunCheckpointStore, ThreadRunCheckpointStore } from './cogitator/run-checkpoints.js';
|
|
20
|
+
import { parseStructuredOutput, structuredOutputProblem, 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 = {
|
|
@@ -81,6 +89,7 @@ export class Cogitator {
|
|
|
81
89
|
};
|
|
82
90
|
costEstimator;
|
|
83
91
|
initPromise;
|
|
92
|
+
memoryInit;
|
|
84
93
|
runLimiter;
|
|
85
94
|
/**
|
|
86
95
|
* Create a new Cogitator runtime.
|
|
@@ -131,7 +140,45 @@ export class Cogitator {
|
|
|
131
140
|
* ```
|
|
132
141
|
*/
|
|
133
142
|
async run(agent, options) {
|
|
134
|
-
|
|
143
|
+
return this.execute(agent, options);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Continue a run that paused for tool approvals (`status: 'paused'`) from its
|
|
147
|
+
* `checkpoint`. Approved calls run, declined ones answer the model with the
|
|
148
|
+
* reason, and the run goes on; calls without a decision pause it again.
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```ts
|
|
152
|
+
* const paused = await cog.run(agent, { input: 'Refund order 42' });
|
|
153
|
+
* if (paused.status === 'paused') {
|
|
154
|
+
* await store.save(paused.checkpoint);
|
|
155
|
+
* // ... later, once someone approved it
|
|
156
|
+
* const result = await cog.resume(agent, checkpoint, {
|
|
157
|
+
* decisions: { [paused.pendingApprovals[0].toolCallId]: { approved: true } },
|
|
158
|
+
* });
|
|
159
|
+
* }
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
async resume(agent, target, options = {}) {
|
|
163
|
+
if (typeof target !== 'string' && target.version !== 1) {
|
|
164
|
+
throw new CogitatorError({
|
|
165
|
+
message: `Unsupported run checkpoint version: ${String(target.version)}`,
|
|
166
|
+
code: ErrorCode.VALIDATION_ERROR,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
const { decisions = {}, defaultDecision, userId, ...rest } = options;
|
|
170
|
+
const threadId = typeof target === 'string' ? target : target.threadId;
|
|
171
|
+
return this.execute(agent, { ...rest, input: '', threadId, ...(userId !== undefined && { userId }) }, {
|
|
172
|
+
...(typeof target === 'string' ? {} : { checkpoint: target }),
|
|
173
|
+
decisions,
|
|
174
|
+
...(defaultDecision && { defaultDecision }),
|
|
175
|
+
checkUser: typeof target === 'string' || userId !== undefined,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
async execute(agent, options, resumeFrom) {
|
|
179
|
+
let checkpoint = resumeFrom?.checkpoint;
|
|
180
|
+
let prompt;
|
|
181
|
+
let runId = checkpoint?.runId ?? `run_${nanoid(12)}`;
|
|
135
182
|
const threadId = options.threadId ?? `thread_${nanoid(12)}`;
|
|
136
183
|
const traceId = `trace_${nanoid(16)}`;
|
|
137
184
|
const startTime = Date.now();
|
|
@@ -167,69 +214,109 @@ export class Cogitator {
|
|
|
167
214
|
try {
|
|
168
215
|
releaseRunSlot = await this.acquireRunSlot(abortController.signal);
|
|
169
216
|
throwIfAborted(abortController.signal);
|
|
170
|
-
|
|
217
|
+
if (!checkpoint) {
|
|
218
|
+
options.onRunStart?.({ runId, agentId: agent.id, input: options.input, threadId });
|
|
219
|
+
}
|
|
171
220
|
const agentModel = this.resolveModel(agent);
|
|
172
221
|
await this.initializeAll(agentModel);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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}`);
|
|
222
|
+
if (resumeFrom) {
|
|
223
|
+
checkpoint ??= (await this.runCheckpointStore().load(threadId)) ?? undefined;
|
|
224
|
+
if (!checkpoint) {
|
|
225
|
+
throw new CogitatorError({
|
|
226
|
+
message: `Thread ${threadId} has no paused run`,
|
|
227
|
+
code: ErrorCode.RUN_NOT_PAUSED,
|
|
228
|
+
});
|
|
192
229
|
}
|
|
193
|
-
|
|
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(', ');
|
|
230
|
+
if (resumeFrom.checkUser && checkpoint.userId !== options.userId) {
|
|
204
231
|
throw new CogitatorError({
|
|
205
|
-
message: `
|
|
206
|
-
code: ErrorCode.
|
|
207
|
-
details: { threats: injectionResult.threats },
|
|
232
|
+
message: `The paused run in thread ${threadId} belongs to another user`,
|
|
233
|
+
code: ErrorCode.THREAD_ACCESS_DENIED,
|
|
208
234
|
});
|
|
209
235
|
}
|
|
236
|
+
runId = checkpoint.runId;
|
|
237
|
+
options = { ...options, userId: checkpoint.userId };
|
|
210
238
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
239
|
+
prompt = checkpoint?.prompt;
|
|
240
|
+
if (!checkpoint && (this.config.prompts || this.promptRegistry)) {
|
|
241
|
+
const resolution = await this.prompts.resolve(agent, threadId);
|
|
242
|
+
prompt = resolution.prompt;
|
|
243
|
+
if (resolution.instructions !== agent.instructions) {
|
|
244
|
+
agent = agent.clone({ id: agent.id, instructions: resolution.instructions });
|
|
215
245
|
}
|
|
216
246
|
}
|
|
217
|
-
|
|
218
|
-
|
|
247
|
+
let active = agent;
|
|
248
|
+
if (checkpoint?.activeAgent && checkpoint.activeAgent !== agent.name) {
|
|
249
|
+
const found = findHandoffAgent(agent, checkpoint.activeAgent);
|
|
250
|
+
if (!found) {
|
|
251
|
+
throw new CogitatorError({
|
|
252
|
+
message: `The paused run is in agent "${checkpoint.activeAgent}", which ${agent.name} cannot hand over to`,
|
|
253
|
+
code: ErrorCode.VALIDATION_ERROR,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
active = found;
|
|
257
|
+
}
|
|
258
|
+
const handoffs = [...(checkpoint?.handoffs ?? [])];
|
|
259
|
+
const buildRegistry = (owner) => {
|
|
260
|
+
const ownerRegistry = new ToolRegistry();
|
|
261
|
+
ownerRegistry.registerMany(this.tools.getAll());
|
|
262
|
+
if (owner.tools.length > 0)
|
|
263
|
+
ownerRegistry.registerMany(owner.tools);
|
|
264
|
+
const handoff = handoffTools(owner);
|
|
265
|
+
ownerRegistry.registerMany(handoff.tools);
|
|
266
|
+
return { registry: ownerRegistry, targets: handoff.targets };
|
|
267
|
+
};
|
|
268
|
+
let { registry, targets: handoffTargets } = buildRegistry(active);
|
|
269
|
+
let effectiveModel = agentModel;
|
|
270
|
+
let routeProvider = agent.config.provider;
|
|
271
|
+
let backend;
|
|
272
|
+
let model;
|
|
273
|
+
let input;
|
|
274
|
+
let messages;
|
|
275
|
+
if (checkpoint) {
|
|
276
|
+
input = checkpoint.input;
|
|
277
|
+
effectiveModel = checkpoint.model;
|
|
278
|
+
routeProvider = checkpoint.provider;
|
|
279
|
+
({ backend, model } = this.route(effectiveModel, routeProvider));
|
|
280
|
+
messages = [...checkpoint.messages];
|
|
219
281
|
}
|
|
220
|
-
|
|
221
|
-
options.
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
282
|
+
else {
|
|
283
|
+
input = await buildInputWithAudio(options.input, options.audio, {
|
|
284
|
+
apiKey: this.config.llm?.providers?.openai?.apiKey ??
|
|
285
|
+
(options.audio?.length ? readEnv('OPENAI_API_KEY') : undefined),
|
|
286
|
+
signal: abortController.signal,
|
|
287
|
+
});
|
|
288
|
+
const runOptions = input === options.input ? options : { ...options, input };
|
|
289
|
+
if (this.state.costRouter && this.config.costRouting?.autoSelectModel) {
|
|
290
|
+
const recommendation = await this.state.costRouter.recommendModel(input);
|
|
291
|
+
effectiveModel = `${recommendation.provider}/${recommendation.modelId}`;
|
|
292
|
+
routeProvider = recommendation.provider;
|
|
293
|
+
const budgetCheck = this.state.costRouter.checkBudget(recommendation.estimatedCost);
|
|
294
|
+
if (!budgetCheck.allowed) {
|
|
295
|
+
throw new Error(`Budget exceeded: ${budgetCheck.reason}`);
|
|
296
|
+
}
|
|
297
|
+
backend = this.getBackend(effectiveModel, recommendation.provider);
|
|
298
|
+
model = recommendation.modelId;
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
({ backend, model } = this.route(effectiveModel, agent.config.provider));
|
|
302
|
+
}
|
|
303
|
+
await this.abandonPausedRun(agent, runOptions, threadId);
|
|
304
|
+
messages = await this.prepareMessages(agent, runOptions, input, threadId);
|
|
225
305
|
}
|
|
226
|
-
const allToolCalls = [];
|
|
227
|
-
let totalInputTokens = 0;
|
|
228
|
-
let totalOutputTokens = 0;
|
|
229
|
-
let
|
|
306
|
+
const allToolCalls = [...(checkpoint?.toolCalls ?? [])];
|
|
307
|
+
let totalInputTokens = checkpoint?.usage.inputTokens ?? 0;
|
|
308
|
+
let totalOutputTokens = checkpoint?.usage.outputTokens ?? 0;
|
|
309
|
+
let cachedInputTokens = checkpoint?.usage.cachedInputTokens ?? 0;
|
|
310
|
+
let cacheWriteTokens = checkpoint?.usage.cacheWriteTokens ?? 0;
|
|
311
|
+
let reasoningTokens = checkpoint?.usage.reasoningTokens ?? 0;
|
|
312
|
+
const reasoningParts = [...(checkpoint?.reasoning ?? [])];
|
|
313
|
+
let reasoning = options.reasoning ?? active.config.reasoning;
|
|
314
|
+
const promptCache = this.config.llm?.promptCache ?? {};
|
|
315
|
+
let iterations = checkpoint?.iterations ?? 0;
|
|
230
316
|
const maxIterations = agent.config?.maxIterations ?? 10;
|
|
231
|
-
let lastToolCallSig = '';
|
|
232
|
-
|
|
317
|
+
let lastToolCallSig = checkpoint?.lastToolCallSignature ?? '';
|
|
318
|
+
let pausedTurn;
|
|
319
|
+
let responseFormat = toLLMResponseFormat(active.config.responseFormat);
|
|
233
320
|
const allReflections = [];
|
|
234
321
|
const allActions = [];
|
|
235
322
|
const agentContext = {
|
|
@@ -242,10 +329,191 @@ export class Cogitator {
|
|
|
242
329
|
previousActions: [],
|
|
243
330
|
availableTools: registry.getNames(),
|
|
244
331
|
};
|
|
245
|
-
if (this.state.reflectionEngine && this.config.reflection?.enabled) {
|
|
332
|
+
if (!checkpoint && this.state.reflectionEngine && this.config.reflection?.enabled) {
|
|
246
333
|
await enrichMessagesWithInsights(messages, this.state.reflectionEngine, agentContext);
|
|
247
334
|
}
|
|
248
|
-
|
|
335
|
+
const switchTo = (target, reason) => {
|
|
336
|
+
const event = {
|
|
337
|
+
from: active.name,
|
|
338
|
+
to: target.name,
|
|
339
|
+
...(typeof reason === 'string' && reason && { reason }),
|
|
340
|
+
};
|
|
341
|
+
handoffs.push(event);
|
|
342
|
+
options.onHandoff?.(event);
|
|
343
|
+
const system = messages[0];
|
|
344
|
+
const content = system?.role === 'system' &&
|
|
345
|
+
typeof system.content === 'string' &&
|
|
346
|
+
system.content.startsWith(active.instructions)
|
|
347
|
+
? target.instructions + system.content.slice(active.instructions.length)
|
|
348
|
+
: target.instructions;
|
|
349
|
+
if (system?.role === 'system')
|
|
350
|
+
messages[0] = { role: 'system', content };
|
|
351
|
+
else
|
|
352
|
+
messages.unshift({ role: 'system', content });
|
|
353
|
+
spans.push(createSpan('agent.handoff', traceId, rootSpanId, Date.now(), Date.now(), { 'handoff.from': event.from, 'handoff.to': event.to }, 'ok', 'internal', options.onSpan));
|
|
354
|
+
active = target;
|
|
355
|
+
({ registry, targets: handoffTargets } = buildRegistry(target));
|
|
356
|
+
effectiveModel = this.resolveModel(target);
|
|
357
|
+
routeProvider = target.config.provider;
|
|
358
|
+
({ backend, model } = this.route(effectiveModel, routeProvider));
|
|
359
|
+
reasoning = options.reasoning ?? target.config.reasoning;
|
|
360
|
+
responseFormat = toLLMResponseFormat(target.config.responseFormat);
|
|
361
|
+
lastToolCallSig = '';
|
|
362
|
+
};
|
|
363
|
+
const handleToolTurn = async (toolCalls, resumed) => {
|
|
364
|
+
if (!resumed) {
|
|
365
|
+
const currentSig = toolCalls
|
|
366
|
+
.map((tc) => `${tc.name}:${JSON.stringify(tc.arguments)}`)
|
|
367
|
+
.join('|');
|
|
368
|
+
if (currentSig === lastToolCallSig) {
|
|
369
|
+
for (const tc of toolCalls) {
|
|
370
|
+
const errorResult = {
|
|
371
|
+
callId: tc.id,
|
|
372
|
+
name: tc.name,
|
|
373
|
+
result: null,
|
|
374
|
+
error: 'Duplicate tool call detected. Try a different approach.',
|
|
375
|
+
};
|
|
376
|
+
const duplicateMessage = createToolMessage(tc, errorResult);
|
|
377
|
+
messages.push(duplicateMessage);
|
|
378
|
+
if (this.state.memoryAdapter &&
|
|
379
|
+
options.saveHistory !== false &&
|
|
380
|
+
options.useMemory !== false) {
|
|
381
|
+
await saveEntry(threadId, active.id, duplicateMessage, this.state.memoryAdapter, undefined, [errorResult], options.onMemoryError, options.userId);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
lastToolCallSig = '';
|
|
385
|
+
return undefined;
|
|
386
|
+
}
|
|
387
|
+
lastToolCallSig = currentSig;
|
|
388
|
+
for (const toolCall of toolCalls) {
|
|
389
|
+
allToolCalls.push(toolCall);
|
|
390
|
+
options.onToolCall?.(toolCall);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const decisions = new Map(Object.entries(resumed?.decisions ?? {}));
|
|
394
|
+
const pending = [];
|
|
395
|
+
for (const toolCall of toolCalls) {
|
|
396
|
+
const tool = registry.get(toolCall.name);
|
|
397
|
+
if (!tool || decisions.has(toolCall.id) || !needsApproval(tool, toolCall.arguments)) {
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
const request = {
|
|
401
|
+
toolCallId: toolCall.id,
|
|
402
|
+
toolName: toolCall.name,
|
|
403
|
+
arguments: toolCall.arguments,
|
|
404
|
+
description: tool.description,
|
|
405
|
+
...(tool.sideEffects && { sideEffects: [...tool.sideEffects] }),
|
|
406
|
+
};
|
|
407
|
+
const decision = resumed?.fallback ?? (await this.decideApproval(request, options));
|
|
408
|
+
if (decision === 'pause')
|
|
409
|
+
pending.push(request);
|
|
410
|
+
else
|
|
411
|
+
decisions.set(toolCall.id, decision);
|
|
412
|
+
}
|
|
413
|
+
if (pending.length > 0) {
|
|
414
|
+
return { toolCalls, decisions: Object.fromEntries(decisions), pending };
|
|
415
|
+
}
|
|
416
|
+
if (resumed)
|
|
417
|
+
await this.runCheckpointStore().delete(threadId);
|
|
418
|
+
const executeToolCall = async (toolCall) => {
|
|
419
|
+
const toolSpanStart = Date.now();
|
|
420
|
+
const decision = decisions.get(toolCall.id);
|
|
421
|
+
if (decision?.approved === false) {
|
|
422
|
+
const declined = {
|
|
423
|
+
callId: toolCall.id,
|
|
424
|
+
name: toolCall.name,
|
|
425
|
+
result: null,
|
|
426
|
+
error: `The user declined this tool call${decision.reason ? `: ${decision.reason}` : ''}`,
|
|
427
|
+
};
|
|
428
|
+
return { toolCall, result: declined, toolSpanStart, toolSpanEnd: Date.now() };
|
|
429
|
+
}
|
|
430
|
+
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, {
|
|
431
|
+
threadId,
|
|
432
|
+
userId: options.userId,
|
|
433
|
+
channelType: options.channelType,
|
|
434
|
+
channelId: options.channelId,
|
|
435
|
+
}, decision?.approved === true), abortController.signal);
|
|
436
|
+
const toolSpanEnd = Date.now();
|
|
437
|
+
return { toolCall, result, toolSpanStart, toolSpanEnd };
|
|
438
|
+
};
|
|
439
|
+
const toolResults = options.parallelToolCalls
|
|
440
|
+
? await Promise.all(toolCalls.map(executeToolCall))
|
|
441
|
+
: await (async () => {
|
|
442
|
+
const results = [];
|
|
443
|
+
for (const toolCall of toolCalls) {
|
|
444
|
+
results.push(await executeToolCall(toolCall));
|
|
445
|
+
}
|
|
446
|
+
return results;
|
|
447
|
+
})();
|
|
448
|
+
const reflectionMessages = [];
|
|
449
|
+
for (const { toolCall, result, toolSpanStart, toolSpanEnd } of toolResults) {
|
|
450
|
+
const toolSpan = createSpan(`tool.${toolCall.name}`, traceId, rootSpanId, toolSpanStart, toolSpanEnd, {
|
|
451
|
+
'tool.name': toolCall.name,
|
|
452
|
+
'tool.call_id': toolCall.id,
|
|
453
|
+
'tool.arguments': JSON.stringify(toolCall.arguments),
|
|
454
|
+
'tool.success': !result.error,
|
|
455
|
+
'tool.error': result.error,
|
|
456
|
+
}, result.error ? 'error' : 'ok', 'internal', options.onSpan);
|
|
457
|
+
spans.push(toolSpan);
|
|
458
|
+
options.onToolResult?.(result);
|
|
459
|
+
const toolMessage = createToolMessage(toolCall, result);
|
|
460
|
+
messages.push(toolMessage);
|
|
461
|
+
if (this.state.memoryAdapter &&
|
|
462
|
+
options.saveHistory !== false &&
|
|
463
|
+
options.useMemory !== false) {
|
|
464
|
+
await saveEntry(threadId, active.id, toolMessage, this.state.memoryAdapter, undefined, [result], options.onMemoryError, options.userId);
|
|
465
|
+
}
|
|
466
|
+
const action = {
|
|
467
|
+
type: 'tool_call',
|
|
468
|
+
toolName: toolCall.name,
|
|
469
|
+
input: toolCall.arguments,
|
|
470
|
+
output: result.result,
|
|
471
|
+
error: result.error,
|
|
472
|
+
duration: toolSpanEnd - toolSpanStart,
|
|
473
|
+
};
|
|
474
|
+
allActions.push(action);
|
|
475
|
+
const reflection = this.config.reflection;
|
|
476
|
+
const reflectOnFailure = action.error !== undefined && !!reflection?.reflectAfterError;
|
|
477
|
+
if (this.state.reflectionEngine &&
|
|
478
|
+
reflection?.enabled &&
|
|
479
|
+
(reflectOnFailure || reflection.reflectAfterToolCall)) {
|
|
480
|
+
try {
|
|
481
|
+
const reflectionResult = reflectOnFailure
|
|
482
|
+
? await this.state.reflectionEngine.reflectOnError(action, agentContext)
|
|
483
|
+
: await this.state.reflectionEngine.reflectOnToolCall(action, agentContext);
|
|
484
|
+
allReflections.push(reflectionResult.reflection);
|
|
485
|
+
if (reflectionResult.shouldAdjustStrategy && reflectionResult.suggestedAction) {
|
|
486
|
+
reflectionMessages.push({
|
|
487
|
+
role: 'system',
|
|
488
|
+
content: `Reflection: ${reflectionResult.reflection.analysis.reasoning}. Consider: ${reflectionResult.suggestedAction}`,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
catch (reflectionError) {
|
|
493
|
+
getLogger().warn('Reflection failed', {
|
|
494
|
+
error: reflectionError instanceof Error
|
|
495
|
+
? reflectionError.message
|
|
496
|
+
: String(reflectionError),
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
messages.push(...reflectionMessages);
|
|
502
|
+
const handoff = toolCalls.find((call) => handoffTargets.has(call.name));
|
|
503
|
+
const target = handoff && handoffTargets.get(handoff.name);
|
|
504
|
+
if (handoff && target)
|
|
505
|
+
switchTo(target, handoff.arguments.reason);
|
|
506
|
+
return undefined;
|
|
507
|
+
};
|
|
508
|
+
if (checkpoint && resumeFrom) {
|
|
509
|
+
pausedTurn = await handleToolTurn(checkpoint.turn.toolCalls, {
|
|
510
|
+
decisions: { ...checkpoint.turn.decisions, ...resumeFrom.decisions },
|
|
511
|
+
fallback: resumeFrom.defaultDecision,
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
const streaming = Boolean(options.stream && options.onToken);
|
|
515
|
+
let structuredRepaired = false;
|
|
516
|
+
while (!pausedTurn && iterations < maxIterations) {
|
|
249
517
|
throwIfAborted(abortController.signal);
|
|
250
518
|
this.assertTokenBudget(totalInputTokens + totalOutputTokens);
|
|
251
519
|
if (this.state.contextManager?.shouldCompress(messages, effectiveModel)) {
|
|
@@ -258,19 +526,21 @@ export class Cogitator {
|
|
|
258
526
|
agentContext.previousActions = [...allActions];
|
|
259
527
|
const llmSpanStart = Date.now();
|
|
260
528
|
let response;
|
|
261
|
-
if (
|
|
262
|
-
response = await waitForAbortable(streamChat(backend, model, messages, registry,
|
|
529
|
+
if (streaming && options.onToken) {
|
|
530
|
+
response = await waitForAbortable(streamChat(backend, model, messages, registry, active, options.onToken, abortController.signal, responseFormat, { reasoning, cache: promptCache, onReasoning: options.onReasoning }), abortController.signal);
|
|
263
531
|
}
|
|
264
532
|
else {
|
|
265
533
|
response = await waitForAbortable(backend.chat({
|
|
266
534
|
model,
|
|
267
535
|
messages,
|
|
268
536
|
tools: registry.getSchemas(),
|
|
269
|
-
temperature:
|
|
270
|
-
topP:
|
|
271
|
-
maxTokens:
|
|
272
|
-
stop:
|
|
537
|
+
temperature: active.config.temperature,
|
|
538
|
+
topP: active.config.topP,
|
|
539
|
+
maxTokens: active.config.maxTokens,
|
|
540
|
+
stop: active.config.stopSequences,
|
|
273
541
|
responseFormat,
|
|
542
|
+
reasoning,
|
|
543
|
+
cache: promptCache,
|
|
274
544
|
signal: abortController.signal,
|
|
275
545
|
}), abortController.signal);
|
|
276
546
|
}
|
|
@@ -279,11 +549,22 @@ export class Cogitator {
|
|
|
279
549
|
'llm.iteration': iterations,
|
|
280
550
|
'llm.input_tokens': response.usage.inputTokens,
|
|
281
551
|
'llm.output_tokens': response.usage.outputTokens,
|
|
552
|
+
...(response.usage.cachedInputTokens && {
|
|
553
|
+
'llm.cached_input_tokens': response.usage.cachedInputTokens,
|
|
554
|
+
}),
|
|
555
|
+
...(response.usage.reasoningTokens && {
|
|
556
|
+
'llm.reasoning_tokens': response.usage.reasoningTokens,
|
|
557
|
+
}),
|
|
282
558
|
'llm.finish_reason': response.finishReason,
|
|
283
559
|
}, 'ok', 'client', options.onSpan);
|
|
284
560
|
spans.push(llmSpan);
|
|
285
561
|
totalInputTokens += response.usage.inputTokens;
|
|
286
562
|
totalOutputTokens += response.usage.outputTokens;
|
|
563
|
+
cachedInputTokens += response.usage.cachedInputTokens ?? 0;
|
|
564
|
+
cacheWriteTokens += response.usage.cacheWriteTokens ?? 0;
|
|
565
|
+
reasoningTokens += response.usage.reasoningTokens ?? 0;
|
|
566
|
+
if (response.reasoning)
|
|
567
|
+
reasoningParts.push(response.reasoning);
|
|
287
568
|
let outputContent = response.content;
|
|
288
569
|
if (this.state.constitutionalAI?.config.filterOutput) {
|
|
289
570
|
const outputResult = await this.state.constitutionalAI.filterOutput(outputContent, messages);
|
|
@@ -304,125 +585,93 @@ export class Cogitator {
|
|
|
304
585
|
}
|
|
305
586
|
: { role: 'assistant', content: outputContent };
|
|
306
587
|
messages.push(assistantMessage);
|
|
588
|
+
const finalAnswer = !(response.finishReason === 'tool_calls' &&
|
|
589
|
+
response.toolCalls &&
|
|
590
|
+
response.toolCalls.length > 0);
|
|
591
|
+
const structuredProblem = finalAnswer && !streaming && !structuredRepaired && iterations < maxIterations
|
|
592
|
+
? structuredOutputProblem(active.config.responseFormat, outputContent)
|
|
593
|
+
: undefined;
|
|
594
|
+
if (structuredProblem) {
|
|
595
|
+
structuredRepaired = true;
|
|
596
|
+
messages.push({
|
|
597
|
+
role: 'user',
|
|
598
|
+
content: `Your answer does not match the required response format (${structuredProblem}). Reply again with only the corrected JSON.`,
|
|
599
|
+
});
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
307
602
|
if (this.state.memoryAdapter &&
|
|
308
603
|
options.saveHistory !== false &&
|
|
309
604
|
options.useMemory !== false) {
|
|
310
|
-
await saveEntry(threadId,
|
|
311
|
-
}
|
|
312
|
-
if (response.finishReason === 'tool_calls' &&
|
|
313
|
-
response.toolCalls &&
|
|
314
|
-
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);
|
|
605
|
+
await saveEntry(threadId, active.id, assistantMessage, this.state.memoryAdapter, response.toolCalls, undefined, options.onMemoryError, options.userId);
|
|
417
606
|
}
|
|
418
|
-
|
|
607
|
+
if (finalAnswer || !response.toolCalls)
|
|
419
608
|
break;
|
|
420
|
-
|
|
609
|
+
pausedTurn = await handleToolTurn(response.toolCalls);
|
|
610
|
+
if (pausedTurn)
|
|
611
|
+
break;
|
|
612
|
+
}
|
|
613
|
+
if (pausedTurn) {
|
|
614
|
+
const pausedCheckpoint = {
|
|
615
|
+
version: 1,
|
|
616
|
+
runId,
|
|
617
|
+
agentId: active.id,
|
|
618
|
+
threadId,
|
|
619
|
+
...(options.userId !== undefined && { userId: options.userId }),
|
|
620
|
+
model: effectiveModel,
|
|
621
|
+
...(routeProvider !== undefined && { provider: routeProvider }),
|
|
622
|
+
input,
|
|
623
|
+
messages,
|
|
624
|
+
toolCalls: allToolCalls,
|
|
625
|
+
...(prompt && { prompt }),
|
|
626
|
+
...(active !== agent && { activeAgent: active.name }),
|
|
627
|
+
...(handoffs.length > 0 && { handoffs }),
|
|
628
|
+
turn: { toolCalls: pausedTurn.toolCalls, decisions: pausedTurn.decisions },
|
|
629
|
+
iterations,
|
|
630
|
+
lastToolCallSignature: lastToolCallSig,
|
|
631
|
+
usage: {
|
|
632
|
+
inputTokens: totalInputTokens,
|
|
633
|
+
outputTokens: totalOutputTokens,
|
|
634
|
+
cachedInputTokens,
|
|
635
|
+
cacheWriteTokens,
|
|
636
|
+
reasoningTokens,
|
|
637
|
+
},
|
|
638
|
+
reasoning: reasoningParts,
|
|
639
|
+
startedAt: checkpoint?.startedAt ?? startTime,
|
|
640
|
+
};
|
|
641
|
+
const paused = {
|
|
642
|
+
output: getTextContent(messages[messages.length - 1]?.content ?? ''),
|
|
643
|
+
runId,
|
|
644
|
+
agentId: agent.id,
|
|
645
|
+
threadId,
|
|
646
|
+
status: 'paused',
|
|
647
|
+
pendingApprovals: pausedTurn.pending,
|
|
648
|
+
...(handoffs.length > 0 && { handoffs, finalAgent: active.name }),
|
|
649
|
+
checkpoint: pausedCheckpoint,
|
|
650
|
+
usage: {
|
|
651
|
+
inputTokens: totalInputTokens,
|
|
652
|
+
outputTokens: totalOutputTokens,
|
|
653
|
+
totalTokens: totalInputTokens + totalOutputTokens,
|
|
654
|
+
cost: this.calculateCost(effectiveModel, {
|
|
655
|
+
inputTokens: totalInputTokens,
|
|
656
|
+
outputTokens: totalOutputTokens,
|
|
657
|
+
cachedInputTokens,
|
|
658
|
+
cacheWriteTokens,
|
|
659
|
+
}),
|
|
660
|
+
duration: Date.now() - startTime,
|
|
661
|
+
},
|
|
662
|
+
...(reasoningParts.length > 0 && { reasoning: reasoningParts.join('\n\n') }),
|
|
663
|
+
toolCalls: allToolCalls,
|
|
664
|
+
messages,
|
|
665
|
+
trace: { traceId, spans },
|
|
666
|
+
};
|
|
667
|
+
await this.runCheckpointStore().save(pausedCheckpoint);
|
|
668
|
+
options.onRunComplete?.(paused);
|
|
669
|
+
return paused;
|
|
421
670
|
}
|
|
422
671
|
const endTime = Date.now();
|
|
423
672
|
const lastAssistantMessage = messages.filter((m) => m.role === 'assistant').pop();
|
|
424
673
|
const finalOutput = lastAssistantMessage ? getTextContent(lastAssistantMessage.content) : '';
|
|
425
|
-
const structured = parseStructuredOutput(
|
|
674
|
+
const structured = parseStructuredOutput(active.config.responseFormat, finalOutput);
|
|
426
675
|
if (this.state.reflectionEngine &&
|
|
427
676
|
this.config.reflection?.enabled &&
|
|
428
677
|
this.config.reflection.reflectAtEnd) {
|
|
@@ -448,7 +697,12 @@ export class Cogitator {
|
|
|
448
697
|
'run.output_tokens': totalOutputTokens,
|
|
449
698
|
}, 'ok', 'server', options.onSpan, rootSpanId);
|
|
450
699
|
spans.unshift(rootSpan);
|
|
451
|
-
const runCost = this.calculateCost(effectiveModel,
|
|
700
|
+
const runCost = this.calculateCost(effectiveModel, {
|
|
701
|
+
inputTokens: totalInputTokens,
|
|
702
|
+
outputTokens: totalOutputTokens,
|
|
703
|
+
cachedInputTokens,
|
|
704
|
+
cacheWriteTokens,
|
|
705
|
+
});
|
|
452
706
|
if (this.state.costRouter) {
|
|
453
707
|
this.state.costRouter.recordCost({
|
|
454
708
|
runId,
|
|
@@ -463,6 +717,9 @@ export class Cogitator {
|
|
|
463
717
|
const result = {
|
|
464
718
|
output: finalOutput,
|
|
465
719
|
...(structured !== undefined && { structured }),
|
|
720
|
+
status: 'completed',
|
|
721
|
+
...(prompt && { prompt }),
|
|
722
|
+
...(handoffs.length > 0 && { handoffs, finalAgent: active.name }),
|
|
466
723
|
runId,
|
|
467
724
|
agentId: agent.id,
|
|
468
725
|
threadId,
|
|
@@ -473,7 +730,11 @@ export class Cogitator {
|
|
|
473
730
|
totalTokens: totalInputTokens + totalOutputTokens,
|
|
474
731
|
cost: runCost,
|
|
475
732
|
duration: endTime - startTime,
|
|
733
|
+
...(reasoningTokens > 0 && { reasoningTokens }),
|
|
734
|
+
...(cachedInputTokens > 0 && { cachedInputTokens }),
|
|
735
|
+
...(cacheWriteTokens > 0 && { cacheWriteTokens }),
|
|
476
736
|
},
|
|
737
|
+
...(reasoningParts.length > 0 && { reasoning: reasoningParts.join('\n\n') }),
|
|
477
738
|
toolCalls: allToolCalls,
|
|
478
739
|
messages,
|
|
479
740
|
trace: {
|
|
@@ -485,10 +746,14 @@ export class Cogitator {
|
|
|
485
746
|
? await this.state.reflectionEngine.getSummary(agent.id)
|
|
486
747
|
: undefined,
|
|
487
748
|
};
|
|
749
|
+
if (prompt)
|
|
750
|
+
await this.recordPrompt(prompt, result, Date.now() - startTime);
|
|
488
751
|
options.onRunComplete?.(result);
|
|
489
752
|
return result;
|
|
490
753
|
}
|
|
491
754
|
catch (error) {
|
|
755
|
+
if (prompt)
|
|
756
|
+
await this.recordPrompt(prompt, undefined, Date.now() - startTime);
|
|
492
757
|
const endTime = Date.now();
|
|
493
758
|
const errorSpan = createSpan('agent.run', traceId, undefined, startTime, endTime, {
|
|
494
759
|
'agent.id': agent.id,
|
|
@@ -509,6 +774,129 @@ export class Cogitator {
|
|
|
509
774
|
removeParentAbortListener?.();
|
|
510
775
|
}
|
|
511
776
|
}
|
|
777
|
+
/**
|
|
778
|
+
* The messages a new run starts with: the thread's history and the input,
|
|
779
|
+
* once the input passed the injection and constitutional checks, with the
|
|
780
|
+
* input saved to memory.
|
|
781
|
+
*/
|
|
782
|
+
async prepareMessages(agent, options, input, threadId) {
|
|
783
|
+
const messages = await buildInitialMessages(agent, options, threadId, this.state.memoryAdapter, this.state.contextBuilder);
|
|
784
|
+
const pii = this.config.security?.pii;
|
|
785
|
+
if (pii?.mode === 'block') {
|
|
786
|
+
const found = new PiiMasker(pii).find(input);
|
|
787
|
+
if (found.length > 0) {
|
|
788
|
+
throw new CogitatorError({
|
|
789
|
+
message: `The input contains personal data: ${[...new Set(found.map((f) => f.type))].join(', ')}`,
|
|
790
|
+
code: ErrorCode.PII_DETECTED,
|
|
791
|
+
details: { types: [...new Set(found.map((f) => f.type))] },
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
if (this.state.injectionDetector) {
|
|
796
|
+
const injectionResult = await this.state.injectionDetector.analyze(input);
|
|
797
|
+
if (injectionResult.action === 'blocked') {
|
|
798
|
+
const threatTypes = injectionResult.threats.map((t) => t.type).join(', ');
|
|
799
|
+
throw new CogitatorError({
|
|
800
|
+
message: `Prompt injection detected: ${threatTypes}`,
|
|
801
|
+
code: ErrorCode.PROMPT_INJECTION_DETECTED,
|
|
802
|
+
details: { threats: injectionResult.threats },
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
if (this.state.constitutionalAI?.config.filterInput) {
|
|
807
|
+
const inputResult = await this.state.constitutionalAI.filterInput(input);
|
|
808
|
+
if (!inputResult.allowed) {
|
|
809
|
+
throw new Error(`Input blocked: ${inputResult.blockedReason ?? 'Policy violation'}`);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
if (options.context) {
|
|
813
|
+
addContextToMessages(messages, options.context);
|
|
814
|
+
}
|
|
815
|
+
if (this.state.memoryAdapter && options.saveHistory !== false && options.useMemory !== false) {
|
|
816
|
+
const currentUserMessage = messages[messages.length - 1];
|
|
817
|
+
await saveEntry(threadId, agent.id, currentUserMessage, this.state.memoryAdapter, undefined, undefined, options.onMemoryError, options.userId);
|
|
818
|
+
}
|
|
819
|
+
return messages;
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Versioned instructions and A/B tests of this Cogitator's agents
|
|
823
|
+
* (`prompts` config, in process memory by default).
|
|
824
|
+
*/
|
|
825
|
+
get prompts() {
|
|
826
|
+
this.promptRegistry ??= new PromptRegistry(this.config.prompts);
|
|
827
|
+
return this.promptRegistry;
|
|
828
|
+
}
|
|
829
|
+
async recordPrompt(prompt, result, durationMs) {
|
|
830
|
+
try {
|
|
831
|
+
await this.prompts.record(prompt, { result, durationMs });
|
|
832
|
+
}
|
|
833
|
+
catch (error) {
|
|
834
|
+
getLogger().warn('Could not record the outcome of a run against its instructions', {
|
|
835
|
+
prompt: prompt.key,
|
|
836
|
+
error: error instanceof Error ? error.message : String(error),
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
runCheckpointStore() {
|
|
841
|
+
if (this.config.runCheckpoints)
|
|
842
|
+
return this.config.runCheckpoints;
|
|
843
|
+
const memory = this.state.memoryAdapter;
|
|
844
|
+
if (memory) {
|
|
845
|
+
if (this.threadCheckpoints?.memory !== memory) {
|
|
846
|
+
this.threadCheckpoints = { memory, store: new ThreadRunCheckpointStore(memory) };
|
|
847
|
+
}
|
|
848
|
+
return this.threadCheckpoints.store;
|
|
849
|
+
}
|
|
850
|
+
this.processCheckpoints ??= new InMemoryRunCheckpointStore();
|
|
851
|
+
return this.processCheckpoints;
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* A new run on a thread whose run is waiting for approvals means the user
|
|
855
|
+
* moved on: the waiting calls are answered as declined, so the thread's
|
|
856
|
+
* history stays whole, and the pause is dropped.
|
|
857
|
+
*/
|
|
858
|
+
async abandonPausedRun(agent, options, threadId) {
|
|
859
|
+
if (options.threadId === undefined)
|
|
860
|
+
return;
|
|
861
|
+
const store = this.runCheckpointStore();
|
|
862
|
+
const paused = await store.load(threadId).catch((error) => {
|
|
863
|
+
getLogger().warn('Could not read the paused run of a thread', {
|
|
864
|
+
threadId,
|
|
865
|
+
error: error instanceof Error ? error.message : String(error),
|
|
866
|
+
});
|
|
867
|
+
return null;
|
|
868
|
+
});
|
|
869
|
+
if (!paused)
|
|
870
|
+
return;
|
|
871
|
+
if (options.threadAccess !== 'shared' && paused.userId !== options.userId)
|
|
872
|
+
return;
|
|
873
|
+
if (this.state.memoryAdapter && options.useMemory !== false && options.saveHistory !== false) {
|
|
874
|
+
for (const toolCall of paused.turn.toolCalls) {
|
|
875
|
+
const result = {
|
|
876
|
+
callId: toolCall.id,
|
|
877
|
+
name: toolCall.name,
|
|
878
|
+
result: null,
|
|
879
|
+
error: 'The user moved on without approving this tool call',
|
|
880
|
+
};
|
|
881
|
+
await saveEntry(threadId, agent.id, createToolMessage(toolCall, result), this.state.memoryAdapter, undefined, [result], options.onMemoryError, options.userId);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
await store.delete(threadId);
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* The decision for a call that needs approval: the run's `onApproval`, else
|
|
888
|
+
* `guardrails.onToolApproval`, else a pause.
|
|
889
|
+
*/
|
|
890
|
+
async decideApproval(request, options) {
|
|
891
|
+
if (options.onApproval)
|
|
892
|
+
return options.onApproval(request);
|
|
893
|
+
const legacy = this.config.guardrails?.onToolApproval;
|
|
894
|
+
if (legacy) {
|
|
895
|
+
const approved = await legacy(request.toolName, request.arguments, request.sideEffects ?? []);
|
|
896
|
+
return approved ? { approved: true } : { approved: false };
|
|
897
|
+
}
|
|
898
|
+
return 'pause';
|
|
899
|
+
}
|
|
512
900
|
async acquireRunSlot(signal) {
|
|
513
901
|
const max = this.config.limits?.maxConcurrentRuns;
|
|
514
902
|
if (max === undefined)
|
|
@@ -551,9 +939,7 @@ export class Cogitator {
|
|
|
551
939
|
await this.initPromise;
|
|
552
940
|
}
|
|
553
941
|
async _doInitializeAll(agentModel) {
|
|
554
|
-
|
|
555
|
-
await initializeMemory(this.config, this.state);
|
|
556
|
-
}
|
|
942
|
+
await this.getMemory();
|
|
557
943
|
if (this.config.reflection?.enabled && !this.state.reflectionInitialized) {
|
|
558
944
|
await initializeReflection(this.config, this.state, agentModel, (model) => this.route(model));
|
|
559
945
|
}
|
|
@@ -606,7 +992,7 @@ export class Cogitator {
|
|
|
606
992
|
const cached = this.backends.get(name);
|
|
607
993
|
if (cached)
|
|
608
994
|
return cached;
|
|
609
|
-
const backend = withLLMRetry(this.createBackend(name), this.config.llm?.retry);
|
|
995
|
+
const backend = withPiiMasking(withLLMRetry(this.createBackend(name), this.config.llm?.retry), this.config.security?.pii);
|
|
610
996
|
this.backends.set(name, backend);
|
|
611
997
|
return backend;
|
|
612
998
|
}
|
|
@@ -624,13 +1010,9 @@ export class Cogitator {
|
|
|
624
1010
|
code: ErrorCode.CONFIGURATION_ERROR,
|
|
625
1011
|
});
|
|
626
1012
|
}
|
|
627
|
-
calculateCost(model,
|
|
1013
|
+
calculateCost(model, usage) {
|
|
628
1014
|
const { model: modelName } = parseModel(model);
|
|
629
|
-
|
|
630
|
-
if (!price) {
|
|
631
|
-
return 0;
|
|
632
|
-
}
|
|
633
|
-
return (inputTokens * price.input + outputTokens * price.output) / 1_000_000;
|
|
1015
|
+
return calculateModelCost(modelName, usage) ?? 0;
|
|
634
1016
|
}
|
|
635
1017
|
/**
|
|
636
1018
|
* Get accumulated insights from reflection for an agent.
|
|
@@ -736,8 +1118,23 @@ export class Cogitator {
|
|
|
736
1118
|
});
|
|
737
1119
|
}
|
|
738
1120
|
/**
|
|
739
|
-
*
|
|
740
|
-
*
|
|
1121
|
+
* The memory adapter, connecting it on first use when `memory` is
|
|
1122
|
+
* configured, so threads can be read before any agent has run. `undefined`
|
|
1123
|
+
* when memory is not configured or could not connect (it is tried again
|
|
1124
|
+
* on the next call).
|
|
1125
|
+
*/
|
|
1126
|
+
async getMemory() {
|
|
1127
|
+
if (!this.state.memoryInitialized && this.config.memory?.adapter) {
|
|
1128
|
+
this.memoryInit ??= initializeMemory(this.config, this.state).finally(() => {
|
|
1129
|
+
this.memoryInit = undefined;
|
|
1130
|
+
});
|
|
1131
|
+
await this.memoryInit;
|
|
1132
|
+
}
|
|
1133
|
+
return this.state.memoryAdapter;
|
|
1134
|
+
}
|
|
1135
|
+
/**
|
|
1136
|
+
* The memory adapter once connected: by a run, by {@link getMemory}, or
|
|
1137
|
+
* set here. `undefined` before then — use `getMemory()` to connect it.
|
|
741
1138
|
*/
|
|
742
1139
|
get memory() {
|
|
743
1140
|
return this.state.memoryAdapter;
|
|
@@ -803,4 +1200,16 @@ function waitForAbortable(promise, signal) {
|
|
|
803
1200
|
});
|
|
804
1201
|
});
|
|
805
1202
|
}
|
|
1203
|
+
/** Whether a call to `tool` with `args` needs approval; a check that throws counts as yes. */
|
|
1204
|
+
function needsApproval(tool, args) {
|
|
1205
|
+
const check = tool.requiresApproval;
|
|
1206
|
+
if (typeof check !== 'function')
|
|
1207
|
+
return check === true;
|
|
1208
|
+
try {
|
|
1209
|
+
return check(args);
|
|
1210
|
+
}
|
|
1211
|
+
catch {
|
|
1212
|
+
return true;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
806
1215
|
//# sourceMappingURL=runtime.js.map
|