import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm' import { LlmAdapter } from '@deepseek-ai/dsh-llm' /** Helpers to write scripted responses tersely. */ export function textResponse(text: string): StreamChunk[] { return [ { type: 'block-start', index: 0, blockType: 'text' }, ...[...text].map((char): StreamChunk => ({ type: 'text-delta', index: 0, text: char })), { type: 'block-end', index: 0, block: { type: 'text', text } }, { type: 'usage', usage: { inputTokens: 10, outputTokens: text.length } }, { type: 'finish', reason: { kind: 'stop' } }, ] } export function toolCallResponse(callId: string, name: string, args: object, text?: string): StreamChunk[] { const argumentsJson = JSON.stringify(args) const chunks: StreamChunk[] = [] let index = 0 if (text) { chunks.push( { type: 'block-start', index, blockType: 'text' }, { type: 'text-delta', index, text }, { type: 'block-end', index, block: { type: 'text', text } }, ) index += 1 } chunks.push( { type: 'block-start', index, blockType: 'tool-call' }, { type: 'tool-call-delta', index, id: callId, name, argumentsDelta: argumentsJson.slice(0, 5) }, { type: 'tool-call-delta', index, id: callId, argumentsDelta: argumentsJson.slice(5) }, { type: 'block-end', index, block: { type: 'tool-call', id: callId, name, arguments: argumentsJson }, }, { type: 'usage', usage: { inputTokens: 10, outputTokens: 5 } }, { type: 'finish', reason: { kind: 'tool-calls' } }, ) return chunks } /** * Mock adapter driven by a script: each model call consumes the next entry. * Records every request it receives for assertions. An entry may be a * function to compute chunks from the request, or a 'hang' marker that * streams one chunk then waits until aborted. */ export class MockAdapter extends LlmAdapter { requests: GenerateOptions[] = [] constructor(private script: (StreamChunk[] | ((options: GenerateOptions) => StreamChunk[]) | 'hang')[]) { super() } async * stream(options: GenerateOptions): AsyncIterable { this.requests.push(options) const entry = this.script.shift() if (!entry) throw new Error('MockAdapter: script exhausted') if (entry === 'hang') { yield { type: 'block-start', index: 0, blockType: 'text' } yield { type: 'text-delta', index: 0, text: 'partial' } await new Promise((_resolve, reject) => { if (options.signal?.aborted) return reject(new Error('aborted')) options.signal?.addEventListener('abort', () => reject(new Error('aborted')), { once: true }) }) return } const chunks = typeof entry === 'function' ? entry(options) : entry for (const chunk of chunks) { if (options.signal?.aborted) throw new Error('aborted') yield chunk } } }