Files
deepseek-harness/packages/agent-loop/tests/mock-adapter.ts
Tianyi Cui d2fb352f3e Enable maximum-strict TypeScript across our packages
tsconfig.base.json adds noUncheckedIndexedAccess,
exactOptionalPropertyTypes, noImplicitOverride,
noFallthroughCasesInSwitch, noUnusedLocals, and noUnusedParameters on
top of strict. Vendored packages opt out of the new flags locally
(their tsconfigs are ours to regenerate; their source is not), keeping
upstream-sync friendliness.

Our code fixed accordingly: index accesses acknowledge undefined
(assembler flush cursors, lastTurnNumber); optional properties are
omitted instead of set-to-undefined (GenerateResult.usage,
ToolDefinition.strict, GenerateOptions.system/tools, error payloads
via an errorData helper); Session.onAppend is explicitly
`(…) => void | undefined`; tests and examples updated for unused
parameters and indexed access.
2026-06-11 14:02:47 +08:00

75 lines
2.8 KiB
TypeScript

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<StreamChunk> {
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<void>((_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
}
}
}