@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/README.md +36 -0
- package/dist/chunk-J6S6ZJJA.js +1227 -0
- package/dist/index-ChnUfIk1.d.cts +426 -0
- package/dist/index-ChnUfIk1.d.ts +426 -0
- package/dist/index.cjs +1595 -0
- package/dist/index.d.cts +109 -0
- package/dist/index.d.ts +109 -0
- package/dist/index.js +337 -0
- package/dist/providers/index.cjs +1262 -0
- package/dist/providers/index.d.cts +1 -0
- package/dist/providers/index.d.ts +1 -0
- package/dist/providers/index.js +26 -0
- package/package.json +29 -0
|
@@ -0,0 +1,1262 @@
|
|
|
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/providers/index.ts
|
|
21
|
+
var providers_exports = {};
|
|
22
|
+
__export(providers_exports, {
|
|
23
|
+
ProviderError: () => ProviderError,
|
|
24
|
+
callOpenAICompletions: () => callOpenAICompletions,
|
|
25
|
+
createAnthropicStream: () => createAnthropicStream,
|
|
26
|
+
createOpenAICompletionsStream: () => createOpenAICompletionsStream,
|
|
27
|
+
createOpenAIResponsesStream: () => createOpenAIResponsesStream,
|
|
28
|
+
resolveAnthropicConfig: () => resolveAnthropicConfig,
|
|
29
|
+
resolveOpenAICompletionsConfig: () => resolveOpenAICompletionsConfig,
|
|
30
|
+
resolveOpenAIResponsesConfig: () => resolveOpenAIResponsesConfig,
|
|
31
|
+
streamAnthropic: () => streamAnthropic,
|
|
32
|
+
streamOpenAICompletions: () => streamOpenAICompletions,
|
|
33
|
+
streamOpenAIResponses: () => streamOpenAIResponses
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(providers_exports);
|
|
36
|
+
|
|
37
|
+
// src/providers/shared.ts
|
|
38
|
+
var ProviderError = class extends Error {
|
|
39
|
+
code;
|
|
40
|
+
status;
|
|
41
|
+
retryable;
|
|
42
|
+
details;
|
|
43
|
+
constructor(input) {
|
|
44
|
+
super(input.message);
|
|
45
|
+
this.name = "ProviderError";
|
|
46
|
+
this.code = input.code;
|
|
47
|
+
this.status = input.status;
|
|
48
|
+
this.retryable = input.retryable ?? false;
|
|
49
|
+
this.details = input.details;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
function defaultEnv() {
|
|
53
|
+
return process.env;
|
|
54
|
+
}
|
|
55
|
+
function normalizeBaseUrl(baseUrl) {
|
|
56
|
+
return baseUrl.replace(/\/+$/, "");
|
|
57
|
+
}
|
|
58
|
+
function nonEmpty(value) {
|
|
59
|
+
const trimmed = value?.trim();
|
|
60
|
+
return trimmed && trimmed.length > 0 ? trimmed : void 0;
|
|
61
|
+
}
|
|
62
|
+
function requireValue(name, value, hint) {
|
|
63
|
+
const normalized = nonEmpty(value);
|
|
64
|
+
if (!normalized) {
|
|
65
|
+
throw new ProviderError({
|
|
66
|
+
code: "missing_config",
|
|
67
|
+
message: `Missing ${name}: ${hint}.`
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return normalized;
|
|
71
|
+
}
|
|
72
|
+
function isRecord(value) {
|
|
73
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
74
|
+
}
|
|
75
|
+
function asNumber(value) {
|
|
76
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
77
|
+
}
|
|
78
|
+
function asTrimmedString(value) {
|
|
79
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
80
|
+
}
|
|
81
|
+
function truncateString(value, maxLength = 4e3) {
|
|
82
|
+
return value.length > maxLength ? `${value.slice(0, maxLength)}...` : value;
|
|
83
|
+
}
|
|
84
|
+
function createRequestSignal(input) {
|
|
85
|
+
if (!input.signal && !input.timeoutMs) {
|
|
86
|
+
return { cleanup: () => void 0 };
|
|
87
|
+
}
|
|
88
|
+
const controller = new AbortController();
|
|
89
|
+
let timeout;
|
|
90
|
+
const abortFromParent = () => {
|
|
91
|
+
controller.abort(input.signal?.reason ?? new Error("Request aborted."));
|
|
92
|
+
};
|
|
93
|
+
if (input.signal?.aborted) {
|
|
94
|
+
abortFromParent();
|
|
95
|
+
} else {
|
|
96
|
+
input.signal?.addEventListener("abort", abortFromParent, { once: true });
|
|
97
|
+
}
|
|
98
|
+
if (input.timeoutMs) {
|
|
99
|
+
timeout = setTimeout(() => {
|
|
100
|
+
controller.abort(new Error(`Request timed out after ${input.timeoutMs}ms.`));
|
|
101
|
+
}, input.timeoutMs);
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
signal: controller.signal,
|
|
105
|
+
cleanup: () => {
|
|
106
|
+
if (timeout) clearTimeout(timeout);
|
|
107
|
+
input.signal?.removeEventListener("abort", abortFromParent);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
async function readErrorBody(response) {
|
|
112
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
113
|
+
try {
|
|
114
|
+
if (contentType.includes("application/json")) {
|
|
115
|
+
return await response.json();
|
|
116
|
+
}
|
|
117
|
+
return await response.text();
|
|
118
|
+
} catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function isRetryableStatus(status) {
|
|
123
|
+
return status === 408 || status === 409 || status === 429 || status >= 500;
|
|
124
|
+
}
|
|
125
|
+
function normalizeRequestError(error, signal) {
|
|
126
|
+
if (error instanceof ProviderError) return error;
|
|
127
|
+
if (signal?.aborted) {
|
|
128
|
+
return new ProviderError({
|
|
129
|
+
code: "request_aborted",
|
|
130
|
+
message: signal.reason instanceof Error ? signal.reason.message : "Request aborted.",
|
|
131
|
+
retryable: true
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return new ProviderError({
|
|
135
|
+
code: "request_failed",
|
|
136
|
+
message: error instanceof Error ? error.message : "Request failed.",
|
|
137
|
+
retryable: true
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
var DEFAULT_MAX_RETRIES = 2;
|
|
141
|
+
var DEFAULT_RETRY_DELAY_MS = 500;
|
|
142
|
+
function normalizeRetryNumber(value, fallback) {
|
|
143
|
+
if (value === void 0) return fallback;
|
|
144
|
+
return Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
|
|
145
|
+
}
|
|
146
|
+
function shouldRetry(input) {
|
|
147
|
+
return input.error.retryable && input.attempts < input.maxRetries && !input.signal?.aborted;
|
|
148
|
+
}
|
|
149
|
+
async function waitForRetry(delayMs, signal) {
|
|
150
|
+
if (delayMs <= 0) return;
|
|
151
|
+
if (signal?.aborted) {
|
|
152
|
+
throw new ProviderError({
|
|
153
|
+
code: "request_aborted",
|
|
154
|
+
message: "Request aborted.",
|
|
155
|
+
retryable: true
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
await new Promise((resolve, reject) => {
|
|
159
|
+
const timeout = setTimeout(() => {
|
|
160
|
+
signal?.removeEventListener("abort", abort);
|
|
161
|
+
resolve();
|
|
162
|
+
}, delayMs);
|
|
163
|
+
const abort = () => {
|
|
164
|
+
clearTimeout(timeout);
|
|
165
|
+
reject(new ProviderError({
|
|
166
|
+
code: "request_aborted",
|
|
167
|
+
message: "Request aborted.",
|
|
168
|
+
retryable: true
|
|
169
|
+
}));
|
|
170
|
+
};
|
|
171
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
function normalizeUsage(usage) {
|
|
175
|
+
if (!usage) return void 0;
|
|
176
|
+
const inputTokens = asNumber(usage.prompt_tokens) ?? asNumber(usage.input_tokens);
|
|
177
|
+
const outputTokens = asNumber(usage.completion_tokens) ?? asNumber(usage.output_tokens);
|
|
178
|
+
const totalTokens = asNumber(usage.total_tokens);
|
|
179
|
+
if (inputTokens === void 0 && outputTokens === void 0 && totalTokens === void 0) {
|
|
180
|
+
return void 0;
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
...inputTokens !== void 0 ? { inputTokens } : {},
|
|
184
|
+
...outputTokens !== void 0 ? { outputTokens } : {},
|
|
185
|
+
...totalTokens !== void 0 ? { totalTokens } : {}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function summarizeRequestUsage(request) {
|
|
189
|
+
return { inputMessages: request.messages.length + (request.system ? 1 : 0) };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// src/sse.ts
|
|
193
|
+
function flushSseEvent(state) {
|
|
194
|
+
if (!state.event && state.data.length === 0) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
const event = {
|
|
198
|
+
event: state.event,
|
|
199
|
+
data: state.data.join("\n")
|
|
200
|
+
};
|
|
201
|
+
state.event = null;
|
|
202
|
+
state.data = [];
|
|
203
|
+
return event;
|
|
204
|
+
}
|
|
205
|
+
function decodeSseLine(line, state) {
|
|
206
|
+
if (line === "") {
|
|
207
|
+
return flushSseEvent(state);
|
|
208
|
+
}
|
|
209
|
+
if (line.startsWith(":")) {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
const delimiterIndex = line.indexOf(":");
|
|
213
|
+
const fieldName = delimiterIndex === -1 ? line : line.slice(0, delimiterIndex);
|
|
214
|
+
let value = delimiterIndex === -1 ? "" : line.slice(delimiterIndex + 1);
|
|
215
|
+
if (value.startsWith(" ")) {
|
|
216
|
+
value = value.slice(1);
|
|
217
|
+
}
|
|
218
|
+
if (fieldName === "event") {
|
|
219
|
+
state.event = value;
|
|
220
|
+
} else if (fieldName === "data") {
|
|
221
|
+
state.data.push(value);
|
|
222
|
+
}
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
function nextLineBreakIndex(text) {
|
|
226
|
+
const cr = text.indexOf("\r");
|
|
227
|
+
const lf = text.indexOf("\n");
|
|
228
|
+
if (cr === -1) return lf;
|
|
229
|
+
if (lf === -1) return cr;
|
|
230
|
+
return Math.min(cr, lf);
|
|
231
|
+
}
|
|
232
|
+
function consumeLine(text) {
|
|
233
|
+
const index = nextLineBreakIndex(text);
|
|
234
|
+
if (index === -1) return null;
|
|
235
|
+
let nextIndex = index + 1;
|
|
236
|
+
if (text[index] === "\r" && text[nextIndex] === "\n") {
|
|
237
|
+
nextIndex += 1;
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
line: text.slice(0, index),
|
|
241
|
+
rest: text.slice(nextIndex)
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
async function* iterateSseMessages(body, signal) {
|
|
245
|
+
const reader = body.getReader();
|
|
246
|
+
const decoder = new TextDecoder();
|
|
247
|
+
const state = { event: null, data: [] };
|
|
248
|
+
let buffer = "";
|
|
249
|
+
try {
|
|
250
|
+
while (true) {
|
|
251
|
+
if (signal?.aborted) {
|
|
252
|
+
throw new Error("Request was aborted");
|
|
253
|
+
}
|
|
254
|
+
const { value, done } = await reader.read();
|
|
255
|
+
if (done) break;
|
|
256
|
+
buffer += decoder.decode(value, { stream: true });
|
|
257
|
+
let consumed2 = consumeLine(buffer);
|
|
258
|
+
while (consumed2) {
|
|
259
|
+
buffer = consumed2.rest;
|
|
260
|
+
const event = decodeSseLine(consumed2.line, state);
|
|
261
|
+
if (event) yield event;
|
|
262
|
+
consumed2 = consumeLine(buffer);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
buffer += decoder.decode();
|
|
266
|
+
let consumed = consumeLine(buffer);
|
|
267
|
+
while (consumed) {
|
|
268
|
+
buffer = consumed.rest;
|
|
269
|
+
const event = decodeSseLine(consumed.line, state);
|
|
270
|
+
if (event) yield event;
|
|
271
|
+
consumed = consumeLine(buffer);
|
|
272
|
+
}
|
|
273
|
+
if (buffer.length > 0) {
|
|
274
|
+
const event = decodeSseLine(buffer, state);
|
|
275
|
+
if (event) yield event;
|
|
276
|
+
}
|
|
277
|
+
const trailingEvent = flushSseEvent(state);
|
|
278
|
+
if (trailingEvent) yield trailingEvent;
|
|
279
|
+
} finally {
|
|
280
|
+
reader.releaseLock();
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/providers/openai-completions.ts
|
|
285
|
+
function resolveOpenAICompletionsConfig(input = {}) {
|
|
286
|
+
const env = input.env ?? defaultEnv();
|
|
287
|
+
const baseUrl = nonEmpty(input.baseUrl) ?? nonEmpty(env.ROWAN_OPENAI_BASE_URL) ?? "https://api.openai.com/v1";
|
|
288
|
+
const apiKey = nonEmpty(input.apiKey) ?? nonEmpty(env.ROWAN_OPENAI_API_KEY);
|
|
289
|
+
const model = nonEmpty(input.model) ?? nonEmpty(env.ROWAN_MODEL);
|
|
290
|
+
return {
|
|
291
|
+
baseUrl: normalizeBaseUrl(baseUrl),
|
|
292
|
+
apiKey: requireValue("API key", apiKey, "set ROWAN_OPENAI_API_KEY or pass --api-key"),
|
|
293
|
+
model: requireValue("model", model, "set ROWAN_MODEL or pass --model"),
|
|
294
|
+
...input.temperature !== void 0 ? { temperature: input.temperature } : {},
|
|
295
|
+
...input.maxTokens !== void 0 ? { maxTokens: input.maxTokens } : {},
|
|
296
|
+
...input.timeoutMs !== void 0 ? { timeoutMs: input.timeoutMs } : {},
|
|
297
|
+
...input.maxRetries !== void 0 ? { maxRetries: input.maxRetries } : {},
|
|
298
|
+
...input.retryDelayMs !== void 0 ? { retryDelayMs: input.retryDelayMs } : {},
|
|
299
|
+
...input.fetch ? { fetch: input.fetch } : {},
|
|
300
|
+
...input.responseFormat !== void 0 ? { responseFormat: input.responseFormat } : {}
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
function providerErrorFromBody(body) {
|
|
304
|
+
if (typeof body === "string") {
|
|
305
|
+
const message2 = asTrimmedString(body);
|
|
306
|
+
return message2 ? { message: truncateString(message2) } : void 0;
|
|
307
|
+
}
|
|
308
|
+
if (!isRecord(body)) return void 0;
|
|
309
|
+
const error = body.error;
|
|
310
|
+
if (typeof error === "string") {
|
|
311
|
+
const message2 = asTrimmedString(error);
|
|
312
|
+
return message2 ? { message: message2 } : void 0;
|
|
313
|
+
}
|
|
314
|
+
const source = isRecord(error) ? error : body;
|
|
315
|
+
const message = asTrimmedString(source.message);
|
|
316
|
+
const code = asTrimmedString(source.code);
|
|
317
|
+
const type = asTrimmedString(source.type);
|
|
318
|
+
if (!message && !code && !type) return void 0;
|
|
319
|
+
return { ...message ? { message } : {}, ...code ? { code } : {}, ...type ? { type } : {} };
|
|
320
|
+
}
|
|
321
|
+
function normalizeHttpError(response, body, context) {
|
|
322
|
+
const providerError = providerErrorFromBody(body);
|
|
323
|
+
const providerMessage = asTrimmedString(providerError?.message);
|
|
324
|
+
const statusSummary = response.statusText ? `${response.status} ${response.statusText}` : String(response.status);
|
|
325
|
+
const message = providerMessage ? `Request failed (${statusSummary}): ${providerMessage}` : `Request failed with status ${statusSummary}.`;
|
|
326
|
+
return new ProviderError({
|
|
327
|
+
code: "http_error",
|
|
328
|
+
message,
|
|
329
|
+
status: response.status,
|
|
330
|
+
retryable: isRetryableStatus(response.status),
|
|
331
|
+
details: {
|
|
332
|
+
endpoint: context.endpoint,
|
|
333
|
+
model: context.model,
|
|
334
|
+
status: response.status,
|
|
335
|
+
...providerError ? { providerError } : {}
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
function convertMessages(messages) {
|
|
340
|
+
const result = [];
|
|
341
|
+
for (const msg of messages) {
|
|
342
|
+
if (msg.role === "user") {
|
|
343
|
+
if (typeof msg.content === "string") {
|
|
344
|
+
result.push({ role: "user", content: msg.content });
|
|
345
|
+
} else {
|
|
346
|
+
const parts = [];
|
|
347
|
+
for (const part of msg.content) {
|
|
348
|
+
if (part.type === "text") {
|
|
349
|
+
parts.push({ type: "text", text: part.text });
|
|
350
|
+
} else if (part.type === "image") {
|
|
351
|
+
parts.push({ type: "image_url", image_url: { url: `data:${part.mimeType};base64,${part.data}` } });
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
result.push({ role: "user", content: parts });
|
|
355
|
+
}
|
|
356
|
+
} else if (msg.role === "assistant") {
|
|
357
|
+
if (typeof msg.content === "string") {
|
|
358
|
+
result.push({ role: "assistant", content: msg.content });
|
|
359
|
+
} else {
|
|
360
|
+
const toolUseBlocks = msg.content.filter((p) => p.type === "tool_use");
|
|
361
|
+
const textBlocks = msg.content.filter((p) => p.type === "text");
|
|
362
|
+
const text = textBlocks.map((p) => p.text).join("") || null;
|
|
363
|
+
if (toolUseBlocks.length > 0) {
|
|
364
|
+
const toolCalls = toolUseBlocks.map((p) => ({
|
|
365
|
+
id: p.id,
|
|
366
|
+
type: "function",
|
|
367
|
+
function: {
|
|
368
|
+
name: p.name,
|
|
369
|
+
arguments: typeof p.input === "string" ? p.input : JSON.stringify(p.input)
|
|
370
|
+
}
|
|
371
|
+
}));
|
|
372
|
+
result.push({ role: "assistant", content: text, tool_calls: toolCalls });
|
|
373
|
+
} else {
|
|
374
|
+
result.push({ role: "assistant", content: text });
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
} else if (msg.role === "tool") {
|
|
378
|
+
if (typeof msg.content === "string") {
|
|
379
|
+
result.push({ role: "tool", content: msg.content, tool_call_id: "" });
|
|
380
|
+
} else {
|
|
381
|
+
for (const part of msg.content) {
|
|
382
|
+
if (part.type === "tool_result") {
|
|
383
|
+
result.push({ role: "tool", content: part.content, tool_call_id: part.toolUseId });
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return result;
|
|
390
|
+
}
|
|
391
|
+
function convertTools(tools) {
|
|
392
|
+
return tools.map((tool) => ({
|
|
393
|
+
type: "function",
|
|
394
|
+
function: { name: tool.name, description: tool.description, parameters: tool.parameters }
|
|
395
|
+
}));
|
|
396
|
+
}
|
|
397
|
+
function buildRequestBody(config, request, stream) {
|
|
398
|
+
const messages = convertMessages(request.messages);
|
|
399
|
+
if (request.system) {
|
|
400
|
+
messages.unshift({ role: "system", content: request.system });
|
|
401
|
+
}
|
|
402
|
+
const body = {
|
|
403
|
+
model: config.model,
|
|
404
|
+
messages,
|
|
405
|
+
stream
|
|
406
|
+
};
|
|
407
|
+
if (stream) {
|
|
408
|
+
body.stream_options = { include_usage: true };
|
|
409
|
+
}
|
|
410
|
+
if (request.temperature !== void 0 || config.temperature !== void 0) {
|
|
411
|
+
body.temperature = request.temperature ?? config.temperature ?? 0;
|
|
412
|
+
}
|
|
413
|
+
if (request.maxTokens ?? config.maxTokens) {
|
|
414
|
+
body.max_tokens = request.maxTokens ?? config.maxTokens;
|
|
415
|
+
}
|
|
416
|
+
if (request.tools && request.tools.length > 0) {
|
|
417
|
+
body.tools = convertTools(request.tools);
|
|
418
|
+
}
|
|
419
|
+
if (config.responseFormat) {
|
|
420
|
+
body.response_format = { type: "json_object" };
|
|
421
|
+
}
|
|
422
|
+
return body;
|
|
423
|
+
}
|
|
424
|
+
function mapFinishReason(reason) {
|
|
425
|
+
switch (reason) {
|
|
426
|
+
case null:
|
|
427
|
+
case void 0:
|
|
428
|
+
return "end_turn";
|
|
429
|
+
case "stop":
|
|
430
|
+
return "end_turn";
|
|
431
|
+
case "length":
|
|
432
|
+
return "max_tokens";
|
|
433
|
+
case "tool_calls":
|
|
434
|
+
return "tool_use";
|
|
435
|
+
case "content_filter":
|
|
436
|
+
return "error";
|
|
437
|
+
default:
|
|
438
|
+
return "unknown";
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
async function* streamChatCompletions(config, request, options = {}) {
|
|
442
|
+
const maxRetries = normalizeRetryNumber(config.maxRetries, DEFAULT_MAX_RETRIES);
|
|
443
|
+
const retryDelayMs = normalizeRetryNumber(config.retryDelayMs, DEFAULT_RETRY_DELAY_MS);
|
|
444
|
+
const body = buildRequestBody(config, request, true);
|
|
445
|
+
const fetchImpl = config.fetch ?? fetch;
|
|
446
|
+
const endpoint = `${normalizeBaseUrl(config.baseUrl)}/chat/completions`;
|
|
447
|
+
const requestUsage = summarizeRequestUsage(request);
|
|
448
|
+
let attempts = 0;
|
|
449
|
+
while (true) {
|
|
450
|
+
attempts += 1;
|
|
451
|
+
const { signal, cleanup } = createRequestSignal({ signal: options.signal, timeoutMs: config.timeoutMs });
|
|
452
|
+
try {
|
|
453
|
+
let rebuildPartial2 = function() {
|
|
454
|
+
partial.contentBlocks = [];
|
|
455
|
+
if (content) {
|
|
456
|
+
partial.contentBlocks.push({ type: "text", text: content });
|
|
457
|
+
}
|
|
458
|
+
for (const tc of toolCalls.values()) {
|
|
459
|
+
partial.contentBlocks.push({
|
|
460
|
+
type: "tool_call",
|
|
461
|
+
id: tc.id,
|
|
462
|
+
name: tc.name,
|
|
463
|
+
args: tc.arguments
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
var rebuildPartial = rebuildPartial2;
|
|
468
|
+
const response = await fetchImpl(endpoint, {
|
|
469
|
+
method: "POST",
|
|
470
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${config.apiKey}` },
|
|
471
|
+
body: JSON.stringify(body),
|
|
472
|
+
signal
|
|
473
|
+
});
|
|
474
|
+
if (!response.ok) {
|
|
475
|
+
throw normalizeHttpError(response, await readErrorBody(response), { endpoint, model: config.model });
|
|
476
|
+
}
|
|
477
|
+
if (!response.body) {
|
|
478
|
+
throw new ProviderError({ code: "no_body", message: "Response body is null.", retryable: true });
|
|
479
|
+
}
|
|
480
|
+
yield { type: "model_requested", model: request.model, usage: { ...requestUsage } };
|
|
481
|
+
if (!response.headers.get("content-type")?.includes("text/event-stream")) {
|
|
482
|
+
const data = await response.json();
|
|
483
|
+
const choice = data.choices?.[0];
|
|
484
|
+
const message = choice?.message;
|
|
485
|
+
const content2 = message?.content ?? "";
|
|
486
|
+
const partial2 = {
|
|
487
|
+
role: "assistant",
|
|
488
|
+
contentBlocks: []
|
|
489
|
+
};
|
|
490
|
+
yield { type: "start", partial: { ...partial2, contentBlocks: [...partial2.contentBlocks] } };
|
|
491
|
+
if (content2) {
|
|
492
|
+
partial2.contentBlocks.push({ type: "text", text: content2 });
|
|
493
|
+
yield { type: "text_delta", text: content2, partial: { ...partial2, contentBlocks: [...partial2.contentBlocks] } };
|
|
494
|
+
}
|
|
495
|
+
const toolCallResults2 = [];
|
|
496
|
+
for (const [index, tc] of (message?.tool_calls ?? []).entries()) {
|
|
497
|
+
const id = tc.id ?? `call_${index}`;
|
|
498
|
+
const name = tc.function?.name ?? "";
|
|
499
|
+
const args = tc.function?.arguments ?? "";
|
|
500
|
+
partial2.contentBlocks.push({ type: "tool_call", id, name, args });
|
|
501
|
+
yield { type: "tool_call_start", id, name, partial: { ...partial2, contentBlocks: [...partial2.contentBlocks] } };
|
|
502
|
+
yield { type: "tool_call_end", id, name, arguments: args, partial: { ...partial2, contentBlocks: [...partial2.contentBlocks] } };
|
|
503
|
+
let parsedArgs = args;
|
|
504
|
+
try {
|
|
505
|
+
parsedArgs = JSON.parse(args);
|
|
506
|
+
} catch {
|
|
507
|
+
}
|
|
508
|
+
toolCallResults2.push({ id, name, arguments: parsedArgs });
|
|
509
|
+
}
|
|
510
|
+
const usage2 = normalizeUsage(data.usage);
|
|
511
|
+
yield {
|
|
512
|
+
type: "done",
|
|
513
|
+
response: {
|
|
514
|
+
content: content2,
|
|
515
|
+
stopReason: mapFinishReason(choice?.finish_reason),
|
|
516
|
+
...toolCallResults2.length > 0 ? { toolCalls: toolCallResults2 } : {},
|
|
517
|
+
...usage2 ? { usage: usage2 } : {}
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
let content = "";
|
|
523
|
+
let finishReason = null;
|
|
524
|
+
let usage;
|
|
525
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
526
|
+
const partial = {
|
|
527
|
+
role: "assistant",
|
|
528
|
+
contentBlocks: []
|
|
529
|
+
};
|
|
530
|
+
yield { type: "start", partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
|
|
531
|
+
for await (const sse of iterateSseMessages(response.body, signal)) {
|
|
532
|
+
if (sse.data === "[DONE]") break;
|
|
533
|
+
let chunk;
|
|
534
|
+
try {
|
|
535
|
+
chunk = JSON.parse(sse.data);
|
|
536
|
+
} catch {
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
if (chunk.usage) usage = normalizeUsage(chunk.usage);
|
|
540
|
+
const choice = chunk.choices?.[0];
|
|
541
|
+
if (!choice) continue;
|
|
542
|
+
const delta = choice.delta;
|
|
543
|
+
if (delta) {
|
|
544
|
+
if (delta.content) {
|
|
545
|
+
content += delta.content;
|
|
546
|
+
rebuildPartial2();
|
|
547
|
+
yield { type: "text_delta", text: delta.content, partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
|
|
548
|
+
}
|
|
549
|
+
if (delta.tool_calls) {
|
|
550
|
+
for (const tc of delta.tool_calls) {
|
|
551
|
+
const existing = toolCalls.get(tc.index);
|
|
552
|
+
if (!existing) {
|
|
553
|
+
const newTc = { id: tc.id ?? "", name: tc.function?.name ?? "", arguments: tc.function?.arguments ?? "" };
|
|
554
|
+
toolCalls.set(tc.index, newTc);
|
|
555
|
+
if (tc.id || tc.function?.name) {
|
|
556
|
+
rebuildPartial2();
|
|
557
|
+
yield { type: "tool_call_start", id: newTc.id, name: newTc.name, partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
|
|
558
|
+
}
|
|
559
|
+
if (tc.function?.arguments) {
|
|
560
|
+
yield { type: "tool_call_delta", id: newTc.id, arguments: tc.function.arguments, partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
|
|
561
|
+
}
|
|
562
|
+
} else {
|
|
563
|
+
if (tc.id) existing.id = tc.id;
|
|
564
|
+
if (tc.function?.name) existing.name = tc.function.name;
|
|
565
|
+
if (tc.function?.arguments) {
|
|
566
|
+
existing.arguments += tc.function.arguments;
|
|
567
|
+
rebuildPartial2();
|
|
568
|
+
yield { type: "tool_call_delta", id: existing.id, arguments: tc.function.arguments, partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (choice.finish_reason) finishReason = choice.finish_reason;
|
|
575
|
+
}
|
|
576
|
+
for (const tc of toolCalls.values()) {
|
|
577
|
+
rebuildPartial2();
|
|
578
|
+
yield { type: "tool_call_end", id: tc.id, name: tc.name, arguments: tc.arguments, partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
|
|
579
|
+
}
|
|
580
|
+
const toolCallResults = [];
|
|
581
|
+
for (const tc of toolCalls.values()) {
|
|
582
|
+
let parsedArgs = tc.arguments;
|
|
583
|
+
try {
|
|
584
|
+
parsedArgs = JSON.parse(tc.arguments);
|
|
585
|
+
} catch {
|
|
586
|
+
}
|
|
587
|
+
toolCallResults.push({ id: tc.id, name: tc.name, arguments: parsedArgs });
|
|
588
|
+
}
|
|
589
|
+
yield {
|
|
590
|
+
type: "done",
|
|
591
|
+
response: {
|
|
592
|
+
content,
|
|
593
|
+
stopReason: mapFinishReason(finishReason),
|
|
594
|
+
...toolCallResults.length > 0 ? { toolCalls: toolCallResults } : {},
|
|
595
|
+
...usage ? { usage } : {}
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
return;
|
|
599
|
+
} catch (error) {
|
|
600
|
+
const requestError = normalizeRequestError(error, signal);
|
|
601
|
+
if (!shouldRetry({ error: requestError, attempts, maxRetries, signal: options.signal })) {
|
|
602
|
+
yield { type: "error", error: requestError };
|
|
603
|
+
yield { type: "done", response: { content: "", stopReason: "error" } };
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
await waitForRetry(retryDelayMs * 2 ** (attempts - 1), options.signal);
|
|
607
|
+
} finally {
|
|
608
|
+
cleanup();
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
function createOpenAICompletionsStream(config) {
|
|
613
|
+
const normalizedConfig = { ...config, baseUrl: normalizeBaseUrl(config.baseUrl) };
|
|
614
|
+
return async function* openAICompletionsStream(request, options) {
|
|
615
|
+
yield* streamChatCompletions(normalizedConfig, request, options);
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
var streamOpenAICompletions = (model, request, options) => {
|
|
619
|
+
const config = resolveOpenAICompletionsConfig({
|
|
620
|
+
baseUrl: model.baseUrl,
|
|
621
|
+
model: model.id
|
|
622
|
+
});
|
|
623
|
+
return streamChatCompletions(config, request, options);
|
|
624
|
+
};
|
|
625
|
+
async function callOpenAICompletions(config, request, options = {}) {
|
|
626
|
+
const maxRetries = normalizeRetryNumber(config.maxRetries, DEFAULT_MAX_RETRIES);
|
|
627
|
+
const retryDelayMs = normalizeRetryNumber(config.retryDelayMs, DEFAULT_RETRY_DELAY_MS);
|
|
628
|
+
const body = buildRequestBody(config, request, false);
|
|
629
|
+
const fetchImpl = config.fetch ?? fetch;
|
|
630
|
+
const endpoint = `${normalizeBaseUrl(config.baseUrl)}/chat/completions`;
|
|
631
|
+
let attempts = 0;
|
|
632
|
+
while (true) {
|
|
633
|
+
attempts += 1;
|
|
634
|
+
const { signal, cleanup } = createRequestSignal({ signal: options.signal, timeoutMs: config.timeoutMs });
|
|
635
|
+
try {
|
|
636
|
+
const response = await fetchImpl(endpoint, {
|
|
637
|
+
method: "POST",
|
|
638
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${config.apiKey}` },
|
|
639
|
+
body: JSON.stringify(body),
|
|
640
|
+
signal
|
|
641
|
+
});
|
|
642
|
+
if (!response.ok) {
|
|
643
|
+
throw normalizeHttpError(response, await readErrorBody(response), { endpoint, model: config.model });
|
|
644
|
+
}
|
|
645
|
+
const data = await response.json();
|
|
646
|
+
return { content: data.choices?.[0]?.message?.content ?? "", usage: normalizeUsage(data.usage) };
|
|
647
|
+
} catch (error) {
|
|
648
|
+
const requestError = normalizeRequestError(error, signal);
|
|
649
|
+
if (!shouldRetry({ error: requestError, attempts, maxRetries, signal: options.signal })) throw requestError;
|
|
650
|
+
await waitForRetry(retryDelayMs * 2 ** (attempts - 1), options.signal);
|
|
651
|
+
} finally {
|
|
652
|
+
cleanup();
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// src/providers/openai-responses.ts
|
|
658
|
+
function resolveOpenAIResponsesConfig(input = {}) {
|
|
659
|
+
const env = input.env ?? defaultEnv();
|
|
660
|
+
const baseUrl = nonEmpty(input.baseUrl) ?? nonEmpty(env.ROWAN_OPENAI_BASE_URL) ?? "https://api.openai.com/v1";
|
|
661
|
+
const apiKey = nonEmpty(input.apiKey) ?? nonEmpty(env.ROWAN_OPENAI_API_KEY);
|
|
662
|
+
const model = nonEmpty(input.model) ?? nonEmpty(env.ROWAN_MODEL);
|
|
663
|
+
return {
|
|
664
|
+
baseUrl: normalizeBaseUrl(baseUrl),
|
|
665
|
+
apiKey: requireValue("API key", apiKey, "set ROWAN_OPENAI_API_KEY or pass --api-key"),
|
|
666
|
+
model: requireValue("model", model, "set ROWAN_MODEL or pass --model"),
|
|
667
|
+
...input.temperature !== void 0 ? { temperature: input.temperature } : {},
|
|
668
|
+
...input.maxTokens !== void 0 ? { maxTokens: input.maxTokens } : {},
|
|
669
|
+
...input.timeoutMs !== void 0 ? { timeoutMs: input.timeoutMs } : {},
|
|
670
|
+
...input.maxRetries !== void 0 ? { maxRetries: input.maxRetries } : {},
|
|
671
|
+
...input.retryDelayMs !== void 0 ? { retryDelayMs: input.retryDelayMs } : {},
|
|
672
|
+
...input.fetch ? { fetch: input.fetch } : {},
|
|
673
|
+
...input.reasoningEffort !== void 0 ? { reasoningEffort: input.reasoningEffort } : {}
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
function normalizeHttpError2(response, body, context) {
|
|
677
|
+
let providerMessage;
|
|
678
|
+
if (isRecord(body) && isRecord(body.error)) {
|
|
679
|
+
providerMessage = asTrimmedString(body.error.message);
|
|
680
|
+
}
|
|
681
|
+
const statusSummary = response.statusText ? `${response.status} ${response.statusText}` : String(response.status);
|
|
682
|
+
const message = providerMessage ? `Request failed (${statusSummary}): ${providerMessage}` : `Request failed with status ${statusSummary}.`;
|
|
683
|
+
return new ProviderError({
|
|
684
|
+
code: "http_error",
|
|
685
|
+
message,
|
|
686
|
+
status: response.status,
|
|
687
|
+
retryable: isRetryableStatus(response.status),
|
|
688
|
+
details: { endpoint: context.endpoint, model: context.model, status: response.status, ...isRecord(body) ? { providerError: body.error } : {} }
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
function convertMessages2(messages) {
|
|
692
|
+
const result = [];
|
|
693
|
+
for (const msg of messages) {
|
|
694
|
+
if (msg.role === "user") {
|
|
695
|
+
if (typeof msg.content === "string") {
|
|
696
|
+
result.push({ role: "user", content: msg.content });
|
|
697
|
+
} else {
|
|
698
|
+
const text = msg.content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
|
|
699
|
+
if (text) result.push({ role: "user", content: text });
|
|
700
|
+
}
|
|
701
|
+
} else if (msg.role === "assistant") {
|
|
702
|
+
if (typeof msg.content === "string") {
|
|
703
|
+
result.push({ role: "assistant", content: msg.content });
|
|
704
|
+
} else {
|
|
705
|
+
const text = msg.content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
|
|
706
|
+
if (text) result.push({ role: "assistant", content: text });
|
|
707
|
+
for (const part of msg.content) {
|
|
708
|
+
if (part.type === "tool_use") {
|
|
709
|
+
result.push({
|
|
710
|
+
type: "function_call",
|
|
711
|
+
id: part.id,
|
|
712
|
+
name: part.name,
|
|
713
|
+
arguments: typeof part.input === "string" ? part.input : JSON.stringify(part.input)
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
} else if (msg.role === "tool") {
|
|
719
|
+
if (typeof msg.content === "string") {
|
|
720
|
+
result.push({ type: "function_call_output", call_id: "", output: msg.content });
|
|
721
|
+
} else {
|
|
722
|
+
for (const part of msg.content) {
|
|
723
|
+
if (part.type === "tool_result") {
|
|
724
|
+
result.push({
|
|
725
|
+
type: "function_call_output",
|
|
726
|
+
call_id: part.toolUseId,
|
|
727
|
+
output: part.content
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
return result;
|
|
735
|
+
}
|
|
736
|
+
function convertTools2(tools) {
|
|
737
|
+
return tools.map((tool) => ({
|
|
738
|
+
type: "function",
|
|
739
|
+
name: tool.name,
|
|
740
|
+
description: tool.description,
|
|
741
|
+
parameters: tool.parameters,
|
|
742
|
+
strict: false
|
|
743
|
+
}));
|
|
744
|
+
}
|
|
745
|
+
function buildRequestBody2(config, request) {
|
|
746
|
+
const input = convertMessages2(request.messages);
|
|
747
|
+
const body = {
|
|
748
|
+
model: config.model,
|
|
749
|
+
input,
|
|
750
|
+
stream: true
|
|
751
|
+
};
|
|
752
|
+
if (request.system) {
|
|
753
|
+
body.instructions = request.system;
|
|
754
|
+
}
|
|
755
|
+
if (request.maxTokens ?? config.maxTokens) {
|
|
756
|
+
body.max_output_tokens = request.maxTokens ?? config.maxTokens;
|
|
757
|
+
}
|
|
758
|
+
if (request.tools && request.tools.length > 0) {
|
|
759
|
+
body.tools = convertTools2(request.tools);
|
|
760
|
+
}
|
|
761
|
+
if (config.reasoningEffort) {
|
|
762
|
+
body.reasoning = { effort: config.reasoningEffort, summary: "auto" };
|
|
763
|
+
}
|
|
764
|
+
return body;
|
|
765
|
+
}
|
|
766
|
+
function mapStopReason(reason) {
|
|
767
|
+
switch (reason) {
|
|
768
|
+
case "completed":
|
|
769
|
+
return "end_turn";
|
|
770
|
+
case "max_tokens":
|
|
771
|
+
return "max_tokens";
|
|
772
|
+
case "incomplete":
|
|
773
|
+
return "max_tokens";
|
|
774
|
+
default:
|
|
775
|
+
return "unknown";
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
async function* streamResponses(config, request, options = {}) {
|
|
779
|
+
const maxRetries = normalizeRetryNumber(config.maxRetries, DEFAULT_MAX_RETRIES);
|
|
780
|
+
const retryDelayMs = normalizeRetryNumber(config.retryDelayMs, DEFAULT_RETRY_DELAY_MS);
|
|
781
|
+
const body = buildRequestBody2(config, request);
|
|
782
|
+
const fetchImpl = config.fetch ?? fetch;
|
|
783
|
+
const endpoint = `${normalizeBaseUrl(config.baseUrl)}/responses`;
|
|
784
|
+
const requestUsage = summarizeRequestUsage(request);
|
|
785
|
+
let attempts = 0;
|
|
786
|
+
while (true) {
|
|
787
|
+
attempts += 1;
|
|
788
|
+
const { signal, cleanup } = createRequestSignal({ signal: options.signal, timeoutMs: config.timeoutMs });
|
|
789
|
+
try {
|
|
790
|
+
let rebuildPartial2 = function() {
|
|
791
|
+
partial.contentBlocks = [];
|
|
792
|
+
if (content) {
|
|
793
|
+
partial.contentBlocks.push({ type: "text", text: content });
|
|
794
|
+
}
|
|
795
|
+
for (const tc of toolCalls.values()) {
|
|
796
|
+
partial.contentBlocks.push({
|
|
797
|
+
type: "tool_call",
|
|
798
|
+
id: tc.id,
|
|
799
|
+
name: tc.name,
|
|
800
|
+
args: tc.arguments
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
};
|
|
804
|
+
var rebuildPartial = rebuildPartial2;
|
|
805
|
+
const response = await fetchImpl(endpoint, {
|
|
806
|
+
method: "POST",
|
|
807
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${config.apiKey}` },
|
|
808
|
+
body: JSON.stringify(body),
|
|
809
|
+
signal
|
|
810
|
+
});
|
|
811
|
+
if (!response.ok) {
|
|
812
|
+
throw normalizeHttpError2(response, await readErrorBody(response), { endpoint, model: config.model });
|
|
813
|
+
}
|
|
814
|
+
if (!response.body) {
|
|
815
|
+
throw new ProviderError({ code: "no_body", message: "Response body is null.", retryable: true });
|
|
816
|
+
}
|
|
817
|
+
yield { type: "model_requested", model: request.model, usage: { ...requestUsage } };
|
|
818
|
+
let content = "";
|
|
819
|
+
let stopReason = null;
|
|
820
|
+
let usage;
|
|
821
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
822
|
+
const partial = {
|
|
823
|
+
role: "assistant",
|
|
824
|
+
contentBlocks: []
|
|
825
|
+
};
|
|
826
|
+
yield { type: "start", partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
|
|
827
|
+
for await (const sse of iterateSseMessages(response.body, signal)) {
|
|
828
|
+
let event;
|
|
829
|
+
try {
|
|
830
|
+
event = JSON.parse(sse.data);
|
|
831
|
+
} catch {
|
|
832
|
+
continue;
|
|
833
|
+
}
|
|
834
|
+
switch (event.type) {
|
|
835
|
+
case "response.output_text.delta":
|
|
836
|
+
content += event.delta;
|
|
837
|
+
rebuildPartial2();
|
|
838
|
+
yield { type: "text_delta", text: event.delta, partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
|
|
839
|
+
break;
|
|
840
|
+
case "response.output_item.added":
|
|
841
|
+
if (event.item.type === "function_call") {
|
|
842
|
+
const tc = { id: event.item.id ?? "", name: event.item.name ?? "", arguments: "" };
|
|
843
|
+
toolCalls.set(event.output_index, tc);
|
|
844
|
+
rebuildPartial2();
|
|
845
|
+
yield { type: "tool_call_start", id: tc.id, name: tc.name, partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
|
|
846
|
+
}
|
|
847
|
+
break;
|
|
848
|
+
case "response.function_call_arguments.delta": {
|
|
849
|
+
const tc = toolCalls.get(event.output_index);
|
|
850
|
+
if (tc) {
|
|
851
|
+
tc.arguments += event.delta;
|
|
852
|
+
rebuildPartial2();
|
|
853
|
+
yield { type: "tool_call_delta", id: tc.id, arguments: event.delta, partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
|
|
854
|
+
}
|
|
855
|
+
break;
|
|
856
|
+
}
|
|
857
|
+
case "response.function_call_arguments.done": {
|
|
858
|
+
const tc = toolCalls.get(event.output_index);
|
|
859
|
+
if (tc) {
|
|
860
|
+
tc.arguments = event.arguments;
|
|
861
|
+
}
|
|
862
|
+
break;
|
|
863
|
+
}
|
|
864
|
+
case "response.output_item.done": {
|
|
865
|
+
if (event.item.type === "function_call") {
|
|
866
|
+
const tc = toolCalls.get(event.output_index);
|
|
867
|
+
if (tc) {
|
|
868
|
+
if (event.item.arguments) tc.arguments = event.item.arguments;
|
|
869
|
+
rebuildPartial2();
|
|
870
|
+
yield { type: "tool_call_end", id: tc.id, name: tc.name, arguments: tc.arguments, partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
break;
|
|
874
|
+
}
|
|
875
|
+
case "response.completed":
|
|
876
|
+
if (event.response.usage) {
|
|
877
|
+
usage = {
|
|
878
|
+
inputTokens: event.response.usage.input_tokens,
|
|
879
|
+
outputTokens: event.response.usage.output_tokens,
|
|
880
|
+
totalTokens: event.response.usage.total_tokens
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
stopReason = "completed";
|
|
884
|
+
break;
|
|
885
|
+
case "response.incomplete":
|
|
886
|
+
if (event.response.usage) {
|
|
887
|
+
usage = {
|
|
888
|
+
inputTokens: event.response.usage.input_tokens,
|
|
889
|
+
outputTokens: event.response.usage.output_tokens,
|
|
890
|
+
totalTokens: event.response.usage.total_tokens
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
stopReason = event.response.incomplete_details?.reason ?? "incomplete";
|
|
894
|
+
break;
|
|
895
|
+
case "error":
|
|
896
|
+
throw new ProviderError({
|
|
897
|
+
code: "stream_error",
|
|
898
|
+
message: event.error.message,
|
|
899
|
+
details: { type: event.error.type }
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
const toolCallResults = [];
|
|
904
|
+
for (const tc of toolCalls.values()) {
|
|
905
|
+
let parsedArgs = tc.arguments;
|
|
906
|
+
try {
|
|
907
|
+
parsedArgs = JSON.parse(tc.arguments);
|
|
908
|
+
} catch {
|
|
909
|
+
}
|
|
910
|
+
toolCallResults.push({ id: tc.id, name: tc.name, arguments: parsedArgs });
|
|
911
|
+
}
|
|
912
|
+
yield {
|
|
913
|
+
type: "done",
|
|
914
|
+
response: {
|
|
915
|
+
content,
|
|
916
|
+
stopReason: mapStopReason(stopReason),
|
|
917
|
+
...toolCallResults.length > 0 ? { toolCalls: toolCallResults } : {},
|
|
918
|
+
...usage ? { usage } : {}
|
|
919
|
+
}
|
|
920
|
+
};
|
|
921
|
+
return;
|
|
922
|
+
} catch (error) {
|
|
923
|
+
const requestError = normalizeRequestError(error, signal);
|
|
924
|
+
if (!shouldRetry({ error: requestError, attempts, maxRetries, signal: options.signal })) {
|
|
925
|
+
yield { type: "error", error: requestError };
|
|
926
|
+
yield { type: "done", response: { content: "", stopReason: "error" } };
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
await waitForRetry(retryDelayMs * 2 ** (attempts - 1), options.signal);
|
|
930
|
+
} finally {
|
|
931
|
+
cleanup();
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
function createOpenAIResponsesStream(config) {
|
|
936
|
+
const normalizedConfig = { ...config, baseUrl: normalizeBaseUrl(config.baseUrl) };
|
|
937
|
+
return async function* openAIResponsesStream(request, options) {
|
|
938
|
+
yield* streamResponses(normalizedConfig, request, options);
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
var streamOpenAIResponses = (model, request, options) => {
|
|
942
|
+
const config = resolveOpenAIResponsesConfig({
|
|
943
|
+
baseUrl: model.baseUrl,
|
|
944
|
+
model: model.id
|
|
945
|
+
});
|
|
946
|
+
return streamResponses(config, request, options);
|
|
947
|
+
};
|
|
948
|
+
|
|
949
|
+
// src/providers/anthropic.ts
|
|
950
|
+
var DEFAULT_MAX_TOKENS = 8192;
|
|
951
|
+
function resolveAnthropicConfig(input = {}) {
|
|
952
|
+
const env = input.env ?? defaultEnv();
|
|
953
|
+
const baseUrl = nonEmpty(input.baseUrl) ?? nonEmpty(env.ROWAN_ANTHROPIC_BASE_URL) ?? "https://api.anthropic.com";
|
|
954
|
+
const apiKey = nonEmpty(input.apiKey) ?? nonEmpty(env.ROWAN_ANTHROPIC_API_KEY) ?? nonEmpty(env.ANTHROPIC_API_KEY);
|
|
955
|
+
const model = nonEmpty(input.model) ?? nonEmpty(env.ROWAN_MODEL);
|
|
956
|
+
return {
|
|
957
|
+
baseUrl: normalizeBaseUrl(baseUrl),
|
|
958
|
+
apiKey: requireValue("API key", apiKey, "set ROWAN_ANTHROPIC_API_KEY or pass --api-key"),
|
|
959
|
+
model: requireValue("model", model, "set ROWAN_MODEL or pass --model"),
|
|
960
|
+
...input.maxTokens !== void 0 ? { maxTokens: input.maxTokens } : {},
|
|
961
|
+
...input.timeoutMs !== void 0 ? { timeoutMs: input.timeoutMs } : {},
|
|
962
|
+
...input.maxRetries !== void 0 ? { maxRetries: input.maxRetries } : {},
|
|
963
|
+
...input.retryDelayMs !== void 0 ? { retryDelayMs: input.retryDelayMs } : {},
|
|
964
|
+
...input.fetch ? { fetch: input.fetch } : {},
|
|
965
|
+
...input.thinking ? { thinking: input.thinking } : {}
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
function normalizeHttpError3(response, body) {
|
|
969
|
+
let providerMessage;
|
|
970
|
+
if (isRecord(body) && isRecord(body.error)) {
|
|
971
|
+
providerMessage = asTrimmedString(body.error.message);
|
|
972
|
+
}
|
|
973
|
+
const statusSummary = response.statusText ? `${response.status} ${response.statusText}` : String(response.status);
|
|
974
|
+
const message = providerMessage ? `Anthropic request failed (${statusSummary}): ${providerMessage}` : `Anthropic request failed with status ${statusSummary}.`;
|
|
975
|
+
return new ProviderError({
|
|
976
|
+
code: "http_error",
|
|
977
|
+
message,
|
|
978
|
+
status: response.status,
|
|
979
|
+
retryable: isRetryableStatus(response.status),
|
|
980
|
+
details: { status: response.status, ...isRecord(body) ? { providerError: body.error } : {} }
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
function convertContentParts(parts) {
|
|
984
|
+
const hasNonText = parts.some((p) => p.type !== "text");
|
|
985
|
+
if (!hasNonText) {
|
|
986
|
+
return parts.filter((p) => p.type === "text").map((p) => p.text).join("\n");
|
|
987
|
+
}
|
|
988
|
+
const blocks = [];
|
|
989
|
+
for (const part of parts) {
|
|
990
|
+
if (part.type === "text") {
|
|
991
|
+
blocks.push({ type: "text", text: part.text });
|
|
992
|
+
} else if (part.type === "image") {
|
|
993
|
+
blocks.push({
|
|
994
|
+
type: "image",
|
|
995
|
+
source: { type: "base64", media_type: part.mimeType, data: part.data }
|
|
996
|
+
});
|
|
997
|
+
} else if (part.type === "tool_use") {
|
|
998
|
+
blocks.push({ type: "tool_use", id: part.id, name: part.name, input: part.input });
|
|
999
|
+
} else if (part.type === "tool_result") {
|
|
1000
|
+
blocks.push({ type: "tool_result", tool_use_id: part.toolUseId, content: part.content, is_error: part.isError });
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
return blocks;
|
|
1004
|
+
}
|
|
1005
|
+
function convertMessages3(messages) {
|
|
1006
|
+
const result = [];
|
|
1007
|
+
for (const msg of messages) {
|
|
1008
|
+
if (msg.role === "user") {
|
|
1009
|
+
if (typeof msg.content === "string") {
|
|
1010
|
+
result.push({ role: "user", content: msg.content });
|
|
1011
|
+
} else {
|
|
1012
|
+
result.push({ role: "user", content: convertContentParts(msg.content) });
|
|
1013
|
+
}
|
|
1014
|
+
} else if (msg.role === "assistant") {
|
|
1015
|
+
if (typeof msg.content === "string") {
|
|
1016
|
+
result.push({ role: "assistant", content: msg.content });
|
|
1017
|
+
} else {
|
|
1018
|
+
const hasToolUse = msg.content.some((p) => p.type === "tool_use");
|
|
1019
|
+
if (hasToolUse) {
|
|
1020
|
+
result.push({ role: "assistant", content: convertContentParts(msg.content) });
|
|
1021
|
+
} else {
|
|
1022
|
+
const texts = msg.content.filter((p) => p.type === "text").map((p) => p.text);
|
|
1023
|
+
result.push({ role: "assistant", content: texts.join("\n") });
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
} else if (msg.role === "tool") {
|
|
1027
|
+
if (typeof msg.content === "string") {
|
|
1028
|
+
result.push({ role: "user", content: msg.content });
|
|
1029
|
+
} else {
|
|
1030
|
+
result.push({ role: "user", content: convertContentParts(msg.content) });
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
return result;
|
|
1035
|
+
}
|
|
1036
|
+
function convertTools3(tools) {
|
|
1037
|
+
return tools.map((tool) => {
|
|
1038
|
+
const schema = tool.parameters ?? {};
|
|
1039
|
+
return {
|
|
1040
|
+
name: tool.name,
|
|
1041
|
+
description: tool.description,
|
|
1042
|
+
input_schema: {
|
|
1043
|
+
type: "object",
|
|
1044
|
+
properties: schema.properties ?? {},
|
|
1045
|
+
required: schema.required ?? []
|
|
1046
|
+
}
|
|
1047
|
+
};
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
function buildRequestBody3(config, request) {
|
|
1051
|
+
const messages = convertMessages3(request.messages);
|
|
1052
|
+
const body = {
|
|
1053
|
+
model: config.model,
|
|
1054
|
+
messages,
|
|
1055
|
+
max_tokens: request.maxTokens ?? config.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
1056
|
+
stream: true
|
|
1057
|
+
};
|
|
1058
|
+
if (request.system) body.system = request.system;
|
|
1059
|
+
if (request.temperature !== void 0) body.temperature = request.temperature;
|
|
1060
|
+
if (request.tools && request.tools.length > 0) body.tools = convertTools3(request.tools);
|
|
1061
|
+
if (config.thinking) {
|
|
1062
|
+
body.thinking = { type: "enabled", budget_tokens: config.thinking.budgetTokens };
|
|
1063
|
+
}
|
|
1064
|
+
return body;
|
|
1065
|
+
}
|
|
1066
|
+
function mapStopReason2(reason) {
|
|
1067
|
+
switch (reason) {
|
|
1068
|
+
case "end_turn":
|
|
1069
|
+
return "end_turn";
|
|
1070
|
+
case "max_tokens":
|
|
1071
|
+
return "max_tokens";
|
|
1072
|
+
case "tool_use":
|
|
1073
|
+
return "tool_use";
|
|
1074
|
+
case "stop_sequence":
|
|
1075
|
+
return "stop";
|
|
1076
|
+
default:
|
|
1077
|
+
return "unknown";
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
var MESSAGE_EVENTS = /* @__PURE__ */ new Set([
|
|
1081
|
+
"message_start",
|
|
1082
|
+
"message_delta",
|
|
1083
|
+
"message_stop",
|
|
1084
|
+
"content_block_start",
|
|
1085
|
+
"content_block_delta",
|
|
1086
|
+
"content_block_stop"
|
|
1087
|
+
]);
|
|
1088
|
+
async function* streamAnthropicMessages(config, request, options = {}) {
|
|
1089
|
+
const maxRetries = normalizeRetryNumber(config.maxRetries, DEFAULT_MAX_RETRIES);
|
|
1090
|
+
const retryDelayMs = normalizeRetryNumber(config.retryDelayMs, DEFAULT_RETRY_DELAY_MS);
|
|
1091
|
+
const body = buildRequestBody3(config, request);
|
|
1092
|
+
const fetchImpl = config.fetch ?? fetch;
|
|
1093
|
+
const endpoint = `${normalizeBaseUrl(config.baseUrl)}/v1/messages`;
|
|
1094
|
+
const requestUsage = summarizeRequestUsage(request);
|
|
1095
|
+
let attempts = 0;
|
|
1096
|
+
while (true) {
|
|
1097
|
+
attempts += 1;
|
|
1098
|
+
const { signal, cleanup } = createRequestSignal({ signal: options.signal, timeoutMs: config.timeoutMs });
|
|
1099
|
+
try {
|
|
1100
|
+
let rebuildPartial2 = function() {
|
|
1101
|
+
partial.contentBlocks = [];
|
|
1102
|
+
if (thinking) {
|
|
1103
|
+
partial.contentBlocks.push({ type: "thinking", thinking });
|
|
1104
|
+
}
|
|
1105
|
+
if (content) {
|
|
1106
|
+
partial.contentBlocks.push({ type: "text", text: content });
|
|
1107
|
+
}
|
|
1108
|
+
for (const tc of toolCalls.values()) {
|
|
1109
|
+
partial.contentBlocks.push({
|
|
1110
|
+
type: "tool_call",
|
|
1111
|
+
id: tc.id,
|
|
1112
|
+
name: tc.name,
|
|
1113
|
+
args: tc.arguments
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
};
|
|
1117
|
+
var rebuildPartial = rebuildPartial2;
|
|
1118
|
+
const response = await fetchImpl(endpoint, {
|
|
1119
|
+
method: "POST",
|
|
1120
|
+
headers: {
|
|
1121
|
+
"content-type": "application/json",
|
|
1122
|
+
"x-api-key": config.apiKey,
|
|
1123
|
+
"anthropic-version": "2023-06-01"
|
|
1124
|
+
},
|
|
1125
|
+
body: JSON.stringify(body),
|
|
1126
|
+
signal
|
|
1127
|
+
});
|
|
1128
|
+
if (!response.ok) throw normalizeHttpError3(response, await readErrorBody(response));
|
|
1129
|
+
if (!response.body) {
|
|
1130
|
+
throw new ProviderError({ code: "no_body", message: "Response body is null.", retryable: true });
|
|
1131
|
+
}
|
|
1132
|
+
yield { type: "model_requested", model: request.model, usage: { ...requestUsage } };
|
|
1133
|
+
let content = "";
|
|
1134
|
+
let thinking = "";
|
|
1135
|
+
let stopReason = null;
|
|
1136
|
+
let inputTokens = 0;
|
|
1137
|
+
let outputTokens = 0;
|
|
1138
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
1139
|
+
const blockTypes = /* @__PURE__ */ new Map();
|
|
1140
|
+
const partial = {
|
|
1141
|
+
role: "assistant",
|
|
1142
|
+
contentBlocks: []
|
|
1143
|
+
};
|
|
1144
|
+
for await (const sse of iterateSseMessages(response.body, signal)) {
|
|
1145
|
+
if (!sse.event || !MESSAGE_EVENTS.has(sse.event)) continue;
|
|
1146
|
+
let event;
|
|
1147
|
+
try {
|
|
1148
|
+
event = JSON.parse(sse.data);
|
|
1149
|
+
} catch {
|
|
1150
|
+
continue;
|
|
1151
|
+
}
|
|
1152
|
+
switch (event.type) {
|
|
1153
|
+
case "message_start":
|
|
1154
|
+
inputTokens = event.message.usage.input_tokens;
|
|
1155
|
+
outputTokens = event.message.usage.output_tokens;
|
|
1156
|
+
break;
|
|
1157
|
+
case "content_block_start":
|
|
1158
|
+
blockTypes.set(event.index, event.content_block.type);
|
|
1159
|
+
if (event.content_block.type === "tool_use") {
|
|
1160
|
+
const tc = { id: event.content_block.id ?? "", name: event.content_block.name ?? "", arguments: "" };
|
|
1161
|
+
toolCalls.set(event.index, tc);
|
|
1162
|
+
rebuildPartial2();
|
|
1163
|
+
yield { type: "tool_call_start", id: tc.id, name: tc.name, partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
|
|
1164
|
+
}
|
|
1165
|
+
break;
|
|
1166
|
+
case "content_block_delta":
|
|
1167
|
+
if (event.delta.type === "text_delta") {
|
|
1168
|
+
content += event.delta.text;
|
|
1169
|
+
rebuildPartial2();
|
|
1170
|
+
yield { type: "text_delta", text: event.delta.text, partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
|
|
1171
|
+
} else if (event.delta.type === "thinking_delta") {
|
|
1172
|
+
thinking += event.delta.thinking;
|
|
1173
|
+
rebuildPartial2();
|
|
1174
|
+
yield { type: "thinking_delta", thinking: event.delta.thinking, partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
|
|
1175
|
+
} else if (event.delta.type === "input_json_delta") {
|
|
1176
|
+
const tc = toolCalls.get(event.index);
|
|
1177
|
+
if (tc) {
|
|
1178
|
+
tc.arguments += event.delta.partial_json;
|
|
1179
|
+
rebuildPartial2();
|
|
1180
|
+
yield { type: "tool_call_delta", id: tc.id, arguments: event.delta.partial_json, partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
break;
|
|
1184
|
+
case "content_block_stop": {
|
|
1185
|
+
if (blockTypes.get(event.index) === "tool_use") {
|
|
1186
|
+
const tc = toolCalls.get(event.index);
|
|
1187
|
+
if (tc) {
|
|
1188
|
+
rebuildPartial2();
|
|
1189
|
+
yield { type: "tool_call_end", id: tc.id, name: tc.name, arguments: tc.arguments, partial: { ...partial, contentBlocks: [...partial.contentBlocks] } };
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
break;
|
|
1193
|
+
}
|
|
1194
|
+
case "message_delta":
|
|
1195
|
+
if (event.delta.stop_reason) stopReason = event.delta.stop_reason;
|
|
1196
|
+
outputTokens = event.usage.output_tokens;
|
|
1197
|
+
break;
|
|
1198
|
+
case "message_stop":
|
|
1199
|
+
break;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
const toolCallResults = [];
|
|
1203
|
+
for (const tc of toolCalls.values()) {
|
|
1204
|
+
let parsedArgs = tc.arguments;
|
|
1205
|
+
try {
|
|
1206
|
+
parsedArgs = JSON.parse(tc.arguments);
|
|
1207
|
+
} catch {
|
|
1208
|
+
}
|
|
1209
|
+
toolCallResults.push({ id: tc.id, name: tc.name, arguments: parsedArgs });
|
|
1210
|
+
}
|
|
1211
|
+
const usage = { inputTokens, outputTokens, totalTokens: inputTokens + outputTokens };
|
|
1212
|
+
yield {
|
|
1213
|
+
type: "done",
|
|
1214
|
+
response: {
|
|
1215
|
+
content,
|
|
1216
|
+
...thinking ? { thinking } : {},
|
|
1217
|
+
stopReason: mapStopReason2(stopReason ?? "end_turn"),
|
|
1218
|
+
...toolCallResults.length > 0 ? { toolCalls: toolCallResults } : {},
|
|
1219
|
+
usage
|
|
1220
|
+
}
|
|
1221
|
+
};
|
|
1222
|
+
return;
|
|
1223
|
+
} catch (error) {
|
|
1224
|
+
const requestError = normalizeRequestError(error, signal);
|
|
1225
|
+
if (!shouldRetry({ error: requestError, attempts, maxRetries, signal: options.signal })) {
|
|
1226
|
+
yield { type: "error", error: requestError };
|
|
1227
|
+
yield { type: "done", response: { content: "", stopReason: "error" } };
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
await waitForRetry(retryDelayMs * 2 ** (attempts - 1), options.signal);
|
|
1231
|
+
} finally {
|
|
1232
|
+
cleanup();
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
function createAnthropicStream(config) {
|
|
1237
|
+
const normalizedConfig = { ...config, baseUrl: normalizeBaseUrl(config.baseUrl) };
|
|
1238
|
+
return async function* anthropicStream(request, options) {
|
|
1239
|
+
yield* streamAnthropicMessages(normalizedConfig, request, options);
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
var streamAnthropic = (model, request, options) => {
|
|
1243
|
+
const config = resolveAnthropicConfig({
|
|
1244
|
+
baseUrl: model.baseUrl,
|
|
1245
|
+
model: model.id
|
|
1246
|
+
});
|
|
1247
|
+
return streamAnthropicMessages(config, request, options);
|
|
1248
|
+
};
|
|
1249
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1250
|
+
0 && (module.exports = {
|
|
1251
|
+
ProviderError,
|
|
1252
|
+
callOpenAICompletions,
|
|
1253
|
+
createAnthropicStream,
|
|
1254
|
+
createOpenAICompletionsStream,
|
|
1255
|
+
createOpenAIResponsesStream,
|
|
1256
|
+
resolveAnthropicConfig,
|
|
1257
|
+
resolveOpenAICompletionsConfig,
|
|
1258
|
+
resolveOpenAIResponsesConfig,
|
|
1259
|
+
streamAnthropic,
|
|
1260
|
+
streamOpenAICompletions,
|
|
1261
|
+
streamOpenAIResponses
|
|
1262
|
+
});
|