The first plugin for DeepSeek Harness
In Harness, a plugin is a TypeScript module that exports an apply function.
The framework calls apply when loading the plugin, passing in a ctx (context object).
We register capabilities through ctx, such as event listeners, tools, and LLM adapters.
ctxContext is the context object that the framework passes to each plugin.
ctxIt is both the entry point for registering capabilities and records all resources registered by the plugin.
Create a local project
We need to install from source first:
git clone https://github.com/deepseek-ai/deepseek-harness.git cd deepseek-harness pnpm install pnpm run build pnpm dsh web
Next, create a scratch-plugin project to hold our plugin.
Execute in the repository root directory:
mkdir -p scratch-plugin/src
Minimal plugin: hello-plugin
Create my-plugin.ts under scratch-plugin/src.
cd scratch-plugin/src
Below is a complete, usable plugin configuration—nothing is missing.
Example
import type { Context } from '@deepseek-ai/cordis'
// name is the plugin name, used to identify this plugin in logs and configuration.
export const name = 'hello-plugin'
// apply is the plugin's entry point: the framework calls it when loading the plugin.
export function apply(ctx: Context) {
// The required dependencies are ready before apply is executed (see Part 9)
console.log('[hello-plugin] plugin loaded!')
}
This code does only one thing: it prints a line of log when loaded. It doesn't register any capabilities, but it is already a qualified plugin.
Three forms of the plugin
In addition to the function form shown above, plugins also support object form and class form.
| Form | Syntax | Applicable scenarios |
|---|---|---|
| Function form | Export an independent apply function | For most plugins, this is the simplest and most direct approach. |
| Object form | export default an object with name / inject / apply | When you need to declare metadata at the same time |
| Class form | export default a Service subclass | When a plugin needs to provide services to other plugins |
Function form
Exporting name and apply separately is the default style in the official examples.
Example
import type { Context } from '@deepseek-ai/cordis'
export const name = 'my-plugin'
export function apply(ctx: Context) {
// Register capabilities here
}
Object form
Put name, inject, and apply into a default-exported object.
Example
import type { Context } from '@deepseek-ai/cordis'
export default {
name: 'my-plugin',
inject: ['tools'],
apply(ctx: Context) {
// ...
},
}
Class form
The class form inherits the Service base class and is suitable for plugins that provide services externally.
Example
import { Service, type Context } from '@deepseek-ai/cordis'
export default class MyService extends Service {
static inject = ['tools']
constructor(ctx: Context) {
// The first parameter is ctx, the second is the service name
super(ctx, 'myService')
// Put synchronous initialization in the constructor
}
}
How to choose
In most cases, the function form is sufficient.
When a plugin needs to provide services to other plugins, use the class form.
Tip: The core of the class form is super(ctx, 'service name').
The full mechanism of services and dependencies will be covered in Part 14.