- Updated the sandbox integration test to verify JSON fields using snake_case for CCR configuration. - Added detailed documentation for the sandbox API, including properties, methods, and use cases for file operations and command execution. - Enhanced context API documentation to include sandbox operations, improving clarity on available features when sandbox is configured.
14 KiB
14 KiB
Context API
The ctx object provides access to messaging, memory, tracing, and MCP operations.
Properties
interface Context {
chat_id: string; // Chat session ID
assistant_id: string; // Assistant ID
locale: string; // User locale (e.g., "en-us")
theme: string; // UI theme
route: string; // Request route
referer: string; // Request source
metadata: Record<string, any>; // Custom metadata
authorized: Record<string, any>; // Auth info
memory: Memory; // Memory namespaces
trace: Trace; // Tracing API
mcp: MCP; // MCP operations
search: Search; // Search API
agent: Agent; // Agent-to-Agent calls (A2A)
llm: LLM; // Direct LLM calls
sandbox?: Sandbox; // Sandbox operations (optional)
}
Messaging
Send Complete Message
ctx.Send({ type: "text", props: { content: "Hello!" } });
ctx.Send("Hello!"); // Shorthand for text
Streaming Messages
const msgId = ctx.SendStream("Starting...");
ctx.Append(msgId, " processing...");
ctx.Append(msgId, " done!");
ctx.End(msgId);
Update Streaming Message
const msgId = ctx.SendStream({ type: "loading", props: { message: "Loading..." } });
// ... do work ...
ctx.Replace(msgId, { type: "text", props: { content: "Complete!" } });
ctx.End(msgId);
Merge Data
const msgId = ctx.SendStream({ type: "status", props: { progress: 0 } });
ctx.Merge(msgId, { progress: 50 }, "props");
ctx.Merge(msgId, { progress: 100, status: "done" }, "props");
ctx.End(msgId);
Set Field
const msgId = ctx.SendStream({ type: "result", props: {} });
ctx.Set(msgId, "success", "props.status");
ctx.Set(msgId, { count: 10 }, "props.data");
ctx.End(msgId);
Block Grouping
const blockId = ctx.BlockID();
ctx.Send("Step 1", blockId);
ctx.Send("Step 2", blockId);
ctx.Send("Step 3", blockId);
ctx.EndBlock(blockId);
ID Generators
const msgId = ctx.MessageID(); // "M1", "M2", ...
const blockId = ctx.BlockID(); // "B1", "B2", ...
const threadId = ctx.ThreadID(); // "T1", "T2", ...
Memory
Four-level hierarchical memory system:
| Namespace | Scope | Persistence |
|---|---|---|
ctx.memory.user |
Per user | Persistent |
ctx.memory.team |
Per team | Persistent |
ctx.memory.chat |
Per chat | Persistent |
ctx.memory.context |
Per request | Temporary |
Basic Operations
// Get/Set
ctx.memory.user.Set("theme", "dark");
const theme = ctx.memory.user.Get("theme");
// With TTL (seconds)
ctx.memory.context.Set("temp", data, 300);
// Check/Delete
if (ctx.memory.chat.Has("topic")) {
ctx.memory.chat.Del("topic");
}
// Get and delete atomically
const token = ctx.memory.context.GetDel("one_time_token");
// Collection operations
const keys = ctx.memory.user.Keys();
const count = ctx.memory.chat.Len();
ctx.memory.context.Clear();
Counters
const views = ctx.memory.user.Incr("page_views");
const credits = ctx.memory.user.Decr("credits", 5);
Lists
ctx.memory.chat.Push("history", [msg1, msg2]);
const last = ctx.memory.chat.Pop("queue");
const items = ctx.memory.chat.Pull("queue", 5);
const all = ctx.memory.chat.PullAll("queue");
Sets
ctx.memory.user.AddToSet("visited", ["/home", "/about"]);
Array Access
const len = ctx.memory.chat.ArrayLen("messages");
const first = ctx.memory.chat.ArrayGet("messages", 0);
const last = ctx.memory.chat.ArrayGet("messages", -1);
ctx.memory.chat.ArraySet("messages", 0, newMsg);
const slice = ctx.memory.chat.ArraySlice("messages", -10, -1);
const page = ctx.memory.chat.ArrayPage("messages", 1, 20);
const all = ctx.memory.chat.ArrayAll("messages");
Trace
Create Nodes
const node = ctx.trace.Add(
{ query: "input data" },
{
label: "Processing",
type: "process",
icon: "play",
description: "Processing user request"
}
);
Logging
ctx.trace.Info("Starting process");
ctx.trace.Debug("Variable: " + value);
ctx.trace.Warn("Deprecated feature");
ctx.trace.Error("Operation failed");
// Or on node
node.Info("Step completed");
Node Lifecycle
node.SetOutput({ result: data });
node.SetMetadata("duration", 1500);
node.Complete({ status: "done" });
// or
node.Fail("Error message");
Parallel Nodes
const nodes = ctx.trace.Parallel([
{ input: { url: "api1" }, option: { label: "API 1" } },
{ input: { url: "api2" }, option: { label: "API 2" } }
]);
Child Nodes
const parent = ctx.trace.Add({}, { label: "Parent" });
const child = parent.Add({}, { label: "Child" });
MCP
Tools
// List tools
const tools = ctx.mcp.ListTools("server-id");
// Call single tool - returns parsed result directly
const result = ctx.mcp.CallTool("server-id", "tool-name", { arg: "value" });
console.log(result.field); // Direct access to parsed data
// Call multiple sequentially - returns array of parsed results
const results = ctx.mcp.CallTools("server-id", [
{ name: "tool1", arguments: { a: 1 } },
{ name: "tool2", arguments: { b: 2 } }
]);
results.forEach(r => console.log(r));
// Call multiple in parallel - returns array of parsed results
const results = ctx.mcp.CallToolsParallel("server-id", [
{ name: "tool1", arguments: {} },
{ name: "tool2", arguments: {} }
]);
results.forEach(r => console.log(r));
Cross-Server Tool Calls
// Call tools across multiple MCP servers (like Promise.all)
const results = ctx.mcp.All([
{ mcp: "server1", tool: "search", arguments: { q: "query" } },
{ mcp: "server2", tool: "fetch", arguments: { id: 123 } }
]);
// First success wins (like Promise.any)
const results = ctx.mcp.Any([
{ mcp: "primary", tool: "search", arguments: { q: "query" } },
{ mcp: "backup", tool: "search", arguments: { q: "query" } }
]);
// First complete wins (like Promise.race)
const results = ctx.mcp.Race([
{ mcp: "region-us", tool: "ping", arguments: {} },
{ mcp: "region-eu", tool: "ping", arguments: {} }
]);
// Result structure
interface MCPToolResult {
mcp: string; // Server ID
tool: string; // Tool name
result?: any; // Parsed result content
error?: string; // Error if failed
}
Resources
const resources = ctx.mcp.ListResources("server-id");
const data = ctx.mcp.ReadResource("server-id", "resource://uri");
Prompts
const prompts = ctx.mcp.ListPrompts("server-id");
const prompt = ctx.mcp.GetPrompt("server-id", "prompt-name", { arg: "value" });
Search
Single Search
// Web search
const webResult = ctx.search.Web("query", {
limit: 10,
sites: ["example.com"],
time_range: "week"
});
// Knowledge base
const kbResult = ctx.search.KB("query", {
collections: ["docs"],
threshold: 0.7,
graph: true
});
// Database
const dbResult = ctx.search.DB("query", {
models: ["model.name"],
wheres: [{ column: "status", value: "active" }],
limit: 20
});
Parallel Search
// Wait for all
const results = ctx.search.All([
{ type: "web", query: "topic" },
{ type: "kb", query: "topic", collections: ["docs"] }
]);
// First success
const results = ctx.search.Any([
{ type: "web", query: "topic" },
{ type: "kb", query: "topic" }
]);
// First complete
const results = ctx.search.Race([
{ type: "web", query: "topic" },
{ type: "kb", query: "topic" }
]);
Result Structure
interface SearchResult {
type: "web" | "kb" | "db";
query: string;
source: "hook" | "auto" | "user";
items: {
citation_id: string;
title: string;
url: string;
content: string;
score: number;
}[];
error?: string;
}
Agent API
The ctx.agent object provides methods to call other agents from within hooks, enabling agent-to-agent communication (A2A).
Single Agent Call
// Basic call
const result = ctx.agent.Call("assistant-id", messages);
// With options and callback
const result = ctx.agent.Call("assistant-id", messages, {
connector: "gpt-4o",
mode: "chat",
metadata: { source: "hook" },
skip: { history: false, trace: false, output: false },
onChunk: (msg) => {
console.log("Received:", msg.type, msg.props);
return 0; // 0 = continue, non-zero = stop
}
});
Agent Options
interface AgentCallOptions {
connector?: string; // Override LLM connector
mode?: string; // Agent mode ("chat", "task")
metadata?: Record<string, any>; // Custom metadata passed to hooks
skip?: {
history?: boolean; // Skip loading chat history
trace?: boolean; // Skip trace recording
output?: boolean; // Skip output to client
keyword?: boolean; // Skip keyword extraction
search?: boolean; // Skip search
content_parsing?: boolean; // Skip content parsing
};
onChunk?: (msg: Message) => number; // Callback (0=continue, non-zero=stop)
}
Parallel Agent Calls
// Wait for all agents to complete (like Promise.all)
const results = ctx.agent.All([
{ agent: "agent-1", messages: [...] },
{ agent: "agent-2", messages: [...] }
]);
// Return first successful result (like Promise.any)
const results = ctx.agent.Any([
{ agent: "agent-1", messages: [...] },
{ agent: "agent-2", messages: [...] }
]);
// Return first completed result (like Promise.race)
const results = ctx.agent.Race([
{ agent: "agent-1", messages: [...] },
{ agent: "agent-2", messages: [...] }
]);
// With global callback for all responses
const results = ctx.agent.All([
{ agent: "agent-1", messages: [...] },
{ agent: "agent-2", messages: [...] }
], {
onChunk: (agentId, index, msg) => {
console.log(`Agent ${agentId} [${index}]:`, msg.type);
return 0;
}
});
Result Structure
interface AgentResult {
agent_id: string;
response?: Response;
content?: string;
error?: string;
}
Message Object (onChunk callback)
interface Message {
type: string; // "text", "thinking", "tool_call", "error"
props?: Record<string, any>; // e.g., { content: "Hello" }
chunk_id?: string; // C1, C2, ...
message_id?: string; // M1, M2, ...
delta?: boolean; // Incremental update flag
}
Sandbox API
The ctx.sandbox object provides access to sandbox operations when the assistant is configured with a sandbox executor (e.g., Claude CLI). Only available when sandbox is configured in package.yao.
Properties
ctx.sandbox.workdir // Workspace directory path (e.g., "/workspace")
File Operations
// Read file
const content = ctx.sandbox.ReadFile("config.json");
// Write file
ctx.sandbox.WriteFile("output.txt", "Hello World");
// List directory
const files = ctx.sandbox.ListDir("src");
files.forEach(f => console.log(f.name, f.is_dir, f.size));
Command Execution
// Execute command (returns stdout)
const output = ctx.sandbox.Exec(["npm", "test"]);
// Handle errors
try {
ctx.sandbox.Exec(["git", "commit", "-m", "fix"]);
} catch (e) {
console.error("Command failed:", e.message);
}
FileInfo Structure
interface FileInfo {
name: string; // File/directory name
size: number; // Size in bytes
is_dir: boolean; // True if directory
}
Use Cases
// Prepare workspace before execution
function Create(ctx, messages) {
if (ctx.sandbox) {
ctx.sandbox.WriteFile("config.json", JSON.stringify({ debug: true }));
}
return { messages };
}
// Post-process results
function Next(ctx, payload) {
if (ctx.sandbox && !payload.error) {
const files = ctx.sandbox.ListDir("output");
return { data: { generated: files.map(f => f.name) } };
}
return null;
}
LLM API
The ctx.llm object provides direct access to LLM connectors for streaming completions.
Single LLM Call
// Basic streaming call
const result = ctx.llm.Stream("gpt-4o", [
{ role: "user", content: "Hello" }
]);
// With options and callback
const result = ctx.llm.Stream("gpt-4o", messages, {
temperature: 0.7,
max_tokens: 2000,
onChunk: (msg) => {
console.log("Chunk:", msg.props?.content);
return 0;
}
});
Parallel LLM Calls
// Wait for all LLM calls (like Promise.all)
const results = ctx.llm.All([
{ connector: "gpt-4o", messages: [...] },
{ connector: "claude-3", messages: [...] }
]);
// Return first successful result (like Promise.any)
const results = ctx.llm.Any([
{ connector: "gpt-4o", messages: [...] },
{ connector: "claude-3", messages: [...] }
]);
// Return first completed result (like Promise.race)
const results = ctx.llm.Race([
{ connector: "gpt-4o", messages: [...] },
{ connector: "claude-3", messages: [...] }
]);
// With global callback
const results = ctx.llm.All([
{ connector: "gpt-4o", messages: [...] },
{ connector: "claude-3", messages: [...] }
], {
onChunk: (connectorId, index, msg) => {
console.log(`LLM ${connectorId} [${index}]:`, msg.type);
return 0;
}
});
LLM Options
interface LlmOptions {
temperature?: number;
max_tokens?: number;
max_completion_tokens?: number;
top_p?: number;
presence_penalty?: number;
frequency_penalty?: number;
stop?: string | string[];
user?: string;
seed?: number;
tools?: object[];
tool_choice?: string | object;
response_format?: { type: string; json_schema?: object };
reasoning_effort?: string;
onChunk?: (msg: Message) => number;
}
Result Structure
interface LlmResult {
connector: string;
response?: CompletionResponse;
content?: string;
error?: string;
}