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:
Yichen Jiang
2026-08-09 23:33:35 +08:00
3271 changed files with 69578 additions and 24953 deletions

View File

@@ -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))
}
}

View File

@@ -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