| 149 | */ |
| 150 | // eslint-disable-next-line @typescript-eslint/no-explicit-any -- mock adapter callbacks receive internal SDK types |
| 151 | export function createMockAdapter(options: { |
| 152 | chatStreamFn?: (opts: any) => AsyncIterable<StreamChunk> |
| 153 | /** Array of chunk sequences: chatStream returns iterations[0] on first call, iterations[1] on second, etc. */ |
| 154 | iterations?: Array<Array<StreamChunk>> |
| 155 | structuredOutput?: (opts: any) => Promise<{ data: unknown; rawText: string }> |
| 156 | /** Optional native streaming structured output. When omitted, the adapter |
| 157 | * has no `structuredOutputStream` and consumers fall through to the |
| 158 | * synthesized fallback in `runStructuredFinalization`. */ |
| 159 | structuredOutputStream?: (opts: any) => AsyncIterable<StreamChunk> |
| 160 | /** When true, the adapter declares it natively combines tools + a |
| 161 | * schema-constrained final answer in one streaming call (issue #605). |
| 162 | * The engine then forwards `outputSchema` into `chatStream` and skips |
| 163 | * the separate finalization round-trip. */ |
| 164 | supportsCombinedToolsAndSchema?: boolean |
| 165 | }) { |
| 166 | const calls: Array<TextOptions<any, any>> = [] |
| 167 | let callIndex = 0 |
| 168 | |
| 169 | const adapter: AnyTextAdapter = { |
| 170 | kind: 'text' as const, |
| 171 | name: 'mock', |
| 172 | model: 'test-model' as const, |
| 173 | '~types': { |
| 174 | providerOptions: {} as Record<string, unknown>, |
| 175 | inputModalities: ['text'] as readonly ['text'], |
| 176 | messageMetadataByModality: { |
| 177 | text: undefined as unknown, |
| 178 | image: undefined as unknown, |
| 179 | audio: undefined as unknown, |
| 180 | video: undefined as unknown, |
| 181 | document: undefined as unknown, |
| 182 | }, |
| 183 | toolCapabilities: [] as ReadonlyArray<string>, |
| 184 | toolCallMetadata: undefined as unknown, |
| 185 | systemPromptMetadata: undefined as never, |
| 186 | }, |
| 187 | chatStream: (opts: any) => { |
| 188 | calls.push(opts) |
| 189 | |
| 190 | if (options.chatStreamFn) { |
| 191 | return options.chatStreamFn(opts) |
| 192 | } |
| 193 | |
| 194 | if (options.iterations) { |
| 195 | const chunks = options.iterations[callIndex] || [] |
| 196 | callIndex++ |
| 197 | return (async function* () { |
| 198 | for (const c of chunks) yield c |
| 199 | })() |
| 200 | } |
| 201 | |
| 202 | return (async function* () {})() |
| 203 | }, |
| 204 | structuredOutput: |
| 205 | options.structuredOutput ?? (async () => ({ data: {}, rawText: '{}' })), |
| 206 | } |
| 207 | |
| 208 | if (options.structuredOutputStream) { |