@archships/dim-agent-sdk
v0.0.36
Published
An agent-first TypeScript SDK with provider adapters, sessions, hooks, plugins, and runtime gateways.
Readme
@archships/dim-agent-sdk
An agent-first TypeScript SDK with canonical multi-provider contracts, session/tool loop, hook-based plugins, runtime gateways, and builtin local coding tools.
Install
Recommended runtime: Node.js >=18.
npm install @archships/dim-agent-sdkQuick start
import { createAgent, createModel, createOpenAIAdapter } from '@archships/dim-agent-sdk'
const model = createModel(
createOpenAIAdapter({
apiKey: process.env.OPENAI_API_KEY,
baseUrl: 'https://api.openai.com/v1',
defaultModel: 'gpt-4o-mini',
}),
)
const agent = createAgent({
model,
cwd: process.cwd(),
})
const session = await agent.createSession({
systemPrompt: 'You are a coding agent. Use tools when needed.',
})
const itemId = session.send(
'Create hello.txt in the current directory and write hello world into it.',
)
for await (const event of session.receive()) {
if (event.itemId !== itemId) continue
if (event.type === 'text_delta') process.stdout.write(event.delta)
if (event.type === 'done') console.log(event.message.content)
}When a session has tools available, the runtime also injects an internal tool-call JSON contract into the effective system prompt.
Models are told to emit one complete JSON object per tool call.
If a streamed tool call still arrives with malformed pre-execution JSON, the SDK now surfaces it as a failed recoverable tool_result, stores that failed result in session history when the tool name is known, and keeps the run alive instead of failing the whole item immediately.
Provider debug logging
Builtin provider adapters support optional adapter-level request logging.
Pass debug.logFilePath when constructing the adapter to write JSONL records for normalized model requests, AI SDK call options, final HTTP requests, and normalized provider errors.
This feature is off by default, and secret-bearing fields such as authorization / apiKey are redacted unless you explicitly set redactSecrets: false.
import path from 'node:path'
import { createModel, createOpenAIAdapter } from '@archships/dim-agent-sdk'
const model = createModel(
createOpenAIAdapter({
apiKey: process.env.OPENAI_API_KEY,
baseUrl: 'https://api.openai.com/v1',
defaultModel: 'gpt-4o-mini',
debug: {
logFilePath: path.join(process.cwd(), 'logs/provider-debug.jsonl'),
},
}),
)Manual
- Developer manual: docs/manual/README.md
- Chinese guide: docs/manual/zh/README.md
- English guide: docs/manual/en/README.md
- SDK API reference (zh): docs/manual/zh/sdk-api-reference.md
- Plugin API reference (zh): docs/manual/zh/plugin-api-reference.md
- SDK API reference (en): docs/manual/en/sdk-api-reference.md
- Plugin API reference (en): docs/manual/en/plugin-api-reference.md
- Host integration cookbook: start in docs/manual/en/sdk.md or docs/manual/zh/sdk.md
Included core capabilities
- Canonical content / message / tool / model / state contracts
- Canonical message content aligned to AI SDK V3 prompt parts:
textandfile createAgent()->Agent->Session- Queue-first session flow:
send(),sendBatch(),steer(),receive(),getQueueStatus() - Long-lived host hygiene:
Session.dispose()releases session-scoped runtime registrations and controller-owned resources when a session is no longer needed - Session events:
text_delta, optionalthinking_delta,tool_call_start,tool_call_args_delta,tool_call_end,tool_call,plugin_event, recoverable and regulartool_result,done,error - Provider adapters:
openai-compatible,openai-responses,anthropic,gemini,zenmux,aihubmix,aihubmix-responses,moonshotai,deepseek,xai,xai-responses - Builtin tools:
read,write,edit,exec - Hook-first plugin integration
- Host-layer ordered subagents with
SubagentOrchestrator,InProcessSessionSubagentExecutor,ProcessSessionSubagentExecutor, andSubagentProcessRegistry - Runtime gateways: file system, git, exec, network, model
- Namespaced plugin session state with snapshot restore support
- In-memory and file-based persistence
Hook support
Supported public hooks in the current runtime:
run.starttool.beforeExecutetool.afterExecutecontext.compact.beforenotify.messagerun.stoprun.endsession.error
Reserved / experimental hook names that are typed but not wired into the runtime yet:
subagent.stop
Current failure policy:
- Sync middleware is blocking and fail-fast
- Observers are best-effort
mode: 'async'is observer-onlytimeoutMsapplies per hook handler
Official plugin packages
| Package | Support level | Notes |
| ------------------------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------- |
| @archships/dim-plugin-auto-compact | supported | Official auto compaction plugin; requires compaction.ownerPluginId: 'auto-compact' |
| @archships/dim-plugin-grep-glob | supported | Registers grep and glob filesystem tools |
| @archships/dim-plugin-mcp-client | supported | Controller-driven MCP client for session-scoped server connections and tool injection; use @archships/dim-agent-sdk >= 0.0.23 and @archships/dim-plugin-api >= 0.0.9; host-only prompt/context/tool injection remains compatible |
| @archships/dim-plugin-skills | supported | Metadata-first file-backed skills plugin with always-on catalog metadata, model-callable activation tools, and a session controller |
| @archships/dim-plugin-plan-mode | supported | Session-scoped planning guardrail with host-data-backed drafts, restricted exec, and plan_read / plan_write; use @archships/dim-agent-sdk >= 0.0.23 and @archships/dim-plugin-api >= 0.0.9 |
| @archships/dim-plugin-memory | experimental | Placeholder package; not part of the current supported surface yet |
| @archships/dim-plugin-web | experimental | Placeholder package; not part of the current supported surface yet |
| @archships/dim-plugin-scheduler | experimental | Placeholder package; not part of the current supported surface yet |
| @archships/dim-plugin-research-mode | experimental | Evidence-first research guardrail with a session controller, staged prompt injection, research state, and delegate_tasks guidance |
Compaction and state model
session.messagesalways keeps the full original history for UI and restoresystemPromptis text-only; user messages can carry mixedtext+filecontent- Canonical request projection is controlled by SDK compaction state:
cursor,systemSegments,checkpoints Session.getStatus()returns the canonical read-only session status snapshot used by hook runtime contextSession.getPlugin(pluginId)returns a session-scoped plugin controller when the plugin exposes oneSession.dispose()unregisters the session from compaction and plugin-state services, and also disposes session-scoped plugin controllers such asmcp-client- Plugins can persist their own namespaced session state through
pluginState - If
compaction.ownerPluginIdis configured, only that plugin can write canonical compaction through plugin services Session.compact()remains available as an app-level overrideestimateByHeuristic({ messages, tools, model })is exported for hosts and plugins that want the same default request-budget estimate the SDK uses whencompaction.estimatoris unset;fileblocks use a fixed token budget instead of raw base64 length- If threshold compaction still cannot fit the next request, the runtime can do one bounded recovery pass that rewrites only the newest oversized result payloads (
tooloutputs and subagent parent-commit packages) into short overflow summaries before finally raisingcontext_compaction_required - Hook handlers receive the same canonical status through
context.status, without exposing full message history or other plugins' state
Provider notes
createOpenAIAdapter(): OpenAI-compatible Chat Completions style; mapsreasoning_content/reasoningintothinking_deltaand replays assistantthinkingback upstreamcreateOpenAIResponsesAdapter(): official OpenAI Responses API; maps reasoning summaries intothinking_delta, sends full canonical history by default, and only reusespreviousResponseIdwhenusePreviousResponseId: truecreateAnthropicAdapter(): maps Claude thinking blocks intothinking_delta; when overridingbaseUrl, the adapter appends/v1if it is missing; Anthropic-compatible routes now auto-inject explicit prompt caching unlesscache.modeis set to'off'createGeminiAdapter(): maps Gemini thought parts intothinking_deltacreateZenMuxAdapter(): ZenMux adapter; routesanthropic/*models throughhttps://zenmux.ai/api/anthropic/v1/messagesand sends every other model through the official ZenMux OpenAI-compatible endpoint athttps://zenmux.ai/api/v1/chat/completions.baseUrlis kept for compatibility but ignored at runtime.createAihubmixAdapter()/createAihubmixResponsesAdapter(): AIHubMix chat + responses adapters; theresponsesvariant now sends full canonical history by default and only reusespreviousResponseIdwhenusePreviousResponseId: true. If you omitapiKey, you must inject the real upstream authentication inside customfetch.createMoonshotAIAdapter(): MoonshotAI language model adapter with thinking budget mappingcreateDeepSeekAdapter(): DeepSeek chat adapter with reasoning ->thinking_deltacreateXaiAdapter()/createXaiResponsesAdapter(): xAI chat + responses adapters; theresponsesvariant now sends full canonical history by default and only reusespreviousResponseIdwhenusePreviousResponseId: true- Official adapters use realtime upstream streaming by default. Set adapter
streamMode: 'buffered'or requeststreamMode: 'buffered'to replay a completed response instead. - Session runtime fills missing
maxOutputTokenswith4000aftermodel.requestmiddleware runs. Directmodel.stream()calls still pass through whatever the caller provides. - The SDK no longer injects implicit workspace context into model requests; if the model needs file or Git state, let it call tools explicitly.
- Builtin providers are also available from public subpaths such as
@archships/dim-agent-sdk/providers/openaiand@archships/dim-agent-sdk/providers/xai-responses - Published packages ship these public entrypoints directly; consumers should not import
dist/src/*or patchnode_modulesafter install - Other deep internal imports are not part of the public API, even though package-local tests use path aliases against
src/* - Custom providers can follow the same factory pattern via
@archships/dim-agent-sdk/providers/core; implementstream()for realtime deltas,generate()for buffered results, or both
Anthropic-compatible prompt caching defaults:
cache.modedefaults to'auto'cache.ttldefaults to'5m'cache: { mode: 'off' }disables SDK-managedcache_controlinjection- explicit
providerOptions.anthropic.cacheControlon a message, content block, or tool definition is forwarded unchanged and disables auto injection for that request
import { createProviderFactory } from '@archships/dim-agent-sdk/providers/core'Demo
Provider-free demos:
pnpm run demo:host: approval + notification host control-plane walkthroughpnpm run demo:persistence:FileStateStore+session.save()+restoreSession()walkthroughpnpm run demo:plan-mode: official session-scoped plugin controller +hostDataDirwalkthroughpnpm run demo:compaction: scripted compaction runtime walkthroughpnpm run demo:auto-compact: scripted official auto compact plugin demopnpm run demo:subagents: scripted ordered subagent walkthrough forin-processand child-ownedprocessmodes
Provider-backed demos:
pnpm run demo:openai: builtin tools smoke demopnpm run demo:hooks: Hook v2 scenario runner
Repo demo files:
packages/dim-agent-sdk/demo/host-control-plane-scripted.tspackages/dim-agent-sdk/demo/persistence-scripted.tspackages/dim-agent-sdk/demo/plan-mode-plugin.tspackages/dim-agent-sdk/demo/compaction-scripted.tspackages/dim-agent-sdk/demo/auto-compact-plugin.tspackages/dim-agent-sdk/demo/subagents-scripted.tspackages/dim-agent-sdk/demo/openai-tools.tspackages/dim-agent-sdk/demo/openai-hooks.ts
demo:hooks currently runs these provider-backed scenarios:
lifecycleapproval-denysynthetic-resultnotification-controlstop-finalize
Provider-backed demos use createOpenAIAdapter() against the OpenAI-compatible Chat API and require DIM_TEST_API_KEY, DIM_TEST_BASE_URL, and optional DIM_TEST_MODEL_ID.
Plan mode v2 notes:
- plan drafts live under
<hostDataDir>/plans/<sessionId>/plan.md - host applications drive plan mode through
session.getPlugin('plan-mode') plan_writeis the only writable artifact exposed to the model while plan mode is activeenable()/disable()changes affect the next run, not the current in-flight runagent.deleteSession(sessionId)removes both the persisted snapshot and the matching plan draft directory
Testing
Local repository verification is split into three layers:
- Local workspace development expects Node.js
20.19+or22.12+because repository verification uses officialVite 7 + Vitest + Oxlint. pnpm run test: full local regression, including deterministictest/e2e/*.e2e.test.tspnpm run test:e2e: deterministic end-to-end workflows forplan-mode, code-agent tool loops, auto-compact restore, and approval / permission boundariespnpm run test:plugins: focused plugin contract and integration testspnpm run test:smoke: env-gated provider smoke forprovider-tools,provider-hooks,provider-plan-mode, andprovider-subagent-process; excludes long-taskpnpm run test:smoke:providers: focused multi-provider builtin-tool smoke intest/smoke/provider-tools.smoke.tspnpm run test:smoke:long-task: default manual OpenAI-compatible long-task smoke; it currently aliases theguidescase without subagentspnpm run test:smoke:long-task:subagents: the same defaultguidescase with host-owned delegation plus child-ownedprocesssubagents enabled throughDIM_TEST_LONG_TASK_SUBAGENTS=1pnpm run test:smoke:long-task:guides/pnpm run test:smoke:long-task:guides:subagents: explicit beginner-guide generation case formodelinfo-clipnpm run test:smoke:long-task:rust-port/pnpm run test:smoke:long-task:rust-port:subagents: review-oriented Rust port case for the same reference repo
The provider smoke layer reuses:
DIM_TEST_API_KEYDIM_TEST_BASE_URLDIM_TEST_BASE_URL_ANTHROPIC(optional; enables the Anthropic smoke lane)DIM_TEST_MODEL_ID(optional)DIM_TEST_LONG_TASK_CASE(optional;guidesby default, also supportsrust-port)DIM_TEST_LONG_TASK_SUBAGENTS(optional; only1enables the subagent mode for the long-task smoke)
The focused multi-provider tools smoke loads repo-root .env.smoke automatically. Start from .env.smoke.example, fill only the providers you want to run, and use per-provider keys such as DIM_TEST_OPENAI_API_KEY, DIM_TEST_OPENAI_MODEL_ID, and optional DIM_TEST_OPENAI_BASE_URL. ZenMux is the exception: DIM_TEST_ZENMUX_BASE_URL is ignored because createZenMuxAdapter() now pins the official ZenMux endpoints, and you can optionally add DIM_TEST_ZENMUX_ANTHROPIC_MODEL_ID to create a second ZenMux smoke lane that exercises the Anthropic-compatible route. That smoke keeps its temporary workdirs under os.tmpdir()/dim-sdk-provider-smoke/<provider-id>/run-* and removes each run directory after the test finishes.
Smoke tests assert stable invariants such as tool calls, plugin_event, notifications, and on-disk side effects. They intentionally avoid exact natural-language output matching unless the smoke is explicitly about artifact structure.
The long-task smoke is tuned for diagnosis as much as pass/fail: it runs only through the OpenAI-compatible adapter, reuses a generic harness plus named cases, and preserves both the final workspace and debug logs for inspection. The default guides case generates beginner-friendly Markdown guides for every tracked file; the rust-port case asks the model to translate the same reference repo into a reviewable Rust cargo project. Every run now prints and writes a unified summary with host rounds, model turns, tool-call count, total tokens, cache/token detail breakdowns when the provider exposes them, delegation/process-batch counts, and case-specific stats. It also writes a full-fidelity session timeline array to logs/long-task-session.json, alongside the staged debug logs in logs/long-task-smoke.jsonl, so parent and delegated child context can be replayed during debugging. Those staged diagnostics now include explicit compaction_notification and compaction_state_after_hook entries, which makes it much easier to see whether auto compact triggered and why it still failed to bring the request back under budget. Ordered subagents remain opt-in through pnpm run test:smoke:long-task:subagents or DIM_TEST_LONG_TASK_SUBAGENTS=1.
