Merge remote-tracking branch 'origin/master' into worktree/web-multimodal-image-input
# Conflicts: # .agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.i18n.yaml # docs/config-catalog.md # docs/cordis-catalog/events.md # docs/cordis-catalog/services.md # docs/core-data-structures/core.md # docs/core-data-structures/core.zh.md # docs/core-data-structures/llm-streaming.i18n.yaml # docs/core-data-structures/llm-streaming.md # docs/core-data-structures/llm-streaming.zh.md # docs/event-producer-consumer.md # docs/module-graph.md # docs/subsystems/attachment.i18n.yaml # docs/subsystems/attachment.md # docs/subsystems/attachment.zh.md # docs/subsystems/core.i18n.yaml # examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl # packages/README.i18n.yaml # packages/README.md # packages/README.zh.md # packages/client/runtime/package.json # packages/client/ui-conversation/package.json # packages/client/ui-conversation/src/client/chat/AssistantMarkdown.tsx # packages/client/ui-conversation/src/client/chat/ChatView.tsx # packages/client/ui-conversation/src/client/chat/MessageItem.tsx # packages/client/ui-conversation/src/client/index.ts # packages/client/ui-conversation/tests/input-bar.spec.tsx # packages/compact/compact-basic/README.i18n.yaml # packages/compact/compact-basic/README.md # packages/compact/compact-basic/README.zh.md # packages/host/apiproxy/src/api-proxy.ts # packages/host/apiproxy/src/api/index.ts # packages/host/apiproxy/src/api/sessions.ts # packages/host/apiproxy/src/index.ts # packages/host/apiproxy/tests/api-proxy-models.spec.ts # packages/self-modification/tool-cordis/src/api-catalog.ts # pnpm-lock.yaml # scripts/type-equiv.manifest.json
This commit is contained in:
@@ -69,7 +69,7 @@ export interface DeepSeekConnectionOptions {
|
||||
retryPolicy: ResolvedRetryPolicy
|
||||
}
|
||||
|
||||
/** Constructor options for {@link DeepSeekAdapter}: the two resolution seams the plugin owns. */
|
||||
/** Constructor options for {@link DeepSeekAdapter}: the two resolution hooks the plugin owns. */
|
||||
export interface DeepSeekAdapterOptions {
|
||||
/** Current validated connection facts; called once per operation. */
|
||||
options: () => DeepSeekConnectionOptions
|
||||
@@ -221,7 +221,13 @@ export class DeepSeekAdapter extends LlmAdapter {
|
||||
? consumer.signal
|
||||
: AbortSignal.any([options.signal, consumer.signal])
|
||||
using watchdog = idleWatchdog(upstream, connection.streamIdleTimeoutMs, STREAM_IDLE_TIMEOUT_CODE)
|
||||
const iterator = this.request(options, watchdog.signal, connection, apiKey)[Symbol.asyncIterator]()
|
||||
const iterator = this.request(
|
||||
options,
|
||||
watchdog.signal,
|
||||
connection,
|
||||
apiKey,
|
||||
() => { watchdog.pulse() },
|
||||
)[Symbol.asyncIterator]()
|
||||
let exhausted = false
|
||||
try {
|
||||
while (true) {
|
||||
@@ -262,6 +268,7 @@ export class DeepSeekAdapter extends LlmAdapter {
|
||||
signal: AbortSignal,
|
||||
connection: DeepSeekConnectionOptions,
|
||||
apiKey: string,
|
||||
onComment: () => void,
|
||||
): AsyncIterable<StreamChunk> {
|
||||
const body = serializeRequest(options, connection.defaults)
|
||||
// Prepared outside the try so the TRANSPORT label below covers exactly the
|
||||
@@ -296,7 +303,7 @@ export class DeepSeekAdapter extends LlmAdapter {
|
||||
// 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.
|
||||
// lets `errorChain` render the full diagnosis at every reporting boundary.
|
||||
throw new LlmError(
|
||||
`DeepSeek API request to ${connection.baseURL} failed`,
|
||||
'TRANSPORT',
|
||||
@@ -327,6 +334,6 @@ export class DeepSeekAdapter extends LlmAdapter {
|
||||
throw new LlmError('DeepSeek API returned no response body', 'EMPTY_RESPONSE')
|
||||
}
|
||||
|
||||
yield* translate(parseSse(response.body))
|
||||
yield* translate(parseSse(response.body, onComment))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
/**
|
||||
* Decode an SSE byte stream into event `data` payloads. Framing — chunk
|
||||
* reassembly, UTF-8/CRLF/BOM handling, comment and non-data field skipping,
|
||||
* multi-`data:` joining — is `eventsource-parser`'s; this module keeps only
|
||||
* the DeepSeek protocol: the literal `[DONE]` is yielded so the caller owns
|
||||
* final flushing, and EOF before it raises {@link LlmError}. Framing is
|
||||
* spec-strict: an event dispatches only on its blank-line terminator, so an
|
||||
* unterminated tail at EOF is truncation, not a flushable payload.
|
||||
* multi-`data:` joining — is `eventsource-parser`'s. Comments are reported
|
||||
* only through an optional transport-activity callback. This module keeps the
|
||||
* DeepSeek protocol: the literal `[DONE]` is yielded so the caller owns final
|
||||
* flushing, and EOF before it raises {@link LlmError}. Framing is spec-strict:
|
||||
* an event dispatches only on its blank-line terminator, so an unterminated
|
||||
* tail at EOF is truncation, not a flushable payload.
|
||||
*
|
||||
* @module dsh-llm-deepseek/sse
|
||||
*/
|
||||
@@ -21,12 +22,16 @@ export const DONE = '[DONE]'
|
||||
* value and returns; throws `LlmError('STREAM_CLOSED')` when the stream ends
|
||||
* without it (truncated response — the model call cannot be trusted).
|
||||
* @param stream - raw SSE bytes; reads may split anywhere, including mid-UTF-8 sequence.
|
||||
* @param onComment - optional transport-activity callback; comments never enter the yielded payload stream.
|
||||
* @returns each event's data payload in arrival order, the `[DONE]` sentinel last.
|
||||
*/
|
||||
export async function* parseSse(stream: ReadableStream<BufferSource>): AsyncGenerator<string> {
|
||||
export async function* parseSse(
|
||||
stream: ReadableStream<BufferSource>,
|
||||
onComment?: (comment: string) => void,
|
||||
): AsyncGenerator<string> {
|
||||
const events = stream
|
||||
.pipeThrough(new TextDecoderStream())
|
||||
.pipeThrough(new EventSourceParserStream())
|
||||
.pipeThrough(new EventSourceParserStream({ onComment }))
|
||||
for await (const { data } of events) {
|
||||
yield data
|
||||
if (data === DONE) return
|
||||
|
||||
Reference in New Issue
Block a user