@tuttiai/core 0.5.0 → 0.7.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 +21 -4
- package/dist/index.js +652 -184
- package/dist/index.js.map +1 -1
- package/package.json +9 -2
package/dist/index.js
CHANGED
|
@@ -1,3 +1,107 @@
|
|
|
1
|
+
// src/logger.ts
|
|
2
|
+
import pino from "pino";
|
|
3
|
+
var createLogger = (name) => pino({
|
|
4
|
+
name,
|
|
5
|
+
level: process.env.TUTTI_LOG_LEVEL ?? "info",
|
|
6
|
+
transport: process.env.NODE_ENV === "production" ? void 0 : {
|
|
7
|
+
target: "pino-pretty",
|
|
8
|
+
options: {
|
|
9
|
+
colorize: true,
|
|
10
|
+
translateTime: "HH:MM:ss",
|
|
11
|
+
ignore: "pid,hostname"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
var logger = createLogger("tutti");
|
|
16
|
+
|
|
17
|
+
// src/telemetry.ts
|
|
18
|
+
import { trace, SpanStatusCode } from "@opentelemetry/api";
|
|
19
|
+
var tracer = trace.getTracer("tutti", "1.0.0");
|
|
20
|
+
var TuttiTracer = {
|
|
21
|
+
agentRun(agentName, sessionId, fn) {
|
|
22
|
+
return tracer.startActiveSpan("agent.run", async (span) => {
|
|
23
|
+
span.setAttribute("agent.name", agentName);
|
|
24
|
+
span.setAttribute("session.id", sessionId);
|
|
25
|
+
try {
|
|
26
|
+
const result = await fn();
|
|
27
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
28
|
+
return result;
|
|
29
|
+
} catch (err) {
|
|
30
|
+
span.setStatus({
|
|
31
|
+
code: SpanStatusCode.ERROR,
|
|
32
|
+
message: err instanceof Error ? err.message : String(err)
|
|
33
|
+
});
|
|
34
|
+
throw err;
|
|
35
|
+
} finally {
|
|
36
|
+
span.end();
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
llmCall(model, fn) {
|
|
41
|
+
return tracer.startActiveSpan("llm.call", async (span) => {
|
|
42
|
+
span.setAttribute("llm.model", model);
|
|
43
|
+
try {
|
|
44
|
+
const result = await fn();
|
|
45
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
46
|
+
return result;
|
|
47
|
+
} catch (err) {
|
|
48
|
+
span.setStatus({
|
|
49
|
+
code: SpanStatusCode.ERROR,
|
|
50
|
+
message: err instanceof Error ? err.message : String(err)
|
|
51
|
+
});
|
|
52
|
+
throw err;
|
|
53
|
+
} finally {
|
|
54
|
+
span.end();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
toolCall(toolName, fn) {
|
|
59
|
+
return tracer.startActiveSpan("tool.call", async (span) => {
|
|
60
|
+
span.setAttribute("tool.name", toolName);
|
|
61
|
+
try {
|
|
62
|
+
const result = await fn();
|
|
63
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
64
|
+
return result;
|
|
65
|
+
} catch (err) {
|
|
66
|
+
span.setStatus({
|
|
67
|
+
code: SpanStatusCode.ERROR,
|
|
68
|
+
message: err instanceof Error ? err.message : String(err)
|
|
69
|
+
});
|
|
70
|
+
throw err;
|
|
71
|
+
} finally {
|
|
72
|
+
span.end();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// src/telemetry-setup.ts
|
|
79
|
+
import { NodeSDK } from "@opentelemetry/sdk-node";
|
|
80
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
81
|
+
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
|
|
82
|
+
var sdk;
|
|
83
|
+
function initTelemetry(config) {
|
|
84
|
+
if (!config.enabled || sdk) return;
|
|
85
|
+
const endpoint = config.endpoint ?? "http://localhost:4318";
|
|
86
|
+
const exporter = new OTLPTraceExporter({
|
|
87
|
+
url: `${endpoint}/v1/traces`,
|
|
88
|
+
headers: config.headers
|
|
89
|
+
});
|
|
90
|
+
sdk = new NodeSDK({
|
|
91
|
+
traceExporter: exporter,
|
|
92
|
+
instrumentations: [getNodeAutoInstrumentations({ "@opentelemetry/instrumentation-fs": { enabled: false } })],
|
|
93
|
+
serviceName: process.env.OTEL_SERVICE_NAME ?? "tutti"
|
|
94
|
+
});
|
|
95
|
+
sdk.start();
|
|
96
|
+
logger.info({ endpoint }, "OpenTelemetry tracing enabled");
|
|
97
|
+
}
|
|
98
|
+
async function shutdownTelemetry() {
|
|
99
|
+
if (sdk) {
|
|
100
|
+
await sdk.shutdown();
|
|
101
|
+
sdk = void 0;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
1
105
|
// src/agent-runner.ts
|
|
2
106
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
3
107
|
|
|
@@ -148,157 +252,181 @@ The session may have expired or the ID is incorrect.
|
|
|
148
252
|
Omit session_id to start a new conversation.`
|
|
149
253
|
);
|
|
150
254
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
agent_name: agent.name,
|
|
154
|
-
session_id: session.id
|
|
155
|
-
});
|
|
156
|
-
const allTools = agent.voices.flatMap((v) => v.tools);
|
|
157
|
-
const toolDefs = allTools.map(toolToDefinition);
|
|
158
|
-
const messages = [
|
|
159
|
-
...session.messages,
|
|
160
|
-
{ role: "user", content: input }
|
|
161
|
-
];
|
|
162
|
-
const maxTurns = agent.max_turns ?? DEFAULT_MAX_TURNS;
|
|
163
|
-
const maxToolCalls = agent.max_tool_calls ?? DEFAULT_MAX_TOOL_CALLS;
|
|
164
|
-
const budget = agent.budget ? new TokenBudget(agent.budget, agent.model ?? "") : void 0;
|
|
165
|
-
const totalUsage = { input_tokens: 0, output_tokens: 0 };
|
|
166
|
-
let turns = 0;
|
|
167
|
-
let totalToolCalls = 0;
|
|
168
|
-
while (turns < maxTurns) {
|
|
169
|
-
turns++;
|
|
255
|
+
return TuttiTracer.agentRun(agent.name, session.id, async () => {
|
|
256
|
+
logger.info({ agent: agent.name, session: session.id }, "Agent started");
|
|
170
257
|
this.events.emit({
|
|
171
|
-
type: "
|
|
258
|
+
type: "agent:start",
|
|
172
259
|
agent_name: agent.name,
|
|
173
|
-
session_id: session.id
|
|
174
|
-
turn: turns
|
|
260
|
+
session_id: session.id
|
|
175
261
|
});
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
262
|
+
const allTools = agent.voices.flatMap((v) => v.tools);
|
|
263
|
+
const toolDefs = allTools.map(toolToDefinition);
|
|
264
|
+
const messages = [
|
|
265
|
+
...session.messages,
|
|
266
|
+
{ role: "user", content: input }
|
|
267
|
+
];
|
|
268
|
+
const maxTurns = agent.max_turns ?? DEFAULT_MAX_TURNS;
|
|
269
|
+
const maxToolCalls = agent.max_tool_calls ?? DEFAULT_MAX_TOOL_CALLS;
|
|
270
|
+
const budget = agent.budget ? new TokenBudget(agent.budget, agent.model ?? "") : void 0;
|
|
271
|
+
const totalUsage = { input_tokens: 0, output_tokens: 0 };
|
|
272
|
+
let turns = 0;
|
|
273
|
+
let totalToolCalls = 0;
|
|
274
|
+
while (turns < maxTurns) {
|
|
275
|
+
turns++;
|
|
276
|
+
logger.info({ agent: agent.name, session: session.id, turn: turns }, "Turn started");
|
|
277
|
+
this.events.emit({
|
|
278
|
+
type: "turn:start",
|
|
279
|
+
agent_name: agent.name,
|
|
280
|
+
session_id: session.id,
|
|
281
|
+
turn: turns
|
|
282
|
+
});
|
|
283
|
+
let systemPrompt = agent.system_prompt;
|
|
284
|
+
const memCfg = agent.semantic_memory;
|
|
285
|
+
if (memCfg?.enabled && this.semanticMemory) {
|
|
286
|
+
const maxMemories = memCfg.max_memories ?? 5;
|
|
287
|
+
const injectSystem = memCfg.inject_system !== false;
|
|
288
|
+
if (injectSystem) {
|
|
289
|
+
const memories = await this.semanticMemory.search(
|
|
290
|
+
input,
|
|
291
|
+
agent.name,
|
|
292
|
+
maxMemories
|
|
293
|
+
);
|
|
294
|
+
if (memories.length > 0) {
|
|
295
|
+
const memoryBlock = memories.map((m) => `- ${m.content}`).join("\n");
|
|
296
|
+
systemPrompt += "\n\nRelevant context from previous sessions:\n" + memoryBlock;
|
|
297
|
+
}
|
|
190
298
|
}
|
|
191
299
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
300
|
+
const request = {
|
|
301
|
+
model: agent.model,
|
|
302
|
+
system: systemPrompt,
|
|
303
|
+
messages,
|
|
304
|
+
tools: toolDefs.length > 0 ? toolDefs : void 0
|
|
305
|
+
};
|
|
306
|
+
logger.debug({ agent: agent.name, model: agent.model }, "LLM request");
|
|
307
|
+
this.events.emit({
|
|
308
|
+
type: "llm:request",
|
|
309
|
+
agent_name: agent.name,
|
|
310
|
+
request
|
|
311
|
+
});
|
|
312
|
+
const response = await TuttiTracer.llmCall(
|
|
313
|
+
agent.model ?? "unknown",
|
|
314
|
+
() => agent.streaming ? this.streamToResponse(agent.name, request) : this.provider.chat(request)
|
|
315
|
+
);
|
|
316
|
+
logger.debug(
|
|
317
|
+
{ agent: agent.name, stopReason: response.stop_reason, usage: response.usage },
|
|
318
|
+
"LLM response"
|
|
319
|
+
);
|
|
320
|
+
this.events.emit({
|
|
321
|
+
type: "llm:response",
|
|
322
|
+
agent_name: agent.name,
|
|
323
|
+
response
|
|
324
|
+
});
|
|
325
|
+
totalUsage.input_tokens += response.usage.input_tokens;
|
|
326
|
+
totalUsage.output_tokens += response.usage.output_tokens;
|
|
327
|
+
if (budget) {
|
|
328
|
+
budget.add(response.usage.input_tokens, response.usage.output_tokens);
|
|
329
|
+
const status = budget.check();
|
|
330
|
+
if (status === "warning") {
|
|
331
|
+
logger.warn(
|
|
332
|
+
{ agent: agent.name, tokens: budget.total_tokens, cost_usd: budget.estimated_cost_usd },
|
|
333
|
+
"Approaching token budget limit"
|
|
334
|
+
);
|
|
335
|
+
this.events.emit({
|
|
336
|
+
type: "budget:warning",
|
|
337
|
+
agent_name: agent.name,
|
|
338
|
+
tokens: budget.total_tokens,
|
|
339
|
+
cost_usd: budget.estimated_cost_usd
|
|
340
|
+
});
|
|
341
|
+
} else if (status === "exceeded") {
|
|
342
|
+
logger.warn(
|
|
343
|
+
{ agent: agent.name, tokens: budget.total_tokens, cost_usd: budget.estimated_cost_usd },
|
|
344
|
+
"Token budget exceeded"
|
|
345
|
+
);
|
|
346
|
+
this.events.emit({
|
|
347
|
+
type: "budget:exceeded",
|
|
348
|
+
agent_name: agent.name,
|
|
349
|
+
tokens: budget.total_tokens,
|
|
350
|
+
cost_usd: budget.estimated_cost_usd
|
|
351
|
+
});
|
|
352
|
+
messages.push({ role: "assistant", content: response.content });
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
messages.push({ role: "assistant", content: response.content });
|
|
357
|
+
this.events.emit({
|
|
358
|
+
type: "turn:end",
|
|
359
|
+
agent_name: agent.name,
|
|
360
|
+
session_id: session.id,
|
|
361
|
+
turn: turns
|
|
362
|
+
});
|
|
363
|
+
if (response.stop_reason !== "tool_use") {
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
const toolUseBlocks = response.content.filter(
|
|
367
|
+
(b) => b.type === "tool_use"
|
|
368
|
+
);
|
|
369
|
+
totalToolCalls += toolUseBlocks.length;
|
|
370
|
+
if (totalToolCalls > maxToolCalls) {
|
|
371
|
+
messages.push({
|
|
372
|
+
role: "user",
|
|
373
|
+
content: toolUseBlocks.map((block) => ({
|
|
374
|
+
type: "tool_result",
|
|
375
|
+
tool_use_id: block.id,
|
|
376
|
+
content: `Tool call rate limit exceeded: ${totalToolCalls} calls (max: ${maxToolCalls})`,
|
|
377
|
+
is_error: true
|
|
378
|
+
}))
|
|
228
379
|
});
|
|
229
|
-
messages.push({ role: "assistant", content: response.content });
|
|
230
380
|
break;
|
|
231
381
|
}
|
|
382
|
+
const toolTimeoutMs = agent.tool_timeout_ms ?? DEFAULT_TOOL_TIMEOUT_MS;
|
|
383
|
+
const toolContext = {
|
|
384
|
+
session_id: session.id,
|
|
385
|
+
agent_name: agent.name
|
|
386
|
+
};
|
|
387
|
+
if (memCfg?.enabled && this.semanticMemory) {
|
|
388
|
+
const sm = this.semanticMemory;
|
|
389
|
+
const agentName = agent.name;
|
|
390
|
+
toolContext.memory = {
|
|
391
|
+
remember: async (content, metadata = {}) => {
|
|
392
|
+
await sm.add({ agent_name: agentName, content, metadata });
|
|
393
|
+
},
|
|
394
|
+
recall: async (query, limit) => {
|
|
395
|
+
const entries = await sm.search(query, agentName, limit);
|
|
396
|
+
return entries.map((e) => ({ id: e.id, content: e.content }));
|
|
397
|
+
},
|
|
398
|
+
forget: async (id) => {
|
|
399
|
+
await sm.delete(id);
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
const toolResults = await Promise.all(
|
|
404
|
+
toolUseBlocks.map(
|
|
405
|
+
(block) => this.executeTool(allTools, block, toolContext, toolTimeoutMs)
|
|
406
|
+
)
|
|
407
|
+
);
|
|
408
|
+
messages.push({ role: "user", content: toolResults });
|
|
232
409
|
}
|
|
233
|
-
|
|
410
|
+
this.sessions.update(session.id, messages);
|
|
411
|
+
const lastAssistant = messages.filter((m) => m.role === "assistant").at(-1);
|
|
412
|
+
const output = extractText(lastAssistant?.content);
|
|
413
|
+
logger.info(
|
|
414
|
+
{ agent: agent.name, session: session.id, turns, usage: totalUsage },
|
|
415
|
+
"Agent finished"
|
|
416
|
+
);
|
|
234
417
|
this.events.emit({
|
|
235
|
-
type: "
|
|
418
|
+
type: "agent:end",
|
|
236
419
|
agent_name: agent.name,
|
|
237
|
-
session_id: session.id
|
|
238
|
-
turn: turns
|
|
420
|
+
session_id: session.id
|
|
239
421
|
});
|
|
240
|
-
|
|
241
|
-
break;
|
|
242
|
-
}
|
|
243
|
-
const toolUseBlocks = response.content.filter(
|
|
244
|
-
(b) => b.type === "tool_use"
|
|
245
|
-
);
|
|
246
|
-
totalToolCalls += toolUseBlocks.length;
|
|
247
|
-
if (totalToolCalls > maxToolCalls) {
|
|
248
|
-
messages.push({
|
|
249
|
-
role: "user",
|
|
250
|
-
content: toolUseBlocks.map((block) => ({
|
|
251
|
-
type: "tool_result",
|
|
252
|
-
tool_use_id: block.id,
|
|
253
|
-
content: `Tool call rate limit exceeded: ${totalToolCalls} calls (max: ${maxToolCalls})`,
|
|
254
|
-
is_error: true
|
|
255
|
-
}))
|
|
256
|
-
});
|
|
257
|
-
break;
|
|
258
|
-
}
|
|
259
|
-
const toolTimeoutMs = agent.tool_timeout_ms ?? DEFAULT_TOOL_TIMEOUT_MS;
|
|
260
|
-
const toolContext = {
|
|
422
|
+
return {
|
|
261
423
|
session_id: session.id,
|
|
262
|
-
|
|
424
|
+
output,
|
|
425
|
+
messages,
|
|
426
|
+
turns,
|
|
427
|
+
usage: totalUsage
|
|
263
428
|
};
|
|
264
|
-
if (memCfg?.enabled && this.semanticMemory) {
|
|
265
|
-
const sm = this.semanticMemory;
|
|
266
|
-
const agentName = agent.name;
|
|
267
|
-
toolContext.memory = {
|
|
268
|
-
remember: async (content, metadata = {}) => {
|
|
269
|
-
await sm.add({ agent_name: agentName, content, metadata });
|
|
270
|
-
},
|
|
271
|
-
recall: async (query, limit) => {
|
|
272
|
-
const entries = await sm.search(query, agentName, limit);
|
|
273
|
-
return entries.map((e) => ({ id: e.id, content: e.content }));
|
|
274
|
-
},
|
|
275
|
-
forget: async (id) => {
|
|
276
|
-
await sm.delete(id);
|
|
277
|
-
}
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
const toolResults = await Promise.all(
|
|
281
|
-
toolUseBlocks.map(
|
|
282
|
-
(block) => this.executeTool(allTools, block, toolContext, toolTimeoutMs)
|
|
283
|
-
)
|
|
284
|
-
);
|
|
285
|
-
messages.push({ role: "user", content: toolResults });
|
|
286
|
-
}
|
|
287
|
-
this.sessions.update(session.id, messages);
|
|
288
|
-
const lastAssistant = messages.filter((m) => m.role === "assistant").at(-1);
|
|
289
|
-
const output = extractText(lastAssistant?.content);
|
|
290
|
-
this.events.emit({
|
|
291
|
-
type: "agent:end",
|
|
292
|
-
agent_name: agent.name,
|
|
293
|
-
session_id: session.id
|
|
294
429
|
});
|
|
295
|
-
return {
|
|
296
|
-
session_id: session.id,
|
|
297
|
-
output,
|
|
298
|
-
messages,
|
|
299
|
-
turns,
|
|
300
|
-
usage: totalUsage
|
|
301
|
-
};
|
|
302
430
|
}
|
|
303
431
|
async executeWithTimeout(fn, timeoutMs, toolName) {
|
|
304
432
|
return Promise.race([
|
|
@@ -316,6 +444,38 @@ Increase tool_timeout_ms in your agent config, or check if the tool is hanging.`
|
|
|
316
444
|
)
|
|
317
445
|
]);
|
|
318
446
|
}
|
|
447
|
+
async streamToResponse(agentName, request) {
|
|
448
|
+
const content = [];
|
|
449
|
+
let textBuffer = "";
|
|
450
|
+
let usage = { input_tokens: 0, output_tokens: 0 };
|
|
451
|
+
let stopReason = "end_turn";
|
|
452
|
+
for await (const chunk of this.provider.stream(request)) {
|
|
453
|
+
if (chunk.type === "text" && chunk.text) {
|
|
454
|
+
textBuffer += chunk.text;
|
|
455
|
+
this.events.emit({
|
|
456
|
+
type: "token:stream",
|
|
457
|
+
agent_name: agentName,
|
|
458
|
+
text: chunk.text
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
if (chunk.type === "tool_use" && chunk.tool) {
|
|
462
|
+
content.push({
|
|
463
|
+
type: "tool_use",
|
|
464
|
+
id: chunk.tool.id,
|
|
465
|
+
name: chunk.tool.name,
|
|
466
|
+
input: chunk.tool.input
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
if (chunk.type === "usage") {
|
|
470
|
+
if (chunk.usage) usage = chunk.usage;
|
|
471
|
+
if (chunk.stop_reason) stopReason = chunk.stop_reason;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (textBuffer) {
|
|
475
|
+
content.unshift({ type: "text", text: textBuffer });
|
|
476
|
+
}
|
|
477
|
+
return { id: "", content, stop_reason: stopReason, usage };
|
|
478
|
+
}
|
|
319
479
|
async executeTool(tools, block, context, timeoutMs) {
|
|
320
480
|
const tool = tools.find((t) => t.name === block.name);
|
|
321
481
|
if (!tool) {
|
|
@@ -327,55 +487,64 @@ Increase tool_timeout_ms in your agent config, or check if the tool is hanging.`
|
|
|
327
487
|
is_error: true
|
|
328
488
|
};
|
|
329
489
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
agent_name: context.agent_name,
|
|
333
|
-
tool_name: block.name,
|
|
334
|
-
input: block.input
|
|
335
|
-
});
|
|
336
|
-
try {
|
|
337
|
-
const parsed = tool.parameters.parse(block.input);
|
|
338
|
-
const result = await this.executeWithTimeout(
|
|
339
|
-
() => tool.execute(parsed, context),
|
|
340
|
-
timeoutMs,
|
|
341
|
-
block.name
|
|
342
|
-
);
|
|
490
|
+
return TuttiTracer.toolCall(block.name, async () => {
|
|
491
|
+
logger.debug({ tool: block.name, input: block.input }, "Tool called");
|
|
343
492
|
this.events.emit({
|
|
344
|
-
type: "tool:
|
|
493
|
+
type: "tool:start",
|
|
345
494
|
agent_name: context.agent_name,
|
|
346
495
|
tool_name: block.name,
|
|
347
|
-
|
|
496
|
+
input: block.input
|
|
348
497
|
});
|
|
349
|
-
|
|
350
|
-
|
|
498
|
+
try {
|
|
499
|
+
const parsed = tool.parameters.parse(block.input);
|
|
500
|
+
const result = await this.executeWithTimeout(
|
|
501
|
+
() => tool.execute(parsed, context),
|
|
502
|
+
timeoutMs,
|
|
503
|
+
block.name
|
|
504
|
+
);
|
|
505
|
+
logger.debug({ tool: block.name, result: result.content }, "Tool completed");
|
|
506
|
+
this.events.emit({
|
|
507
|
+
type: "tool:end",
|
|
508
|
+
agent_name: context.agent_name,
|
|
509
|
+
tool_name: block.name,
|
|
510
|
+
result
|
|
511
|
+
});
|
|
512
|
+
const scan = PromptGuard.scan(result.content);
|
|
513
|
+
if (!scan.safe) {
|
|
514
|
+
logger.warn(
|
|
515
|
+
{ tool: block.name, patterns: scan.found },
|
|
516
|
+
"Potential prompt injection detected in tool output"
|
|
517
|
+
);
|
|
518
|
+
this.events.emit({
|
|
519
|
+
type: "security:injection_detected",
|
|
520
|
+
agent_name: context.agent_name,
|
|
521
|
+
tool_name: block.name,
|
|
522
|
+
patterns: scan.found
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
return {
|
|
526
|
+
type: "tool_result",
|
|
527
|
+
tool_use_id: block.id,
|
|
528
|
+
content: PromptGuard.wrap(block.name, result.content),
|
|
529
|
+
is_error: result.is_error
|
|
530
|
+
};
|
|
531
|
+
} catch (error) {
|
|
532
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
533
|
+
logger.error({ error: message, tool: block.name }, "Tool failed");
|
|
351
534
|
this.events.emit({
|
|
352
|
-
type: "
|
|
535
|
+
type: "tool:error",
|
|
353
536
|
agent_name: context.agent_name,
|
|
354
537
|
tool_name: block.name,
|
|
355
|
-
|
|
538
|
+
error: error instanceof Error ? error : new Error(message)
|
|
356
539
|
});
|
|
540
|
+
return {
|
|
541
|
+
type: "tool_result",
|
|
542
|
+
tool_use_id: block.id,
|
|
543
|
+
content: SecretsManager.redact(`Tool execution error: ${message}`),
|
|
544
|
+
is_error: true
|
|
545
|
+
};
|
|
357
546
|
}
|
|
358
|
-
|
|
359
|
-
type: "tool_result",
|
|
360
|
-
tool_use_id: block.id,
|
|
361
|
-
content: PromptGuard.wrap(block.name, result.content),
|
|
362
|
-
is_error: result.is_error
|
|
363
|
-
};
|
|
364
|
-
} catch (error) {
|
|
365
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
366
|
-
this.events.emit({
|
|
367
|
-
type: "tool:error",
|
|
368
|
-
agent_name: context.agent_name,
|
|
369
|
-
tool_name: block.name,
|
|
370
|
-
error: error instanceof Error ? error : new Error(message)
|
|
371
|
-
});
|
|
372
|
-
return {
|
|
373
|
-
type: "tool_result",
|
|
374
|
-
tool_use_id: block.id,
|
|
375
|
-
content: SecretsManager.redact(`Tool execution error: ${message}`),
|
|
376
|
-
is_error: true
|
|
377
|
-
};
|
|
378
|
-
}
|
|
547
|
+
});
|
|
379
548
|
}
|
|
380
549
|
};
|
|
381
550
|
function toolToDefinition(tool) {
|
|
@@ -509,13 +678,14 @@ var PostgresSessionStore = class {
|
|
|
509
678
|
session.updated_at
|
|
510
679
|
]
|
|
511
680
|
).catch((err) => {
|
|
512
|
-
|
|
513
|
-
|
|
681
|
+
logger.error(
|
|
682
|
+
{ error: err instanceof Error ? err.message : String(err), session: session.id },
|
|
683
|
+
"Failed to persist session to Postgres"
|
|
514
684
|
);
|
|
515
685
|
});
|
|
516
686
|
return session;
|
|
517
687
|
}
|
|
518
|
-
get(
|
|
688
|
+
get(_id) {
|
|
519
689
|
return void 0;
|
|
520
690
|
}
|
|
521
691
|
/**
|
|
@@ -545,8 +715,9 @@ var PostgresSessionStore = class {
|
|
|
545
715
|
WHERE id = $2`,
|
|
546
716
|
[JSON.stringify(messages), id]
|
|
547
717
|
).catch((err) => {
|
|
548
|
-
|
|
549
|
-
|
|
718
|
+
logger.error(
|
|
719
|
+
{ error: err instanceof Error ? err.message : String(err), session: id },
|
|
720
|
+
"Failed to update session in Postgres"
|
|
550
721
|
);
|
|
551
722
|
});
|
|
552
723
|
}
|
|
@@ -616,8 +787,9 @@ var PermissionGuard = class {
|
|
|
616
787
|
(p) => p === "shell" || p === "filesystem"
|
|
617
788
|
);
|
|
618
789
|
if (dangerous.length > 0) {
|
|
619
|
-
|
|
620
|
-
|
|
790
|
+
logger.warn(
|
|
791
|
+
{ voice: voice.name, permissions: dangerous },
|
|
792
|
+
"Voice has elevated permissions"
|
|
621
793
|
);
|
|
622
794
|
}
|
|
623
795
|
}
|
|
@@ -641,6 +813,10 @@ var TuttiRuntime = class _TuttiRuntime {
|
|
|
641
813
|
this._sessions,
|
|
642
814
|
this.semanticMemory
|
|
643
815
|
);
|
|
816
|
+
if (score.telemetry) {
|
|
817
|
+
initTelemetry(score.telemetry);
|
|
818
|
+
}
|
|
819
|
+
logger.info({ score: score.name, agents: Object.keys(score.agents) }, "Runtime initialized");
|
|
644
820
|
}
|
|
645
821
|
/**
|
|
646
822
|
* Create a runtime with async initialization (required for Postgres).
|
|
@@ -847,9 +1023,15 @@ var AgentSchema = z2.object({
|
|
|
847
1023
|
max_tool_calls: z2.number().int().positive("max_tool_calls must be a positive number").optional(),
|
|
848
1024
|
tool_timeout_ms: z2.number().int().positive("tool_timeout_ms must be a positive number").optional(),
|
|
849
1025
|
budget: BudgetSchema.optional(),
|
|
1026
|
+
streaming: z2.boolean().optional(),
|
|
850
1027
|
delegates: z2.array(z2.string()).optional(),
|
|
851
1028
|
role: z2.enum(["orchestrator", "specialist"]).optional()
|
|
852
1029
|
}).passthrough();
|
|
1030
|
+
var TelemetrySchema = z2.object({
|
|
1031
|
+
enabled: z2.boolean(),
|
|
1032
|
+
endpoint: z2.string().url("telemetry.endpoint must be a valid URL").optional(),
|
|
1033
|
+
headers: z2.record(z2.string(), z2.string()).optional()
|
|
1034
|
+
}).strict();
|
|
853
1035
|
var ScoreSchema = z2.object({
|
|
854
1036
|
provider: z2.object({ chat: z2.function() }).passthrough().refine((p) => typeof p.chat === "function", {
|
|
855
1037
|
message: "provider must have a chat() method \u2014 did you forget to pass a provider instance?"
|
|
@@ -861,7 +1043,8 @@ var ScoreSchema = z2.object({
|
|
|
861
1043
|
name: z2.string().optional(),
|
|
862
1044
|
description: z2.string().optional(),
|
|
863
1045
|
default_model: z2.string().optional(),
|
|
864
|
-
entry: z2.string().optional()
|
|
1046
|
+
entry: z2.string().optional(),
|
|
1047
|
+
telemetry: TelemetrySchema.optional()
|
|
865
1048
|
}).passthrough();
|
|
866
1049
|
function validateScore(config) {
|
|
867
1050
|
const result = ScoreSchema.safeParse(config);
|
|
@@ -958,6 +1141,7 @@ var AnthropicProvider = class {
|
|
|
958
1141
|
});
|
|
959
1142
|
} catch (error) {
|
|
960
1143
|
const msg = error instanceof Error ? error.message : String(error);
|
|
1144
|
+
logger.error({ error: msg, provider: "anthropic" }, "Provider request failed");
|
|
961
1145
|
throw new Error(
|
|
962
1146
|
`Anthropic API error: ${msg}
|
|
963
1147
|
Check that ANTHROPIC_API_KEY is set correctly in your .env file.`
|
|
@@ -987,6 +1171,90 @@ Check that ANTHROPIC_API_KEY is set correctly in your .env file.`
|
|
|
987
1171
|
}
|
|
988
1172
|
};
|
|
989
1173
|
}
|
|
1174
|
+
async *stream(request) {
|
|
1175
|
+
if (!request.model) {
|
|
1176
|
+
throw new Error(
|
|
1177
|
+
"AnthropicProvider requires a model on ChatRequest.\nSet model on the agent or default_model on the score."
|
|
1178
|
+
);
|
|
1179
|
+
}
|
|
1180
|
+
let raw;
|
|
1181
|
+
try {
|
|
1182
|
+
raw = await this.client.messages.create({
|
|
1183
|
+
model: request.model,
|
|
1184
|
+
max_tokens: request.max_tokens ?? 4096,
|
|
1185
|
+
system: request.system ?? "",
|
|
1186
|
+
messages: request.messages.map((msg) => ({
|
|
1187
|
+
role: msg.role,
|
|
1188
|
+
content: msg.content
|
|
1189
|
+
})),
|
|
1190
|
+
tools: request.tools?.map((tool) => ({
|
|
1191
|
+
name: tool.name,
|
|
1192
|
+
description: tool.description,
|
|
1193
|
+
input_schema: tool.input_schema
|
|
1194
|
+
})),
|
|
1195
|
+
...request.temperature != null && { temperature: request.temperature },
|
|
1196
|
+
...request.stop_sequences && { stop_sequences: request.stop_sequences },
|
|
1197
|
+
stream: true
|
|
1198
|
+
});
|
|
1199
|
+
} catch (error) {
|
|
1200
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1201
|
+
logger.error({ error: msg, provider: "anthropic" }, "Provider stream failed");
|
|
1202
|
+
throw new Error(
|
|
1203
|
+
`Anthropic API error: ${msg}
|
|
1204
|
+
Check that ANTHROPIC_API_KEY is set correctly in your .env file.`
|
|
1205
|
+
);
|
|
1206
|
+
}
|
|
1207
|
+
const toolBlocks = /* @__PURE__ */ new Map();
|
|
1208
|
+
let inputTokens = 0;
|
|
1209
|
+
let outputTokens = 0;
|
|
1210
|
+
let stopReason = "end_turn";
|
|
1211
|
+
for await (const event of raw) {
|
|
1212
|
+
if (event.type === "message_start") {
|
|
1213
|
+
inputTokens = event.message.usage.input_tokens;
|
|
1214
|
+
}
|
|
1215
|
+
if (event.type === "content_block_start") {
|
|
1216
|
+
if (event.content_block.type === "tool_use") {
|
|
1217
|
+
toolBlocks.set(event.index, {
|
|
1218
|
+
id: event.content_block.id,
|
|
1219
|
+
name: event.content_block.name,
|
|
1220
|
+
json: ""
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
if (event.type === "content_block_delta") {
|
|
1225
|
+
if (event.delta.type === "text_delta") {
|
|
1226
|
+
yield { type: "text", text: event.delta.text };
|
|
1227
|
+
}
|
|
1228
|
+
if (event.delta.type === "input_json_delta") {
|
|
1229
|
+
const block = toolBlocks.get(event.index);
|
|
1230
|
+
if (block) block.json += event.delta.partial_json;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
if (event.type === "content_block_stop") {
|
|
1234
|
+
const block = toolBlocks.get(event.index);
|
|
1235
|
+
if (block) {
|
|
1236
|
+
yield {
|
|
1237
|
+
type: "tool_use",
|
|
1238
|
+
tool: {
|
|
1239
|
+
id: block.id,
|
|
1240
|
+
name: block.name,
|
|
1241
|
+
input: block.json ? JSON.parse(block.json) : {}
|
|
1242
|
+
}
|
|
1243
|
+
};
|
|
1244
|
+
toolBlocks.delete(event.index);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
if (event.type === "message_delta") {
|
|
1248
|
+
outputTokens = event.usage.output_tokens;
|
|
1249
|
+
stopReason = event.delta.stop_reason ?? "end_turn";
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
yield {
|
|
1253
|
+
type: "usage",
|
|
1254
|
+
usage: { input_tokens: inputTokens, output_tokens: outputTokens },
|
|
1255
|
+
stop_reason: stopReason
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
990
1258
|
};
|
|
991
1259
|
|
|
992
1260
|
// src/providers/openai.ts
|
|
@@ -1070,6 +1338,7 @@ var OpenAIProvider = class {
|
|
|
1070
1338
|
});
|
|
1071
1339
|
} catch (error) {
|
|
1072
1340
|
const msg = error instanceof Error ? error.message : String(error);
|
|
1341
|
+
logger.error({ error: msg, provider: "openai" }, "Provider request failed");
|
|
1073
1342
|
throw new Error(
|
|
1074
1343
|
`OpenAI API error: ${msg}
|
|
1075
1344
|
Check that OPENAI_API_KEY is set correctly in your .env file.`
|
|
@@ -1114,6 +1383,112 @@ Check that OPENAI_API_KEY is set correctly in your .env file.`
|
|
|
1114
1383
|
}
|
|
1115
1384
|
};
|
|
1116
1385
|
}
|
|
1386
|
+
async *stream(request) {
|
|
1387
|
+
if (!request.model) {
|
|
1388
|
+
throw new Error(
|
|
1389
|
+
"OpenAIProvider requires a model on ChatRequest.\nSet model on the agent or default_model on the score."
|
|
1390
|
+
);
|
|
1391
|
+
}
|
|
1392
|
+
const messages = [];
|
|
1393
|
+
if (request.system) {
|
|
1394
|
+
messages.push({ role: "system", content: request.system });
|
|
1395
|
+
}
|
|
1396
|
+
for (const msg of request.messages) {
|
|
1397
|
+
if (msg.role === "user") {
|
|
1398
|
+
if (typeof msg.content === "string") {
|
|
1399
|
+
messages.push({ role: "user", content: msg.content });
|
|
1400
|
+
} else {
|
|
1401
|
+
for (const block of msg.content) {
|
|
1402
|
+
if (block.type === "tool_result") {
|
|
1403
|
+
messages.push({ role: "tool", tool_call_id: block.tool_use_id, content: block.content });
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
} else if (msg.role === "assistant") {
|
|
1408
|
+
if (typeof msg.content === "string") {
|
|
1409
|
+
messages.push({ role: "assistant", content: msg.content });
|
|
1410
|
+
} else {
|
|
1411
|
+
const textParts = msg.content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
1412
|
+
const toolCalls2 = msg.content.filter((b) => b.type === "tool_use").map((b) => {
|
|
1413
|
+
const block = b;
|
|
1414
|
+
return { id: block.id, type: "function", function: { name: block.name, arguments: JSON.stringify(block.input) } };
|
|
1415
|
+
});
|
|
1416
|
+
messages.push({ role: "assistant", content: textParts || null, ...toolCalls2.length > 0 && { tool_calls: toolCalls2 } });
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
const tools = request.tools?.map((tool) => ({
|
|
1421
|
+
type: "function",
|
|
1422
|
+
function: { name: tool.name, description: tool.description, parameters: tool.input_schema }
|
|
1423
|
+
}));
|
|
1424
|
+
let raw;
|
|
1425
|
+
try {
|
|
1426
|
+
raw = await this.client.chat.completions.create({
|
|
1427
|
+
model: request.model,
|
|
1428
|
+
messages,
|
|
1429
|
+
tools: tools && tools.length > 0 ? tools : void 0,
|
|
1430
|
+
max_tokens: request.max_tokens,
|
|
1431
|
+
temperature: request.temperature,
|
|
1432
|
+
stop: request.stop_sequences,
|
|
1433
|
+
stream: true,
|
|
1434
|
+
stream_options: { include_usage: true }
|
|
1435
|
+
});
|
|
1436
|
+
} catch (error) {
|
|
1437
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1438
|
+
logger.error({ error: msg, provider: "openai" }, "Provider stream failed");
|
|
1439
|
+
throw new Error(
|
|
1440
|
+
`OpenAI API error: ${msg}
|
|
1441
|
+
Check that OPENAI_API_KEY is set correctly in your .env file.`
|
|
1442
|
+
);
|
|
1443
|
+
}
|
|
1444
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
1445
|
+
let finishReason = "end_turn";
|
|
1446
|
+
for await (const chunk of raw) {
|
|
1447
|
+
const choice = chunk.choices[0];
|
|
1448
|
+
if (!choice) {
|
|
1449
|
+
if (chunk.usage) {
|
|
1450
|
+
yield {
|
|
1451
|
+
type: "usage",
|
|
1452
|
+
usage: { input_tokens: chunk.usage.prompt_tokens, output_tokens: chunk.usage.completion_tokens },
|
|
1453
|
+
stop_reason: finishReason
|
|
1454
|
+
};
|
|
1455
|
+
}
|
|
1456
|
+
continue;
|
|
1457
|
+
}
|
|
1458
|
+
if (choice.delta.content) {
|
|
1459
|
+
yield { type: "text", text: choice.delta.content };
|
|
1460
|
+
}
|
|
1461
|
+
if (choice.delta.tool_calls) {
|
|
1462
|
+
for (const tc of choice.delta.tool_calls) {
|
|
1463
|
+
if (tc.id) {
|
|
1464
|
+
toolCalls.set(tc.index, { id: tc.id, name: tc.function?.name ?? "", args: "" });
|
|
1465
|
+
}
|
|
1466
|
+
const existing = toolCalls.get(tc.index);
|
|
1467
|
+
if (existing && tc.function?.arguments) {
|
|
1468
|
+
existing.args += tc.function.arguments;
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
if (choice.finish_reason) {
|
|
1473
|
+
for (const tc of toolCalls.values()) {
|
|
1474
|
+
yield { type: "tool_use", tool: { id: tc.id, name: tc.name, input: JSON.parse(tc.args || "{}") } };
|
|
1475
|
+
}
|
|
1476
|
+
switch (choice.finish_reason) {
|
|
1477
|
+
case "tool_calls":
|
|
1478
|
+
finishReason = "tool_use";
|
|
1479
|
+
break;
|
|
1480
|
+
case "length":
|
|
1481
|
+
finishReason = "max_tokens";
|
|
1482
|
+
break;
|
|
1483
|
+
case "stop":
|
|
1484
|
+
finishReason = "end_turn";
|
|
1485
|
+
break;
|
|
1486
|
+
default:
|
|
1487
|
+
finishReason = "end_turn";
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1117
1492
|
};
|
|
1118
1493
|
|
|
1119
1494
|
// src/providers/gemini.ts
|
|
@@ -1206,6 +1581,7 @@ var GeminiProvider = class {
|
|
|
1206
1581
|
});
|
|
1207
1582
|
} catch (error) {
|
|
1208
1583
|
const msg = error instanceof Error ? error.message : String(error);
|
|
1584
|
+
logger.error({ error: msg, provider: "gemini" }, "Provider request failed");
|
|
1209
1585
|
throw new Error(
|
|
1210
1586
|
`Gemini API error: ${msg}
|
|
1211
1587
|
Check that GEMINI_API_KEY is set correctly in your .env file.`
|
|
@@ -1250,6 +1626,93 @@ Check that GEMINI_API_KEY is set correctly in your .env file.`
|
|
|
1250
1626
|
}
|
|
1251
1627
|
};
|
|
1252
1628
|
}
|
|
1629
|
+
async *stream(request) {
|
|
1630
|
+
const model = request.model ?? "gemini-2.0-flash";
|
|
1631
|
+
const tools = [];
|
|
1632
|
+
if (request.tools && request.tools.length > 0) {
|
|
1633
|
+
tools.push({
|
|
1634
|
+
functionDeclarations: request.tools.map((tool) => ({
|
|
1635
|
+
name: tool.name,
|
|
1636
|
+
description: tool.description,
|
|
1637
|
+
parameters: convertJsonSchemaToGemini(tool.input_schema)
|
|
1638
|
+
}))
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
1641
|
+
const generativeModel = this.client.getGenerativeModel({
|
|
1642
|
+
model,
|
|
1643
|
+
systemInstruction: request.system,
|
|
1644
|
+
tools: tools.length > 0 ? tools : void 0
|
|
1645
|
+
});
|
|
1646
|
+
const contents = [];
|
|
1647
|
+
for (const msg of request.messages) {
|
|
1648
|
+
if (msg.role === "user") {
|
|
1649
|
+
if (typeof msg.content === "string") {
|
|
1650
|
+
contents.push({ role: "user", parts: [{ text: msg.content }] });
|
|
1651
|
+
} else {
|
|
1652
|
+
const parts = [];
|
|
1653
|
+
for (const block of msg.content) {
|
|
1654
|
+
if (block.type === "tool_result") {
|
|
1655
|
+
parts.push({ functionResponse: { name: block.tool_use_id, response: { content: block.content } } });
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
if (parts.length > 0) contents.push({ role: "user", parts });
|
|
1659
|
+
}
|
|
1660
|
+
} else if (msg.role === "assistant") {
|
|
1661
|
+
if (typeof msg.content === "string") {
|
|
1662
|
+
contents.push({ role: "model", parts: [{ text: msg.content }] });
|
|
1663
|
+
} else {
|
|
1664
|
+
const parts = [];
|
|
1665
|
+
for (const block of msg.content) {
|
|
1666
|
+
if (block.type === "text") parts.push({ text: block.text });
|
|
1667
|
+
else if (block.type === "tool_use") parts.push({ functionCall: { name: block.name, args: block.input } });
|
|
1668
|
+
}
|
|
1669
|
+
if (parts.length > 0) contents.push({ role: "model", parts });
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
let result;
|
|
1674
|
+
try {
|
|
1675
|
+
result = await generativeModel.generateContentStream({
|
|
1676
|
+
contents,
|
|
1677
|
+
generationConfig: {
|
|
1678
|
+
maxOutputTokens: request.max_tokens,
|
|
1679
|
+
temperature: request.temperature,
|
|
1680
|
+
stopSequences: request.stop_sequences
|
|
1681
|
+
}
|
|
1682
|
+
});
|
|
1683
|
+
} catch (error) {
|
|
1684
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1685
|
+
logger.error({ error: msg, provider: "gemini" }, "Provider stream failed");
|
|
1686
|
+
throw new Error(
|
|
1687
|
+
`Gemini API error: ${msg}
|
|
1688
|
+
Check that GEMINI_API_KEY is set correctly in your .env file.`
|
|
1689
|
+
);
|
|
1690
|
+
}
|
|
1691
|
+
let hasToolCalls = false;
|
|
1692
|
+
for await (const chunk of result.stream) {
|
|
1693
|
+
const candidate = chunk.candidates?.[0];
|
|
1694
|
+
if (!candidate) continue;
|
|
1695
|
+
for (const part of candidate.content.parts) {
|
|
1696
|
+
if ("text" in part && part.text) {
|
|
1697
|
+
yield { type: "text", text: part.text };
|
|
1698
|
+
}
|
|
1699
|
+
if ("functionCall" in part && part.functionCall) {
|
|
1700
|
+
hasToolCalls = true;
|
|
1701
|
+
yield {
|
|
1702
|
+
type: "tool_use",
|
|
1703
|
+
tool: { id: part.functionCall.name, name: part.functionCall.name, input: part.functionCall.args ?? {} }
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
const response = await result.response;
|
|
1709
|
+
const usage = response.usageMetadata;
|
|
1710
|
+
yield {
|
|
1711
|
+
type: "usage",
|
|
1712
|
+
usage: { input_tokens: usage?.promptTokenCount ?? 0, output_tokens: usage?.candidatesTokenCount ?? 0 },
|
|
1713
|
+
stop_reason: hasToolCalls ? "tool_use" : "end_turn"
|
|
1714
|
+
};
|
|
1715
|
+
}
|
|
1253
1716
|
};
|
|
1254
1717
|
function convertJsonSchemaToGemini(schema) {
|
|
1255
1718
|
const type = schema.type;
|
|
@@ -1277,7 +1740,12 @@ export {
|
|
|
1277
1740
|
SecretsManager,
|
|
1278
1741
|
TokenBudget,
|
|
1279
1742
|
TuttiRuntime,
|
|
1743
|
+
TuttiTracer,
|
|
1744
|
+
createLogger,
|
|
1280
1745
|
defineScore,
|
|
1746
|
+
initTelemetry,
|
|
1747
|
+
logger,
|
|
1748
|
+
shutdownTelemetry,
|
|
1281
1749
|
validateScore
|
|
1282
1750
|
};
|
|
1283
1751
|
//# sourceMappingURL=index.js.map
|