mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
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.
75 lines
2.8 KiB
TypeScript
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
|
|
}
|
|
}
|
|
}
|