@tanstack/openai-base 0.1.0

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 (110) hide show
  1. package/dist/esm/adapters/chat-completions-text.d.ts +76 -0
  2. package/dist/esm/adapters/chat-completions-text.js +411 -0
  3. package/dist/esm/adapters/chat-completions-text.js.map +1 -0
  4. package/dist/esm/adapters/chat-completions-tool-converter.d.ts +24 -0
  5. package/dist/esm/adapters/chat-completions-tool-converter.js +29 -0
  6. package/dist/esm/adapters/chat-completions-tool-converter.js.map +1 -0
  7. package/dist/esm/adapters/image.d.ts +32 -0
  8. package/dist/esm/adapters/image.js +69 -0
  9. package/dist/esm/adapters/image.js.map +1 -0
  10. package/dist/esm/adapters/responses-text.d.ts +115 -0
  11. package/dist/esm/adapters/responses-text.js +635 -0
  12. package/dist/esm/adapters/responses-text.js.map +1 -0
  13. package/dist/esm/adapters/responses-tool-converter.d.ts +35 -0
  14. package/dist/esm/adapters/responses-tool-converter.js +27 -0
  15. package/dist/esm/adapters/responses-tool-converter.js.map +1 -0
  16. package/dist/esm/adapters/summarize.d.ts +28 -0
  17. package/dist/esm/adapters/summarize.js +74 -0
  18. package/dist/esm/adapters/summarize.js.map +1 -0
  19. package/dist/esm/adapters/transcription.d.ts +39 -0
  20. package/dist/esm/adapters/transcription.js +139 -0
  21. package/dist/esm/adapters/transcription.js.map +1 -0
  22. package/dist/esm/adapters/tts.d.ts +26 -0
  23. package/dist/esm/adapters/tts.js +65 -0
  24. package/dist/esm/adapters/tts.js.map +1 -0
  25. package/dist/esm/adapters/video.d.ts +48 -0
  26. package/dist/esm/adapters/video.js +192 -0
  27. package/dist/esm/adapters/video.js.map +1 -0
  28. package/dist/esm/index.d.ts +15 -0
  29. package/dist/esm/index.js +65 -0
  30. package/dist/esm/index.js.map +1 -0
  31. package/dist/esm/tools/apply-patch-tool.d.ts +11 -0
  32. package/dist/esm/tools/apply-patch-tool.js +17 -0
  33. package/dist/esm/tools/apply-patch-tool.js.map +1 -0
  34. package/dist/esm/tools/code-interpreter-tool.d.ts +11 -0
  35. package/dist/esm/tools/code-interpreter-tool.js +22 -0
  36. package/dist/esm/tools/code-interpreter-tool.js.map +1 -0
  37. package/dist/esm/tools/computer-use-tool.d.ts +11 -0
  38. package/dist/esm/tools/computer-use-tool.js +23 -0
  39. package/dist/esm/tools/computer-use-tool.js.map +1 -0
  40. package/dist/esm/tools/custom-tool.d.ts +11 -0
  41. package/dist/esm/tools/custom-tool.js +23 -0
  42. package/dist/esm/tools/custom-tool.js.map +1 -0
  43. package/dist/esm/tools/file-search-tool.d.ts +11 -0
  44. package/dist/esm/tools/file-search-tool.js +30 -0
  45. package/dist/esm/tools/file-search-tool.js.map +1 -0
  46. package/dist/esm/tools/function-tool.d.ts +15 -0
  47. package/dist/esm/tools/function-tool.js +24 -0
  48. package/dist/esm/tools/function-tool.js.map +1 -0
  49. package/dist/esm/tools/image-generation-tool.d.ts +11 -0
  50. package/dist/esm/tools/image-generation-tool.js +27 -0
  51. package/dist/esm/tools/image-generation-tool.js.map +1 -0
  52. package/dist/esm/tools/index.d.ts +27 -0
  53. package/dist/esm/tools/local-shell-tool.d.ts +11 -0
  54. package/dist/esm/tools/local-shell-tool.js +17 -0
  55. package/dist/esm/tools/local-shell-tool.js.map +1 -0
  56. package/dist/esm/tools/mcp-tool.d.ts +12 -0
  57. package/dist/esm/tools/mcp-tool.js +31 -0
  58. package/dist/esm/tools/mcp-tool.js.map +1 -0
  59. package/dist/esm/tools/shell-tool.d.ts +11 -0
  60. package/dist/esm/tools/shell-tool.js +17 -0
  61. package/dist/esm/tools/shell-tool.js.map +1 -0
  62. package/dist/esm/tools/tool-choice.d.ts +17 -0
  63. package/dist/esm/tools/tool-converter.d.ts +6 -0
  64. package/dist/esm/tools/tool-converter.js +61 -0
  65. package/dist/esm/tools/tool-converter.js.map +1 -0
  66. package/dist/esm/tools/web-search-preview-tool.d.ts +11 -0
  67. package/dist/esm/tools/web-search-preview-tool.js +20 -0
  68. package/dist/esm/tools/web-search-preview-tool.js.map +1 -0
  69. package/dist/esm/tools/web-search-tool.d.ts +11 -0
  70. package/dist/esm/tools/web-search-tool.js +16 -0
  71. package/dist/esm/tools/web-search-tool.js.map +1 -0
  72. package/dist/esm/types/config.d.ts +4 -0
  73. package/dist/esm/types/message-metadata.d.ts +19 -0
  74. package/dist/esm/types/provider-options.d.ts +40 -0
  75. package/dist/esm/utils/client.d.ts +3 -0
  76. package/dist/esm/utils/client.js +8 -0
  77. package/dist/esm/utils/client.js.map +1 -0
  78. package/dist/esm/utils/schema-converter.d.ts +12 -0
  79. package/dist/esm/utils/schema-converter.js +65 -0
  80. package/dist/esm/utils/schema-converter.js.map +1 -0
  81. package/package.json +57 -0
  82. package/src/adapters/chat-completions-text.ts +817 -0
  83. package/src/adapters/chat-completions-tool-converter.ts +70 -0
  84. package/src/adapters/image.ts +158 -0
  85. package/src/adapters/responses-text.ts +1147 -0
  86. package/src/adapters/responses-tool-converter.ts +77 -0
  87. package/src/adapters/summarize.ts +174 -0
  88. package/src/adapters/transcription.ts +194 -0
  89. package/src/adapters/tts.ts +124 -0
  90. package/src/adapters/video.ts +385 -0
  91. package/src/index.ts +24 -0
  92. package/src/tools/apply-patch-tool.ts +32 -0
  93. package/src/tools/code-interpreter-tool.ts +39 -0
  94. package/src/tools/computer-use-tool.ts +38 -0
  95. package/src/tools/custom-tool.ts +33 -0
  96. package/src/tools/file-search-tool.ts +51 -0
  97. package/src/tools/function-tool.ts +44 -0
  98. package/src/tools/image-generation-tool.ts +51 -0
  99. package/src/tools/index.ts +41 -0
  100. package/src/tools/local-shell-tool.ts +32 -0
  101. package/src/tools/mcp-tool.ts +47 -0
  102. package/src/tools/shell-tool.ts +30 -0
  103. package/src/tools/tool-choice.ts +31 -0
  104. package/src/tools/tool-converter.ts +68 -0
  105. package/src/tools/web-search-preview-tool.ts +39 -0
  106. package/src/tools/web-search-tool.ts +38 -0
  107. package/src/types/config.ts +5 -0
  108. package/src/utils/client.ts +8 -0
  109. package/src/utils/request-options.ts +16 -0
  110. package/src/utils/schema-converter.ts +89 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"responses-text.js","sources":["../../../src/adapters/responses-text.ts"],"sourcesContent":["import { BaseTextAdapter } from '@tanstack/ai/adapters'\nimport { generateId, transformNullsToUndefined } from '@tanstack/ai-utils'\nimport { createOpenAICompatibleClient } from '../utils/client'\nimport { makeStructuredOutputCompatible } from '../utils/schema-converter'\nimport { convertToolsToResponsesFormat } from './responses-tool-converter'\nimport type {\n StructuredOutputOptions,\n StructuredOutputResult,\n} from '@tanstack/ai/adapters'\nimport type OpenAI_SDK from 'openai'\nimport type { Responses } from 'openai/resources'\nimport type {\n ContentPart,\n DefaultMessageMetadataByModality,\n Modality,\n ModelMessage,\n StreamChunk,\n TextOptions,\n} from '@tanstack/ai'\nimport type { OpenAICompatibleClientConfig } from '../types/config'\n\n/**\n * OpenAI-compatible Responses API Text Adapter\n *\n * A generalized base class for providers that use the OpenAI Responses API\n * (`/v1/responses`). Providers like OpenAI (native), Azure OpenAI, and others\n * that implement the Responses API can extend this class and only need to:\n * - Set `baseURL` in the config\n * - Lock the generic type parameters to provider-specific types\n * - Override specific methods for quirks\n *\n * Key differences from the Chat Completions adapter:\n * - Uses `client.responses.create()` instead of `client.chat.completions.create()`\n * - Messages use `ResponseInput` format\n * - System prompts go in `instructions` field, not as array messages\n * - Streaming events are completely different (9+ event types vs simple delta chunks)\n * - Supports reasoning/thinking tokens via `response.reasoning_text.delta`\n * - Structured output uses `text.format` in the request (not `response_format`)\n * - Tool calls use `response.function_call_arguments.delta`\n * - Content parts are `input_text`, `input_image`, `input_file`\n *\n * All methods that build requests or process responses are `protected` so subclasses\n * can override them.\n */\nexport class OpenAICompatibleResponsesTextAdapter<\n TModel extends string,\n TProviderOptions extends Record<string, any> = Record<string, any>,\n TInputModalities extends ReadonlyArray<Modality> = ReadonlyArray<Modality>,\n TMessageMetadata extends DefaultMessageMetadataByModality =\n DefaultMessageMetadataByModality,\n> extends BaseTextAdapter<\n TModel,\n TProviderOptions,\n TInputModalities,\n TMessageMetadata\n> {\n readonly kind = 'text' as const\n readonly name: string\n\n protected client: OpenAI_SDK\n\n constructor(\n config: OpenAICompatibleClientConfig,\n model: TModel,\n name: string = 'openai-compatible-responses',\n ) {\n super({}, model)\n this.name = name\n this.client = createOpenAICompatibleClient(config)\n }\n\n async *chatStream(\n options: TextOptions<TProviderOptions>,\n ): AsyncIterable<StreamChunk> {\n // Track tool call metadata by unique ID\n // Responses API streams tool calls with deltas — first chunk has ID/name,\n // subsequent chunks only have args.\n // We assign our own indices as we encounter unique tool call IDs.\n const toolCallMetadata = new Map<\n string,\n { index: number; name: string; callId: string; started: boolean }\n >()\n const requestParams = this.mapOptionsToRequest(options)\n const timestamp = Date.now()\n\n // AG-UI lifecycle tracking\n const aguiState = {\n runId: generateId(this.name),\n messageId: generateId(this.name),\n timestamp,\n hasEmittedRunStarted: false,\n }\n\n try {\n const response = await this.client.responses.create(\n {\n ...requestParams,\n stream: true,\n },\n {\n headers: options.request?.headers,\n signal: options.request?.signal,\n },\n )\n\n yield* this.processStreamChunks(\n response,\n toolCallMetadata,\n options,\n aguiState,\n )\n } catch (error: unknown) {\n const err = error as Error & { code?: string }\n\n // Emit RUN_STARTED if not yet emitted\n if (!aguiState.hasEmittedRunStarted) {\n aguiState.hasEmittedRunStarted = true\n yield {\n type: 'RUN_STARTED',\n runId: aguiState.runId,\n model: options.model,\n timestamp,\n }\n }\n\n // Emit AG-UI RUN_ERROR\n yield {\n type: 'RUN_ERROR',\n runId: aguiState.runId,\n model: options.model,\n timestamp,\n error: {\n message: err.message || 'Unknown error',\n code: err.code,\n },\n }\n\n console.error(\n `>>> [${this.name}] chatStream: Fatal error during response creation <<<`,\n )\n console.error('>>> Error message:', err.message)\n console.error('>>> Error stack:', err.stack)\n console.error('>>> Full error:', err)\n }\n }\n\n /**\n * Generate structured output using the provider's native JSON Schema response format.\n * Uses stream: false to get the complete response in one call.\n *\n * OpenAI-compatible Responses APIs have strict requirements for structured output:\n * - All properties must be in the `required` array\n * - Optional fields should have null added to their type union\n * - additionalProperties must be false for all objects\n *\n * The outputSchema is already JSON Schema (converted in the ai layer).\n * We apply provider-specific transformations for structured output compatibility.\n */\n async structuredOutput(\n options: StructuredOutputOptions<TProviderOptions>,\n ): Promise<StructuredOutputResult<unknown>> {\n const { chatOptions, outputSchema } = options\n const requestParams = this.mapOptionsToRequest(chatOptions)\n\n // Apply provider-specific transformations for structured output compatibility\n const jsonSchema = this.makeStructuredOutputCompatible(\n outputSchema,\n outputSchema.required || [],\n )\n\n try {\n const response = await this.client.responses.create(\n {\n ...requestParams,\n stream: false,\n // Configure structured output via text.format\n text: {\n format: {\n type: 'json_schema',\n name: 'structured_output',\n schema: jsonSchema,\n strict: true,\n },\n },\n },\n {\n headers: chatOptions.request?.headers,\n signal: chatOptions.request?.signal,\n },\n )\n\n // Extract text content from the response\n const rawText = this.extractTextFromResponse(\n response as OpenAI_SDK.Responses.Response,\n )\n\n // Parse the JSON response\n let parsed: unknown\n try {\n parsed = JSON.parse(rawText)\n } catch {\n throw new Error(\n `Failed to parse structured output as JSON. Content: ${rawText.slice(0, 200)}${rawText.length > 200 ? '...' : ''}`,\n )\n }\n\n // Transform null values to undefined to match original Zod schema expectations\n // Provider returns null for optional fields we made nullable in the schema\n const transformed = transformNullsToUndefined(parsed)\n\n return {\n data: transformed,\n rawText,\n }\n } catch (error: unknown) {\n const err = error as Error\n console.error(\n `>>> [${this.name}] structuredOutput: Error during response creation <<<`,\n )\n console.error('>>> Error message:', err.message)\n throw error\n }\n }\n\n /**\n * Applies provider-specific transformations for structured output compatibility.\n * Override this in subclasses to handle provider-specific quirks.\n */\n protected makeStructuredOutputCompatible(\n schema: Record<string, any>,\n originalRequired: Array<string>,\n ): Record<string, any> {\n return makeStructuredOutputCompatible(schema, originalRequired)\n }\n\n /**\n * Extract text content from a non-streaming Responses API response.\n * Override this in subclasses for provider-specific response shapes.\n */\n protected extractTextFromResponse(\n response: OpenAI_SDK.Responses.Response,\n ): string {\n let textContent = ''\n\n for (const item of response.output) {\n if (item.type === 'message') {\n for (const part of item.content) {\n if (part.type === 'output_text') {\n textContent += part.text\n }\n }\n }\n }\n\n return textContent\n }\n\n /**\n * Processes streamed chunks from the Responses API and yields AG-UI events.\n * Override this in subclasses to handle provider-specific stream behavior.\n *\n * Handles the following event types:\n * - response.created / response.incomplete / response.failed\n * - response.output_text.delta\n * - response.reasoning_text.delta\n * - response.reasoning_summary_text.delta\n * - response.content_part.added / response.content_part.done\n * - response.output_item.added\n * - response.function_call_arguments.delta / response.function_call_arguments.done\n * - response.completed\n * - error\n */\n protected async *processStreamChunks(\n stream: AsyncIterable<OpenAI_SDK.Responses.ResponseStreamEvent>,\n toolCallMetadata: Map<\n string,\n { index: number; name: string; callId: string; started: boolean }\n >,\n options: TextOptions,\n aguiState: {\n runId: string\n messageId: string\n timestamp: number\n hasEmittedRunStarted: boolean\n },\n ): AsyncIterable<StreamChunk> {\n let accumulatedContent = ''\n let accumulatedReasoning = ''\n const timestamp = aguiState.timestamp\n let chunkCount = 0\n\n // Track if we've been streaming deltas to avoid duplicating content from done events\n let hasStreamedContentDeltas = false\n let hasStreamedReasoningDeltas = false\n\n // Preserve response metadata across events\n let model: string = options.model\n\n // AG-UI lifecycle tracking\n let stepId: string | null = null\n let hasEmittedTextMessageStart = false\n let hasEmittedStepStarted = false\n\n try {\n for await (const chunk of stream) {\n chunkCount++\n\n // Emit RUN_STARTED on first chunk\n if (!aguiState.hasEmittedRunStarted) {\n aguiState.hasEmittedRunStarted = true\n yield {\n type: 'RUN_STARTED',\n runId: aguiState.runId,\n model: model || options.model,\n timestamp,\n }\n }\n\n const handleContentPart = (contentPart: {\n type: string\n text?: string\n refusal?: string\n }): StreamChunk => {\n if (contentPart.type === 'output_text') {\n accumulatedContent += contentPart.text || ''\n return {\n type: 'TEXT_MESSAGE_CONTENT',\n messageId: aguiState.messageId,\n model: model || options.model,\n timestamp,\n delta: contentPart.text || '',\n content: accumulatedContent,\n }\n }\n\n if (contentPart.type === 'reasoning_text') {\n accumulatedReasoning += contentPart.text || ''\n return {\n type: 'STEP_FINISHED',\n stepId: stepId || generateId(this.name),\n model: model || options.model,\n timestamp,\n delta: contentPart.text || '',\n content: accumulatedReasoning,\n }\n }\n return {\n type: 'RUN_ERROR',\n runId: aguiState.runId,\n model: model || options.model,\n timestamp,\n error: {\n message: contentPart.refusal || 'Unknown refusal',\n },\n }\n }\n\n // handle general response events\n if (\n chunk.type === 'response.created' ||\n chunk.type === 'response.incomplete' ||\n chunk.type === 'response.failed'\n ) {\n model = chunk.response.model\n // Reset streaming flags for new response\n hasStreamedContentDeltas = false\n hasStreamedReasoningDeltas = false\n hasEmittedTextMessageStart = false\n hasEmittedStepStarted = false\n accumulatedContent = ''\n accumulatedReasoning = ''\n if (chunk.response.error) {\n yield {\n type: 'RUN_ERROR',\n runId: aguiState.runId,\n model: chunk.response.model,\n timestamp,\n error: chunk.response.error,\n }\n }\n if (chunk.response.incomplete_details) {\n yield {\n type: 'RUN_ERROR',\n runId: aguiState.runId,\n model: chunk.response.model,\n timestamp,\n error: {\n message: chunk.response.incomplete_details.reason ?? '',\n },\n }\n }\n }\n\n // Handle output text deltas (token-by-token streaming)\n // response.output_text.delta provides incremental text updates\n if (chunk.type === 'response.output_text.delta' && chunk.delta) {\n // Delta can be an array of strings or a single string\n const textDelta = Array.isArray(chunk.delta)\n ? chunk.delta.join('')\n : typeof chunk.delta === 'string'\n ? chunk.delta\n : ''\n\n if (textDelta) {\n // Emit TEXT_MESSAGE_START on first text content\n if (!hasEmittedTextMessageStart) {\n hasEmittedTextMessageStart = true\n yield {\n type: 'TEXT_MESSAGE_START',\n messageId: aguiState.messageId,\n model: model || options.model,\n timestamp,\n role: 'assistant',\n }\n }\n\n accumulatedContent += textDelta\n hasStreamedContentDeltas = true\n yield {\n type: 'TEXT_MESSAGE_CONTENT',\n messageId: aguiState.messageId,\n model: model || options.model,\n timestamp,\n delta: textDelta,\n content: accumulatedContent,\n }\n }\n }\n\n // Handle reasoning deltas (token-by-token thinking/reasoning streaming)\n // response.reasoning_text.delta provides incremental reasoning updates\n if (chunk.type === 'response.reasoning_text.delta' && chunk.delta) {\n // Delta can be an array of strings or a single string\n const reasoningDelta = Array.isArray(chunk.delta)\n ? chunk.delta.join('')\n : typeof chunk.delta === 'string'\n ? chunk.delta\n : ''\n\n if (reasoningDelta) {\n // Emit STEP_STARTED on first reasoning content\n if (!hasEmittedStepStarted) {\n hasEmittedStepStarted = true\n stepId = generateId(this.name)\n yield {\n type: 'STEP_STARTED',\n stepId,\n model: model || options.model,\n timestamp,\n stepType: 'thinking',\n }\n }\n\n accumulatedReasoning += reasoningDelta\n hasStreamedReasoningDeltas = true\n yield {\n type: 'STEP_FINISHED',\n stepId: stepId || generateId(this.name),\n model: model || options.model,\n timestamp,\n delta: reasoningDelta,\n content: accumulatedReasoning,\n }\n }\n }\n\n // Handle reasoning summary deltas (when using reasoning.summary option)\n // response.reasoning_summary_text.delta provides incremental summary updates\n if (\n chunk.type === 'response.reasoning_summary_text.delta' &&\n chunk.delta\n ) {\n const summaryDelta =\n typeof chunk.delta === 'string' ? chunk.delta : ''\n\n if (summaryDelta) {\n // Emit STEP_STARTED on first reasoning content\n if (!hasEmittedStepStarted) {\n hasEmittedStepStarted = true\n stepId = generateId(this.name)\n yield {\n type: 'STEP_STARTED',\n stepId,\n model: model || options.model,\n timestamp,\n stepType: 'thinking',\n }\n }\n\n accumulatedReasoning += summaryDelta\n hasStreamedReasoningDeltas = true\n yield {\n type: 'STEP_FINISHED',\n stepId: stepId || generateId(this.name),\n model: model || options.model,\n timestamp,\n delta: summaryDelta,\n content: accumulatedReasoning,\n }\n }\n }\n\n // handle content_part added events for text, reasoning and refusals\n if (chunk.type === 'response.content_part.added') {\n const contentPart = chunk.part\n // Emit TEXT_MESSAGE_START if this is text content\n if (\n contentPart.type === 'output_text' &&\n !hasEmittedTextMessageStart\n ) {\n hasEmittedTextMessageStart = true\n yield {\n type: 'TEXT_MESSAGE_START',\n messageId: aguiState.messageId,\n model: model || options.model,\n timestamp,\n role: 'assistant',\n }\n }\n // Emit STEP_STARTED if this is reasoning content\n if (contentPart.type === 'reasoning_text' && !hasEmittedStepStarted) {\n hasEmittedStepStarted = true\n stepId = generateId(this.name)\n yield {\n type: 'STEP_STARTED',\n stepId,\n model: model || options.model,\n timestamp,\n stepType: 'thinking',\n }\n }\n yield handleContentPart(contentPart)\n }\n\n if (chunk.type === 'response.content_part.done') {\n const contentPart = chunk.part\n\n // Skip emitting chunks for content parts that we've already streamed via deltas\n // The done event is just a completion marker, not new content\n if (contentPart.type === 'output_text' && hasStreamedContentDeltas) {\n // Content already accumulated from deltas, skip\n continue\n }\n if (\n contentPart.type === 'reasoning_text' &&\n hasStreamedReasoningDeltas\n ) {\n // Reasoning already accumulated from deltas, skip\n continue\n }\n\n // Only emit if we haven't been streaming deltas (e.g., for non-streaming responses)\n yield handleContentPart(contentPart)\n }\n\n // handle output_item.added to capture function call metadata (name)\n if (chunk.type === 'response.output_item.added') {\n const item = chunk.item\n if (item.type === 'function_call') {\n // call_id is the required correlation ID for function_call_output.\n // id is the internal item ID used by delta/done events (item_id).\n // Use id when available (for the metadata map keyed by item_id),\n // falling back to call_id for providers that omit id.\n const itemId = item.id || item.call_id\n const callId = item.call_id || item.id || ''\n\n // Store the function name for later use\n if (!toolCallMetadata.has(itemId)) {\n toolCallMetadata.set(itemId, {\n index: chunk.output_index,\n name: item.name || '',\n callId,\n started: false,\n })\n }\n // Emit TOOL_CALL_START\n yield {\n type: 'TOOL_CALL_START',\n toolCallId: callId,\n toolName: item.name || '',\n model: model || options.model,\n timestamp,\n index: chunk.output_index,\n }\n toolCallMetadata.get(itemId)!.started = true\n }\n }\n\n // Handle function call arguments delta (streaming)\n if (\n chunk.type === 'response.function_call_arguments.delta' &&\n chunk.delta\n ) {\n const metadata = toolCallMetadata.get(chunk.item_id)\n yield {\n type: 'TOOL_CALL_ARGS',\n toolCallId: metadata?.callId || chunk.item_id,\n model: model || options.model,\n timestamp,\n delta: chunk.delta,\n args: metadata ? undefined : chunk.delta,\n }\n }\n\n if (chunk.type === 'response.function_call_arguments.done') {\n const { item_id } = chunk\n\n // Get the function name from metadata (captured in output_item.added)\n const metadata = toolCallMetadata.get(item_id)\n const name = metadata?.name || ''\n const callId = metadata?.callId || item_id\n\n // Parse arguments\n let parsedInput: unknown = {}\n try {\n parsedInput = chunk.arguments ? JSON.parse(chunk.arguments) : {}\n } catch {\n parsedInput = {}\n }\n\n yield {\n type: 'TOOL_CALL_END',\n toolCallId: callId,\n toolName: name,\n model: model || options.model,\n timestamp,\n input: parsedInput,\n }\n }\n\n if (chunk.type === 'response.completed') {\n // Emit TEXT_MESSAGE_END if we had text content\n if (hasEmittedTextMessageStart) {\n yield {\n type: 'TEXT_MESSAGE_END',\n messageId: aguiState.messageId,\n model: model || options.model,\n timestamp,\n }\n }\n\n // Determine finish reason based on output\n // If there are function_call items in the output, it's a tool_calls finish\n const hasFunctionCalls = chunk.response.output.some(\n (item: unknown) =>\n (item as { type: string }).type === 'function_call',\n )\n\n yield {\n type: 'RUN_FINISHED',\n runId: aguiState.runId,\n model: model || options.model,\n timestamp,\n usage: {\n promptTokens: chunk.response.usage?.input_tokens || 0,\n completionTokens: chunk.response.usage?.output_tokens || 0,\n totalTokens: chunk.response.usage?.total_tokens || 0,\n },\n finishReason: hasFunctionCalls ? 'tool_calls' : 'stop',\n }\n }\n\n if (chunk.type === 'error') {\n yield {\n type: 'RUN_ERROR',\n runId: aguiState.runId,\n model: model || options.model,\n timestamp,\n error: {\n message: chunk.message,\n code: chunk.code ?? undefined,\n },\n }\n }\n }\n } catch (error: unknown) {\n const err = error as Error & { code?: string }\n console.error(`[${this.name}] Stream ended with error:`, err.message)\n yield {\n type: 'RUN_ERROR',\n runId: aguiState.runId,\n model: options.model,\n timestamp,\n error: {\n message: err.message || 'Unknown error occurred',\n code: err.code,\n },\n }\n }\n }\n\n /**\n * Maps common TextOptions to Responses API request format.\n * Override this in subclasses to add provider-specific options.\n */\n protected mapOptionsToRequest(\n options: TextOptions,\n ): Omit<OpenAI_SDK.Responses.ResponseCreateParams, 'stream'> {\n const input = this.convertMessagesToInput(options.messages)\n\n const tools = options.tools\n ? convertToolsToResponsesFormat(\n options.tools,\n this.makeStructuredOutputCompatible.bind(this),\n )\n : undefined\n\n const modelOptions = options.modelOptions\n\n return {\n model: options.model,\n temperature: options.temperature,\n max_output_tokens: options.maxTokens,\n top_p: options.topP,\n metadata: options.metadata,\n instructions: options.systemPrompts?.join('\\n'),\n ...modelOptions,\n input,\n tools,\n }\n }\n\n /**\n * Converts ModelMessage[] to Responses API ResponseInput format.\n * Override this in subclasses for provider-specific message format quirks.\n *\n * Key differences from Chat Completions:\n * - Tool results use `function_call_output` type (not `tool` role)\n * - Assistant tool calls are `function_call` objects (not nested in `tool_calls`)\n * - User content uses `input_text`, `input_image`, `input_file` types\n * - System prompts go in `instructions`, not as messages\n */\n protected convertMessagesToInput(\n messages: Array<ModelMessage>,\n ): Responses.ResponseInput {\n const result: Responses.ResponseInput = []\n\n for (const message of messages) {\n // Handle tool messages - convert to FunctionToolCallOutput\n if (message.role === 'tool') {\n result.push({\n type: 'function_call_output',\n call_id: message.toolCallId || '',\n output:\n typeof message.content === 'string'\n ? message.content\n : JSON.stringify(message.content),\n })\n continue\n }\n\n // Handle assistant messages\n if (message.role === 'assistant') {\n // If the assistant message has tool calls, add them as FunctionToolCall objects\n // Responses API expects arguments as a string (JSON string)\n if (message.toolCalls && message.toolCalls.length > 0) {\n for (const toolCall of message.toolCalls) {\n // Keep arguments as string for Responses API\n const argumentsString =\n typeof toolCall.function.arguments === 'string'\n ? toolCall.function.arguments\n : JSON.stringify(toolCall.function.arguments)\n\n result.push({\n type: 'function_call',\n call_id: toolCall.id,\n name: toolCall.function.name,\n arguments: argumentsString,\n })\n }\n }\n\n // Add the assistant's text message if there is content\n if (message.content) {\n const contentStr = this.extractTextContent(message.content)\n if (contentStr) {\n result.push({\n type: 'message',\n role: 'assistant',\n content: contentStr,\n })\n }\n }\n\n continue\n }\n\n // Handle user messages (default case) — support multimodal content\n const contentParts = this.normalizeContent(message.content)\n const inputContent: Array<Responses.ResponseInputContent> = []\n\n for (const part of contentParts) {\n inputContent.push(this.convertContentPartToInput(part))\n }\n\n // If no content parts, add empty text\n if (inputContent.length === 0) {\n inputContent.push({ type: 'input_text', text: '' })\n }\n\n result.push({\n type: 'message',\n role: 'user',\n content: inputContent,\n })\n }\n\n return result\n }\n\n /**\n * Converts a ContentPart to Responses API input content item.\n * Handles text, image, and audio content parts.\n * Override this in subclasses for additional content types or provider-specific metadata.\n */\n protected convertContentPartToInput(\n part: ContentPart,\n ): Responses.ResponseInputContent {\n switch (part.type) {\n case 'text':\n return {\n type: 'input_text',\n text: part.content,\n }\n case 'image': {\n const imageMetadata = part.metadata as\n | { detail?: 'auto' | 'low' | 'high' }\n | undefined\n if (part.source.type === 'url') {\n return {\n type: 'input_image',\n image_url: part.source.value,\n detail: imageMetadata?.detail || 'auto',\n }\n }\n // For base64 data, construct a data URI using the mimeType from source\n const imageValue = part.source.value\n const imageUrl = imageValue.startsWith('data:')\n ? imageValue\n : `data:${part.source.mimeType};base64,${imageValue}`\n return {\n type: 'input_image',\n image_url: imageUrl,\n detail: imageMetadata?.detail || 'auto',\n }\n }\n case 'audio': {\n if (part.source.type === 'url') {\n return {\n type: 'input_file',\n file_url: part.source.value,\n }\n }\n return {\n type: 'input_file',\n file_data: part.source.value,\n }\n }\n\n default:\n throw new Error(`Unsupported content part type: ${part.type}`)\n }\n }\n\n /**\n * Normalizes message content to an array of ContentPart.\n * Handles backward compatibility with string content.\n */\n protected normalizeContent(\n content: string | null | Array<ContentPart>,\n ): Array<ContentPart> {\n if (content === null) {\n return []\n }\n if (typeof content === 'string') {\n return [{ type: 'text', content: content }]\n }\n return content\n }\n\n /**\n * Extracts text content from a content value that may be string, null, or ContentPart array.\n */\n protected extractTextContent(\n content: string | null | Array<ContentPart>,\n ): string {\n if (content === null) {\n return ''\n }\n if (typeof content === 'string') {\n return content\n }\n // It's an array of ContentPart\n return content\n .filter((p) => p.type === 'text')\n .map((p) => p.content)\n .join('')\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import { Tool } from '@tanstack/ai';
2
+ /**
3
+ * Responses API function tool format.
4
+ * This is distinct from the Chat Completions API tool format.
5
+ *
6
+ * The Responses API uses a flatter structure:
7
+ * { type: 'function', name: string, description?: string, parameters: object, strict?: boolean }
8
+ *
9
+ * vs. Chat Completions:
10
+ * { type: 'function', function: { name, description, parameters }, strict?: boolean }
11
+ */
12
+ export interface ResponsesFunctionTool {
13
+ type: 'function';
14
+ name: string;
15
+ description?: string | null;
16
+ parameters: Record<string, any> | null;
17
+ strict: boolean | null;
18
+ }
19
+ /**
20
+ * Converts a standard Tool to the Responses API FunctionTool format.
21
+ *
22
+ * Tool schemas are already converted to JSON Schema in the ai layer.
23
+ * We apply OpenAI-compatible transformations for strict mode:
24
+ * - All properties in required array
25
+ * - Optional fields made nullable
26
+ * - additionalProperties: false
27
+ *
28
+ * This enables strict mode for all tools automatically.
29
+ */
30
+ export declare function convertFunctionToolToResponsesFormat(tool: Tool, schemaConverter?: (schema: Record<string, any>, required: Array<string>) => Record<string, any>): ResponsesFunctionTool;
31
+ /**
32
+ * Converts an array of standard Tools to Responses API format.
33
+ * The Responses API primarily supports function tools at the base level.
34
+ */
35
+ export declare function convertToolsToResponsesFormat(tools: Array<Tool>, schemaConverter?: (schema: Record<string, any>, required: Array<string>) => Record<string, any>): Array<ResponsesFunctionTool>;
@@ -0,0 +1,27 @@
1
+ import { makeStructuredOutputCompatible } from "../utils/schema-converter.js";
2
+ function convertFunctionToolToResponsesFormat(tool, schemaConverter = makeStructuredOutputCompatible) {
3
+ const inputSchema = tool.inputSchema ?? {
4
+ type: "object",
5
+ properties: {},
6
+ required: []
7
+ };
8
+ const jsonSchema = schemaConverter(inputSchema, inputSchema.required || []);
9
+ jsonSchema.additionalProperties = false;
10
+ return {
11
+ type: "function",
12
+ name: tool.name,
13
+ description: tool.description,
14
+ parameters: jsonSchema,
15
+ strict: true
16
+ };
17
+ }
18
+ function convertToolsToResponsesFormat(tools, schemaConverter) {
19
+ return tools.map(
20
+ (tool) => convertFunctionToolToResponsesFormat(tool, schemaConverter)
21
+ );
22
+ }
23
+ export {
24
+ convertFunctionToolToResponsesFormat,
25
+ convertToolsToResponsesFormat
26
+ };
27
+ //# sourceMappingURL=responses-tool-converter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"responses-tool-converter.js","sources":["../../../src/adapters/responses-tool-converter.ts"],"sourcesContent":["import { makeStructuredOutputCompatible } from '../utils/schema-converter'\nimport type { JSONSchema, Tool } from '@tanstack/ai'\n\n/**\n * Responses API function tool format.\n * This is distinct from the Chat Completions API tool format.\n *\n * The Responses API uses a flatter structure:\n * { type: 'function', name: string, description?: string, parameters: object, strict?: boolean }\n *\n * vs. Chat Completions:\n * { type: 'function', function: { name, description, parameters }, strict?: boolean }\n */\nexport interface ResponsesFunctionTool {\n type: 'function'\n name: string\n description?: string | null\n parameters: Record<string, any> | null\n strict: boolean | null\n}\n\n/**\n * Converts a standard Tool to the Responses API FunctionTool format.\n *\n * Tool schemas are already converted to JSON Schema in the ai layer.\n * We apply OpenAI-compatible transformations for strict mode:\n * - All properties in required array\n * - Optional fields made nullable\n * - additionalProperties: false\n *\n * This enables strict mode for all tools automatically.\n */\nexport function convertFunctionToolToResponsesFormat(\n tool: Tool,\n schemaConverter: (\n schema: Record<string, any>,\n required: Array<string>,\n ) => Record<string, any> = makeStructuredOutputCompatible,\n): ResponsesFunctionTool {\n const inputSchema = (tool.inputSchema ?? {\n type: 'object',\n properties: {},\n required: [],\n }) as JSONSchema\n\n const jsonSchema = schemaConverter(inputSchema, inputSchema.required || [])\n\n // Ensure additionalProperties is false for strict mode\n jsonSchema.additionalProperties = false\n\n return {\n type: 'function',\n name: tool.name,\n description: tool.description,\n parameters: jsonSchema,\n strict: true,\n }\n}\n\n/**\n * Converts an array of standard Tools to Responses API format.\n * The Responses API primarily supports function tools at the base level.\n */\nexport function convertToolsToResponsesFormat(\n tools: Array<Tool>,\n schemaConverter?: (\n schema: Record<string, any>,\n required: Array<string>,\n ) => Record<string, any>,\n): Array<ResponsesFunctionTool> {\n return tools.map((tool) =>\n convertFunctionToolToResponsesFormat(tool, schemaConverter),\n )\n}\n"],"names":[],"mappings":";AAgCO,SAAS,qCACd,MACA,kBAG2B,gCACJ;AACvB,QAAM,cAAe,KAAK,eAAe;AAAA,IACvC,MAAM;AAAA,IACN,YAAY,CAAA;AAAA,IACZ,UAAU,CAAA;AAAA,EAAC;AAGb,QAAM,aAAa,gBAAgB,aAAa,YAAY,YAAY,CAAA,CAAE;AAG1E,aAAW,uBAAuB;AAElC,SAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM,KAAK;AAAA,IACX,aAAa,KAAK;AAAA,IAClB,YAAY;AAAA,IACZ,QAAQ;AAAA,EAAA;AAEZ;AAMO,SAAS,8BACd,OACA,iBAI8B;AAC9B,SAAO,MAAM;AAAA,IAAI,CAAC,SAChB,qCAAqC,MAAM,eAAe;AAAA,EAAA;AAE9D;"}
@@ -0,0 +1,28 @@
1
+ import { BaseSummarizeAdapter } from '@tanstack/ai/adapters';
2
+ import { StreamChunk, SummarizationOptions, SummarizationResult, TextOptions } from '@tanstack/ai';
3
+ /**
4
+ * Minimal interface for a text adapter that supports chatStream.
5
+ * This allows the summarize adapter to work with any OpenAI-compatible
6
+ * text adapter without tight coupling to a specific implementation.
7
+ */
8
+ export interface ChatStreamCapable<TProviderOptions extends object> {
9
+ chatStream: (options: TextOptions<TProviderOptions>) => AsyncIterable<StreamChunk>;
10
+ }
11
+ /**
12
+ * OpenAI-Compatible Summarize Adapter
13
+ *
14
+ * A thin wrapper around a text adapter that adds summarization-specific prompting.
15
+ * Delegates all API calls to the provided text adapter.
16
+ *
17
+ * Subclasses or instantiators provide a text adapter (or factory) at construction
18
+ * time, allowing any OpenAI-compatible provider to get summarization for free by
19
+ * reusing its text adapter.
20
+ */
21
+ export declare class OpenAICompatibleSummarizeAdapter<TModel extends string, TProviderOptions extends object = Record<string, any>> extends BaseSummarizeAdapter<TModel, TProviderOptions> {
22
+ readonly name: string;
23
+ private textAdapter;
24
+ constructor(textAdapter: ChatStreamCapable<TProviderOptions>, model: TModel, name?: string);
25
+ summarize(options: SummarizationOptions): Promise<SummarizationResult>;
26
+ summarizeStream(options: SummarizationOptions): AsyncIterable<StreamChunk>;
27
+ protected buildSummarizationPrompt(options: SummarizationOptions): string;
28
+ }
@@ -0,0 +1,74 @@
1
+ import { BaseSummarizeAdapter } from "@tanstack/ai/adapters";
2
+ class OpenAICompatibleSummarizeAdapter extends BaseSummarizeAdapter {
3
+ constructor(textAdapter, model, name = "openai-compatible") {
4
+ super({}, model);
5
+ this.name = name;
6
+ this.textAdapter = textAdapter;
7
+ }
8
+ async summarize(options) {
9
+ const systemPrompt = this.buildSummarizationPrompt(options);
10
+ let summary = "";
11
+ const id = "";
12
+ let model = options.model;
13
+ let usage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
14
+ for await (const chunk of this.textAdapter.chatStream({
15
+ model: options.model,
16
+ messages: [{ role: "user", content: options.text }],
17
+ systemPrompts: [systemPrompt],
18
+ maxTokens: options.maxLength,
19
+ temperature: 0.3
20
+ })) {
21
+ if (chunk.type === "TEXT_MESSAGE_CONTENT") {
22
+ if (chunk.content) {
23
+ summary = chunk.content;
24
+ } else {
25
+ summary += chunk.delta;
26
+ }
27
+ model = chunk.model || model;
28
+ }
29
+ if (chunk.type === "RUN_FINISHED") {
30
+ if (chunk.usage) {
31
+ usage = chunk.usage;
32
+ }
33
+ }
34
+ }
35
+ return { id, model, summary, usage };
36
+ }
37
+ async *summarizeStream(options) {
38
+ const systemPrompt = this.buildSummarizationPrompt(options);
39
+ yield* this.textAdapter.chatStream({
40
+ model: options.model,
41
+ messages: [{ role: "user", content: options.text }],
42
+ systemPrompts: [systemPrompt],
43
+ maxTokens: options.maxLength,
44
+ temperature: 0.3
45
+ });
46
+ }
47
+ buildSummarizationPrompt(options) {
48
+ let prompt = "You are a professional summarizer. ";
49
+ switch (options.style) {
50
+ case "bullet-points":
51
+ prompt += "Provide a summary in bullet point format. ";
52
+ break;
53
+ case "paragraph":
54
+ prompt += "Provide a summary in paragraph format. ";
55
+ break;
56
+ case "concise":
57
+ prompt += "Provide a very concise summary in 1-2 sentences. ";
58
+ break;
59
+ default:
60
+ prompt += "Provide a clear and concise summary. ";
61
+ }
62
+ if (options.focus && options.focus.length > 0) {
63
+ prompt += `Focus on the following aspects: ${options.focus.join(", ")}. `;
64
+ }
65
+ if (options.maxLength) {
66
+ prompt += `Keep the summary under ${options.maxLength} tokens. `;
67
+ }
68
+ return prompt;
69
+ }
70
+ }
71
+ export {
72
+ OpenAICompatibleSummarizeAdapter
73
+ };
74
+ //# sourceMappingURL=summarize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summarize.js","sources":["../../../src/adapters/summarize.ts"],"sourcesContent":["import { BaseSummarizeAdapter } from '@tanstack/ai/adapters'\nimport type {\n StreamChunk,\n SummarizationOptions,\n SummarizationResult,\n TextOptions,\n} from '@tanstack/ai'\n\n/**\n * Minimal interface for a text adapter that supports chatStream.\n * This allows the summarize adapter to work with any OpenAI-compatible\n * text adapter without tight coupling to a specific implementation.\n */\nexport interface ChatStreamCapable<TProviderOptions extends object> {\n chatStream: (\n options: TextOptions<TProviderOptions>,\n ) => AsyncIterable<StreamChunk>\n}\n\n/**\n * OpenAI-Compatible Summarize Adapter\n *\n * A thin wrapper around a text adapter that adds summarization-specific prompting.\n * Delegates all API calls to the provided text adapter.\n *\n * Subclasses or instantiators provide a text adapter (or factory) at construction\n * time, allowing any OpenAI-compatible provider to get summarization for free by\n * reusing its text adapter.\n */\nexport class OpenAICompatibleSummarizeAdapter<\n TModel extends string,\n TProviderOptions extends object = Record<string, any>,\n> extends BaseSummarizeAdapter<TModel, TProviderOptions> {\n readonly name: string\n\n private textAdapter: ChatStreamCapable<TProviderOptions>\n\n constructor(\n textAdapter: ChatStreamCapable<TProviderOptions>,\n model: TModel,\n name: string = 'openai-compatible',\n ) {\n super({}, model)\n this.name = name\n this.textAdapter = textAdapter\n }\n\n async summarize(options: SummarizationOptions): Promise<SummarizationResult> {\n const systemPrompt = this.buildSummarizationPrompt(options)\n\n let summary = ''\n const id = ''\n let model = options.model\n let usage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 }\n\n for await (const chunk of this.textAdapter.chatStream({\n model: options.model as TModel,\n messages: [{ role: 'user', content: options.text }],\n systemPrompts: [systemPrompt],\n maxTokens: options.maxLength,\n temperature: 0.3,\n } as TextOptions<TProviderOptions>)) {\n if (chunk.type === 'TEXT_MESSAGE_CONTENT') {\n if (chunk.content) {\n summary = chunk.content\n } else {\n summary += chunk.delta\n }\n model = chunk.model || model\n }\n if (chunk.type === 'RUN_FINISHED') {\n if (chunk.usage) {\n usage = chunk.usage\n }\n }\n }\n\n return { id, model, summary, usage }\n }\n\n async *summarizeStream(\n options: SummarizationOptions,\n ): AsyncIterable<StreamChunk> {\n const systemPrompt = this.buildSummarizationPrompt(options)\n\n yield* this.textAdapter.chatStream({\n model: options.model as TModel,\n messages: [{ role: 'user', content: options.text }],\n systemPrompts: [systemPrompt],\n maxTokens: options.maxLength,\n temperature: 0.3,\n } as TextOptions<TProviderOptions>)\n }\n\n protected buildSummarizationPrompt(options: SummarizationOptions): string {\n let prompt = 'You are a professional summarizer. '\n\n switch (options.style) {\n case 'bullet-points':\n prompt += 'Provide a summary in bullet point format. '\n break\n case 'paragraph':\n prompt += 'Provide a summary in paragraph format. '\n break\n case 'concise':\n prompt += 'Provide a very concise summary in 1-2 sentences. '\n break\n default:\n prompt += 'Provide a clear and concise summary. '\n }\n\n if (options.focus && options.focus.length > 0) {\n prompt += `Focus on the following aspects: ${options.focus.join(', ')}. `\n }\n\n if (options.maxLength) {\n prompt += `Keep the summary under ${options.maxLength} tokens. `\n }\n\n return prompt\n }\n}\n"],"names":[],"mappings":";AA6BO,MAAM,yCAGH,qBAA+C;AAAA,EAKvD,YACE,aACA,OACA,OAAe,qBACf;AACA,UAAM,CAAA,GAAI,KAAK;AACf,SAAK,OAAO;AACZ,SAAK,cAAc;AAAA,EACrB;AAAA,EAEA,MAAM,UAAU,SAA6D;AAC3E,UAAM,eAAe,KAAK,yBAAyB,OAAO;AAE1D,QAAI,UAAU;AACd,UAAM,KAAK;AACX,QAAI,QAAQ,QAAQ;AACpB,QAAI,QAAQ,EAAE,cAAc,GAAG,kBAAkB,GAAG,aAAa,EAAA;AAEjE,qBAAiB,SAAS,KAAK,YAAY,WAAW;AAAA,MACpD,OAAO,QAAQ;AAAA,MACf,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,QAAQ,MAAM;AAAA,MAClD,eAAe,CAAC,YAAY;AAAA,MAC5B,WAAW,QAAQ;AAAA,MACnB,aAAa;AAAA,IAAA,CACmB,GAAG;AACnC,UAAI,MAAM,SAAS,wBAAwB;AACzC,YAAI,MAAM,SAAS;AACjB,oBAAU,MAAM;AAAA,QAClB,OAAO;AACL,qBAAW,MAAM;AAAA,QACnB;AACA,gBAAQ,MAAM,SAAS;AAAA,MACzB;AACA,UAAI,MAAM,SAAS,gBAAgB;AACjC,YAAI,MAAM,OAAO;AACf,kBAAQ,MAAM;AAAA,QAChB;AAAA,MACF;AAAA,IACF;AAEA,WAAO,EAAE,IAAI,OAAO,SAAS,MAAA;AAAA,EAC/B;AAAA,EAEA,OAAO,gBACL,SAC4B;AAC5B,UAAM,eAAe,KAAK,yBAAyB,OAAO;AAE1D,WAAO,KAAK,YAAY,WAAW;AAAA,MACjC,OAAO,QAAQ;AAAA,MACf,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,QAAQ,MAAM;AAAA,MAClD,eAAe,CAAC,YAAY;AAAA,MAC5B,WAAW,QAAQ;AAAA,MACnB,aAAa;AAAA,IAAA,CACmB;AAAA,EACpC;AAAA,EAEU,yBAAyB,SAAuC;AACxE,QAAI,SAAS;AAEb,YAAQ,QAAQ,OAAA;AAAA,MACd,KAAK;AACH,kBAAU;AACV;AAAA,MACF,KAAK;AACH,kBAAU;AACV;AAAA,MACF,KAAK;AACH,kBAAU;AACV;AAAA,MACF;AACE,kBAAU;AAAA,IAAA;AAGd,QAAI,QAAQ,SAAS,QAAQ,MAAM,SAAS,GAAG;AAC7C,gBAAU,mCAAmC,QAAQ,MAAM,KAAK,IAAI,CAAC;AAAA,IACvE;AAEA,QAAI,QAAQ,WAAW;AACrB,gBAAU,0BAA0B,QAAQ,SAAS;AAAA,IACvD;AAEA,WAAO;AAAA,EACT;AACF;"}
@@ -0,0 +1,39 @@
1
+ import { BaseTranscriptionAdapter } from '@tanstack/ai/adapters';
2
+ import { TranscriptionOptions, TranscriptionResult } from '@tanstack/ai';
3
+ import { default as OpenAI_SDK } from 'openai';
4
+ import { OpenAICompatibleClientConfig } from '../types/config.js';
5
+ /**
6
+ * OpenAI-Compatible Transcription (Speech-to-Text) Adapter
7
+ *
8
+ * A generalized base class for providers that implement OpenAI-compatible audio
9
+ * transcription APIs. Providers can extend this class and only need to:
10
+ * - Set `baseURL` in the config
11
+ * - Lock the generic type parameters to provider-specific types
12
+ * - Override audio handling or response mapping methods as needed
13
+ *
14
+ * All methods that handle audio input or map response formats are `protected`
15
+ * so subclasses can override them.
16
+ */
17
+ export declare class OpenAICompatibleTranscriptionAdapter<TModel extends string, TProviderOptions extends object = Record<string, any>> extends BaseTranscriptionAdapter<TModel, TProviderOptions> {
18
+ readonly name: string;
19
+ protected client: OpenAI_SDK;
20
+ constructor(config: OpenAICompatibleClientConfig, model: TModel, name?: string);
21
+ transcribe(options: TranscriptionOptions<TProviderOptions>): Promise<TranscriptionResult>;
22
+ protected prepareAudioFile(audio: string | File | Blob | ArrayBuffer): File;
23
+ /**
24
+ * Checks that the global `File` constructor is available.
25
+ * Throws a descriptive error in environments that lack it (e.g. Node < 20).
26
+ */
27
+ private ensureFileSupport;
28
+ /**
29
+ * Decodes a base64 string to an ArrayBuffer.
30
+ * Uses `atob` when available, falling back to `Buffer.from` in Node.js.
31
+ */
32
+ private decodeBase64;
33
+ /**
34
+ * Whether the adapter should default to verbose_json when no response format is specified.
35
+ * Override in provider-specific subclasses for model-specific behavior.
36
+ */
37
+ protected shouldDefaultToVerbose(_model: string): boolean;
38
+ protected mapResponseFormat(format?: 'json' | 'text' | 'srt' | 'verbose_json' | 'vtt'): OpenAI_SDK.Audio.TranscriptionCreateParams['response_format'];
39
+ }
@@ -0,0 +1,139 @@
1
+ import { BaseTranscriptionAdapter } from "@tanstack/ai/adapters";
2
+ import { generateId } from "@tanstack/ai-utils";
3
+ import { createOpenAICompatibleClient } from "../utils/client.js";
4
+ class OpenAICompatibleTranscriptionAdapter extends BaseTranscriptionAdapter {
5
+ constructor(config, model, name = "openai-compatible") {
6
+ super(config, model);
7
+ this.name = name;
8
+ this.client = createOpenAICompatibleClient(config);
9
+ }
10
+ async transcribe(options) {
11
+ const { model, audio, language, prompt, responseFormat, modelOptions } = options;
12
+ const file = this.prepareAudioFile(audio);
13
+ const request = {
14
+ model,
15
+ file,
16
+ language,
17
+ prompt,
18
+ response_format: this.mapResponseFormat(responseFormat),
19
+ ...modelOptions
20
+ };
21
+ const useVerbose = responseFormat === "verbose_json" || !responseFormat && this.shouldDefaultToVerbose(model);
22
+ if (useVerbose) {
23
+ const response = await this.client.audio.transcriptions.create({
24
+ ...request,
25
+ response_format: "verbose_json"
26
+ });
27
+ return {
28
+ id: generateId(this.name),
29
+ model,
30
+ text: response.text,
31
+ language: response.language,
32
+ duration: response.duration,
33
+ segments: response.segments?.map(
34
+ (seg) => ({
35
+ id: seg.id,
36
+ start: seg.start,
37
+ end: seg.end,
38
+ text: seg.text,
39
+ confidence: seg.avg_logprob ? Math.exp(seg.avg_logprob) : void 0
40
+ })
41
+ ),
42
+ words: response.words?.map((w) => ({
43
+ word: w.word,
44
+ start: w.start,
45
+ end: w.end
46
+ }))
47
+ };
48
+ } else {
49
+ const response = await this.client.audio.transcriptions.create(request);
50
+ return {
51
+ id: generateId(this.name),
52
+ model,
53
+ text: typeof response === "string" ? response : response.text,
54
+ language
55
+ };
56
+ }
57
+ }
58
+ prepareAudioFile(audio) {
59
+ if (typeof File !== "undefined" && audio instanceof File) {
60
+ return audio;
61
+ }
62
+ if (typeof Blob !== "undefined" && audio instanceof Blob) {
63
+ this.ensureFileSupport();
64
+ return new File([audio], "audio.mp3", {
65
+ type: audio.type || "audio/mpeg"
66
+ });
67
+ }
68
+ if (typeof ArrayBuffer !== "undefined" && audio instanceof ArrayBuffer) {
69
+ this.ensureFileSupport();
70
+ return new File([audio], "audio.mp3", { type: "audio/mpeg" });
71
+ }
72
+ if (typeof audio === "string") {
73
+ this.ensureFileSupport();
74
+ if (audio.startsWith("data:")) {
75
+ const parts = audio.split(",");
76
+ const header = parts[0];
77
+ const base64Data = parts[1] || "";
78
+ const mimeMatch = header?.match(/data:([^;]+)/);
79
+ const mimeType = mimeMatch?.[1] || "audio/mpeg";
80
+ const bytes2 = this.decodeBase64(base64Data);
81
+ const extension = mimeType.split("/")[1] || "mp3";
82
+ return new File([bytes2], `audio.${extension}`, { type: mimeType });
83
+ }
84
+ const bytes = this.decodeBase64(audio);
85
+ return new File([bytes], "audio.mp3", { type: "audio/mpeg" });
86
+ }
87
+ throw new Error("Invalid audio input type");
88
+ }
89
+ /**
90
+ * Checks that the global `File` constructor is available.
91
+ * Throws a descriptive error in environments that lack it (e.g. Node < 20).
92
+ */
93
+ ensureFileSupport() {
94
+ if (typeof File === "undefined") {
95
+ throw new Error(
96
+ "`File` is not available in this environment. Use Node.js 20 or newer, or pass a File object directly."
97
+ );
98
+ }
99
+ }
100
+ /**
101
+ * Decodes a base64 string to an ArrayBuffer.
102
+ * Uses `atob` when available, falling back to `Buffer.from` in Node.js.
103
+ */
104
+ decodeBase64(base64) {
105
+ if (typeof atob === "function") {
106
+ const binaryStr = atob(base64);
107
+ const bytes = new Uint8Array(binaryStr.length);
108
+ for (let i = 0; i < binaryStr.length; i++) {
109
+ bytes[i] = binaryStr.charCodeAt(i);
110
+ }
111
+ return bytes.buffer;
112
+ }
113
+ if (typeof Buffer !== "undefined") {
114
+ const buf = Buffer.from(base64, "base64");
115
+ return buf.buffer.slice(
116
+ buf.byteOffset,
117
+ buf.byteOffset + buf.byteLength
118
+ );
119
+ }
120
+ throw new Error(
121
+ "Neither `atob` nor `Buffer` is available in this environment. Use a File, Blob, or ArrayBuffer input instead."
122
+ );
123
+ }
124
+ /**
125
+ * Whether the adapter should default to verbose_json when no response format is specified.
126
+ * Override in provider-specific subclasses for model-specific behavior.
127
+ */
128
+ shouldDefaultToVerbose(_model) {
129
+ return false;
130
+ }
131
+ mapResponseFormat(format) {
132
+ if (!format) return "json";
133
+ return format;
134
+ }
135
+ }
136
+ export {
137
+ OpenAICompatibleTranscriptionAdapter
138
+ };
139
+ //# sourceMappingURL=transcription.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcription.js","sources":["../../../src/adapters/transcription.ts"],"sourcesContent":["import { BaseTranscriptionAdapter } from '@tanstack/ai/adapters'\nimport { generateId } from '@tanstack/ai-utils'\nimport { createOpenAICompatibleClient } from '../utils/client'\nimport type {\n TranscriptionOptions,\n TranscriptionResult,\n TranscriptionSegment,\n} from '@tanstack/ai'\nimport type OpenAI_SDK from 'openai'\nimport type { OpenAICompatibleClientConfig } from '../types/config'\n\n/**\n * OpenAI-Compatible Transcription (Speech-to-Text) Adapter\n *\n * A generalized base class for providers that implement OpenAI-compatible audio\n * transcription APIs. Providers can extend this class and only need to:\n * - Set `baseURL` in the config\n * - Lock the generic type parameters to provider-specific types\n * - Override audio handling or response mapping methods as needed\n *\n * All methods that handle audio input or map response formats are `protected`\n * so subclasses can override them.\n */\nexport class OpenAICompatibleTranscriptionAdapter<\n TModel extends string,\n TProviderOptions extends object = Record<string, any>,\n> extends BaseTranscriptionAdapter<TModel, TProviderOptions> {\n readonly name: string\n\n protected client: OpenAI_SDK\n\n constructor(\n config: OpenAICompatibleClientConfig,\n model: TModel,\n name: string = 'openai-compatible',\n ) {\n super(config, model)\n this.name = name\n this.client = createOpenAICompatibleClient(config)\n }\n\n async transcribe(\n options: TranscriptionOptions<TProviderOptions>,\n ): Promise<TranscriptionResult> {\n const { model, audio, language, prompt, responseFormat, modelOptions } =\n options\n\n // Convert audio input to File object\n const file = this.prepareAudioFile(audio)\n\n // Build request\n const request: OpenAI_SDK.Audio.TranscriptionCreateParams = {\n model,\n file,\n language,\n prompt,\n response_format: this.mapResponseFormat(responseFormat),\n ...modelOptions,\n }\n\n // Call API - use verbose_json to get timestamps when available\n const useVerbose =\n responseFormat === 'verbose_json' ||\n (!responseFormat && this.shouldDefaultToVerbose(model))\n\n if (useVerbose) {\n const response = await this.client.audio.transcriptions.create({\n ...request,\n response_format: 'verbose_json',\n })\n\n return {\n id: generateId(this.name),\n model,\n text: response.text,\n language: response.language,\n duration: response.duration,\n segments: response.segments?.map(\n (seg): TranscriptionSegment => ({\n id: seg.id,\n start: seg.start,\n end: seg.end,\n text: seg.text,\n confidence: seg.avg_logprob ? Math.exp(seg.avg_logprob) : undefined,\n }),\n ),\n words: response.words?.map((w) => ({\n word: w.word,\n start: w.start,\n end: w.end,\n })),\n }\n } else {\n const response = await this.client.audio.transcriptions.create(request)\n\n return {\n id: generateId(this.name),\n model,\n text: typeof response === 'string' ? response : response.text,\n language,\n }\n }\n }\n\n protected prepareAudioFile(audio: string | File | Blob | ArrayBuffer): File {\n // If already a File, return it\n if (typeof File !== 'undefined' && audio instanceof File) {\n return audio\n }\n\n // If Blob, convert to File\n if (typeof Blob !== 'undefined' && audio instanceof Blob) {\n this.ensureFileSupport()\n return new File([audio], 'audio.mp3', {\n type: audio.type || 'audio/mpeg',\n })\n }\n\n // If ArrayBuffer, convert to File\n if (typeof ArrayBuffer !== 'undefined' && audio instanceof ArrayBuffer) {\n this.ensureFileSupport()\n return new File([audio], 'audio.mp3', { type: 'audio/mpeg' })\n }\n\n // If base64 string, decode and convert to File\n if (typeof audio === 'string') {\n this.ensureFileSupport()\n\n // Check if it's a data URL\n if (audio.startsWith('data:')) {\n const parts = audio.split(',')\n const header = parts[0]\n const base64Data = parts[1] || ''\n const mimeMatch = header?.match(/data:([^;]+)/)\n const mimeType = mimeMatch?.[1] || 'audio/mpeg'\n const bytes = this.decodeBase64(base64Data)\n const extension = mimeType.split('/')[1] || 'mp3'\n return new File([bytes], `audio.${extension}`, { type: mimeType })\n }\n\n // Assume raw base64\n const bytes = this.decodeBase64(audio)\n return new File([bytes], 'audio.mp3', { type: 'audio/mpeg' })\n }\n\n throw new Error('Invalid audio input type')\n }\n\n /**\n * Checks that the global `File` constructor is available.\n * Throws a descriptive error in environments that lack it (e.g. Node < 20).\n */\n private ensureFileSupport(): void {\n if (typeof File === 'undefined') {\n throw new Error(\n '`File` is not available in this environment. ' +\n 'Use Node.js 20 or newer, or pass a File object directly.',\n )\n }\n }\n\n /**\n * Decodes a base64 string to an ArrayBuffer.\n * Uses `atob` when available, falling back to `Buffer.from` in Node.js.\n */\n private decodeBase64(base64: string): ArrayBuffer {\n if (typeof atob === 'function') {\n const binaryStr = atob(base64)\n const bytes = new Uint8Array(binaryStr.length)\n for (let i = 0; i < binaryStr.length; i++) {\n bytes[i] = binaryStr.charCodeAt(i)\n }\n return bytes.buffer\n }\n\n // Node.js fallback\n if (typeof Buffer !== 'undefined') {\n const buf = Buffer.from(base64, 'base64')\n return buf.buffer.slice(\n buf.byteOffset,\n buf.byteOffset + buf.byteLength,\n )\n }\n\n throw new Error(\n 'Neither `atob` nor `Buffer` is available in this environment. ' +\n 'Use a File, Blob, or ArrayBuffer input instead.',\n )\n }\n\n /**\n * Whether the adapter should default to verbose_json when no response format is specified.\n * Override in provider-specific subclasses for model-specific behavior.\n */\n protected shouldDefaultToVerbose(_model: string): boolean {\n return false\n }\n\n protected mapResponseFormat(\n format?: 'json' | 'text' | 'srt' | 'verbose_json' | 'vtt',\n ): OpenAI_SDK.Audio.TranscriptionCreateParams['response_format'] {\n if (!format) return 'json'\n return format as OpenAI_SDK.Audio.TranscriptionCreateParams['response_format']\n }\n}\n"],"names":["bytes"],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import { BaseTTSAdapter } from '@tanstack/ai/adapters';
2
+ import { TTSOptions, TTSResult } from '@tanstack/ai';
3
+ import { default as OpenAI_SDK } from 'openai';
4
+ import { OpenAICompatibleClientConfig } from '../types/config.js';
5
+ /**
6
+ * OpenAI-Compatible Text-to-Speech Adapter
7
+ *
8
+ * A generalized base class for providers that implement OpenAI-compatible TTS APIs.
9
+ * Providers can extend this class and only need to:
10
+ * - Set `baseURL` in the config
11
+ * - Lock the generic type parameters to provider-specific types
12
+ * - Override validation methods or request building for provider-specific constraints
13
+ *
14
+ * All methods that validate inputs or build requests are `protected` so subclasses
15
+ * can override them.
16
+ */
17
+ export declare class OpenAICompatibleTTSAdapter<TModel extends string, TProviderOptions extends object = Record<string, any>> extends BaseTTSAdapter<TModel, TProviderOptions> {
18
+ readonly name: string;
19
+ protected client: OpenAI_SDK;
20
+ constructor(config: OpenAICompatibleClientConfig, model: TModel, name?: string);
21
+ generateSpeech(options: TTSOptions<TProviderOptions>): Promise<TTSResult>;
22
+ protected validateAudioInput(text: string): void;
23
+ protected validateSpeed(speed?: number): void;
24
+ protected validateInstructions(_model: string, _modelOptions?: TProviderOptions): void;
25
+ protected getContentType(format: string): string;
26
+ }
@@ -0,0 +1,65 @@
1
+ import { BaseTTSAdapter } from "@tanstack/ai/adapters";
2
+ import { generateId } from "@tanstack/ai-utils";
3
+ import { createOpenAICompatibleClient } from "../utils/client.js";
4
+ class OpenAICompatibleTTSAdapter extends BaseTTSAdapter {
5
+ constructor(config, model, name = "openai-compatible") {
6
+ super(config, model);
7
+ this.name = name;
8
+ this.client = createOpenAICompatibleClient(config);
9
+ }
10
+ async generateSpeech(options) {
11
+ const { model, text, voice, format, speed, modelOptions } = options;
12
+ this.validateAudioInput(text);
13
+ this.validateSpeed(speed);
14
+ this.validateInstructions(model, modelOptions);
15
+ const request = {
16
+ model,
17
+ input: text,
18
+ voice: voice || "alloy",
19
+ response_format: format,
20
+ speed,
21
+ ...modelOptions
22
+ };
23
+ const response = await this.client.audio.speech.create(request);
24
+ const arrayBuffer = await response.arrayBuffer();
25
+ const base64 = Buffer.from(arrayBuffer).toString("base64");
26
+ const outputFormat = request.response_format || "mp3";
27
+ const contentType = this.getContentType(outputFormat);
28
+ return {
29
+ id: generateId(this.name),
30
+ model,
31
+ audio: base64,
32
+ format: outputFormat,
33
+ contentType
34
+ };
35
+ }
36
+ validateAudioInput(text) {
37
+ if (text.length > 4096) {
38
+ throw new Error("Input text exceeds maximum length of 4096 characters.");
39
+ }
40
+ }
41
+ validateSpeed(speed) {
42
+ if (speed !== void 0) {
43
+ if (speed < 0.25 || speed > 4) {
44
+ throw new Error("Speed must be between 0.25 and 4.0.");
45
+ }
46
+ }
47
+ }
48
+ validateInstructions(_model, _modelOptions) {
49
+ }
50
+ getContentType(format) {
51
+ const contentTypes = {
52
+ mp3: "audio/mpeg",
53
+ opus: "audio/opus",
54
+ aac: "audio/aac",
55
+ flac: "audio/flac",
56
+ wav: "audio/wav",
57
+ pcm: "audio/pcm"
58
+ };
59
+ return contentTypes[format] || "audio/mpeg";
60
+ }
61
+ }
62
+ export {
63
+ OpenAICompatibleTTSAdapter
64
+ };
65
+ //# sourceMappingURL=tts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tts.js","sources":["../../../src/adapters/tts.ts"],"sourcesContent":["import { BaseTTSAdapter } from '@tanstack/ai/adapters'\nimport { generateId } from '@tanstack/ai-utils'\nimport { createOpenAICompatibleClient } from '../utils/client'\nimport type { TTSOptions, TTSResult } from '@tanstack/ai'\nimport type OpenAI_SDK from 'openai'\nimport type { OpenAICompatibleClientConfig } from '../types/config'\n\n/**\n * OpenAI-Compatible Text-to-Speech Adapter\n *\n * A generalized base class for providers that implement OpenAI-compatible TTS APIs.\n * Providers can extend this class and only need to:\n * - Set `baseURL` in the config\n * - Lock the generic type parameters to provider-specific types\n * - Override validation methods or request building for provider-specific constraints\n *\n * All methods that validate inputs or build requests are `protected` so subclasses\n * can override them.\n */\nexport class OpenAICompatibleTTSAdapter<\n TModel extends string,\n TProviderOptions extends object = Record<string, any>,\n> extends BaseTTSAdapter<TModel, TProviderOptions> {\n readonly name: string\n\n protected client: OpenAI_SDK\n\n constructor(\n config: OpenAICompatibleClientConfig,\n model: TModel,\n name: string = 'openai-compatible',\n ) {\n super(config, model)\n this.name = name\n this.client = createOpenAICompatibleClient(config)\n }\n\n async generateSpeech(\n options: TTSOptions<TProviderOptions>,\n ): Promise<TTSResult> {\n const { model, text, voice, format, speed, modelOptions } = options\n\n // Validate inputs\n this.validateAudioInput(text)\n this.validateSpeed(speed)\n this.validateInstructions(model, modelOptions)\n\n // Build request\n const request: OpenAI_SDK.Audio.SpeechCreateParams = {\n model,\n input: text,\n voice: (voice || 'alloy') as OpenAI_SDK.Audio.SpeechCreateParams['voice'],\n response_format: format,\n speed,\n ...modelOptions,\n }\n\n // Call API\n const response = await this.client.audio.speech.create(request)\n\n // Convert response to base64\n const arrayBuffer = await response.arrayBuffer()\n const base64 = Buffer.from(arrayBuffer).toString('base64')\n\n const outputFormat = (request.response_format as string) || 'mp3'\n const contentType = this.getContentType(outputFormat)\n\n return {\n id: generateId(this.name),\n model,\n audio: base64,\n format: outputFormat,\n contentType,\n }\n }\n\n protected validateAudioInput(text: string): void {\n if (text.length > 4096) {\n throw new Error('Input text exceeds maximum length of 4096 characters.')\n }\n }\n\n protected validateSpeed(speed?: number): void {\n if (speed !== undefined) {\n if (speed < 0.25 || speed > 4.0) {\n throw new Error('Speed must be between 0.25 and 4.0.')\n }\n }\n }\n\n protected validateInstructions(\n _model: string,\n _modelOptions?: TProviderOptions,\n ): void {\n // Default: no instructions validation — subclasses can override\n }\n\n protected getContentType(format: string): string {\n const contentTypes: Record<string, string> = {\n mp3: 'audio/mpeg',\n opus: 'audio/opus',\n aac: 'audio/aac',\n flac: 'audio/flac',\n wav: 'audio/wav',\n pcm: 'audio/pcm',\n }\n return contentTypes[format] || 'audio/mpeg'\n }\n}\n"],"names":[],"mappings":";;;AAmBO,MAAM,mCAGH,eAAyC;AAAA,EAKjD,YACE,QACA,OACA,OAAe,qBACf;AACA,UAAM,QAAQ,KAAK;AACnB,SAAK,OAAO;AACZ,SAAK,SAAS,6BAA6B,MAAM;AAAA,EACnD;AAAA,EAEA,MAAM,eACJ,SACoB;AACpB,UAAM,EAAE,OAAO,MAAM,OAAO,QAAQ,OAAO,iBAAiB;AAG5D,SAAK,mBAAmB,IAAI;AAC5B,SAAK,cAAc,KAAK;AACxB,SAAK,qBAAqB,OAAO,YAAY;AAG7C,UAAM,UAA+C;AAAA,MACnD;AAAA,MACA,OAAO;AAAA,MACP,OAAQ,SAAS;AAAA,MACjB,iBAAiB;AAAA,MACjB;AAAA,MACA,GAAG;AAAA,IAAA;AAIL,UAAM,WAAW,MAAM,KAAK,OAAO,MAAM,OAAO,OAAO,OAAO;AAG9D,UAAM,cAAc,MAAM,SAAS,YAAA;AACnC,UAAM,SAAS,OAAO,KAAK,WAAW,EAAE,SAAS,QAAQ;AAEzD,UAAM,eAAgB,QAAQ,mBAA8B;AAC5D,UAAM,cAAc,KAAK,eAAe,YAAY;AAEpD,WAAO;AAAA,MACL,IAAI,WAAW,KAAK,IAAI;AAAA,MACxB;AAAA,MACA,OAAO;AAAA,MACP,QAAQ;AAAA,MACR;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEU,mBAAmB,MAAoB;AAC/C,QAAI,KAAK,SAAS,MAAM;AACtB,YAAM,IAAI,MAAM,uDAAuD;AAAA,IACzE;AAAA,EACF;AAAA,EAEU,cAAc,OAAsB;AAC5C,QAAI,UAAU,QAAW;AACvB,UAAI,QAAQ,QAAQ,QAAQ,GAAK;AAC/B,cAAM,IAAI,MAAM,qCAAqC;AAAA,MACvD;AAAA,IACF;AAAA,EACF;AAAA,EAEU,qBACR,QACA,eACM;AAAA,EAER;AAAA,EAEU,eAAe,QAAwB;AAC/C,UAAM,eAAuC;AAAA,MAC3C,KAAK;AAAA,MACL,MAAM;AAAA,MACN,KAAK;AAAA,MACL,MAAM;AAAA,MACN,KAAK;AAAA,MACL,KAAK;AAAA,IAAA;AAEP,WAAO,aAAa,MAAM,KAAK;AAAA,EACjC;AACF;"}