@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 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