@tuttiai/core 0.7.0 → 0.8.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/dist/index.d.ts +97 -4
- package/dist/index.js +440 -105
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,189 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var TuttiError = class extends Error {
|
|
3
|
+
constructor(code, message, context = {}) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.code = code;
|
|
6
|
+
this.context = context;
|
|
7
|
+
this.name = this.constructor.name;
|
|
8
|
+
Error.captureStackTrace(this, this.constructor);
|
|
9
|
+
}
|
|
10
|
+
code;
|
|
11
|
+
context;
|
|
12
|
+
};
|
|
13
|
+
var ScoreValidationError = class extends TuttiError {
|
|
14
|
+
constructor(message, context = {}) {
|
|
15
|
+
super("SCORE_INVALID", message, context);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
var AgentNotFoundError = class extends TuttiError {
|
|
19
|
+
constructor(agentId, available) {
|
|
20
|
+
super(
|
|
21
|
+
"AGENT_NOT_FOUND",
|
|
22
|
+
`Agent "${agentId}" not found in your score.
|
|
23
|
+
Available agents: ${available.join(", ")}
|
|
24
|
+
Check your tutti.score.ts \u2014 the agent ID must match the key in the agents object.`,
|
|
25
|
+
{ agent_id: agentId, available }
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var PermissionError = class extends TuttiError {
|
|
30
|
+
constructor(voice, required, granted) {
|
|
31
|
+
const missing = required.filter((p) => !granted.includes(p));
|
|
32
|
+
super(
|
|
33
|
+
"PERMISSION_DENIED",
|
|
34
|
+
`Voice "${voice}" requires permissions not granted: ${missing.join(", ")}
|
|
35
|
+
Grant them in your score file:
|
|
36
|
+
permissions: [${missing.map((p) => "'" + p + "'").join(", ")}]`,
|
|
37
|
+
{ voice, required, granted }
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
var BudgetExceededError = class extends TuttiError {
|
|
42
|
+
constructor(tokens, costUsd, limit) {
|
|
43
|
+
super(
|
|
44
|
+
"BUDGET_EXCEEDED",
|
|
45
|
+
`Token budget exceeded: ${tokens.toLocaleString()} tokens, $${costUsd.toFixed(4)} (limit: ${limit}).`,
|
|
46
|
+
{ tokens, cost_usd: costUsd, limit }
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
var ToolTimeoutError = class extends TuttiError {
|
|
51
|
+
constructor(tool, timeoutMs) {
|
|
52
|
+
super(
|
|
53
|
+
"TOOL_TIMEOUT",
|
|
54
|
+
`Tool "${tool}" timed out after ${timeoutMs}ms.
|
|
55
|
+
Increase tool_timeout_ms in your agent config, or check if the tool is hanging.`,
|
|
56
|
+
{ tool, timeout_ms: timeoutMs }
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var ProviderError = class extends TuttiError {
|
|
61
|
+
constructor(message, context = { provider: "unknown" }) {
|
|
62
|
+
super("PROVIDER_ERROR", message, context);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
var AuthenticationError = class extends ProviderError {
|
|
66
|
+
constructor(provider) {
|
|
67
|
+
super(
|
|
68
|
+
`Authentication failed for ${provider}.
|
|
69
|
+
Check that the API key is set correctly in your .env file.`,
|
|
70
|
+
{ provider }
|
|
71
|
+
);
|
|
72
|
+
Object.defineProperty(this, "code", { value: "AUTH_ERROR" });
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
var RateLimitError = class extends ProviderError {
|
|
76
|
+
retryAfter;
|
|
77
|
+
constructor(provider, retryAfter) {
|
|
78
|
+
const msg = retryAfter ? `Rate limited by ${provider}. Retry after ${retryAfter}s.` : `Rate limited by ${provider}.`;
|
|
79
|
+
super(msg, { provider, retryAfter });
|
|
80
|
+
Object.defineProperty(this, "code", { value: "RATE_LIMIT" });
|
|
81
|
+
this.retryAfter = retryAfter;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
var ContextWindowError = class extends ProviderError {
|
|
85
|
+
maxTokens;
|
|
86
|
+
constructor(provider, maxTokens) {
|
|
87
|
+
super(
|
|
88
|
+
`Context window exceeded for ${provider}.` + (maxTokens ? ` Max: ${maxTokens.toLocaleString()} tokens.` : "") + `
|
|
89
|
+
Reduce message history or use a model with a larger context window.`,
|
|
90
|
+
{ provider, max_tokens: maxTokens }
|
|
91
|
+
);
|
|
92
|
+
Object.defineProperty(this, "code", { value: "CONTEXT_WINDOW" });
|
|
93
|
+
this.maxTokens = maxTokens;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
var VoiceError = class extends TuttiError {
|
|
97
|
+
constructor(message, context) {
|
|
98
|
+
super("VOICE_ERROR", message, context);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
var PathTraversalError = class extends VoiceError {
|
|
102
|
+
constructor(path) {
|
|
103
|
+
super(
|
|
104
|
+
`Path traversal detected: "${path}" is not allowed.
|
|
105
|
+
All file paths must stay within the allowed directory.`,
|
|
106
|
+
{ voice: "filesystem", path }
|
|
107
|
+
);
|
|
108
|
+
Object.defineProperty(this, "code", { value: "PATH_TRAVERSAL" });
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
var UrlValidationError = class extends VoiceError {
|
|
112
|
+
constructor(url) {
|
|
113
|
+
super(
|
|
114
|
+
`URL blocked: "${url}".
|
|
115
|
+
Only http:// and https:// URLs to public hosts are allowed.`,
|
|
116
|
+
{ voice: "playwright", url }
|
|
117
|
+
);
|
|
118
|
+
Object.defineProperty(this, "code", { value: "URL_BLOCKED" });
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// src/hooks/index.ts
|
|
123
|
+
function createLoggingHook(log) {
|
|
124
|
+
return {
|
|
125
|
+
async beforeLLMCall(ctx, request) {
|
|
126
|
+
log.info({ agent: ctx.agent_name, turn: ctx.turn, model: request.model }, "LLM call");
|
|
127
|
+
return request;
|
|
128
|
+
},
|
|
129
|
+
async afterLLMCall(ctx, response) {
|
|
130
|
+
log.info({ agent: ctx.agent_name, turn: ctx.turn, usage: response.usage }, "LLM response");
|
|
131
|
+
},
|
|
132
|
+
async beforeToolCall(ctx, tool, input) {
|
|
133
|
+
log.info({ agent: ctx.agent_name, tool, input }, "Tool call");
|
|
134
|
+
return input;
|
|
135
|
+
},
|
|
136
|
+
async afterToolCall(ctx, tool, result) {
|
|
137
|
+
log.info({ agent: ctx.agent_name, tool, is_error: result.is_error }, "Tool result");
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function createCacheHook(store) {
|
|
143
|
+
function cacheKey(tool, input) {
|
|
144
|
+
return tool + ":" + JSON.stringify(input);
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
async beforeToolCall(_ctx, tool, input) {
|
|
148
|
+
const cached = store.get(cacheKey(tool, input));
|
|
149
|
+
if (cached) return cached;
|
|
150
|
+
return input;
|
|
151
|
+
},
|
|
152
|
+
async afterToolCall(_ctx, tool, result) {
|
|
153
|
+
if (!result.is_error) {
|
|
154
|
+
store.set(cacheKey(tool, result.content), result.content);
|
|
155
|
+
}
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function createBlocklistHook(blockedTools) {
|
|
161
|
+
const blocked = new Set(blockedTools);
|
|
162
|
+
return {
|
|
163
|
+
async beforeToolCall(_ctx, tool) {
|
|
164
|
+
return !blocked.has(tool);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function createMaxCostHook(maxUsd) {
|
|
169
|
+
let totalCost = 0;
|
|
170
|
+
const INPUT_PER_M = 3;
|
|
171
|
+
const OUTPUT_PER_M = 15;
|
|
172
|
+
return {
|
|
173
|
+
async afterLLMCall(_ctx, response) {
|
|
174
|
+
totalCost += response.usage.input_tokens / 1e6 * INPUT_PER_M + response.usage.output_tokens / 1e6 * OUTPUT_PER_M;
|
|
175
|
+
},
|
|
176
|
+
async beforeLLMCall(ctx, request) {
|
|
177
|
+
if (totalCost >= maxUsd) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
"Max cost hook: $" + totalCost.toFixed(4) + " exceeds limit $" + maxUsd.toFixed(2) + " for agent " + ctx.agent_name
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
return request;
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
1
187
|
// src/logger.ts
|
|
2
188
|
import pino from "pino";
|
|
3
189
|
var createLogger = (name) => pino({
|
|
@@ -103,6 +289,7 @@ async function shutdownTelemetry() {
|
|
|
103
289
|
}
|
|
104
290
|
|
|
105
291
|
// src/agent-runner.ts
|
|
292
|
+
import { z } from "zod";
|
|
106
293
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
107
294
|
|
|
108
295
|
// src/secrets.ts
|
|
@@ -232,17 +419,63 @@ var TokenBudget = class {
|
|
|
232
419
|
var DEFAULT_MAX_TURNS = 10;
|
|
233
420
|
var DEFAULT_MAX_TOOL_CALLS = 20;
|
|
234
421
|
var DEFAULT_TOOL_TIMEOUT_MS = 3e4;
|
|
422
|
+
var DEFAULT_HITL_TIMEOUT_S = 300;
|
|
423
|
+
var MAX_PROVIDER_RETRIES = 3;
|
|
424
|
+
var hitlRequestSchema = z.object({
|
|
425
|
+
question: z.string().describe("The question to ask the human"),
|
|
426
|
+
options: z.array(z.string()).optional().describe("If provided, the human picks one of these"),
|
|
427
|
+
timeout_seconds: z.number().optional().describe("How long to wait before timing out (default 300)")
|
|
428
|
+
});
|
|
429
|
+
async function withRetry(fn) {
|
|
430
|
+
for (let attempt = 1; ; attempt++) {
|
|
431
|
+
try {
|
|
432
|
+
return await fn();
|
|
433
|
+
} catch (err) {
|
|
434
|
+
if (attempt >= MAX_PROVIDER_RETRIES || !(err instanceof ProviderError)) {
|
|
435
|
+
throw err;
|
|
436
|
+
}
|
|
437
|
+
if (err instanceof RateLimitError && err.retryAfter) {
|
|
438
|
+
logger.warn({ attempt, retryAfter: err.retryAfter }, "Rate limited, waiting before retry");
|
|
439
|
+
await new Promise((r) => setTimeout(r, err.retryAfter * 1e3));
|
|
440
|
+
} else {
|
|
441
|
+
const delayMs = Math.min(1e3 * 2 ** (attempt - 1), 8e3);
|
|
442
|
+
logger.warn({ attempt, delayMs }, "Provider error, retrying with backoff");
|
|
443
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
235
448
|
var AgentRunner = class {
|
|
236
|
-
constructor(provider, events, sessions, semanticMemory) {
|
|
449
|
+
constructor(provider, events, sessions, semanticMemory, globalHooks) {
|
|
237
450
|
this.provider = provider;
|
|
238
451
|
this.events = events;
|
|
239
452
|
this.sessions = sessions;
|
|
240
453
|
this.semanticMemory = semanticMemory;
|
|
454
|
+
this.globalHooks = globalHooks;
|
|
241
455
|
}
|
|
242
456
|
provider;
|
|
243
457
|
events;
|
|
244
458
|
sessions;
|
|
245
459
|
semanticMemory;
|
|
460
|
+
globalHooks;
|
|
461
|
+
pendingHitl = /* @__PURE__ */ new Map();
|
|
462
|
+
async safeHook(fn) {
|
|
463
|
+
if (!fn) return void 0;
|
|
464
|
+
try {
|
|
465
|
+
return await fn() ?? void 0;
|
|
466
|
+
} catch (err) {
|
|
467
|
+
logger.warn({ error: err instanceof Error ? err.message : String(err) }, "Hook error (non-fatal)");
|
|
468
|
+
return void 0;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
/** Resolve a pending human-in-the-loop request for a session. */
|
|
472
|
+
answer(sessionId, answer) {
|
|
473
|
+
const resolve2 = this.pendingHitl.get(sessionId);
|
|
474
|
+
if (resolve2) {
|
|
475
|
+
this.pendingHitl.delete(sessionId);
|
|
476
|
+
resolve2(answer);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
246
479
|
async run(agent, input, session_id) {
|
|
247
480
|
const session = session_id ? this.sessions.get(session_id) : this.sessions.create(agent.name);
|
|
248
481
|
if (!session) {
|
|
@@ -253,13 +486,31 @@ Omit session_id to start a new conversation.`
|
|
|
253
486
|
);
|
|
254
487
|
}
|
|
255
488
|
return TuttiTracer.agentRun(agent.name, session.id, async () => {
|
|
489
|
+
const agentHooks = agent.hooks;
|
|
490
|
+
const hookCtx = {
|
|
491
|
+
agent_name: agent.name,
|
|
492
|
+
session_id: session.id,
|
|
493
|
+
turn: 0,
|
|
494
|
+
metadata: {}
|
|
495
|
+
};
|
|
496
|
+
await this.safeHook(() => this.globalHooks?.beforeAgentRun?.(hookCtx));
|
|
497
|
+
await this.safeHook(() => agentHooks?.beforeAgentRun?.(hookCtx));
|
|
256
498
|
logger.info({ agent: agent.name, session: session.id }, "Agent started");
|
|
257
499
|
this.events.emit({
|
|
258
500
|
type: "agent:start",
|
|
259
501
|
agent_name: agent.name,
|
|
260
502
|
session_id: session.id
|
|
261
503
|
});
|
|
262
|
-
const
|
|
504
|
+
const voiceCtx = { session_id: session.id, agent_name: agent.name };
|
|
505
|
+
for (const voice of agent.voices) {
|
|
506
|
+
if (voice.setup) {
|
|
507
|
+
await voice.setup(voiceCtx);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
const allTools = [...agent.voices.flatMap((v) => v.tools)];
|
|
511
|
+
if (agent.allow_human_input) {
|
|
512
|
+
allTools.push(this.createHitlTool(agent.name, session.id));
|
|
513
|
+
}
|
|
263
514
|
const toolDefs = allTools.map(toolToDefinition);
|
|
264
515
|
const messages = [
|
|
265
516
|
...session.messages,
|
|
@@ -297,12 +548,17 @@ Omit session_id to start a new conversation.`
|
|
|
297
548
|
}
|
|
298
549
|
}
|
|
299
550
|
}
|
|
300
|
-
|
|
551
|
+
let request = {
|
|
301
552
|
model: agent.model,
|
|
302
553
|
system: systemPrompt,
|
|
303
554
|
messages,
|
|
304
555
|
tools: toolDefs.length > 0 ? toolDefs : void 0
|
|
305
556
|
};
|
|
557
|
+
hookCtx.turn = turns;
|
|
558
|
+
const globalReq = await this.safeHook(() => this.globalHooks?.beforeLLMCall?.(hookCtx, request));
|
|
559
|
+
if (globalReq) request = globalReq;
|
|
560
|
+
const agentReq = await this.safeHook(() => agentHooks?.beforeLLMCall?.(hookCtx, request));
|
|
561
|
+
if (agentReq) request = agentReq;
|
|
306
562
|
logger.debug({ agent: agent.name, model: agent.model }, "LLM request");
|
|
307
563
|
this.events.emit({
|
|
308
564
|
type: "llm:request",
|
|
@@ -311,7 +567,9 @@ Omit session_id to start a new conversation.`
|
|
|
311
567
|
});
|
|
312
568
|
const response = await TuttiTracer.llmCall(
|
|
313
569
|
agent.model ?? "unknown",
|
|
314
|
-
() =>
|
|
570
|
+
() => withRetry(
|
|
571
|
+
() => agent.streaming ? this.streamToResponse(agent.name, request) : this.provider.chat(request)
|
|
572
|
+
)
|
|
315
573
|
);
|
|
316
574
|
logger.debug(
|
|
317
575
|
{ agent: agent.name, stopReason: response.stop_reason, usage: response.usage },
|
|
@@ -322,6 +580,8 @@ Omit session_id to start a new conversation.`
|
|
|
322
580
|
agent_name: agent.name,
|
|
323
581
|
response
|
|
324
582
|
});
|
|
583
|
+
await this.safeHook(() => this.globalHooks?.afterLLMCall?.(hookCtx, response));
|
|
584
|
+
await this.safeHook(() => agentHooks?.afterLLMCall?.(hookCtx, response));
|
|
325
585
|
totalUsage.input_tokens += response.usage.input_tokens;
|
|
326
586
|
totalUsage.output_tokens += response.usage.output_tokens;
|
|
327
587
|
if (budget) {
|
|
@@ -402,7 +662,7 @@ Omit session_id to start a new conversation.`
|
|
|
402
662
|
}
|
|
403
663
|
const toolResults = await Promise.all(
|
|
404
664
|
toolUseBlocks.map(
|
|
405
|
-
(block) => this.executeTool(allTools, block, toolContext, toolTimeoutMs)
|
|
665
|
+
(block) => this.executeTool(allTools, block, toolContext, toolTimeoutMs, hookCtx, agentHooks)
|
|
406
666
|
)
|
|
407
667
|
);
|
|
408
668
|
messages.push({ role: "user", content: toolResults });
|
|
@@ -419,13 +679,16 @@ Omit session_id to start a new conversation.`
|
|
|
419
679
|
agent_name: agent.name,
|
|
420
680
|
session_id: session.id
|
|
421
681
|
});
|
|
422
|
-
|
|
682
|
+
const agentResult = {
|
|
423
683
|
session_id: session.id,
|
|
424
684
|
output,
|
|
425
685
|
messages,
|
|
426
686
|
turns,
|
|
427
687
|
usage: totalUsage
|
|
428
688
|
};
|
|
689
|
+
await this.safeHook(() => this.globalHooks?.afterAgentRun?.(hookCtx, agentResult));
|
|
690
|
+
await this.safeHook(() => agentHooks?.afterAgentRun?.(hookCtx, agentResult));
|
|
691
|
+
return agentResult;
|
|
429
692
|
});
|
|
430
693
|
}
|
|
431
694
|
async executeWithTimeout(fn, timeoutMs, toolName) {
|
|
@@ -433,12 +696,7 @@ Omit session_id to start a new conversation.`
|
|
|
433
696
|
fn(),
|
|
434
697
|
new Promise(
|
|
435
698
|
(_, reject) => setTimeout(
|
|
436
|
-
() => reject(
|
|
437
|
-
new Error(
|
|
438
|
-
`Tool "${toolName}" timed out after ${timeoutMs}ms.
|
|
439
|
-
Increase tool_timeout_ms in your agent config, or check if the tool is hanging.`
|
|
440
|
-
)
|
|
441
|
-
),
|
|
699
|
+
() => reject(new ToolTimeoutError(toolName, timeoutMs)),
|
|
442
700
|
timeoutMs
|
|
443
701
|
)
|
|
444
702
|
)
|
|
@@ -476,7 +734,42 @@ Increase tool_timeout_ms in your agent config, or check if the tool is hanging.`
|
|
|
476
734
|
}
|
|
477
735
|
return { id: "", content, stop_reason: stopReason, usage };
|
|
478
736
|
}
|
|
479
|
-
|
|
737
|
+
createHitlTool(agentName, sessionId) {
|
|
738
|
+
return {
|
|
739
|
+
name: "request_human_input",
|
|
740
|
+
description: "Pause and ask the human for guidance or approval before proceeding.",
|
|
741
|
+
parameters: hitlRequestSchema,
|
|
742
|
+
execute: async (input) => {
|
|
743
|
+
const timeout = (input.timeout_seconds ?? DEFAULT_HITL_TIMEOUT_S) * 1e3;
|
|
744
|
+
logger.info({ agent: agentName, question: input.question }, "Waiting for human input");
|
|
745
|
+
const answer = await new Promise((resolve2) => {
|
|
746
|
+
this.pendingHitl.set(sessionId, resolve2);
|
|
747
|
+
this.events.emit({
|
|
748
|
+
type: "hitl:requested",
|
|
749
|
+
agent_name: agentName,
|
|
750
|
+
session_id: sessionId,
|
|
751
|
+
question: input.question,
|
|
752
|
+
options: input.options
|
|
753
|
+
});
|
|
754
|
+
setTimeout(() => {
|
|
755
|
+
if (this.pendingHitl.has(sessionId)) {
|
|
756
|
+
this.pendingHitl.delete(sessionId);
|
|
757
|
+
this.events.emit({ type: "hitl:timeout", agent_name: agentName, session_id: sessionId });
|
|
758
|
+
resolve2("[timeout: human did not respond within " + timeout / 1e3 + "s]");
|
|
759
|
+
}
|
|
760
|
+
}, timeout);
|
|
761
|
+
});
|
|
762
|
+
this.events.emit({
|
|
763
|
+
type: "hitl:answered",
|
|
764
|
+
agent_name: agentName,
|
|
765
|
+
session_id: sessionId,
|
|
766
|
+
answer
|
|
767
|
+
});
|
|
768
|
+
return { content: "Human responded: " + answer };
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
async executeTool(tools, block, context, timeoutMs, hookCtx, agentHooks) {
|
|
480
773
|
const tool = tools.find((t) => t.name === block.name);
|
|
481
774
|
if (!tool) {
|
|
482
775
|
const available = tools.map((t) => t.name).join(", ") || "(none)";
|
|
@@ -488,6 +781,16 @@ Increase tool_timeout_ms in your agent config, or check if the tool is hanging.`
|
|
|
488
781
|
};
|
|
489
782
|
}
|
|
490
783
|
return TuttiTracer.toolCall(block.name, async () => {
|
|
784
|
+
if (hookCtx) {
|
|
785
|
+
const globalResult = await this.safeHook(() => this.globalHooks?.beforeToolCall?.(hookCtx, block.name, block.input));
|
|
786
|
+
if (globalResult === false) {
|
|
787
|
+
return { type: "tool_result", tool_use_id: block.id, content: "Tool call blocked by hook", is_error: true };
|
|
788
|
+
}
|
|
789
|
+
const agentResult = await this.safeHook(() => agentHooks?.beforeToolCall?.(hookCtx, block.name, block.input));
|
|
790
|
+
if (agentResult === false) {
|
|
791
|
+
return { type: "tool_result", tool_use_id: block.id, content: "Tool call blocked by hook", is_error: true };
|
|
792
|
+
}
|
|
793
|
+
}
|
|
491
794
|
logger.debug({ tool: block.name, input: block.input }, "Tool called");
|
|
492
795
|
this.events.emit({
|
|
493
796
|
type: "tool:start",
|
|
@@ -497,11 +800,17 @@ Increase tool_timeout_ms in your agent config, or check if the tool is hanging.`
|
|
|
497
800
|
});
|
|
498
801
|
try {
|
|
499
802
|
const parsed = tool.parameters.parse(block.input);
|
|
500
|
-
|
|
803
|
+
let result = await this.executeWithTimeout(
|
|
501
804
|
() => tool.execute(parsed, context),
|
|
502
805
|
timeoutMs,
|
|
503
806
|
block.name
|
|
504
807
|
);
|
|
808
|
+
if (hookCtx) {
|
|
809
|
+
const globalMod = await this.safeHook(() => this.globalHooks?.afterToolCall?.(hookCtx, block.name, result));
|
|
810
|
+
if (globalMod) result = globalMod;
|
|
811
|
+
const agentMod = await this.safeHook(() => agentHooks?.afterToolCall?.(hookCtx, block.name, result));
|
|
812
|
+
if (agentMod) result = agentMod;
|
|
813
|
+
}
|
|
505
814
|
logger.debug({ tool: block.name, result: result.content }, "Tool completed");
|
|
506
815
|
this.events.emit({
|
|
507
816
|
type: "tool:end",
|
|
@@ -731,18 +1040,18 @@ var PostgresSessionStore = class {
|
|
|
731
1040
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
732
1041
|
var InMemorySemanticStore = class {
|
|
733
1042
|
entries = [];
|
|
734
|
-
|
|
1043
|
+
add(entry) {
|
|
735
1044
|
const full = {
|
|
736
1045
|
...entry,
|
|
737
1046
|
id: randomUUID3(),
|
|
738
1047
|
created_at: /* @__PURE__ */ new Date()
|
|
739
1048
|
};
|
|
740
1049
|
this.entries.push(full);
|
|
741
|
-
return full;
|
|
1050
|
+
return Promise.resolve(full);
|
|
742
1051
|
}
|
|
743
|
-
|
|
1052
|
+
search(query, agent_name, limit = 5) {
|
|
744
1053
|
const queryTokens = tokenize(query);
|
|
745
|
-
if (queryTokens.size === 0) return [];
|
|
1054
|
+
if (queryTokens.size === 0) return Promise.resolve([]);
|
|
746
1055
|
const agentEntries = this.entries.filter(
|
|
747
1056
|
(e) => e.agent_name === agent_name
|
|
748
1057
|
);
|
|
@@ -755,13 +1064,17 @@ var InMemorySemanticStore = class {
|
|
|
755
1064
|
const score = overlap / queryTokens.size;
|
|
756
1065
|
return { entry, score };
|
|
757
1066
|
});
|
|
758
|
-
return
|
|
1067
|
+
return Promise.resolve(
|
|
1068
|
+
scored.filter((s) => s.score > 0).sort((a, b) => b.score - a.score).slice(0, limit).map((s) => s.entry)
|
|
1069
|
+
);
|
|
759
1070
|
}
|
|
760
|
-
|
|
1071
|
+
delete(id) {
|
|
761
1072
|
this.entries = this.entries.filter((e) => e.id !== id);
|
|
1073
|
+
return Promise.resolve();
|
|
762
1074
|
}
|
|
763
|
-
|
|
1075
|
+
clear(agent_name) {
|
|
764
1076
|
this.entries = this.entries.filter((e) => e.agent_name !== agent_name);
|
|
1077
|
+
return Promise.resolve();
|
|
765
1078
|
}
|
|
766
1079
|
};
|
|
767
1080
|
function tokenize(text) {
|
|
@@ -777,9 +1090,7 @@ var PermissionGuard = class {
|
|
|
777
1090
|
(p) => !granted.includes(p)
|
|
778
1091
|
);
|
|
779
1092
|
if (missing.length > 0) {
|
|
780
|
-
throw new
|
|
781
|
-
"Voice " + voice.name + " requires permissions not granted: " + missing.join(", ") + "\n\nGrant them in your score file:\n permissions: [" + missing.map((p) => "'" + p + "'").join(", ") + "]"
|
|
782
|
-
);
|
|
1093
|
+
throw new PermissionError(voice.name, voice.required_permissions, granted);
|
|
783
1094
|
}
|
|
784
1095
|
}
|
|
785
1096
|
static warn(voice) {
|
|
@@ -811,7 +1122,8 @@ var TuttiRuntime = class _TuttiRuntime {
|
|
|
811
1122
|
score.provider,
|
|
812
1123
|
this.events,
|
|
813
1124
|
this._sessions,
|
|
814
|
-
this.semanticMemory
|
|
1125
|
+
this.semanticMemory,
|
|
1126
|
+
score.hooks
|
|
815
1127
|
);
|
|
816
1128
|
if (score.telemetry) {
|
|
817
1129
|
initTelemetry(score.telemetry);
|
|
@@ -837,15 +1149,17 @@ var TuttiRuntime = class _TuttiRuntime {
|
|
|
837
1149
|
if (memory.provider === "postgres") {
|
|
838
1150
|
const url = memory.url ?? process.env.DATABASE_URL;
|
|
839
1151
|
if (!url) {
|
|
840
|
-
throw new
|
|
841
|
-
"PostgreSQL session store requires a connection URL.\nSet memory.url in your score, or DATABASE_URL in your .env file."
|
|
1152
|
+
throw new ScoreValidationError(
|
|
1153
|
+
"PostgreSQL session store requires a connection URL.\nSet memory.url in your score, or DATABASE_URL in your .env file.",
|
|
1154
|
+
{ field: "memory.url" }
|
|
842
1155
|
);
|
|
843
1156
|
}
|
|
844
1157
|
return new PostgresSessionStore(url);
|
|
845
1158
|
}
|
|
846
|
-
throw new
|
|
1159
|
+
throw new ScoreValidationError(
|
|
847
1160
|
`Unsupported memory provider: "${memory.provider}".
|
|
848
|
-
Supported: "in-memory", "postgres"
|
|
1161
|
+
Supported: "in-memory", "postgres"`,
|
|
1162
|
+
{ field: "memory.provider", value: memory.provider }
|
|
849
1163
|
);
|
|
850
1164
|
}
|
|
851
1165
|
/** The score configuration this runtime was created with. */
|
|
@@ -859,12 +1173,7 @@ Supported: "in-memory", "postgres"`
|
|
|
859
1173
|
async run(agent_name, input, session_id) {
|
|
860
1174
|
const agent = this._score.agents[agent_name];
|
|
861
1175
|
if (!agent) {
|
|
862
|
-
|
|
863
|
-
throw new Error(
|
|
864
|
-
`Agent "${agent_name}" not found in your score.
|
|
865
|
-
Available agents: ${available}
|
|
866
|
-
Check your tutti.score.ts \u2014 the agent ID must match the key in the agents object.`
|
|
867
|
-
);
|
|
1176
|
+
throw new AgentNotFoundError(agent_name, Object.keys(this._score.agents));
|
|
868
1177
|
}
|
|
869
1178
|
const granted = agent.permissions ?? [];
|
|
870
1179
|
for (const voice of agent.voices) {
|
|
@@ -874,6 +1183,13 @@ Check your tutti.score.ts \u2014 the agent ID must match the key in the agents o
|
|
|
874
1183
|
const resolvedAgent = agent.model ? agent : { ...agent, model: this._score.default_model ?? "claude-sonnet-4-20250514" };
|
|
875
1184
|
return this._runner.run(resolvedAgent, input, session_id);
|
|
876
1185
|
}
|
|
1186
|
+
/**
|
|
1187
|
+
* Provide an answer to a pending human-in-the-loop request.
|
|
1188
|
+
* Call this when a `hitl:requested` event fires to resume the agent.
|
|
1189
|
+
*/
|
|
1190
|
+
answer(sessionId, answer) {
|
|
1191
|
+
this._runner.answer(sessionId, answer);
|
|
1192
|
+
}
|
|
877
1193
|
/** Retrieve an existing session. */
|
|
878
1194
|
getSession(id) {
|
|
879
1195
|
return this._sessions.get(id);
|
|
@@ -881,7 +1197,7 @@ Check your tutti.score.ts \u2014 the agent ID must match the key in the agents o
|
|
|
881
1197
|
};
|
|
882
1198
|
|
|
883
1199
|
// src/agent-router.ts
|
|
884
|
-
import { z } from "zod";
|
|
1200
|
+
import { z as z2 } from "zod";
|
|
885
1201
|
var AgentRouter = class {
|
|
886
1202
|
constructor(_score) {
|
|
887
1203
|
this._score = _score;
|
|
@@ -957,9 +1273,9 @@ When the user's request matches a specialist's expertise, delegate to them with
|
|
|
957
1273
|
const runtime = () => this.runtime;
|
|
958
1274
|
const events = () => this.runtime.events;
|
|
959
1275
|
const entryName = score.agents[score.entry ?? "orchestrator"]?.name ?? "orchestrator";
|
|
960
|
-
const parameters =
|
|
961
|
-
agent_id:
|
|
962
|
-
task:
|
|
1276
|
+
const parameters = z2.object({
|
|
1277
|
+
agent_id: z2.enum(delegateIds).describe("Which specialist agent to delegate to"),
|
|
1278
|
+
task: z2.string().describe("The specific task description to pass to the specialist")
|
|
963
1279
|
});
|
|
964
1280
|
return {
|
|
965
1281
|
name: "delegate_to_agent",
|
|
@@ -1000,50 +1316,51 @@ import { pathToFileURL } from "url";
|
|
|
1000
1316
|
import { resolve } from "path";
|
|
1001
1317
|
|
|
1002
1318
|
// src/score-schema.ts
|
|
1003
|
-
import { z as
|
|
1004
|
-
var PermissionSchema =
|
|
1005
|
-
var VoiceSchema =
|
|
1006
|
-
name:
|
|
1007
|
-
tools:
|
|
1008
|
-
required_permissions:
|
|
1319
|
+
import { z as z3 } from "zod";
|
|
1320
|
+
var PermissionSchema = z3.enum(["network", "filesystem", "shell", "browser"]);
|
|
1321
|
+
var VoiceSchema = z3.object({
|
|
1322
|
+
name: z3.string().min(1, "Voice name cannot be empty"),
|
|
1323
|
+
tools: z3.array(z3.any()),
|
|
1324
|
+
required_permissions: z3.array(PermissionSchema)
|
|
1009
1325
|
}).passthrough();
|
|
1010
|
-
var BudgetSchema =
|
|
1011
|
-
max_tokens:
|
|
1012
|
-
max_cost_usd:
|
|
1013
|
-
warn_at_percent:
|
|
1326
|
+
var BudgetSchema = z3.object({
|
|
1327
|
+
max_tokens: z3.number().positive().optional(),
|
|
1328
|
+
max_cost_usd: z3.number().positive().optional(),
|
|
1329
|
+
warn_at_percent: z3.number().min(1).max(100).optional()
|
|
1014
1330
|
}).strict();
|
|
1015
|
-
var AgentSchema =
|
|
1016
|
-
name:
|
|
1017
|
-
system_prompt:
|
|
1018
|
-
voices:
|
|
1019
|
-
model:
|
|
1020
|
-
description:
|
|
1021
|
-
permissions:
|
|
1022
|
-
max_turns:
|
|
1023
|
-
max_tool_calls:
|
|
1024
|
-
tool_timeout_ms:
|
|
1331
|
+
var AgentSchema = z3.object({
|
|
1332
|
+
name: z3.string().min(1, "Agent name cannot be empty"),
|
|
1333
|
+
system_prompt: z3.string().min(1, "Agent system_prompt cannot be empty"),
|
|
1334
|
+
voices: z3.array(VoiceSchema),
|
|
1335
|
+
model: z3.string().optional(),
|
|
1336
|
+
description: z3.string().optional(),
|
|
1337
|
+
permissions: z3.array(PermissionSchema).optional(),
|
|
1338
|
+
max_turns: z3.number().int().positive("max_turns must be a positive number").optional(),
|
|
1339
|
+
max_tool_calls: z3.number().int().positive("max_tool_calls must be a positive number").optional(),
|
|
1340
|
+
tool_timeout_ms: z3.number().int().positive("tool_timeout_ms must be a positive number").optional(),
|
|
1025
1341
|
budget: BudgetSchema.optional(),
|
|
1026
|
-
streaming:
|
|
1027
|
-
|
|
1028
|
-
|
|
1342
|
+
streaming: z3.boolean().optional(),
|
|
1343
|
+
allow_human_input: z3.boolean().optional(),
|
|
1344
|
+
delegates: z3.array(z3.string()).optional(),
|
|
1345
|
+
role: z3.enum(["orchestrator", "specialist"]).optional()
|
|
1029
1346
|
}).passthrough();
|
|
1030
|
-
var TelemetrySchema =
|
|
1031
|
-
enabled:
|
|
1032
|
-
endpoint:
|
|
1033
|
-
headers:
|
|
1347
|
+
var TelemetrySchema = z3.object({
|
|
1348
|
+
enabled: z3.boolean(),
|
|
1349
|
+
endpoint: z3.string().url("telemetry.endpoint must be a valid URL").optional(),
|
|
1350
|
+
headers: z3.record(z3.string(), z3.string()).optional()
|
|
1034
1351
|
}).strict();
|
|
1035
|
-
var ScoreSchema =
|
|
1036
|
-
provider:
|
|
1352
|
+
var ScoreSchema = z3.object({
|
|
1353
|
+
provider: z3.object({ chat: z3.function() }).passthrough().refine((p) => typeof p.chat === "function", {
|
|
1037
1354
|
message: "provider must have a chat() method \u2014 did you forget to pass a provider instance?"
|
|
1038
1355
|
}),
|
|
1039
|
-
agents:
|
|
1356
|
+
agents: z3.record(z3.string(), AgentSchema).refine(
|
|
1040
1357
|
(agents) => Object.keys(agents).length > 0,
|
|
1041
1358
|
{ message: "Score must define at least one agent" }
|
|
1042
1359
|
),
|
|
1043
|
-
name:
|
|
1044
|
-
description:
|
|
1045
|
-
default_model:
|
|
1046
|
-
entry:
|
|
1360
|
+
name: z3.string().optional(),
|
|
1361
|
+
description: z3.string().optional(),
|
|
1362
|
+
default_model: z3.string().optional(),
|
|
1363
|
+
entry: z3.string().optional(),
|
|
1047
1364
|
telemetry: TelemetrySchema.optional()
|
|
1048
1365
|
}).passthrough();
|
|
1049
1366
|
function validateScore(config) {
|
|
@@ -1053,7 +1370,7 @@ function validateScore(config) {
|
|
|
1053
1370
|
const path = issue.path.length > 0 ? issue.path.join(".") : "(root)";
|
|
1054
1371
|
return ` - ${path}: ${issue.message}`;
|
|
1055
1372
|
});
|
|
1056
|
-
throw new
|
|
1373
|
+
throw new ScoreValidationError(
|
|
1057
1374
|
"Invalid score file:\n" + issues.join("\n")
|
|
1058
1375
|
);
|
|
1059
1376
|
}
|
|
@@ -1063,18 +1380,20 @@ function validateScore(config) {
|
|
|
1063
1380
|
if (agent.delegates) {
|
|
1064
1381
|
for (const delegateId of agent.delegates) {
|
|
1065
1382
|
if (!agentKeys.includes(delegateId)) {
|
|
1066
|
-
throw new
|
|
1383
|
+
throw new ScoreValidationError(
|
|
1067
1384
|
`Invalid score file:
|
|
1068
|
-
- agents.${key}.delegates: references unknown agent "${delegateId}". Available: ${agentKeys.join(", ")}
|
|
1385
|
+
- agents.${key}.delegates: references unknown agent "${delegateId}". Available: ${agentKeys.join(", ")}`,
|
|
1386
|
+
{ field: `agents.${key}.delegates`, value: delegateId }
|
|
1069
1387
|
);
|
|
1070
1388
|
}
|
|
1071
1389
|
}
|
|
1072
1390
|
}
|
|
1073
1391
|
}
|
|
1074
1392
|
if (data.entry && !agentKeys.includes(data.entry)) {
|
|
1075
|
-
throw new
|
|
1393
|
+
throw new ScoreValidationError(
|
|
1076
1394
|
`Invalid score file:
|
|
1077
|
-
- entry: references unknown agent "${data.entry}". Available: ${agentKeys.join(", ")}
|
|
1395
|
+
- entry: references unknown agent "${data.entry}". Available: ${agentKeys.join(", ")}`,
|
|
1396
|
+
{ field: "entry", value: data.entry }
|
|
1078
1397
|
);
|
|
1079
1398
|
}
|
|
1080
1399
|
}
|
|
@@ -1117,8 +1436,9 @@ var AnthropicProvider = class {
|
|
|
1117
1436
|
}
|
|
1118
1437
|
async chat(request) {
|
|
1119
1438
|
if (!request.model) {
|
|
1120
|
-
throw new
|
|
1121
|
-
"AnthropicProvider requires a model on ChatRequest.\nSet model on the agent or default_model on the score."
|
|
1439
|
+
throw new ProviderError(
|
|
1440
|
+
"AnthropicProvider requires a model on ChatRequest.\nSet model on the agent or default_model on the score.",
|
|
1441
|
+
{ provider: "anthropic" }
|
|
1122
1442
|
);
|
|
1123
1443
|
}
|
|
1124
1444
|
let response;
|
|
@@ -1142,10 +1462,10 @@ var AnthropicProvider = class {
|
|
|
1142
1462
|
} catch (error) {
|
|
1143
1463
|
const msg = error instanceof Error ? error.message : String(error);
|
|
1144
1464
|
logger.error({ error: msg, provider: "anthropic" }, "Provider request failed");
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
);
|
|
1465
|
+
if (msg.includes("authentication") || msg.includes("apiKey") || msg.includes("authToken")) {
|
|
1466
|
+
throw new AuthenticationError("anthropic");
|
|
1467
|
+
}
|
|
1468
|
+
throw new ProviderError(`Anthropic API error: ${msg}`, { provider: "anthropic" });
|
|
1149
1469
|
}
|
|
1150
1470
|
const content = response.content.map((block) => {
|
|
1151
1471
|
if (block.type === "text") {
|
|
@@ -1173,8 +1493,9 @@ Check that ANTHROPIC_API_KEY is set correctly in your .env file.`
|
|
|
1173
1493
|
}
|
|
1174
1494
|
async *stream(request) {
|
|
1175
1495
|
if (!request.model) {
|
|
1176
|
-
throw new
|
|
1177
|
-
"AnthropicProvider requires a model on ChatRequest.\nSet model on the agent or default_model on the score."
|
|
1496
|
+
throw new ProviderError(
|
|
1497
|
+
"AnthropicProvider requires a model on ChatRequest.\nSet model on the agent or default_model on the score.",
|
|
1498
|
+
{ provider: "anthropic" }
|
|
1178
1499
|
);
|
|
1179
1500
|
}
|
|
1180
1501
|
let raw;
|
|
@@ -1199,10 +1520,10 @@ Check that ANTHROPIC_API_KEY is set correctly in your .env file.`
|
|
|
1199
1520
|
} catch (error) {
|
|
1200
1521
|
const msg = error instanceof Error ? error.message : String(error);
|
|
1201
1522
|
logger.error({ error: msg, provider: "anthropic" }, "Provider stream failed");
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
);
|
|
1523
|
+
if (msg.includes("authentication") || msg.includes("apiKey") || msg.includes("authToken")) {
|
|
1524
|
+
throw new AuthenticationError("anthropic");
|
|
1525
|
+
}
|
|
1526
|
+
throw new ProviderError(`Anthropic API error: ${msg}`, { provider: "anthropic" });
|
|
1206
1527
|
}
|
|
1207
1528
|
const toolBlocks = /* @__PURE__ */ new Map();
|
|
1208
1529
|
let inputTokens = 0;
|
|
@@ -1269,8 +1590,9 @@ var OpenAIProvider = class {
|
|
|
1269
1590
|
}
|
|
1270
1591
|
async chat(request) {
|
|
1271
1592
|
if (!request.model) {
|
|
1272
|
-
throw new
|
|
1273
|
-
"OpenAIProvider requires a model on ChatRequest.\nSet model on the agent or default_model on the score."
|
|
1593
|
+
throw new ProviderError(
|
|
1594
|
+
"OpenAIProvider requires a model on ChatRequest.\nSet model on the agent or default_model on the score.",
|
|
1595
|
+
{ provider: "openai" }
|
|
1274
1596
|
);
|
|
1275
1597
|
}
|
|
1276
1598
|
const messages = [];
|
|
@@ -1339,10 +1661,10 @@ var OpenAIProvider = class {
|
|
|
1339
1661
|
} catch (error) {
|
|
1340
1662
|
const msg = error instanceof Error ? error.message : String(error);
|
|
1341
1663
|
logger.error({ error: msg, provider: "openai" }, "Provider request failed");
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
);
|
|
1664
|
+
if (msg.includes("Incorrect API key") || msg.includes("authentication")) {
|
|
1665
|
+
throw new AuthenticationError("openai");
|
|
1666
|
+
}
|
|
1667
|
+
throw new ProviderError(`OpenAI API error: ${msg}`, { provider: "openai" });
|
|
1346
1668
|
}
|
|
1347
1669
|
const choice = response.choices[0];
|
|
1348
1670
|
const content = [];
|
|
@@ -1385,8 +1707,9 @@ Check that OPENAI_API_KEY is set correctly in your .env file.`
|
|
|
1385
1707
|
}
|
|
1386
1708
|
async *stream(request) {
|
|
1387
1709
|
if (!request.model) {
|
|
1388
|
-
throw new
|
|
1389
|
-
"OpenAIProvider requires a model on ChatRequest.\nSet model on the agent or default_model on the score."
|
|
1710
|
+
throw new ProviderError(
|
|
1711
|
+
"OpenAIProvider requires a model on ChatRequest.\nSet model on the agent or default_model on the score.",
|
|
1712
|
+
{ provider: "openai" }
|
|
1390
1713
|
);
|
|
1391
1714
|
}
|
|
1392
1715
|
const messages = [];
|
|
@@ -1501,9 +1824,7 @@ var GeminiProvider = class {
|
|
|
1501
1824
|
constructor(options = {}) {
|
|
1502
1825
|
const apiKey = options.api_key ?? SecretsManager.optional("GEMINI_API_KEY");
|
|
1503
1826
|
if (!apiKey) {
|
|
1504
|
-
throw new
|
|
1505
|
-
"GeminiProvider requires an API key.\nSet GEMINI_API_KEY in your .env file, or pass api_key to the constructor:\n new GeminiProvider({ api_key: 'your-key' })"
|
|
1506
|
-
);
|
|
1827
|
+
throw new AuthenticationError("gemini");
|
|
1507
1828
|
}
|
|
1508
1829
|
this.client = new GoogleGenerativeAI(apiKey);
|
|
1509
1830
|
}
|
|
@@ -1582,10 +1903,7 @@ var GeminiProvider = class {
|
|
|
1582
1903
|
} catch (error) {
|
|
1583
1904
|
const msg = error instanceof Error ? error.message : String(error);
|
|
1584
1905
|
logger.error({ error: msg, provider: "gemini" }, "Provider request failed");
|
|
1585
|
-
throw new
|
|
1586
|
-
`Gemini API error: ${msg}
|
|
1587
|
-
Check that GEMINI_API_KEY is set correctly in your .env file.`
|
|
1588
|
-
);
|
|
1906
|
+
throw new ProviderError(`Gemini API error: ${msg}`, { provider: "gemini" });
|
|
1589
1907
|
}
|
|
1590
1908
|
const response = result.response;
|
|
1591
1909
|
const candidate = response.candidates?.[0];
|
|
@@ -1725,23 +2043,40 @@ function convertJsonSchemaToGemini(schema) {
|
|
|
1725
2043
|
};
|
|
1726
2044
|
}
|
|
1727
2045
|
export {
|
|
2046
|
+
AgentNotFoundError,
|
|
1728
2047
|
AgentRouter,
|
|
1729
2048
|
AgentRunner,
|
|
1730
2049
|
AnthropicProvider,
|
|
2050
|
+
AuthenticationError,
|
|
2051
|
+
BudgetExceededError,
|
|
2052
|
+
ContextWindowError,
|
|
1731
2053
|
EventBus,
|
|
1732
2054
|
GeminiProvider,
|
|
1733
2055
|
InMemorySemanticStore,
|
|
1734
2056
|
InMemorySessionStore,
|
|
1735
2057
|
OpenAIProvider,
|
|
2058
|
+
PathTraversalError,
|
|
2059
|
+
PermissionError,
|
|
1736
2060
|
PermissionGuard,
|
|
1737
2061
|
PostgresSessionStore,
|
|
1738
2062
|
PromptGuard,
|
|
2063
|
+
ProviderError,
|
|
2064
|
+
RateLimitError,
|
|
1739
2065
|
ScoreLoader,
|
|
2066
|
+
ScoreValidationError,
|
|
1740
2067
|
SecretsManager,
|
|
1741
2068
|
TokenBudget,
|
|
2069
|
+
ToolTimeoutError,
|
|
2070
|
+
TuttiError,
|
|
1742
2071
|
TuttiRuntime,
|
|
1743
2072
|
TuttiTracer,
|
|
2073
|
+
UrlValidationError,
|
|
2074
|
+
VoiceError,
|
|
2075
|
+
createBlocklistHook,
|
|
2076
|
+
createCacheHook,
|
|
1744
2077
|
createLogger,
|
|
2078
|
+
createLoggingHook,
|
|
2079
|
+
createMaxCostHook,
|
|
1745
2080
|
defineScore,
|
|
1746
2081
|
initTelemetry,
|
|
1747
2082
|
logger,
|