Cookbook
Hands-on recipes: add a workspace package, add a model tool, and wire an LLM adapter
Three hands-on recipes: add a workspace package, add a model tool, and wire an LLM adapter. Each requires a source checkout (see the run-from-source part of Installation).
Recipe 1: add a workspace package
1. Create the package
packages/<group>/<pkg>/
package.json # copy from packages/core/tools, adjust name/description/deps
tsconfig.json # extends ../../../tsconfig.base.json, rootDir src, outDir lib/types
src/index.ts # service default export, or plugin (name/inject/apply/Config)
README.md # service API, events, extension points, and design notesKey package.json invariants: private: true, a version matching the root package.json, type: module, @deepseek-ai/cordis in both peerDependencies and devDependencies (same range), and @deepseek-ai/schemastery in dependencies (it is a runtime validator).
2. Register it in the root configs
- Add
{ "path": "./packages/<group>/<pkg>" }to thereferencesoftsconfig.host.json(Host) ortsconfig.client.json(Client). An ordinary package belongs to exactly one aggregate, never both. - A client plugin package additionally declares
dsh.clientand extendstsconfig.base.client.json.
3. Decide the package topology
Split a swappable capability into Service Definition / Service Provider / Consumer packages when the roles evolve independently; a single-purpose plugin stays one package. Name the stable current responsibility, not the first implementation or a future expansion.
4. Verify
pnpm install
pnpm run constraints && pnpm run typecheck && pnpm run lint
pnpm run build && pnpm run hygieneRecipe 2: add a model tool
Create a greet tool. Replace the plugin file with:
import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
export const name = 'greet-tool'
export const inject = ['tools']
export function apply(ctx: Context) {
ctx.tools.register(defineTool({
name: 'greet',
description: 'Greet someone by name.',
parameters: {
name: { type: 'string', required: true, description: 'The name to greet' },
},
output: {
schema: { type: 'string' },
render: (_args, value) => [{ type: 'text', text: value }],
},
async execute(args) {
return `Hello, ${args.name}!`
},
}))
}inject makes Cordis wait for the tool registry; defineTool infers and validates args from parameters.
pnpm dsh web --patch ./scratch-plugin/cordis.ymlOpen http://127.0.0.1:3080 and ask Use the greet tool to greet Ada. — the model can call greet. For the full tool contract, see Registering Tools.
Recipe 3: wire an LLM adapter
An adapter translates the harness's provider-neutral request into a provider API call and the response back into harness chunks:
import type { Context } from '@deepseek-ai/cordis'
import Schema from '@deepseek-ai/schemastery'
import { LlmAdapter, type GenerateOptions, type StreamChunk } from '@deepseek-ai/dsh-llm'
class MyAdapter extends LlmAdapter {
private apiKey: string
constructor(apiKey: string) {
super()
this.apiKey = apiKey
}
async *stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
// 1. Convert options.messages to the provider format.
// 2. Call the streaming API.
// 3. Convert the response into StreamChunk values.
}
}
export interface Config {
apiKey: string
providers: string[]
}
export const Config: Schema<Config> = Schema.object({
apiKey: Schema.string().required(),
providers: Schema.array(Schema.string()).required(),
})
export const name = 'my-llm-adapter'
export const inject = ['llm']
export function apply(ctx: Context, config: Config) {
ctx.llm.registerAdapter(config.providers, new MyAdapter(config.apiKey))
}Hard streaming rules: every block-start has a matching block-end; index starts at 0 and identifies content-block order; tool-call arguments are raw JSON strings end-to-end; emit usage before finish; finish is the final chunk. For a field the provider cannot honor, throw LlmError rather than silently dropping it.
Use it from cordis.yml:
- id: my-llm
name: './src/my-llm-adapter.ts'
config:
apiKey: !!js process.env.MY_API_KEY
providers:
- my-provider
- id: agent-loop
name: '@deepseek-ai/dsh-agent-loop'
config:
agents:
- id: main
provider: my-provider
model: my-model-v1Reference implementations in the repository: packages/llm/llm-deepseek (direct HTTP) and packages/llm/llm-pi-ai (wrapping an LLM library).
Next steps
- The full tool contract is in Registering Tools.
- The three-role seam design is in Architecture.