@wrongstack/providers 0.1.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.js ADDED
@@ -0,0 +1,1206 @@
1
+ import { safeParse, ProviderError, sanitizeJsonString } from '@wrongstack/core';
2
+
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __esm = (fn, res) => function __init() {
6
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
7
+ };
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+
13
+ // src/aggregate.ts
14
+ var aggregate_exports = {};
15
+ __export(aggregate_exports, {
16
+ aggregateStream: () => aggregateStream
17
+ });
18
+ async function aggregateStream(stream, onEvent) {
19
+ let model = "";
20
+ let stopReason = "end_turn";
21
+ let usage = { input: 0, output: 0 };
22
+ const textBuffers = [];
23
+ let currentTextIndex = -1;
24
+ const toolBuffers = /* @__PURE__ */ new Map();
25
+ const blockOrder = [];
26
+ for await (const ev of stream) {
27
+ if (onEvent) onEvent(ev);
28
+ switch (ev.type) {
29
+ case "message_start":
30
+ model = ev.model;
31
+ break;
32
+ case "text_delta":
33
+ if (currentTextIndex === -1) {
34
+ currentTextIndex = textBuffers.length;
35
+ textBuffers.push("");
36
+ blockOrder.push({ kind: "text", idx: currentTextIndex });
37
+ }
38
+ textBuffers[currentTextIndex] = (textBuffers[currentTextIndex] ?? "") + ev.text;
39
+ break;
40
+ case "tool_use_start":
41
+ currentTextIndex = -1;
42
+ toolBuffers.set(ev.id, { name: ev.name, partial: "" });
43
+ blockOrder.push({ kind: "tool", id: ev.id });
44
+ break;
45
+ case "tool_use_input_delta": {
46
+ const b = toolBuffers.get(ev.id);
47
+ if (b) b.partial += ev.partial;
48
+ break;
49
+ }
50
+ case "tool_use_stop": {
51
+ const b = toolBuffers.get(ev.id);
52
+ if (b) {
53
+ if (ev.input !== void 0) {
54
+ b.input = ev.input;
55
+ } else if (b.partial) {
56
+ const parsed = safeParse(b.partial);
57
+ b.input = parsed.ok ? parsed.value : { _raw: b.partial };
58
+ } else {
59
+ b.input = {};
60
+ }
61
+ }
62
+ currentTextIndex = -1;
63
+ break;
64
+ }
65
+ case "message_stop":
66
+ stopReason = ev.stopReason;
67
+ usage = ev.usage;
68
+ break;
69
+ }
70
+ }
71
+ const content = [];
72
+ for (const b of blockOrder) {
73
+ if (b.kind === "text") {
74
+ const text = textBuffers[b.idx] ?? "";
75
+ if (text) content.push({ type: "text", text });
76
+ } else {
77
+ const tb = toolBuffers.get(b.id);
78
+ if (tb) {
79
+ content.push({
80
+ type: "tool_use",
81
+ id: b.id,
82
+ name: tb.name,
83
+ input: tb.input ?? {}
84
+ });
85
+ }
86
+ }
87
+ }
88
+ if (content.length === 0) content.push({ type: "text", text: "" });
89
+ return { content, stopReason, usage, model };
90
+ }
91
+ var init_aggregate = __esm({
92
+ "src/aggregate.ts"() {
93
+ }
94
+ });
95
+ function parseProviderHttpError(providerId, status, rawText) {
96
+ const body = parseBody(rawText);
97
+ const retryable = isRetryable(status, body.type);
98
+ const message = `${providerId} HTTP ${status}`;
99
+ return new ProviderError(message, status, retryable, providerId, { body });
100
+ }
101
+ function parseBody(rawText) {
102
+ const raw = rawText.slice(0, 2e3);
103
+ const body = { raw };
104
+ if (!rawText.trim()) return body;
105
+ let parsed;
106
+ try {
107
+ parsed = JSON.parse(rawText);
108
+ } catch {
109
+ return body;
110
+ }
111
+ if (!isRecord(parsed)) return body;
112
+ const errField = parsed["error"];
113
+ if (isRecord(errField)) {
114
+ const t = stringOf(errField["type"]) ?? stringOf(errField["status"]);
115
+ const m = stringOf(errField["message"]);
116
+ if (t) body.type = t;
117
+ if (m) body.message = m;
118
+ } else if (typeof errField === "string") {
119
+ body.message = errField;
120
+ }
121
+ if (!body.type) {
122
+ const t = stringOf(parsed["type"]);
123
+ if (t && t !== "error") body.type = t;
124
+ }
125
+ if (!body.message) {
126
+ const m = stringOf(parsed["message"]);
127
+ if (m) body.message = m;
128
+ }
129
+ const reqId = stringOf(parsed["request_id"]) ?? stringOf(parsed["requestId"]) ?? stringOf(parsed["id"]);
130
+ if (reqId) body.requestId = reqId;
131
+ return body;
132
+ }
133
+ function isRetryable(status, type) {
134
+ if (status === 0) return true;
135
+ if (status === 408 || status === 429 || status === 529) return true;
136
+ if (status >= 500 && status < 600) return true;
137
+ if (type === "overloaded_error" || type === "rate_limit_error") return true;
138
+ return false;
139
+ }
140
+ function isRecord(v) {
141
+ return typeof v === "object" && v !== null && !Array.isArray(v);
142
+ }
143
+ function stringOf(v) {
144
+ return typeof v === "string" && v.length > 0 ? v : void 0;
145
+ }
146
+
147
+ // src/tool-format/to-anthropic.ts
148
+ function toolsToAnthropic(tools) {
149
+ return tools.map((t) => ({
150
+ name: t.name,
151
+ description: t.description,
152
+ input_schema: t.inputSchema ?? {
153
+ type: "object",
154
+ properties: {}
155
+ }
156
+ }));
157
+ }
158
+
159
+ // src/stop-reason.ts
160
+ function normalizeAnthropic(stop) {
161
+ switch (stop) {
162
+ case "end_turn":
163
+ return "end_turn";
164
+ case "tool_use":
165
+ return "tool_use";
166
+ case "max_tokens":
167
+ return "max_tokens";
168
+ case "stop_sequence":
169
+ return "stop_sequence";
170
+ case "refusal":
171
+ return "refusal";
172
+ default:
173
+ return "end_turn";
174
+ }
175
+ }
176
+ function normalizeOpenAI(stop) {
177
+ switch (stop) {
178
+ case "stop":
179
+ return "end_turn";
180
+ case "tool_calls":
181
+ case "function_call":
182
+ return "tool_use";
183
+ case "length":
184
+ return "max_tokens";
185
+ case "content_filter":
186
+ return "refusal";
187
+ default:
188
+ return "end_turn";
189
+ }
190
+ }
191
+ function normalizeGemini(stop) {
192
+ switch (stop) {
193
+ case "SAFETY":
194
+ case "RECITATION":
195
+ case "hallucination":
196
+ return "refusal";
197
+ case "stop":
198
+ case "STOP":
199
+ return "end_turn";
200
+ case "max_tokens":
201
+ case "MAX_TOKENS":
202
+ return "max_tokens";
203
+ case "tool_use":
204
+ case "TOOL_USE":
205
+ return "tool_use";
206
+ default:
207
+ return "end_turn";
208
+ }
209
+ }
210
+
211
+ // src/sse.ts
212
+ async function* parseSSE(body) {
213
+ if (!body) return;
214
+ const decoder = new TextDecoder("utf-8");
215
+ let buffer = "";
216
+ let event = "message";
217
+ const dataLines = [];
218
+ const flush = () => {
219
+ if (dataLines.length === 0 && event === "message") return void 0;
220
+ const data = dataLines.join("\n");
221
+ const msg = { event, data };
222
+ event = "message";
223
+ dataLines.length = 0;
224
+ return msg;
225
+ };
226
+ const processLine = (line) => {
227
+ if (line === "") return flush();
228
+ if (line.startsWith(":")) return void 0;
229
+ const colonIdx = line.indexOf(":");
230
+ let field;
231
+ let value;
232
+ if (colonIdx === -1) {
233
+ field = line;
234
+ value = "";
235
+ } else {
236
+ field = line.slice(0, colonIdx);
237
+ value = line.slice(colonIdx + 1);
238
+ if (value.startsWith(" ")) value = value.slice(1);
239
+ }
240
+ if (field === "event") event = value || "message";
241
+ else if (field === "data") dataLines.push(value);
242
+ return void 0;
243
+ };
244
+ if (isNodeReadable(body)) {
245
+ for await (const chunk of body) {
246
+ buffer += typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
247
+ const lines = splitBuffer(buffer);
248
+ buffer = lines.tail;
249
+ for (const line of lines.lines) {
250
+ const msg = processLine(line);
251
+ if (msg) yield msg;
252
+ }
253
+ }
254
+ } else {
255
+ const reader = body.getReader();
256
+ try {
257
+ for (; ; ) {
258
+ const { done, value } = await reader.read();
259
+ if (done) break;
260
+ buffer += decoder.decode(value, { stream: true });
261
+ const lines = splitBuffer(buffer);
262
+ buffer = lines.tail;
263
+ for (const line of lines.lines) {
264
+ const msg = processLine(line);
265
+ if (msg) yield msg;
266
+ }
267
+ }
268
+ } finally {
269
+ reader.releaseLock();
270
+ }
271
+ }
272
+ if (buffer.length > 0) {
273
+ const msg = processLine(buffer);
274
+ if (msg) yield msg;
275
+ }
276
+ const final = flush();
277
+ if (final) yield final;
278
+ }
279
+ function splitBuffer(buf) {
280
+ const norm = buf.replace(/\r\n/g, "\n");
281
+ const parts = norm.split("\n");
282
+ const tail = parts.pop() ?? "";
283
+ return { lines: parts, tail };
284
+ }
285
+ function isNodeReadable(b) {
286
+ return !!b && typeof b === "object" && typeof b.pipe === "function" && typeof b.on === "function";
287
+ }
288
+ async function safeText(res) {
289
+ try {
290
+ return await res.text();
291
+ } catch {
292
+ return "";
293
+ }
294
+ }
295
+ var WireAdapter = class {
296
+ constructor(apiKey, baseUrl, fetchImpl = fetch) {
297
+ this.apiKey = apiKey;
298
+ this.baseUrl = baseUrl;
299
+ this.fetchImpl = fetchImpl;
300
+ if (!apiKey) throw new Error(`${this.constructor.name}: apiKey required`);
301
+ }
302
+ apiKey;
303
+ baseUrl;
304
+ fetchImpl;
305
+ async complete(req, opts) {
306
+ const { aggregateStream: aggregateStream2 } = await Promise.resolve().then(() => (init_aggregate(), aggregate_exports));
307
+ return aggregateStream2(this.stream(req, opts));
308
+ }
309
+ async *stream(req, opts) {
310
+ const url = this.buildUrl(req);
311
+ const headers = this.buildHeaders(req);
312
+ const body = this.buildBody(req);
313
+ let httpRes;
314
+ try {
315
+ httpRes = await this.fetchImpl(url, {
316
+ method: "POST",
317
+ headers,
318
+ body: JSON.stringify(body),
319
+ signal: opts.signal
320
+ });
321
+ } catch (err) {
322
+ if (opts.signal.aborted) throw err;
323
+ throw new ProviderError(
324
+ err instanceof Error ? err.message : String(err),
325
+ 0,
326
+ true,
327
+ this.id,
328
+ { cause: err, body: { message: err instanceof Error ? err.message : String(err) } }
329
+ );
330
+ }
331
+ if (!httpRes.ok) {
332
+ const text = await safeText(httpRes);
333
+ throw this.translateError(httpRes.status, text);
334
+ }
335
+ yield* this.parseStream(httpRes.body, req.model);
336
+ }
337
+ /** Per-request headers. `apiKey` is already in scope — call `super.buildHeaders` first. */
338
+ buildHeaders(_req) {
339
+ return {
340
+ "content-type": "application/json",
341
+ accept: "text/event-stream"
342
+ };
343
+ }
344
+ /** Build a ProviderError from an HTTP failure response. */
345
+ translateError(status, body) {
346
+ return parseProviderHttpError(this.id, status, body);
347
+ }
348
+ };
349
+
350
+ // src/anthropic.ts
351
+ var DEFAULT_BASE = "https://api.anthropic.com";
352
+ var DEFAULT_VERSION = "2023-06-01";
353
+ var AnthropicProvider = class extends WireAdapter {
354
+ id = "anthropic";
355
+ capabilities = {
356
+ tools: true,
357
+ parallelTools: true,
358
+ vision: true,
359
+ streaming: true,
360
+ promptCache: true,
361
+ systemPrompt: true,
362
+ jsonMode: false,
363
+ maxContext: 2e5,
364
+ cacheControl: "native"
365
+ };
366
+ opts;
367
+ constructor(opts) {
368
+ super(opts.apiKey, opts.baseUrl ?? DEFAULT_BASE, opts.fetchImpl);
369
+ this.opts = opts;
370
+ }
371
+ buildUrl(_req) {
372
+ const base = this.baseUrl.replace(/\/+$/, "");
373
+ if (/\/v\d+\/messages$/.test(base)) return base;
374
+ if (/\/v\d+$/.test(base)) return `${base}/messages`;
375
+ return `${base}/v1/messages`;
376
+ }
377
+ buildHeaders(req) {
378
+ const headers = {
379
+ ...super.buildHeaders(req),
380
+ "x-api-key": this.apiKey,
381
+ "anthropic-version": this.opts.apiVersion ?? DEFAULT_VERSION
382
+ };
383
+ if (this.opts.beta && this.opts.beta.length > 0) {
384
+ headers["anthropic-beta"] = this.opts.beta.join(",");
385
+ }
386
+ return headers;
387
+ }
388
+ buildBody(req) {
389
+ const body = {
390
+ model: req.model,
391
+ max_tokens: req.maxTokens,
392
+ messages: req.messages.map((m) => this.normalizeMessage(m)),
393
+ stream: true
394
+ };
395
+ if (req.system && req.system.length > 0) body["system"] = req.system;
396
+ if (req.tools && req.tools.length > 0) body["tools"] = toolsToAnthropic(req.tools);
397
+ if (req.temperature !== void 0) body["temperature"] = req.temperature;
398
+ if (req.topP !== void 0) body["top_p"] = req.topP;
399
+ if (req.stopSequences) body["stop_sequences"] = req.stopSequences;
400
+ if (req.toolChoice) body["tool_choice"] = req.toolChoice;
401
+ return body;
402
+ }
403
+ parseStream(body, fallbackModel) {
404
+ return parseAnthropicStream(body, fallbackModel);
405
+ }
406
+ translateError(status, text) {
407
+ return parseProviderHttpError(this.id, status, text);
408
+ }
409
+ normalizeMessage(m) {
410
+ return {
411
+ role: m.role === "system" ? "user" : m.role,
412
+ content: typeof m.content === "string" ? m.content : m.content
413
+ };
414
+ }
415
+ };
416
+ async function* parseAnthropicStream(body, fallbackModel) {
417
+ const blocks = /* @__PURE__ */ new Map();
418
+ let model = fallbackModel;
419
+ let usage = { input: 0, output: 0 };
420
+ let stopReason = "end_turn";
421
+ let started = false;
422
+ for await (const msg of parseSSE(body)) {
423
+ if (!msg.data || msg.data === "[DONE]") continue;
424
+ const parsed = safeParse(msg.data);
425
+ if (!parsed.ok || !parsed.value) continue;
426
+ const ev = parsed.value;
427
+ const type = String(ev["type"] ?? msg.event);
428
+ switch (type) {
429
+ case "message_start": {
430
+ const message = ev["message"];
431
+ if (message?.model) model = message.model;
432
+ usage = {
433
+ input: message?.usage?.input_tokens ?? 0,
434
+ output: 0,
435
+ cacheRead: message?.usage?.cache_read_input_tokens,
436
+ cacheWrite: message?.usage?.cache_creation_input_tokens
437
+ };
438
+ if (!started) {
439
+ started = true;
440
+ yield { type: "message_start", model };
441
+ }
442
+ break;
443
+ }
444
+ case "content_block_start": {
445
+ const index = Number(ev["index"] ?? 0);
446
+ const cb = ev["content_block"];
447
+ if (cb?.type === "tool_use") {
448
+ blocks.set(index, { kind: "tool_use", id: cb.id, name: cb.name, partial: "" });
449
+ if (cb.id && cb.name) {
450
+ yield { type: "tool_use_start", id: cb.id, name: cb.name };
451
+ }
452
+ } else if (cb?.type === "text") {
453
+ blocks.set(index, { kind: "text", partial: "" });
454
+ } else {
455
+ blocks.set(index, { kind: "unknown", partial: "" });
456
+ }
457
+ break;
458
+ }
459
+ case "content_block_delta": {
460
+ const index = Number(ev["index"] ?? 0);
461
+ const delta = ev["delta"];
462
+ const block = blocks.get(index);
463
+ if (!block || !delta) break;
464
+ if (delta.type === "text_delta" && typeof delta.text === "string") {
465
+ yield { type: "text_delta", text: delta.text };
466
+ } else if (delta.type === "input_json_delta" && typeof delta.partial_json === "string") {
467
+ if (block.id) {
468
+ block.partial += delta.partial_json;
469
+ yield { type: "tool_use_input_delta", id: block.id, partial: delta.partial_json };
470
+ }
471
+ }
472
+ break;
473
+ }
474
+ case "content_block_stop": {
475
+ const index = Number(ev["index"] ?? 0);
476
+ const block = blocks.get(index);
477
+ if (block?.kind === "tool_use" && block.id) {
478
+ const input = block.partial ? safeParse(block.partial).value ?? {} : {};
479
+ yield { type: "tool_use_stop", id: block.id, input };
480
+ }
481
+ break;
482
+ }
483
+ case "message_delta": {
484
+ const delta = ev["delta"];
485
+ const u = ev["usage"];
486
+ if (delta?.stop_reason !== void 0) {
487
+ stopReason = normalizeAnthropic(delta.stop_reason);
488
+ }
489
+ if (u?.output_tokens !== void 0) usage = { ...usage, output: u.output_tokens };
490
+ break;
491
+ }
492
+ case "message_stop":
493
+ yield { type: "message_stop", stopReason, usage };
494
+ break;
495
+ case "error": {
496
+ const err = ev["error"];
497
+ throw new ProviderError(
498
+ err?.message ?? "Anthropic stream error",
499
+ 0,
500
+ false,
501
+ "anthropic",
502
+ { body: { type: err?.type, message: err?.message } }
503
+ );
504
+ }
505
+ }
506
+ }
507
+ if (started) {
508
+ yield { type: "message_stop", stopReason, usage };
509
+ }
510
+ }
511
+
512
+ // src/tool-format/to-openai.ts
513
+ function toolsToOpenAI(tools) {
514
+ return tools.map((t) => ({
515
+ type: "function",
516
+ function: {
517
+ name: t.name,
518
+ description: t.description,
519
+ parameters: t.inputSchema ?? {
520
+ type: "object",
521
+ properties: {}
522
+ }
523
+ }
524
+ }));
525
+ }
526
+ function messagesToOpenAI(system, messages, opts = {}) {
527
+ const out = [];
528
+ if (system && system.length > 0) {
529
+ const sysText = system.map((b) => b.text).join("\n\n");
530
+ if (opts.systemAsMessage) {
531
+ out.push({ role: "user", content: sysText });
532
+ } else {
533
+ out.push({ role: "system", content: sysText });
534
+ }
535
+ }
536
+ for (const msg of messages) {
537
+ if (msg.role === "user") {
538
+ const blocks = normalizeContent(msg.content);
539
+ const toolResults = blocks.filter((b) => b.type === "tool_result");
540
+ const others = blocks.filter((b) => b.type !== "tool_result");
541
+ if (others.length > 0) {
542
+ out.push({
543
+ role: "user",
544
+ content: opts.flattenContentToString ? blocksToString(others) : blocksToContentArray(others)
545
+ });
546
+ }
547
+ for (const r of toolResults) {
548
+ const content = typeof r.content === "string" ? r.content : JSON.stringify(r.content);
549
+ out.push({
550
+ role: "tool",
551
+ tool_call_id: r.tool_use_id,
552
+ content
553
+ });
554
+ }
555
+ } else if (msg.role === "assistant") {
556
+ const blocks = normalizeContent(msg.content);
557
+ const textBlocks = blocks.filter((b) => b.type === "text");
558
+ const toolUses = blocks.filter((b) => b.type === "tool_use");
559
+ const text = textBlocks.map((b) => b.text).join("");
560
+ const toolCalls = toolUses.map((u) => ({
561
+ id: u.id,
562
+ type: "function",
563
+ function: { name: u.name, arguments: JSON.stringify(u.input) }
564
+ }));
565
+ const message = { role: "assistant" };
566
+ if (toolCalls.length > 0) {
567
+ message.tool_calls = toolCalls;
568
+ if (text) {
569
+ message.content = text;
570
+ } else if (opts.emptyToolCallContent === "empty_string") {
571
+ message.content = "";
572
+ }
573
+ } else {
574
+ message.content = text;
575
+ }
576
+ out.push(message);
577
+ }
578
+ }
579
+ return out;
580
+ }
581
+ function normalizeContent(content) {
582
+ return typeof content === "string" ? [{ type: "text", text: content }] : content;
583
+ }
584
+ function blocksToString(blocks) {
585
+ return blocks.map((b) => {
586
+ if (b.type === "text") return b.text;
587
+ if (b.type === "image") return "[image]";
588
+ return "";
589
+ }).join("");
590
+ }
591
+ function blocksToContentArray(blocks) {
592
+ const hasImage = blocks.some((b) => b.type === "image");
593
+ if (!hasImage) {
594
+ return blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
595
+ }
596
+ return blocks.map((b) => {
597
+ if (b.type === "text") return { type: "text", text: b.text };
598
+ if (b.type === "image") {
599
+ const url = b.source.type === "url" ? b.source.url ?? "" : `data:${b.source.media_type ?? "image/png"};base64,${b.source.data ?? ""}`;
600
+ return { type: "image_url", image_url: { url } };
601
+ }
602
+ return null;
603
+ }).filter((c) => c !== null);
604
+ }
605
+
606
+ // src/openai.ts
607
+ var DEFAULT_BASE2 = "https://api.openai.com/v1";
608
+ var OpenAIProvider = class extends WireAdapter {
609
+ id;
610
+ capabilities;
611
+ opts;
612
+ constructor(opts) {
613
+ super(opts.apiKey, opts.baseUrl ?? DEFAULT_BASE2, opts.fetchImpl);
614
+ this.opts = opts;
615
+ this.id = opts.id ?? "openai";
616
+ this.capabilities = {
617
+ tools: true,
618
+ parallelTools: !opts.quirks?.parallelToolsDisabled,
619
+ vision: true,
620
+ streaming: true,
621
+ promptCache: false,
622
+ systemPrompt: !opts.quirks?.systemAsMessage,
623
+ jsonMode: true,
624
+ maxContext: 128e3,
625
+ cacheControl: "auto",
626
+ ...opts.capabilities
627
+ };
628
+ }
629
+ buildUrl(_req) {
630
+ const base = this.baseUrl.replace(/\/+$/, "");
631
+ if (/\/chat\/completions$/.test(base)) return base;
632
+ if (/\/v\d+(\/[a-z0-9_-]+)*$/i.test(base)) return `${base}/chat/completions`;
633
+ return `${base}/v1/chat/completions`;
634
+ }
635
+ buildHeaders(req) {
636
+ const headers = {
637
+ ...super.buildHeaders(req),
638
+ authorization: `Bearer ${this.apiKey}`
639
+ };
640
+ if (this.opts.organization) {
641
+ headers["openai-organization"] = this.opts.organization;
642
+ }
643
+ return headers;
644
+ }
645
+ buildBody(req) {
646
+ const body = {
647
+ model: req.model,
648
+ messages: messagesToOpenAI(this.stripCacheControl(req), req.messages, {
649
+ ...this.opts.quirks
650
+ }),
651
+ max_tokens: req.maxTokens,
652
+ stream: true,
653
+ stream_options: { include_usage: true }
654
+ };
655
+ if (req.tools && req.tools.length > 0) {
656
+ body["tools"] = toolsToOpenAI(req.tools);
657
+ if (req.toolChoice) {
658
+ if (typeof req.toolChoice === "string") {
659
+ body["tool_choice"] = req.toolChoice === "required" ? "required" : req.toolChoice;
660
+ } else {
661
+ body["tool_choice"] = {
662
+ type: "function",
663
+ function: { name: req.toolChoice.name }
664
+ };
665
+ }
666
+ }
667
+ }
668
+ if (req.temperature !== void 0) body["temperature"] = req.temperature;
669
+ if (req.topP !== void 0) body["top_p"] = req.topP;
670
+ if (req.stopSequences) body["stop"] = req.stopSequences;
671
+ return body;
672
+ }
673
+ parseStream(body, fallbackModel) {
674
+ return parseOpenAIStream(body, fallbackModel);
675
+ }
676
+ translateError(status, text) {
677
+ return parseProviderHttpError(this.id, status, text);
678
+ }
679
+ stripCacheControl(req) {
680
+ if (!req.system) return void 0;
681
+ return req.system.map((b) => {
682
+ const { cache_control: _cc, ...rest } = b;
683
+ return rest;
684
+ });
685
+ }
686
+ };
687
+ async function* parseOpenAIStream(body, fallbackModel) {
688
+ let model = fallbackModel;
689
+ let usage = { input: 0, output: 0 };
690
+ let stopReason = "end_turn";
691
+ let started = false;
692
+ const toolByIndex = /* @__PURE__ */ new Map();
693
+ for await (const msg of parseSSE(body)) {
694
+ if (!msg.data || msg.data === "[DONE]") continue;
695
+ const parsed = safeParse(msg.data);
696
+ if (!parsed.ok || !parsed.value) continue;
697
+ const obj = parsed.value;
698
+ if (typeof obj["model"] === "string") model = obj["model"];
699
+ if (!started) {
700
+ started = true;
701
+ yield { type: "message_start", model };
702
+ }
703
+ const choices = obj["choices"];
704
+ const choice = choices?.[0];
705
+ if (choice?.delta?.content) {
706
+ yield { type: "text_delta", text: choice.delta.content };
707
+ }
708
+ if (choice?.delta?.tool_calls) {
709
+ for (const tc of choice.delta.tool_calls) {
710
+ const idx = tc.index ?? 0;
711
+ let entry = toolByIndex.get(idx);
712
+ if (!entry && tc.id && tc.function?.name) {
713
+ entry = { id: tc.id, name: tc.function.name, argBuf: "" };
714
+ toolByIndex.set(idx, entry);
715
+ yield { type: "tool_use_start", id: entry.id, name: entry.name };
716
+ }
717
+ if (entry && tc.function?.arguments) {
718
+ entry.argBuf += tc.function.arguments;
719
+ yield {
720
+ type: "tool_use_input_delta",
721
+ id: entry.id,
722
+ partial: tc.function.arguments
723
+ };
724
+ }
725
+ }
726
+ }
727
+ if (choice?.finish_reason) {
728
+ stopReason = normalizeOpenAI(choice.finish_reason);
729
+ }
730
+ const u = obj["usage"];
731
+ if (u) {
732
+ usage = {
733
+ input: u.prompt_tokens ?? usage.input,
734
+ output: u.completion_tokens ?? usage.output,
735
+ cacheRead: u.prompt_tokens_details?.cached_tokens ?? usage.cacheRead
736
+ };
737
+ }
738
+ }
739
+ for (const entry of toolByIndex.values()) {
740
+ const input = entry.argBuf ? safeParse(entry.argBuf).value ?? { _raw: entry.argBuf } : {};
741
+ yield { type: "tool_use_stop", id: entry.id, input };
742
+ }
743
+ if (started) {
744
+ yield { type: "message_stop", stopReason, usage };
745
+ }
746
+ }
747
+
748
+ // src/openai-compatible.ts
749
+ var OpenAICompatibleProvider = class extends OpenAIProvider {
750
+ extraHeaders;
751
+ urlOverride;
752
+ constructor(opts) {
753
+ super({
754
+ apiKey: opts.apiKey,
755
+ baseUrl: opts.baseUrl,
756
+ fetchImpl: opts.fetchImpl,
757
+ id: opts.id,
758
+ capabilities: opts.capabilities,
759
+ quirks: {
760
+ ...opts.quirks,
761
+ parallelToolsDisabled: opts.quirks?.parallelToolsDisabled,
762
+ jsonArgumentsBuggy: opts.quirks?.jsonArgumentsBuggy
763
+ }
764
+ });
765
+ this.extraHeaders = opts.headers;
766
+ this.urlOverride = opts.urlOverride;
767
+ }
768
+ buildUrl(req) {
769
+ if (this.urlOverride) {
770
+ return this.urlOverride(this.baseUrl, req);
771
+ }
772
+ return super.buildUrl(req);
773
+ }
774
+ buildHeaders(req) {
775
+ return {
776
+ ...super.buildHeaders(req),
777
+ ...this.extraHeaders
778
+ };
779
+ }
780
+ };
781
+ var DEFAULT_BASE3 = "https://generativelanguage.googleapis.com/v1beta";
782
+ var GoogleProvider = class extends WireAdapter {
783
+ id;
784
+ capabilities;
785
+ opts;
786
+ constructor(opts) {
787
+ super(opts.apiKey, opts.baseUrl ?? DEFAULT_BASE3, opts.fetchImpl);
788
+ this.opts = opts;
789
+ this.id = opts.id ?? "google";
790
+ this.capabilities = {
791
+ tools: true,
792
+ parallelTools: true,
793
+ vision: true,
794
+ streaming: true,
795
+ promptCache: false,
796
+ systemPrompt: true,
797
+ jsonMode: true,
798
+ maxContext: 1e6,
799
+ cacheControl: "none",
800
+ ...opts.capabilities
801
+ };
802
+ }
803
+ buildUrl(req) {
804
+ return `${this.baseUrl}/models/${encodeURIComponent(req.model)}:streamGenerateContent?alt=sse`;
805
+ }
806
+ buildHeaders(req) {
807
+ return {
808
+ ...super.buildHeaders(req),
809
+ "x-goog-api-key": this.apiKey
810
+ };
811
+ }
812
+ buildBody(req) {
813
+ const body = {
814
+ contents: messagesToGemini(req.messages),
815
+ generationConfig: this.buildGenConfig(req)
816
+ };
817
+ if (req.system && req.system.length > 0) {
818
+ body["systemInstruction"] = {
819
+ parts: req.system.map((b) => ({ text: b.text }))
820
+ };
821
+ }
822
+ if (req.tools && req.tools.length > 0) {
823
+ body["tools"] = [{ functionDeclarations: toolsToGemini(req.tools) }];
824
+ }
825
+ return body;
826
+ }
827
+ parseStream(body, fallbackModel) {
828
+ return parseGoogleStream(body, fallbackModel);
829
+ }
830
+ translateError(status, text) {
831
+ return parseProviderHttpError(this.id, status, text);
832
+ }
833
+ buildGenConfig(req) {
834
+ const cfg = { maxOutputTokens: req.maxTokens };
835
+ if (req.temperature !== void 0) cfg["temperature"] = req.temperature;
836
+ if (req.topP !== void 0) cfg["topP"] = req.topP;
837
+ if (req.stopSequences) cfg["stopSequences"] = req.stopSequences;
838
+ return cfg;
839
+ }
840
+ };
841
+ function toolsToGemini(tools) {
842
+ return tools.map((t) => ({
843
+ name: t.name,
844
+ description: t.description,
845
+ parameters: t.inputSchema ?? { type: "object", properties: {} }
846
+ }));
847
+ }
848
+ function messagesToGemini(messages) {
849
+ const out = [];
850
+ for (const m of messages) {
851
+ if (m.role === "system") continue;
852
+ const blocks = typeof m.content === "string" ? [{ type: "text", text: m.content }] : m.content;
853
+ if (m.role === "assistant") {
854
+ const parts = [];
855
+ for (const b of blocks) {
856
+ if (b.type === "text" && b.text) parts.push({ text: b.text });
857
+ else if (b.type === "tool_use") {
858
+ parts.push({ functionCall: { name: b.name, args: b.input } });
859
+ }
860
+ }
861
+ if (parts.length > 0) out.push({ role: "model", parts });
862
+ continue;
863
+ }
864
+ const textParts = [];
865
+ const functionParts = [];
866
+ for (const b of blocks) {
867
+ if (b.type === "text" && b.text) textParts.push({ text: b.text });
868
+ else if (b.type === "tool_result") {
869
+ const responseText = typeof b.content === "string" ? b.content : JSON.stringify(b.content);
870
+ const fnName = b.name ?? b.tool_use_id;
871
+ functionParts.push({
872
+ functionResponse: {
873
+ name: fnName,
874
+ response: { content: responseText }
875
+ }
876
+ });
877
+ } else if (b.type === "image" && b.source.type === "base64") {
878
+ textParts.push({
879
+ inlineData: {
880
+ mimeType: b.source.media_type ?? "image/png",
881
+ data: b.source.data ?? ""
882
+ }
883
+ });
884
+ }
885
+ }
886
+ if (textParts.length > 0) out.push({ role: "user", parts: textParts });
887
+ if (functionParts.length > 0) out.push({ role: "function", parts: functionParts });
888
+ }
889
+ return out;
890
+ }
891
+ async function* parseGoogleStream(body, fallbackModel) {
892
+ let model = fallbackModel;
893
+ let usage = { input: 0, output: 0 };
894
+ let stopReason = "end_turn";
895
+ let started = false;
896
+ for await (const msg of parseSSE(body)) {
897
+ if (!msg.data || msg.data === "[DONE]") continue;
898
+ const parsed = safeParse(msg.data);
899
+ if (!parsed.ok || !parsed.value) continue;
900
+ const obj = parsed.value;
901
+ if (obj.modelVersion) model = obj.modelVersion;
902
+ if (!started) {
903
+ started = true;
904
+ yield { type: "message_start", model };
905
+ }
906
+ const candidate = obj.candidates?.[0];
907
+ for (const part of candidate?.content?.parts ?? []) {
908
+ if (typeof part.text === "string" && part.text.length > 0) {
909
+ yield { type: "text_delta", text: part.text };
910
+ } else if (part.functionCall) {
911
+ const id = `${part.functionCall.name}_${Math.random().toString(36).slice(2, 10)}`;
912
+ yield { type: "tool_use_start", id, name: part.functionCall.name };
913
+ yield {
914
+ type: "tool_use_stop",
915
+ id,
916
+ input: part.functionCall.args ?? {}
917
+ };
918
+ }
919
+ }
920
+ if (candidate?.finishReason) {
921
+ stopReason = normalizeGemini(candidate.finishReason);
922
+ }
923
+ const u = obj.usageMetadata;
924
+ if (u) {
925
+ usage = {
926
+ input: u.promptTokenCount ?? usage.input,
927
+ output: u.candidatesTokenCount ?? usage.output,
928
+ cacheRead: u.cachedContentTokenCount ?? usage.cacheRead
929
+ };
930
+ }
931
+ }
932
+ if (started) {
933
+ yield { type: "message_stop", stopReason, usage };
934
+ }
935
+ }
936
+
937
+ // src/capabilities.ts
938
+ var BASE_BY_FAMILY = {
939
+ anthropic: {
940
+ tools: true,
941
+ parallelTools: true,
942
+ vision: true,
943
+ streaming: true,
944
+ promptCache: true,
945
+ systemPrompt: true,
946
+ jsonMode: false,
947
+ maxContext: 2e5,
948
+ cacheControl: "native"
949
+ },
950
+ openai: {
951
+ tools: true,
952
+ parallelTools: true,
953
+ vision: true,
954
+ streaming: true,
955
+ promptCache: false,
956
+ systemPrompt: true,
957
+ jsonMode: true,
958
+ maxContext: 128e3,
959
+ cacheControl: "auto"
960
+ },
961
+ "openai-compatible": {
962
+ tools: true,
963
+ parallelTools: true,
964
+ vision: false,
965
+ streaming: true,
966
+ promptCache: false,
967
+ systemPrompt: true,
968
+ jsonMode: false,
969
+ maxContext: 32e3,
970
+ cacheControl: "none"
971
+ },
972
+ google: {
973
+ tools: true,
974
+ parallelTools: true,
975
+ vision: true,
976
+ streaming: true,
977
+ promptCache: false,
978
+ systemPrompt: true,
979
+ jsonMode: true,
980
+ maxContext: 1e6,
981
+ cacheControl: "none"
982
+ },
983
+ unsupported: {
984
+ tools: false,
985
+ parallelTools: false,
986
+ vision: false,
987
+ streaming: false,
988
+ promptCache: false,
989
+ systemPrompt: false,
990
+ jsonMode: false,
991
+ maxContext: 0,
992
+ cacheControl: "none"
993
+ }
994
+ };
995
+ function baseFor(family) {
996
+ return BASE_BY_FAMILY[family] ?? BASE_BY_FAMILY.unsupported;
997
+ }
998
+ async function capabilitiesFor(registry, providerId, modelId) {
999
+ const provider = await registry.getProvider(providerId);
1000
+ const base = baseFor(provider?.family ?? "unsupported");
1001
+ const model = await registry.getModel(providerId, modelId);
1002
+ if (!model) return { ...base };
1003
+ return {
1004
+ ...base,
1005
+ tools: model.capabilities.tools && base.tools,
1006
+ vision: model.capabilities.vision && base.vision,
1007
+ maxContext: model.capabilities.maxContext || base.maxContext
1008
+ };
1009
+ }
1010
+
1011
+ // src/tool-format/from-anthropic.ts
1012
+ function contentFromAnthropic(blocks, opts = {}) {
1013
+ const out = [];
1014
+ for (const b of blocks) {
1015
+ if (b.type === "text" && typeof b.text === "string") {
1016
+ out.push({ type: "text", text: b.text });
1017
+ } else if (b.type === "tool_use" && b.id && b.name) {
1018
+ const input = isPlainObject(b.input) ? b.input : {};
1019
+ out.push({ type: "tool_use", id: b.id, name: b.name, input });
1020
+ } else if (b.type === "tool_result" && b.tool_use_id) {
1021
+ out.push({
1022
+ type: "tool_result",
1023
+ tool_use_id: b.tool_use_id,
1024
+ content: normalizeToolResultContent(b.content, opts),
1025
+ is_error: b.is_error
1026
+ });
1027
+ } else if (b.type === "image" && b.source) {
1028
+ const src = b.source;
1029
+ const kind = src.type === "url" ? "url" : "base64";
1030
+ out.push({
1031
+ type: "image",
1032
+ source: {
1033
+ type: kind,
1034
+ ...src.media_type ? { media_type: src.media_type } : {},
1035
+ ...src.data ? { data: src.data } : {},
1036
+ ...src.url ? { url: src.url } : {}
1037
+ }
1038
+ });
1039
+ } else if (b.type) {
1040
+ opts.onUnsupported?.(b.type, b);
1041
+ }
1042
+ }
1043
+ return out;
1044
+ }
1045
+ function normalizeToolResultContent(raw, opts) {
1046
+ if (typeof raw === "string") return raw;
1047
+ if (Array.isArray(raw)) {
1048
+ const blocks = contentFromAnthropic(raw, opts);
1049
+ return blocks.map((b) => b.type === "text" ? b.text : `[${b.type}]`).join("");
1050
+ }
1051
+ if (raw === void 0 || raw === null) return "";
1052
+ return JSON.stringify(raw);
1053
+ }
1054
+ function isPlainObject(v) {
1055
+ return typeof v === "object" && v !== null && !Array.isArray(v);
1056
+ }
1057
+ function contentFromOpenAI(choice, opts = {}) {
1058
+ const out = [];
1059
+ const text = choice.message.content;
1060
+ if (typeof text === "string" && text.length > 0) {
1061
+ out.push({ type: "text", text });
1062
+ }
1063
+ for (const tc of choice.message.tool_calls ?? []) {
1064
+ const raw = tc.function.arguments ?? "{}";
1065
+ const input = parseToolArguments(raw, tc.function.name, tc.id, opts);
1066
+ const block = {
1067
+ type: "tool_use",
1068
+ id: tc.id,
1069
+ name: tc.function.name,
1070
+ input
1071
+ };
1072
+ out.push(block);
1073
+ }
1074
+ if (out.length === 0) {
1075
+ out.push({ type: "text", text: "" });
1076
+ }
1077
+ return out;
1078
+ }
1079
+ function parseToolArguments(raw, toolName, toolCallId, opts) {
1080
+ try {
1081
+ const parsed = JSON.parse(raw);
1082
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1083
+ return parsed;
1084
+ }
1085
+ opts.onParseFailure?.({ toolName, toolCallId, raw });
1086
+ return { __raw_arguments: raw };
1087
+ } catch {
1088
+ try {
1089
+ const sanitized = JSON.parse(sanitizeJsonString(raw));
1090
+ if (sanitized && typeof sanitized === "object" && !Array.isArray(sanitized)) {
1091
+ return sanitized;
1092
+ }
1093
+ } catch {
1094
+ }
1095
+ opts.onParseFailure?.({ toolName, toolCallId, raw });
1096
+ return { __raw_arguments: raw };
1097
+ }
1098
+ }
1099
+
1100
+ // src/index.ts
1101
+ async function buildProviderFactoriesFromRegistry(opts) {
1102
+ const providers = await opts.registry.listProviders();
1103
+ const factories = [];
1104
+ const unsupported = [];
1105
+ for (const p of providers) {
1106
+ if (p.family === "unsupported") {
1107
+ unsupported.push(p);
1108
+ continue;
1109
+ }
1110
+ factories.push({
1111
+ type: p.id,
1112
+ family: p.family,
1113
+ create: (cfg) => makeProvider(p, cfg)
1114
+ });
1115
+ }
1116
+ factories.push({
1117
+ type: "openai-compatible",
1118
+ family: "openai-compatible",
1119
+ create: (cfg) => new OpenAICompatibleProvider({
1120
+ id: "openai-compatible",
1121
+ apiKey: requireKey(cfg),
1122
+ baseUrl: cfg.baseUrl ?? "",
1123
+ headers: cfg.headers,
1124
+ quirks: cfg.quirks
1125
+ })
1126
+ });
1127
+ if (unsupported.length > 0 && opts.log) {
1128
+ opts.log.info(
1129
+ `${unsupported.length} provider(s) need a plugin (unsupported wire family): ` + unsupported.map((p) => p.id).join(", ")
1130
+ );
1131
+ }
1132
+ return factories;
1133
+ }
1134
+ function makeProvider(p, cfg) {
1135
+ const family = cfg.family ?? p.family;
1136
+ const envVars = cfg.envVars && cfg.envVars.length > 0 ? cfg.envVars : p.envVars;
1137
+ const apiKey = cfg.apiKey ?? readFromEnv(envVars);
1138
+ if (!apiKey && family !== "unsupported") {
1139
+ throw new Error(
1140
+ `Provider "${p.id}" requires an API key. Set ${envVars.join(" or ") || "apiKey in config"} or run \`wstack auth ${p.id}\`.`
1141
+ );
1142
+ }
1143
+ const baseUrl = cfg.baseUrl ?? p.apiBase;
1144
+ if (!family || family === "unsupported") {
1145
+ if (family === "unsupported") {
1146
+ throw new Error(
1147
+ `Provider "${p.id}" uses an unsupported wire family (${p.npm ?? "unknown"}). Register a custom factory via a plugin to enable it.`
1148
+ );
1149
+ }
1150
+ throw new Error(
1151
+ `Provider "${p.id}" has no wire family configured. Set an explicit family ("anthropic" | "openai" | "openai-compatible" | "google") in config or the models.dev catalog.`
1152
+ );
1153
+ }
1154
+ switch (family) {
1155
+ case "anthropic":
1156
+ return new AnthropicProvider({ apiKey, baseUrl });
1157
+ case "openai":
1158
+ return new OpenAIProvider({
1159
+ apiKey,
1160
+ baseUrl,
1161
+ id: p.id,
1162
+ quirks: cfg.quirks
1163
+ });
1164
+ case "openai-compatible":
1165
+ return new OpenAICompatibleProvider({
1166
+ id: p.id,
1167
+ apiKey,
1168
+ baseUrl: baseUrl ?? "",
1169
+ headers: cfg.headers,
1170
+ quirks: cfg.quirks
1171
+ });
1172
+ case "google":
1173
+ return new GoogleProvider({ id: p.id, apiKey, baseUrl });
1174
+ }
1175
+ }
1176
+ function makeProviderFromConfig(id, cfg) {
1177
+ if (!cfg.family) {
1178
+ throw new Error(
1179
+ `Provider "${id}" needs an explicit family ("anthropic" | "openai" | "openai-compatible" | "google") when not in the models.dev catalog.`
1180
+ );
1181
+ }
1182
+ const synthetic = {
1183
+ id,
1184
+ family: cfg.family,
1185
+ apiBase: cfg.baseUrl,
1186
+ envVars: cfg.envVars ?? [],
1187
+ models: (cfg.models ?? []).map((m) => ({ id: m, name: m })),
1188
+ npm: void 0
1189
+ };
1190
+ return makeProvider(synthetic, cfg);
1191
+ }
1192
+ function readFromEnv(vars) {
1193
+ for (const v of vars) {
1194
+ const val = process.env[v];
1195
+ if (val) return val;
1196
+ }
1197
+ return void 0;
1198
+ }
1199
+ function requireKey(cfg) {
1200
+ if (cfg.apiKey) return cfg.apiKey;
1201
+ throw new Error("Provider config requires apiKey (or set the corresponding env var).");
1202
+ }
1203
+
1204
+ export { AnthropicProvider, GoogleProvider, OpenAICompatibleProvider, OpenAIProvider, WireAdapter, buildProviderFactoriesFromRegistry, capabilitiesFor, contentFromAnthropic, contentFromOpenAI, makeProviderFromConfig, messagesToOpenAI, normalizeAnthropic, normalizeOpenAI, parseProviderHttpError, toolsToAnthropic, toolsToOpenAI };
1205
+ //# sourceMappingURL=index.js.map
1206
+ //# sourceMappingURL=index.js.map