Merge latest invariant registration gate

This commit is contained in:
Tianyi Cui
2026-07-20 20:29:29 +08:00
22 changed files with 329 additions and 81 deletions
+1 -1
View File
@@ -42,7 +42,7 @@ Every request carries the shared attribution header from dsh-llm's `attributionH
## Errors
Non-2xx responses throw `LlmError` with stable codes: `AUTH` (401/403), `RATE_LIMIT` (429), `CONTEXT_WINDOW_EXCEEDED` (a 400 whose provider code, type, or message identifies context overflow), `INVALID_REQUEST` (other 400s), `SERVER` (5xx), `HTTP_<status>` otherwise. Protocol violations throw `STREAM_CLOSED` (no `[DONE]`) or `MALFORMED_RESPONSE` (bad JSON payload). Unknown wire `finish_reason`s (e.g. `content_filter`, `insufficient_system_resource`) become `finish {kind: 'error', code: <REASON>}` chunks.
Non-2xx responses throw `LlmError` with stable codes: `AUTH` (401/403), `RATE_LIMIT` (429), `CONTEXT_WINDOW_EXCEEDED` (a 400 whose provider code, type, or message identifies context overflow), `INVALID_REQUEST` (other 400s), `SERVER` (5xx), `HTTP_<status>` otherwise. A transport failure before any response (DNS, refused connection, TLS, proxy) throws `NETWORK` naming the configured endpoint and chaining fetch's `TypeError: fetch failed` as `cause`, so `errorChain` renders the underlying diagnosis; an abort keeps its `DOMException` so the loop classifies it as cancellation. Protocol violations throw `STREAM_CLOSED` (no `[DONE]`) or `MALFORMED_RESPONSE` (bad JSON payload). Unknown wire `finish_reason`s (e.g. `content_filter`, `insufficient_system_resource`) become `finish {kind: 'error', code: <REASON>}` chunks.
## Testing
+34 -14
View File
@@ -81,23 +81,43 @@ export class DeepSeekAdapter extends LlmAdapter {
async * stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
const body = serializeRequest(options, this.options.defaults ?? {})
// Prepared outside the try so the NETWORK label below covers exactly the
// transport boundary, never a serialization failure.
const payload = JSON.stringify(body)
const headers = {
'authorization': `Bearer ${this.options.apiKey}`,
'content-type': 'application/json',
'accept': 'text/event-stream',
...attributionHeaders(),
...options.sessionId !== undefined
? { 'x-deepseek-harness-session-id': String(options.sessionId) }
: {},
}
// TODO(http): adopt the Cordis HTTP service when shared transport configuration
// outweighs its additional runtime dependencies.
const response = await fetch(`${this.options.baseURL}/chat/completions`, {
method: 'POST',
headers: {
'authorization': `Bearer ${this.options.apiKey}`,
'content-type': 'application/json',
'accept': 'text/event-stream',
...attributionHeaders(),
...options.sessionId !== undefined
? { 'x-deepseek-harness-session-id': String(options.sessionId) }
: {},
},
body: JSON.stringify(body),
...options.signal ? { signal: options.signal } : {},
})
let response: Response
try {
response = await fetch(`${this.options.baseURL}/chat/completions`, {
method: 'POST',
headers,
body: payload,
...options.signal ? { signal: options.signal } : {},
})
} catch (error: unknown) {
// An aborted request rethrows its original rejection (the signal's abort
// reason) so the loop classifies it as cancellation, not a provider failure.
if (options.signal?.aborted) throw error
// fetch wraps every transport failure (DNS, refused connection, TLS,
// proxy) in a bare `TypeError: fetch failed` whose actionable detail
// lives on `cause`. Wrapping with the endpoint and chaining the cause
// lets `errorChain` render the full diagnosis at every reporting seam.
throw new LlmError(
`DeepSeek API request to ${this.options.baseURL} failed`,
'NETWORK',
{ cause: error },
)
}
if (!response.ok) {
let message = `DeepSeek API error (HTTP ${response.status})`
@@ -2,7 +2,7 @@ import { createServer } from 'node:http'
import type { IncomingMessage, Server, ServerResponse } from 'node:http'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { Context } from 'cordis'
import LlmService, { CONTEXT_WINDOW_EXCEEDED_CODE, LlmError, userAgent } from '@deepseek-ai/dsh-llm'
import LlmService, { CONTEXT_WINDOW_EXCEEDED_CODE, errorChain, LlmError, userAgent } from '@deepseek-ai/dsh-llm'
import { SessionId } from '@deepseek-ai/dsh-session'
import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
import { DeepSeekAdapter } from '@deepseek-ai/dsh-llm-deepseek'
@@ -228,6 +228,39 @@ describe('DeepSeekAdapter against a mock server', () => {
expect(httpErrorCode(418)).toBe('HTTP_418')
})
it('wraps a transport failure in NETWORK with the fetch cause chain in the message', async () => {
// Port 1 is reserved/unbound: fetch rejects with `TypeError: fetch failed`
// whose actionable detail (ECONNREFUSED) lives on `cause`.
const ctx = await harness('http://127.0.0.1:1')
let caught: unknown
try {
await assemble(ctx, { model: 'deepseek-v4-flash', messages: [] })
} catch (error: unknown) {
caught = error
}
expect(caught).toBeInstanceOf(LlmError)
const llmError = caught as LlmError
expect(llmError.code).toBe('NETWORK')
expect(llmError.message).toContain('http://127.0.0.1:1')
expect(llmError.cause).toBeInstanceOf(TypeError)
// The chain renderer reaches the transport diagnosis through the cause.
expect(errorChain(llmError)).toMatch(/ECONNREFUSED|EADDRNOTAVAIL|bad port/)
})
it('keeps an abort rejection unwrapped so the loop classifies it as cancellation', async () => {
const controller = new AbortController()
controller.abort()
const ctx = await harness('http://127.0.0.1:1')
let caught: unknown
try {
await assemble(ctx, { model: 'deepseek-v4-flash', messages: [], signal: controller.signal })
} catch (error: unknown) {
caught = error
}
expect(caught).not.toBeInstanceOf(LlmError)
expect((caught as Error).name).toBe('AbortError')
})
it('throws EMPTY_RESPONSE when the response has no body', async () => {
const adapter = new DeepSeekAdapter({ apiKey: 'k', baseURL: 'http://127.0.0.1:1' })
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(