- Changed property names from `MCP` to `mcp` and `Trace` to `trace` in the Context object for consistency with JavaScript naming conventions. - Updated related tests and documentation to reflect these changes, ensuring clarity and uniformity across the API. - Enhanced examples in JSAPI.md to demonstrate the new property names, improving developer guidance.
1700 lines
42 KiB
Markdown
1700 lines
42 KiB
Markdown
# Context JavaScript API Documentation
|
|
|
|
## Overview
|
|
|
|
The Context JavaScript API provides a comprehensive interface for interacting with the Yao Agent system from JavaScript/TypeScript hooks (Create, Next). The Context object exposes agent state, configuration, messaging capabilities, trace operations, and MCP (Model Context Protocol) integrations.
|
|
|
|
## Context Object
|
|
|
|
The Context object is automatically passed to hook functions and provides access to the agent's execution environment.
|
|
|
|
### Basic Properties
|
|
|
|
```typescript
|
|
interface Context {
|
|
// Identifiers
|
|
chat_id: string; // Current chat session ID
|
|
assistant_id: string; // Assistant identifier
|
|
|
|
// Configuration
|
|
locale: string; // User locale (e.g., "en", "zh-cn")
|
|
theme: string; // UI theme preference
|
|
accept: string; // Output format ("standard", "cui-web", "cui-native", etc.)
|
|
route: string; // Request route path
|
|
referer: string; // Request referer
|
|
|
|
// Client Information
|
|
client: {
|
|
type: string; // Client type
|
|
user_agent: string; // User agent string
|
|
ip: string; // Client IP address
|
|
};
|
|
|
|
// Dynamic Data
|
|
metadata: Record<string, any>; // Custom metadata (empty object if not set)
|
|
authorized: Record<string, any>; // Authorization data (empty object if not set)
|
|
|
|
// Objects
|
|
space: Space; // Shared data space for passing data between requests
|
|
trace: Trace; // Trace object for debugging and monitoring
|
|
mcp: MCP; // MCP object for external tool/resource access
|
|
}
|
|
```
|
|
|
|
## Methods
|
|
|
|
### Send Messages
|
|
|
|
The Context provides several methods for sending messages to the client:
|
|
|
|
| Method | Description | Auto `message_end` | Updatable |
|
|
| ------------------------------------ | --------------------------- | ------------------ | --------- |
|
|
| `Send(message, block_id?)` | Send a complete message | ✅ Yes | ❌ No |
|
|
| `SendStream(message, block_id?)` | Start a streaming message | ❌ No | ✅ Yes |
|
|
| `Append(message_id, content, path?)` | Append content to a message | - | - |
|
|
| `Replace(message_id, message)` | Replace message content | - | - |
|
|
| `Merge(message_id, data, path?)` | Merge data into message | - | - |
|
|
| `Set(message_id, data, path)` | Set a field in message | - | - |
|
|
| `End(message_id, final_content?)` | Finalize streaming message | ✅ Yes | - |
|
|
|
|
> **Note:** `Append`, `Replace`, `Merge`, and `Set` only work with messages started via `SendStream()`. Messages sent via `Send()` are immediately finalized and cannot be updated.
|
|
|
|
#### `ctx.Send(message, block_id?): string`
|
|
|
|
Sends a message to the client and automatically flushes the output.
|
|
|
|
**Parameters:**
|
|
|
|
- `message`: Message object or string
|
|
- `block_id`: String (optional) - Block ID to send this message in. If omitted, no block ID is assigned.
|
|
|
|
**Returns:**
|
|
|
|
- `string`: The message ID (auto-generated if not provided in the message object)
|
|
|
|
**Message Object Structure:**
|
|
|
|
```typescript
|
|
interface Message {
|
|
// Required
|
|
type: string; // Message type: "text", "tool", "image", etc.
|
|
|
|
// Common fields
|
|
props?: Record<string, any>; // Message properties (passed to frontend component)
|
|
message_id?: string; // Message ID (auto-generated if omitted)
|
|
block_id?: string; // Block ID (NOT auto-generated, has priority over block_id parameter)
|
|
thread_id?: string; // Thread ID (auto-set from Stack for nested agents)
|
|
|
|
// Metadata (optional)
|
|
metadata?: Record<string, any>; // Custom metadata
|
|
}
|
|
```
|
|
|
|
**Examples:**
|
|
|
|
```javascript
|
|
// Send text message (object format) and capture message ID
|
|
const message_id = ctx.Send({
|
|
type: "text",
|
|
props: { content: "Hello, World!" },
|
|
});
|
|
console.log("Sent message:", message_id);
|
|
|
|
// Send text message (shorthand) - no block ID by default
|
|
const text_id = ctx.Send("Hello, World!");
|
|
|
|
// Send multiple messages in the same block (same bubble/card in UI)
|
|
const block_id = ctx.BlockID(); // Generate block ID first
|
|
const msg1 = ctx.Send("Step 1: Analyzing...", block_id);
|
|
const msg2 = ctx.Send("Step 2: Processing...", block_id);
|
|
const msg3 = ctx.Send("Step 3: Complete!", block_id);
|
|
|
|
// Specify block_id in message object (highest priority)
|
|
const msg4 = ctx.Send({
|
|
type: "text",
|
|
props: { content: "In specific block" },
|
|
block_id: "B2", // This takes priority over second parameter
|
|
});
|
|
|
|
// Send tool message with custom IDs
|
|
const tool_id = ctx.Send({
|
|
type: "tool",
|
|
message_id: "custom-tool-msg-1",
|
|
block_id: "B_tools",
|
|
props: {
|
|
name: "calculator",
|
|
result: { sum: 42 },
|
|
},
|
|
});
|
|
|
|
// Send image message
|
|
const image_id = ctx.Send({
|
|
type: "image",
|
|
props: {
|
|
url: "https://example.com/image.png",
|
|
alt: "Example Image",
|
|
},
|
|
});
|
|
```
|
|
|
|
**Block Management:**
|
|
|
|
```javascript
|
|
// Scenario 1: Simple message (most common)
|
|
function Next(ctx, payload) {
|
|
const { completion } = payload;
|
|
|
|
// Send a complete message
|
|
ctx.Send({
|
|
type: "text",
|
|
props: { content: completion.content },
|
|
});
|
|
}
|
|
|
|
// Scenario 2: Loading indicator before slow operation
|
|
function Next(ctx, payload) {
|
|
// Start a streaming message for loading
|
|
const loading_id = ctx.SendStream({
|
|
type: "loading",
|
|
props: { message: "Fetching data..." },
|
|
});
|
|
|
|
// Do slow operation (e.g., external API call)
|
|
const result = fetchExternalData();
|
|
|
|
// Replace loading with result
|
|
ctx.Replace(loading_id, {
|
|
type: "text",
|
|
props: { content: result },
|
|
});
|
|
ctx.End(loading_id);
|
|
}
|
|
|
|
// Scenario 3: Grouping messages in one block (special case)
|
|
function Create(ctx, messages) {
|
|
// Generate a block ID for grouping
|
|
const block_id = ctx.BlockID(); // "B1"
|
|
|
|
ctx.Send("# Analysis Results", block_id);
|
|
ctx.Send("- Finding 1: ...", block_id);
|
|
ctx.Send("- Finding 2: ...", block_id);
|
|
ctx.Send("- Finding 3: ...", block_id);
|
|
|
|
// All messages appear in the same card/bubble in the UI
|
|
}
|
|
|
|
// Scenario 4: LLM response + follow-up card in same block
|
|
function Next(ctx, payload) {
|
|
const { completion } = payload;
|
|
const block_id = ctx.BlockID();
|
|
|
|
// LLM response
|
|
ctx.Send({
|
|
type: "text",
|
|
props: { content: completion.content },
|
|
block_id: block_id,
|
|
});
|
|
|
|
// Action card (grouped with LLM response)
|
|
ctx.Send({
|
|
type: "card",
|
|
props: {
|
|
title: "Related Actions",
|
|
actions: ["action1", "action2"],
|
|
},
|
|
block_id: block_id,
|
|
});
|
|
}
|
|
```
|
|
|
|
**Notes:**
|
|
|
|
- **Message ID** is automatically generated if not provided
|
|
- **Block ID** is NOT auto-generated by default (remains empty unless manually specified)
|
|
- Most messages don't need a Block ID (each message is independent)
|
|
- Only specify Block ID in special cases (e.g., grouping LLM output with a follow-up card)
|
|
- **Block ID priority**: message.block_id > block_id parameter > empty
|
|
- **Thread ID** is automatically set from Stack for non-root calls (nested agents)
|
|
- Returns the message ID for reference in subsequent operations
|
|
- Output is automatically flushed after sending
|
|
- Throws exception on failure
|
|
- `Send()` automatically sends `message_end` event - the message is complete and cannot be updated
|
|
- **For updatable messages**, use `ctx.SendStream()` instead (see below)
|
|
|
|
#### `ctx.SendStream(message, block_id?): string`
|
|
|
|
Sends a streaming message that can be appended to later. Unlike `Send()`, this does NOT automatically send `message_end` event. Use `ctx.Append()` to add content, then `ctx.End()` to finalize.
|
|
|
|
**Parameters:**
|
|
|
|
- `message`: Message object or string
|
|
- `block_id`: String (optional) - Block ID to send this message in
|
|
|
|
**Returns:**
|
|
|
|
- `string`: The message ID (for use with `Append` and `End`)
|
|
|
|
**Examples:**
|
|
|
|
```javascript
|
|
// Start a streaming message
|
|
const msg_id = ctx.SendStream({
|
|
type: "text",
|
|
props: { content: "# Title\n\n" },
|
|
});
|
|
|
|
// Append content in chunks (simulating streaming)
|
|
ctx.Append(msg_id, "First paragraph. ");
|
|
ctx.Append(msg_id, "Second sentence. ");
|
|
ctx.Append(msg_id, "Third sentence.\n\n");
|
|
|
|
// Finalize the message (sends message_end event)
|
|
ctx.End(msg_id);
|
|
```
|
|
|
|
**String Shorthand:**
|
|
|
|
```javascript
|
|
// SendStream with string shorthand
|
|
const msg_id = ctx.SendStream("Starting analysis...");
|
|
ctx.Append(msg_id, " processing...");
|
|
ctx.Append(msg_id, " done!");
|
|
ctx.End(msg_id);
|
|
// Final content: "Starting analysis... processing... done!"
|
|
```
|
|
|
|
**With Block ID:**
|
|
|
|
```javascript
|
|
const block_id = ctx.BlockID();
|
|
const msg_id = ctx.SendStream("Step 1: ", block_id);
|
|
ctx.Append(msg_id, "Analyzing data...");
|
|
ctx.End(msg_id);
|
|
```
|
|
|
|
**Notes:**
|
|
|
|
- Returns the message ID immediately for use with `Append` and `End`
|
|
- Sends `message_start` event but NOT `message_end` (unlike `Send`)
|
|
- Must call `ctx.End(msg_id)` to finalize the message
|
|
- Content appended via `ctx.Append()` is accumulated for storage
|
|
- Ideal for streaming text output where you control the timing
|
|
|
|
#### `ctx.End(message_id, final_content?): string`
|
|
|
|
Finalizes a streaming message started with `SendStream()`. Sends `message_end` event with the complete accumulated content.
|
|
|
|
**Parameters:**
|
|
|
|
- `message_id`: String - The message ID returned by `SendStream()`
|
|
- `final_content`: String (optional) - Final content to append before ending
|
|
|
|
**Returns:**
|
|
|
|
- `string`: The message ID
|
|
|
|
**Examples:**
|
|
|
|
```javascript
|
|
// Basic usage
|
|
const msg_id = ctx.SendStream("Hello");
|
|
ctx.Append(msg_id, " World");
|
|
ctx.End(msg_id);
|
|
// Final: "Hello World"
|
|
|
|
// End with final content
|
|
const msg_id2 = ctx.SendStream("Processing");
|
|
ctx.Append(msg_id2, "...");
|
|
ctx.End(msg_id2, " Complete!");
|
|
// Final: "Processing... Complete!"
|
|
```
|
|
|
|
**Notes:**
|
|
|
|
- Must be called after `SendStream()` to send `message_end` event
|
|
- Optional `final_content` is appended before sending `message_end`
|
|
- The complete accumulated content is included in `message_end.extra.content`
|
|
- Throws exception if `message_id` is not a string
|
|
|
|
**Send vs SendStream Comparison:**
|
|
|
|
| Feature | `Send()` | `SendStream()` |
|
|
| --------------------- | ----------------- | ------------------- |
|
|
| `message_start` event | ✅ Auto | ✅ Auto |
|
|
| `message_end` event | ✅ Auto | ❌ Manual (`End()`) |
|
|
| Use case | Complete messages | Streaming output |
|
|
| Content accumulation | N/A | Via `Append()` |
|
|
| Storage | Immediate | On `End()` |
|
|
|
|
**Streaming Workflow Example:**
|
|
|
|
```javascript
|
|
function Create(ctx, messages) {
|
|
// Start streaming output
|
|
const msg_id = ctx.SendStream({
|
|
type: "text",
|
|
props: { content: "# Analysis Report\n\n" },
|
|
});
|
|
|
|
// Simulate streaming chunks
|
|
ctx.Append(msg_id, "## Section 1\n");
|
|
ctx.Append(msg_id, "Processing data...\n\n");
|
|
|
|
// Do some work
|
|
const result = analyzeData();
|
|
|
|
ctx.Append(msg_id, "## Section 2\n");
|
|
ctx.Append(msg_id, `Found ${result.count} items.\n\n`);
|
|
|
|
// Finalize with conclusion
|
|
ctx.End(msg_id, "## Conclusion\nAnalysis complete.");
|
|
|
|
return { messages };
|
|
}
|
|
```
|
|
|
|
#### `ctx.Replace(message_id, message): string`
|
|
|
|
Replaces the content of a streaming message. **Only works with messages started via `SendStream()`**.
|
|
|
|
**Parameters:**
|
|
|
|
- `message_id`: String - The ID of the streaming message (returned by `SendStream()`)
|
|
- `message`: Message object or string - The new message content
|
|
|
|
**Returns:**
|
|
|
|
- `string`: The message ID (same as the provided message_id)
|
|
|
|
**Examples:**
|
|
|
|
```javascript
|
|
// Start a streaming message
|
|
const msg_id = ctx.SendStream({
|
|
type: "loading",
|
|
props: { message: "Loading..." },
|
|
});
|
|
|
|
// Replace with new content
|
|
ctx.Replace(msg_id, {
|
|
type: "text",
|
|
props: { content: "Data loaded successfully!" },
|
|
});
|
|
|
|
// Finalize the message
|
|
ctx.End(msg_id);
|
|
```
|
|
|
|
**Use Cases:**
|
|
|
|
```javascript
|
|
// Progress updates with replacement
|
|
function Next(ctx, payload) {
|
|
const msg_id = ctx.SendStream("Step 1/3: Starting...");
|
|
|
|
// ... do work ...
|
|
ctx.Replace(msg_id, "Step 2/3: Processing...");
|
|
|
|
// ... do more work ...
|
|
ctx.Replace(msg_id, "Step 3/3: Finalizing...");
|
|
|
|
// ... finish ...
|
|
ctx.Replace(msg_id, "Complete! ✓");
|
|
ctx.End(msg_id);
|
|
}
|
|
|
|
// Loading to result transition
|
|
function Next(ctx, payload) {
|
|
const msg_id = ctx.SendStream({
|
|
type: "loading",
|
|
props: { message: "Fetching results..." },
|
|
});
|
|
|
|
const results = fetchData();
|
|
|
|
ctx.Replace(msg_id, {
|
|
type: "text",
|
|
props: { content: `Found ${results.length} results` },
|
|
});
|
|
ctx.End(msg_id);
|
|
}
|
|
```
|
|
|
|
**Notes:**
|
|
|
|
- **Only works with `SendStream()` messages** - `Send()` messages cannot be replaced
|
|
- Replaces the entire message content, not just specific fields
|
|
- Must call `ctx.End(msg_id)` after all updates to finalize the message
|
|
- Output is automatically flushed after replacing
|
|
- Throws exception on failure
|
|
|
|
#### `ctx.Append(message_id, content, path?): string`
|
|
|
|
Appends content to a streaming message. **Only works with messages started via `SendStream()`**.
|
|
|
|
**Parameters:**
|
|
|
|
- `message_id`: String - The ID of the streaming message (returned by `SendStream()`)
|
|
- `content`: Message object or string - The content to append
|
|
- `path`: String (optional) - The delta path to append to (e.g., "props.content", "props.data")
|
|
|
|
**Returns:**
|
|
|
|
- `string`: The message ID (same as the provided message_id)
|
|
|
|
**Examples:**
|
|
|
|
```javascript
|
|
// Start a streaming message
|
|
const msg_id = ctx.SendStream("Starting");
|
|
|
|
// Append more text (default path)
|
|
ctx.Append(msg_id, "... processing");
|
|
ctx.Append(msg_id, "... done!");
|
|
|
|
// Finalize the message
|
|
ctx.End(msg_id);
|
|
// Final content: "Starting... processing... done!"
|
|
|
|
// Append to specific path
|
|
const data_id = ctx.SendStream({
|
|
type: "data",
|
|
props: {
|
|
content: "Item 1\n",
|
|
status: "loading",
|
|
},
|
|
});
|
|
|
|
ctx.Append(data_id, "Item 2\n", "props.content");
|
|
ctx.Append(data_id, "Item 3\n", "props.content");
|
|
ctx.End(data_id);
|
|
// Final: props.content = "Item 1\nItem 2\nItem 3\n"
|
|
```
|
|
|
|
**Use Cases:**
|
|
|
|
```javascript
|
|
// Streaming text output (simulating LLM-like output)
|
|
function Create(ctx, messages) {
|
|
const msg_id = ctx.SendStream("");
|
|
|
|
ctx.Append(msg_id, "The");
|
|
ctx.Append(msg_id, " quick");
|
|
ctx.Append(msg_id, " brown");
|
|
ctx.Append(msg_id, " fox");
|
|
|
|
ctx.End(msg_id);
|
|
// Final: "The quick brown fox"
|
|
|
|
return { messages };
|
|
}
|
|
|
|
// Progress logs
|
|
function Next(ctx, payload) {
|
|
const log_id = ctx.SendStream({
|
|
type: "log",
|
|
props: { content: "Starting process\n" },
|
|
});
|
|
|
|
// Step 1
|
|
doStep1();
|
|
ctx.Append(log_id, "Step 1 complete\n", "props.content");
|
|
|
|
// Step 2
|
|
doStep2();
|
|
ctx.Append(log_id, "Step 2 complete\n", "props.content");
|
|
|
|
// Finish
|
|
ctx.Append(log_id, "All done!\n", "props.content");
|
|
ctx.End(log_id);
|
|
}
|
|
```
|
|
|
|
**Notes:**
|
|
|
|
- **Only works with `SendStream()` messages** - `Send()` messages cannot be appended to
|
|
- Uses delta append operation (adds to existing content, doesn't replace)
|
|
- If `path` is omitted, appends to the default content location (`props.content`)
|
|
- Must call `ctx.End(msg_id)` after all appends to finalize the message
|
|
- Output is automatically flushed after appending
|
|
- Throws exception on failure
|
|
- block_id and ThreadID are inherited from the original message
|
|
|
|
#### `ctx.Merge(message_id, data, path?): string`
|
|
|
|
Merges data into a streaming message object. **Only works with messages started via `SendStream()`**.
|
|
|
|
**Parameters:**
|
|
|
|
- `message_id`: String - The ID of the streaming message (returned by `SendStream()`)
|
|
- `data`: Object - The data to merge (should be an object)
|
|
- `path`: String (optional) - The delta path to merge into (e.g., "props", "props.metadata")
|
|
|
|
**Returns:**
|
|
|
|
- `string`: The message ID (same as the provided message_id)
|
|
|
|
**Examples:**
|
|
|
|
```javascript
|
|
// Start a streaming message with object data
|
|
const msg_id = ctx.SendStream({
|
|
type: "status",
|
|
props: {
|
|
status: "running",
|
|
progress: 0,
|
|
started: true,
|
|
},
|
|
});
|
|
|
|
// Merge updates into props (adds/updates fields, keeps others unchanged)
|
|
ctx.Merge(msg_id, { progress: 50 }, "props");
|
|
// Result: props = { status: "running", progress: 50, started: true }
|
|
|
|
ctx.Merge(msg_id, { progress: 100, status: "completed" }, "props");
|
|
// Result: props = { status: "completed", progress: 100, started: true }
|
|
|
|
// Finalize the message
|
|
ctx.End(msg_id);
|
|
```
|
|
|
|
**Use Cases:**
|
|
|
|
```javascript
|
|
// Updating task progress
|
|
function Next(ctx, payload) {
|
|
const task_id = ctx.SendStream({
|
|
type: "task",
|
|
props: {
|
|
name: "Data Processing",
|
|
status: "pending",
|
|
progress: 0,
|
|
},
|
|
});
|
|
|
|
ctx.Merge(task_id, { status: "running" }, "props");
|
|
doStep1();
|
|
ctx.Merge(task_id, { progress: 25 }, "props");
|
|
doStep2();
|
|
ctx.Merge(task_id, { progress: 50 }, "props");
|
|
doStep3();
|
|
ctx.Merge(task_id, { progress: 100, status: "completed" }, "props");
|
|
|
|
ctx.End(task_id);
|
|
}
|
|
|
|
// Building metadata incrementally
|
|
function Create(ctx, messages) {
|
|
const data_id = ctx.SendStream({
|
|
type: "data",
|
|
props: { content: "Result data" },
|
|
});
|
|
|
|
ctx.Merge(data_id, { metadata: { source: "api" } }, "props");
|
|
ctx.Merge(data_id, { metadata: { timestamp: Date.now() } }, "props");
|
|
// metadata fields are merged together
|
|
|
|
ctx.End(data_id);
|
|
return { messages };
|
|
}
|
|
```
|
|
|
|
**Notes:**
|
|
|
|
- **Only works with `SendStream()` messages** - `Send()` messages cannot be merged into
|
|
- Uses delta merge operation (merges objects, doesn't replace)
|
|
- Only works with object data (for merging key-value pairs)
|
|
- Existing fields not in the merge data remain unchanged
|
|
- If `path` is omitted, merges into the default object location
|
|
- Must call `ctx.End(msg_id)` after all merges to finalize the message
|
|
- Output is automatically flushed after merging
|
|
- Throws exception on failure
|
|
- block_id and ThreadID are inherited from the original message
|
|
|
|
#### `ctx.Set(message_id, data, path): string`
|
|
|
|
Sets a new field or value in a streaming message. **Only works with messages started via `SendStream()`**.
|
|
|
|
**Parameters:**
|
|
|
|
- `message_id`: String - The ID of the streaming message (returned by `SendStream()`)
|
|
- `data`: Any - The value to set
|
|
- `path`: String (required) - The delta path where to set the value (e.g., "props.newField", "props.metadata.key")
|
|
|
|
**Returns:**
|
|
|
|
- `string`: The message ID (same as the provided message_id)
|
|
|
|
**Examples:**
|
|
|
|
```javascript
|
|
// Start a streaming message
|
|
const msg_id = ctx.SendStream({
|
|
type: "result",
|
|
props: {
|
|
content: "Initial content",
|
|
},
|
|
});
|
|
|
|
// Set a new field
|
|
ctx.Set(msg_id, "success", "props.status");
|
|
// Result: props.status = "success"
|
|
|
|
// Set a nested object
|
|
ctx.Set(msg_id, { duration: 1500, cached: true }, "props.metadata");
|
|
// Result: props.metadata = { duration: 1500, cached: true }
|
|
|
|
// Finalize the message
|
|
ctx.End(msg_id);
|
|
```
|
|
|
|
**Use Cases:**
|
|
|
|
```javascript
|
|
// Adding computed metadata after initial send
|
|
function Next(ctx, payload) {
|
|
const result_id = ctx.SendStream({
|
|
type: "search_result",
|
|
props: { results: search_results },
|
|
});
|
|
|
|
ctx.Set(result_id, search_results.length, "props.count");
|
|
ctx.Set(result_id, Date.now(), "props.timestamp");
|
|
ctx.Set(result_id, "relevance", "props.sort_by");
|
|
|
|
ctx.End(result_id);
|
|
}
|
|
|
|
// Conditionally adding fields
|
|
function Create(ctx, messages) {
|
|
const msg_id = ctx.SendStream({
|
|
type: "operation",
|
|
props: { name: "Process Data" },
|
|
});
|
|
|
|
try {
|
|
const result = processData();
|
|
ctx.Set(msg_id, "success", "props.status");
|
|
ctx.Set(msg_id, result, "props.data");
|
|
} catch (e) {
|
|
ctx.Set(msg_id, e.message, "props.error");
|
|
ctx.Set(msg_id, "error", "props.status");
|
|
}
|
|
|
|
ctx.End(msg_id);
|
|
return { messages };
|
|
}
|
|
```
|
|
|
|
**Notes:**
|
|
|
|
- **Only works with `SendStream()` messages** - `Send()` messages cannot be modified
|
|
- Uses delta set operation (creates/sets new fields)
|
|
- The `path` parameter is **required** (must specify where to set the value)
|
|
- Creates the path if it doesn't exist
|
|
- Use for adding new fields or completely replacing a field's value
|
|
- For updating existing object fields, consider using `Merge` instead
|
|
- Must call `ctx.End(msg_id)` after all sets to finalize the message
|
|
- Output is automatically flushed after setting
|
|
- Throws exception on failure
|
|
- block_id and ThreadID are inherited from the original message
|
|
|
|
### ID Generators
|
|
|
|
These methods generate unique IDs for manual message management. Useful when you need to specify IDs before sending messages or for advanced Block/Thread management.
|
|
|
|
#### `ctx.MessageID(): string`
|
|
|
|
Generates a unique message ID.
|
|
|
|
**Returns:**
|
|
|
|
- `string`: Message ID in format "M1", "M2", "M3"...
|
|
|
|
**Example:**
|
|
|
|
```javascript
|
|
// Generate IDs manually
|
|
const id_1 = ctx.MessageID(); // "M1"
|
|
const id_2 = ctx.MessageID(); // "M2"
|
|
|
|
// Use custom ID
|
|
ctx.Send({
|
|
type: "text",
|
|
message_id: id_1,
|
|
props: { content: "Hello" },
|
|
});
|
|
```
|
|
|
|
#### `ctx.BlockID(): string`
|
|
|
|
Generates a unique block ID for grouping messages.
|
|
|
|
**Returns:**
|
|
|
|
- `string`: Block ID in format "B1", "B2", "B3"...
|
|
|
|
**Example:**
|
|
|
|
```javascript
|
|
// Generate block ID for grouping messages
|
|
const block_id = ctx.BlockID(); // "B1"
|
|
|
|
// Send multiple messages in the same block
|
|
ctx.Send("Step 1: Analyzing...", block_id);
|
|
ctx.Send("Step 2: Processing...", block_id);
|
|
ctx.Send("Step 3: Complete!", block_id);
|
|
|
|
// All three messages appear in the same card/bubble in UI
|
|
```
|
|
|
|
**Use Cases:**
|
|
|
|
```javascript
|
|
// Scenario: LLM output + follow-up card in same block
|
|
const block_id = ctx.BlockID();
|
|
|
|
// LLM response
|
|
const llm_result = Process("llms.chat", {...});
|
|
ctx.Send({
|
|
type: "text",
|
|
props: { content: llm_result.content },
|
|
block_id: block_id,
|
|
});
|
|
|
|
// Follow-up action card (grouped with LLM output)
|
|
ctx.Send({
|
|
type: "card",
|
|
props: {
|
|
title: "Related Actions",
|
|
actions: [...]
|
|
},
|
|
block_id: block_id,
|
|
});
|
|
```
|
|
|
|
#### `ctx.ThreadID(): string`
|
|
|
|
Generates a unique thread ID for concurrent operations.
|
|
|
|
**Returns:**
|
|
|
|
- `string`: Thread ID in format "T1", "T2", "T3"...
|
|
|
|
**Example:**
|
|
|
|
```javascript
|
|
// For advanced parallel processing scenarios
|
|
const thread_id = ctx.ThreadID(); // "T1"
|
|
|
|
// Send messages in a specific thread
|
|
ctx.Send({
|
|
type: "text",
|
|
props: { content: "Parallel task 1" },
|
|
thread_id: thread_id,
|
|
});
|
|
```
|
|
|
|
**Notes:**
|
|
|
|
- IDs are generated sequentially within each context
|
|
- Each context has its own ID counter (starts from 1)
|
|
- IDs are guaranteed to be unique within the same request/stream
|
|
- ThreadID is usually auto-managed by Stack, manual generation is for advanced use cases
|
|
|
|
### Lifecycle Management
|
|
|
|
#### `ctx.EndBlock(block_id): void`
|
|
|
|
Manually sends a `block_end` event for the specified block. Use this to explicitly mark the end of a block.
|
|
|
|
**Parameters:**
|
|
|
|
- `block_id`: String - The block ID to end
|
|
|
|
**Returns:**
|
|
|
|
- `void`
|
|
|
|
**Example:**
|
|
|
|
```javascript
|
|
// Create a block for grouped messages
|
|
const block_id = ctx.BlockID(); // "B1"
|
|
|
|
// Send messages in the block
|
|
ctx.Send("Analyzing data...", block_id);
|
|
ctx.Send("Processing results...", block_id);
|
|
ctx.Send("Complete!", block_id);
|
|
|
|
// Manually end the block
|
|
ctx.EndBlock(block_id);
|
|
```
|
|
|
|
**Block Lifecycle Events:**
|
|
|
|
When you send messages with a `block_id`:
|
|
|
|
1. **First message**: Automatically sends `block_start` event
|
|
2. **Subsequent messages**: No additional block events
|
|
3. **Manual end**: Call `ctx.EndBlock(block_id)` to send `block_end` event
|
|
|
|
**block_end Event Format:**
|
|
|
|
```json
|
|
{
|
|
"type": "event",
|
|
"props": {
|
|
"event": "block_end",
|
|
"message": "Block ended",
|
|
"data": {
|
|
"block_id": "B1",
|
|
"timestamp": 1764483531624,
|
|
"duration_ms": 1523,
|
|
"message_count": 3,
|
|
"status": "completed"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Notes:**
|
|
|
|
- `block_start` is sent automatically when the first message with a new `block_id` is sent
|
|
- `block_end` must be called manually via `ctx.EndBlock()`
|
|
- You can track multiple blocks simultaneously (each has independent lifecycle)
|
|
- Automatically flushes output after sending the event
|
|
|
|
**Use Cases:**
|
|
|
|
```javascript
|
|
// Use case 1: Progress reporting in a block
|
|
function Create(ctx, messages) {
|
|
const block_id = ctx.BlockID();
|
|
|
|
ctx.Send("Step 1: Analyzing data...", block_id);
|
|
// ... analysis logic ...
|
|
|
|
ctx.Send("Step 2: Processing results...", block_id);
|
|
// ... processing logic ...
|
|
|
|
ctx.Send("Step 3: Complete!", block_id);
|
|
|
|
// Mark the block as complete
|
|
ctx.EndBlock(block_id);
|
|
|
|
return { messages };
|
|
}
|
|
|
|
// Use case 2: Multiple parallel blocks
|
|
function Create(ctx, messages) {
|
|
const llm_block = ctx.BlockID(); // "B1"
|
|
const mcp_block = ctx.BlockID(); // "B2"
|
|
|
|
// LLM output block
|
|
ctx.Send("Thinking...", llm_block);
|
|
const response = callLLM();
|
|
ctx.Send(response, llm_block);
|
|
ctx.EndBlock(llm_block);
|
|
|
|
// MCP tool call block
|
|
ctx.Send("Fetching data...", mcp_block);
|
|
const data = ctx.mcp.CallTool("tool", "method", {});
|
|
ctx.Send(`Found ${data.length} results`, mcp_block);
|
|
ctx.EndBlock(mcp_block);
|
|
|
|
return { messages };
|
|
}
|
|
```
|
|
|
|
### Resource Cleanup
|
|
|
|
#### `ctx.Release()`
|
|
|
|
Manually releases Context resources. This is optional as cleanup happens automatically via garbage collection.
|
|
|
|
**Example:**
|
|
|
|
```javascript
|
|
try {
|
|
// Use context
|
|
ctx.Send("Processing...");
|
|
} finally {
|
|
ctx.Release(); // Manual cleanup
|
|
}
|
|
```
|
|
|
|
## Trace API
|
|
|
|
The `ctx.trace` object provides comprehensive tracing capabilities for debugging and monitoring agent execution.
|
|
|
|
### Node Operations
|
|
|
|
#### `ctx.trace.Add(input, options)`
|
|
|
|
Creates a new trace node (sequential step).
|
|
|
|
**Parameters:**
|
|
|
|
- `input`: Input data for the node
|
|
- `options`: Node configuration object
|
|
|
|
**Options Structure:**
|
|
|
|
```typescript
|
|
interface TraceNodeOption {
|
|
label: string; // Display label
|
|
type: string; // Node type identifier
|
|
icon: string; // Icon identifier
|
|
description: string; // Node description
|
|
metadata?: Record<string, any>; // Additional metadata
|
|
}
|
|
```
|
|
|
|
**Example:**
|
|
|
|
```javascript
|
|
const search_node = ctx.trace.Add(
|
|
{ query: "What is AI?" },
|
|
{
|
|
label: "Search Query",
|
|
type: "search",
|
|
icon: "search",
|
|
description: "Searching for AI information",
|
|
}
|
|
);
|
|
```
|
|
|
|
#### `ctx.trace.Parallel(inputs)`
|
|
|
|
Creates multiple parallel trace nodes for concurrent operations.
|
|
|
|
**Parameters:**
|
|
|
|
- `inputs`: Array of parallel input objects
|
|
|
|
**Input Structure:**
|
|
|
|
```typescript
|
|
interface ParallelInput {
|
|
input: any; // Input data
|
|
option: TraceNodeOption; // Node configuration
|
|
}
|
|
```
|
|
|
|
**Example:**
|
|
|
|
```javascript
|
|
const parallel_nodes = ctx.trace.Parallel([
|
|
{
|
|
input: { url: "https://api1.com" },
|
|
option: {
|
|
label: "API Call 1",
|
|
type: "api",
|
|
icon: "cloud",
|
|
description: "Fetching from API 1",
|
|
},
|
|
},
|
|
{
|
|
input: { url: "https://api2.com" },
|
|
option: {
|
|
label: "API Call 2",
|
|
type: "api",
|
|
icon: "cloud",
|
|
description: "Fetching from API 2",
|
|
},
|
|
},
|
|
]);
|
|
```
|
|
|
|
### Logging Methods
|
|
|
|
Add log entries to the current trace node:
|
|
|
|
```javascript
|
|
// Information logs
|
|
ctx.trace.Info("Processing started", { step: 1 });
|
|
|
|
// Debug logs
|
|
ctx.trace.Debug("Variable value", { value: 42 });
|
|
|
|
// Warning logs
|
|
ctx.trace.Warn("Deprecated feature used", { feature: "old_api" });
|
|
|
|
// Error logs
|
|
ctx.trace.Error("Operation failed", { error: "timeout" });
|
|
```
|
|
|
|
### Node Status Operations
|
|
|
|
#### `node.SetOutput(output)`
|
|
|
|
Sets the output data for a node.
|
|
|
|
```javascript
|
|
const search_node = ctx.trace.Add({ query: "search" }, options);
|
|
search_node.SetOutput({ results: [...] });
|
|
```
|
|
|
|
#### `node.SetMetadata(key, value)`
|
|
|
|
Sets metadata for a node.
|
|
|
|
```javascript
|
|
search_node.SetMetadata("duration", 1500);
|
|
search_node.SetMetadata("cache_hit", true);
|
|
```
|
|
|
|
#### `node.Complete(output?)`
|
|
|
|
Marks a node as completed (optionally with output).
|
|
|
|
```javascript
|
|
search_node.Complete({ status: "success", data: [...] });
|
|
```
|
|
|
|
#### `node.Fail(error)`
|
|
|
|
Marks a node as failed with an error.
|
|
|
|
```javascript
|
|
try {
|
|
// Operation
|
|
} catch (error) {
|
|
search_node.Fail(error);
|
|
}
|
|
```
|
|
|
|
### Query Operations
|
|
|
|
#### `ctx.trace.GetRootNode()`
|
|
|
|
Returns the root node of the trace tree.
|
|
|
|
```javascript
|
|
const root_node = ctx.trace.GetRootNode();
|
|
console.log(root_node.id, root_node.label);
|
|
```
|
|
|
|
#### `ctx.trace.GetNode(id)`
|
|
|
|
Retrieves a specific node by ID.
|
|
|
|
```javascript
|
|
const target_node = ctx.trace.GetNode("node-123");
|
|
```
|
|
|
|
#### `ctx.trace.GetCurrentNodes()`
|
|
|
|
Returns the current active nodes (may be multiple if in parallel state).
|
|
|
|
```javascript
|
|
const current_nodes = ctx.trace.GetCurrentNodes();
|
|
```
|
|
|
|
### Memory Space Operations
|
|
|
|
#### `ctx.trace.CreateSpace(option)`
|
|
|
|
Creates a memory space for storing key-value data.
|
|
|
|
```javascript
|
|
const memory_space = ctx.trace.CreateSpace({
|
|
label: "Context Memory",
|
|
type: "context",
|
|
icon: "database",
|
|
description: "Stores conversation context",
|
|
});
|
|
```
|
|
|
|
#### `ctx.trace.GetSpace(id)`
|
|
|
|
Retrieves a memory space by ID.
|
|
|
|
```javascript
|
|
const context_space = ctx.trace.GetSpace("context");
|
|
```
|
|
|
|
#### `ctx.trace.HasSpace(id)`
|
|
|
|
Checks if a memory space exists.
|
|
|
|
```javascript
|
|
if (ctx.trace.HasSpace("context")) {
|
|
// Space exists
|
|
}
|
|
```
|
|
|
|
#### `ctx.trace.DeleteSpace(id)`
|
|
|
|
Deletes a memory space.
|
|
|
|
```javascript
|
|
ctx.trace.DeleteSpace("temp_storage");
|
|
```
|
|
|
|
#### `ctx.trace.ListSpaces()`
|
|
|
|
Lists all memory spaces.
|
|
|
|
```javascript
|
|
const all_spaces = ctx.trace.ListSpaces();
|
|
all_spaces.forEach((space) => {
|
|
console.log(space.id, space.label);
|
|
});
|
|
```
|
|
|
|
## Space API
|
|
|
|
The `ctx.space` object provides a shared data space for passing data between requests and agent calls. This is useful for storing temporary data that needs to be accessed across different hooks or nested agent calls.
|
|
|
|
### Methods
|
|
|
|
#### `ctx.space.Get(key): any`
|
|
|
|
Gets a value from the space.
|
|
|
|
**Parameters:**
|
|
|
|
- `key`: String - The key to retrieve
|
|
|
|
**Returns:**
|
|
|
|
- `any`: The value, or `null` if not found
|
|
|
|
**Example:**
|
|
|
|
```javascript
|
|
const user_data = ctx.space.Get("user_data");
|
|
if (user_data) {
|
|
console.log("Found user:", user_data.name);
|
|
}
|
|
```
|
|
|
|
#### `ctx.space.Set(key, value): void`
|
|
|
|
Sets a value in the space.
|
|
|
|
**Parameters:**
|
|
|
|
- `key`: String - The key to set
|
|
- `value`: Any - The value to store
|
|
|
|
**Example:**
|
|
|
|
```javascript
|
|
ctx.space.Set("user_data", { name: "John", id: 123 });
|
|
ctx.space.Set("processing_status", "started");
|
|
```
|
|
|
|
#### `ctx.space.Delete(key): void`
|
|
|
|
Deletes a key from the space.
|
|
|
|
**Parameters:**
|
|
|
|
- `key`: String - The key to delete
|
|
|
|
**Example:**
|
|
|
|
```javascript
|
|
ctx.space.Delete("temp_data");
|
|
```
|
|
|
|
#### `ctx.space.GetDel(key): any`
|
|
|
|
Gets a value and immediately deletes it. Convenient for one-time use data.
|
|
|
|
**Parameters:**
|
|
|
|
- `key`: String - The key to retrieve and delete
|
|
|
|
**Returns:**
|
|
|
|
- `any`: The value, or `null` if not found
|
|
|
|
**Example:**
|
|
|
|
```javascript
|
|
// Store file metadata in parent agent
|
|
ctx.space.Set("file_metadata", { name: "report.pdf", size: 1024 });
|
|
|
|
// In child agent, get and consume the data
|
|
const metadata = ctx.space.GetDel("file_metadata");
|
|
// metadata is now deleted from space
|
|
```
|
|
|
|
### Use Cases
|
|
|
|
```javascript
|
|
// Use case 1: Pass data between hooks
|
|
function Create(ctx, messages) {
|
|
// Store data for later use
|
|
ctx.space.Set("original_query", messages[0].content);
|
|
return { messages };
|
|
}
|
|
|
|
function Next(ctx, payload) {
|
|
// Retrieve data from Create hook
|
|
const query = ctx.space.Get("original_query");
|
|
console.log("Original query was:", query);
|
|
}
|
|
|
|
// Use case 2: Pass data to nested agent calls
|
|
function Create(ctx, messages) {
|
|
// Prepare context for child agent
|
|
ctx.space.Set("parent_context", {
|
|
user_id: ctx.authorized.user_id,
|
|
session_start: Date.now(),
|
|
});
|
|
|
|
// Call child agent...
|
|
}
|
|
|
|
// Use case 3: One-time data consumption
|
|
function Next(ctx, payload) {
|
|
// Get and delete in one operation
|
|
const temp_data = ctx.space.GetDel("temp_processing_data");
|
|
if (temp_data) {
|
|
// Process and discard
|
|
}
|
|
}
|
|
```
|
|
|
|
**Notes:**
|
|
|
|
- Space is shared across all hooks within the same request
|
|
- Space persists across nested agent calls (A2A)
|
|
- Values can be any JSON-serializable data
|
|
- Use `GetDel` for data that should only be consumed once
|
|
|
|
## MCP API
|
|
|
|
The `ctx.mcp` object provides access to Model Context Protocol operations for interacting with external tools, resources, and prompts.
|
|
|
|
### Resource Operations
|
|
|
|
#### `ctx.mcp.ListResources(client)`
|
|
|
|
Lists available resources from an MCP client.
|
|
|
|
```javascript
|
|
const fs_resources = ctx.mcp.ListResources("filesystem");
|
|
```
|
|
|
|
#### `ctx.mcp.ReadResource(client, uri)`
|
|
|
|
Reads a specific resource.
|
|
|
|
```javascript
|
|
const file_content = ctx.mcp.ReadResource(
|
|
"filesystem",
|
|
"file:///path/to/file.txt"
|
|
);
|
|
```
|
|
|
|
### Tool Operations
|
|
|
|
#### `ctx.mcp.ListTools(client)`
|
|
|
|
Lists available tools from an MCP client.
|
|
|
|
```javascript
|
|
const available_tools = ctx.mcp.ListTools("toolkit");
|
|
```
|
|
|
|
#### `ctx.mcp.CallTool(client, name, args)`
|
|
|
|
Calls a single tool.
|
|
|
|
```javascript
|
|
const calc_result = ctx.mcp.CallTool("calculator", "add", {
|
|
a: 10,
|
|
b: 32,
|
|
});
|
|
```
|
|
|
|
#### `ctx.mcp.CallTools(client, calls)`
|
|
|
|
Calls multiple tools sequentially.
|
|
|
|
```javascript
|
|
const tool_results = ctx.mcp.CallTools("toolkit", [
|
|
{ name: "tool1", args: { param: "value1" } },
|
|
{ name: "tool2", args: { param: "value2" } },
|
|
]);
|
|
```
|
|
|
|
#### `ctx.mcp.CallToolsParallel(client, calls)`
|
|
|
|
Calls multiple tools in parallel.
|
|
|
|
```javascript
|
|
const parallel_results = ctx.mcp.CallToolsParallel("toolkit", [
|
|
{ name: "api1", args: { endpoint: "/users" } },
|
|
{ name: "api2", args: { endpoint: "/posts" } },
|
|
]);
|
|
```
|
|
|
|
### Prompt Operations
|
|
|
|
#### `ctx.mcp.ListPrompts(client)`
|
|
|
|
Lists available prompts from an MCP client.
|
|
|
|
```javascript
|
|
const available_prompts = ctx.mcp.ListPrompts("prompt_library");
|
|
```
|
|
|
|
#### `ctx.mcp.GetPrompt(client, name, args?)`
|
|
|
|
Retrieves a specific prompt.
|
|
|
|
```javascript
|
|
const review_prompt = ctx.mcp.GetPrompt("prompt_library", "code_review", {
|
|
language: "javascript",
|
|
});
|
|
```
|
|
|
|
### Sample Operations
|
|
|
|
#### `ctx.mcp.CreateSample(client, uri, sample)`
|
|
|
|
Creates a sample for a resource.
|
|
|
|
```javascript
|
|
ctx.mcp.CreateSample("filesystem", "file:///examples", {
|
|
name: "example1",
|
|
content: "Sample content",
|
|
});
|
|
```
|
|
|
|
## Hooks
|
|
|
|
The Agent system supports two hooks that can be defined in the assistant's `index.ts` file:
|
|
|
|
### Create Hook
|
|
|
|
Called before the LLM call. Use this to preprocess messages, add context, or configure the LLM request.
|
|
|
|
**Signature:**
|
|
|
|
```typescript
|
|
function Create(ctx: Context, messages: Message[]): HookCreateResponse | null;
|
|
```
|
|
|
|
**Parameters:**
|
|
|
|
- `ctx`: Context object
|
|
- `messages`: Array of input messages (including chat history if enabled)
|
|
|
|
**Return Value (`HookCreateResponse`):**
|
|
|
|
```typescript
|
|
interface HookCreateResponse {
|
|
// Messages to be sent to the assistant (can modify/replace input messages)
|
|
messages?: Message[];
|
|
|
|
// Audio configuration (for models that support audio output)
|
|
audio?: AudioConfig;
|
|
|
|
// Generation parameters (override assistant defaults)
|
|
temperature?: number;
|
|
max_tokens?: number;
|
|
max_completion_tokens?: number;
|
|
|
|
// MCP configuration - add/override MCP servers for this request
|
|
mcp_servers?: MCPServerConfig[];
|
|
|
|
// Prompt configuration
|
|
prompts?: string; // Prompt preset key to use
|
|
disable_global_prompts?: boolean; // Disable global prompts
|
|
|
|
// Tool configuration
|
|
tools?: ToolConfig[]; // Override tools for this request
|
|
disable_tools?: boolean; // Disable all tools
|
|
}
|
|
```
|
|
|
|
**Example:**
|
|
|
|
```javascript
|
|
function Create(ctx, messages) {
|
|
// Store data for Next hook
|
|
ctx.space.Set("user_query", messages[0]?.content);
|
|
|
|
// Modify messages
|
|
const enhanced_messages = messages.map((msg) => ({
|
|
...msg,
|
|
content: msg.content + "\n\nPlease be concise.",
|
|
}));
|
|
|
|
// Return configuration
|
|
return {
|
|
messages: enhanced_messages,
|
|
temperature: 0.7,
|
|
max_tokens: 2000,
|
|
};
|
|
}
|
|
```
|
|
|
|
### Next Hook
|
|
|
|
Called after the LLM response (and tool calls if any). Use this to post-process the response, send custom messages, or delegate to another agent.
|
|
|
|
**Signature:**
|
|
|
|
```typescript
|
|
function Next(ctx: Context, payload: NextHookPayload): NextHookResponse | null;
|
|
```
|
|
|
|
**Parameters:**
|
|
|
|
- `ctx`: Context object
|
|
- `payload`: Object containing:
|
|
|
|
```typescript
|
|
interface NextHookPayload {
|
|
messages: Message[]; // Messages sent to the assistant
|
|
completion?: CompletionResponse; // LLM response
|
|
tools?: ToolCallResponse[]; // Tool call results (if any)
|
|
error?: string; // Error message if LLM call failed
|
|
}
|
|
|
|
interface CompletionResponse {
|
|
content: string; // LLM text response
|
|
tool_calls?: ToolCall[]; // Tool calls requested by LLM
|
|
usage?: UsageInfo; // Token usage statistics
|
|
}
|
|
|
|
interface ToolCallResponse {
|
|
toolcall_id: string;
|
|
server: string; // MCP server name
|
|
tool: string; // Tool name
|
|
arguments?: any; // Arguments passed to tool
|
|
result?: any; // Tool execution result
|
|
error?: string; // Error if tool failed
|
|
}
|
|
```
|
|
|
|
**Return Value (`NextHookResponse`):**
|
|
|
|
```typescript
|
|
interface NextHookResponse {
|
|
// Delegate to another agent (recursive call)
|
|
delegate?: {
|
|
agent_id: string; // Target agent ID
|
|
messages: Message[]; // Messages to send
|
|
};
|
|
|
|
// Custom response data (returned to user)
|
|
data?: any;
|
|
|
|
// Metadata for debugging
|
|
metadata?: Record<string, any>;
|
|
}
|
|
```
|
|
|
|
**Example:**
|
|
|
|
```javascript
|
|
function Next(ctx, payload) {
|
|
const { messages, completion, tools, error } = payload;
|
|
|
|
if (error) {
|
|
ctx.Send({
|
|
type: "error",
|
|
props: { message: error },
|
|
});
|
|
return null;
|
|
}
|
|
|
|
// Process tool results
|
|
if (tools && tools.length > 0) {
|
|
const results = tools.map((t) => t.result);
|
|
ctx.Send(`Tool results: ${JSON.stringify(results)}`);
|
|
}
|
|
|
|
// Return custom data
|
|
return {
|
|
data: {
|
|
response: completion?.content,
|
|
processed: true,
|
|
},
|
|
metadata: {
|
|
tool_count: tools?.length || 0,
|
|
},
|
|
};
|
|
}
|
|
```
|
|
|
|
### Hook Execution Flow
|
|
|
|
```
|
|
User Input
|
|
↓
|
|
[Create Hook] → Preprocess messages, configure LLM
|
|
↓
|
|
[LLM Call] → Get completion from language model
|
|
↓
|
|
[Tool Calls] → Execute any tool calls (if requested by LLM)
|
|
↓
|
|
[Next Hook] → Post-process response, send messages
|
|
↓
|
|
Response to User
|
|
```
|
|
|
|
**Notes:**
|
|
|
|
- Hooks are optional - if not defined, the agent uses default behavior
|
|
- Return `null` or `undefined` from hooks to use default behavior
|
|
- Hooks can send messages directly via `ctx.Send()`, `ctx.SendStream()`, etc.
|
|
- Use `ctx.space` to pass data between Create and Next hooks
|
|
|
|
## Complete Example
|
|
|
|
Here's a comprehensive example using various Context API features:
|
|
|
|
```javascript
|
|
/**
|
|
* Create Hook - Initialize and prepare for LLM call
|
|
* @param {Context} ctx - Agent context
|
|
* @param {Array} messages - Input messages
|
|
*/
|
|
function Create(ctx, messages) {
|
|
// Store original query in space for later use
|
|
ctx.space.Set("original_query", messages[0]?.content || "");
|
|
|
|
// Add trace node
|
|
ctx.trace.Add(
|
|
{ messages },
|
|
{
|
|
label: "Create Hook",
|
|
type: "hook",
|
|
icon: "play",
|
|
description: "Preparing messages for LLM",
|
|
}
|
|
);
|
|
|
|
return { messages };
|
|
}
|
|
|
|
/**
|
|
* Next Hook - Process LLM response and enhance with tools
|
|
* @param {Context} ctx - Agent context
|
|
* @param {Object} payload - Hook payload
|
|
* @param {Array} payload.messages - Messages sent to the assistant
|
|
* @param {Object} payload.completion - Completion response from LLM
|
|
* @param {Array} payload.tools - Tool call results
|
|
* @param {string} payload.error - Error message if failed
|
|
*/
|
|
function Next(ctx, payload) {
|
|
try {
|
|
const { messages, completion, tools, error } = payload;
|
|
|
|
// Retrieve data from Create hook
|
|
const original_query = ctx.space.Get("original_query");
|
|
|
|
// Create trace node for custom processing
|
|
const process_node = ctx.trace.Add(
|
|
{ completion, tools },
|
|
{
|
|
label: "Custom Processing",
|
|
type: "custom",
|
|
icon: "settings",
|
|
description: "Enhancing response with external data",
|
|
}
|
|
);
|
|
|
|
ctx.trace.Info("Starting custom processing", {
|
|
original_query: original_query,
|
|
tool_count: tools?.length || 0,
|
|
});
|
|
|
|
// Start streaming output
|
|
const msg_id = ctx.SendStream("# Search Results\n\n");
|
|
|
|
// Call MCP tool for additional data
|
|
const search_results = ctx.mcp.CallTool("search_engine", "search", {
|
|
query: "latest AI news",
|
|
limit: 5,
|
|
});
|
|
|
|
// Stream results as they come
|
|
ctx.Append(msg_id, `Found ${search_results.length} articles:\n\n`);
|
|
|
|
search_results.forEach((result, i) => {
|
|
ctx.Append(msg_id, `${i + 1}. **${result.title}**\n`);
|
|
ctx.Append(msg_id, ` ${result.summary}\n\n`);
|
|
});
|
|
|
|
// Finalize the streaming message
|
|
ctx.End(msg_id, "---\n*Search complete*");
|
|
|
|
// Update trace
|
|
process_node.SetMetadata("search_results_count", search_results.length);
|
|
process_node.Complete({ status: "success" });
|
|
|
|
return {
|
|
data: { sources: search_results },
|
|
metadata: { processed: true },
|
|
};
|
|
} catch (error) {
|
|
ctx.trace.Error("Processing failed", { error: error.message });
|
|
throw error;
|
|
}
|
|
}
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Error Handling**: Always wrap Context operations in try-catch blocks
|
|
2. **Resource Cleanup**: Use try-finally pattern for manual cleanup if needed
|
|
3. **Trace Organization**: Create meaningful trace nodes with descriptive labels
|
|
4. **Logging Levels**: Use appropriate log levels (Debug for development, Info for progress, Error for failures)
|
|
5. **Message IDs**: Let the system auto-generate message IDs unless you need specific tracking
|
|
6. **Parallel Operations**: Use `Trace.Parallel()` for concurrent operations to maintain trace clarity
|
|
7. **Space Usage**: Use `ctx.space` for passing data between hooks and nested agent calls
|
|
8. **Streaming Messages**: Use `SendStream()` + `Append()` + `End()` for streaming output; use `Send()` for complete messages
|
|
9. **Block Grouping**: Only use Block IDs when you need to group multiple messages together (e.g., LLM output + follow-up card)
|
|
|
|
## Error Handling
|
|
|
|
All Context methods throw exceptions on failure. Always handle errors appropriately:
|
|
|
|
```javascript
|
|
try {
|
|
ctx.Send(message);
|
|
} catch (error) {
|
|
ctx.trace.Error("Failed to send message", { error: error.message });
|
|
throw error;
|
|
}
|
|
```
|
|
|
|
## TypeScript Support
|
|
|
|
For TypeScript projects, the Context types are automatically inferred. You can also import explicit types:
|
|
|
|
```typescript
|
|
import { Context, Message, TraceNodeOption } from "@yaoapps/types";
|
|
|
|
interface NextPayload {
|
|
messages: Message[];
|
|
completion: any;
|
|
tools: any[];
|
|
error?: string;
|
|
}
|
|
|
|
function Next(ctx: Context, payload: NextPayload): any {
|
|
// Your code with full type checking
|
|
const { messages, completion, tools, error } = payload;
|
|
// ...
|
|
}
|
|
```
|
|
|
|
## See Also
|
|
|
|
- [Agent Hooks Documentation](../hooks/README.md)
|
|
- [MCP Protocol Specification](../mcp/README.md)
|
|
- [Trace System Documentation](../../trace/README.md)
|
|
- [Message Format Specification](../message/README.md)
|