mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
The adapter takes an options thunk and a per-stream credential resolver instead of frozen construction facts: base URL, catalog, defaults, idle budget, and the API key re-resolve at each operation, so a settings or credential change reaches the very next request while in-flight streams keep the facts they started with. resolveAdapterOptions is the one explicit resolve step (entry config fails loud at load; a live snapshot failing a beyond-schema bound keeps the last good options). The plugin layers its entry config under the optional llm-deepseek settings section and resolves keys literal-first through ctx.credentials with an ambient env fallback; a missing key now registers the route, warns, and fails each request with actionable MISSING_CREDENTIAL instead of failing plugin load. The registration-captured retry policy re-registers the route in place when it changes.
83 lines
3.1 KiB
TypeScript
83 lines
3.1 KiB
TypeScript
import { createServer } from 'node:http'
|
|
import type { IncomingMessage, Server, ServerResponse } from 'node:http'
|
|
|
|
/** One scripted behavior for the next request the mock server receives. */
|
|
export type Behavior =
|
|
| { kind: 'sse'; events: string[]; delayMs?: number }
|
|
| { kind: 'http-error'; status: number; body: string; contentType?: string; headers?: Record<string, string> }
|
|
| { kind: 'close-early'; events: string[] }
|
|
|
|
export interface MockServer {
|
|
url: string
|
|
/** Bodies of received requests, in order. */
|
|
requests: unknown[]
|
|
/** Header bags of received requests, in order (parallel to `requests`). */
|
|
headers: IncomingMessage['headers'][]
|
|
script: Behavior[]
|
|
close(): Promise<void>
|
|
}
|
|
|
|
const servers: Server[] = []
|
|
|
|
/** Close every server opened since the last call; run from each spec's afterEach. */
|
|
export async function closeMockServers(): Promise<void> {
|
|
await Promise.all(servers.splice(0).map(server => new Promise(resolve => server.close(resolve))))
|
|
}
|
|
|
|
/** A minimal complete text generation, reused by request-shape assertions. */
|
|
export const textEvents = [
|
|
'{"choices":[{"delta":{"role":"assistant","content":null,"reasoning_content":""}}]}',
|
|
'{"choices":[{"delta":{"content":"hello"}}]}',
|
|
'{"choices":[{"delta":{"content":""},"finish_reason":"stop"}],"usage":{"prompt_tokens":3,"completion_tokens":1}}',
|
|
'[DONE]',
|
|
]
|
|
|
|
/** Local chat-completions stand-in: replays scripted behaviors per request. */
|
|
export async function mockServer(script: Behavior[]): Promise<MockServer> {
|
|
const requests: unknown[] = []
|
|
const headers: IncomingMessage['headers'][] = []
|
|
const server = createServer((request: IncomingMessage, response: ServerResponse) => {
|
|
let body = ''
|
|
request.on('data', (chunk: Buffer) => { body += chunk.toString('utf8') })
|
|
request.on('end', () => {
|
|
requests.push(JSON.parse(body))
|
|
headers.push(request.headers)
|
|
const behavior = script.shift()
|
|
if (!behavior) {
|
|
response.writeHead(500).end('mock script exhausted')
|
|
return
|
|
}
|
|
if (behavior.kind === 'http-error') {
|
|
response.writeHead(behavior.status, {
|
|
'content-type': behavior.contentType ?? 'application/json',
|
|
...behavior.headers,
|
|
})
|
|
response.end(behavior.body)
|
|
return
|
|
}
|
|
response.writeHead(200, { 'content-type': 'text/event-stream' })
|
|
const write = (index: number): void => {
|
|
if (index >= behavior.events.length) {
|
|
if (behavior.kind === 'sse') response.end()
|
|
else response.destroy() // close-early: drop the socket mid-stream
|
|
return
|
|
}
|
|
response.write(`data: ${behavior.events[index]}\n\n`)
|
|
setTimeout(() => { write(index + 1) }, behavior.kind === 'sse' ? behavior.delayMs ?? 0 : 5)
|
|
}
|
|
write(0)
|
|
})
|
|
})
|
|
servers.push(server)
|
|
await new Promise<void>(resolve => server.listen(0, '127.0.0.1', resolve))
|
|
const address = server.address()
|
|
if (address === null || typeof address === 'string') throw new Error('no port')
|
|
return {
|
|
url: `http://127.0.0.1:${address.port}`,
|
|
requests,
|
|
headers,
|
|
script,
|
|
close: () => new Promise(resolve => server.close(() => { resolve() })),
|
|
}
|
|
}
|