docs: rebalance prose cleanup and add trimming skill

This commit is contained in:
Tianyi Cui
2026-07-13 23:27:00 +08:00
parent fcdc318dda
commit 148046b9c8
392 changed files with 2801 additions and 1754 deletions

View File

@@ -46,4 +46,4 @@ Every product adapter sends application identity on provider HTTP requests. `att
### Real adapters
Two adapters implement `LlmAdapter` against this vocabulary, deliberately built on different internals to keep the contract honest (see [the twin LLM adapters](../../../docs/rfc/implemented/architecture/2026-06-13-twin-llm-adapters.md)): [`@deepseek-ai/dsh-llm-deepseek`](../llm-deepseek) (hand-rolled fetch/SSE) and [`@deepseek-ai/dsh-llm-pi-ai`](../llm-pi-ai) (via `@earendil-works/pi-ai`). The pair pinned down the `StreamChunk` conventions now documented in `types.ts` (usage before finish, raw-string tool arguments, the two sanctioned error paths).
Two adapters implement `LlmAdapter` on different internals: [`@deepseek-ai/dsh-llm-deepseek`](../llm-deepseek) uses hand-rolled fetch/SSE, while [`@deepseek-ai/dsh-llm-pi-ai`](../llm-pi-ai) uses `@earendil-works/pi-ai`. Both follow the `StreamChunk` conventions in `types.ts`: usage precedes finish, tool arguments remain raw strings, and errors take one of two sanctioned paths. See [the twin LLM adapters](../../../docs/rfc/implemented/architecture/2026-06-13-twin-llm-adapters.md) for the design rationale.

View File

@@ -73,8 +73,8 @@ export class BlockAssembler {
}
case 'block-end': {
const partial = this.ensure(chunk.index, chunk.block.type)
// First close wins: a second block-end for an already-closed index is a straggler (same
// rule as post-close deltas).
// First close wins; ignoring re-close stragglers keeps streamed output
// and the final assembled block in agreement.
if (partial.block) return
partial.block = chunk.block
return chunk.block

View File

@@ -1,4 +1,8 @@
/**
* Centralize the non-secret product identity every provider request sends as `User-Agent`, keeping
* adapters from drifting. See
* `docs/rfc/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md`.
*
* App-attribution vocabulary for provider requests.
* @module @deepseek-ai/dsh-llm/attribution
*/

View File

@@ -1,5 +1,8 @@
/**
* The call configuration of a conversation and its comparison/freeze utilities.
* Conversation call configuration and freeze utilities. Model and sampling
* values are request-header state that can affect cache reuse; request
* waterfalls replace them and the loop logs changes instead of allowing
* silent per-call drift.
* @module dsh-llm/call-config
*/
@@ -30,9 +33,9 @@ export function callConfigEquals(a: LlmCallConfig, b: LlmCallConfig): boolean {
}
/**
* Deep-freeze a value in place so any later mutation throws (ESM code runs in strict mode),
* and return it.
*
* Deep-freeze a value in place, guarding cycles, so later mutation throws.
* {@link AbortSignal} objects are deliberately skipped because they are the
* request's live cancellation channel and freezing them breaks abort.
* @param value - the value to freeze in place.
* @returns the same value, frozen.
*/

View File

@@ -1,6 +1,6 @@
/**
* The harness error taxonomy: one base class so failures carry a stable, machine-routable
* `code` and chain their `cause`, instead of flattening to a bare message string.
* Harness error base with a stable machine-routable code and chained cause.
* Package errors extend it so tool results and replay can retain failure class.
* @module @deepseek-ai/dsh-llm/error
*/

View File

@@ -54,7 +54,10 @@ export class LlmError extends HarnessError {
}
/**
* Base class for LLM provider adapters.
* Provider-wire adapter for the harness message and stream vocabulary. Register implementations
* with `ctx.llm.registerAdapter(models, adapter)`. Every provider HTTP request must include
* `attributionHeaders()`; prove that at the wire or library header-hook boundary. The hand-rolled
* DeepSeek and pi-ai adapters intentionally exercise this contract through different internals.
*/
export abstract class LlmAdapter {
/**

View File

@@ -1,11 +1,14 @@
/**
* Exhaustiveness helper for switches over core unions.
* Exhaustiveness helper for closed core unions. Use {@link assertNever} at the default branch so a
* new variant fails compilation at every required handler. Do not use it for declaration-merged
* unions such as session events or content blocks: handle known variants and explicitly fall
* through because plugins may add valid unknown cases.
* @module @deepseek-ai/dsh-llm/never
*/
/**
* Mark an unreachable closed-union branch and diagnose values that escaped
* static exhaustiveness.
* Mark an unreachable closed-union branch. A newly unhandled typed variant fails at the call site;
* a value that escaped its type throws with diagnostics at runtime.
* @param value - the impossible value; typed `never` so an unhandled variant fails compilation at the call site.
* @param context - optional label (e.g. the switch site) prefixed into the throw message.
* @returns never — it always throws, with the offending value JSON-rendered in the message.

View File

@@ -1,5 +1,7 @@
/**
* Provider-neutral message and streaming vocabulary.
* Canonical provider-neutral message and streaming vocabulary for the loop,
* session log, and plugins. Adapters alone translate provider wire shapes;
* mapped interfaces make the content, source, and finish unions extensible.
*/
import type { Branded } from '@deepseek-ai/dsh-brand'
@@ -102,6 +104,10 @@ export interface TokenUsage {
/**
* Raw streaming protocol emitted by adapters.
* Block indexes correlate interleaved deltas, and `block-end` carries the
* assembled block. Adapters emit usage before the terminal finish and nothing
* afterward; tool arguments remain raw JSON strings. Failures either throw or
* end with `error`/`aborted`, and consumers must handle both paths.
*/
export type StreamChunk =
| { type: 'block-start'; index: number; blockType: ContentBlockType }
@@ -150,8 +156,8 @@ export interface GenerateOptions {
stop?: string[]
signal?: AbortSignal
/**
* The id of the session this request belongs to — stamped by the agent loop from
* `agent.session.id`.
* Session identity stamped by the loop for listener routing. Adapters ignore
* it; replay uses it to keep concurrent parent and child cursors independent.
*/
sessionId?: Branded<'SessionId'>
}

View File

@@ -63,8 +63,8 @@ describe('BlockAssembler', () => {
it('throws from assemble() when a partial has an unhandled blockType', () => {
const assembler = new BlockAssembler()
// A partial whose blockType is not text/reasoning/tool-call cannot be assembled without its
// block-end.
// Unknown declaration-merged block types cannot be assembled from partial deltas. Opening a
// plugin-added `video` block without its required `block-end` exercises that failure.
assembler.push({ type: 'block-start', index: 0, blockType: 'video' } as unknown as StreamChunk)
expect(() => assembler.blocks()).toThrow('cannot assemble incomplete block of type "video"')
})
@@ -133,10 +133,9 @@ describe('assertNever', () => {
})
})
describe('BlockAssembler regressions (property-test findings)', () => {
describe('BlockAssembler duplicate-close contract', () => {
it('first block-end wins: a duplicate block-end for a closed index is ignored', () => {
// Found by fast-check (the property-testing RFC): two block-ends at the same index made the
// streamed prefix (first block) disagree with final blocks() (second block).
// The first close wins so streamed and final output cannot disagree.
const chunks: StreamChunk[] = [
{ type: 'block-end', index: 0, block: { type: 'reasoning', text: 'first' } },
{ type: 'block-end', index: 0, block: { type: 'text', text: 'second' } },