Files
deepseek-harness/packages/llm-pi-ai
Tianyi Cui ab19fed77c Add two DeepSeek LLM adapters: dsh-llm-deepseek and dsh-llm-pi-ai
The first real LlmAdapter implementations, shipped as a deliberate pair:
same models and wire protocol, completely different internals, so the
StreamChunk protocol is verified across independent implementations.

- dsh-llm-deepseek: hand-rolled fetch + SSE parser + chunk-translation
  state machine against the official chat-completions format (thinking
  mode via top-level thinking/reasoning_effort; the empty-string
  reasoning_content first chunk; usage attached to the finish chunk or
  trailing; reasoning_content passback on tool-call turns; disjoint
  cache-token accounting).
- dsh-llm-pi-ai: the same endpoint through @earendil-works/pi-ai,
  mapping its event vocabulary (parsed tool arguments, in-stream error
  events, folded reasoning tokens) onto the same chunks.

The agent loop now honors the in-band error path: an adapter that ends
its stream with finish {kind:error|aborted} (the only option for
adapters that can't throw mid-stream, like pi-ai) is translated into a
step error, so the turn ends error/aborted with a logged error event
instead of a normal completed assistant message. This makes the
StreamChunk error contract real for both adapters; docs/architecture.md
and the StreamChunk doc are updated accordingly.

New yarn test:e2e (vitest.e2e.config.ts, *.e2e.ts) runs key-gated
real-API matrices for both adapters across V4 Flash/Pro and all
thinking/effort levels; it self-skips without DEEPSEEK_API_KEY. Unit
suites run against local node:http mock SSE servers at 100% per-file
coverage.
2026-06-13 18:30:03 +08:00
..

@deepseek-ai/dsh-llm-pi-ai

DeepSeek adapter for the harness LLM seam backed by @earendil-works/pi-ai (the LLM library behind the pi agent).

Why a second adapter exists

@deepseek-ai/dsh-llm-deepseek already talks to the same endpoint. This package is its design-verification twin: same models, same wire protocol, completely different internals — a unified LLM library with its own event vocabulary versus hand-rolled fetch/SSE. Anything the harness StreamChunk protocol cannot express for BOTH implementations is a core-vocabulary bug. The differences it exercised on purpose:

  • pi-ai hands back tool-call arguments as parsed objects; the harness keeps raw JSON strings (re-stringified at block-end).
  • pi-ai reports failures as in-stream error events (it never throws mid-stream); these map to finish {kind:'error'|'aborted'} chunks — the protocol's other sanctioned error path besides throwing (which llm-deepseek uses).
  • pi-ai folds reasoning tokens into usage.output; there is no separate reasoning count to map.
  • pi-ai's options omit stop sequences; GenerateOptions.stop is injected via its onPayload hook.

Config

Same shape as llm-deepseek (one-line swap in cordis.yml), with pi-ai's thinking-level vocabulary:

- id: llm
  name: '@deepseek-ai/dsh-llm-pi-ai'
  config:
    apiKey: !!js process.env.DEEPSEEK_API_KEY
    baseURL: !!js process.env.DEEPSEEK_BASE_URL
    models: [deepseek-v4-flash, deepseek-v4-pro]
    reasoning: high   # off | high | xhigh (xhigh → wire 'max')

Dependency weight

pi-ai declares the openai/anthropic/google/mistral/AWS SDKs as install-time dependencies. They are lazy-loaded — only the openai SDK actually loads for this adapter — but they do land in node_modules. Accepted for a package whose purpose is design verification.

Limitations

Same MVP contract as llm-deepseek: prefill throws UNSUPPORTED, images are not representable, tool_choice is not mapped.

Testing

Unit suites run against a local node:http mock SSE server (pi-ai's openai SDK happily talks to any base URL). Real-API coverage in tests/adapter.e2e.ts (yarn test:e2e, key-gated): V4 Flash + V4 Pro across all exposed reasoning levels (off/high/xhigh), the thinking+tools round trip, and a cross-adapter structural-equivalence check against llm-deepseek.