@astralform/js 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +349 -0
- package/dist/index.cjs +1006 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +526 -0
- package/dist/index.d.ts +526 -0
- package/dist/index.js +967 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
# @astralform/js
|
|
2
|
+
|
|
3
|
+
JavaScript/TypeScript SDK for [Astralform](https://astralform.ai) — AI agent orchestration with SSE streaming, client-side tool execution, and [WebMCP](https://developer.chrome.com/docs/extensions/ai/webmcp) bridge support.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @astralform/js
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { ChatSession } from "@astralform/js";
|
|
15
|
+
|
|
16
|
+
const session = new ChatSession({
|
|
17
|
+
apiKey: "your-api-key",
|
|
18
|
+
userId: "user-123",
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
session.on((event) => {
|
|
22
|
+
switch (event.type) {
|
|
23
|
+
case "chunk":
|
|
24
|
+
process.stdout.write(event.text);
|
|
25
|
+
break;
|
|
26
|
+
case "complete":
|
|
27
|
+
console.log("\nDone!");
|
|
28
|
+
break;
|
|
29
|
+
case "error":
|
|
30
|
+
console.error(event.error.message);
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
await session.connect();
|
|
36
|
+
await session.send("What is the capital of France?");
|
|
37
|
+
session.disconnect();
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Features
|
|
41
|
+
|
|
42
|
+
- **SSE Streaming** — Real-time token-by-token responses via Server-Sent Events
|
|
43
|
+
- **Client-Side Tools** — Register tools that the LLM can call, executed locally in your app
|
|
44
|
+
- **WebMCP Bridge** — Auto-discovers browser tools from `navigator.modelContext` (Chrome 146+)
|
|
45
|
+
- **Multi-Agent** — Route messages to specific agents or let the supervisor choose
|
|
46
|
+
- **Conversation Management** — Create, switch, delete, and resume conversations
|
|
47
|
+
- **Zero Dependencies** — Uses only native APIs (`fetch`, `ReadableStream`, `crypto`)
|
|
48
|
+
- **Universal** — ESM + CJS, works in browsers and Node.js 18+
|
|
49
|
+
|
|
50
|
+
## Configuration
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { ChatSession } from "@astralform/js";
|
|
54
|
+
|
|
55
|
+
const session = new ChatSession({
|
|
56
|
+
apiKey: "your-api-key", // Required — Astralform project API key
|
|
57
|
+
userId: "user-123", // Required — identifies the end user
|
|
58
|
+
baseURL: "http://localhost:8000", // Optional — defaults to https://api.astralform.ai
|
|
59
|
+
fetch: customFetch, // Optional — custom fetch implementation
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Events
|
|
64
|
+
|
|
65
|
+
Subscribe to events with `.on()`, which returns an unsubscribe function:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
const unsubscribe = session.on((event) => {
|
|
69
|
+
switch (event.type) {
|
|
70
|
+
case "connected":
|
|
71
|
+
// Session connected, project status and tools loaded
|
|
72
|
+
break;
|
|
73
|
+
case "chunk":
|
|
74
|
+
// Streaming text chunk: event.text
|
|
75
|
+
break;
|
|
76
|
+
case "complete":
|
|
77
|
+
// Response finished: event.content, event.conversationId, event.title
|
|
78
|
+
break;
|
|
79
|
+
case "tool_call":
|
|
80
|
+
// Tool invoked: event.request.toolName, event.request.arguments
|
|
81
|
+
break;
|
|
82
|
+
case "tool_executing":
|
|
83
|
+
// Tool running: event.name
|
|
84
|
+
break;
|
|
85
|
+
case "tool_completed":
|
|
86
|
+
// Tool finished: event.name, event.result
|
|
87
|
+
break;
|
|
88
|
+
case "agent_start":
|
|
89
|
+
// Agent began processing: event.agentName, event.agentDisplayName
|
|
90
|
+
break;
|
|
91
|
+
case "agent_end":
|
|
92
|
+
// Agent finished: event.agentName
|
|
93
|
+
break;
|
|
94
|
+
case "model_info":
|
|
95
|
+
// LLM model identified: event.name
|
|
96
|
+
break;
|
|
97
|
+
case "error":
|
|
98
|
+
// Error occurred: event.error
|
|
99
|
+
break;
|
|
100
|
+
case "disconnected":
|
|
101
|
+
// Session disconnected
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Later: unsubscribe()
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Client-Side Tools
|
|
110
|
+
|
|
111
|
+
Register tools that the LLM can invoke. Tool names **must** start with `mcp_` so the backend routes them to the client for execution.
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
session.toolRegistry.registerTool(
|
|
115
|
+
"mcp_get_current_time",
|
|
116
|
+
"Get the current date and time",
|
|
117
|
+
{
|
|
118
|
+
type: "object",
|
|
119
|
+
properties: {
|
|
120
|
+
timezone: {
|
|
121
|
+
type: "string",
|
|
122
|
+
description: "IANA timezone (e.g. America/New_York)",
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
async (args) => {
|
|
127
|
+
const tz = (args.timezone as string) || "UTC";
|
|
128
|
+
return new Date().toLocaleString("en-US", { timeZone: tz });
|
|
129
|
+
},
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
await session.send("What time is it in Tokyo?");
|
|
133
|
+
// The LLM calls mcp_get_current_time → SDK executes it → result sent back → LLM responds
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
The tool execution flow is handled automatically:
|
|
137
|
+
|
|
138
|
+
1. LLM requests a client tool call via SSE
|
|
139
|
+
2. SDK executes the tool handler locally
|
|
140
|
+
3. SDK posts the result to `/v1/tool-result`
|
|
141
|
+
4. SDK continues the SSE stream for the LLM's final response
|
|
142
|
+
|
|
143
|
+
## WebMCP Bridge
|
|
144
|
+
|
|
145
|
+
On Chrome 146+ with WebMCP support, the SDK auto-discovers browser-registered tools:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
await session.connect(); // Automatically calls navigator.modelContext.tools.list()
|
|
149
|
+
|
|
150
|
+
console.log("WebMCP available:", session.webMCP.isAvailable());
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
You can also register tools that appear in both WebMCP and Astralform:
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
session.webMCP.registerTool(
|
|
157
|
+
"page_content",
|
|
158
|
+
"Get the current page content",
|
|
159
|
+
{
|
|
160
|
+
type: "object",
|
|
161
|
+
properties: {
|
|
162
|
+
selector: { type: "string", description: "CSS selector" },
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
async (args) => {
|
|
166
|
+
const el = document.querySelector((args.selector as string) || "body");
|
|
167
|
+
return el?.textContent ?? "Not found";
|
|
168
|
+
},
|
|
169
|
+
);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
WebMCP tools are registered with the `mcp_webmcp_` prefix in the tool manifest sent to the backend.
|
|
173
|
+
|
|
174
|
+
## Multi-Agent
|
|
175
|
+
|
|
176
|
+
Send messages to specific agents:
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
await session.connect();
|
|
180
|
+
|
|
181
|
+
// List available agents
|
|
182
|
+
console.log(session.agents);
|
|
183
|
+
|
|
184
|
+
// Send to a specific agent
|
|
185
|
+
await session.send("Help me debug this", { agentName: "debugger" });
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Conversation Management
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
// Create a new conversation
|
|
192
|
+
const id = await session.createNewConversation();
|
|
193
|
+
|
|
194
|
+
// Switch to an existing conversation (loads messages from backend)
|
|
195
|
+
await session.switchConversation("conversation-id");
|
|
196
|
+
|
|
197
|
+
// Delete a conversation
|
|
198
|
+
await session.deleteConversation("conversation-id");
|
|
199
|
+
|
|
200
|
+
// Edit and resend from a checkpoint
|
|
201
|
+
await session.resendFromCheckpoint("message-id", "Updated message");
|
|
202
|
+
|
|
203
|
+
// Access state
|
|
204
|
+
session.conversationId; // Current conversation ID
|
|
205
|
+
session.conversations; // All conversations
|
|
206
|
+
session.messages; // Messages in current conversation
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Toggle Tools
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
// Toggle platform tools (e.g. web search)
|
|
213
|
+
session.toggleTool("search"); // Returns true if now enabled, false if disabled
|
|
214
|
+
|
|
215
|
+
// Toggle MCP tools
|
|
216
|
+
session.toggleMcp("github__list_repos");
|
|
217
|
+
|
|
218
|
+
// Check enabled state
|
|
219
|
+
session.enabledTools; // Set<string>
|
|
220
|
+
session.enabledMcp; // Set<string>
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Low-Level Client
|
|
224
|
+
|
|
225
|
+
For direct API access without session state management:
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
import { AstralformClient } from "@astralform/js";
|
|
229
|
+
|
|
230
|
+
const client = new AstralformClient({
|
|
231
|
+
apiKey: "your-api-key",
|
|
232
|
+
userId: "user-123",
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// REST endpoints
|
|
236
|
+
const status = await client.getProjectStatus();
|
|
237
|
+
const conversations = await client.getConversations();
|
|
238
|
+
const messages = await client.getMessages("conversation-id");
|
|
239
|
+
const tools = await client.getTools();
|
|
240
|
+
const mcpTools = await client.getMcpTools();
|
|
241
|
+
const agents = await client.getAgents();
|
|
242
|
+
const skills = await client.getSkills();
|
|
243
|
+
|
|
244
|
+
// Job-based streaming
|
|
245
|
+
const job = await client.createJob({ message: "Hello" });
|
|
246
|
+
for await (const event of client.streamJobEvents(job.job_id)) {
|
|
247
|
+
const data = JSON.parse(event.data);
|
|
248
|
+
if (data.type === "content_block_delta") {
|
|
249
|
+
process.stdout.write(data.delta.text);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Custom Storage
|
|
255
|
+
|
|
256
|
+
The SDK uses in-memory storage by default. Implement `ChatStorage` for persistence:
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
import { ChatSession, type ChatStorage } from "@astralform/js";
|
|
260
|
+
|
|
261
|
+
const myStorage: ChatStorage = {
|
|
262
|
+
fetchConversations: async () => { /* ... */ },
|
|
263
|
+
fetchConversation: async (id) => { /* ... */ },
|
|
264
|
+
createConversation: async (id, title) => { /* ... */ },
|
|
265
|
+
updateConversationTitle: async (id, title) => { /* ... */ },
|
|
266
|
+
deleteConversation: async (id) => { /* ... */ },
|
|
267
|
+
fetchMessages: async (conversationId) => { /* ... */ },
|
|
268
|
+
addMessage: async (message, conversationId) => { /* ... */ },
|
|
269
|
+
updateMessageStatus: async (id, status) => { /* ... */ },
|
|
270
|
+
deleteMessage: async (id) => { /* ... */ },
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const session = new ChatSession(config, myStorage);
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Error Handling
|
|
277
|
+
|
|
278
|
+
The SDK throws typed errors:
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
import {
|
|
282
|
+
AuthenticationError, // 401 — invalid API key
|
|
283
|
+
RateLimitError, // 429 — rate limit exceeded
|
|
284
|
+
LLMNotConfiguredError, // LLM provider not set up
|
|
285
|
+
ServerError, // 5xx or unexpected errors
|
|
286
|
+
ConnectionError, // Network failures
|
|
287
|
+
StreamAbortedError, // Stream cancelled via disconnect()
|
|
288
|
+
} from "@astralform/js";
|
|
289
|
+
|
|
290
|
+
session.on((event) => {
|
|
291
|
+
if (event.type === "error") {
|
|
292
|
+
if (event.error instanceof AuthenticationError) {
|
|
293
|
+
// Redirect to login
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Framework Integration
|
|
300
|
+
|
|
301
|
+
The SDK is headless — it works with any UI framework. Here's a React example:
|
|
302
|
+
|
|
303
|
+
```tsx
|
|
304
|
+
import { ChatSession, type ChatEvent } from "@astralform/js";
|
|
305
|
+
import { useEffect, useRef, useState } from "react";
|
|
306
|
+
|
|
307
|
+
function useChat(apiKey: string, userId: string) {
|
|
308
|
+
const sessionRef = useRef<ChatSession>();
|
|
309
|
+
const [messages, setMessages] = useState<string[]>([]);
|
|
310
|
+
const [streaming, setStreaming] = useState("");
|
|
311
|
+
|
|
312
|
+
useEffect(() => {
|
|
313
|
+
const session = new ChatSession({ apiKey, userId });
|
|
314
|
+
sessionRef.current = session;
|
|
315
|
+
|
|
316
|
+
session.on((event: ChatEvent) => {
|
|
317
|
+
switch (event.type) {
|
|
318
|
+
case "chunk":
|
|
319
|
+
setStreaming((s) => s + event.text);
|
|
320
|
+
break;
|
|
321
|
+
case "complete":
|
|
322
|
+
setMessages((m) => [...m, event.content]);
|
|
323
|
+
setStreaming("");
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
session.connect();
|
|
329
|
+
return () => session.disconnect();
|
|
330
|
+
}, [apiKey, userId]);
|
|
331
|
+
|
|
332
|
+
const send = (text: string) => sessionRef.current?.send(text);
|
|
333
|
+
|
|
334
|
+
return { messages, streaming, send };
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## Development
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
npm install # Install dependencies
|
|
342
|
+
npm run build # Build ESM + CJS + types
|
|
343
|
+
npm test # Run tests
|
|
344
|
+
npm run typecheck # Type check
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## License
|
|
348
|
+
|
|
349
|
+
MIT
|