@rowan-agent/models 0.4.4

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.cjs ADDED
@@ -0,0 +1,1595 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ MODELS: () => MODELS,
24
+ ProviderError: () => ProviderError,
25
+ calculateCost: () => calculateCost,
26
+ callOpenAICompletions: () => callOpenAICompletions,
27
+ clearApiProviders: () => clearApiProviders,
28
+ clearModels: () => clearModels,
29
+ createAnthropicStream: () => createAnthropicStream,
30
+ createDispatchStream: () => createDispatchStream,
31
+ createOpenAICompletionsStream: () => createOpenAICompletionsStream,
32
+ createOpenAIResponsesStream: () => createOpenAIResponsesStream,
33
+ getAllModels: () => getAllModels,
34
+ getApiProvider: () => getApiProvider,
35
+ getModel: () => getModel,
36
+ getModels: () => getModels,
37
+ getProviders: () => getProviders,
38
+ iterateSseMessages: () => iterateSseMessages,
39
+ listApiProviders: () => listApiProviders,
40
+ registerApiProvider: () => registerApiProvider,
41
+ registerBuiltInApiProviders: () => registerBuiltInApiProviders,
42
+ registerModel: () => registerModel,
43
+ registerProvider: () => registerProvider,
44
+ resolveAnthropicConfig: () => resolveAnthropicConfig,
45
+ resolveModel: () => resolveModel,
46
+ resolveOpenAICompletionsConfig: () => resolveOpenAICompletionsConfig,
47
+ resolveOpenAIResponsesConfig: () => resolveOpenAIResponsesConfig,
48
+ stream: () => stream,
49
+ streamAnthropic: () => streamAnthropic,
50
+ streamByRef: () => streamByRef,
51
+ streamOpenAICompletions: () => streamOpenAICompletions,
52
+ streamOpenAIResponses: () => streamOpenAIResponses,
53
+ textFromPartial: () => textFromPartial,
54
+ toolCallsFromPartial: () => toolCallsFromPartial,
55
+ unregisterApiProvider: () => unregisterApiProvider,
56
+ unregisterProviderModels: () => unregisterProviderModels
57
+ });
58
+ module.exports = __toCommonJS(index_exports);
59
+
60
+ // src/protocol.ts
61
+ function textFromPartial(partial) {
62
+ return partial.contentBlocks.filter((b) => b.type === "text").map((b) => b.text).join("");
63
+ }
64
+ function toolCallsFromPartial(partial) {
65
+ return partial.contentBlocks.filter((b) => b.type === "tool_call");
66
+ }
67
+
68
+ // src/sse.ts
69
+ function flushSseEvent(state) {
70
+ if (!state.event && state.data.length === 0) {
71
+ return null;
72
+ }
73
+ const event = {
74
+ event: state.event,
75
+ data: state.data.join("\n")
76
+ };
77
+ state.event = null;
78
+ state.data = [];
79
+ return event;
80
+ }
81
+ function decodeSseLine(line, state) {
82
+ if (line === "") {
83
+ return flushSseEvent(state);
84
+ }
85
+ if (line.startsWith(":")) {
86
+ return null;
87
+ }
88
+ const delimiterIndex = line.indexOf(":");
89
+ const fieldName = delimiterIndex === -1 ? line : line.slice(0, delimiterIndex);
90
+ let value = delimiterIndex === -1 ? "" : line.slice(delimiterIndex + 1);
91
+ if (value.startsWith(" ")) {
92
+ value = value.slice(1);
93
+ }
94
+ if (fieldName === "event") {
95
+ state.event = value;
96
+ } else if (fieldName === "data") {
97
+ state.data.push(value);
98
+ }
99
+ return null;
100
+ }
101
+ function nextLineBreakIndex(text) {
102
+ const cr = text.indexOf("\r");
103
+ const lf = text.indexOf("\n");
104
+ if (cr === -1) return lf;
105
+ if (lf === -1) return cr;
106
+ return Math.min(cr, lf);
107
+ }
108
+ function consumeLine(text) {
109
+ const index = nextLineBreakIndex(text);
110
+ if (index === -1) return null;
111
+ let nextIndex = index + 1;
112
+ if (text[index] === "\r" && text[nextIndex] === "\n") {
113
+ nextIndex += 1;
114
+ }
115
+ return {
116
+ line: text.slice(0, index),
117
+ rest: text.slice(nextIndex)
118
+ };
119
+ }
120
+ async function* iterateSseMessages(body, signal) {
121
+ const reader = body.getReader();
122
+ const decoder = new TextDecoder();
123
+ const state = { event: null, data: [] };
124
+ let buffer = "";
125
+ try {
126
+ while (true) {
127
+ if (signal?.aborted) {
128
+ throw new Error("Request was aborted");
129
+ }
130
+ const { value, done } = await reader.read();
131
+ if (done) break;
132
+ buffer += decoder.decode(value, { stream: true });
133
+ let consumed2 = consumeLine(buffer);
134
+ while (consumed2) {
135
+ buffer = consumed2.rest;
136
+ const event = decodeSseLine(consumed2.line, state);
137
+ if (event) yield event;
138
+ consumed2 = consumeLine(buffer);
139
+ }
140
+ }
141
+ buffer += decoder.decode();
142
+ let consumed = consumeLine(buffer);
143
+ while (consumed) {
144
+ buffer = consumed.rest;
145
+ const event = decodeSseLine(consumed.line, state);
146
+ if (event) yield event;
147
+ consumed = consumeLine(buffer);
148
+ }
149
+ if (buffer.length > 0) {
150
+ const event = decodeSseLine(buffer, state);
151
+ if (event) yield event;
152
+ }
153
+ const trailingEvent = flushSseEvent(state);
154
+ if (trailingEvent) yield trailingEvent;
155
+ } finally {
156
+ reader.releaseLock();
157
+ }
158
+ }
159
+
160
+ // src/models.ts
161
+ var modelRegistry = /* @__PURE__ */ new Map();
162
+ function registerModel(model) {
163
+ let providerModels = modelRegistry.get(model.provider);
164
+ if (!providerModels) {
165
+ providerModels = /* @__PURE__ */ new Map();
166
+ modelRegistry.set(model.provider, providerModels);
167
+ }
168
+ providerModels.set(model.id, model);
169
+ }
170
+ function getModel(provider, modelId) {
171
+ return modelRegistry.get(provider)?.get(modelId);
172
+ }
173
+ function resolveModel(ref) {
174
+ const slashIndex = ref.indexOf("/");
175
+ if (slashIndex === -1) {
176
+ for (const models of modelRegistry.values()) {
177
+ const found = models.get(ref);
178
+ if (found) return found;
179
+ }
180
+ return void 0;
181
+ }
182
+ const provider = ref.slice(0, slashIndex);
183
+ const modelId = ref.slice(slashIndex + 1);
184
+ return getModel(provider, modelId);
185
+ }
186
+ function getProviders() {
187
+ return [...modelRegistry.keys()];
188
+ }
189
+ function getModels(provider) {
190
+ const models = modelRegistry.get(provider);
191
+ return models ? [...models.values()] : [];
192
+ }
193
+ function getAllModels() {
194
+ const result = [];
195
+ for (const models of modelRegistry.values()) {
196
+ result.push(...models.values());
197
+ }
198
+ return result;
199
+ }
200
+ function clearModels() {
201
+ modelRegistry.clear();
202
+ }
203
+ function unregisterProviderModels(provider) {
204
+ const models = modelRegistry.get(provider);
205
+ if (!models) return 0;
206
+ const count = models.size;
207
+ modelRegistry.delete(provider);
208
+ return count;
209
+ }
210
+ function calculateCost(model, usage) {
211
+ const input = (usage.inputTokens ?? 0) / 1e6 * model.cost.input;
212
+ const output = (usage.outputTokens ?? 0) / 1e6 * model.cost.output;
213
+ const cacheRead = (usage.cacheReadTokens ?? 0) / 1e6 * model.cost.cacheRead;
214
+ const cacheWrite = (usage.cacheWriteTokens ?? 0) / 1e6 * model.cost.cacheWrite;
215
+ return { input, output, cacheRead, cacheWrite, total: input + output + cacheRead + cacheWrite };
216
+ }
217
+
218
+ // src/models.generated.ts
219
+ var MODELS = {
220
+ anthropic: {
221
+ "claude-sonnet-4-20250514": {
222
+ id: "claude-sonnet-4-20250514",
223
+ name: "Claude Sonnet 4",
224
+ api: "anthropic-messages",
225
+ provider: "anthropic",
226
+ baseUrl: "https://api.anthropic.com",
227
+ reasoning: true,
228
+ input: ["text", "image"],
229
+ cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
230
+ contextWindow: 2e5,
231
+ maxTokens: 16e3
232
+ },
233
+ "claude-opus-4-20250514": {
234
+ id: "claude-opus-4-20250514",
235
+ name: "Claude Opus 4",
236
+ api: "anthropic-messages",
237
+ provider: "anthropic",
238
+ baseUrl: "https://api.anthropic.com",
239
+ reasoning: true,
240
+ input: ["text", "image"],
241
+ cost: { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
242
+ contextWindow: 2e5,
243
+ maxTokens: 32e3
244
+ },
245
+ "claude-haiku-4-20250514": {
246
+ id: "claude-haiku-4-20250514",
247
+ name: "Claude Haiku 4",
248
+ api: "anthropic-messages",
249
+ provider: "anthropic",
250
+ baseUrl: "https://api.anthropic.com",
251
+ reasoning: false,
252
+ input: ["text", "image"],
253
+ cost: { input: 0.8, output: 4, cacheRead: 0.08, cacheWrite: 1 },
254
+ contextWindow: 2e5,
255
+ maxTokens: 8192
256
+ }
257
+ },
258
+ openai: {
259
+ "gpt-4o": {
260
+ id: "gpt-4o",
261
+ name: "GPT-4o",
262
+ api: "openai-completions",
263
+ provider: "openai",
264
+ baseUrl: "https://api.openai.com/v1",
265
+ reasoning: false,
266
+ input: ["text", "image"],
267
+ cost: { input: 2.5, output: 10, cacheRead: 1.25, cacheWrite: 0 },
268
+ contextWindow: 128e3,
269
+ maxTokens: 16384
270
+ },
271
+ "gpt-4o-mini": {
272
+ id: "gpt-4o-mini",
273
+ name: "GPT-4o Mini",
274
+ api: "openai-completions",
275
+ provider: "openai",
276
+ baseUrl: "https://api.openai.com/v1",
277
+ reasoning: false,
278
+ input: ["text", "image"],
279
+ cost: { input: 0.15, output: 0.6, cacheRead: 0.075, cacheWrite: 0 },
280
+ contextWindow: 128e3,
281
+ maxTokens: 16384
282
+ },
283
+ "o3": {
284
+ id: "o3",
285
+ name: "o3",
286
+ api: "openai-responses",
287
+ provider: "openai",
288
+ baseUrl: "https://api.openai.com/v1",
289
+ reasoning: true,
290
+ input: ["text", "image"],
291
+ cost: { input: 2, output: 8, cacheRead: 0.5, cacheWrite: 0 },
292
+ contextWindow: 2e5,
293
+ maxTokens: 1e5
294
+ },
295
+ "o4-mini": {
296
+ id: "o4-mini",
297
+ name: "o4-mini",
298
+ api: "openai-responses",
299
+ provider: "openai",
300
+ baseUrl: "https://api.openai.com/v1",
301
+ reasoning: true,
302
+ input: ["text", "image"],
303
+ cost: { input: 1.1, output: 4.4, cacheRead: 0.275, cacheWrite: 0 },
304
+ contextWindow: 2e5,
305
+ maxTokens: 1e5
306
+ },
307
+ "gpt-4.1": {
308
+ id: "gpt-4.1",
309
+ name: "GPT-4.1",
310
+ api: "openai-completions",
311
+ provider: "openai",
312
+ baseUrl: "https://api.openai.com/v1",
313
+ reasoning: false,
314
+ input: ["text", "image"],
315
+ cost: { input: 2, output: 8, cacheRead: 0.5, cacheWrite: 0 },
316
+ contextWindow: 1047576,
317
+ maxTokens: 32768
318
+ },
319
+ "gpt-4.1-mini": {
320
+ id: "gpt-4.1-mini",
321
+ name: "GPT-4.1 Mini",
322
+ api: "openai-completions",
323
+ provider: "openai",
324
+ baseUrl: "https://api.openai.com/v1",
325
+ reasoning: false,
326
+ input: ["text", "image"],
327
+ cost: { input: 0.4, output: 1.6, cacheRead: 0.1, cacheWrite: 0 },
328
+ contextWindow: 1047576,
329
+ maxTokens: 32768
330
+ }
331
+ },
332
+ deepseek: {
333
+ "deepseek-chat": {
334
+ id: "deepseek-chat",
335
+ name: "DeepSeek V3",
336
+ api: "openai-completions",
337
+ provider: "deepseek",
338
+ baseUrl: "https://api.deepseek.com/v1",
339
+ reasoning: false,
340
+ input: ["text"],
341
+ cost: { input: 0.27, output: 1.1, cacheRead: 0.07, cacheWrite: 0 },
342
+ contextWindow: 64e3,
343
+ maxTokens: 8192
344
+ },
345
+ "deepseek-reasoner": {
346
+ id: "deepseek-reasoner",
347
+ name: "DeepSeek R1",
348
+ api: "openai-completions",
349
+ provider: "deepseek",
350
+ baseUrl: "https://api.deepseek.com/v1",
351
+ reasoning: true,
352
+ input: ["text"],
353
+ cost: { input: 0.55, output: 2.19, cacheRead: 0.14, cacheWrite: 0 },
354
+ contextWindow: 64e3,
355
+ maxTokens: 8192
356
+ }
357
+ },
358
+ openrouter: {
359
+ "anthropic/claude-sonnet-4": {
360
+ id: "anthropic/claude-sonnet-4",
361
+ name: "Claude Sonnet 4 (OpenRouter)",
362
+ api: "openai-completions",
363
+ provider: "openrouter",
364
+ baseUrl: "https://openrouter.ai/api/v1",
365
+ reasoning: true,
366
+ input: ["text", "image"],
367
+ cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 0 },
368
+ contextWindow: 2e5,
369
+ maxTokens: 16e3
370
+ }
371
+ }
372
+ };
373
+
374
+ // src/providers/shared.ts
375
+ var ProviderError = class extends Error {
376
+ code;
377
+ status;
378
+ retryable;
379
+ details;
380
+ constructor(input) {
381
+ super(input.message);
382
+ this.name = "ProviderError";
383
+ this.code = input.code;
384
+ this.status = input.status;
385
+ this.retryable = input.retryable ?? false;
386
+ this.details = input.details;
387
+ }
388
+ };
389
+ function defaultEnv() {
390
+ return process.env;
391
+ }
392
+ function normalizeBaseUrl(baseUrl) {
393
+ return baseUrl.replace(/\/+$/, "");
394
+ }
395
+ function nonEmpty(value) {
396
+ const trimmed = value?.trim();
397
+ return trimmed && trimmed.length > 0 ? trimmed : void 0;
398
+ }
399
+ function requireValue(name, value, hint) {
400
+ const normalized = nonEmpty(value);
401
+ if (!normalized) {
402
+ throw new ProviderError({
403
+ code: "missing_config",
404
+ message: `Missing ${name}: ${hint}.`
405
+ });
406
+ }
407
+ return normalized;
408
+ }
409
+ function isRecord(value) {
410
+ return typeof value === "object" && value !== null && !Array.isArray(value);
411
+ }
412
+ function asNumber(value) {
413
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
414
+ }
415
+ function asTrimmedString(value) {
416
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
417
+ }
418
+ function truncateString(value, maxLength = 4e3) {
419
+ return value.length > maxLength ? `${value.slice(0, maxLength)}...` : value;
420
+ }
421
+ function createRequestSignal(input) {
422
+ if (!input.signal && !input.timeoutMs) {
423
+ return { cleanup: () => void 0 };
424
+ }
425
+ const controller = new AbortController();
426
+ let timeout;
427
+ const abortFromParent = () => {
428
+ controller.abort(input.signal?.reason ?? new Error("Request aborted."));
429
+ };
430
+ if (input.signal?.aborted) {
431
+ abortFromParent();
432
+ } else {
433
+ input.signal?.addEventListener("abort", abortFromParent, { once: true });
434
+ }
435
+ if (input.timeoutMs) {
436
+ timeout = setTimeout(() => {
437
+ controller.abort(new Error(`Request timed out after ${input.timeoutMs}ms.`));
438
+ }, input.timeoutMs);
439
+ }
440
+ return {
441
+ signal: controller.signal,
442
+ cleanup: () => {
443
+ if (timeout) clearTimeout(timeout);
444
+ input.signal?.removeEventListener("abort", abortFromParent);
445
+ }
446
+ };
447
+ }
448
+ async function readErrorBody(response) {
449
+ const contentType = response.headers.get("content-type") ?? "";
450
+ try {
451
+ if (contentType.includes("application/json")) {
452
+ return await response.json();
453
+ }
454
+ return await response.text();
455
+ } catch {
456
+ return null;
457
+ }
458
+ }
459
+ function isRetryableStatus(status) {
460
+ return status === 408 || status === 409 || status === 429 || status >= 500;
461
+ }
462
+ function normalizeRequestError(error, signal) {
463
+ if (error instanceof ProviderError) return error;
464
+ if (signal?.aborted) {
465
+ return new ProviderError({
466
+ code: "request_aborted",
467
+ message: signal.reason instanceof Error ? signal.reason.message : "Request aborted.",
468
+ retryable: true
469
+ });
470
+ }
471
+ return new ProviderError({
472
+ code: "request_failed",
473
+ message: error instanceof Error ? error.message : "Request failed.",
474
+ retryable: true
475
+ });
476
+ }
477
+ var DEFAULT_MAX_RETRIES = 2;
478
+ var DEFAULT_RETRY_DELAY_MS = 500;
479
+ function normalizeRetryNumber(value, fallback) {
480
+ if (value === void 0) return fallback;
481
+ return Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
482
+ }
483
+ function shouldRetry(input) {
484
+ return input.error.retryable && input.attempts < input.maxRetries && !input.signal?.aborted;
485
+ }
486
+ async function waitForRetry(delayMs, signal) {
487
+ if (delayMs <= 0) return;
488
+ if (signal?.aborted) {
489
+ throw new ProviderError({
490
+ code: "request_aborted",
491
+ message: "Request aborted.",
492
+ retryable: true
493
+ });
494
+ }
495
+ await new Promise((resolve, reject) => {
496
+ const timeout = setTimeout(() => {
497
+ signal?.removeEventListener("abort", abort);
498
+ resolve();
499
+ }, delayMs);
500
+ const abort = () => {
501
+ clearTimeout(timeout);
502
+ reject(new ProviderError({
503
+ code: "request_aborted",
504
+ message: "Request aborted.",
505
+ retryable: true
506
+ }));
507
+ };
508
+ signal?.addEventListener("abort", abort, { once: true });
509
+ });
510
+ }
511
+ function normalizeUsage(usage) {
512
+ if (!usage) return void 0;
513
+ const inputTokens = asNumber(usage.prompt_tokens) ?? asNumber(usage.input_tokens);
514
+ const outputTokens = asNumber(usage.completion_tokens) ?? asNumber(usage.output_tokens);
515
+ const totalTokens = asNumber(usage.total_tokens);
516
+ if (inputTokens === void 0 && outputTokens === void 0 && totalTokens === void 0) {
517
+ return void 0;
518
+ }
519
+ return {
520
+ ...inputTokens !== void 0 ? { inputTokens } : {},
521
+ ...outputTokens !== void 0 ? { outputTokens } : {},
522
+ ...totalTokens !== void 0 ? { totalTokens } : {}
523
+ };
524
+ }
525
+ function summarizeRequestUsage(request) {
526
+ return { inputMessages: request.messages.length + (request.system ? 1 : 0) };
527
+ }
528
+
529
+ // src/providers/openai-completions.ts
530
+ function resolveOpenAICompletionsConfig(input = {}) {
531
+ const env = input.env ?? defaultEnv();
532
+ const baseUrl = nonEmpty(input.baseUrl) ?? nonEmpty(env.ROWAN_OPENAI_BASE_URL) ?? "https://api.openai.com/v1";
533
+ const apiKey = nonEmpty(input.apiKey) ?? nonEmpty(env.ROWAN_OPENAI_API_KEY);
534
+ const model = nonEmpty(input.model) ?? nonEmpty(env.ROWAN_MODEL);
535
+ return {
536
+ baseUrl: normalizeBaseUrl(baseUrl),
537
+ apiKey: requireValue("API key", apiKey, "set ROWAN_OPENAI_API_KEY or pass --api-key"),
538
+ model: requireValue("model", model, "set ROWAN_MODEL or pass --model"),
539
+ ...input.temperature !== void 0 ? { temperature: input.temperature } : {},
540
+ ...input.maxTokens !== void 0 ? { maxTokens: input.maxTokens } : {},
541
+ ...input.timeoutMs !== void 0 ? { timeoutMs: input.timeoutMs } : {},
542
+ ...input.maxRetries !== void 0 ? { maxRetries: input.maxRetries } : {},
543
+ ...input.retryDelayMs !== void 0 ? { retryDelayMs: input.retryDelayMs } : {},
544
+ ...input.fetch ? { fetch: input.fetch } : {},
545
+ ...input.responseFormat !== void 0 ? { responseFormat: input.responseFormat } : {}
546
+ };
547
+ }
548
+ function providerErrorFromBody(body) {
549
+ if (typeof body === "string") {
550
+ const message2 = asTrimmedString(body);
551
+ return message2 ? { message: truncateString(message2) } : void 0;
552
+ }
553
+ if (!isRecord(body)) return void 0;
554
+ const error = body.error;
555
+ if (typeof error === "string") {
556
+ const message2 = asTrimmedString(error);
557
+ return message2 ? { message: message2 } : void 0;
558
+ }
559
+ const source = isRecord(error) ? error : body;
560
+ const message = asTrimmedString(source.message);
561
+ const code = asTrimmedString(source.code);
562
+ const type = asTrimmedString(source.type);
563
+ if (!message && !code && !type) return void 0;
564
+ return { ...message ? { message } : {}, ...code ? { code } : {}, ...type ? { type } : {} };
565
+ }
566
+ function normalizeHttpError(response, body, context) {
567
+ const providerError = providerErrorFromBody(body);
568
+ const providerMessage = asTrimmedString(providerError?.message);
569
+ const statusSummary = response.statusText ? `${response.status} ${response.statusText}` : String(response.status);
570
+ const message = providerMessage ? `Request failed (${statusSummary}): ${providerMessage}` : `Request failed with status ${statusSummary}.`;
571
+ return new ProviderError({
572
+ code: "http_error",
573
+ message,
574
+ status: response.status,
575
+ retryable: isRetryableStatus(response.status),
576
+ details: {
577
+ endpoint: context.endpoint,
578
+ model: context.model,
579
+ status: response.status,
580
+ ...providerError ? { providerError } : {}
581
+ }
582
+ });
583
+ }
584
+ function convertMessages(messages) {
585
+ const result = [];
586
+ for (const msg of messages) {
587
+ if (msg.role === "user") {
588
+ if (typeof msg.content === "string") {
589
+ result.push({ role: "user", content: msg.content });
590
+ } else {
591
+ const parts = [];
592
+ for (const part of msg.content) {
593
+ if (part.type === "text") {
594
+ parts.push({ type: "text", text: part.text });
595
+ } else if (part.type === "image") {
596
+ parts.push({ type: "image_url", image_url: { url: `data:${part.mimeType};base64,${part.data}` } });
597
+ }
598
+ }
599
+ result.push({ role: "user", content: parts });
600
+ }
601
+ } else if (msg.role === "assistant") {
602
+ if (typeof msg.content === "string") {
603
+ result.push({ role: "assistant", content: msg.content });
604
+ } else {
605
+ const toolUseBlocks = msg.content.filter((p) => p.type === "tool_use");
606
+ const textBlocks = msg.content.filter((p) => p.type === "text");
607
+ const text = textBlocks.map((p) => p.text).join("") || null;
608
+ if (toolUseBlocks.length > 0) {
609
+ const toolCalls = toolUseBlocks.map((p) => ({
610
+ id: p.id,
611
+ type: "function",
612
+ function: {
613
+ name: p.name,
614
+ arguments: typeof p.input === "string" ? p.input : JSON.stringify(p.input)
615
+ }
616
+ }));
617
+ result.push({ role: "assistant", content: text, tool_calls: toolCalls });
618
+ } else {
619
+ result.push({ role: "assistant", content: text });
620
+ }
621
+ }
622
+ } else if (msg.role === "tool") {
623
+ if (typeof msg.content === "string") {
624
+ result.push({ role: "tool", content: msg.content, tool_call_id: "" });
625
+ } else {
626
+ for (const part of msg.content) {
627
+ if (part.type === "tool_result") {
628
+ result.push({ role: "tool", content: part.content, tool_call_id: part.toolUseId });
629
+ }
630
+ }
631
+ }
632
+ }
633
+ }
634
+ return result;
635
+ }
636
+ function convertTools(tools) {
637
+ return tools.map((tool) => ({
638
+ type: "function",
639
+ function: { name: tool.name, description: tool.description, parameters: tool.parameters }
640
+ }));
641
+ }
642
+ function buildRequestBody(config, request, stream2) {
643
+ const messages = convertMessages(request.messages);
644
+ if (request.system) {
645
+ messages.unshift({ role: "system", content: request.system });
646
+ }
647
+ const body = {
648
+ model: config.model,
649
+ messages,
650
+ stream: stream2
651
+ };
652
+ if (stream2) {
653
+ body.stream_options = { include_usage: true };
654
+ }
655
+ if (request.temperature !== void 0 || config.temperature !== void 0) {
656
+ body.temperature = request.temperature ?? config.temperature ?? 0;
657
+ }
658
+ if (request.maxTokens ?? config.maxTokens) {
659
+ body.max_tokens = request.maxTokens ?? config.maxTokens;
660
+ }
661
+ if (request.tools && request.tools.length > 0) {
662
+ body.tools = convertTools(request.tools);
663
+ }
664
+ if (config.responseFormat) {
665
+ body.response_format = { type: "json_object" };
666
+ }
667
+ return body;
668
+ }
669
+ function mapFinishReason(reason) {
670
+ switch (reason) {
671
+ case null:
672
+ case void 0:
673
+ return "end_turn";
674
+ case "stop":
675
+ return "end_turn";
676
+ case "length":
677
+ return "max_tokens";
678
+ case "tool_calls":
679
+ return "tool_use";
680
+ case "content_filter":
681
+ return "error";
682
+ default:
683
+ return "unknown";
684
+ }
685
+ }
686
+ async function* streamChatCompletions(config, request, options = {}) {
687
+ const maxRetries = normalizeRetryNumber(config.maxRetries, DEFAULT_MAX_RETRIES);
688
+ const retryDelayMs = normalizeRetryNumber(config.retryDelayMs, DEFAULT_RETRY_DELAY_MS);
689
+ const body = buildRequestBody(config, request, true);
690
+ const fetchImpl = config.fetch ?? fetch;
691
+ const endpoint = `${normalizeBaseUrl(config.baseUrl)}/chat/completions`;
692
+ const requestUsage = summarizeRequestUsage(request);
693
+ let attempts = 0;
694
+ while (true) {
695
+ attempts += 1;
696
+ const { signal, cleanup } = createRequestSignal({ signal: options.signal, timeoutMs: config.timeoutMs });
697
+ try {
698
+ let rebuildPartial2 = function() {
699
+ partial.contentBlocks = [];
700
+ if (content) {
701
+ partial.contentBlocks.push({ type: "text", text: content });
702
+ }
703
+ for (const tc of toolCalls.values()) {
704
+ partial.contentBlocks.push({
705
+ type: "tool_call",
706
+ id: tc.id,
707
+ name: tc.name,
708
+ args: tc.arguments
709
+ });
710
+ }
711
+ };
712
+ var rebuildPartial = rebuildPartial2;
713
+ const response = await fetchImpl(endpoint, {
714
+ method: "POST",
715
+ headers: { "content-type": "application/json", authorization: `Bearer ${config.apiKey}` },
716
+ body: JSON.stringify(body),
717
+ signal
718
+ });
719
+ if (!response.ok) {
720
+ throw normalizeHttpError(response, await readErrorBody(response), { endpoint, model: config.model });
721
+ }
722
+ if (!response.body) {
723
+ throw new ProviderError({ code: "no_body", message: "Response body is null.", retryable: true });
724
+ }
725
+ yield { type: "model_requested", model: request.model, usage: { ...requestUsage } };
726
+ if (!response.headers.get("content-type")?.includes("text/event-stream")) {
727
+ const data = await response.json();
728
+ const choice = data.choices?.[0];
729
+ const message = choice?.message;
730
+ const content2 = message?.content ?? "";
731
+ const partial2 = {
732
+ role: "assistant",
733
+ contentBlocks: []
734
+ };
735
+ yield { type: "start", partial: { ...partial2, contentBlocks: [...partial2.contentBlocks] } };
736
+ if (content2) {
737
+ partial2.contentBlocks.push({ type: "text", text: content2 });
738
+ yield { type: "text_delta", text: content2, partial: { ...partial2, contentBlocks: [...partial2.contentBlocks] } };
739
+ }
740
+ const toolCallResults2 = [];
741
+ for (const [index, tc] of (message?.tool_calls ?? []).entries()) {
742
+ const id = tc.id ?? `call_${index}`;
743
+ const name = tc.function?.name ?? "";
744
+ const args = tc.function?.arguments ?? "";
745
+ partial2.contentBlocks.push({ type: "tool_call", id, name, args });
746
+ yield { type: "tool_call_start", id, name, partial: { ...partial2, contentBlocks: [...partial2.contentBlocks] } };
747
+ yield { type: "tool_call_end", id, name, arguments: args, partial: { ...partial2, contentBlocks: [...partial2.contentBlocks] } };
748
+ let parsedArgs = args;
749
+ try {
750
+ parsedArgs = JSON.parse(args);
751
+ } catch {
752
+ }
753
+ toolCallResults2.push({ id, name, arguments: parsedArgs });
754
+ }
755
+ const usage2 = normalizeUsage(data.usage);
756
+ yield {
757
+ type: "done",
758
+ response: {
759
+ content: content2,
760
+ stopReason: mapFinishReason(choice?.finish_reason),
761
+ ...toolCallResults2.length > 0 ? { toolCalls: toolCallResults2 } : {},
762
+ ...usage2 ? { usage: usage2 } : {}
763
+ }
764
+ };
765
+ return;
766
+ }
767
+ let content = "";
768
+ let finishReason = null;
769
+ let usage;
770
+ const toolCalls = /* @__PURE__ */ new Map();
771
+ const partial = {
772
+ role: "assistant",
773
+ contentBlocks: []
774
+ };
775
+ yield { type: "start", partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
776
+ for await (const sse of iterateSseMessages(response.body, signal)) {
777
+ if (sse.data === "[DONE]") break;
778
+ let chunk;
779
+ try {
780
+ chunk = JSON.parse(sse.data);
781
+ } catch {
782
+ continue;
783
+ }
784
+ if (chunk.usage) usage = normalizeUsage(chunk.usage);
785
+ const choice = chunk.choices?.[0];
786
+ if (!choice) continue;
787
+ const delta = choice.delta;
788
+ if (delta) {
789
+ if (delta.content) {
790
+ content += delta.content;
791
+ rebuildPartial2();
792
+ yield { type: "text_delta", text: delta.content, partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
793
+ }
794
+ if (delta.tool_calls) {
795
+ for (const tc of delta.tool_calls) {
796
+ const existing = toolCalls.get(tc.index);
797
+ if (!existing) {
798
+ const newTc = { id: tc.id ?? "", name: tc.function?.name ?? "", arguments: tc.function?.arguments ?? "" };
799
+ toolCalls.set(tc.index, newTc);
800
+ if (tc.id || tc.function?.name) {
801
+ rebuildPartial2();
802
+ yield { type: "tool_call_start", id: newTc.id, name: newTc.name, partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
803
+ }
804
+ if (tc.function?.arguments) {
805
+ yield { type: "tool_call_delta", id: newTc.id, arguments: tc.function.arguments, partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
806
+ }
807
+ } else {
808
+ if (tc.id) existing.id = tc.id;
809
+ if (tc.function?.name) existing.name = tc.function.name;
810
+ if (tc.function?.arguments) {
811
+ existing.arguments += tc.function.arguments;
812
+ rebuildPartial2();
813
+ yield { type: "tool_call_delta", id: existing.id, arguments: tc.function.arguments, partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
814
+ }
815
+ }
816
+ }
817
+ }
818
+ }
819
+ if (choice.finish_reason) finishReason = choice.finish_reason;
820
+ }
821
+ for (const tc of toolCalls.values()) {
822
+ rebuildPartial2();
823
+ yield { type: "tool_call_end", id: tc.id, name: tc.name, arguments: tc.arguments, partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
824
+ }
825
+ const toolCallResults = [];
826
+ for (const tc of toolCalls.values()) {
827
+ let parsedArgs = tc.arguments;
828
+ try {
829
+ parsedArgs = JSON.parse(tc.arguments);
830
+ } catch {
831
+ }
832
+ toolCallResults.push({ id: tc.id, name: tc.name, arguments: parsedArgs });
833
+ }
834
+ yield {
835
+ type: "done",
836
+ response: {
837
+ content,
838
+ stopReason: mapFinishReason(finishReason),
839
+ ...toolCallResults.length > 0 ? { toolCalls: toolCallResults } : {},
840
+ ...usage ? { usage } : {}
841
+ }
842
+ };
843
+ return;
844
+ } catch (error) {
845
+ const requestError = normalizeRequestError(error, signal);
846
+ if (!shouldRetry({ error: requestError, attempts, maxRetries, signal: options.signal })) {
847
+ yield { type: "error", error: requestError };
848
+ yield { type: "done", response: { content: "", stopReason: "error" } };
849
+ return;
850
+ }
851
+ await waitForRetry(retryDelayMs * 2 ** (attempts - 1), options.signal);
852
+ } finally {
853
+ cleanup();
854
+ }
855
+ }
856
+ }
857
+ function createOpenAICompletionsStream(config) {
858
+ const normalizedConfig = { ...config, baseUrl: normalizeBaseUrl(config.baseUrl) };
859
+ return async function* openAICompletionsStream(request, options) {
860
+ yield* streamChatCompletions(normalizedConfig, request, options);
861
+ };
862
+ }
863
+ var streamOpenAICompletions = (model, request, options) => {
864
+ const config = resolveOpenAICompletionsConfig({
865
+ baseUrl: model.baseUrl,
866
+ model: model.id
867
+ });
868
+ return streamChatCompletions(config, request, options);
869
+ };
870
+ async function callOpenAICompletions(config, request, options = {}) {
871
+ const maxRetries = normalizeRetryNumber(config.maxRetries, DEFAULT_MAX_RETRIES);
872
+ const retryDelayMs = normalizeRetryNumber(config.retryDelayMs, DEFAULT_RETRY_DELAY_MS);
873
+ const body = buildRequestBody(config, request, false);
874
+ const fetchImpl = config.fetch ?? fetch;
875
+ const endpoint = `${normalizeBaseUrl(config.baseUrl)}/chat/completions`;
876
+ let attempts = 0;
877
+ while (true) {
878
+ attempts += 1;
879
+ const { signal, cleanup } = createRequestSignal({ signal: options.signal, timeoutMs: config.timeoutMs });
880
+ try {
881
+ const response = await fetchImpl(endpoint, {
882
+ method: "POST",
883
+ headers: { "content-type": "application/json", authorization: `Bearer ${config.apiKey}` },
884
+ body: JSON.stringify(body),
885
+ signal
886
+ });
887
+ if (!response.ok) {
888
+ throw normalizeHttpError(response, await readErrorBody(response), { endpoint, model: config.model });
889
+ }
890
+ const data = await response.json();
891
+ return { content: data.choices?.[0]?.message?.content ?? "", usage: normalizeUsage(data.usage) };
892
+ } catch (error) {
893
+ const requestError = normalizeRequestError(error, signal);
894
+ if (!shouldRetry({ error: requestError, attempts, maxRetries, signal: options.signal })) throw requestError;
895
+ await waitForRetry(retryDelayMs * 2 ** (attempts - 1), options.signal);
896
+ } finally {
897
+ cleanup();
898
+ }
899
+ }
900
+ }
901
+
902
+ // src/providers/openai-responses.ts
903
+ function resolveOpenAIResponsesConfig(input = {}) {
904
+ const env = input.env ?? defaultEnv();
905
+ const baseUrl = nonEmpty(input.baseUrl) ?? nonEmpty(env.ROWAN_OPENAI_BASE_URL) ?? "https://api.openai.com/v1";
906
+ const apiKey = nonEmpty(input.apiKey) ?? nonEmpty(env.ROWAN_OPENAI_API_KEY);
907
+ const model = nonEmpty(input.model) ?? nonEmpty(env.ROWAN_MODEL);
908
+ return {
909
+ baseUrl: normalizeBaseUrl(baseUrl),
910
+ apiKey: requireValue("API key", apiKey, "set ROWAN_OPENAI_API_KEY or pass --api-key"),
911
+ model: requireValue("model", model, "set ROWAN_MODEL or pass --model"),
912
+ ...input.temperature !== void 0 ? { temperature: input.temperature } : {},
913
+ ...input.maxTokens !== void 0 ? { maxTokens: input.maxTokens } : {},
914
+ ...input.timeoutMs !== void 0 ? { timeoutMs: input.timeoutMs } : {},
915
+ ...input.maxRetries !== void 0 ? { maxRetries: input.maxRetries } : {},
916
+ ...input.retryDelayMs !== void 0 ? { retryDelayMs: input.retryDelayMs } : {},
917
+ ...input.fetch ? { fetch: input.fetch } : {},
918
+ ...input.reasoningEffort !== void 0 ? { reasoningEffort: input.reasoningEffort } : {}
919
+ };
920
+ }
921
+ function normalizeHttpError2(response, body, context) {
922
+ let providerMessage;
923
+ if (isRecord(body) && isRecord(body.error)) {
924
+ providerMessage = asTrimmedString(body.error.message);
925
+ }
926
+ const statusSummary = response.statusText ? `${response.status} ${response.statusText}` : String(response.status);
927
+ const message = providerMessage ? `Request failed (${statusSummary}): ${providerMessage}` : `Request failed with status ${statusSummary}.`;
928
+ return new ProviderError({
929
+ code: "http_error",
930
+ message,
931
+ status: response.status,
932
+ retryable: isRetryableStatus(response.status),
933
+ details: { endpoint: context.endpoint, model: context.model, status: response.status, ...isRecord(body) ? { providerError: body.error } : {} }
934
+ });
935
+ }
936
+ function convertMessages2(messages) {
937
+ const result = [];
938
+ for (const msg of messages) {
939
+ if (msg.role === "user") {
940
+ if (typeof msg.content === "string") {
941
+ result.push({ role: "user", content: msg.content });
942
+ } else {
943
+ const text = msg.content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
944
+ if (text) result.push({ role: "user", content: text });
945
+ }
946
+ } else if (msg.role === "assistant") {
947
+ if (typeof msg.content === "string") {
948
+ result.push({ role: "assistant", content: msg.content });
949
+ } else {
950
+ const text = msg.content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
951
+ if (text) result.push({ role: "assistant", content: text });
952
+ for (const part of msg.content) {
953
+ if (part.type === "tool_use") {
954
+ result.push({
955
+ type: "function_call",
956
+ id: part.id,
957
+ name: part.name,
958
+ arguments: typeof part.input === "string" ? part.input : JSON.stringify(part.input)
959
+ });
960
+ }
961
+ }
962
+ }
963
+ } else if (msg.role === "tool") {
964
+ if (typeof msg.content === "string") {
965
+ result.push({ type: "function_call_output", call_id: "", output: msg.content });
966
+ } else {
967
+ for (const part of msg.content) {
968
+ if (part.type === "tool_result") {
969
+ result.push({
970
+ type: "function_call_output",
971
+ call_id: part.toolUseId,
972
+ output: part.content
973
+ });
974
+ }
975
+ }
976
+ }
977
+ }
978
+ }
979
+ return result;
980
+ }
981
+ function convertTools2(tools) {
982
+ return tools.map((tool) => ({
983
+ type: "function",
984
+ name: tool.name,
985
+ description: tool.description,
986
+ parameters: tool.parameters,
987
+ strict: false
988
+ }));
989
+ }
990
+ function buildRequestBody2(config, request) {
991
+ const input = convertMessages2(request.messages);
992
+ const body = {
993
+ model: config.model,
994
+ input,
995
+ stream: true
996
+ };
997
+ if (request.system) {
998
+ body.instructions = request.system;
999
+ }
1000
+ if (request.maxTokens ?? config.maxTokens) {
1001
+ body.max_output_tokens = request.maxTokens ?? config.maxTokens;
1002
+ }
1003
+ if (request.tools && request.tools.length > 0) {
1004
+ body.tools = convertTools2(request.tools);
1005
+ }
1006
+ if (config.reasoningEffort) {
1007
+ body.reasoning = { effort: config.reasoningEffort, summary: "auto" };
1008
+ }
1009
+ return body;
1010
+ }
1011
+ function mapStopReason(reason) {
1012
+ switch (reason) {
1013
+ case "completed":
1014
+ return "end_turn";
1015
+ case "max_tokens":
1016
+ return "max_tokens";
1017
+ case "incomplete":
1018
+ return "max_tokens";
1019
+ default:
1020
+ return "unknown";
1021
+ }
1022
+ }
1023
+ async function* streamResponses(config, request, options = {}) {
1024
+ const maxRetries = normalizeRetryNumber(config.maxRetries, DEFAULT_MAX_RETRIES);
1025
+ const retryDelayMs = normalizeRetryNumber(config.retryDelayMs, DEFAULT_RETRY_DELAY_MS);
1026
+ const body = buildRequestBody2(config, request);
1027
+ const fetchImpl = config.fetch ?? fetch;
1028
+ const endpoint = `${normalizeBaseUrl(config.baseUrl)}/responses`;
1029
+ const requestUsage = summarizeRequestUsage(request);
1030
+ let attempts = 0;
1031
+ while (true) {
1032
+ attempts += 1;
1033
+ const { signal, cleanup } = createRequestSignal({ signal: options.signal, timeoutMs: config.timeoutMs });
1034
+ try {
1035
+ let rebuildPartial2 = function() {
1036
+ partial.contentBlocks = [];
1037
+ if (content) {
1038
+ partial.contentBlocks.push({ type: "text", text: content });
1039
+ }
1040
+ for (const tc of toolCalls.values()) {
1041
+ partial.contentBlocks.push({
1042
+ type: "tool_call",
1043
+ id: tc.id,
1044
+ name: tc.name,
1045
+ args: tc.arguments
1046
+ });
1047
+ }
1048
+ };
1049
+ var rebuildPartial = rebuildPartial2;
1050
+ const response = await fetchImpl(endpoint, {
1051
+ method: "POST",
1052
+ headers: { "content-type": "application/json", authorization: `Bearer ${config.apiKey}` },
1053
+ body: JSON.stringify(body),
1054
+ signal
1055
+ });
1056
+ if (!response.ok) {
1057
+ throw normalizeHttpError2(response, await readErrorBody(response), { endpoint, model: config.model });
1058
+ }
1059
+ if (!response.body) {
1060
+ throw new ProviderError({ code: "no_body", message: "Response body is null.", retryable: true });
1061
+ }
1062
+ yield { type: "model_requested", model: request.model, usage: { ...requestUsage } };
1063
+ let content = "";
1064
+ let stopReason = null;
1065
+ let usage;
1066
+ const toolCalls = /* @__PURE__ */ new Map();
1067
+ const partial = {
1068
+ role: "assistant",
1069
+ contentBlocks: []
1070
+ };
1071
+ yield { type: "start", partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
1072
+ for await (const sse of iterateSseMessages(response.body, signal)) {
1073
+ let event;
1074
+ try {
1075
+ event = JSON.parse(sse.data);
1076
+ } catch {
1077
+ continue;
1078
+ }
1079
+ switch (event.type) {
1080
+ case "response.output_text.delta":
1081
+ content += event.delta;
1082
+ rebuildPartial2();
1083
+ yield { type: "text_delta", text: event.delta, partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
1084
+ break;
1085
+ case "response.output_item.added":
1086
+ if (event.item.type === "function_call") {
1087
+ const tc = { id: event.item.id ?? "", name: event.item.name ?? "", arguments: "" };
1088
+ toolCalls.set(event.output_index, tc);
1089
+ rebuildPartial2();
1090
+ yield { type: "tool_call_start", id: tc.id, name: tc.name, partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
1091
+ }
1092
+ break;
1093
+ case "response.function_call_arguments.delta": {
1094
+ const tc = toolCalls.get(event.output_index);
1095
+ if (tc) {
1096
+ tc.arguments += event.delta;
1097
+ rebuildPartial2();
1098
+ yield { type: "tool_call_delta", id: tc.id, arguments: event.delta, partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
1099
+ }
1100
+ break;
1101
+ }
1102
+ case "response.function_call_arguments.done": {
1103
+ const tc = toolCalls.get(event.output_index);
1104
+ if (tc) {
1105
+ tc.arguments = event.arguments;
1106
+ }
1107
+ break;
1108
+ }
1109
+ case "response.output_item.done": {
1110
+ if (event.item.type === "function_call") {
1111
+ const tc = toolCalls.get(event.output_index);
1112
+ if (tc) {
1113
+ if (event.item.arguments) tc.arguments = event.item.arguments;
1114
+ rebuildPartial2();
1115
+ yield { type: "tool_call_end", id: tc.id, name: tc.name, arguments: tc.arguments, partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
1116
+ }
1117
+ }
1118
+ break;
1119
+ }
1120
+ case "response.completed":
1121
+ if (event.response.usage) {
1122
+ usage = {
1123
+ inputTokens: event.response.usage.input_tokens,
1124
+ outputTokens: event.response.usage.output_tokens,
1125
+ totalTokens: event.response.usage.total_tokens
1126
+ };
1127
+ }
1128
+ stopReason = "completed";
1129
+ break;
1130
+ case "response.incomplete":
1131
+ if (event.response.usage) {
1132
+ usage = {
1133
+ inputTokens: event.response.usage.input_tokens,
1134
+ outputTokens: event.response.usage.output_tokens,
1135
+ totalTokens: event.response.usage.total_tokens
1136
+ };
1137
+ }
1138
+ stopReason = event.response.incomplete_details?.reason ?? "incomplete";
1139
+ break;
1140
+ case "error":
1141
+ throw new ProviderError({
1142
+ code: "stream_error",
1143
+ message: event.error.message,
1144
+ details: { type: event.error.type }
1145
+ });
1146
+ }
1147
+ }
1148
+ const toolCallResults = [];
1149
+ for (const tc of toolCalls.values()) {
1150
+ let parsedArgs = tc.arguments;
1151
+ try {
1152
+ parsedArgs = JSON.parse(tc.arguments);
1153
+ } catch {
1154
+ }
1155
+ toolCallResults.push({ id: tc.id, name: tc.name, arguments: parsedArgs });
1156
+ }
1157
+ yield {
1158
+ type: "done",
1159
+ response: {
1160
+ content,
1161
+ stopReason: mapStopReason(stopReason),
1162
+ ...toolCallResults.length > 0 ? { toolCalls: toolCallResults } : {},
1163
+ ...usage ? { usage } : {}
1164
+ }
1165
+ };
1166
+ return;
1167
+ } catch (error) {
1168
+ const requestError = normalizeRequestError(error, signal);
1169
+ if (!shouldRetry({ error: requestError, attempts, maxRetries, signal: options.signal })) {
1170
+ yield { type: "error", error: requestError };
1171
+ yield { type: "done", response: { content: "", stopReason: "error" } };
1172
+ return;
1173
+ }
1174
+ await waitForRetry(retryDelayMs * 2 ** (attempts - 1), options.signal);
1175
+ } finally {
1176
+ cleanup();
1177
+ }
1178
+ }
1179
+ }
1180
+ function createOpenAIResponsesStream(config) {
1181
+ const normalizedConfig = { ...config, baseUrl: normalizeBaseUrl(config.baseUrl) };
1182
+ return async function* openAIResponsesStream(request, options) {
1183
+ yield* streamResponses(normalizedConfig, request, options);
1184
+ };
1185
+ }
1186
+ var streamOpenAIResponses = (model, request, options) => {
1187
+ const config = resolveOpenAIResponsesConfig({
1188
+ baseUrl: model.baseUrl,
1189
+ model: model.id
1190
+ });
1191
+ return streamResponses(config, request, options);
1192
+ };
1193
+
1194
+ // src/providers/anthropic.ts
1195
+ var DEFAULT_MAX_TOKENS = 8192;
1196
+ function resolveAnthropicConfig(input = {}) {
1197
+ const env = input.env ?? defaultEnv();
1198
+ const baseUrl = nonEmpty(input.baseUrl) ?? nonEmpty(env.ROWAN_ANTHROPIC_BASE_URL) ?? "https://api.anthropic.com";
1199
+ const apiKey = nonEmpty(input.apiKey) ?? nonEmpty(env.ROWAN_ANTHROPIC_API_KEY) ?? nonEmpty(env.ANTHROPIC_API_KEY);
1200
+ const model = nonEmpty(input.model) ?? nonEmpty(env.ROWAN_MODEL);
1201
+ return {
1202
+ baseUrl: normalizeBaseUrl(baseUrl),
1203
+ apiKey: requireValue("API key", apiKey, "set ROWAN_ANTHROPIC_API_KEY or pass --api-key"),
1204
+ model: requireValue("model", model, "set ROWAN_MODEL or pass --model"),
1205
+ ...input.maxTokens !== void 0 ? { maxTokens: input.maxTokens } : {},
1206
+ ...input.timeoutMs !== void 0 ? { timeoutMs: input.timeoutMs } : {},
1207
+ ...input.maxRetries !== void 0 ? { maxRetries: input.maxRetries } : {},
1208
+ ...input.retryDelayMs !== void 0 ? { retryDelayMs: input.retryDelayMs } : {},
1209
+ ...input.fetch ? { fetch: input.fetch } : {},
1210
+ ...input.thinking ? { thinking: input.thinking } : {}
1211
+ };
1212
+ }
1213
+ function normalizeHttpError3(response, body) {
1214
+ let providerMessage;
1215
+ if (isRecord(body) && isRecord(body.error)) {
1216
+ providerMessage = asTrimmedString(body.error.message);
1217
+ }
1218
+ const statusSummary = response.statusText ? `${response.status} ${response.statusText}` : String(response.status);
1219
+ const message = providerMessage ? `Anthropic request failed (${statusSummary}): ${providerMessage}` : `Anthropic request failed with status ${statusSummary}.`;
1220
+ return new ProviderError({
1221
+ code: "http_error",
1222
+ message,
1223
+ status: response.status,
1224
+ retryable: isRetryableStatus(response.status),
1225
+ details: { status: response.status, ...isRecord(body) ? { providerError: body.error } : {} }
1226
+ });
1227
+ }
1228
+ function convertContentParts(parts) {
1229
+ const hasNonText = parts.some((p) => p.type !== "text");
1230
+ if (!hasNonText) {
1231
+ return parts.filter((p) => p.type === "text").map((p) => p.text).join("\n");
1232
+ }
1233
+ const blocks = [];
1234
+ for (const part of parts) {
1235
+ if (part.type === "text") {
1236
+ blocks.push({ type: "text", text: part.text });
1237
+ } else if (part.type === "image") {
1238
+ blocks.push({
1239
+ type: "image",
1240
+ source: { type: "base64", media_type: part.mimeType, data: part.data }
1241
+ });
1242
+ } else if (part.type === "tool_use") {
1243
+ blocks.push({ type: "tool_use", id: part.id, name: part.name, input: part.input });
1244
+ } else if (part.type === "tool_result") {
1245
+ blocks.push({ type: "tool_result", tool_use_id: part.toolUseId, content: part.content, is_error: part.isError });
1246
+ }
1247
+ }
1248
+ return blocks;
1249
+ }
1250
+ function convertMessages3(messages) {
1251
+ const result = [];
1252
+ for (const msg of messages) {
1253
+ if (msg.role === "user") {
1254
+ if (typeof msg.content === "string") {
1255
+ result.push({ role: "user", content: msg.content });
1256
+ } else {
1257
+ result.push({ role: "user", content: convertContentParts(msg.content) });
1258
+ }
1259
+ } else if (msg.role === "assistant") {
1260
+ if (typeof msg.content === "string") {
1261
+ result.push({ role: "assistant", content: msg.content });
1262
+ } else {
1263
+ const hasToolUse = msg.content.some((p) => p.type === "tool_use");
1264
+ if (hasToolUse) {
1265
+ result.push({ role: "assistant", content: convertContentParts(msg.content) });
1266
+ } else {
1267
+ const texts = msg.content.filter((p) => p.type === "text").map((p) => p.text);
1268
+ result.push({ role: "assistant", content: texts.join("\n") });
1269
+ }
1270
+ }
1271
+ } else if (msg.role === "tool") {
1272
+ if (typeof msg.content === "string") {
1273
+ result.push({ role: "user", content: msg.content });
1274
+ } else {
1275
+ result.push({ role: "user", content: convertContentParts(msg.content) });
1276
+ }
1277
+ }
1278
+ }
1279
+ return result;
1280
+ }
1281
+ function convertTools3(tools) {
1282
+ return tools.map((tool) => {
1283
+ const schema = tool.parameters ?? {};
1284
+ return {
1285
+ name: tool.name,
1286
+ description: tool.description,
1287
+ input_schema: {
1288
+ type: "object",
1289
+ properties: schema.properties ?? {},
1290
+ required: schema.required ?? []
1291
+ }
1292
+ };
1293
+ });
1294
+ }
1295
+ function buildRequestBody3(config, request) {
1296
+ const messages = convertMessages3(request.messages);
1297
+ const body = {
1298
+ model: config.model,
1299
+ messages,
1300
+ max_tokens: request.maxTokens ?? config.maxTokens ?? DEFAULT_MAX_TOKENS,
1301
+ stream: true
1302
+ };
1303
+ if (request.system) body.system = request.system;
1304
+ if (request.temperature !== void 0) body.temperature = request.temperature;
1305
+ if (request.tools && request.tools.length > 0) body.tools = convertTools3(request.tools);
1306
+ if (config.thinking) {
1307
+ body.thinking = { type: "enabled", budget_tokens: config.thinking.budgetTokens };
1308
+ }
1309
+ return body;
1310
+ }
1311
+ function mapStopReason2(reason) {
1312
+ switch (reason) {
1313
+ case "end_turn":
1314
+ return "end_turn";
1315
+ case "max_tokens":
1316
+ return "max_tokens";
1317
+ case "tool_use":
1318
+ return "tool_use";
1319
+ case "stop_sequence":
1320
+ return "stop";
1321
+ default:
1322
+ return "unknown";
1323
+ }
1324
+ }
1325
+ var MESSAGE_EVENTS = /* @__PURE__ */ new Set([
1326
+ "message_start",
1327
+ "message_delta",
1328
+ "message_stop",
1329
+ "content_block_start",
1330
+ "content_block_delta",
1331
+ "content_block_stop"
1332
+ ]);
1333
+ async function* streamAnthropicMessages(config, request, options = {}) {
1334
+ const maxRetries = normalizeRetryNumber(config.maxRetries, DEFAULT_MAX_RETRIES);
1335
+ const retryDelayMs = normalizeRetryNumber(config.retryDelayMs, DEFAULT_RETRY_DELAY_MS);
1336
+ const body = buildRequestBody3(config, request);
1337
+ const fetchImpl = config.fetch ?? fetch;
1338
+ const endpoint = `${normalizeBaseUrl(config.baseUrl)}/v1/messages`;
1339
+ const requestUsage = summarizeRequestUsage(request);
1340
+ let attempts = 0;
1341
+ while (true) {
1342
+ attempts += 1;
1343
+ const { signal, cleanup } = createRequestSignal({ signal: options.signal, timeoutMs: config.timeoutMs });
1344
+ try {
1345
+ let rebuildPartial2 = function() {
1346
+ partial.contentBlocks = [];
1347
+ if (thinking) {
1348
+ partial.contentBlocks.push({ type: "thinking", thinking });
1349
+ }
1350
+ if (content) {
1351
+ partial.contentBlocks.push({ type: "text", text: content });
1352
+ }
1353
+ for (const tc of toolCalls.values()) {
1354
+ partial.contentBlocks.push({
1355
+ type: "tool_call",
1356
+ id: tc.id,
1357
+ name: tc.name,
1358
+ args: tc.arguments
1359
+ });
1360
+ }
1361
+ };
1362
+ var rebuildPartial = rebuildPartial2;
1363
+ const response = await fetchImpl(endpoint, {
1364
+ method: "POST",
1365
+ headers: {
1366
+ "content-type": "application/json",
1367
+ "x-api-key": config.apiKey,
1368
+ "anthropic-version": "2023-06-01"
1369
+ },
1370
+ body: JSON.stringify(body),
1371
+ signal
1372
+ });
1373
+ if (!response.ok) throw normalizeHttpError3(response, await readErrorBody(response));
1374
+ if (!response.body) {
1375
+ throw new ProviderError({ code: "no_body", message: "Response body is null.", retryable: true });
1376
+ }
1377
+ yield { type: "model_requested", model: request.model, usage: { ...requestUsage } };
1378
+ let content = "";
1379
+ let thinking = "";
1380
+ let stopReason = null;
1381
+ let inputTokens = 0;
1382
+ let outputTokens = 0;
1383
+ const toolCalls = /* @__PURE__ */ new Map();
1384
+ const blockTypes = /* @__PURE__ */ new Map();
1385
+ const partial = {
1386
+ role: "assistant",
1387
+ contentBlocks: []
1388
+ };
1389
+ for await (const sse of iterateSseMessages(response.body, signal)) {
1390
+ if (!sse.event || !MESSAGE_EVENTS.has(sse.event)) continue;
1391
+ let event;
1392
+ try {
1393
+ event = JSON.parse(sse.data);
1394
+ } catch {
1395
+ continue;
1396
+ }
1397
+ switch (event.type) {
1398
+ case "message_start":
1399
+ inputTokens = event.message.usage.input_tokens;
1400
+ outputTokens = event.message.usage.output_tokens;
1401
+ break;
1402
+ case "content_block_start":
1403
+ blockTypes.set(event.index, event.content_block.type);
1404
+ if (event.content_block.type === "tool_use") {
1405
+ const tc = { id: event.content_block.id ?? "", name: event.content_block.name ?? "", arguments: "" };
1406
+ toolCalls.set(event.index, tc);
1407
+ rebuildPartial2();
1408
+ yield { type: "tool_call_start", id: tc.id, name: tc.name, partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
1409
+ }
1410
+ break;
1411
+ case "content_block_delta":
1412
+ if (event.delta.type === "text_delta") {
1413
+ content += event.delta.text;
1414
+ rebuildPartial2();
1415
+ yield { type: "text_delta", text: event.delta.text, partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
1416
+ } else if (event.delta.type === "thinking_delta") {
1417
+ thinking += event.delta.thinking;
1418
+ rebuildPartial2();
1419
+ yield { type: "thinking_delta", thinking: event.delta.thinking, partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
1420
+ } else if (event.delta.type === "input_json_delta") {
1421
+ const tc = toolCalls.get(event.index);
1422
+ if (tc) {
1423
+ tc.arguments += event.delta.partial_json;
1424
+ rebuildPartial2();
1425
+ yield { type: "tool_call_delta", id: tc.id, arguments: event.delta.partial_json, partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
1426
+ }
1427
+ }
1428
+ break;
1429
+ case "content_block_stop": {
1430
+ if (blockTypes.get(event.index) === "tool_use") {
1431
+ const tc = toolCalls.get(event.index);
1432
+ if (tc) {
1433
+ rebuildPartial2();
1434
+ yield { type: "tool_call_end", id: tc.id, name: tc.name, arguments: tc.arguments, partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
1435
+ }
1436
+ }
1437
+ break;
1438
+ }
1439
+ case "message_delta":
1440
+ if (event.delta.stop_reason) stopReason = event.delta.stop_reason;
1441
+ outputTokens = event.usage.output_tokens;
1442
+ break;
1443
+ case "message_stop":
1444
+ break;
1445
+ }
1446
+ }
1447
+ const toolCallResults = [];
1448
+ for (const tc of toolCalls.values()) {
1449
+ let parsedArgs = tc.arguments;
1450
+ try {
1451
+ parsedArgs = JSON.parse(tc.arguments);
1452
+ } catch {
1453
+ }
1454
+ toolCallResults.push({ id: tc.id, name: tc.name, arguments: parsedArgs });
1455
+ }
1456
+ const usage = { inputTokens, outputTokens, totalTokens: inputTokens + outputTokens };
1457
+ yield {
1458
+ type: "done",
1459
+ response: {
1460
+ content,
1461
+ ...thinking ? { thinking } : {},
1462
+ stopReason: mapStopReason2(stopReason ?? "end_turn"),
1463
+ ...toolCallResults.length > 0 ? { toolCalls: toolCallResults } : {},
1464
+ usage
1465
+ }
1466
+ };
1467
+ return;
1468
+ } catch (error) {
1469
+ const requestError = normalizeRequestError(error, signal);
1470
+ if (!shouldRetry({ error: requestError, attempts, maxRetries, signal: options.signal })) {
1471
+ yield { type: "error", error: requestError };
1472
+ yield { type: "done", response: { content: "", stopReason: "error" } };
1473
+ return;
1474
+ }
1475
+ await waitForRetry(retryDelayMs * 2 ** (attempts - 1), options.signal);
1476
+ } finally {
1477
+ cleanup();
1478
+ }
1479
+ }
1480
+ }
1481
+ function createAnthropicStream(config) {
1482
+ const normalizedConfig = { ...config, baseUrl: normalizeBaseUrl(config.baseUrl) };
1483
+ return async function* anthropicStream(request, options) {
1484
+ yield* streamAnthropicMessages(normalizedConfig, request, options);
1485
+ };
1486
+ }
1487
+ var streamAnthropic = (model, request, options) => {
1488
+ const config = resolveAnthropicConfig({
1489
+ baseUrl: model.baseUrl,
1490
+ model: model.id
1491
+ });
1492
+ return streamAnthropicMessages(config, request, options);
1493
+ };
1494
+
1495
+ // src/registry.ts
1496
+ var apiProviderRegistry = /* @__PURE__ */ new Map();
1497
+ function registerApiProvider(provider) {
1498
+ apiProviderRegistry.set(provider.api, provider);
1499
+ }
1500
+ function getApiProvider(api) {
1501
+ return apiProviderRegistry.get(api);
1502
+ }
1503
+ function listApiProviders() {
1504
+ return [...apiProviderRegistry.keys()];
1505
+ }
1506
+ function clearApiProviders() {
1507
+ apiProviderRegistry.clear();
1508
+ }
1509
+ function unregisterApiProvider(api) {
1510
+ return apiProviderRegistry.delete(api);
1511
+ }
1512
+ function registerBuiltInApiProviders() {
1513
+ registerApiProvider({ api: "openai-completions", stream: streamOpenAICompletions });
1514
+ registerApiProvider({ api: "openai-responses", stream: streamOpenAIResponses });
1515
+ registerApiProvider({ api: "anthropic-messages", stream: streamAnthropic });
1516
+ }
1517
+ function stream(model, request, options) {
1518
+ const provider = apiProviderRegistry.get(model.api);
1519
+ if (!provider) {
1520
+ throw new Error(
1521
+ `No API provider registered for api "${model.api}". Registered: ${[...apiProviderRegistry.keys()].join(", ") || "(none)"}. Call registerBuiltInApiProviders() or registerApiProvider() first.`
1522
+ );
1523
+ }
1524
+ return provider.stream(model, request, options);
1525
+ }
1526
+ function streamByRef(ref, request, options = {}) {
1527
+ const model = typeof ref === "string" ? resolveModel(ref) : getModel(ref.provider, ref.name);
1528
+ if (!model) {
1529
+ const key = typeof ref === "string" ? ref : `${ref.provider}/${ref.name}`;
1530
+ throw new Error(`Model not found: "${key}". Register it with registerModel() first.`);
1531
+ }
1532
+ return stream(model, { ...request, model: { provider: model.provider, name: model.id } }, options);
1533
+ }
1534
+ var legacyProviders = /* @__PURE__ */ new Map();
1535
+ function registerProvider(name, factory) {
1536
+ legacyProviders.set(name, factory);
1537
+ }
1538
+ function createDispatchStream() {
1539
+ if (apiProviderRegistry.size === 0) {
1540
+ registerBuiltInApiProviders();
1541
+ }
1542
+ return async function* dispatchStream(request, options) {
1543
+ const model = getModel(request.model.provider, request.model.name) ?? resolveModel(request.model.name);
1544
+ if (model) {
1545
+ yield* stream(model, request, options);
1546
+ return;
1547
+ }
1548
+ const factory = legacyProviders.get(request.model.provider);
1549
+ if (factory) {
1550
+ const streamFn = factory(request.model);
1551
+ yield* streamFn(request, options);
1552
+ return;
1553
+ }
1554
+ throw new Error(
1555
+ `No provider for "${request.model.provider}/${request.model.name}". Register a model with registerModel() or a provider with registerProvider().`
1556
+ );
1557
+ };
1558
+ }
1559
+ // Annotate the CommonJS export names for ESM import in node:
1560
+ 0 && (module.exports = {
1561
+ MODELS,
1562
+ ProviderError,
1563
+ calculateCost,
1564
+ callOpenAICompletions,
1565
+ clearApiProviders,
1566
+ clearModels,
1567
+ createAnthropicStream,
1568
+ createDispatchStream,
1569
+ createOpenAICompletionsStream,
1570
+ createOpenAIResponsesStream,
1571
+ getAllModels,
1572
+ getApiProvider,
1573
+ getModel,
1574
+ getModels,
1575
+ getProviders,
1576
+ iterateSseMessages,
1577
+ listApiProviders,
1578
+ registerApiProvider,
1579
+ registerBuiltInApiProviders,
1580
+ registerModel,
1581
+ registerProvider,
1582
+ resolveAnthropicConfig,
1583
+ resolveModel,
1584
+ resolveOpenAICompletionsConfig,
1585
+ resolveOpenAIResponsesConfig,
1586
+ stream,
1587
+ streamAnthropic,
1588
+ streamByRef,
1589
+ streamOpenAICompletions,
1590
+ streamOpenAIResponses,
1591
+ textFromPartial,
1592
+ toolCallsFromPartial,
1593
+ unregisterApiProvider,
1594
+ unregisterProviderModels
1595
+ });