@providerprotocol/ai 0.0.7 → 0.0.9

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.
@@ -3,7 +3,7 @@ import type { Message } from '../../types/messages.ts';
3
3
  import type { StreamEvent } from '../../types/stream.ts';
4
4
  import type { Tool, ToolCall } from '../../types/tool.ts';
5
5
  import type { TokenUsage } from '../../types/turn.ts';
6
- import type { ContentBlock, TextBlock, ImageBlock } from '../../types/content.ts';
6
+ import type { ContentBlock, TextBlock, ImageBlock, AssistantContent } from '../../types/content.ts';
7
7
  import {
8
8
  AssistantMessage,
9
9
  isUserMessage,
@@ -16,11 +16,13 @@ import type {
16
16
  OpenAIResponsesInputItem,
17
17
  OpenAIResponsesContentPart,
18
18
  OpenAIResponsesTool,
19
+ OpenAIResponsesToolUnion,
19
20
  OpenAIResponsesResponse,
20
21
  OpenAIResponsesStreamEvent,
21
22
  OpenAIResponsesOutputItem,
22
23
  OpenAIResponsesMessageOutput,
23
24
  OpenAIResponsesFunctionCallOutput,
25
+ OpenAIResponsesImageGenerationOutput,
24
26
  } from './types.ts';
25
27
 
26
28
  /**
@@ -36,16 +38,23 @@ export function transformRequest(
36
38
  ): OpenAIResponsesRequest {
37
39
  const params = request.params ?? ({} as OpenAIResponsesParams);
38
40
 
41
+ // Extract built-in tools from params before spreading
42
+ const builtInTools = params.tools as OpenAIResponsesToolUnion[] | undefined;
43
+ const { tools: _paramsTools, ...restParams } = params;
44
+
39
45
  // Spread params to pass through all fields, then set required fields
40
46
  const openaiRequest: OpenAIResponsesRequest = {
41
- ...params,
47
+ ...restParams,
42
48
  model: modelId,
43
49
  input: transformInputItems(request.messages, request.system),
44
50
  };
45
51
 
46
- // Tools come from request, not params
47
- if (request.tools && request.tools.length > 0) {
48
- openaiRequest.tools = request.tools.map(transformTool);
52
+ // Merge tools: UPP function tools from request + built-in tools from params
53
+ const functionTools: OpenAIResponsesToolUnion[] = request.tools?.map(transformTool) ?? [];
54
+ const allTools: OpenAIResponsesToolUnion[] = [...functionTools, ...(builtInTools ?? [])];
55
+
56
+ if (allTools.length > 0) {
57
+ openaiRequest.tools = allTools;
49
58
  }
50
59
 
51
60
  // Structured output via text.format (overrides params.text if set)
@@ -276,8 +285,8 @@ function transformTool(tool: Tool): OpenAIResponsesTool {
276
285
  * Transform OpenAI Responses API response to UPP LLMResponse
277
286
  */
278
287
  export function transformResponse(data: OpenAIResponsesResponse): LLMResponse {
279
- // Extract text content and tool calls from output items
280
- const textContent: TextBlock[] = [];
288
+ // Extract content and tool calls from output items
289
+ const content: AssistantContent[] = [];
281
290
  const toolCalls: ToolCall[] = [];
282
291
  const functionCallItems: Array<{
283
292
  id: string;
@@ -291,20 +300,19 @@ export function transformResponse(data: OpenAIResponsesResponse): LLMResponse {
291
300
  for (const item of data.output) {
292
301
  if (item.type === 'message') {
293
302
  const messageItem = item as OpenAIResponsesMessageOutput;
294
- for (const content of messageItem.content) {
295
- if (content.type === 'output_text') {
296
- textContent.push({ type: 'text', text: content.text });
303
+ for (const part of messageItem.content) {
304
+ if (part.type === 'output_text') {
305
+ content.push({ type: 'text', text: part.text });
297
306
  // Try to parse as JSON for structured output (native JSON mode)
298
- // Only set data if text is valid JSON
299
307
  if (structuredData === undefined) {
300
308
  try {
301
- structuredData = JSON.parse(content.text);
309
+ structuredData = JSON.parse(part.text);
302
310
  } catch {
303
311
  // Not valid JSON - that's fine, might not be structured output
304
312
  }
305
313
  }
306
- } else if (content.type === 'refusal') {
307
- textContent.push({ type: 'text', text: content.refusal });
314
+ } else if (part.type === 'refusal') {
315
+ content.push({ type: 'text', text: part.refusal });
308
316
  hadRefusal = true;
309
317
  }
310
318
  }
@@ -327,11 +335,20 @@ export function transformResponse(data: OpenAIResponsesResponse): LLMResponse {
327
335
  name: functionCall.name,
328
336
  arguments: functionCall.arguments,
329
337
  });
338
+ } else if (item.type === 'image_generation_call') {
339
+ const imageGen = item as OpenAIResponsesImageGenerationOutput;
340
+ if (imageGen.result) {
341
+ content.push({
342
+ type: 'image',
343
+ mimeType: 'image/png',
344
+ source: { type: 'base64', data: imageGen.result },
345
+ } as ImageBlock);
346
+ }
330
347
  }
331
348
  }
332
349
 
333
350
  const message = new AssistantMessage(
334
- textContent,
351
+ content,
335
352
  toolCalls.length > 0 ? toolCalls : undefined,
336
353
  {
337
354
  id: data.id,
@@ -339,7 +356,6 @@ export function transformResponse(data: OpenAIResponsesResponse): LLMResponse {
339
356
  openai: {
340
357
  model: data.model,
341
358
  status: data.status,
342
- // Store response_id for multi-turn tool calling
343
359
  response_id: data.id,
344
360
  functionCallItems:
345
361
  functionCallItems.length > 0 ? functionCallItems : undefined,
@@ -388,6 +404,7 @@ export interface ResponsesStreamState {
388
404
  number,
389
405
  { itemId?: string; callId?: string; name?: string; arguments: string }
390
406
  >;
407
+ images: string[]; // Base64 image data from image_generation_call outputs
391
408
  status: string;
392
409
  inputTokens: number;
393
410
  outputTokens: number;
@@ -403,6 +420,7 @@ export function createStreamState(): ResponsesStreamState {
403
420
  model: '',
404
421
  textByIndex: new Map(),
405
422
  toolCalls: new Map(),
423
+ images: [],
406
424
  status: 'in_progress',
407
425
  inputTokens: 0,
408
426
  outputTokens: 0,
@@ -479,6 +497,11 @@ export function transformStreamEvent(
479
497
  existing.arguments = functionCall.arguments;
480
498
  }
481
499
  state.toolCalls.set(event.output_index, existing);
500
+ } else if (event.item.type === 'image_generation_call') {
501
+ const imageGen = event.item as OpenAIResponsesImageGenerationOutput;
502
+ if (imageGen.result) {
503
+ state.images.push(imageGen.result);
504
+ }
482
505
  }
483
506
  events.push({
484
507
  type: 'content_block_stop',
@@ -579,13 +602,13 @@ export function transformStreamEvent(
579
602
  * Build LLMResponse from accumulated stream state
580
603
  */
581
604
  export function buildResponseFromState(state: ResponsesStreamState): LLMResponse {
582
- const textContent: TextBlock[] = [];
605
+ const content: AssistantContent[] = [];
583
606
  let structuredData: unknown;
584
607
 
585
608
  // Combine all text content
586
609
  for (const [, text] of state.textByIndex) {
587
610
  if (text) {
588
- textContent.push({ type: 'text', text });
611
+ content.push({ type: 'text', text });
589
612
  // Try to parse as JSON for structured output (native JSON mode)
590
613
  if (structuredData === undefined) {
591
614
  try {
@@ -597,6 +620,15 @@ export function buildResponseFromState(state: ResponsesStreamState): LLMResponse
597
620
  }
598
621
  }
599
622
 
623
+ // Add any generated images
624
+ for (const imageData of state.images) {
625
+ content.push({
626
+ type: 'image',
627
+ mimeType: 'image/png',
628
+ source: { type: 'base64', data: imageData },
629
+ } as ImageBlock);
630
+ }
631
+
600
632
  const toolCalls: ToolCall[] = [];
601
633
  const functionCallItems: Array<{
602
634
  id: string;
@@ -633,7 +665,7 @@ export function buildResponseFromState(state: ResponsesStreamState): LLMResponse
633
665
  }
634
666
 
635
667
  const message = new AssistantMessage(
636
- textContent,
668
+ content,
637
669
  toolCalls.length > 0 ? toolCalls : undefined,
638
670
  {
639
671
  id: state.id,