@providerprotocol/ai 0.0.10 → 0.0.12

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.
Files changed (64) hide show
  1. package/dist/index.d.ts +7 -1
  2. package/dist/index.js +37 -9
  3. package/dist/index.js.map +1 -1
  4. package/package.json +1 -10
  5. package/src/anthropic/index.ts +0 -3
  6. package/src/core/image.ts +0 -188
  7. package/src/core/llm.ts +0 -624
  8. package/src/core/provider.ts +0 -92
  9. package/src/google/index.ts +0 -3
  10. package/src/http/errors.ts +0 -112
  11. package/src/http/fetch.ts +0 -210
  12. package/src/http/index.ts +0 -31
  13. package/src/http/keys.ts +0 -136
  14. package/src/http/retry.ts +0 -205
  15. package/src/http/sse.ts +0 -136
  16. package/src/index.ts +0 -32
  17. package/src/ollama/index.ts +0 -3
  18. package/src/openai/index.ts +0 -39
  19. package/src/openrouter/index.ts +0 -11
  20. package/src/providers/anthropic/index.ts +0 -17
  21. package/src/providers/anthropic/llm.ts +0 -196
  22. package/src/providers/anthropic/transform.ts +0 -434
  23. package/src/providers/anthropic/types.ts +0 -213
  24. package/src/providers/google/index.ts +0 -17
  25. package/src/providers/google/llm.ts +0 -203
  26. package/src/providers/google/transform.ts +0 -447
  27. package/src/providers/google/types.ts +0 -214
  28. package/src/providers/ollama/index.ts +0 -43
  29. package/src/providers/ollama/llm.ts +0 -272
  30. package/src/providers/ollama/transform.ts +0 -434
  31. package/src/providers/ollama/types.ts +0 -260
  32. package/src/providers/openai/index.ts +0 -186
  33. package/src/providers/openai/llm.completions.ts +0 -201
  34. package/src/providers/openai/llm.responses.ts +0 -211
  35. package/src/providers/openai/transform.completions.ts +0 -561
  36. package/src/providers/openai/transform.responses.ts +0 -708
  37. package/src/providers/openai/types.ts +0 -1249
  38. package/src/providers/openrouter/index.ts +0 -177
  39. package/src/providers/openrouter/llm.completions.ts +0 -201
  40. package/src/providers/openrouter/llm.responses.ts +0 -211
  41. package/src/providers/openrouter/transform.completions.ts +0 -538
  42. package/src/providers/openrouter/transform.responses.ts +0 -742
  43. package/src/providers/openrouter/types.ts +0 -717
  44. package/src/providers/xai/index.ts +0 -223
  45. package/src/providers/xai/llm.completions.ts +0 -201
  46. package/src/providers/xai/llm.messages.ts +0 -195
  47. package/src/providers/xai/llm.responses.ts +0 -211
  48. package/src/providers/xai/transform.completions.ts +0 -565
  49. package/src/providers/xai/transform.messages.ts +0 -448
  50. package/src/providers/xai/transform.responses.ts +0 -678
  51. package/src/providers/xai/types.ts +0 -938
  52. package/src/types/content.ts +0 -133
  53. package/src/types/errors.ts +0 -85
  54. package/src/types/index.ts +0 -105
  55. package/src/types/llm.ts +0 -211
  56. package/src/types/messages.ts +0 -205
  57. package/src/types/provider.ts +0 -195
  58. package/src/types/schema.ts +0 -58
  59. package/src/types/stream.ts +0 -146
  60. package/src/types/thread.ts +0 -226
  61. package/src/types/tool.ts +0 -88
  62. package/src/types/turn.ts +0 -118
  63. package/src/utils/id.ts +0 -28
  64. package/src/xai/index.ts +0 -41
