feat: add canonical typed tool outputs
This commit is contained in:
@@ -18,7 +18,7 @@ Beyond the schema's type/required/enum checks, `execute` rejects an empty or dup
|
||||
|
||||
## Rendering
|
||||
|
||||
The tool writes only the session event; it does not render. UIs subscribe to `session/event` and render the `todo/write` data themselves: the [TUI app](../../examples/tui-demo) shows a persistent plan, and the [ACP bridge](../../ui/acp) maps the list to a `plan` sessionUpdate (synthesizing the `priority` ACP requires).
|
||||
The canonical result is `{ todos, counts: { pending, inProgress, completed } }`; its Native renderer returns the compact update acknowledgement. The tool also writes the full `todo/write` session event. UIs subscribe to `session/event` and render that durable list themselves: the [TUI app](../../examples/tui-demo) shows a persistent plan, and the [ACP bridge](../../ui/acp) maps the list to a `plan` sessionUpdate (synthesizing the `priority` ACP requires).
|
||||
|
||||
## Export shape
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
*/
|
||||
|
||||
import type { Context } from 'cordis'
|
||||
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
|
||||
import { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
import type { TodoItem } from '@deepseek-ai/dsh-session'
|
||||
|
||||
@@ -80,7 +79,41 @@ export function apply(ctx: Context): void {
|
||||
},
|
||||
},
|
||||
},
|
||||
execute(args, exec): Promise<ContentBlock[]> {
|
||||
output: {
|
||||
schema: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
todos: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
items: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
content: { type: 'string', required: true },
|
||||
status: { type: 'string', required: true, enum: [...STATUSES] },
|
||||
},
|
||||
},
|
||||
},
|
||||
counts: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: true,
|
||||
properties: {
|
||||
pending: { type: 'integer', required: true },
|
||||
inProgress: { type: 'integer', required: true },
|
||||
completed: { type: 'integer', required: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
render: (_args, value) => [{
|
||||
type: 'text',
|
||||
text: `Updated todo list: ${value.counts.pending} pending, ${value.counts.inProgress} in progress, ${value.counts.completed} completed.`,
|
||||
}],
|
||||
},
|
||||
execute(args, exec) {
|
||||
const todos = toTodoList(args.todos)
|
||||
if (!exec.agent) {
|
||||
// The list is per-agent-session state; a non-agent caller (no owning
|
||||
@@ -89,10 +122,14 @@ export function apply(ctx: Context): void {
|
||||
}
|
||||
exec.agent.session.append('todo/write', { todos })
|
||||
const count = (status: TodoItem['status']): number => todos.filter(t => t.status === status).length
|
||||
return Promise.resolve([{
|
||||
type: 'text',
|
||||
text: `Updated todo list: ${count('pending')} pending, ${count('in_progress')} in progress, ${count('completed')} completed.`,
|
||||
}])
|
||||
return Promise.resolve({
|
||||
todos: todos.map(todo => ({ content: todo.content, status: todo.status })),
|
||||
counts: {
|
||||
pending: count('pending'),
|
||||
inProgress: count('in_progress'),
|
||||
completed: count('completed'),
|
||||
},
|
||||
})
|
||||
},
|
||||
presentCall: args => ({ card: 'generic', title: 'Update todo list', kind: 'other', rawInput: args.todos }),
|
||||
}))
|
||||
|
||||
@@ -70,6 +70,11 @@ describe('dsh-tool-todo', () => {
|
||||
]
|
||||
const result = await callTodo(ctx, { todos }, { agent })
|
||||
expect(result.isError).toBe(false)
|
||||
if (result.isError) throw new Error('expected todo_write success')
|
||||
expect(result.value).toEqual({
|
||||
todos,
|
||||
counts: { pending: 1, inProgress: 1, completed: 0 },
|
||||
})
|
||||
expect(text(result)).toContain('1 pending, 1 in progress, 0 completed')
|
||||
|
||||
const event = agent.session.events.findLast(e => e.type === 'todo/write')!
|
||||
|
||||
Reference in New Issue
Block a user