Not yet available. The Agent SDK is tracked to be built — today, use the Client SDKs against the live chat completions API. The example below illustrates the intended API.
A skills loader is a pattern for agents that need to pick from a large library of tools. Instead of passing every tool on every call — which inflates the prompt and can confuse the model — the agent first asks the model which skills it needs, then loads and exposes only those for the working loop.
- Describe available skills to the model (name + one-line description).
- The model selects which skills it needs for this task.
- Load those skill implementations.
- Run the main agent loop with the loaded tools.
export interface SkillMeta {
name: string;
description: string;
load: () => Promise<Tool>;
}
export const SKILLS: SkillMeta[] = [
{
name: "get_weather",
description: "Get current weather conditions for a city.",
load: () => import("./weather").then((m) => m.weatherTool),
},
{
name: "search_web",
description: "Search the web and return a summary of the top results.",
load: () => import("./search").then((m) => m.searchTool),
},
{
name: "run_calculation",
description: "Evaluate a mathematical expression and return the result.",
load: () => import("./calculator").then((m) => m.calculatorTool),
},
{
name: "send_email",
description: "Send an email to a recipient (requires approval).",
load: () => import("./email").then((m) => m.emailTool),
},
];
import { OpenDunes } from "@opendunes/agent-sdk";
import { SKILLS } from "./skills/registry";
const client = new OpenDunes({ apiKey: process.env.OPENDUNES_API_KEY! });
async function runWithSkills(task: string) {
const selectionResult = await client.callModel({
model: "anthropic/claude-sonnet-5",
messages: [
{
role: "system",
content: `You are a task planner. Given a task, output a JSON array of skill names you will need.
Available skills:\n${SKILLS.map((s) => `- ${s.name}: ${s.description}`).join("\n")}
Reply with ONLY a JSON array, e.g. ["get_weather", "run_calculation"].`,
},
{ role: "user", content: task },
],
temperature: 0,
});
const selectedNames: string[] = JSON.parse(selectionResult.output);
const tools = await Promise.all(
SKILLS.filter((s) => selectedNames.includes(s.name)).map((s) => s.load())
);
console.log(`Loaded ${tools.length} skill(s): ${tools.map((t) => t.name).join(", ")}`);
const result = await client.callModel({
model: "anthropic/claude-sonnet-5",
messages: [{ role: "user", content: task }],
tools,
stopConditions: [{ type: "no_tool_calls" }, { type: "max_turns", maxTurns: 10 }],
stream: true,
onToken: (delta) => process.stdout.write(delta),
});
console.log(`\nDone in ${result.items.filter((i) => i.type === "tool_call").length} tool call(s).`);
}
runWithSkills("What's the weather in Algiers, and how many degrees warmer is it than the 20°C average?");
- The selection turn is cheap — it uses a small prompt and returns a tiny JSON array.
- The main loop only sees the tools it needs, keeping the context window small and reducing irrelevant tool calls.
- Adding a new skill means adding one entry to
SKILLS — the rest of the agent is unchanged.
For skills that require approval (like send_email), the approval function on the tool handles it automatically — no changes needed in the loader:
export const emailTool: Tool = {
name: "send_email",
approval: async (call) => {
return await promptUser(`Send email to ${call.arguments.to}? (y/n)`);
},
};
- Tools — tool definition and dispatch.
- Tool Approval & State Persistence — approval callbacks.
- Dynamic Parameters — vary tools per turn.
- Example: Weather Tool — a simpler single-tool example.