Merge remote-tracking branch 'origin/master' into worktree/llm-reasoning-effort

# Conflicts:
#	.agents/notes/implemented/feature/2026-07-17-dedicated-full-screen-tui-front-door.i18n.yaml
#	docs/architecture.i18n.yaml
#	docs/core-data-structures/llm-streaming.i18n.yaml
#	docs/core-data-structures/session.i18n.yaml
#	docs/event-producer-consumer.md
#	examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/stdout.expected.jsonl
This commit is contained in:
Yichen Jiang
2026-07-25 22:24:25 +08:00
870 changed files with 21704 additions and 28791 deletions
+1 -1
View File
@@ -47,7 +47,7 @@ If a listener rewrites assembled assistant content, the loop drops replay state
## Vocabulary differences
- pi-ai tool-call arguments are parsed objects; the harness stores raw JSON strings. The adapter parses input and re-stringifies output.
- pi-ai reports failures as in-stream error events; these map to `finish {kind:'error'|'aborted', failure}` chunks. Provider-specific error text distinguishes terminal `QUOTA` from transient `RATE_LIMIT`, while text and usage signals evaluated against the resolved model's context window normalize overflow to `CONTEXT_WINDOW_EXCEEDED`.
- pi-ai reports failures as in-stream error events; these map to `finish {kind:'error'|'aborted', failure}` chunks. Provider-specific error text distinguishes terminal `QUOTA` from transient `RATE_LIMIT`, while text and usage signals evaluated against the resolved model's context window normalize overflow to `CONTEXT_WINDOW_EXCEEDED`. A terminal `stop` whose message carries no content blocks maps to a `finish {kind:'error'}` with code `EMPTY_RESPONSE` (retried by default policy) instead of a successful empty message.
- pi-ai folds reasoning tokens into output usage; there is no separate reasoning count to map.
- pi-ai may internally support an `off` thinking level, but the Harness reasoning-effort capability deliberately excludes mode changes.
- `GenerateOptions.stop` is rejected with `UNSUPPORTED_OPTION` because pi-ai's common streaming surface cannot guarantee it across providers.
+16 -3
View File
@@ -8,7 +8,7 @@
* @module dsh-llm-pi-ai/stream
*/
import { CallId, CONTEXT_WINDOW_EXCEEDED_CODE, isContextWindowExceededError, isQuotaExceededError, LlmError, QUOTA_EXCEEDED_CODE } from '@deepseek-ai/dsh-llm'
import { CallId, CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, isContextWindowExceededError, isQuotaExceededError, LlmError, QUOTA_EXCEEDED_CODE } from '@deepseek-ai/dsh-llm'
import type { FinishReason, StreamChunk, TokenUsage } from '@deepseek-ai/dsh-llm'
import { isContextOverflow } from '@earendil-works/pi-ai'
import type { AssistantMessage, AssistantMessageEvent, Usage as PiUsage } from '@earendil-works/pi-ai'
@@ -48,7 +48,8 @@ function classifyPiAiError(message: string): string {
* @param contextWindow - resolved catalog capacity for usage-based overflow detection.
* @returns the mapped harness reason. Recognized error text, `stop` usage above
* `contextWindow`, and zero-output `length` usage that fills the window map
* to `CONTEXT_WINDOW_EXCEEDED`.
* to `CONTEXT_WINDOW_EXCEEDED`; a `stop` with no content blocks maps to an
* `EMPTY_RESPONSE` error.
*/
export function mapStopReason(message: AssistantMessage, contextWindow?: number): FinishReason {
const piAiOverflow = isContextOverflow(message, contextWindow)
@@ -66,7 +67,19 @@ export function mapStopReason(message: AssistantMessage, contextWindow?: number)
}
switch (message.stopReason) {
case 'stop': return { kind: 'stop' }
case 'stop':
// A terminal stop that produced no content blocks is a degenerate
// provider completion, not a successful (empty) assistant message.
if (message.content.length === 0) {
return {
kind: 'error',
failure: {
message: `model "${message.model}" returned a completed response with no content`,
code: EMPTY_RESPONSE_CODE,
},
}
}
return { kind: 'stop' }
case 'length': return { kind: 'max-tokens' }
case 'toolUse': return { kind: 'tool-calls' }
case 'aborted': return {
+20 -3
View File
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { CallId, CONTEXT_WINDOW_EXCEEDED_CODE, LlmError } from '@deepseek-ai/dsh-llm'
import { CallId, CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, LlmError } from '@deepseek-ai/dsh-llm'
import type { ContentBlock, StreamChunk } from '@deepseek-ai/dsh-llm'
import type { AssistantMessage, AssistantMessageEvent, Usage } from '@earendil-works/pi-ai'
import { toPiContext } from '../src/context.ts'
@@ -520,7 +520,22 @@ describe('mapStopReason / mapUsage', () => {
['toolUse', { kind: 'tool-calls' }],
['aborted', { kind: 'aborted', failure: { message: 'pi-ai stream aborted', code: 'ABORTED' } }],
] as const)('maps %s', (stopReason, expected) => {
expect(mapStopReason(assistant({ stopReason }))).toEqual(expected)
expect(mapStopReason(assistant({ stopReason, content: [{ type: 'text', text: 'ok' }] }))).toEqual(expected)
})
it('classifies a completed stop with no content as an EMPTY_RESPONSE error', () => {
expect(mapStopReason(assistant({ stopReason: 'stop' }))).toEqual({
kind: 'error',
failure: {
message: 'model "deepseek-v4-flash" returned a completed response with no content',
code: EMPTY_RESPONSE_CODE,
},
})
})
it('keeps a thinking-only stop successful (any block counts as content)', () => {
expect(mapStopReason(assistant({ stopReason: 'stop', content: [{ type: 'thinking', thinking: 'mull' }] })))
.toEqual({ kind: 'stop' })
})
it('defaults the error message when pi-ai omits it', () => {
@@ -580,7 +595,9 @@ describe('mapStopReason / mapUsage', () => {
})
it('uses the resolved context window for silent and length-stop overflows', () => {
const silent = assistant({ stopReason: 'stop', usage: usage(101, 0) })
// Non-empty content keeps the no-window branch on the successful stop path
// (an empty stop is EMPTY_RESPONSE, covered above); overflow wins over both.
const silent = assistant({ stopReason: 'stop', usage: usage(101, 0), content: [{ type: 'text', text: 'x' }] })
expect(mapStopReason(silent)).toEqual({ kind: 'stop' })
expect(mapStopReason(silent, 100)).toEqual({
kind: 'error',