@agentv/core 4.25.1 → 4.25.2-next.1

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.
@@ -0,0 +1,7 @@
1
+ import {
2
+ AgentvProvider
3
+ } from "./chunk-5XV3FAAD.js";
4
+ export {
5
+ AgentvProvider
6
+ };
7
+ //# sourceMappingURL=agentv-provider-MUIGGIP3.js.map
@@ -0,0 +1,616 @@
1
+ // src/evaluation/providers/llm-providers.ts
2
+ import {
3
+ complete as piComplete,
4
+ getModel as piGetModel,
5
+ registerBuiltInApiProviders
6
+ } from "@mariozechner/pi-ai";
7
+ registerBuiltInApiProviders();
8
+ var DEFAULT_SYSTEM_PROMPT = "You are a careful assistant. Follow all provided instructions and do not fabricate results.";
9
+ var OpenAIProvider = class {
10
+ id;
11
+ kind = "openai";
12
+ targetName;
13
+ piModel;
14
+ defaults;
15
+ retryConfig;
16
+ apiKey;
17
+ constructor(targetName, config) {
18
+ this.id = `openai:${targetName}`;
19
+ this.targetName = targetName;
20
+ this.apiKey = config.apiKey;
21
+ this.defaults = {
22
+ temperature: config.temperature,
23
+ maxOutputTokens: config.maxOutputTokens
24
+ };
25
+ this.retryConfig = config.retry;
26
+ this.piModel = resolvePiModel({
27
+ providerName: "openai",
28
+ apiId: config.apiFormat === "responses" ? "openai-responses" : "openai-completions",
29
+ modelId: config.model,
30
+ baseUrl: config.baseURL
31
+ });
32
+ }
33
+ async invoke(request) {
34
+ return invokePiAi({
35
+ model: this.piModel,
36
+ apiKey: this.apiKey,
37
+ request,
38
+ defaults: this.defaults,
39
+ retryConfig: this.retryConfig
40
+ });
41
+ }
42
+ };
43
+ var OpenRouterProvider = class {
44
+ id;
45
+ kind = "openrouter";
46
+ targetName;
47
+ piModel;
48
+ defaults;
49
+ retryConfig;
50
+ apiKey;
51
+ constructor(targetName, config) {
52
+ this.id = `openrouter:${targetName}`;
53
+ this.targetName = targetName;
54
+ this.apiKey = config.apiKey;
55
+ this.defaults = {
56
+ temperature: config.temperature,
57
+ maxOutputTokens: config.maxOutputTokens
58
+ };
59
+ this.retryConfig = config.retry;
60
+ this.piModel = resolvePiModel({
61
+ providerName: "openrouter",
62
+ apiId: "openai-completions",
63
+ modelId: config.model,
64
+ baseUrl: "https://openrouter.ai/api/v1"
65
+ });
66
+ }
67
+ async invoke(request) {
68
+ return invokePiAi({
69
+ model: this.piModel,
70
+ apiKey: this.apiKey,
71
+ request,
72
+ defaults: this.defaults,
73
+ retryConfig: this.retryConfig
74
+ });
75
+ }
76
+ };
77
+ var AnthropicProvider = class {
78
+ id;
79
+ kind = "anthropic";
80
+ targetName;
81
+ piModel;
82
+ defaults;
83
+ retryConfig;
84
+ apiKey;
85
+ thinkingBudget;
86
+ constructor(targetName, config) {
87
+ this.id = `anthropic:${targetName}`;
88
+ this.targetName = targetName;
89
+ this.apiKey = config.apiKey;
90
+ this.thinkingBudget = config.thinkingBudget;
91
+ this.defaults = {
92
+ temperature: config.temperature,
93
+ maxOutputTokens: config.maxOutputTokens,
94
+ thinkingBudget: config.thinkingBudget
95
+ };
96
+ this.retryConfig = config.retry;
97
+ this.piModel = resolvePiModel({
98
+ providerName: "anthropic",
99
+ apiId: "anthropic-messages",
100
+ modelId: config.model
101
+ });
102
+ }
103
+ async invoke(request) {
104
+ const providerOptions = this.thinkingBudget !== void 0 ? { thinkingEnabled: true, thinkingBudgetTokens: this.thinkingBudget } : void 0;
105
+ return invokePiAi({
106
+ model: this.piModel,
107
+ apiKey: this.apiKey,
108
+ request,
109
+ defaults: this.defaults,
110
+ retryConfig: this.retryConfig,
111
+ ...providerOptions ? { providerOptions } : {}
112
+ });
113
+ }
114
+ };
115
+ var GeminiProvider = class {
116
+ id;
117
+ kind = "gemini";
118
+ targetName;
119
+ piModel;
120
+ defaults;
121
+ retryConfig;
122
+ apiKey;
123
+ constructor(targetName, config) {
124
+ this.id = `gemini:${targetName}`;
125
+ this.targetName = targetName;
126
+ this.apiKey = config.apiKey;
127
+ this.defaults = {
128
+ temperature: config.temperature,
129
+ maxOutputTokens: config.maxOutputTokens
130
+ };
131
+ this.retryConfig = config.retry;
132
+ this.piModel = resolvePiModel({
133
+ providerName: "google",
134
+ apiId: "google-generative-ai",
135
+ modelId: config.model
136
+ });
137
+ }
138
+ async invoke(request) {
139
+ return invokePiAi({
140
+ model: this.piModel,
141
+ apiKey: this.apiKey,
142
+ request,
143
+ defaults: this.defaults,
144
+ retryConfig: this.retryConfig
145
+ });
146
+ }
147
+ };
148
+ var AzureProvider = class {
149
+ id;
150
+ kind = "azure";
151
+ targetName;
152
+ piModel;
153
+ defaults;
154
+ retryConfig;
155
+ apiKey;
156
+ providerOptions;
157
+ constructor(targetName, config) {
158
+ this.id = `azure:${targetName}`;
159
+ this.targetName = targetName;
160
+ this.apiKey = config.apiKey;
161
+ this.defaults = {
162
+ temperature: config.temperature,
163
+ maxOutputTokens: config.maxOutputTokens
164
+ };
165
+ this.retryConfig = config.retry;
166
+ const trimmed = config.resourceName.trim();
167
+ const isFullUrl = /^https?:\/\//i.test(trimmed);
168
+ const baseUrl = isFullUrl ? buildAzureBaseUrl(trimmed) : void 0;
169
+ this.providerOptions = {
170
+ ...baseUrl ? { azureBaseUrl: baseUrl } : { azureResourceName: trimmed },
171
+ ...config.version ? { azureApiVersion: config.version } : {}
172
+ };
173
+ this.piModel = resolvePiModel({
174
+ providerName: "azure-openai-responses",
175
+ apiId: "azure-openai-responses",
176
+ // The "model id" for Azure is the deployment name.
177
+ modelId: config.deploymentName,
178
+ ...baseUrl ? { baseUrl } : {}
179
+ });
180
+ }
181
+ async invoke(request) {
182
+ return invokePiAi({
183
+ model: this.piModel,
184
+ apiKey: this.apiKey,
185
+ request,
186
+ defaults: this.defaults,
187
+ retryConfig: this.retryConfig,
188
+ providerOptions: this.providerOptions
189
+ });
190
+ }
191
+ };
192
+ function buildAzureBaseUrl(input) {
193
+ const trimmed = input.replace(/\/+$/, "");
194
+ if (trimmed.endsWith("/openai/v1")) return trimmed;
195
+ if (trimmed.endsWith("/openai")) return `${trimmed}/v1`;
196
+ return `${trimmed}/openai/v1`;
197
+ }
198
+ async function invokePiAi(options) {
199
+ const { model, apiKey, request, defaults, retryConfig, providerOptions } = options;
200
+ const tools = request.tools && request.tools.length > 0 ? request.tools : void 0;
201
+ const maxSteps = tools ? Math.max(1, request.maxSteps ?? 1) : 1;
202
+ const { systemPrompt, messages } = chatPromptToPiContext(buildChatPrompt(request));
203
+ if (request.images && request.images.length > 0) {
204
+ attachImagesToLastUserMessage(messages, request.images);
205
+ }
206
+ const piTools = tools ? tools.map((t) => ({
207
+ name: t.name,
208
+ description: t.description,
209
+ parameters: t.parameters
210
+ })) : void 0;
211
+ const ctx = { systemPrompt, messages, ...piTools ? { tools: piTools } : {} };
212
+ const { temperature, maxOutputTokens } = resolveModelSettings(request, defaults);
213
+ const callOptions = {
214
+ ...apiKey !== void 0 ? { apiKey } : {},
215
+ temperature,
216
+ ...maxOutputTokens !== void 0 ? { maxTokens: maxOutputTokens } : {},
217
+ signal: request.signal,
218
+ ...providerOptions ?? {}
219
+ };
220
+ const startTime = (/* @__PURE__ */ new Date()).toISOString();
221
+ const startMs = Date.now();
222
+ const aggregateUsage = { input: 0, output: 0, cacheRead: 0, cost: 0 };
223
+ let stepCount = 0;
224
+ let toolCallCount = 0;
225
+ let result = await withRetry(
226
+ () => piComplete(model, ctx, callOptions),
227
+ retryConfig,
228
+ request.signal
229
+ );
230
+ ctx.messages.push(result);
231
+ stepCount = 1;
232
+ accumulateUsage(aggregateUsage, result.usage);
233
+ while (tools) {
234
+ const calls = result.content.filter(
235
+ (b) => b.type === "toolCall"
236
+ );
237
+ if (calls.length === 0) break;
238
+ if (stepCount >= maxSteps) break;
239
+ toolCallCount += calls.length;
240
+ for (const call of calls) {
241
+ const tool = tools.find((t) => t.name === call.name);
242
+ let output;
243
+ let isError = false;
244
+ try {
245
+ if (!tool) {
246
+ throw new Error(`pi-ai adapter: model called unknown tool '${call.name}'`);
247
+ }
248
+ output = await tool.execute(call.arguments);
249
+ } catch (err) {
250
+ output = err instanceof Error ? err.message : String(err);
251
+ isError = true;
252
+ }
253
+ ctx.messages.push({
254
+ role: "toolResult",
255
+ toolCallId: call.id,
256
+ toolName: call.name,
257
+ content: [
258
+ { type: "text", text: typeof output === "string" ? output : JSON.stringify(output) }
259
+ ],
260
+ isError,
261
+ timestamp: Date.now()
262
+ });
263
+ }
264
+ result = await withRetry(
265
+ () => piComplete(model, ctx, callOptions),
266
+ retryConfig,
267
+ request.signal
268
+ );
269
+ ctx.messages.push(result);
270
+ stepCount += 1;
271
+ accumulateUsage(aggregateUsage, result.usage);
272
+ }
273
+ const endTime = (/* @__PURE__ */ new Date()).toISOString();
274
+ const durationMs = Date.now() - startMs;
275
+ return mapPiResponse(result, {
276
+ durationMs,
277
+ startTime,
278
+ endTime,
279
+ aggregateUsage,
280
+ steps: tools ? { count: stepCount, toolCallCount } : void 0
281
+ });
282
+ }
283
+ function accumulateUsage(agg, u) {
284
+ agg.input += u.input;
285
+ agg.output += u.output;
286
+ agg.cacheRead += u.cacheRead;
287
+ agg.cost += u.cost.total;
288
+ }
289
+ function resolvePiModel(args) {
290
+ const { providerName, apiId, modelId, baseUrl } = args;
291
+ let model;
292
+ try {
293
+ model = piGetModel(providerName, modelId);
294
+ } catch {
295
+ model = void 0;
296
+ }
297
+ if (!model) {
298
+ const fallbackBaseUrl = baseUrl ?? defaultBaseUrlFor(providerName);
299
+ if (!fallbackBaseUrl) {
300
+ throw new Error(
301
+ `pi-ai adapter cannot resolve a baseUrl for provider '${providerName}' / model '${modelId}'. Either set the target's baseUrl/endpoint or use a model id pi-ai recognizes.`
302
+ );
303
+ }
304
+ model = {
305
+ id: modelId,
306
+ name: modelId,
307
+ api: apiId,
308
+ provider: providerName,
309
+ baseUrl: fallbackBaseUrl,
310
+ reasoning: false,
311
+ input: ["text"],
312
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
313
+ contextWindow: 128e3,
314
+ maxTokens: 16384
315
+ };
316
+ }
317
+ if (model.api !== apiId) {
318
+ model = { ...model, api: apiId };
319
+ }
320
+ if (baseUrl) {
321
+ model = { ...model, baseUrl };
322
+ }
323
+ return model;
324
+ }
325
+ function defaultBaseUrlFor(providerName) {
326
+ if (providerName === "openai") return "https://api.openai.com/v1";
327
+ if (providerName === "openrouter") return "https://openrouter.ai/api/v1";
328
+ return void 0;
329
+ }
330
+ function chatPromptToPiContext(chatPrompt) {
331
+ const systemSegments = [];
332
+ const messages = [];
333
+ const now = Date.now();
334
+ for (const message of chatPrompt) {
335
+ if (message.role === "system") {
336
+ systemSegments.push(message.content);
337
+ continue;
338
+ }
339
+ if (message.role === "user") {
340
+ messages.push({ role: "user", content: message.content, timestamp: now });
341
+ continue;
342
+ }
343
+ if (message.role === "assistant") {
344
+ messages.push({
345
+ role: "assistant",
346
+ content: [{ type: "text", text: message.content }],
347
+ api: "",
348
+ provider: "",
349
+ model: "",
350
+ usage: {
351
+ input: 0,
352
+ output: 0,
353
+ cacheRead: 0,
354
+ cacheWrite: 0,
355
+ totalTokens: 0,
356
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
357
+ },
358
+ stopReason: "stop",
359
+ timestamp: now
360
+ });
361
+ continue;
362
+ }
363
+ if (message.role === "tool" || message.role === "function") {
364
+ const prefix = message.name ? `@[${message.name}]: ` : "@[Tool]: ";
365
+ messages.push({
366
+ role: "assistant",
367
+ content: [{ type: "text", text: `${prefix}${message.content}` }],
368
+ api: "",
369
+ provider: "",
370
+ model: "",
371
+ usage: {
372
+ input: 0,
373
+ output: 0,
374
+ cacheRead: 0,
375
+ cacheWrite: 0,
376
+ totalTokens: 0,
377
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
378
+ },
379
+ stopReason: "stop",
380
+ timestamp: now
381
+ });
382
+ continue;
383
+ }
384
+ throw new Error(`pi-ai adapter received unsupported message role '${message.role}'.`);
385
+ }
386
+ return {
387
+ systemPrompt: systemSegments.length > 0 ? systemSegments.join("\n\n") : void 0,
388
+ messages
389
+ };
390
+ }
391
+ function attachImagesToLastUserMessage(messages, images) {
392
+ if (!images || images.length === 0) return;
393
+ for (let i = messages.length - 1; i >= 0; i--) {
394
+ const m = messages[i];
395
+ if (m.role !== "user") continue;
396
+ const text = typeof m.content === "string" ? m.content : "";
397
+ messages[i] = {
398
+ ...m,
399
+ content: [
400
+ ...text ? [{ type: "text", text }] : [],
401
+ ...images.map((img) => ({
402
+ type: "image",
403
+ data: img.source,
404
+ mimeType: img.media_type
405
+ }))
406
+ ]
407
+ };
408
+ return;
409
+ }
410
+ messages.push({
411
+ role: "user",
412
+ content: images.map((img) => ({
413
+ type: "image",
414
+ data: img.source,
415
+ mimeType: img.media_type
416
+ })),
417
+ timestamp: Date.now()
418
+ });
419
+ }
420
+ function mapPiResponse(result, timing) {
421
+ const text = result.content.filter((b) => b.type === "text").map((b) => b.text).join("");
422
+ const cached = timing.aggregateUsage.cacheRead > 0 ? timing.aggregateUsage.cacheRead : void 0;
423
+ const tokenUsage = {
424
+ input: timing.aggregateUsage.input,
425
+ output: timing.aggregateUsage.output,
426
+ ...cached !== void 0 ? { cached } : {}
427
+ };
428
+ const costUsd = timing.aggregateUsage.cost > 0 ? timing.aggregateUsage.cost : void 0;
429
+ return {
430
+ raw: result,
431
+ usage: toJsonObject(result.usage),
432
+ output: [{ role: "assistant", content: text }],
433
+ tokenUsage,
434
+ ...costUsd !== void 0 ? { costUsd } : {},
435
+ durationMs: timing.durationMs,
436
+ startTime: timing.startTime,
437
+ endTime: timing.endTime,
438
+ ...timing.steps ? { steps: timing.steps } : {}
439
+ };
440
+ }
441
+ function buildChatPrompt(request) {
442
+ const provided = request.chatPrompt?.length ? request.chatPrompt : void 0;
443
+ if (provided) {
444
+ const hasSystemMessage = provided.some((message) => message.role === "system");
445
+ if (hasSystemMessage) {
446
+ return provided;
447
+ }
448
+ const systemContent2 = resolveSystemContent(request);
449
+ return [{ role: "system", content: systemContent2 }, ...provided];
450
+ }
451
+ const systemContent = resolveSystemContent(request);
452
+ const userContent = request.question.trim();
453
+ return [
454
+ { role: "system", content: systemContent },
455
+ { role: "user", content: userContent }
456
+ ];
457
+ }
458
+ function resolveSystemContent(request) {
459
+ if (request.systemPrompt && request.systemPrompt.trim().length > 0) {
460
+ return request.systemPrompt.trim();
461
+ }
462
+ return DEFAULT_SYSTEM_PROMPT;
463
+ }
464
+ function resolveModelSettings(request, defaults) {
465
+ return {
466
+ temperature: request.temperature ?? defaults.temperature,
467
+ maxOutputTokens: request.maxOutputTokens ?? defaults.maxOutputTokens
468
+ };
469
+ }
470
+ function toJsonObject(value) {
471
+ if (!value || typeof value !== "object") {
472
+ return void 0;
473
+ }
474
+ try {
475
+ return JSON.parse(JSON.stringify(value));
476
+ } catch {
477
+ return void 0;
478
+ }
479
+ }
480
+ function extractStatus(error) {
481
+ if (!error || typeof error !== "object") return void 0;
482
+ const candidate = error;
483
+ const directStatus = candidate.status ?? candidate.statusCode;
484
+ if (typeof directStatus === "number" && Number.isFinite(directStatus)) {
485
+ return directStatus;
486
+ }
487
+ const responseStatus = typeof candidate.response === "object" && candidate.response ? candidate.response.status : void 0;
488
+ if (typeof responseStatus === "number" && Number.isFinite(responseStatus)) {
489
+ return responseStatus;
490
+ }
491
+ const message = typeof candidate.message === "string" ? candidate.message : void 0;
492
+ if (message) {
493
+ const match = message.match(/HTTP\s+(\d{3})/i);
494
+ if (match) {
495
+ const parsed = Number.parseInt(match[1], 10);
496
+ if (Number.isFinite(parsed)) return parsed;
497
+ }
498
+ }
499
+ return void 0;
500
+ }
501
+ function isNetworkError(error) {
502
+ if (!error || typeof error !== "object") return false;
503
+ const candidate = error;
504
+ if (candidate.name === "AbortError") return false;
505
+ const code = candidate.code;
506
+ if (typeof code === "string" && /^E(AI|CONN|HOST|NET|PIPE|TIME|REFUSED|RESET)/i.test(code)) {
507
+ return true;
508
+ }
509
+ const message = typeof candidate.message === "string" ? candidate.message : void 0;
510
+ if (message && /(network|fetch failed|ECONNRESET|ENOTFOUND|EAI_AGAIN|ETIMEDOUT|ECONNREFUSED)/i.test(message)) {
511
+ return true;
512
+ }
513
+ return false;
514
+ }
515
+ function isRetryableError(error, retryableStatusCodes) {
516
+ const status = extractStatus(error);
517
+ if (status === 401 || status === 403) return false;
518
+ if (typeof status === "number") return retryableStatusCodes.includes(status);
519
+ return isNetworkError(error);
520
+ }
521
+ function calculateRetryDelay(attempt, config) {
522
+ const delay = Math.min(
523
+ config.maxDelayMs,
524
+ config.initialDelayMs * config.backoffFactor ** attempt
525
+ );
526
+ return delay * (0.75 + Math.random() * 0.5);
527
+ }
528
+ async function sleep(ms) {
529
+ return new Promise((resolve) => setTimeout(resolve, ms));
530
+ }
531
+ async function withRetry(fn, retryConfig, signal) {
532
+ const config = {
533
+ maxRetries: retryConfig?.maxRetries ?? 3,
534
+ initialDelayMs: retryConfig?.initialDelayMs ?? 1e3,
535
+ maxDelayMs: retryConfig?.maxDelayMs ?? 6e4,
536
+ backoffFactor: retryConfig?.backoffFactor ?? 2,
537
+ retryableStatusCodes: retryConfig?.retryableStatusCodes ?? [500, 408, 429, 502, 503, 504]
538
+ };
539
+ let lastError;
540
+ for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
541
+ if (signal?.aborted) {
542
+ throw new Error(`Request aborted: ${signal.reason ?? "Unknown reason"}`);
543
+ }
544
+ try {
545
+ return await fn();
546
+ } catch (error) {
547
+ lastError = error;
548
+ if (attempt >= config.maxRetries) break;
549
+ if (!isRetryableError(error, config.retryableStatusCodes)) throw error;
550
+ const delay = calculateRetryDelay(attempt, config);
551
+ await sleep(delay);
552
+ }
553
+ }
554
+ throw lastError;
555
+ }
556
+
557
+ // src/evaluation/providers/agentv-provider.ts
558
+ var AgentvProvider = class {
559
+ id;
560
+ kind = "agentv";
561
+ targetName;
562
+ piModel;
563
+ defaults;
564
+ constructor(targetName, config) {
565
+ this.id = `agentv:${targetName}`;
566
+ this.targetName = targetName;
567
+ const { providerName, apiId, modelId } = parseAgentvModel(config.model);
568
+ this.piModel = resolvePiModel({ providerName, apiId, modelId });
569
+ this.defaults = { temperature: config.temperature };
570
+ }
571
+ async invoke(request) {
572
+ return invokePiAi({
573
+ model: this.piModel,
574
+ request,
575
+ defaults: this.defaults
576
+ });
577
+ }
578
+ };
579
+ function parseAgentvModel(model) {
580
+ const colonIndex = model.indexOf(":");
581
+ if (colonIndex === -1) {
582
+ throw new Error(
583
+ `Invalid agentv model "${model}". Expected "provider:model" (e.g., "openai:gpt-5-mini").`
584
+ );
585
+ }
586
+ const provider = model.slice(0, colonIndex);
587
+ const modelId = model.slice(colonIndex + 1);
588
+ switch (provider) {
589
+ case "openai":
590
+ return { providerName: "openai", apiId: "openai-completions", modelId };
591
+ case "anthropic":
592
+ return { providerName: "anthropic", apiId: "anthropic-messages", modelId };
593
+ case "azure":
594
+ return {
595
+ providerName: "azure-openai-responses",
596
+ apiId: "azure-openai-responses",
597
+ modelId
598
+ };
599
+ case "google":
600
+ return { providerName: "google", apiId: "google-generative-ai", modelId };
601
+ default:
602
+ throw new Error(
603
+ `Unsupported agentv provider "${provider}" in "${model}". Supported: openai, anthropic, azure, google.`
604
+ );
605
+ }
606
+ }
607
+
608
+ export {
609
+ OpenAIProvider,
610
+ OpenRouterProvider,
611
+ AnthropicProvider,
612
+ GeminiProvider,
613
+ AzureProvider,
614
+ AgentvProvider
615
+ };
616
+ //# sourceMappingURL=chunk-5XV3FAAD.js.map