Merge remote-tracking branch 'origin/master' into worktree/web-multimodal-image-input
# Conflicts: # apps/cli/src/web.ts # apps/web/tests/smoke-fixture.e2e.ts # docs/architecture.i18n.yaml # packages/client/connection/src/client/fixture.ts # packages/client/ui-conversation/README.md # packages/client/ui-conversation/package.json # packages/client/ui-conversation/src/client/apply.ts # packages/client/ui-conversation/src/client/chat/ChatView.tsx # packages/client/ui-conversation/src/client/chat/register.ts # packages/client/ui-conversation/src/client/contract/slots.ts # packages/client/ui-conversation/src/client/contract/views.ts # packages/client/ui-conversation/src/client/service.ts # packages/client/ui-conversation/src/client/skeleton/ConversationRoot.tsx # packages/client/ui-conversation/src/client/stores.ts # packages/client/ui-conversation/tests/skeleton-branches.spec.tsx # packages/cordis/tool-cordis/src/api-catalog.ts # packages/host/runtime/src/boot.ts # packages/host/runtime/tests/host-runtime.spec.ts # pnpm-lock.yaml
This commit is contained in:
@@ -60,7 +60,7 @@ import {
|
||||
} from '@deepseek-ai/dsh-agent'
|
||||
import type {} from '@deepseek-ai/dsh-commands'
|
||||
import { encodeSessionReferenceUri } from '@deepseek-ai/dsh-session-reference'
|
||||
import { displayPromptContent, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import { displayPromptContent, SessionId, type JsonValue } from '@deepseek-ai/dsh-session'
|
||||
// Side-effect type import: resolves `ctx.get('permission')` to the service.
|
||||
import type {} from '@deepseek-ai/dsh-permission'
|
||||
import type { SessionEvent, TodoItem, TurnEndReason } from '@deepseek-ai/dsh-session'
|
||||
@@ -1499,7 +1499,7 @@ export class ToolPresenter {
|
||||
* @param meta - the result's machine-readable meta, forwarded when present.
|
||||
* @returns a normalized tool-owned view or raw-content fallback.
|
||||
*/
|
||||
result(callId: CallId, content: ContentBlock[], isError: boolean, meta?: unknown): ToolResultView {
|
||||
result(callId: CallId, content: ContentBlock[], isError: boolean, meta?: JsonValue): ToolResultView {
|
||||
const call = this.pending.get(callId)
|
||||
this.pending.delete(callId)
|
||||
// No remembered call (unknown/late callId) → nothing to present from; raw content.
|
||||
|
||||
@@ -12,6 +12,11 @@ import FsLocal from '@deepseek-ai/dsh-fs-local'
|
||||
import * as ToolFs from '@deepseek-ai/dsh-tool-fs'
|
||||
import { streamSessionEventUpdate, agentOptions, todosToPlan, ToolPresenter } from '../src/index.ts'
|
||||
|
||||
const UNUSED_TOOL_OUTPUT: ToolDefinition['output'] = {
|
||||
schema: { type: 'null' },
|
||||
render: () => [],
|
||||
}
|
||||
|
||||
/** Collect the updates a single event produces (no presenter → generic fallback). */
|
||||
function updatesFor(event: SessionEvent): SessionNotification['update'][] {
|
||||
const out: SessionNotification['update'][] = []
|
||||
@@ -279,6 +284,7 @@ describe('ToolPresenter (tool-owned presentation via the tool registry)', () =>
|
||||
name: 'bash',
|
||||
description: 'run a command',
|
||||
parameters: {},
|
||||
output: UNUSED_TOOL_OUTPUT,
|
||||
execute: async () => [],
|
||||
presentCall: (args: unknown) => {
|
||||
const a = args as { command: string; description: string }
|
||||
@@ -336,7 +342,7 @@ describe('ToolPresenter (tool-owned presentation via the tool registry)', () =>
|
||||
})
|
||||
|
||||
it('a tool with no presentCall/presentResult gets the generic fallback (title = name)', () => {
|
||||
const plain: ToolDefinition = { name: 'plain', description: 'p', parameters: {}, execute: async () => [] }
|
||||
const plain: ToolDefinition = { name: 'plain', description: 'p', parameters: {}, output: UNUSED_TOOL_OUTPUT, execute: async () => [] }
|
||||
const presenter = new ToolPresenter(registryOf(plain))
|
||||
const [update] = updatesWith(presenter, evt('tool/call', {
|
||||
turn: 1, step: 1, callId: CallId('c1'), name: 'plain', arguments: '{"a":1}',
|
||||
@@ -352,6 +358,7 @@ describe('ToolPresenter (tool-owned presentation via the tool registry)', () =>
|
||||
name: 'mini',
|
||||
description: 'm',
|
||||
parameters: {},
|
||||
output: UNUSED_TOOL_OUTPUT,
|
||||
execute: async () => [],
|
||||
presentCall: () => ({ card: 'generic', title: 'Doing a thing' }),
|
||||
presentResult: () => ({ card: 'generic', title: 'Did the thing' }),
|
||||
@@ -398,6 +405,7 @@ describe('ToolPresenter (tool-owned presentation via the tool registry)', () =>
|
||||
name: 'boom',
|
||||
description: 'b',
|
||||
parameters: {},
|
||||
output: UNUSED_TOOL_OUTPUT,
|
||||
execute: async () => [],
|
||||
presentCall: () => { throw new Error('call boom') },
|
||||
presentResult: () => { throw new Error('result boom') },
|
||||
@@ -427,6 +435,7 @@ describe('ToolPresenter (tool-owned presentation via the tool registry)', () =>
|
||||
name: 'boom',
|
||||
description: 'b',
|
||||
parameters: {},
|
||||
output: UNUSED_TOOL_OUTPUT,
|
||||
execute: async () => [],
|
||||
presentCall: () => { throw new Error('call boom') },
|
||||
presentResult: () => { throw new Error('result boom') },
|
||||
@@ -450,6 +459,7 @@ describe('ToolPresenter (tool-owned presentation via the tool registry)', () =>
|
||||
name: 'rogue',
|
||||
description: 'r',
|
||||
parameters: {},
|
||||
output: UNUSED_TOOL_OUTPUT,
|
||||
execute: async () => [],
|
||||
// A card value outside the union — forced with a cast (no valid input reaches this).
|
||||
presentCall: () => ({ card: 'chart', title: 'nope' }) as unknown as ReturnType<NonNullable<ToolDefinition['presentCall']>>,
|
||||
@@ -468,6 +478,7 @@ describe('ToolPresenter (tool-owned presentation via the tool registry)', () =>
|
||||
name: 'rogue',
|
||||
description: 'r',
|
||||
parameters: {},
|
||||
output: UNUSED_TOOL_OUTPUT,
|
||||
execute: async () => [],
|
||||
presentCall: () => ({ card: 'generic', title: 'r' }),
|
||||
presentResult: () => ({ card: 'chart' }) as unknown as ReturnType<NonNullable<ToolDefinition['presentResult']>>,
|
||||
@@ -530,6 +541,7 @@ describe('terminal-card mapping (capability-gated)', () => {
|
||||
name: 'bash',
|
||||
description: 'run a command',
|
||||
parameters: {},
|
||||
output: UNUSED_TOOL_OUTPUT,
|
||||
execute: async () => [],
|
||||
presentCall: (args: unknown) => {
|
||||
const command = (args as { command: string }).command
|
||||
@@ -691,6 +703,7 @@ describe('terminal-card mapping (capability-gated)', () => {
|
||||
name: 'bash',
|
||||
description: 'run a command',
|
||||
parameters: {},
|
||||
output: UNUSED_TOOL_OUTPUT,
|
||||
execute: async () => [],
|
||||
presentCall: (args: unknown) => ({ card: 'terminal', title: (args as { command: string }).command }),
|
||||
}
|
||||
@@ -713,6 +726,7 @@ describe('diff-card mapping', () => {
|
||||
name: 'writer',
|
||||
description: 'writes a file',
|
||||
parameters: {},
|
||||
output: UNUSED_TOOL_OUTPUT,
|
||||
execute: async () => [],
|
||||
presentCall: () => view as ReturnType<NonNullable<ToolDefinition['presentCall']>>,
|
||||
})
|
||||
@@ -848,6 +862,7 @@ describe('result-time diff card (REAL fs edit tool → tool_call_update diff blo
|
||||
name: 'writer',
|
||||
description: 'writes a file',
|
||||
parameters: {},
|
||||
output: UNUSED_TOOL_OUTPUT,
|
||||
execute: async () => [],
|
||||
presentCall: () => ({ card: 'diff', title: 'Write x', diffs: [{ path: 'x', oldText: null, newText: 'y' }] }),
|
||||
presentResult: () => ({ card: 'diff', diffs: [] }),
|
||||
|
||||
@@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
import { mkdtemp, rm } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
import { defineContentToolFixture } from '@deepseek-ai/dsh-tools'
|
||||
import { PROTOCOL_VERSION } from '@agentclientprotocol/sdk'
|
||||
import {
|
||||
errorResponse,
|
||||
@@ -63,7 +63,7 @@ describe('acp bridge — turn outcomes', () => {
|
||||
storageDir,
|
||||
script: [toolCallResponse('c1', 'bash', { command: 'echo hi' }), textResponse('done')],
|
||||
})
|
||||
harness.ctx.tools.register(defineTool({
|
||||
harness.ctx.tools.register(defineContentToolFixture({
|
||||
name: 'bash',
|
||||
description: 'run a command',
|
||||
parameters: { command: { type: 'string' } },
|
||||
@@ -204,7 +204,7 @@ describe('acp bridge — turn outcomes', () => {
|
||||
storageDir,
|
||||
script: [toolCallResponse('c1', 'kaboom', { x: 1 }), textResponse('done')],
|
||||
})
|
||||
harness.ctx.tools.register(defineTool({
|
||||
harness.ctx.tools.register(defineContentToolFixture({
|
||||
name: 'kaboom',
|
||||
description: 'explodes when presented',
|
||||
parameters: { x: { type: 'number' } },
|
||||
@@ -227,7 +227,7 @@ describe('acp bridge — turn outcomes', () => {
|
||||
storageDir,
|
||||
script: [toolCallResponse('c1', 'bash', { command: 'boom' }), textResponse('ok')],
|
||||
})
|
||||
harness.ctx.tools.register(defineTool({
|
||||
harness.ctx.tools.register(defineContentToolFixture({
|
||||
name: 'bash',
|
||||
description: 'run a command',
|
||||
parameters: { command: { type: 'string' } },
|
||||
|
||||
Reference in New Issue
Block a user