@revojs/ai 0.1.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,331 @@
1
+ import { mergeHeaders, readDataFrames, toStream } from "../index.js";
2
+ import { toJsonSchema } from "revojs/shared";
3
+ //#region src/protocols/google-models.ts
4
+ function toBase64(data) {
5
+ let binary = "";
6
+ for (let index = 0; index < data.length; index += 32768) binary += String.fromCharCode(...data.subarray(index, index + 32768));
7
+ return btoa(binary);
8
+ }
9
+ function toUsage(usage) {
10
+ const prompt = usage?.promptTokenCount;
11
+ const candidates = usage?.candidatesTokenCount;
12
+ const cached = usage?.cachedContentTokenCount;
13
+ const thoughts = usage?.thoughtsTokenCount;
14
+ return {
15
+ inputTokens: {
16
+ total: prompt,
17
+ noCache: prompt !== void 0 && cached !== void 0 ? Math.max(0, prompt - cached) : prompt,
18
+ cacheRead: cached,
19
+ cacheWrite: void 0
20
+ },
21
+ outputTokens: {
22
+ total: candidates,
23
+ text: candidates !== void 0 && thoughts !== void 0 ? Math.max(0, candidates - thoughts) : candidates,
24
+ reasoning: thoughts
25
+ },
26
+ raw: usage
27
+ };
28
+ }
29
+ function toFinishReason(raw, toolCalls) {
30
+ return {
31
+ unified: toolCalls ? "tool-calls" : raw === "MAX_TOKENS" ? "length" : raw === "SAFETY" || raw === "BLOCKLIST" || raw === "PROHIBITED_CONTENT" || raw === "SPII" ? "content-filter" : raw === void 0 || raw === "STOP" || raw === "FINISH_REASON_UNSPECIFIED" ? "stop" : "other",
32
+ raw
33
+ };
34
+ }
35
+ function toResponseValue(output) {
36
+ if (output.type === "text" || output.type === "error-text") return { output: output.value };
37
+ const value = "value" in output ? output.value : void 0;
38
+ return typeof value === "object" && value !== null ? value : { output: JSON.stringify(value ?? null) };
39
+ }
40
+ function toBody(options) {
41
+ let system = "";
42
+ const warnings = [];
43
+ const contents = [];
44
+ for (const message of options.prompt) {
45
+ if (message.role === "system") {
46
+ system += (system.length > 0 ? "\n" : "") + message.content;
47
+ continue;
48
+ }
49
+ if (message.role === "user") {
50
+ const parts = message.content.flatMap((part) => {
51
+ if (part.type === "text") return [{ text: part.text }];
52
+ if (part.type === "file" && part.data.type === "url") return [{ fileData: {
53
+ fileUri: part.data.url.toString(),
54
+ mimeType: part.mediaType
55
+ } }];
56
+ if (part.type === "file" && part.data.type === "data" && typeof part.data.data === "string") return [{ inlineData: {
57
+ mimeType: part.mediaType,
58
+ data: part.data.data
59
+ } }];
60
+ if (part.type === "file" && part.data.type === "data" && typeof part.data.data !== "string") return [{ inlineData: {
61
+ mimeType: part.mediaType,
62
+ data: toBase64(part.data.data)
63
+ } }];
64
+ warnings.push({
65
+ type: "unsupported",
66
+ feature: `user ${part.type === "file" ? `file (${part.mediaType})` : "custom"} message part`
67
+ });
68
+ return [];
69
+ });
70
+ if (parts.length > 0) contents.push({
71
+ role: "user",
72
+ parts
73
+ });
74
+ continue;
75
+ }
76
+ if (message.role === "tool") {
77
+ const parts = message.content.flatMap((part) => {
78
+ if (part.type !== "tool-result") return [];
79
+ return [{ functionResponse: {
80
+ name: part.toolName,
81
+ response: toResponseValue(part.output)
82
+ } }];
83
+ });
84
+ if (parts.length > 0) contents.push({
85
+ role: "user",
86
+ parts
87
+ });
88
+ continue;
89
+ }
90
+ const parts = message.content.flatMap((part) => {
91
+ if (part.type === "text" && part.text.length > 0) return [{ text: part.text }];
92
+ if (part.type === "tool-call") {
93
+ let args = {};
94
+ try {
95
+ args = JSON.parse(typeof part.input === "string" ? part.input : JSON.stringify(part.input ?? {}));
96
+ } catch {
97
+ args = {};
98
+ }
99
+ return [{ functionCall: {
100
+ name: part.toolName,
101
+ args
102
+ } }];
103
+ }
104
+ if (part.type === "tool-result") return [{ functionResponse: {
105
+ name: part.toolName,
106
+ response: toResponseValue(part.output)
107
+ } }];
108
+ if (part.type === "reasoning") return [{
109
+ text: part.text,
110
+ thought: true
111
+ }];
112
+ warnings.push({
113
+ type: "compatibility",
114
+ feature: `assistant ${part.type} message part`
115
+ });
116
+ return [];
117
+ });
118
+ if (parts.length > 0) contents.push({
119
+ role: "model",
120
+ parts
121
+ });
122
+ }
123
+ if (options.reasoning === "none") warnings.push({
124
+ type: "unsupported",
125
+ feature: "disabling reasoning"
126
+ });
127
+ const functionDeclarations = options.tools?.flatMap((tool) => {
128
+ if (tool.type !== "function") {
129
+ warnings.push({
130
+ type: "unsupported",
131
+ feature: `provider tool ${tool.id}`
132
+ });
133
+ return [];
134
+ }
135
+ return [{
136
+ name: tool.name,
137
+ description: tool.description,
138
+ parameters: toJsonSchema(tool.inputSchema)
139
+ }];
140
+ });
141
+ return [{
142
+ ...system.length > 0 ? { systemInstruction: { parts: [{ text: system }] } } : {},
143
+ contents,
144
+ generationConfig: {
145
+ ...options.maxOutputTokens !== void 0 ? { maxOutputTokens: options.maxOutputTokens } : {},
146
+ ...options.temperature !== void 0 ? { temperature: options.temperature } : {},
147
+ ...options.topP !== void 0 ? { topP: options.topP } : {},
148
+ ...options.topK !== void 0 ? { topK: options.topK } : {},
149
+ ...options.stopSequences !== void 0 ? { stopSequences: options.stopSequences } : {},
150
+ ...options.seed !== void 0 ? { seed: options.seed } : {},
151
+ ...options.responseFormat !== void 0 && options.responseFormat.type === "json" ? {
152
+ responseMimeType: "application/json",
153
+ ...options.responseFormat.schema !== void 0 ? { responseSchema: toJsonSchema(options.responseFormat.schema) } : {}
154
+ } : {},
155
+ ...options.reasoning === "none" ? { thinkingConfig: { thinkingBudget: 0 } } : {}
156
+ },
157
+ ...functionDeclarations !== void 0 && functionDeclarations.length > 0 ? { tools: [{ functionDeclarations }] } : {},
158
+ ...options.toolChoice !== void 0 ? { toolConfig: { functionCallingConfig: {
159
+ mode: options.toolChoice.type === "tool" ? "ANY" : options.toolChoice.type.toUpperCase(),
160
+ ...options.toolChoice.type === "tool" ? { allowedFunctionNames: [options.toolChoice.toolName] } : {}
161
+ } } } : {},
162
+ ...options.providerOptions?.google
163
+ }, warnings];
164
+ }
165
+ function url(config, model, stream) {
166
+ const base = `${config.baseURL.replace(/\/+$/, "")}/models/${model}`;
167
+ return stream ? `${base}:streamGenerateContent?alt=sse` : `${base}:generateContent`;
168
+ }
169
+ function headers(config, options) {
170
+ return {
171
+ "Content-Type": "application/json",
172
+ ...mergeHeaders(config.apiKey === void 0 ? {} : { "x-goog-api-key": config.apiKey }, options.headers)
173
+ };
174
+ }
175
+ async function* readParts(response, warnings) {
176
+ yield {
177
+ type: "stream-start",
178
+ warnings
179
+ };
180
+ let metadataSent = false;
181
+ let textStarted = false;
182
+ let reasoningStarted = false;
183
+ let toolCount = 0;
184
+ let toolCalls = false;
185
+ let finishRaw;
186
+ let usage;
187
+ for await (const data of readDataFrames(response)) {
188
+ let chunk;
189
+ try {
190
+ chunk = JSON.parse(data);
191
+ } catch {
192
+ continue;
193
+ }
194
+ if (chunk.error !== void 0) throw new Error(chunk.error.message ?? "Unknown AI provider error");
195
+ if (!metadataSent && (chunk.responseId !== void 0 || chunk.modelVersion !== void 0)) {
196
+ metadataSent = true;
197
+ yield {
198
+ type: "response-metadata",
199
+ id: chunk.responseId,
200
+ modelId: chunk.modelVersion
201
+ };
202
+ }
203
+ if (chunk.usageMetadata !== void 0) usage = toUsage(chunk.usageMetadata);
204
+ const candidate = chunk.candidates?.at(0);
205
+ if (candidate?.finishReason !== void 0) finishRaw = candidate.finishReason;
206
+ for (const part of candidate?.content?.parts ?? []) {
207
+ if (part.thought === true && typeof part.text === "string" && part.text.length > 0) {
208
+ if (!reasoningStarted) {
209
+ reasoningStarted = true;
210
+ yield {
211
+ type: "reasoning-start",
212
+ id: "gemini-reasoning-0"
213
+ };
214
+ }
215
+ yield {
216
+ type: "reasoning-delta",
217
+ id: "gemini-reasoning-0",
218
+ delta: part.text
219
+ };
220
+ continue;
221
+ }
222
+ if (part.functionCall !== void 0) {
223
+ toolCalls = true;
224
+ yield {
225
+ type: "tool-call",
226
+ toolCallId: `gemini-tool-${toolCount++}`,
227
+ toolName: part.functionCall.name ?? "",
228
+ input: JSON.stringify(part.functionCall.args ?? {})
229
+ };
230
+ continue;
231
+ }
232
+ if (typeof part.text === "string" && part.text.length > 0) {
233
+ if (!textStarted) {
234
+ textStarted = true;
235
+ yield {
236
+ type: "text-start",
237
+ id: "gemini-text-0"
238
+ };
239
+ }
240
+ yield {
241
+ type: "text-delta",
242
+ id: "gemini-text-0",
243
+ delta: part.text
244
+ };
245
+ }
246
+ }
247
+ }
248
+ if (reasoningStarted) yield {
249
+ type: "reasoning-end",
250
+ id: "gemini-reasoning-0"
251
+ };
252
+ if (textStarted) yield {
253
+ type: "text-end",
254
+ id: "gemini-text-0"
255
+ };
256
+ yield {
257
+ type: "finish",
258
+ usage: usage ?? toUsage(void 0),
259
+ finishReason: toFinishReason(finishRaw, toolCalls)
260
+ };
261
+ }
262
+ function geminiGenerateProtocol(config) {
263
+ return async (options) => {
264
+ const [body, warnings] = toBody(options);
265
+ const response = await fetch(url(config, options.model, false), {
266
+ method: "POST",
267
+ headers: headers(config, options),
268
+ body: JSON.stringify(body),
269
+ signal: options.abortSignal
270
+ });
271
+ if (!response.ok) throw new Error(`AI provider responded with ${response.status}: ${(await response.text()).slice(0, 500)}`);
272
+ const data = await response.json();
273
+ const content = [];
274
+ let toolCount = 0;
275
+ let toolCalls = false;
276
+ for (const part of data.candidates?.at(0)?.content?.parts ?? []) {
277
+ if (part.thought === true && typeof part.text === "string" && part.text.length > 0) {
278
+ content.push({
279
+ type: "reasoning",
280
+ text: part.text
281
+ });
282
+ continue;
283
+ }
284
+ if (part.functionCall !== void 0) {
285
+ toolCalls = true;
286
+ content.push({
287
+ type: "tool-call",
288
+ toolCallId: `gemini-tool-${toolCount++}`,
289
+ toolName: part.functionCall.name ?? "",
290
+ input: JSON.stringify(part.functionCall.args ?? {})
291
+ });
292
+ continue;
293
+ }
294
+ if (typeof part.text === "string" && part.text.length > 0) content.push({
295
+ type: "text",
296
+ text: part.text
297
+ });
298
+ }
299
+ return {
300
+ content,
301
+ finishReason: toFinishReason(data.candidates?.at(0)?.finishReason, toolCalls),
302
+ usage: toUsage(data.usageMetadata),
303
+ request: { body },
304
+ response: {
305
+ id: data.responseId,
306
+ modelId: data.modelVersion,
307
+ headers: Object.fromEntries(response.headers.entries())
308
+ },
309
+ warnings
310
+ };
311
+ };
312
+ }
313
+ function geminiStreamProtocol(config) {
314
+ return async (options) => {
315
+ const [body, warnings] = toBody(options);
316
+ const response = await fetch(url(config, options.model, true), {
317
+ method: "POST",
318
+ headers: headers(config, options),
319
+ body: JSON.stringify(body),
320
+ signal: options.abortSignal
321
+ });
322
+ if (!response.ok) throw new Error(`AI provider responded with ${response.status}: ${(await response.text()).slice(0, 500)}`);
323
+ return {
324
+ request: { body },
325
+ response: { headers: Object.fromEntries(response.headers.entries()) },
326
+ stream: toStream(() => readParts(response, warnings))
327
+ };
328
+ };
329
+ }
330
+ //#endregion
331
+ export { geminiGenerateProtocol, geminiStreamProtocol };
@@ -0,0 +1,7 @@
1
+ import { type LanguageModelCallOptions, type LanguageModelGenerateResult, type LanguageModelStreamResult, type Protocol } from "../index";
2
+ export interface ChatCompletionConfig {
3
+ baseURL: string;
4
+ apiKey: string | undefined;
5
+ }
6
+ export declare function chatCompletionsGenerateProtocol(config: ChatCompletionConfig): Protocol<LanguageModelCallOptions, LanguageModelGenerateResult>;
7
+ export declare function chatCompletionsStreamProtocol(config: ChatCompletionConfig): Protocol<LanguageModelCallOptions, LanguageModelStreamResult>;
@@ -0,0 +1,334 @@
1
+ import { mergeHeaders, readDataFrames, toStream } from "../index.js";
2
+ import { toJsonSchema } from "revojs/shared";
3
+ //#region src/protocols/openai-chat-completions.ts
4
+ function toMessages(prompt) {
5
+ const wire = [];
6
+ const warnings = [];
7
+ for (const message of prompt) {
8
+ if (message.role === "system") {
9
+ wire.push({
10
+ role: "system",
11
+ content: message.content
12
+ });
13
+ continue;
14
+ }
15
+ if (message.role === "user") {
16
+ wire.push({
17
+ role: "user",
18
+ content: message.content.flatMap((part) => {
19
+ if (part.type === "file" && part.data.type === "url") return [{
20
+ type: "image_url",
21
+ image_url: { url: part.data.url.toString() }
22
+ }];
23
+ if (part.type === "file" && part.data.type === "data" && typeof part.data.data === "string") return [{
24
+ type: "image_url",
25
+ image_url: { url: `data:${part.mediaType};base64,${part.data.data}` }
26
+ }];
27
+ if (part.type === "text") return [{
28
+ type: "text",
29
+ text: part.text
30
+ }];
31
+ warnings.push({
32
+ type: "unsupported",
33
+ feature: `user ${part.type === "file" ? `file (${part.mediaType})` : "custom"} message part`
34
+ });
35
+ return [];
36
+ })
37
+ });
38
+ continue;
39
+ }
40
+ if (message.role === "assistant") {
41
+ const text = message.content.filter((part) => part.type === "text").map((part) => part.text).join("");
42
+ const calls = message.content.filter((part) => part.type === "tool-call");
43
+ wire.push({
44
+ role: "assistant",
45
+ content: text.length > 0 ? text : null,
46
+ ...calls.length > 0 ? { tool_calls: calls.map((call) => ({
47
+ id: call.toolCallId,
48
+ type: "function",
49
+ function: {
50
+ name: call.toolName,
51
+ arguments: call.input
52
+ }
53
+ })) } : {}
54
+ });
55
+ continue;
56
+ }
57
+ for (const part of message.content) {
58
+ if (part.type !== "tool-result") continue;
59
+ const value = "value" in part.output ? part.output.value : void 0;
60
+ wire.push({
61
+ role: "tool",
62
+ tool_call_id: part.toolCallId,
63
+ content: part.output.type === "text" || part.output.type === "error-text" ? part.output.value : JSON.stringify(value ?? null)
64
+ });
65
+ }
66
+ }
67
+ return [wire, warnings];
68
+ }
69
+ function toBody(config, options, stream) {
70
+ const [messages, warnings] = toMessages(options.prompt);
71
+ return [
72
+ {
73
+ model: options.model,
74
+ messages,
75
+ ...stream ? {
76
+ stream: true,
77
+ stream_options: { include_usage: true }
78
+ } : {},
79
+ ...options.maxOutputTokens !== void 0 ? { max_completion_tokens: options.maxOutputTokens } : {},
80
+ ...options.temperature !== void 0 ? { temperature: options.temperature } : {},
81
+ ...options.topP !== void 0 ? { top_p: options.topP } : {},
82
+ ...options.stopSequences !== void 0 ? { stop: options.stopSequences } : {},
83
+ ...options.seed !== void 0 ? { seed: options.seed } : {},
84
+ ...options.presencePenalty !== void 0 ? { presence_penalty: options.presencePenalty } : {},
85
+ ...options.frequencyPenalty !== void 0 ? { frequency_penalty: options.frequencyPenalty } : {},
86
+ ...options.tools !== void 0 && options.tools.length > 0 ? { tools: options.tools.map((tool) => tool.type === "provider" ? {
87
+ type: tool.id,
88
+ [tool.name]: tool.args
89
+ } : {
90
+ type: "function",
91
+ function: {
92
+ name: tool.name,
93
+ description: tool.description,
94
+ parameters: toJsonSchema(tool.inputSchema),
95
+ strict: tool.strict
96
+ }
97
+ }) } : {},
98
+ ...options.toolChoice !== void 0 ? { tool_choice: options.toolChoice.type === "tool" ? {
99
+ type: "function",
100
+ function: { name: options.toolChoice.toolName }
101
+ } : options.toolChoice.type } : {},
102
+ ...options.responseFormat !== void 0 ? { response_format: options.responseFormat.type === "json" ? options.responseFormat.schema === void 0 ? { type: "json_object" } : {
103
+ type: "json_schema",
104
+ json_schema: {
105
+ name: options.responseFormat.name ?? "response",
106
+ description: options.responseFormat.description,
107
+ schema: toJsonSchema(options.responseFormat.schema)
108
+ }
109
+ } : { type: "text" } } : {},
110
+ ...options.reasoning !== void 0 && options.reasoning !== "provider-default" ? { reasoning_effort: options.reasoning } : {},
111
+ ...options.providerOptions?.openai
112
+ },
113
+ warnings,
114
+ `${config.baseURL.replace(/\/+$/, "")}/chat/completions`
115
+ ];
116
+ }
117
+ function toUsage(chunk) {
118
+ return {
119
+ inputTokens: {
120
+ total: chunk?.prompt_tokens,
121
+ noCache: chunk?.prompt_tokens,
122
+ cacheRead: void 0,
123
+ cacheWrite: void 0
124
+ },
125
+ outputTokens: {
126
+ total: chunk?.completion_tokens,
127
+ text: chunk?.completion_tokens_details?.reasoning_tokens === void 0 ? chunk?.completion_tokens : void 0,
128
+ reasoning: chunk?.completion_tokens_details?.reasoning_tokens
129
+ },
130
+ raw: chunk
131
+ };
132
+ }
133
+ function toFinishReason(raw) {
134
+ if (raw === "length" || raw === "max_tokens") return {
135
+ unified: "length",
136
+ raw
137
+ };
138
+ if (raw === "tool_calls") return {
139
+ unified: "tool-calls",
140
+ raw
141
+ };
142
+ if (raw === "content_filter") return {
143
+ unified: "content-filter",
144
+ raw
145
+ };
146
+ return {
147
+ unified: "stop",
148
+ raw: raw ?? void 0
149
+ };
150
+ }
151
+ async function* readParts(response, warnings) {
152
+ yield {
153
+ type: "stream-start",
154
+ warnings
155
+ };
156
+ const tools = /* @__PURE__ */ new Map();
157
+ let metadataSent = false;
158
+ let textStarted = false;
159
+ let reasoningStarted = false;
160
+ let finishRaw;
161
+ let usage;
162
+ for await (const data of readDataFrames(response)) {
163
+ if (data === "[DONE]") break;
164
+ let chunk;
165
+ try {
166
+ chunk = JSON.parse(data);
167
+ } catch {
168
+ continue;
169
+ }
170
+ if (!metadataSent) {
171
+ metadataSent = true;
172
+ yield {
173
+ type: "response-metadata",
174
+ id: chunk.id,
175
+ modelId: chunk.model
176
+ };
177
+ }
178
+ if (chunk.usage !== void 0) usage = toUsage(chunk.usage);
179
+ const choice = chunk.choices?.at(0);
180
+ if (typeof choice?.finish_reason === "string") finishRaw = choice.finish_reason;
181
+ const delta = choice?.delta;
182
+ if (delta === void 0) continue;
183
+ const thinking = typeof delta.reasoning_content === "string" ? delta.reasoning_content : typeof delta.reasoning === "string" ? delta.reasoning : "";
184
+ if (thinking.length > 0) {
185
+ if (!reasoningStarted) {
186
+ reasoningStarted = true;
187
+ yield {
188
+ type: "reasoning-start",
189
+ id: "openai-reasoning-0"
190
+ };
191
+ }
192
+ yield {
193
+ type: "reasoning-delta",
194
+ id: "openai-reasoning-0",
195
+ delta: thinking
196
+ };
197
+ }
198
+ if (typeof delta.content === "string" && delta.content.length > 0) {
199
+ if (!textStarted) {
200
+ textStarted = true;
201
+ yield {
202
+ type: "text-start",
203
+ id: "openai-text-0"
204
+ };
205
+ }
206
+ yield {
207
+ type: "text-delta",
208
+ id: "openai-text-0",
209
+ delta: delta.content
210
+ };
211
+ }
212
+ for (const partial of delta.tool_calls ?? []) {
213
+ const index = partial.index ?? 0;
214
+ let accumulator = tools.get(index);
215
+ if (accumulator === void 0) {
216
+ accumulator = {
217
+ streamId: `openai-tool-${index}`,
218
+ callId: partial.id ?? "",
219
+ name: partial.function?.name ?? "",
220
+ arguments: ""
221
+ };
222
+ tools.set(index, accumulator);
223
+ yield {
224
+ type: "tool-input-start",
225
+ id: accumulator.streamId,
226
+ toolName: accumulator.name
227
+ };
228
+ }
229
+ if (partial.id !== void 0 && partial.id !== accumulator.callId) accumulator.callId = partial.id;
230
+ if (partial.function?.name !== void 0) accumulator.name += partial.function.name;
231
+ if (partial.function?.arguments !== void 0) {
232
+ accumulator.arguments += partial.function.arguments;
233
+ yield {
234
+ type: "tool-input-delta",
235
+ id: accumulator.streamId,
236
+ delta: partial.function.arguments
237
+ };
238
+ }
239
+ }
240
+ }
241
+ if (reasoningStarted) yield {
242
+ type: "reasoning-end",
243
+ id: "openai-reasoning-0"
244
+ };
245
+ if (textStarted) yield {
246
+ type: "text-end",
247
+ id: "openai-text-0"
248
+ };
249
+ for (const [, accumulator] of [...tools.entries()].sort(([left], [right]) => left - right)) {
250
+ yield {
251
+ type: "tool-input-end",
252
+ id: accumulator.streamId
253
+ };
254
+ if (accumulator.callId.length > 0 || accumulator.name.length > 0) yield {
255
+ type: "tool-call",
256
+ toolCallId: accumulator.callId.length > 0 ? accumulator.callId : accumulator.streamId,
257
+ toolName: accumulator.name,
258
+ input: accumulator.arguments
259
+ };
260
+ }
261
+ yield {
262
+ type: "finish",
263
+ usage: usage ?? toUsage(void 0),
264
+ finishReason: toFinishReason(finishRaw)
265
+ };
266
+ }
267
+ function chatCompletionsGenerateProtocol(config) {
268
+ return async (options) => {
269
+ const [body, warnings, url] = toBody(config, options, false);
270
+ const response = await fetch(url, {
271
+ method: "POST",
272
+ headers: {
273
+ "Content-Type": "application/json",
274
+ ...mergeHeaders(config.apiKey === void 0 ? {} : { Authorization: `Bearer ${config.apiKey}` }, options.headers)
275
+ },
276
+ body: JSON.stringify(body),
277
+ signal: options.abortSignal
278
+ });
279
+ if (!response.ok) throw new Error(`AI provider responded with ${response.status}: ${(await response.text()).slice(0, 500)}`);
280
+ const chunk = await response.json();
281
+ const choice = chunk.choices?.at(0);
282
+ if (choice?.message === void 0) throw new Error("AI provider responded without choices");
283
+ const reasoning = typeof choice.message.reasoning_content === "string" ? choice.message.reasoning_content : typeof choice.message.reasoning === "string" ? choice.message.reasoning : "";
284
+ const content = [];
285
+ if (reasoning.length > 0) content.push({
286
+ type: "reasoning",
287
+ text: reasoning
288
+ });
289
+ if (typeof choice.message.content === "string" && choice.message.content.length > 0) content.push({
290
+ type: "text",
291
+ text: choice.message.content
292
+ });
293
+ for (const call of choice.message.tool_calls ?? []) content.push({
294
+ type: "tool-call",
295
+ toolCallId: call.id ?? "",
296
+ toolName: call.function?.name ?? "",
297
+ input: call.function?.arguments ?? ""
298
+ });
299
+ return {
300
+ content,
301
+ finishReason: toFinishReason(choice.finish_reason),
302
+ usage: toUsage(chunk.usage),
303
+ request: { body },
304
+ response: {
305
+ id: chunk.id,
306
+ modelId: chunk.model,
307
+ headers: Object.fromEntries(response.headers.entries())
308
+ },
309
+ warnings
310
+ };
311
+ };
312
+ }
313
+ function chatCompletionsStreamProtocol(config) {
314
+ return async (options) => {
315
+ const [body, warnings, url] = toBody(config, options, true);
316
+ const response = await fetch(url, {
317
+ method: "POST",
318
+ headers: {
319
+ "Content-Type": "application/json",
320
+ ...mergeHeaders(config.apiKey === void 0 ? {} : { Authorization: `Bearer ${config.apiKey}` }, options.headers)
321
+ },
322
+ body: JSON.stringify(body),
323
+ signal: options.abortSignal
324
+ });
325
+ if (!response.ok) throw new Error(`AI provider responded with ${response.status}: ${(await response.text()).slice(0, 500)}`);
326
+ return {
327
+ request: { body },
328
+ response: { headers: Object.fromEntries(response.headers.entries()) },
329
+ stream: toStream(() => readParts(response, warnings))
330
+ };
331
+ };
332
+ }
333
+ //#endregion
334
+ export { chatCompletionsGenerateProtocol, chatCompletionsStreamProtocol };
@@ -0,0 +1,7 @@
1
+ import { type LanguageModelCallOptions, type LanguageModelGenerateResult, type LanguageModelStreamResult, type Protocol } from "../index";
2
+ export interface ResponsesConfig {
3
+ baseURL: string;
4
+ apiKey: string | undefined;
5
+ }
6
+ export declare function responsesGenerateProtocol(config: ResponsesConfig): Protocol<LanguageModelCallOptions, LanguageModelGenerateResult>;
7
+ export declare function responsesStreamProtocol(config: ResponsesConfig): Protocol<LanguageModelCallOptions, LanguageModelStreamResult>;