@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/dist/index.cjs ADDED
@@ -0,0 +1,1006 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AstralformClient: () => AstralformClient,
24
+ AstralformError: () => AstralformError,
25
+ AuthenticationError: () => AuthenticationError,
26
+ ChatSession: () => ChatSession,
27
+ ConnectionError: () => ConnectionError,
28
+ InMemoryStorage: () => InMemoryStorage,
29
+ LLMNotConfiguredError: () => LLMNotConfiguredError,
30
+ RateLimitError: () => RateLimitError,
31
+ ServerError: () => ServerError,
32
+ StreamAbortedError: () => StreamAbortedError,
33
+ ToolRegistry: () => ToolRegistry,
34
+ generateId: () => generateId,
35
+ streamJobSSE: () => streamJobSSE
36
+ });
37
+ module.exports = __toCommonJS(index_exports);
38
+
39
+ // src/errors.ts
40
+ var AstralformError = class extends Error {
41
+ constructor(message, code) {
42
+ super(message);
43
+ this.code = code;
44
+ this.name = "AstralformError";
45
+ }
46
+ };
47
+ var AuthenticationError = class extends AstralformError {
48
+ constructor(message = "Invalid or missing API key") {
49
+ super(message, "authentication_error");
50
+ this.name = "AuthenticationError";
51
+ }
52
+ };
53
+ var RateLimitError = class extends AstralformError {
54
+ constructor(message = "Rate limit exceeded") {
55
+ super(message, "rate_limit_error");
56
+ this.name = "RateLimitError";
57
+ }
58
+ };
59
+ var LLMNotConfiguredError = class extends AstralformError {
60
+ constructor(message = "LLM provider not configured for this project") {
61
+ super(message, "llm_not_configured");
62
+ this.name = "LLMNotConfiguredError";
63
+ }
64
+ };
65
+ var ServerError = class extends AstralformError {
66
+ constructor(message = "Internal server error") {
67
+ super(message, "server_error");
68
+ this.name = "ServerError";
69
+ }
70
+ };
71
+ var ConnectionError = class extends AstralformError {
72
+ constructor(message = "Failed to connect to server") {
73
+ super(message, "connection_error");
74
+ this.name = "ConnectionError";
75
+ }
76
+ };
77
+ var StreamAbortedError = class extends AstralformError {
78
+ constructor(message = "Stream was aborted") {
79
+ super(message, "stream_aborted");
80
+ this.name = "StreamAbortedError";
81
+ }
82
+ };
83
+
84
+ // src/streaming.ts
85
+ async function* streamJobSSE(options) {
86
+ const { url, headers, signal, fetchFn } = options;
87
+ let response;
88
+ try {
89
+ response = await fetchFn(url, {
90
+ method: "GET",
91
+ headers,
92
+ signal
93
+ });
94
+ } catch (err) {
95
+ if (err instanceof DOMException && err.name === "AbortError") {
96
+ throw new StreamAbortedError();
97
+ }
98
+ throw new ConnectionError(
99
+ err instanceof Error ? err.message : "Failed to connect"
100
+ );
101
+ }
102
+ if (!response.ok) {
103
+ const rawText = await response.text().catch(() => "");
104
+ const text = rawText ? rawText.slice(0, 500).replace(/Bearer\s+\S+/gi, "Bearer [REDACTED]") : "";
105
+ switch (response.status) {
106
+ case 401:
107
+ throw new AuthenticationError();
108
+ case 429:
109
+ throw new RateLimitError();
110
+ default:
111
+ throw new ServerError(text || `HTTP ${response.status}`);
112
+ }
113
+ }
114
+ if (!response.body) {
115
+ throw new ConnectionError("Response body is null");
116
+ }
117
+ const reader = response.body.getReader();
118
+ const decoder = new TextDecoder();
119
+ let buffer = "";
120
+ let currentEvent = "";
121
+ try {
122
+ while (true) {
123
+ const { done, value } = await reader.read();
124
+ if (done) break;
125
+ buffer += decoder.decode(value, { stream: true });
126
+ const lines = buffer.split("\n");
127
+ buffer = lines.pop() ?? "";
128
+ for (const line of lines) {
129
+ if (line.startsWith("event: ")) {
130
+ currentEvent = line.slice(7).trim();
131
+ } else if (line.startsWith("data: ")) {
132
+ const data = line.slice(6);
133
+ if (data === "[DONE]") {
134
+ return;
135
+ }
136
+ yield { event: currentEvent || "message", data };
137
+ }
138
+ if (line === "") {
139
+ currentEvent = "";
140
+ }
141
+ }
142
+ }
143
+ } catch (err) {
144
+ if (err instanceof DOMException && err.name === "AbortError") {
145
+ throw new StreamAbortedError();
146
+ }
147
+ throw err;
148
+ } finally {
149
+ reader.releaseLock();
150
+ }
151
+ }
152
+
153
+ // src/client.ts
154
+ var DEFAULT_BASE_URL = "https://api.astralform.ai";
155
+ function validateBaseURL(url) {
156
+ const cleaned = url.replace(/\/+$/, "");
157
+ try {
158
+ const parsed = new URL(cleaned);
159
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
160
+ throw new Error(
161
+ `Invalid baseURL protocol "${parsed.protocol}" - only http: and https: are allowed`
162
+ );
163
+ }
164
+ return parsed.origin + parsed.pathname.replace(/\/+$/, "");
165
+ } catch (err) {
166
+ if (err instanceof Error && err.message.includes("Invalid baseURL")) {
167
+ throw err;
168
+ }
169
+ throw new Error(`Invalid baseURL: "${cleaned}" is not a valid URL`);
170
+ }
171
+ }
172
+ var AstralformClient = class {
173
+ constructor(config) {
174
+ if (!config.apiKey || typeof config.apiKey !== "string") {
175
+ throw new Error("apiKey is required and must be a non-empty string");
176
+ }
177
+ this.apiKey = config.apiKey;
178
+ this.baseURL = validateBaseURL(config.baseURL ?? DEFAULT_BASE_URL);
179
+ this.userId = config.userId;
180
+ this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
181
+ }
182
+ get headers() {
183
+ return {
184
+ Authorization: `Bearer ${this.apiKey}`,
185
+ "X-End-User-ID": this.userId,
186
+ "Content-Type": "application/json"
187
+ };
188
+ }
189
+ async request(method, path, body) {
190
+ const response = await this.fetchFn(`${this.baseURL}${path}`, {
191
+ method,
192
+ headers: this.headers,
193
+ body: body !== void 0 ? JSON.stringify(body) : void 0
194
+ }).catch((err) => {
195
+ throw new ConnectionError(
196
+ err instanceof Error ? err.message : "Failed to connect"
197
+ );
198
+ });
199
+ await this.handleError(response);
200
+ return response;
201
+ }
202
+ async get(path) {
203
+ const response = await this.request("GET", path);
204
+ return response.json();
205
+ }
206
+ async post(path, body) {
207
+ const response = await this.request("POST", path, body);
208
+ return response.json();
209
+ }
210
+ async del(path) {
211
+ await this.request("DELETE", path);
212
+ }
213
+ async handleError(response) {
214
+ if (response.ok) return;
215
+ const text = await response.text().catch(() => "");
216
+ switch (response.status) {
217
+ case 401:
218
+ throw new AuthenticationError();
219
+ case 429:
220
+ throw new RateLimitError();
221
+ default: {
222
+ const safeText = text ? text.slice(0, 500).replace(/Bearer\s+\S+/gi, "Bearer [REDACTED]") : "";
223
+ throw new ServerError(safeText || `HTTP ${response.status}`);
224
+ }
225
+ }
226
+ }
227
+ // --- REST Methods ---
228
+ async getHealth() {
229
+ return this.get("/v1/health");
230
+ }
231
+ async getProjectStatus() {
232
+ const raw = await this.get("/v1/project/status");
233
+ return {
234
+ isReady: raw.is_ready,
235
+ llmConfigured: raw.llm_configured,
236
+ llmProvider: raw.llm_provider,
237
+ llmModel: raw.llm_model,
238
+ message: raw.message
239
+ };
240
+ }
241
+ async getConversations(limit = 50, offset = 0) {
242
+ const safeLimit = Math.max(1, Math.min(200, Math.floor(Number(limit))));
243
+ const safeOffset = Math.max(0, Math.floor(Number(offset)));
244
+ const raw = await this.get(`/v1/conversations?limit=${safeLimit}&offset=${safeOffset}`);
245
+ return raw.map((c) => ({
246
+ id: c.id,
247
+ title: c.title,
248
+ messageCount: c.message_count,
249
+ createdAt: c.created_at,
250
+ updatedAt: c.updated_at
251
+ }));
252
+ }
253
+ async getMessages(conversationId) {
254
+ const raw = await this.get(`/v1/conversations/${encodeURIComponent(conversationId)}/messages`);
255
+ return raw.map((m) => ({
256
+ id: m.id,
257
+ conversationId: m.conversation_id,
258
+ role: m.role,
259
+ content: m.content,
260
+ parentId: m.parent_id,
261
+ status: "complete",
262
+ createdAt: m.created_at
263
+ }));
264
+ }
265
+ async deleteConversation(id) {
266
+ await this.del(`/v1/conversations/${encodeURIComponent(id)}`);
267
+ }
268
+ async getAgents() {
269
+ const raw = await this.get("/v1/agents");
270
+ return raw.map((a) => ({
271
+ name: a.name,
272
+ displayName: a.display_name,
273
+ description: a.description,
274
+ isOrchestrator: a.is_orchestrator,
275
+ isEnabled: a.is_enabled,
276
+ avatarUrl: a.avatar_url
277
+ }));
278
+ }
279
+ async getSkills() {
280
+ const raw = await this.get("/v1/skills");
281
+ return raw.map((s) => ({
282
+ name: s.name,
283
+ displayName: s.display_name,
284
+ description: s.description,
285
+ isEnabled: s.is_enabled
286
+ }));
287
+ }
288
+ async submitToolResult(request) {
289
+ await this.post("/v1/tool-result", request);
290
+ }
291
+ // --- Conversation Assets ---
292
+ mapAsset(raw) {
293
+ return {
294
+ id: raw.id,
295
+ kind: raw.kind,
296
+ originalName: raw.original_name,
297
+ mediaType: raw.media_type,
298
+ sizeBytes: raw.size_bytes,
299
+ workspacePath: raw.workspace_path,
300
+ sourceMessageId: raw.source_message_id,
301
+ agentName: raw.agent_name,
302
+ createdAt: raw.created_at
303
+ };
304
+ }
305
+ async uploadFile(conversationId, file, filename) {
306
+ const formData = new FormData();
307
+ formData.append("file", file, filename);
308
+ const response = await this.fetchFn(
309
+ `${this.baseURL}/v1/conversations/${encodeURIComponent(conversationId)}/uploads`,
310
+ {
311
+ method: "POST",
312
+ headers: {
313
+ Authorization: `Bearer ${this.apiKey}`,
314
+ "X-End-User-ID": this.userId
315
+ },
316
+ body: formData
317
+ }
318
+ ).catch((err) => {
319
+ throw new ConnectionError(
320
+ err instanceof Error ? err.message : "Failed to connect"
321
+ );
322
+ });
323
+ await this.handleError(response);
324
+ const raw = await response.json();
325
+ return this.mapAsset(raw);
326
+ }
327
+ async listUploads(conversationId) {
328
+ const raw = await this.get(
329
+ `/v1/conversations/${encodeURIComponent(conversationId)}/uploads`
330
+ );
331
+ return raw.map((r) => this.mapAsset(r));
332
+ }
333
+ async listOutputs(conversationId) {
334
+ const raw = await this.get(
335
+ `/v1/conversations/${encodeURIComponent(conversationId)}/outputs`
336
+ );
337
+ return raw.map((r) => this.mapAsset(r));
338
+ }
339
+ // --- Jobs API ---
340
+ async createJob(request) {
341
+ return this.post("/v1/jobs", request);
342
+ }
343
+ async *streamJobEvents(jobId, afterSeq = -1, signal) {
344
+ const url = `${this.baseURL}/v1/jobs/${encodeURIComponent(jobId)}/events?after=${afterSeq}`;
345
+ yield* streamJobSSE({
346
+ url,
347
+ headers: this.headers,
348
+ signal,
349
+ fetchFn: this.fetchFn
350
+ });
351
+ }
352
+ async cancelJob(jobId) {
353
+ await this.post(`/v1/jobs/${encodeURIComponent(jobId)}/cancel`, {});
354
+ }
355
+ };
356
+
357
+ // src/storage.ts
358
+ var InMemoryStorage = class {
359
+ constructor() {
360
+ this.conversations = /* @__PURE__ */ new Map();
361
+ this.messages = /* @__PURE__ */ new Map();
362
+ }
363
+ async fetchConversations() {
364
+ return Array.from(this.conversations.values()).sort(
365
+ (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
366
+ );
367
+ }
368
+ async fetchConversation(id) {
369
+ return this.conversations.get(id) ?? null;
370
+ }
371
+ async createConversation(id, title) {
372
+ const now = (/* @__PURE__ */ new Date()).toISOString();
373
+ const conversation = {
374
+ id,
375
+ title,
376
+ messageCount: 0,
377
+ createdAt: now,
378
+ updatedAt: now
379
+ };
380
+ this.conversations.set(id, conversation);
381
+ this.messages.set(id, []);
382
+ return conversation;
383
+ }
384
+ async updateConversationTitle(id, title) {
385
+ const conv = this.conversations.get(id);
386
+ if (conv) {
387
+ conv.title = title;
388
+ conv.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
389
+ }
390
+ }
391
+ async deleteConversation(id) {
392
+ this.conversations.delete(id);
393
+ this.messages.delete(id);
394
+ }
395
+ async fetchMessages(conversationId) {
396
+ return this.messages.get(conversationId) ?? [];
397
+ }
398
+ async addMessage(message, conversationId) {
399
+ const msgs = this.messages.get(conversationId) ?? [];
400
+ msgs.push(message);
401
+ this.messages.set(conversationId, msgs);
402
+ const conv = this.conversations.get(conversationId);
403
+ if (conv) {
404
+ conv.messageCount = msgs.length;
405
+ conv.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
406
+ }
407
+ }
408
+ async updateMessageStatus(id, status) {
409
+ for (const msgs of this.messages.values()) {
410
+ const msg = msgs.find((m) => m.id === id);
411
+ if (msg) {
412
+ msg.status = status;
413
+ return;
414
+ }
415
+ }
416
+ }
417
+ async deleteMessage(id) {
418
+ for (const [convId, msgs] of this.messages.entries()) {
419
+ const idx = msgs.findIndex((m) => m.id === id);
420
+ if (idx !== -1) {
421
+ msgs.splice(idx, 1);
422
+ const conv = this.conversations.get(convId);
423
+ if (conv) {
424
+ conv.messageCount = msgs.length;
425
+ }
426
+ return;
427
+ }
428
+ }
429
+ }
430
+ };
431
+
432
+ // src/tools.ts
433
+ var TOOL_NAME_PATTERN = /^[a-zA-Z0-9_.\-]+$/;
434
+ function sanitizeArgs(args) {
435
+ const clean = /* @__PURE__ */ Object.create(null);
436
+ for (const key of Object.keys(args)) {
437
+ if (key === "__proto__" || key === "constructor" || key === "prototype") {
438
+ continue;
439
+ }
440
+ clean[key] = args[key];
441
+ }
442
+ return clean;
443
+ }
444
+ var ToolRegistry = class {
445
+ constructor() {
446
+ this.tools = /* @__PURE__ */ new Map();
447
+ }
448
+ registerTool(name, description, inputSchema, handler) {
449
+ if (!name || !TOOL_NAME_PATTERN.test(name)) {
450
+ throw new Error(
451
+ `Invalid tool name "${name}" - must match ${TOOL_NAME_PATTERN}`
452
+ );
453
+ }
454
+ if (name.length > 256) {
455
+ throw new Error("Tool name must be 256 characters or fewer");
456
+ }
457
+ this.tools.set(name, { name, description, inputSchema, handler });
458
+ }
459
+ unregisterTool(name) {
460
+ return this.tools.delete(name);
461
+ }
462
+ hasTool(name) {
463
+ return this.tools.has(name);
464
+ }
465
+ async executeTool(request) {
466
+ const tool = this.tools.get(request.toolName);
467
+ if (!tool) {
468
+ return {
469
+ call_id: request.callId,
470
+ tool_name: request.toolName,
471
+ result: `Tool "${request.toolName}" not found`,
472
+ is_error: true
473
+ };
474
+ }
475
+ try {
476
+ const result = await tool.handler(sanitizeArgs(request.arguments));
477
+ return {
478
+ call_id: request.callId,
479
+ tool_name: request.toolName,
480
+ result,
481
+ is_error: false
482
+ };
483
+ } catch (err) {
484
+ return {
485
+ call_id: request.callId,
486
+ tool_name: request.toolName,
487
+ result: err instanceof Error ? err.message : String(err),
488
+ is_error: true
489
+ };
490
+ }
491
+ }
492
+ getManifest() {
493
+ return Array.from(this.tools.values()).map((t) => ({
494
+ name: t.name,
495
+ description: t.description,
496
+ parameters: t.inputSchema
497
+ }));
498
+ }
499
+ getToolNames() {
500
+ return Array.from(this.tools.keys());
501
+ }
502
+ clear() {
503
+ this.tools.clear();
504
+ }
505
+ };
506
+
507
+ // src/utils.ts
508
+ function generateId() {
509
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
510
+ return crypto.randomUUID();
511
+ }
512
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
513
+ const r = Math.random() * 16 | 0;
514
+ const v = c === "x" ? r : r & 3 | 8;
515
+ return v.toString(16);
516
+ });
517
+ }
518
+
519
+ // src/session.ts
520
+ var ChatSession = class {
521
+ constructor(config, storage) {
522
+ // State
523
+ this.conversationId = null;
524
+ this.conversations = [];
525
+ this.messages = [];
526
+ this.streamingContent = "";
527
+ this.isStreaming = false;
528
+ this.executingTool = null;
529
+ this.projectStatus = null;
530
+ this.agents = [];
531
+ this.skills = [];
532
+ this.enabledClientTools = /* @__PURE__ */ new Set();
533
+ this.modelDisplayName = null;
534
+ // New state fields
535
+ this.thinkingContent = "";
536
+ this.isThinking = false;
537
+ this.activeSubagents = /* @__PURE__ */ new Map();
538
+ this.sources = [];
539
+ this.capsuleOutputs = [];
540
+ this.todos = [];
541
+ this.activeTools = /* @__PURE__ */ new Map();
542
+ this.handlers = /* @__PURE__ */ new Set();
543
+ this.abortController = null;
544
+ /** Last received sequence number for resumable reconnection */
545
+ this.lastSeq = -1;
546
+ /** Current job ID for cancellation */
547
+ this.currentJobId = null;
548
+ this.client = new AstralformClient(config);
549
+ this.toolRegistry = new ToolRegistry();
550
+ this.storage = storage ?? new InMemoryStorage();
551
+ }
552
+ on(handler) {
553
+ this.handlers.add(handler);
554
+ return () => {
555
+ this.handlers.delete(handler);
556
+ };
557
+ }
558
+ emit(event) {
559
+ for (const handler of this.handlers) {
560
+ try {
561
+ handler(event);
562
+ } catch {
563
+ }
564
+ }
565
+ }
566
+ async connect() {
567
+ const [status, conversations, agents, skills] = await Promise.allSettled([
568
+ this.client.getProjectStatus(),
569
+ this.client.getConversations(),
570
+ this.client.getAgents().catch(() => []),
571
+ this.client.getSkills().catch(() => [])
572
+ ]);
573
+ if (status.status === "fulfilled") {
574
+ this.projectStatus = status.value;
575
+ }
576
+ if (conversations.status === "fulfilled") {
577
+ this.conversations = conversations.value;
578
+ }
579
+ if (agents.status === "fulfilled") {
580
+ this.agents = agents.value;
581
+ }
582
+ if (skills.status === "fulfilled") {
583
+ this.skills = skills.value;
584
+ }
585
+ this.emit({ type: "connected" });
586
+ }
587
+ async send(content, options) {
588
+ if (this.isStreaming) return;
589
+ const conversationId = options?.conversationId ?? this.conversationId ?? void 0;
590
+ const userMessage = {
591
+ id: generateId(),
592
+ conversationId: conversationId ?? "",
593
+ role: "user",
594
+ content,
595
+ status: "complete",
596
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
597
+ };
598
+ if (conversationId) {
599
+ await this.storage.addMessage(userMessage, conversationId);
600
+ }
601
+ this.messages.push(userMessage);
602
+ const request = {
603
+ message: content,
604
+ conversation_id: conversationId,
605
+ mcp_manifest: this.toolRegistry.getManifest(),
606
+ enabled_mcp: Array.from(
607
+ options?.enabledClientTools ?? this.enabledClientTools
608
+ ),
609
+ upload_ids: options?.uploadIds,
610
+ agent_name: options?.agentName
611
+ };
612
+ await this.processStream(request);
613
+ }
614
+ async resendFromCheckpoint(messageId, newContent) {
615
+ if (this.isStreaming) return;
616
+ const request = {
617
+ message: newContent,
618
+ conversation_id: this.conversationId ?? void 0,
619
+ resend_from: messageId,
620
+ mcp_manifest: this.toolRegistry.getManifest(),
621
+ enabled_mcp: Array.from(this.enabledClientTools)
622
+ };
623
+ await this.processStream(request);
624
+ }
625
+ async processStream(request) {
626
+ this.isStreaming = true;
627
+ this.streamingContent = "";
628
+ this.thinkingContent = "";
629
+ this.isThinking = false;
630
+ this.activeSubagents.clear();
631
+ this.sources = [];
632
+ this.capsuleOutputs = [];
633
+ this.todos = [];
634
+ this.activeTools.clear();
635
+ this.abortController = new AbortController();
636
+ try {
637
+ await this.consumeJobStream(request);
638
+ } catch (err) {
639
+ this.emit({
640
+ type: "error",
641
+ error: err instanceof Error ? err : new ConnectionError(String(err))
642
+ });
643
+ } finally {
644
+ this.isStreaming = false;
645
+ this.executingTool = null;
646
+ this.abortController = null;
647
+ }
648
+ }
649
+ async consumeJobStream(request) {
650
+ const job = await this.client.createJob(request);
651
+ this.currentJobId = job.job_id;
652
+ let conversationId = job.conversation_id;
653
+ if (!this.conversationId) {
654
+ this.conversationId = conversationId;
655
+ }
656
+ const messageId = job.message_id;
657
+ this.lastSeq = -1;
658
+ let stopTitle;
659
+ const stream = this.client.streamJobEvents(
660
+ job.job_id,
661
+ this.lastSeq,
662
+ this.abortController?.signal
663
+ );
664
+ for await (const raw of stream) {
665
+ let parsed;
666
+ try {
667
+ const data = JSON.parse(raw.data);
668
+ if (typeof data !== "object" || data === null || typeof data.type !== "string") {
669
+ if (typeof data?.seq === "number") {
670
+ this.lastSeq = data.seq;
671
+ }
672
+ continue;
673
+ }
674
+ parsed = data;
675
+ if (typeof data.seq === "number") {
676
+ this.lastSeq = data.seq;
677
+ }
678
+ } catch {
679
+ continue;
680
+ }
681
+ switch (parsed.type) {
682
+ case "message_start":
683
+ conversationId = parsed.conversation_id;
684
+ if (!this.conversationId) {
685
+ this.conversationId = conversationId;
686
+ }
687
+ if (parsed.model_display_name) {
688
+ this.modelDisplayName = parsed.model_display_name;
689
+ this.emit({
690
+ type: "model_info",
691
+ name: parsed.model_display_name
692
+ });
693
+ }
694
+ if (parsed.agent_name) {
695
+ this.emit({
696
+ type: "agent_start",
697
+ agentName: parsed.agent_name,
698
+ agentDisplayName: parsed.agent_display_name
699
+ });
700
+ }
701
+ break;
702
+ case "content_block_delta":
703
+ this.streamingContent += parsed.delta.text;
704
+ this.emit({ type: "chunk", text: parsed.delta.text });
705
+ break;
706
+ case "tool_use_start": {
707
+ const toolCall = {
708
+ callId: parsed.call_id,
709
+ toolName: parsed.tool,
710
+ displayName: parsed.display_name,
711
+ description: parsed.description,
712
+ arguments: parsed.arguments,
713
+ isClientTool: parsed.is_client_tool
714
+ };
715
+ this.activeTools.set(parsed.call_id, {
716
+ toolName: parsed.tool,
717
+ displayName: parsed.display_name,
718
+ description: parsed.description,
719
+ arguments: parsed.arguments,
720
+ callId: parsed.call_id,
721
+ status: parsed.is_client_tool ? "calling" : "executing",
722
+ isClientTool: parsed.is_client_tool
723
+ });
724
+ this.emit({ type: "tool_call", request: toolCall });
725
+ if (parsed.is_client_tool) {
726
+ const results = await this.executeClientTools([toolCall]);
727
+ await this.client.submitToolResult({
728
+ conversation_id: conversationId,
729
+ message_id: messageId,
730
+ tool_results: results
731
+ });
732
+ }
733
+ break;
734
+ }
735
+ case "tool_use_end": {
736
+ const toolState = this.activeTools.get(parsed.call_id);
737
+ if (toolState) {
738
+ toolState.status = "completed";
739
+ }
740
+ this.emit({
741
+ type: "tool_end",
742
+ callId: parsed.call_id,
743
+ toolName: parsed.tool
744
+ });
745
+ break;
746
+ }
747
+ case "agent_start":
748
+ this.emit({
749
+ type: "agent_start",
750
+ agentName: parsed.agent_name,
751
+ agentDisplayName: parsed.agent_display_name,
752
+ avatarUrl: parsed.avatar_url
753
+ });
754
+ break;
755
+ case "agent_end":
756
+ this.emit({ type: "agent_end", agentName: parsed.agent_name });
757
+ break;
758
+ case "subagent_start":
759
+ this.activeSubagents.set(parsed.tool_call_id, {
760
+ agentName: parsed.agent_name,
761
+ displayName: parsed.display_name,
762
+ avatarUrl: parsed.avatar_url,
763
+ description: parsed.description,
764
+ content: "",
765
+ isActive: true
766
+ });
767
+ this.emit({
768
+ type: "subagent_start",
769
+ agentName: parsed.agent_name,
770
+ displayName: parsed.display_name,
771
+ toolCallId: parsed.tool_call_id,
772
+ avatarUrl: parsed.avatar_url,
773
+ description: parsed.description
774
+ });
775
+ break;
776
+ case "subagent_update": {
777
+ const sub = this.activeSubagents.get(parsed.tool_call_id);
778
+ if (sub) {
779
+ sub.agentName = parsed.agent_name;
780
+ sub.displayName = parsed.display_name;
781
+ }
782
+ this.emit({
783
+ type: "subagent_update",
784
+ agentName: parsed.agent_name,
785
+ displayName: parsed.display_name,
786
+ toolCallId: parsed.tool_call_id
787
+ });
788
+ break;
789
+ }
790
+ case "subagent_content_delta": {
791
+ const subagent = this.activeSubagents.get(parsed.tool_call_id);
792
+ if (subagent) {
793
+ subagent.content += parsed.delta.text;
794
+ }
795
+ this.emit({
796
+ type: "subagent_chunk",
797
+ agentName: parsed.agent_name,
798
+ toolCallId: parsed.tool_call_id,
799
+ text: parsed.delta.text
800
+ });
801
+ break;
802
+ }
803
+ case "subagent_end": {
804
+ const sub = this.activeSubagents.get(parsed.tool_call_id);
805
+ if (sub) {
806
+ sub.isActive = false;
807
+ }
808
+ this.emit({
809
+ type: "subagent_end",
810
+ agentName: parsed.agent_name,
811
+ displayName: parsed.display_name,
812
+ toolCallId: parsed.tool_call_id
813
+ });
814
+ break;
815
+ }
816
+ case "thinking_delta":
817
+ this.thinkingContent += parsed.delta.text;
818
+ this.isThinking = true;
819
+ this.emit({ type: "thinking_delta", text: parsed.delta.text });
820
+ break;
821
+ case "thinking_complete":
822
+ this.isThinking = false;
823
+ this.emit({ type: "thinking_complete" });
824
+ break;
825
+ case "sources":
826
+ this.sources.push(...parsed.sources);
827
+ this.emit({ type: "sources", sources: parsed.sources });
828
+ break;
829
+ case "capsule_output": {
830
+ const capsule = {
831
+ toolName: parsed.tool_name,
832
+ agentName: parsed.agent_name,
833
+ command: parsed.command,
834
+ output: parsed.output,
835
+ durationMs: parsed.duration_ms
836
+ };
837
+ this.capsuleOutputs.push(capsule);
838
+ this.emit({ type: "capsule_output", ...capsule });
839
+ break;
840
+ }
841
+ case "todo_update":
842
+ this.todos = parsed.todos;
843
+ this.emit({ type: "todo_update", todos: parsed.todos });
844
+ break;
845
+ case "subagent_tool_use":
846
+ this.emit({
847
+ type: "subagent_tool_use",
848
+ agentName: parsed.agent_name,
849
+ toolName: parsed.tool,
850
+ toolCallId: parsed.tool_call_id,
851
+ result: parsed.result
852
+ });
853
+ break;
854
+ case "asset_created":
855
+ this.emit({
856
+ type: "asset_created",
857
+ assetId: parsed.asset_id,
858
+ name: parsed.name,
859
+ url: parsed.url,
860
+ mediaType: parsed.media_type,
861
+ sizeBytes: parsed.size_bytes
862
+ });
863
+ break;
864
+ case "retry":
865
+ this.emit({
866
+ type: "retry",
867
+ attempt: parsed.attempt,
868
+ maxAttempts: parsed.max_attempts,
869
+ delaySeconds: parsed.delay_seconds
870
+ });
871
+ break;
872
+ case "message_stop":
873
+ stopTitle = parsed.title;
874
+ break;
875
+ case "error":
876
+ this.emit({
877
+ type: "error",
878
+ error: new AstralformError(parsed.message, parsed.code)
879
+ });
880
+ break;
881
+ }
882
+ }
883
+ this.currentJobId = null;
884
+ await this.completeStream(conversationId, messageId, stopTitle);
885
+ }
886
+ async executeClientTools(toolCalls) {
887
+ const results = [];
888
+ for (const call of toolCalls) {
889
+ this.executingTool = call.toolName;
890
+ const toolState = this.activeTools.get(call.callId);
891
+ if (toolState) {
892
+ toolState.status = "executing";
893
+ }
894
+ this.emit({ type: "tool_executing", name: call.toolName });
895
+ const result = await this.toolRegistry.executeTool(call);
896
+ results.push(result);
897
+ if (toolState) {
898
+ toolState.status = "completed";
899
+ }
900
+ this.emit({
901
+ type: "tool_completed",
902
+ name: call.toolName,
903
+ result: result.result
904
+ });
905
+ }
906
+ this.executingTool = null;
907
+ return results;
908
+ }
909
+ async completeStream(conversationId, messageId, title) {
910
+ const assistantMessage = {
911
+ id: messageId || generateId(),
912
+ conversationId,
913
+ role: "assistant",
914
+ content: this.streamingContent,
915
+ status: "complete",
916
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
917
+ };
918
+ this.messages.push(assistantMessage);
919
+ await this.storage.addMessage(assistantMessage, conversationId);
920
+ if (title && conversationId) {
921
+ await this.storage.updateConversationTitle(conversationId, title);
922
+ const conv = this.conversations.find((c) => c.id === conversationId);
923
+ if (conv) {
924
+ conv.title = title;
925
+ }
926
+ }
927
+ this.emit({
928
+ type: "complete",
929
+ content: this.streamingContent,
930
+ conversationId,
931
+ messageId: assistantMessage.id,
932
+ title
933
+ });
934
+ }
935
+ disconnect() {
936
+ if (this.currentJobId) {
937
+ this.client.cancelJob(this.currentJobId).catch(() => {
938
+ });
939
+ this.currentJobId = null;
940
+ }
941
+ this.abortController?.abort();
942
+ this.abortController = null;
943
+ this.isStreaming = false;
944
+ this.streamingContent = "";
945
+ this.executingTool = null;
946
+ this.emit({ type: "disconnected" });
947
+ }
948
+ async createNewConversation() {
949
+ const id = generateId();
950
+ const conversation = await this.storage.createConversation(
951
+ id,
952
+ "New Conversation"
953
+ );
954
+ this.conversations.unshift(conversation);
955
+ this.conversationId = id;
956
+ this.messages = [];
957
+ this.streamingContent = "";
958
+ return id;
959
+ }
960
+ async switchConversation(id) {
961
+ this.conversationId = id;
962
+ try {
963
+ this.messages = await this.client.getMessages(id);
964
+ } catch {
965
+ this.messages = await this.storage.fetchMessages(id);
966
+ }
967
+ this.streamingContent = "";
968
+ }
969
+ async deleteConversation(id) {
970
+ try {
971
+ await this.client.deleteConversation(id);
972
+ } catch {
973
+ }
974
+ await this.storage.deleteConversation(id);
975
+ this.conversations = this.conversations.filter((c) => c.id !== id);
976
+ if (this.conversationId === id) {
977
+ this.conversationId = null;
978
+ this.messages = [];
979
+ }
980
+ }
981
+ toggleClientTool(name) {
982
+ if (this.enabledClientTools.has(name)) {
983
+ this.enabledClientTools.delete(name);
984
+ return false;
985
+ }
986
+ this.enabledClientTools.add(name);
987
+ return true;
988
+ }
989
+ };
990
+ // Annotate the CommonJS export names for ESM import in node:
991
+ 0 && (module.exports = {
992
+ AstralformClient,
993
+ AstralformError,
994
+ AuthenticationError,
995
+ ChatSession,
996
+ ConnectionError,
997
+ InMemoryStorage,
998
+ LLMNotConfiguredError,
999
+ RateLimitError,
1000
+ ServerError,
1001
+ StreamAbortedError,
1002
+ ToolRegistry,
1003
+ generateId,
1004
+ streamJobSSE
1005
+ });
1006
+ //# sourceMappingURL=index.cjs.map