108 lines
4.5 KiB
Markdown
108 lines
4.5 KiB
Markdown
# 7. Into the harness
|
|
|
|
English | [中文](07-into-the-harness.zh.md)
|
|
|
|
This chapter registers a model-callable tool with the harness's `tools` service, executes it through the harness tool pipeline, and observes the result event. It remains keyless and does not call a model.
|
|
|
|
## A tool plugin
|
|
|
|
Create `greet-tool.ts` in `tmp/cordis-tutorial`:
|
|
|
|
```ts
|
|
import type { Context } from 'cordis'
|
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
import { CallId } from '@deepseek-ai/dsh-llm'
|
|
|
|
export const name = 'greet-tool'
|
|
export const inject = ['tools']
|
|
|
|
export function apply(ctx: Context) {
|
|
ctx.tools.register(defineTool({
|
|
name: 'greet',
|
|
description: 'Greet the named person.',
|
|
parameters: {
|
|
name: { type: 'string', required: true, description: 'Who to greet' },
|
|
},
|
|
output: {
|
|
schema: { type: 'string' },
|
|
render: (_args, value) => [{ type: 'text', text: value }],
|
|
},
|
|
async execute(args) {
|
|
return `Hello, ${args.name}!`
|
|
},
|
|
}))
|
|
|
|
// Drive one call through the real execution pipeline, standing in for
|
|
// the model. CallId brands the correlation id a provider would issue.
|
|
void (async () => {
|
|
const result = await ctx.tools.execute({
|
|
callId: CallId('demo-1'),
|
|
name: 'greet',
|
|
arguments: { name: 'Cordis' },
|
|
signal: new AbortController().signal,
|
|
})
|
|
console.log('tool replied:', JSON.stringify(result.content))
|
|
})()
|
|
}
|
|
```
|
|
|
|
Every pattern here is from the earlier chapters: `inject: ['tools']` ([chapter 3](03-services.md)) holds the plugin until the tool registry exists; `ctx.tools.register(...)` attaches the registration disposer to the plugin ([chapter 2](02-lifecycle-and-effects.md)), so unloading unregisters the tool. `defineTool` converts the `parameters` spec to the JSON Schema shown to the model, infers the type of `args`, and validates model-supplied arguments before `execute` runs. The tool returns the canonical value declared by `output.schema`; `output.render` separately produces the Native and durable result content.
|
|
|
|
## An observer plugin
|
|
|
|
Create `tool-logger.ts` — a separate plugin that watches every tool call in the app through the harness's `tools/result` event:
|
|
|
|
```ts
|
|
import type { Context } from 'cordis'
|
|
import type {} from '@deepseek-ai/dsh-tools'
|
|
|
|
export const name = 'tool-logger'
|
|
export const inject = ['tools']
|
|
|
|
export function apply(ctx: Context) {
|
|
ctx.on('tools/result', (exec, result) => {
|
|
const text = result.content
|
|
.map(block => (block.type === 'text' ? block.text : ''))
|
|
.join('')
|
|
console.log(`[tool-logger] ${exec.name} -> ${text}`)
|
|
})
|
|
}
|
|
```
|
|
|
|
The `import type {} from '@deepseek-ai/dsh-tools'` line pulls in the package's declaration merges so `'tools/result'` and its payload are typed — the same move as chapter 4's `stats.ts` import, at package scale.
|
|
|
|
## Compose and run
|
|
|
|
```yaml
|
|
- name: '@deepseek-ai/dsh-system-prompt'
|
|
- name: '@deepseek-ai/dsh-tools'
|
|
- name: './tool-logger.ts'
|
|
- name: './greet-tool.ts'
|
|
```
|
|
|
|
`@deepseek-ai/dsh-tools` injects the `systemPrompt` service because tools contribute schemas to the system prompt, so the composition lists its provider too. Without it, the tools plugin remains PENDING as described in [chapter 6](06-composition-and-hmr.md).
|
|
|
|
```sh
|
|
node --import tsx ../../vendor/cordis/bin.js
|
|
```
|
|
|
|
```
|
|
[tool-logger] greet -> Hello, Cordis!
|
|
tool replied: [{"type":"text","text":"Hello, Cordis!"}]
|
|
```
|
|
|
|
The logger fired first: `tools/result` is emitted as part of result materialization, before `execute`'s promise resolves to the caller. Neither of your plugins knows the other exists — the registry service and the event connect them.
|
|
|
|
## From here to a full agent
|
|
|
|
A real agent is this composition plus more plugins: an LLM adapter, the agent loop, persistence, a front end. Compare [examples/headless-agent/cordis.yml](../../examples/headless-agent/cordis.yml) — you can read every entry in it now. Add your `greet-tool.ts` to a copy of that file.
|
|
|
|
Where to go next:
|
|
|
|
- [Build a tool](../user/develop/basic/tool.md) — more of `defineTool`, including presentation and richer schemas.
|
|
- [Three-layer capability design](../user/develop/practice/index.md) — how the harness structures replaceable capabilities.
|
|
- The generated [services](../cordis-catalog/services.md) and [events](../cordis-catalog/events.md) catalogs — everything you can inject and listen to.
|
|
- [Architecture](../architecture.md) — the system map these plugins live in.
|
|
|
|
[](https://github.com/deepseek-harness/deepseek-harness)
|