agnes-ai-for-dsh
Agnes AI integration for DeepSeek Harness — an LLM provider with multimodal support, image/video generation tools, and a settings panel.
What it provides
| Piece | Description |
|---|---|
| Provider | agnes-ai, registered with the llm-pi-ai adapter (OpenAI-compatible Chat Completions) |
| Chat models | agnes-2.5-flash, agnes-3.0-flash, agnes-2.5-pro, agnes-2.5-pro-beta — all accept image input |
| Tools | agnes_image_generation, agnes_video_generation, agnes_record_generation |
| Settings panel | An Agnes AI section in Settings that lists the live configuration and the model catalogue, and configures models in place — one button per model, no hand-editing cordis.patch.yml |
Install
dsh plugin install agnes-ai-for-dsh
Or add it to a profile's package.json and reinstall:
{
"dependencies": {
"agnes-ai-for-dsh": "git+https://github.com/wangyemen/agnes-ai-for-dsh.git"
}
}
Then add the bundle to dsh.profile.bundles and restart DSH.
Configure
The provider is pre-declared by cordis.patch.yml, so the only required step is
the API key:
- Open Settings → Models.
- Find Agnes AI and enter your API key.
- The key is stored by DSH's credential system under
AGNES_AI_API_KEY.
The default endpoint is the China region:
https://api.agnes-ai.cn/v1
To use the Global endpoint, change baseURL to
https://apihub.agnes-ai.com/v1 either in cordis.patch.yml or directly in
Settings → Models.
Model configuration
A first install declares exactly four models, all free, in cordis.patch.yml:
| Kind | Models |
|---|---|
| Chat | agnes-2.5-flash, agnes-3.0-flash |
| Image | agnes-image-2.5-flash |
| Video | agnes-video-2.5-flash |
Everything else in the catalogue ships deliberately unconfigured — the two paid Pro chat models, the older image model, and the paid video model. Settings → Agnes AI shows each model with its state and the button that changes it:
- 未加入配置 → 启用 writes that model into the provider's
modelsarray; - 已启用 → 取消配置 removes it again;
- 一键配置 appears when no provider block is declared at all and writes the complete default profile (the four models above);
- 恢复默认 drops the user-layer array so
cordis.patch.ymlserves again.
Writes go through ctx.remote.settings.mutate() — the same Remote call the
built-in Models page makes — fenced by the revision the panel last read. A
concurrent change therefore answers settings/conflict and the panel asks you
to click again, instead of silently discarding one of the two writes.
Three consequences worth knowing:
- A write lands in the settings user layer, which replaces the patch layer's
modelsarray wholesale. After the first button click, editingcordis.patch.ymlno longer affects the model list until you press 恢复默认. - A language model is the only thing the provider can serve. Image and video
models are declared so the panel can track them and their ids stay visible;
their entries carry only
idandname, because amodelsitem (PiAiModelProfile) requires nothing else. They also appear in the model picker, where selecting one as a chat model fails — they are meant foragnes_image_generation/agnes_video_generationonly. - The API key is not a model. 一键配置 writes the provider declaration; the credential is still entered under Settings → Models.
Usage
Once enabled, the two media tools are available to the model:
agnes_image_generation— text-to-image, image-to-image and multi-image editing. Parameters:model,prompt,size(1K–4K), optionalratio, optionalimage(input URLs), optionalreturn_base64.agnes_video_generation— asynchronous video generation with polling. Parameters:model,prompt, optionalseconds,mode,size,aspect_ratio, and the mode-specific media fields.
Video generation is asynchronous: the tool creates a task and then polls
GET /agnesapi?video_id=…&model_name=… until it settles. agnes-video-2.5-flash
accepts only 720P; agnes-video-2.5 accepts 720P, 1080P, 1K and 2K.
Notes for maintainers
Tool schemas
The host's defineTool does not accept raw JSON Schema — it requires a
property-map DSL whose entries may carry their own required: true, and it
compiles that into object-rooted JSON Schema itself.
That DSL is not valid JSON Schema. If it ever reaches a provider unconverted, the
provider rejects the entire request (WorkBuddy returns HTTP 400, code 11129,
"工具定义不合规"), so every model reply in the session fails — not merely the one
tool. This is not hypothetical: it is exactly what happened when a bare
require() threw inside an ESM module and a silent identity fallback registered
the raw DSL.
The defence is in src/tools.ts:
compileParameters()compiles the DSL here, so the plugin does not depend on the host helper for correctness.assertValidCompiledSchema()/assertAllToolSchemas()verify the compiled form at load and throw on the invalid shape, so a mistake stops the plugin instead of poisoning every request.- Registration re-checks the parameters
defineToolactually produced and refuses to register if the raw DSL survived.
verify.mjs exercises all of this against the real defineTool.
How the settings panel writes configuration
src/client/index.ts writes through the generated Remote namespace
remote.settings, and needs no seam of its own:
writeSettings(NS, [{ op: 'set', path: ['providers', 'agnes-ai', 'models'], value: next }], revision)
remote.settings is reached through ctx.reflect, never injected, so a runtime
without it still renders the panel and a missing write seam reports its own
refusal instead of hiding the whole section.
unset is the only operation that restores inheritance: an empty set would
mean "a route with no models", not "serve the default list".
The pure helpers the buttons call (enableModelOps, disableModelOps,
setProviderOp, restoreModelsOp) are exported from the bundle, and
verify-client.mjs loads the bundle through a stubbed __ModuleLoader__ to
exercise exactly those functions — including a cross-check that
cordis.patch.yml and DEFAULT_MODEL_IDS declare the same models, so the patch
and the panel cannot drift apart.
Model entries use maxTokens, not defaultMaxTokens
defaultMaxTokens is a provider-level fallback. Inside a models entry it
is an unknown field, so it is ignored and the route default (32768) applies
instead. A per-model output ceiling belongs in the entry's own maxTokens.
Build
npm install
npm run build
lib/ is committed, because the package installs from git and the consuming
profile does not run build scripts. Rebuild before committing any src/
change, or the stale lib/ is what ships.
Verify
node verify.mjs # compiled tool schemas vs the real defineTool
node verify-apply.mjs # what apply() actually registers
node verify-output.mjs # every tool result is lossless JSON
node verify-client.mjs # the panel's read and write path, stub module table
License
MIT
No comments yet. Be the first to write one.