@@ -1,538 +0,0 @@
1
- import type { LLMRequest, LLMResponse } from '../../types/llm.ts';
2
- import type { Message } from '../../types/messages.ts';
3
- import type { StreamEvent } from '../../types/stream.ts';
4
- import type { Tool, ToolCall } from '../../types/tool.ts';
5
- import type { TokenUsage } from '../../types/turn.ts';
6
- import type { ContentBlock, TextBlock, ImageBlock } from '../../types/content.ts';
7
- import {
8
- AssistantMessage,
9
- isUserMessage,
10
- isAssistantMessage,
11
- isToolResultMessage,
12
- } from '../../types/messages.ts';
13
- import type {
14
- OpenRouterCompletionsParams,
15
- OpenRouterCompletionsRequest,
16
- OpenRouterCompletionsMessage,
17
- OpenRouterUserContent,
18
- OpenRouterCompletionsTool,
19
- OpenRouterCompletionsResponse,
20
- OpenRouterCompletionsStreamChunk,
21
- OpenRouterToolCall,
22
- } from './types.ts';
23
-
24
- /**
25
- * Transform UPP request to OpenRouter Chat Completions format
26
- *
27
- * Params are spread directly to allow pass-through of any OpenRouter API fields,
28
- * even those not explicitly defined in our type. This enables developers to
29
- * use new API features without waiting for library updates.
30
- */
31
- export function transformRequest(
32
- request: LLMRequest<OpenRouterCompletionsParams>,
33
- modelId: string
34
- ): OpenRouterCompletionsRequest {
35
- const params = request.params ?? ({} as OpenRouterCompletionsParams);
36
-
37
- // Spread params to pass through all fields, then set required fields
38
- const openrouterRequest: OpenRouterCompletionsRequest = {
39
- ...params,
40
- model: modelId,
41
- messages: transformMessages(request.messages, request.system),
42
- };
43
-
44
- // Tools come from request, not params
45
- if (request.tools && request.tools.length > 0) {
46
- openrouterRequest.tools = request.tools.map(transformTool);
47
- }
48
-
49
- // Structured output via response_format (overrides params.response_format if set)
50
- if (request.structure) {
51
- const schema: Record<string, unknown> = {
52
- type: 'object',
53
- properties: request.structure.properties,
54
- required: request.structure.required,
55
- ...(request.structure.additionalProperties !== undefined
56
- ? { additionalProperties: request.structure.additionalProperties }
57
- : { additionalProperties: false }),
58
- };
59
- if (request.structure.description) {
60
- schema.description = request.structure.description;
61
- }
62
-
63
- openrouterRequest.response_format = {
64
- type: 'json_schema',
65
- json_schema: {
66
- name: 'json_response',
67
- description: request.structure.description,
68
- schema,
69
- strict: true,
70
- },
71
- };
72
- }
73
-
74
- return openrouterRequest;
75
- }
76
-
77
- /**
78
- * Transform messages including system prompt
79
- */
80
- function transformMessages(
81
- messages: Message[],
82
- system?: string
83
- ): OpenRouterCompletionsMessage[] {
84
- const result: OpenRouterCompletionsMessage[] = [];
85
-
86
- // Add system message first if present
87
- if (system) {
88
- result.push({
89
- role: 'system',
90
- content: system,
91
- });
92
- }
93
-
94
- // Transform each message
95
- for (const message of messages) {
96
- // Handle tool result messages specially - they need to produce multiple messages
97
- if (isToolResultMessage(message)) {
98
- const toolMessages = transformToolResults(message);
99
- result.push(...toolMessages);
100
- } else {
101
- const transformed = transformMessage(message);
102
- if (transformed) {
103
- result.push(transformed);
104
- }
105
- }
106
- }
107
-
108
- return result;
109
- }
110
-
111
- /**
112
- * Filter to only valid content blocks with a type property
113
- */
114
- function filterValidContent<T extends { type?: string }>(content: T[]): T[] {
115
- return content.filter((c) => c && typeof c.type === 'string');
116
- }
117
-
118
- /**
119
- * Transform a UPP Message to OpenRouter format
120
- */
121
- function transformMessage(message: Message): OpenRouterCompletionsMessage | null {
122
- if (isUserMessage(message)) {
123
- const validContent = filterValidContent(message.content);
124
- // Check if we can use simple string content
125
- if (validContent.length === 1 && validContent[0]?.type === 'text') {
126
- return {
127
- role: 'user',
128
- content: (validContent[0] as TextBlock).text,
129
- };
130
- }
131
- return {
132
- role: 'user',
133
- content: validContent.map(transformContentBlock),
134
- };
135
- }
136
-
137
- if (isAssistantMessage(message)) {
138
- const validContent = filterValidContent(message.content);
139
- // Extract text content
140
- const textContent = validContent
141
- .filter((c): c is TextBlock => c.type === 'text')
142
- .map((c) => c.text)
143
- .join('');
144
-
145
- const assistantMessage: OpenRouterCompletionsMessage = {
146
- role: 'assistant',
147
- content: textContent || null,
148
- };
149
-
150
- // Add tool calls if present
151
- if (message.toolCalls && message.toolCalls.length > 0) {
152
- (assistantMessage as { tool_calls?: OpenRouterToolCall[] }).tool_calls =
153
- message.toolCalls.map((call) => ({
154
- id: call.toolCallId,
155
- type: 'function' as const,
156
- function: {
157
- name: call.toolName,
158
- arguments: JSON.stringify(call.arguments),
159
- },
160
- }));
161
- }
162
-
163
- return assistantMessage;
164
- }
165
-
166
- if (isToolResultMessage(message)) {
167
- // Tool results are sent as individual tool messages
168
- // Return the first one and handle multiple in a different way
169
- // Actually, we need to return multiple messages for multiple tool results
170
- // This is handled by the caller - transform each result to a message
171
- const results = message.results.map((result) => ({
172
- role: 'tool' as const,
173
- tool_call_id: result.toolCallId,
174
- content:
175
- typeof result.result === 'string'
176
- ? result.result
177
- : JSON.stringify(result.result),
178
- }));
179
-
180
- // For now, return the first result - caller should handle multiple
181
- return results[0] ?? null;
182
- }
183
-
184
- return null;
185
- }
186
-
187
- /**
188
- * Transform multiple tool results to messages
189
- */
190
- export function transformToolResults(
191
- message: Message
192
- ): OpenRouterCompletionsMessage[] {
193
- if (!isToolResultMessage(message)) {
194
- const single = transformMessage(message);
195
- return single ? [single] : [];
196
- }
197
-
198
- return message.results.map((result) => ({
199
- role: 'tool' as const,
200
- tool_call_id: result.toolCallId,
201
- content:
202
- typeof result.result === 'string'
203
- ? result.result
204
- : JSON.stringify(result.result),
205
- }));
206
- }
207
-
208
- /**
209
- * Transform a content block to OpenRouter format
210
- */
211
- function transformContentBlock(block: ContentBlock): OpenRouterUserContent {
212
- switch (block.type) {
213
- case 'text':
214
- return { type: 'text', text: block.text };
215
-
216
- case 'image': {
217
- const imageBlock = block as ImageBlock;
218
- let url: string;
219
-
220
- if (imageBlock.source.type === 'base64') {
221
- url = `data:${imageBlock.mimeType};base64,${imageBlock.source.data}`;
222
- } else if (imageBlock.source.type === 'url') {
223
- url = imageBlock.source.url;
224
- } else if (imageBlock.source.type === 'bytes') {
225
- // Convert bytes to base64
226
- const base64 = btoa(
227
- Array.from(imageBlock.source.data)
228
- .map((b) => String.fromCharCode(b))
229
- .join('')
230
- );
231
- url = `data:${imageBlock.mimeType};base64,${base64}`;
232
- } else {
233
- throw new Error('Unknown image source type');
234
- }
235
-
236
- return {
237
- type: 'image_url',
238
- image_url: { url },
239
- };
240
- }
241
-
242
- default:
243
- throw new Error(`Unsupported content type: ${block.type}`);
244
- }
245
- }
246
-
247
- /**
248
- * Transform a UPP Tool to OpenRouter format
249
- */
250
- function transformTool(tool: Tool): OpenRouterCompletionsTool {
251
- return {
252
- type: 'function',
253
- function: {
254
- name: tool.name,
255
- description: tool.description,
256
- parameters: {
257
- type: 'object',
258
- properties: tool.parameters.properties,
259
- required: tool.parameters.required,
260
- ...(tool.parameters.additionalProperties !== undefined
261
- ? { additionalProperties: tool.parameters.additionalProperties }
262
- : {}),
263
- },
264
- },
265
- };
266
- }
267
-
268
- /**
269
- * Transform OpenRouter response to UPP LLMResponse
270
- */
271
- export function transformResponse(data: OpenRouterCompletionsResponse): LLMResponse {
272
- const choice = data.choices[0];
273
- if (!choice) {
274
- throw new Error('No choices in OpenRouter response');
275
- }
276
-
277
- // Extract text content
278
- const textContent: TextBlock[] = [];
279
- let structuredData: unknown;
280
- if (choice.message.content) {
281
- textContent.push({ type: 'text', text: choice.message.content });
282
- // Try to parse as JSON for structured output (native JSON mode)
283
- try {
284
- structuredData = JSON.parse(choice.message.content);
285
- } catch {
286
- // Not valid JSON - that's fine, might not be structured output
287
- }
288
- }
289
-
290
- // Extract tool calls
291
- const toolCalls: ToolCall[] = [];
292
- if (choice.message.tool_calls) {
293
- for (const call of choice.message.tool_calls) {
294
- let args: Record<string, unknown> = {};
295
- try {
296
- args = JSON.parse(call.function.arguments);
297
- } catch {
298
- // Invalid JSON - use empty object
299
- }
300
- toolCalls.push({
301
- toolCallId: call.id,
302
- toolName: call.function.name,
303
- arguments: args,
304
- });
305
- }
306
- }
307
-
308
- const message = new AssistantMessage(
309
- textContent,
310
- toolCalls.length > 0 ? toolCalls : undefined,
311
- {
312
- id: data.id,
313
- metadata: {
314
- openrouter: {
315
- model: data.model,
316
- finish_reason: choice.finish_reason,
317
- system_fingerprint: data.system_fingerprint,
318
- },
319
- },
320
- }
321
- );
322
-
323
- const usage: TokenUsage = {
324
- inputTokens: data.usage.prompt_tokens,
325
- outputTokens: data.usage.completion_tokens,
326
- totalTokens: data.usage.total_tokens,
327
- };
328
-
329
- // Map finish reason to stop reason
330
- let stopReason = 'end_turn';
331
- switch (choice.finish_reason) {
332
- case 'stop':
333
- stopReason = 'end_turn';
334
- break;
335
- case 'length':
336
- stopReason = 'max_tokens';
337
- break;
338
- case 'tool_calls':
339
- stopReason = 'tool_use';
340
- break;
341
- case 'content_filter':
342
- stopReason = 'content_filter';
343
- break;
344
- }
345
-
346
- return {
347
- message,
348
- usage,
349
- stopReason,
350
- data: structuredData,
351
- };
352
- }
353
-
354
- /**
355
- * State for accumulating streaming response
356
- */
357
- export interface CompletionsStreamState {
358
- id: string;
359
- model: string;
360
- text: string;
361
- toolCalls: Map<number, { id: string; name: string; arguments: string }>;
362
- finishReason: string | null;
363
- inputTokens: number;
364
- outputTokens: number;
365
- }
366
-
367
- /**
368
- * Create initial stream state
369
- */
370
- export function createStreamState(): CompletionsStreamState {
371
- return {
372
- id: '',
373
- model: '',
374
- text: '',
375
- toolCalls: new Map(),
376
- finishReason: null,
377
- inputTokens: 0,
378
- outputTokens: 0,
379
- };
380
- }
381
-
382
- /**
383
- * Transform OpenRouter stream chunk to UPP StreamEvent
384
- * Returns array since one chunk may produce multiple events
385
- */
386
- export function transformStreamEvent(
387
- chunk: OpenRouterCompletionsStreamChunk,
388
- state: CompletionsStreamState
389
- ): StreamEvent[] {
390
- const events: StreamEvent[] = [];
391
-
392
- // Update state with basic info
393
- if (chunk.id && !state.id) {
394
- state.id = chunk.id;
395
- events.push({ type: 'message_start', index: 0, delta: {} });
396
- }
397
- if (chunk.model) {
398
- state.model = chunk.model;
399
- }
400
-
401
- // Process choices
402
- const choice = chunk.choices[0];
403
- if (choice) {
404
- // Text delta
405
- if (choice.delta.content) {
406
- state.text += choice.delta.content;
407
- events.push({
408
- type: 'text_delta',
409
- index: 0,
410
- delta: { text: choice.delta.content },
411
- });
412
- }
413
-
414
- // Tool call deltas
415
- if (choice.delta.tool_calls) {
416
- for (const toolCallDelta of choice.delta.tool_calls) {
417
- const index = toolCallDelta.index;
418
- let toolCall = state.toolCalls.get(index);
419
-
420
- if (!toolCall) {
421
- toolCall = { id: '', name: '', arguments: '' };
422
- state.toolCalls.set(index, toolCall);
423
- }
424
-
425
- if (toolCallDelta.id) {
426
- toolCall.id = toolCallDelta.id;
427
- }
428
- if (toolCallDelta.function?.name) {
429
- toolCall.name = toolCallDelta.function.name;
430
- }
431
- if (toolCallDelta.function?.arguments) {
432
- toolCall.arguments += toolCallDelta.function.arguments;
433
- events.push({
434
- type: 'tool_call_delta',
435
- index: index,
436
- delta: {
437
- toolCallId: toolCall.id,
438
- toolName: toolCall.name,
439
- argumentsJson: toolCallDelta.function.arguments,
440
- },
441
- });
442
- }
443
- }
444
- }
445
-
446
- // Finish reason
447
- if (choice.finish_reason) {
448
- state.finishReason = choice.finish_reason;
449
- events.push({ type: 'message_stop', index: 0, delta: {} });
450
- }
451
- }
452
-
453
- // Usage info (usually comes at the end with stream_options.include_usage)
454
- if (chunk.usage) {
455
- state.inputTokens = chunk.usage.prompt_tokens;
456
- state.outputTokens = chunk.usage.completion_tokens;
457
- }
458
-
459
- return events;
460
- }
461
-
462
- /**
463
- * Build LLMResponse from accumulated stream state
464
- */
465
- export function buildResponseFromState(state: CompletionsStreamState): LLMResponse {
466
- const textContent: TextBlock[] = [];
467
- let structuredData: unknown;
468
- if (state.text) {
469
- textContent.push({ type: 'text', text: state.text });
470
- // Try to parse as JSON for structured output (native JSON mode)
471
- try {
472
- structuredData = JSON.parse(state.text);
473
- } catch {
474
- // Not valid JSON - that's fine, might not be structured output
475
- }
476
- }
477
-
478
- const toolCalls: ToolCall[] = [];
479
- for (const [, toolCall] of state.toolCalls) {
480
- let args: Record<string, unknown> = {};
481
- if (toolCall.arguments) {
482
- try {
483
- args = JSON.parse(toolCall.arguments);
484
- } catch {
485
- // Invalid JSON - use empty object
486
- }
487
- }
488
- toolCalls.push({
489
- toolCallId: toolCall.id,
490
- toolName: toolCall.name,
491
- arguments: args,
492
- });
493
- }
494
-
495
- const message = new AssistantMessage(
496
- textContent,
497
- toolCalls.length > 0 ? toolCalls : undefined,
498
- {
499
- id: state.id,
500
- metadata: {
501
- openrouter: {
502
- model: state.model,
503
- finish_reason: state.finishReason,
504
- },
505
- },
506
- }
507
- );
508
-
509
- const usage: TokenUsage = {
510
- inputTokens: state.inputTokens,
511
- outputTokens: state.outputTokens,
512
- totalTokens: state.inputTokens + state.outputTokens,
513
- };
514
-
515
- // Map finish reason to stop reason
516
- let stopReason = 'end_turn';
517
- switch (state.finishReason) {
518
- case 'stop':
519
- stopReason = 'end_turn';
520
- break;
521
- case 'length':
522
- stopReason = 'max_tokens';
523
- break;
524
- case 'tool_calls':
525
- stopReason = 'tool_use';
526
- break;
527
- case 'content_filter':
528
- stopReason = 'content_filter';
529
- break;
530
- }
531
-
532
- return {
533
- message,
534
- usage,
535
- stopReason,
536
- data: structuredData,
537
- };
538
- }