@codefundi/dispersl-sdk
v0.1.12
Published
Production TypeScript SDK for Dispersl API
Readme
Install
pnpm add @codefundi/dispersl-sdkRequirements
- Node.js
>=18 - TypeScript
>=5(recommended for best type support)
Quick Start
import { AgenticExecutor, DisperslClient } from "@codefundi/dispersl-sdk";
const client = new DisperslClient({
baseUrl: process.env.DISPERSL_API_URL ?? "https://api.dispersl.com/v1",
apiKey: process.env.DISPERSL_API_KEY ?? "",
timeoutMs: 120_000,
retryAttempts: 3
});
const executor = new AgenticExecutor(client);
const result = await executor.runPlanAndAgentLoop({
prompt: "Plan and implement a production webhook pipeline",
agentChoices: "auto", // or ["architect", "security-auditor", "release-manager"]
executionSequence: "sequential" // or "parallel" for concurrent agent execution
});
console.log(result.taskId, result.events.length, result.toolResults.length);SDK Capabilities
- Typed HTTP client with bearer auth, timeout, retry, and status-to-error mapping.
- Full endpoint coverage for
agent/completion,agent/plan, and agent lifecycle APIs. - Incremental NDJSON stream parser with split-buffer handling and parse errors.
- NDJSON chunk normalization (inline
tool_callsin content, top-leveltool_calls→tools) at parse boundary. - Handover parser supporting nested and double-serialized tool arguments.
- Sequential and parallel agent execution modes for flexible workflow orchestration.
- Task continuation support via
taskIdfor multi-phase workflows. - MCP config loading from
.dispersl/mcp.jsonwith env interpolation and runtime overrides. - Agentic execution loop with plan-to-agent transitions, tool execution, and end-session detection.
- Grouped multi-tool responses: one API stream turn → N local executions → one continuation prompt.
Client API Surface
Agent execution endpoints
| Method | Request | Endpoint | Returns |
| --- | --- | --- | --- |
| executeAgentCompletion | AgentCompletionRequest | POST /agent/completion | ReadableStream<Uint8Array> |
| executePlan | AgentPlanRequest | POST /agent/plan | ReadableStream<Uint8Array> |
Agent plan choices
AgentPlanRequest.agent_choice supports:
"auto"(use automatic agent selection)string[]of explicit custom agentname_idvalues
When "auto" is used, the SDK normalizes the wire payload to ["auto"] for API compatibility.
Execution modes
AgentPlanRequest.execution_sequence controls agent parallelism:
"sequential"(default): agents execute one after another"parallel": multiple agents execute concurrently when handed over from plan
For parallel execution, use parallelConcurrency in runPlanAndAgentLoop to limit simultaneous agent runs.
Resource endpoints
| Domain | Method | Endpoint |
| --- | --- | --- |
| Agents | getAgents | GET /agents?limit&nextToken |
| Agents | createAgent | POST /agents/create |
| Agents | editAgent | POST /agents/edit/{id} |
| Agents | getAgent | GET /agents/{id} |
| Agents | deleteAgent | DELETE /agents/{id} |
Agent lifecycle fields and stats
getAgents returns a paginated envelope with:
- pagination:
limit,hasNext,hasPrev,nextToken,prevToken - per-agent lifecycle + stats fields:
id,name_id,name,description,prompt,model,category,stars_count,clone_count,created_at
getAgent returns per-agent detail fields including lifecycle state:
public,active,updated_at
Create/edit request support:
- create:
name,prompt, optionaldescription,model,category,public - edit: optional
name,prompt,description,model,category,public,active
Execution Loop Behavior
AgenticExecutor.runPlanAndAgentLoop provides:
- start state:
plan - max loop guard (
maxLoops, default50) - execution sequence control (
executionSequence:"sequential"or"parallel", detected from plan metadata) - parallel concurrency limit (
parallelConcurrencyfor controlling simultaneous agent runs) - handover handling (
handover_task) - explicit completion handling (
end_sessionandfinish_task) - task continuation support via
taskIdfor multi-phase workflows - continuation prompts when tools run without explicit handover/end
- optional tool execution callback via
ToolExecutorFn
Direct Single-Agent Completion Loop
Use runAgentCompletionLoop to execute POST /agent/completion directly for one name_id until end_session.
const executor = new AgenticExecutor(client);
const result = await executor.runAgentCompletionLoop({
nameId: "architect",
prompt: "Review this backend design and produce a migration plan",
maxLoops: 50
});Behavior:
- fixed agent identity across turns (
nameId) - no handover transition to other agents
- continues until
end_session, no tool calls, ormaxLoopsreached
Task Continuation
Pass taskId to resume work on an existing task and retain context across invocations:
// First run: initial execution
const firstRun = await executor.runPlanAndAgentLoop({
prompt: "Design the system architecture",
agentChoices: "auto"
});
// Continue after initial completion
const result = await executor.runPlanAndAgentLoop({
prompt: "Now implement the core modules",
agentChoices: "auto",
taskId: firstRun.taskId
});Core Types
| Type | Purpose |
| --- | --- |
| DisperslConfig | client init config (baseUrl, apiKey, timeout, retries) |
| AgentCompletionRequest | completion request (name_id + base fields) |
| AgentRequestBase | common fields for agent endpoints |
| AgentPlanRequest | plan request (agent_choice + base fields) |
| AgentCreateRequest | create payload (name, prompt, optional metadata) |
| AgentEditRequest | editable lifecycle fields (name, prompt, model, active, ...) |
| NDJSONChunk | stream chunk payload format |
| ToolCall | tool invocation structure from stream chunks |
| ToolResult | local tool execution result (toolCallId?, toolName, status, output, error?) |
| ToolExecutorFn | host callback that runs a ToolCall locally |
| StreamTurnResult | grouped outcome of one API stream (pendingTools, turnToolResults, nextAction, ...) |
| parseAgentStream | collect all tools from one stream, execute as a batch, return grouped results |
| buildGroupedToolFeedbackPrompt | format N tool results into one continuation prompt |
| PaginatedResponse<T> | list endpoints with pagination envelope |
Error Model
| Error | Trigger |
| --- | --- |
| AuthenticationError | 401 or 403 |
| NotFoundError | 404 |
| ConflictError | 409 |
| RateLimitError | 429 |
| ValidationError | other 4xx |
| ServerError | 5xx |
| TimeoutError | request timeout/abort |
| StreamParseError | NDJSON line/tail parse failure |
| ToolExecutionError | tool callback returns error status |
| HandoverError | handover contract failure (reserved class) |
Tool Setup Guide
Dispersl agents call tools in turns. When the model requests N tools in one response, the SDK collects all N calls from the stream, executes them locally, and sends one grouped continuation prompt with all N results.
Two-part wiring
- Register tool schemas so the API/model knows what is available (
McpRegistry.register). - Provide
ToolExecutorFnso your host runs tools when the agent calls them.
The execute function on McpRegistry.register(...) is catalog metadata. Runtime execution always goes through ToolExecutorFn.
Registering custom tools
import { AgenticExecutor, DisperslClient } from "@codefundi/dispersl-sdk";
const client = new DisperslClient({ baseUrl: "...", apiKey: "..." });
const executor = new AgenticExecutor(client, async (tool) => {
if (tool.function?.name === "get_github_user") {
const args = JSON.parse(tool.function.arguments) as { username: string };
const res = await fetch(`https://api.github.com/users/${args.username}`);
return {
toolCallId: tool.id,
toolName: "get_github_user",
status: "success",
output: JSON.stringify(await res.json()),
};
}
return {
toolCallId: tool.id,
toolName: tool.function?.name ?? "unknown",
status: "error",
output: "",
error: "Unsupported tool",
};
});
executor.mcpTools.register({
name: "get_github_user",
description: "Fetch a public GitHub user profile by username.",
parameters: {
type: "object",
additionalProperties: false,
properties: {
username: { type: "string", description: "GitHub username" },
},
required: ["username"],
},
execute: async () => "handled-by-host-executor",
});Host-defined / built-in tools (grep, list, read, etc.)
Register each local tool on the same registry. Example names used by Code Fundi:
read_file,list_files,grep_workspace,write_to_file,edit_file,execute_command
for (const tool of hostBuiltinTools) {
executor.mcpTools.register(tool);
}All registered tools are sent to the API on every request:
const runtimeTools = executor.mcpTools.list().map((tool) => ({
name: tool.name,
description: tool.description,
inputSchema: tool.parameters,
}));
await client.executeAgentCompletion({
name_id: "coder",
prompt: "Scan the repo",
mcp: { ...mergedMcpConfig, tools: runtimeTools },
});AgenticExecutor loops do this automatically.
.dispersl/mcp.json
Place MCP server configuration at .dispersl/mcp.json (relative to your project cwd):
{
"version": "1",
"servers": {
"code-fundi": {
"transport": "stdio",
"command": "npx",
"args": ["-y", "@codefundi/mcp-server"],
"env": { "CODEFUNDI_API_KEY": "${CODEFUNDI_API_KEY}" },
"enabled": true
}
}
}Load and merge at runtime:
import { McpConfigLoader } from "@codefundi/dispersl-sdk";
const loader = new McpConfigLoader();
const local = loader.loadFromDefaultPath(process.cwd());
const merged = loader.merge(local, runtimeOverride);${ENV_VAR} placeholders are interpolated from process.env.
Grouped tool responses
When the agent emits multiple tools in one turn (e.g. read_file + grep_workspace + list_files):
parseAgentStreamcollects all tool calls from the NDJSON stream (top-leveltools[], inlinetool_calls, streaming content).- Non-control tools execute locally (sequential by default).
- One continuation prompt is built via
buildGroupedToolFeedbackPromptcontaining all results:
Tool results (3 tools executed this turn):
1. [call_abc] read_file => SUCCESS => ...
2. [call_def] grep_workspace => SUCCESS => ...
3. [call_ghi] list_files => SUCCESS => ...Custom streaming hosts can use parseAgentStream directly:
import { parseAgentStream, isControlToolCall } from "@codefundi/dispersl-sdk";
const turn = await parseAgentStream(stream, {
toolExecutor: myExecutor,
captureToolErrors: true,
executeLocally: (tool) => !isControlToolCall(tool),
onChunk: (chunk) => console.log(chunk.message),
});
if (turn.turnToolResults.length > 0) {
const nextPrompt = buildGroupedToolFeedbackPrompt({
agentId: "coder",
previousPrompt: currentPrompt,
results: turn.turnToolResults,
mode: "single",
});
}Control tools
These are not executed locally (workflow signals only):
end_session,finish_task,handover_task
They may appear in the same turn as dynamic tools. Local tool results are still grouped and sent back first.
Mixed turns (builtins + MCP + custom)
Grouping is tool-name-agnostic. A single turn may mix read_file, grep_workspace, CodeFundi MCP tools, and custom registry tools. All are collected, executed via ToolExecutorFn, and returned in one grouped prompt.
MCP Support
McpConfigLoader and McpRegistry support:
- loading
.dispersl/mcp.json ${ENV_VAR}interpolation- merge of local config with runtime overrides
- runtime custom tool registration:
register(tool)unregister(name)list()
Development
pnpm install
pnpm run lint
pnpm run typecheck
pnpm run test -- --run
pnpm run buildExample Quickstarts
End-to-end quickstarts live in root examples/ts:
examples/ts/plan-handover-loop.tsexamples/ts/single-agent-completion.tsexamples/ts/task-insight-progress.tsexamples/ts/agent-lifecycle-and-stats.tsexamples/ts/mcp-custom-agent-flow.ts
Release
- Package name:
@codefundi/dispersl-sdk - TS release workflow:
.github/workflows/release-typescript.yml - Trigger: push tag
ts-v*
