feat(llm): structured error taxonomy with a shared HarnessError base (RFC 005 pt 2)

Introduce HarnessError in dsh-llm (the leaf package): a stable machine-routable
code distinct from the message, cause chaining, name from the subclass, plus
isHarnessError. LlmError, ToolArgsError, and InvariantError now extend it.

Tool failures carry the structure end-to-end: ToolExecutionResult gains
error: { name, code } (populated from a thrown HarnessError), and the loop
forwards it onto the tool/result session event (which gained the same optional
field) for retry/sandbox plugins and replay. The loop's toError wraps non-Error
throws in a HarnessError(code: UNKNOWN, cause) instead of a bare Error.

Landed last and in isolation so it's a pure upgrade over the plain Error+code
the earlier PRs used — independently revertible. Graduates RFC 005 pt 2 ->
ADR 0015; RFC 005 now fully implemented.
This commit is contained in:
Tianyi Cui
2026-06-14 01:07:28 +08:00
parent 7a39616a06
commit 825b57aff9
18 changed files with 224 additions and 32 deletions

View File

@@ -21,6 +21,7 @@
"license": "BSD-3-Clause",
"peerDependencies": {
"@deepseek-ai/dsh-agent": "^0.0.1",
"@deepseek-ai/dsh-llm": "^0.0.1",
"@deepseek-ai/dsh-session": "^0.0.1",
"cordis": "^4.0.0-rc.6"
},

View File

@@ -20,6 +20,7 @@
*/
import type { Context } from 'cordis'
import { HarnessError } from '@deepseek-ai/dsh-llm'
import type { Agent, AgentStatus } from '@deepseek-ai/dsh-agent'
import type { Session, SessionEvent } from '@deepseek-ai/dsh-session'
@@ -27,13 +28,13 @@ export const name = 'invariants'
export const inject = ['sessions']
/**
* Thrown when a harness event-contract invariant is violated. Plain `Error`
* with a `code` for now; a later change promotes the harness error taxonomy.
* Thrown when a harness event-contract invariant is violated. Extends
* {@link HarnessError} (`code: 'INVARIANT'`) so a violation is routable like
* any other harness failure.
*/
export class InvariantError extends Error {
readonly code = 'INVARIANT'
export class InvariantError extends HarnessError {
constructor(message: string) {
super(`invariant violated: ${message}`)
super(`invariant violated: ${message}`, 'INVARIANT')
this.name = 'InvariantError'
}
}