Merge remote-tracking branch 'origin/master' into worktree/web-multimodal-image-input

# Conflicts:
#	apps/cli/README.md
#	apps/cli/src/web.ts
#	apps/web/tests/smoke-fixture.e2e.ts
#	docs/architecture.i18n.yaml
#	docs/module-graph.md
#	examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/stdout.expected.jsonl
#	packages/client/connection/src/client/api.ts
#	packages/client/connection/src/client/fixture.ts
#	packages/client/runtime/README.md
#	packages/client/runtime/src/client/index.ts
#	packages/client/runtime/src/client/sessions/conversation.ts
#	packages/client/runtime/src/client/sessions/service.ts
#	packages/client/runtime/src/client/sessions/session.ts
#	packages/client/ui-conversation/src/client/apply.ts
#	packages/client/ui-conversation/src/client/contract/slots.ts
#	packages/client/ui-conversation/src/client/index.ts
#	packages/client/ui-conversation/src/client/service.ts
#	packages/client/ui-conversation/src/client/skeleton/ConversationRoot.tsx
#	packages/client/ui-conversation/src/client/skeleton/EmptyState.tsx
#	packages/client/ui-conversation/src/client/skeleton/InputBar.tsx
#	packages/client/ui-conversation/src/client/stores.ts
#	packages/client/ui-conversation/tests/apply-inject.spec.tsx
#	packages/client/ui-conversation/tests/service-orchestration.spec.ts
#	packages/client/ui-conversation/tests/skeleton-branches.spec.tsx
#	packages/client/ui-conversation/tests/skeleton.spec.tsx
#	packages/cordis/tool-cordis/src/api-catalog.ts
#	packages/host/apiproxy/package.json
#	packages/host/apiproxy/src/api-proxy.ts
#	packages/host/apiproxy/src/api/sessions.schema.ts
#	packages/host/runtime/package.json
#	packages/host/runtime/src/boot.ts
#	packages/host/runtime/tests/host-runtime.spec.ts
#	packages/host/runtime/tsconfig.json
#	packages/host/webserver/README.md
#	packages/host/webserver/src/index.ts
#	packages/host/webserver/tests/webserver.spec.ts
#	packages/llm/llm-pi-ai/tests/convert.spec.ts
#	packages/ui/acp/src/codec.ts
#	packages/ui/acp/tests/codec.spec.ts
#	pnpm-lock.yaml
This commit is contained in:
Yichen Jiang
2026-07-25 22:35:22 +08:00
1264 changed files with 37694 additions and 29428 deletions

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { BlockAssembler, LlmError } from '@deepseek-ai/dsh-llm'
import { BlockAssembler, EMPTY_RESPONSE_CODE, LlmError } from '@deepseek-ai/dsh-llm'
import type { StreamChunk } from '@deepseek-ai/dsh-llm'
import { DONE } from '../src/sse.ts'
import { mapFinishReason, mapUsage, translate } from '../src/translate.ts'
@@ -203,7 +203,50 @@ describe('translate: finish and usage handling', () => {
it('handles chunks with no choices at all', async () => {
const chunks = await collect(translate(feed({}, DONE)))
expect(chunks).toEqual([{ type: 'finish', reason: { kind: 'stop' } }])
expect(chunks).toEqual([{
type: 'finish',
reason: {
kind: 'error',
failure: { message: 'model returned a completed response with no content', code: EMPTY_RESPONSE_CODE },
},
}])
})
it('classifies an explicit stop with no opened blocks as EMPTY_RESPONSE, after usage', async () => {
const chunks = await collect(translate(feed(
firstChunk,
{ choices: [{ delta: {}, finish_reason: 'stop' }], usage: { prompt_tokens: 7, completion_tokens: 0 } },
DONE,
)))
expect(chunks).toEqual([
{ type: 'usage', usage: { inputTokens: 7, outputTokens: 0 } },
{
type: 'finish',
reason: {
kind: 'error',
failure: { message: 'model returned a completed response with no content', code: EMPTY_RESPONSE_CODE },
},
},
])
})
it('keeps a reasoning-only stream a successful stop (any opened block counts)', async () => {
const chunks = await collect(translate(feed(
firstChunk,
{ choices: [{ delta: { content: null, reasoning_content: 'mull' } }] },
{ choices: [{ delta: {}, finish_reason: 'stop' }] },
DONE,
)))
expect(chunks.at(-1)).toEqual({ type: 'finish', reason: { kind: 'stop' } })
})
it('leaves non-stop finishes unclassified even with no opened blocks', async () => {
const chunks = await collect(translate(feed(
firstChunk,
{ choices: [{ delta: {}, finish_reason: 'length' }] },
DONE,
)))
expect(chunks.at(-1)).toEqual({ type: 'finish', reason: { kind: 'max-tokens' } })
})
})