@astralform/js 0.2.3 → 1.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 +160 -73
- package/dist/index.cjs +972 -1022
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +738 -564
- package/dist/index.d.ts +738 -564
- package/dist/index.js +965 -1019
- package/dist/index.js.map +1 -1
- package/package.json +17 -10
package/dist/index.cjs
CHANGED
|
@@ -23,20 +23,24 @@ __export(index_exports, {
|
|
|
23
23
|
AstralformClient: () => AstralformClient,
|
|
24
24
|
AstralformError: () => AstralformError,
|
|
25
25
|
AuthenticationError: () => AuthenticationError,
|
|
26
|
-
BlockBuilder: () => BlockBuilder,
|
|
27
26
|
ChatEventType: () => ChatEventType,
|
|
28
27
|
ChatSession: () => ChatSession,
|
|
29
28
|
ConnectionError: () => ConnectionError,
|
|
30
29
|
InMemoryStorage: () => InMemoryStorage,
|
|
31
30
|
LLMNotConfiguredError: () => LLMNotConfiguredError,
|
|
31
|
+
ProtocolRegistry: () => ProtocolRegistry,
|
|
32
32
|
RateLimitError: () => RateLimitError,
|
|
33
33
|
ServerError: () => ServerError,
|
|
34
34
|
StreamAbortedError: () => StreamAbortedError,
|
|
35
35
|
StreamManager: () => StreamManager,
|
|
36
36
|
ToolRegistry: () => ToolRegistry,
|
|
37
37
|
generateId: () => generateId,
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
isEmbeddedResource: () => isEmbeddedResource,
|
|
39
|
+
mapSseToChat: () => mapSseToChat,
|
|
40
|
+
parseEmbeddedResource: () => parseEmbeddedResource,
|
|
41
|
+
replayEvents: () => replayEvents,
|
|
42
|
+
streamJobSSE: () => streamJobSSE,
|
|
43
|
+
translateDelta: () => translateDelta
|
|
40
44
|
});
|
|
41
45
|
module.exports = __toCommonJS(index_exports);
|
|
42
46
|
|
|
@@ -55,9 +59,10 @@ var AuthenticationError = class extends AstralformError {
|
|
|
55
59
|
}
|
|
56
60
|
};
|
|
57
61
|
var RateLimitError = class extends AstralformError {
|
|
58
|
-
constructor(message = "Rate limit exceeded") {
|
|
62
|
+
constructor(message = "Rate limit exceeded", details = {}) {
|
|
59
63
|
super(message, "rate_limit_error");
|
|
60
64
|
this.name = "RateLimitError";
|
|
65
|
+
Object.assign(this, details);
|
|
61
66
|
}
|
|
62
67
|
};
|
|
63
68
|
var LLMNotConfiguredError = class extends AstralformError {
|
|
@@ -85,6 +90,143 @@ var StreamAbortedError = class extends AstralformError {
|
|
|
85
90
|
}
|
|
86
91
|
};
|
|
87
92
|
|
|
93
|
+
// src/utils.ts
|
|
94
|
+
function sanitizeErrorText(text) {
|
|
95
|
+
return text.slice(0, 500).replace(/Bearer\s+\S+/gi, "Bearer [REDACTED]");
|
|
96
|
+
}
|
|
97
|
+
function generateId() {
|
|
98
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
99
|
+
return crypto.randomUUID();
|
|
100
|
+
}
|
|
101
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
102
|
+
const r = Math.random() * 16 | 0;
|
|
103
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
104
|
+
return v.toString(16);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/rate-limit.ts
|
|
109
|
+
var DEFAULT_MESSAGE = "Rate limit exceeded";
|
|
110
|
+
function parseNumber(value) {
|
|
111
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
if (typeof value === "string") {
|
|
115
|
+
const trimmed = value.trim();
|
|
116
|
+
if (!trimmed) {
|
|
117
|
+
return void 0;
|
|
118
|
+
}
|
|
119
|
+
const parsed = Number(trimmed);
|
|
120
|
+
if (Number.isFinite(parsed)) {
|
|
121
|
+
return parsed;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return void 0;
|
|
125
|
+
}
|
|
126
|
+
function parseString(value) {
|
|
127
|
+
if (typeof value !== "string") {
|
|
128
|
+
return void 0;
|
|
129
|
+
}
|
|
130
|
+
const trimmed = value.trim();
|
|
131
|
+
return trimmed ? trimmed : void 0;
|
|
132
|
+
}
|
|
133
|
+
function parseJsonObject(rawText) {
|
|
134
|
+
if (!rawText) {
|
|
135
|
+
return void 0;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
const parsed = JSON.parse(rawText);
|
|
139
|
+
if (parsed && typeof parsed === "object") {
|
|
140
|
+
return parsed;
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
144
|
+
return void 0;
|
|
145
|
+
}
|
|
146
|
+
function parseRetryAfterHeader(value) {
|
|
147
|
+
if (!value) {
|
|
148
|
+
return void 0;
|
|
149
|
+
}
|
|
150
|
+
const numeric = parseNumber(value);
|
|
151
|
+
if (numeric !== void 0) {
|
|
152
|
+
return Math.max(0, Math.ceil(numeric));
|
|
153
|
+
}
|
|
154
|
+
const asDate = Date.parse(value);
|
|
155
|
+
if (Number.isFinite(asDate)) {
|
|
156
|
+
const diffMs = asDate - Date.now();
|
|
157
|
+
return Math.max(0, Math.ceil(diffMs / 1e3));
|
|
158
|
+
}
|
|
159
|
+
return void 0;
|
|
160
|
+
}
|
|
161
|
+
function parseResetTimestamp(value) {
|
|
162
|
+
const numeric = parseNumber(value);
|
|
163
|
+
if (numeric !== void 0) {
|
|
164
|
+
if (numeric > 1e12) {
|
|
165
|
+
return Math.floor(numeric);
|
|
166
|
+
}
|
|
167
|
+
return Math.floor(numeric * 1e3);
|
|
168
|
+
}
|
|
169
|
+
const asString = parseString(value);
|
|
170
|
+
if (!asString) {
|
|
171
|
+
return void 0;
|
|
172
|
+
}
|
|
173
|
+
const asDate = Date.parse(asString);
|
|
174
|
+
if (Number.isFinite(asDate)) {
|
|
175
|
+
return asDate;
|
|
176
|
+
}
|
|
177
|
+
return void 0;
|
|
178
|
+
}
|
|
179
|
+
function pickFirst(payload, keys, parser) {
|
|
180
|
+
for (const key of keys) {
|
|
181
|
+
const parsed = parser(payload[key]);
|
|
182
|
+
if (parsed !== void 0) {
|
|
183
|
+
return parsed;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return void 0;
|
|
187
|
+
}
|
|
188
|
+
function buildRateLimitDetails(payload, headers) {
|
|
189
|
+
const headerRetryAfter = headers ? parseRetryAfterHeader(headers.get("retry-after")) : void 0;
|
|
190
|
+
const bodyRetryAfter = pickFirst(
|
|
191
|
+
payload,
|
|
192
|
+
["retry_after", "retryAfter", "retry_after_sec", "retryAfterSec"],
|
|
193
|
+
parseNumber
|
|
194
|
+
);
|
|
195
|
+
const retryAfterSec = bodyRetryAfter ?? headerRetryAfter;
|
|
196
|
+
const headerReset = headers ? parseResetTimestamp(
|
|
197
|
+
headers.get("x-ratelimit-reset") ?? headers.get("x-ratelimit-reset-at")
|
|
198
|
+
) : void 0;
|
|
199
|
+
const bodyReset = parseResetTimestamp(
|
|
200
|
+
payload.reset_at ?? payload.resetAt ?? payload.reset
|
|
201
|
+
);
|
|
202
|
+
const resetAt = bodyReset ?? headerReset ?? (retryAfterSec !== void 0 ? Date.now() + retryAfterSec * 1e3 : void 0);
|
|
203
|
+
const limit = pickFirst(payload, ["limit", "rate_limit", "max"], parseNumber) ?? (headers ? parseNumber(headers.get("x-ratelimit-limit")) : void 0);
|
|
204
|
+
const remaining = pickFirst(payload, ["remaining", "rate_limit_remaining"], parseNumber) ?? (headers ? parseNumber(headers.get("x-ratelimit-remaining")) : void 0);
|
|
205
|
+
const scope = pickFirst(payload, ["scope", "limit_scope"], parseString) ?? (headers ? parseString(headers.get("x-ratelimit-scope")) : void 0);
|
|
206
|
+
const policyId = pickFirst(payload, ["policy_id", "policyId", "policy"], parseString) ?? (headers ? parseString(
|
|
207
|
+
headers.get("x-ratelimit-policy") ?? headers.get("x-ratelimit-policy-id")
|
|
208
|
+
) : void 0);
|
|
209
|
+
const requestId = pickFirst(payload, ["request_id", "requestId"], parseString) ?? (headers ? parseString(
|
|
210
|
+
headers.get("x-request-id") ?? headers.get("x-correlation-id")
|
|
211
|
+
) : void 0);
|
|
212
|
+
return {
|
|
213
|
+
retryAfterSec,
|
|
214
|
+
resetAt,
|
|
215
|
+
scope,
|
|
216
|
+
policyId,
|
|
217
|
+
limit,
|
|
218
|
+
remaining,
|
|
219
|
+
requestId
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function createRateLimitErrorFromHttp(response, rawText) {
|
|
223
|
+
const payload = parseJsonObject(rawText) ?? {};
|
|
224
|
+
const details = buildRateLimitDetails(payload, response.headers);
|
|
225
|
+
const sanitizedText = sanitizeErrorText(rawText);
|
|
226
|
+
const message = pickFirst(payload, ["message", "error_description"], parseString) ?? (sanitizedText || DEFAULT_MESSAGE);
|
|
227
|
+
return new RateLimitError(message, details);
|
|
228
|
+
}
|
|
229
|
+
|
|
88
230
|
// src/streaming.ts
|
|
89
231
|
async function* streamJobSSE(options) {
|
|
90
232
|
const { url, headers, signal, fetchFn } = options;
|
|
@@ -105,12 +247,12 @@ async function* streamJobSSE(options) {
|
|
|
105
247
|
}
|
|
106
248
|
if (!response.ok) {
|
|
107
249
|
const rawText = await response.text().catch(() => "");
|
|
108
|
-
const text = rawText ? rawText
|
|
250
|
+
const text = rawText ? sanitizeErrorText(rawText) : "";
|
|
109
251
|
switch (response.status) {
|
|
110
252
|
case 401:
|
|
111
253
|
throw new AuthenticationError();
|
|
112
254
|
case 429:
|
|
113
|
-
throw
|
|
255
|
+
throw createRateLimitErrorFromHttp(response, rawText);
|
|
114
256
|
default:
|
|
115
257
|
throw new ServerError(text || `HTTP ${response.status}`);
|
|
116
258
|
}
|
|
@@ -173,20 +315,126 @@ function validateBaseURL(url) {
|
|
|
173
315
|
throw new Error(`Invalid baseURL: "${cleaned}" is not a valid URL`);
|
|
174
316
|
}
|
|
175
317
|
}
|
|
318
|
+
function isApiKeyConfig(config) {
|
|
319
|
+
return "apiKey" in config;
|
|
320
|
+
}
|
|
176
321
|
var AstralformClient = class {
|
|
177
322
|
constructor(config) {
|
|
178
|
-
if (
|
|
179
|
-
|
|
323
|
+
if (isApiKeyConfig(config)) {
|
|
324
|
+
if (!config.apiKey || typeof config.apiKey !== "string") {
|
|
325
|
+
throw new Error("apiKey is required and must be a non-empty string");
|
|
326
|
+
}
|
|
327
|
+
if (!config.userId || typeof config.userId !== "string") {
|
|
328
|
+
throw new Error("userId is required in API-key mode");
|
|
329
|
+
}
|
|
330
|
+
this.auth = {
|
|
331
|
+
kind: "api_key",
|
|
332
|
+
apiKey: config.apiKey,
|
|
333
|
+
userId: config.userId
|
|
334
|
+
};
|
|
335
|
+
} else {
|
|
336
|
+
if (!config.accessToken || typeof config.accessToken !== "string") {
|
|
337
|
+
throw new Error(
|
|
338
|
+
"accessToken is required and must be a non-empty string in user-token mode"
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
const projectId = typeof config.projectId === "string" && config.projectId.length > 0 ? config.projectId : null;
|
|
342
|
+
this.auth = {
|
|
343
|
+
kind: "user_token",
|
|
344
|
+
accessToken: config.accessToken,
|
|
345
|
+
projectId,
|
|
346
|
+
endUserId: typeof config.endUserId === "string" && config.endUserId.length > 0 ? config.endUserId : null
|
|
347
|
+
};
|
|
180
348
|
}
|
|
181
|
-
this.apiKey = config.apiKey;
|
|
182
349
|
this.baseURL = validateBaseURL(config.baseURL ?? DEFAULT_BASE_URL);
|
|
183
|
-
this.userId = config.userId;
|
|
184
350
|
this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
185
351
|
}
|
|
352
|
+
/**
|
|
353
|
+
* Replace the current OIDC access token without reconstructing the client.
|
|
354
|
+
* Use after refreshing via the host's token manager (e.g., Supabase JS SDK).
|
|
355
|
+
* Throws if the client was created in API-key mode.
|
|
356
|
+
*/
|
|
357
|
+
updateAccessToken(accessToken) {
|
|
358
|
+
if (this.auth.kind !== "user_token") {
|
|
359
|
+
throw new Error("updateAccessToken is only valid in user-token mode");
|
|
360
|
+
}
|
|
361
|
+
if (!accessToken || typeof accessToken !== "string") {
|
|
362
|
+
throw new Error("accessToken must be a non-empty string");
|
|
363
|
+
}
|
|
364
|
+
this.auth = { ...this.auth, accessToken };
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Swap the active project for a user-token client. The backend verifies the
|
|
368
|
+
* current developer has access to the new project; a 403 comes back if not.
|
|
369
|
+
*/
|
|
370
|
+
updateProjectId(projectId) {
|
|
371
|
+
if (this.auth.kind !== "user_token") {
|
|
372
|
+
throw new Error("updateProjectId is only valid in user-token mode");
|
|
373
|
+
}
|
|
374
|
+
if (!projectId || typeof projectId !== "string") {
|
|
375
|
+
throw new Error("projectId must be a non-empty string");
|
|
376
|
+
}
|
|
377
|
+
this.auth = { ...this.auth, projectId };
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Set (or clear) the end-user override for user-token mode.
|
|
381
|
+
*
|
|
382
|
+
* Pass `null` or an empty string to clear — subsequent requests go
|
|
383
|
+
* back to scoping against the developer's own identity. Throws if
|
|
384
|
+
* called in API-key mode, where end-user context already travels via
|
|
385
|
+
* the constructor's `userId` field.
|
|
386
|
+
*/
|
|
387
|
+
updateEndUserId(endUserId) {
|
|
388
|
+
if (this.auth.kind !== "user_token") {
|
|
389
|
+
throw new Error("updateEndUserId is only valid in user-token mode");
|
|
390
|
+
}
|
|
391
|
+
const normalized = typeof endUserId === "string" && endUserId.length > 0 ? endUserId : null;
|
|
392
|
+
this.auth = { ...this.auth, endUserId: normalized };
|
|
393
|
+
}
|
|
394
|
+
/** Current end-user override in user-token mode, or `null` if unset. */
|
|
395
|
+
get endUserId() {
|
|
396
|
+
return this.auth.kind === "user_token" ? this.auth.endUserId : null;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Active project for user-token mode, or `null` if pre-pick (client
|
|
400
|
+
* was constructed without one). For API-key mode the project is baked
|
|
401
|
+
* into the key, so this getter returns `null` there too — use
|
|
402
|
+
* `authMode` to disambiguate.
|
|
403
|
+
*/
|
|
404
|
+
get projectId() {
|
|
405
|
+
return this.auth.kind === "user_token" ? this.auth.projectId : null;
|
|
406
|
+
}
|
|
407
|
+
/** Which auth mode this client was constructed with. */
|
|
408
|
+
get authMode() {
|
|
409
|
+
return this.auth.kind;
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Authorization + identity headers for the current auth mode, without
|
|
413
|
+
* `Content-Type`. Suitable for JSON requests (paired with the JSON header
|
|
414
|
+
* in the `headers` getter) and for multipart uploads where the browser
|
|
415
|
+
* must set its own `Content-Type` boundary.
|
|
416
|
+
*/
|
|
417
|
+
get authHeaders() {
|
|
418
|
+
if (this.auth.kind === "api_key") {
|
|
419
|
+
return {
|
|
420
|
+
Authorization: `Bearer ${this.auth.apiKey}`,
|
|
421
|
+
"X-End-User-ID": this.auth.userId
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
const headers = {
|
|
425
|
+
Authorization: `Bearer ${this.auth.accessToken}`
|
|
426
|
+
};
|
|
427
|
+
if (this.auth.projectId) {
|
|
428
|
+
headers["X-Project-ID"] = this.auth.projectId;
|
|
429
|
+
}
|
|
430
|
+
if (this.auth.endUserId) {
|
|
431
|
+
headers["X-End-User-ID"] = this.auth.endUserId;
|
|
432
|
+
}
|
|
433
|
+
return headers;
|
|
434
|
+
}
|
|
186
435
|
get headers() {
|
|
187
436
|
return {
|
|
188
|
-
|
|
189
|
-
"X-End-User-ID": this.userId,
|
|
437
|
+
...this.authHeaders,
|
|
190
438
|
"Content-Type": "application/json"
|
|
191
439
|
};
|
|
192
440
|
}
|
|
@@ -221,9 +469,9 @@ var AstralformClient = class {
|
|
|
221
469
|
case 401:
|
|
222
470
|
throw new AuthenticationError();
|
|
223
471
|
case 429:
|
|
224
|
-
throw
|
|
472
|
+
throw createRateLimitErrorFromHttp(response, text);
|
|
225
473
|
default: {
|
|
226
|
-
const safeText = text ? text
|
|
474
|
+
const safeText = text ? sanitizeErrorText(text) : "";
|
|
227
475
|
throw new ServerError(safeText || `HTTP ${response.status}`);
|
|
228
476
|
}
|
|
229
477
|
}
|
|
@@ -234,12 +482,18 @@ var AstralformClient = class {
|
|
|
234
482
|
}
|
|
235
483
|
async getProjectStatus() {
|
|
236
484
|
const raw = await this.get("/v1/project/status");
|
|
485
|
+
const ui = raw.ui_components ?? {};
|
|
237
486
|
return {
|
|
238
487
|
isReady: raw.is_ready,
|
|
239
488
|
llmConfigured: raw.llm_configured,
|
|
240
489
|
llmProvider: raw.llm_provider,
|
|
241
490
|
llmModel: raw.llm_model,
|
|
242
|
-
message: raw.message
|
|
491
|
+
message: raw.message,
|
|
492
|
+
uiComponents: {
|
|
493
|
+
enabled: Boolean(ui.enabled),
|
|
494
|
+
protocol: ui.protocol ?? null,
|
|
495
|
+
mimeType: ui.mime_type ?? null
|
|
496
|
+
}
|
|
243
497
|
};
|
|
244
498
|
}
|
|
245
499
|
async getConversations(limit = 50, offset = 0) {
|
|
@@ -297,6 +551,52 @@ var AstralformClient = class {
|
|
|
297
551
|
async submitToolResult(request) {
|
|
298
552
|
await this.post("/v1/tool-result", request);
|
|
299
553
|
}
|
|
554
|
+
async submitToolApproval(request) {
|
|
555
|
+
await this.post("/v1/tool-approval", request);
|
|
556
|
+
}
|
|
557
|
+
// --- End-user tool-permission self-service ---
|
|
558
|
+
/**
|
|
559
|
+
* List the current end user's own remembered tool-permission grants.
|
|
560
|
+
* Only `conversation`/`always` grants exist (`once` is never persisted).
|
|
561
|
+
* Paginated via `limit` (default 100, max 200) / `offset`; `total` lets you
|
|
562
|
+
* page through all of them.
|
|
563
|
+
*/
|
|
564
|
+
async getMyToolPermissions(options) {
|
|
565
|
+
const params = new URLSearchParams();
|
|
566
|
+
if (options?.limit != null) {
|
|
567
|
+
const safeLimit = Math.max(
|
|
568
|
+
1,
|
|
569
|
+
Math.min(200, Math.floor(Number(options.limit)))
|
|
570
|
+
);
|
|
571
|
+
params.set("limit", String(safeLimit));
|
|
572
|
+
}
|
|
573
|
+
if (options?.offset != null) {
|
|
574
|
+
const safeOffset = Math.max(0, Math.floor(Number(options.offset)));
|
|
575
|
+
params.set("offset", String(safeOffset));
|
|
576
|
+
}
|
|
577
|
+
const qs = params.toString();
|
|
578
|
+
const raw = await this.get(`/v1/me/tool-permissions${qs ? `?${qs}` : ""}`);
|
|
579
|
+
return {
|
|
580
|
+
grants: raw.grants.map((g) => ({
|
|
581
|
+
id: g.id,
|
|
582
|
+
toolName: g.tool_name,
|
|
583
|
+
decision: g.decision,
|
|
584
|
+
scope: g.scope,
|
|
585
|
+
conversationId: g.conversation_id,
|
|
586
|
+
createdAt: g.created_at
|
|
587
|
+
})),
|
|
588
|
+
total: raw.total,
|
|
589
|
+
limit: raw.limit,
|
|
590
|
+
offset: raw.offset
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Revoke one of the current end user's remembered grants by id. The agent
|
|
595
|
+
* will ask again the next time that tool is used.
|
|
596
|
+
*/
|
|
597
|
+
async revokeToolPermission(id) {
|
|
598
|
+
await this.del(`/v1/me/tool-permissions/${encodeURIComponent(id)}`);
|
|
599
|
+
}
|
|
300
600
|
// --- Conversation Assets ---
|
|
301
601
|
mapAsset(raw) {
|
|
302
602
|
return {
|
|
@@ -318,10 +618,7 @@ var AstralformClient = class {
|
|
|
318
618
|
`${this.baseURL}/v1/conversations/${encodeURIComponent(conversationId)}/uploads`,
|
|
319
619
|
{
|
|
320
620
|
method: "POST",
|
|
321
|
-
headers:
|
|
322
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
323
|
-
"X-End-User-ID": this.userId
|
|
324
|
-
},
|
|
621
|
+
headers: this.authHeaders,
|
|
325
622
|
body: formData
|
|
326
623
|
}
|
|
327
624
|
).catch((err) => {
|
|
@@ -345,6 +642,31 @@ var AstralformClient = class {
|
|
|
345
642
|
);
|
|
346
643
|
return raw.map((r) => this.mapAsset(r));
|
|
347
644
|
}
|
|
645
|
+
// --- Account-scoped discovery (user-token mode) ---
|
|
646
|
+
//
|
|
647
|
+
// Lets a signed-in user pick which team/project they want to act on.
|
|
648
|
+
// Backend gates these on OIDC user context (no X-Project-ID required) —
|
|
649
|
+
// sending them in API-key mode yields 401.
|
|
650
|
+
async listTeams() {
|
|
651
|
+
const raw = await this.get("/v1/teams");
|
|
652
|
+
return raw.map((t) => ({
|
|
653
|
+
id: t.id,
|
|
654
|
+
name: t.name,
|
|
655
|
+
slug: t.slug,
|
|
656
|
+
isDefault: t.is_default,
|
|
657
|
+
role: t.role
|
|
658
|
+
}));
|
|
659
|
+
}
|
|
660
|
+
async listProjects(teamId) {
|
|
661
|
+
const raw = await this.get(`/v1/teams/${encodeURIComponent(teamId)}/projects`);
|
|
662
|
+
return raw.map((p) => ({
|
|
663
|
+
id: p.id,
|
|
664
|
+
name: p.name,
|
|
665
|
+
teamId: p.team_id,
|
|
666
|
+
createdAt: p.created_at,
|
|
667
|
+
updatedAt: p.updated_at
|
|
668
|
+
}));
|
|
669
|
+
}
|
|
348
670
|
// --- Jobs API ---
|
|
349
671
|
async createJob(request) {
|
|
350
672
|
return this.post("/v1/jobs", request);
|
|
@@ -361,90 +683,50 @@ var AstralformClient = class {
|
|
|
361
683
|
async cancelJob(jobId) {
|
|
362
684
|
await this.post(`/v1/jobs/${encodeURIComponent(jobId)}/cancel`, {});
|
|
363
685
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
this.thinkingStartMs = null;
|
|
377
|
-
this.activeEditorId = null;
|
|
378
|
-
this.activeTodoId = null;
|
|
379
|
-
}
|
|
380
|
-
// ── Registration ──────────────────────────────────────────────
|
|
381
|
-
on(eventType, handler) {
|
|
382
|
-
this._handlers.set(eventType, handler);
|
|
383
|
-
}
|
|
384
|
-
registerHandlers(handlers) {
|
|
385
|
-
for (const [type, handler] of Object.entries(handlers)) {
|
|
386
|
-
this._handlers.set(type, handler);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
// ── Event processing ──────────────────────────────────────────
|
|
390
|
-
processEvent(event) {
|
|
391
|
-
const handler = this._handlers.get(event.type);
|
|
392
|
-
if (handler === null) return;
|
|
393
|
-
if (!handler) return;
|
|
394
|
-
handler(event, this);
|
|
395
|
-
}
|
|
396
|
-
// ── State ─────────────────────────────────────────────────────
|
|
397
|
-
getBlocks() {
|
|
398
|
-
return [...this._blocks];
|
|
399
|
-
}
|
|
400
|
-
reset() {
|
|
401
|
-
this._blocks = [];
|
|
402
|
-
this.activeTextId = null;
|
|
403
|
-
this.activeThinkingId = null;
|
|
404
|
-
this.thinkingStartMs = null;
|
|
405
|
-
this.activeEditorId = null;
|
|
406
|
-
this.activeTodoId = null;
|
|
407
|
-
}
|
|
408
|
-
setOnChange(fn) {
|
|
409
|
-
this._onChange = fn;
|
|
410
|
-
}
|
|
411
|
-
// ── Block manipulation (used by handlers) ─────────────────────
|
|
412
|
-
addBlock(block) {
|
|
413
|
-
this._blocks = [...this._blocks, block];
|
|
414
|
-
this._notify();
|
|
415
|
-
}
|
|
416
|
-
updateBlock(id, updater) {
|
|
417
|
-
let changed = false;
|
|
418
|
-
this._blocks = this._blocks.map((b) => {
|
|
419
|
-
if (b.id !== id) return b;
|
|
420
|
-
changed = true;
|
|
421
|
-
return updater(b);
|
|
422
|
-
});
|
|
423
|
-
if (changed) this._notify();
|
|
424
|
-
}
|
|
425
|
-
/** Update any block by id with a partial update (type-loose for handlers). */
|
|
426
|
-
patchBlock(id, patch) {
|
|
427
|
-
let changed = false;
|
|
428
|
-
this._blocks = this._blocks.map((b) => {
|
|
429
|
-
if (b.id !== id) return b;
|
|
430
|
-
changed = true;
|
|
431
|
-
return { ...b, ...patch };
|
|
432
|
-
});
|
|
433
|
-
if (changed) this._notify();
|
|
686
|
+
async getJob(jobId) {
|
|
687
|
+
const raw = await this.get(`/v1/jobs/${encodeURIComponent(jobId)}`);
|
|
688
|
+
return {
|
|
689
|
+
jobId: raw.job_id,
|
|
690
|
+
status: raw.status,
|
|
691
|
+
createdAt: raw.created_at ?? null,
|
|
692
|
+
startedAt: raw.started_at ?? null,
|
|
693
|
+
completedAt: raw.completed_at ?? null,
|
|
694
|
+
errorMessage: raw.error_message ?? null,
|
|
695
|
+
inputTokens: raw.input_tokens ?? 0,
|
|
696
|
+
outputTokens: raw.output_tokens ?? 0
|
|
697
|
+
};
|
|
434
698
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
699
|
+
async submitFeedback(jobId, request) {
|
|
700
|
+
const body = {
|
|
701
|
+
rating: request.rating
|
|
702
|
+
};
|
|
703
|
+
if (request.comment != null) body.comment = request.comment;
|
|
704
|
+
const raw = await this.post(`/v1/jobs/${encodeURIComponent(jobId)}/feedback`, body);
|
|
705
|
+
return {
|
|
706
|
+
id: raw.id,
|
|
707
|
+
jobId: raw.job_id,
|
|
708
|
+
rating: raw.rating,
|
|
709
|
+
comment: raw.comment,
|
|
710
|
+
createdAt: raw.created_at
|
|
711
|
+
};
|
|
441
712
|
}
|
|
442
|
-
|
|
443
|
-
|
|
713
|
+
async getActiveJob(conversationId) {
|
|
714
|
+
const raw = await this.get(`/v1/conversations/${encodeURIComponent(conversationId)}/active-job`);
|
|
715
|
+
return {
|
|
716
|
+
jobId: raw.job_id ?? null,
|
|
717
|
+
status: raw.status
|
|
718
|
+
};
|
|
444
719
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
720
|
+
async listJobs(conversationId) {
|
|
721
|
+
const raw = await this.get(`/v1/conversations/${encodeURIComponent(conversationId)}/jobs`);
|
|
722
|
+
return raw.map((j) => ({
|
|
723
|
+
jobId: j.job_id,
|
|
724
|
+
status: j.status,
|
|
725
|
+
replacesJobId: j.replaces_job_id ?? null,
|
|
726
|
+
responseContent: j.response_content ?? null,
|
|
727
|
+
metrics: j.metrics ?? null,
|
|
728
|
+
createdAt: j.created_at ?? null
|
|
729
|
+
}));
|
|
448
730
|
}
|
|
449
731
|
};
|
|
450
732
|
|
|
@@ -598,40 +880,348 @@ var ToolRegistry = class {
|
|
|
598
880
|
}
|
|
599
881
|
};
|
|
600
882
|
|
|
601
|
-
// src/
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
883
|
+
// src/protocol-registry.ts
|
|
884
|
+
var ProtocolRegistry = class {
|
|
885
|
+
constructor() {
|
|
886
|
+
this.adapters = /* @__PURE__ */ new Map();
|
|
887
|
+
}
|
|
888
|
+
/** Register or replace the adapter for a MIME type. */
|
|
889
|
+
register(adapter) {
|
|
890
|
+
this.adapters.set(adapter.mimeType, adapter);
|
|
891
|
+
}
|
|
892
|
+
/** Remove the adapter for a MIME type. No-op if not registered. */
|
|
893
|
+
unregister(mimeType) {
|
|
894
|
+
this.adapters.delete(mimeType);
|
|
895
|
+
}
|
|
896
|
+
/** Returns the adapter for a MIME type, or ``null`` if none is registered. */
|
|
897
|
+
get(mimeType) {
|
|
898
|
+
return this.adapters.get(mimeType) ?? null;
|
|
899
|
+
}
|
|
900
|
+
has(mimeType) {
|
|
901
|
+
return this.adapters.has(mimeType);
|
|
902
|
+
}
|
|
903
|
+
/** Drop every adapter. Called when a session disconnects. */
|
|
904
|
+
clear() {
|
|
905
|
+
this.adapters.clear();
|
|
906
|
+
}
|
|
907
|
+
listMimeTypes() {
|
|
908
|
+
return Array.from(this.adapters.keys());
|
|
909
|
+
}
|
|
910
|
+
};
|
|
911
|
+
|
|
912
|
+
// src/translate.ts
|
|
913
|
+
function translateDelta(wire) {
|
|
914
|
+
switch (wire.channel) {
|
|
915
|
+
case "text":
|
|
916
|
+
return { channel: "text", text: wire.text };
|
|
917
|
+
case "thinking":
|
|
918
|
+
return { channel: "thinking", text: wire.text };
|
|
919
|
+
case "signature":
|
|
920
|
+
return { channel: "signature", signature: wire.signature };
|
|
921
|
+
case "input":
|
|
922
|
+
return { channel: "input", partialJson: wire.partial_json };
|
|
923
|
+
case "input_arg":
|
|
924
|
+
return {
|
|
925
|
+
channel: "inputArg",
|
|
926
|
+
argName: wire.arg_name,
|
|
927
|
+
text: wire.text
|
|
928
|
+
};
|
|
929
|
+
case "output":
|
|
930
|
+
return { channel: "output", stream: wire.stream, chunk: wire.chunk };
|
|
931
|
+
case "status":
|
|
932
|
+
return {
|
|
933
|
+
channel: "status",
|
|
934
|
+
status: wire.status,
|
|
935
|
+
note: wire.note
|
|
936
|
+
};
|
|
937
|
+
default:
|
|
938
|
+
return null;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
function translateAgentIdentity(raw) {
|
|
942
|
+
return {
|
|
943
|
+
name: raw.name ?? "",
|
|
944
|
+
displayName: raw.display_name ?? null,
|
|
945
|
+
avatarUrl: raw.avatar_url ?? null,
|
|
946
|
+
description: raw.description ?? null
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
function translateCustomEvent(name, data) {
|
|
950
|
+
switch (name) {
|
|
951
|
+
case "user_message":
|
|
952
|
+
return {
|
|
953
|
+
type: "user_message",
|
|
954
|
+
content: data.content ?? "",
|
|
955
|
+
createdAt: data.created_at
|
|
956
|
+
};
|
|
957
|
+
case "title_generated":
|
|
958
|
+
return {
|
|
959
|
+
type: "title_generated",
|
|
960
|
+
title: data.title ?? ""
|
|
961
|
+
};
|
|
962
|
+
case "todo_update":
|
|
963
|
+
return {
|
|
964
|
+
type: "todo_update",
|
|
965
|
+
todos: data.todos ?? []
|
|
966
|
+
};
|
|
967
|
+
case "context_update":
|
|
968
|
+
return {
|
|
969
|
+
type: "context_update",
|
|
970
|
+
context: data.context ?? {},
|
|
971
|
+
phase: data.phase ?? null,
|
|
972
|
+
updatedAt: data.updated_at ?? null
|
|
973
|
+
};
|
|
974
|
+
case "subagent_start":
|
|
975
|
+
return {
|
|
976
|
+
type: "subagent_start",
|
|
977
|
+
agent: translateAgentIdentity(
|
|
978
|
+
data.agent ?? {}
|
|
979
|
+
),
|
|
980
|
+
taskCallId: data.task_call_id ?? null
|
|
981
|
+
};
|
|
982
|
+
case "subagent_stop":
|
|
983
|
+
return {
|
|
984
|
+
type: "subagent_stop",
|
|
985
|
+
agent: translateAgentIdentity(
|
|
986
|
+
data.agent ?? {}
|
|
987
|
+
),
|
|
988
|
+
taskCallId: data.task_call_id ?? null
|
|
989
|
+
};
|
|
990
|
+
case "context_warning":
|
|
991
|
+
return {
|
|
992
|
+
type: "context_warning",
|
|
993
|
+
severity: data.severity ?? "warning",
|
|
994
|
+
utilizationPct: data.utilization_pct ?? 0,
|
|
995
|
+
remainingTokens: data.remaining_tokens ?? 0,
|
|
996
|
+
windowTokens: data.window_tokens ?? 0,
|
|
997
|
+
inputTokens: data.input_tokens ?? 0,
|
|
998
|
+
message: data.message ?? ""
|
|
999
|
+
};
|
|
1000
|
+
case "memory_recall":
|
|
1001
|
+
return {
|
|
1002
|
+
type: "memory_recall",
|
|
1003
|
+
memories: data.memories ?? []
|
|
1004
|
+
};
|
|
1005
|
+
case "memory_update":
|
|
1006
|
+
return {
|
|
1007
|
+
type: "memory_update",
|
|
1008
|
+
action: data.action ?? "",
|
|
1009
|
+
memoryId: data.memory_id ?? null,
|
|
1010
|
+
key: data.key ?? null,
|
|
1011
|
+
namespace: data.namespace ?? null
|
|
1012
|
+
};
|
|
1013
|
+
case "desktop_stream":
|
|
1014
|
+
return {
|
|
1015
|
+
type: "desktop_stream",
|
|
1016
|
+
url: data.url ?? "",
|
|
1017
|
+
sandboxId: data.sandbox_id ?? null
|
|
1018
|
+
};
|
|
1019
|
+
case "attachment_staged":
|
|
1020
|
+
return {
|
|
1021
|
+
type: "attachment_staged",
|
|
1022
|
+
attachmentId: data.attachment_id ?? "",
|
|
1023
|
+
filename: data.filename ?? "",
|
|
1024
|
+
contentType: data.content_type ?? null,
|
|
1025
|
+
sizeBytes: data.size_bytes ?? null
|
|
1026
|
+
};
|
|
1027
|
+
case "workspace_ready":
|
|
1028
|
+
return {
|
|
1029
|
+
type: "workspace_ready",
|
|
1030
|
+
sandboxId: data.sandbox_id ?? "",
|
|
1031
|
+
workspacePath: data.workspace_path ?? null
|
|
1032
|
+
};
|
|
1033
|
+
case "asset_created":
|
|
1034
|
+
return {
|
|
1035
|
+
type: "asset_created",
|
|
1036
|
+
assetId: data.asset_id ?? "",
|
|
1037
|
+
filename: data.filename ?? "",
|
|
1038
|
+
url: data.url ?? null,
|
|
1039
|
+
contentType: data.content_type ?? null
|
|
1040
|
+
};
|
|
1041
|
+
case "tool_approval_requested":
|
|
1042
|
+
return {
|
|
1043
|
+
type: "tool_approval_requested",
|
|
1044
|
+
toolName: data.tool_name ?? "",
|
|
1045
|
+
callId: data.call_id ?? "",
|
|
1046
|
+
arguments: data.arguments ?? {},
|
|
1047
|
+
riskLevel: data.risk_level ?? null,
|
|
1048
|
+
reason: data.reason ?? null
|
|
1049
|
+
};
|
|
1050
|
+
case "tool_approval_granted":
|
|
1051
|
+
return {
|
|
1052
|
+
type: "tool_approval_granted",
|
|
1053
|
+
toolName: data.tool_name ?? "",
|
|
1054
|
+
callId: data.call_id ?? ""
|
|
1055
|
+
};
|
|
1056
|
+
case "tool_permission_denied":
|
|
1057
|
+
return {
|
|
1058
|
+
type: "tool_permission_denied",
|
|
1059
|
+
toolName: data.tool_name ?? "",
|
|
1060
|
+
callId: data.call_id ?? "",
|
|
1061
|
+
reason: data.reason ?? null,
|
|
1062
|
+
deniedBy: data.denied_by ?? null
|
|
1063
|
+
};
|
|
1064
|
+
case "tool_harness_warning":
|
|
1065
|
+
return {
|
|
1066
|
+
type: "tool_harness_warning",
|
|
1067
|
+
toolName: data.tool_name ?? "",
|
|
1068
|
+
callId: data.call_id ?? "",
|
|
1069
|
+
message: data.message ?? null,
|
|
1070
|
+
details: data.details ?? null
|
|
1071
|
+
};
|
|
1072
|
+
case "user_unavailable":
|
|
1073
|
+
return {
|
|
1074
|
+
type: "user_unavailable",
|
|
1075
|
+
consecutiveTimeouts: data.consecutive_timeouts ?? 0,
|
|
1076
|
+
toolName: data.tool_name ?? null
|
|
1077
|
+
};
|
|
1078
|
+
case "prompt_suggestion":
|
|
1079
|
+
return {
|
|
1080
|
+
type: "prompt_suggestion",
|
|
1081
|
+
suggestions: data.suggestions ?? []
|
|
1082
|
+
};
|
|
1083
|
+
case "state_changed":
|
|
1084
|
+
return {
|
|
1085
|
+
type: "state_changed",
|
|
1086
|
+
state: data.state ?? ""
|
|
1087
|
+
};
|
|
1088
|
+
default:
|
|
1089
|
+
return { type: "custom", name, data };
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
function legacyCustomEventData(wire) {
|
|
1093
|
+
return wire;
|
|
1094
|
+
}
|
|
1095
|
+
function translateWireEvent(wire) {
|
|
1096
|
+
if (wire.type === "prompt_suggestion") {
|
|
1097
|
+
return translateCustomEvent(
|
|
1098
|
+
"prompt_suggestion",
|
|
1099
|
+
legacyCustomEventData(wire)
|
|
1100
|
+
);
|
|
1101
|
+
}
|
|
1102
|
+
switch (wire.type) {
|
|
1103
|
+
case "message_start":
|
|
1104
|
+
return {
|
|
1105
|
+
type: "message_start",
|
|
1106
|
+
turnId: wire.turn_id,
|
|
1107
|
+
model: wire.model,
|
|
1108
|
+
agentName: wire.agent_name,
|
|
1109
|
+
agentDisplayName: wire.agent_display_name,
|
|
1110
|
+
agentAvatarUrl: wire.agent_avatar_url
|
|
1111
|
+
};
|
|
1112
|
+
case "block_start":
|
|
1113
|
+
return {
|
|
1114
|
+
type: "block_start",
|
|
1115
|
+
turnId: wire.turn_id,
|
|
1116
|
+
path: wire.path,
|
|
1117
|
+
parentPath: wire.parent_path ?? null,
|
|
1118
|
+
kind: wire.kind,
|
|
1119
|
+
metadata: wire.metadata
|
|
1120
|
+
};
|
|
1121
|
+
case "block_delta": {
|
|
1122
|
+
const delta = translateDelta(wire.delta);
|
|
1123
|
+
if (!delta) return null;
|
|
1124
|
+
return {
|
|
1125
|
+
type: "block_delta",
|
|
1126
|
+
turnId: wire.turn_id,
|
|
1127
|
+
path: wire.path,
|
|
1128
|
+
delta
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
case "block_stop":
|
|
1132
|
+
return {
|
|
1133
|
+
type: "block_stop",
|
|
1134
|
+
turnId: wire.turn_id,
|
|
1135
|
+
path: wire.path,
|
|
1136
|
+
status: wire.status,
|
|
1137
|
+
final: wire.final
|
|
1138
|
+
};
|
|
1139
|
+
case "message_stop":
|
|
1140
|
+
return {
|
|
1141
|
+
type: "message_stop",
|
|
1142
|
+
turnId: wire.turn_id,
|
|
1143
|
+
jobId: wire.job_id,
|
|
1144
|
+
stopReason: wire.stop_reason,
|
|
1145
|
+
usage: {
|
|
1146
|
+
inputTokens: wire.usage.input_tokens ?? 0,
|
|
1147
|
+
outputTokens: wire.usage.output_tokens ?? 0,
|
|
1148
|
+
cachedTokens: wire.usage.cached_tokens ?? 0
|
|
1149
|
+
},
|
|
1150
|
+
ttfbMs: wire.ttfb_ms,
|
|
1151
|
+
totalMs: wire.total_ms,
|
|
1152
|
+
stallCount: wire.stall_count
|
|
1153
|
+
};
|
|
1154
|
+
case "stall":
|
|
1155
|
+
return {
|
|
1156
|
+
type: "stall",
|
|
1157
|
+
sinceLastEventMs: wire.since_last_event_ms,
|
|
1158
|
+
stallCount: wire.stall_count
|
|
1159
|
+
};
|
|
1160
|
+
case "retry":
|
|
1161
|
+
return {
|
|
1162
|
+
type: "retry",
|
|
1163
|
+
attempt: wire.attempt,
|
|
1164
|
+
reason: wire.reason,
|
|
1165
|
+
backoffMs: wire.backoff_ms,
|
|
1166
|
+
strategy: wire.strategy ?? null,
|
|
1167
|
+
maxAttempts: wire.max_attempts ?? null,
|
|
1168
|
+
contextRecovery: wire.context_recovery ?? null
|
|
1169
|
+
};
|
|
1170
|
+
case "error":
|
|
1171
|
+
return {
|
|
1172
|
+
type: "error",
|
|
1173
|
+
code: wire.code,
|
|
1174
|
+
message: wire.message,
|
|
1175
|
+
blockPath: wire.block_path ?? null
|
|
1176
|
+
};
|
|
1177
|
+
case "keepalive":
|
|
1178
|
+
return {
|
|
1179
|
+
type: "keepalive",
|
|
1180
|
+
sinceLastEventMs: wire.since_last_event_ms
|
|
1181
|
+
};
|
|
1182
|
+
case "custom":
|
|
1183
|
+
return translateCustomEvent(wire.name, wire.data);
|
|
1184
|
+
default: {
|
|
1185
|
+
const _exhaustive = wire;
|
|
1186
|
+
void _exhaustive;
|
|
1187
|
+
return null;
|
|
1188
|
+
}
|
|
605
1189
|
}
|
|
606
|
-
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
607
|
-
const r = Math.random() * 16 | 0;
|
|
608
|
-
const v = c === "x" ? r : r & 3 | 8;
|
|
609
|
-
return v.toString(16);
|
|
610
|
-
});
|
|
611
1190
|
}
|
|
612
1191
|
|
|
613
1192
|
// src/session.ts
|
|
1193
|
+
function pathEquals(a, b) {
|
|
1194
|
+
if (a.length !== b.length) return false;
|
|
1195
|
+
for (let i = 0; i < a.length; i++) {
|
|
1196
|
+
if (a[i] !== b[i]) return false;
|
|
1197
|
+
}
|
|
1198
|
+
return true;
|
|
1199
|
+
}
|
|
614
1200
|
var ChatSession = class {
|
|
615
|
-
constructor(config, storage
|
|
1201
|
+
constructor(config, storage) {
|
|
1202
|
+
/**
|
|
1203
|
+
* Pluggable UI protocol adapters. Consumers register a framework-
|
|
1204
|
+
* specific adapter (e.g. React) for each MIME type they can render,
|
|
1205
|
+
* typically gated on ``session.projectStatus.uiComponents.protocol``.
|
|
1206
|
+
* ``ToolBlock``-style consumers look up the adapter for an incoming
|
|
1207
|
+
* embedded resource and hand off rendering.
|
|
1208
|
+
*/
|
|
1209
|
+
this.protocols = new ProtocolRegistry();
|
|
616
1210
|
// State
|
|
617
1211
|
this.conversationId = null;
|
|
618
1212
|
this.conversations = [];
|
|
619
1213
|
this.messages = [];
|
|
620
|
-
this.streamingContent = "";
|
|
621
1214
|
this.isStreaming = false;
|
|
622
|
-
this.executingTool = null;
|
|
623
1215
|
this.projectStatus = null;
|
|
624
1216
|
this.agents = [];
|
|
625
1217
|
this.skills = [];
|
|
626
1218
|
this.enabledClientTools = /* @__PURE__ */ new Set();
|
|
627
1219
|
this.modelDisplayName = null;
|
|
628
|
-
//
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
this.
|
|
632
|
-
this.
|
|
633
|
-
this.todos = [];
|
|
634
|
-
this.activeTools = /* @__PURE__ */ new Map();
|
|
1220
|
+
// Minimal in-session accumulation for the assistant message record.
|
|
1221
|
+
// Only top-level ``text`` blocks contribute; subagent / tool output
|
|
1222
|
+
// is tracked by the consumer's own block store.
|
|
1223
|
+
this.accumulatedText = "";
|
|
1224
|
+
this.currentTextPath = null;
|
|
635
1225
|
this.handlers = /* @__PURE__ */ new Set();
|
|
636
1226
|
this.abortController = null;
|
|
637
1227
|
/** Last received sequence number for resumable reconnection */
|
|
@@ -641,13 +1231,6 @@ var ChatSession = class {
|
|
|
641
1231
|
this.client = new AstralformClient(config);
|
|
642
1232
|
this.toolRegistry = new ToolRegistry();
|
|
643
1233
|
this.storage = storage ?? new InMemoryStorage();
|
|
644
|
-
this.blockBuilder = blockBuilder ?? new BlockBuilder();
|
|
645
|
-
this.blockBuilder.setOnChange(() => {
|
|
646
|
-
this.emit({
|
|
647
|
-
type: "blocks_changed",
|
|
648
|
-
blocks: this.blockBuilder.getBlocks()
|
|
649
|
-
});
|
|
650
|
-
});
|
|
651
1234
|
}
|
|
652
1235
|
on(handler) {
|
|
653
1236
|
this.handlers.add(handler);
|
|
@@ -656,9 +1239,6 @@ var ChatSession = class {
|
|
|
656
1239
|
};
|
|
657
1240
|
}
|
|
658
1241
|
emit(event) {
|
|
659
|
-
if (event.type !== "blocks_changed" && event.type !== "connected") {
|
|
660
|
-
this.blockBuilder.processEvent(event);
|
|
661
|
-
}
|
|
662
1242
|
for (const handler of this.handlers) {
|
|
663
1243
|
try {
|
|
664
1244
|
handler(event);
|
|
@@ -711,7 +1291,8 @@ var ChatSession = class {
|
|
|
711
1291
|
),
|
|
712
1292
|
upload_ids: options?.uploadIds,
|
|
713
1293
|
agent_name: options?.agentName,
|
|
714
|
-
enable_search: options?.enableSearch
|
|
1294
|
+
enable_search: options?.enableSearch,
|
|
1295
|
+
plan_mode: options?.planMode
|
|
715
1296
|
};
|
|
716
1297
|
await this.processStream(request);
|
|
717
1298
|
}
|
|
@@ -728,13 +1309,8 @@ var ChatSession = class {
|
|
|
728
1309
|
await this.processStream(request);
|
|
729
1310
|
}
|
|
730
1311
|
resetStreamingState() {
|
|
731
|
-
this.
|
|
732
|
-
this.
|
|
733
|
-
this.isThinking = false;
|
|
734
|
-
this.activeSubagents.clear();
|
|
735
|
-
this.capsuleOutputs = [];
|
|
736
|
-
this.todos = [];
|
|
737
|
-
this.activeTools.clear();
|
|
1312
|
+
this.accumulatedText = "";
|
|
1313
|
+
this.currentTextPath = null;
|
|
738
1314
|
}
|
|
739
1315
|
async processStream(request) {
|
|
740
1316
|
this.isStreaming = true;
|
|
@@ -746,22 +1322,42 @@ var ChatSession = class {
|
|
|
746
1322
|
if (!(err instanceof DOMException && err.name === "AbortError")) {
|
|
747
1323
|
this.emit({
|
|
748
1324
|
type: "error",
|
|
749
|
-
|
|
1325
|
+
code: "connection_error",
|
|
1326
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1327
|
+
blockPath: null
|
|
750
1328
|
});
|
|
751
1329
|
}
|
|
752
1330
|
} finally {
|
|
753
1331
|
this.isStreaming = false;
|
|
754
|
-
this.executingTool = null;
|
|
755
1332
|
this.abortController = null;
|
|
756
1333
|
}
|
|
757
1334
|
}
|
|
758
1335
|
async consumeJobStream(request) {
|
|
759
1336
|
const job = await this.client.createJob(request);
|
|
760
1337
|
this.currentJobId = job.job_id;
|
|
761
|
-
|
|
1338
|
+
const conversationId = job.conversation_id;
|
|
762
1339
|
if (!this.conversationId) {
|
|
763
1340
|
this.conversationId = conversationId;
|
|
764
1341
|
}
|
|
1342
|
+
if (!this.conversations.some((c) => c.id === conversationId)) {
|
|
1343
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1344
|
+
const conv = {
|
|
1345
|
+
id: conversationId,
|
|
1346
|
+
title: "",
|
|
1347
|
+
messageCount: 0,
|
|
1348
|
+
createdAt: now,
|
|
1349
|
+
updatedAt: now
|
|
1350
|
+
};
|
|
1351
|
+
this.conversations.unshift(conv);
|
|
1352
|
+
await this.storage.createConversation(conversationId, "").catch(() => {
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
const lastMsg = this.messages[this.messages.length - 1];
|
|
1356
|
+
if (lastMsg?.role === "user" && !lastMsg.conversationId) {
|
|
1357
|
+
lastMsg.conversationId = conversationId;
|
|
1358
|
+
await this.storage.addMessage(lastMsg, conversationId).catch(() => {
|
|
1359
|
+
});
|
|
1360
|
+
}
|
|
765
1361
|
const messageId = job.message_id;
|
|
766
1362
|
this.lastSeq = -1;
|
|
767
1363
|
const stream = this.client.streamJobEvents(
|
|
@@ -778,7 +1374,8 @@ var ChatSession = class {
|
|
|
778
1374
|
);
|
|
779
1375
|
}
|
|
780
1376
|
/**
|
|
781
|
-
* Shared event consumption loop
|
|
1377
|
+
* Shared event consumption loop. Parses each wire event, updates
|
|
1378
|
+
* minimal session state, and emits typed ChatEvents to consumers.
|
|
782
1379
|
*/
|
|
783
1380
|
async consumeEventStream(stream, conversationId, messageId, executeClientTools) {
|
|
784
1381
|
for await (const raw of stream) {
|
|
@@ -798,419 +1395,112 @@ var ChatSession = class {
|
|
|
798
1395
|
} catch {
|
|
799
1396
|
continue;
|
|
800
1397
|
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
displayName: parsed.display_name,
|
|
830
|
-
description: parsed.description,
|
|
831
|
-
arguments: parsed.arguments,
|
|
832
|
-
isClientTool: parsed.is_client_tool,
|
|
833
|
-
toolCategory: parsed.tool_category,
|
|
834
|
-
iconUrl: parsed.icon_url
|
|
835
|
-
}
|
|
836
|
-
]);
|
|
837
|
-
await this.client.submitToolResult({
|
|
838
|
-
conversation_id: conversationId,
|
|
839
|
-
message_id: messageId,
|
|
840
|
-
tool_results: results
|
|
841
|
-
});
|
|
842
|
-
}
|
|
843
|
-
break;
|
|
844
|
-
}
|
|
845
|
-
case "subagent_content_delta": {
|
|
846
|
-
const subagent = this.activeSubagents.get(parsed.tool_call_id);
|
|
847
|
-
if (subagent) {
|
|
848
|
-
subagent.content += parsed.delta.text;
|
|
849
|
-
}
|
|
850
|
-
this.emit({
|
|
851
|
-
type: "subagent_chunk",
|
|
852
|
-
agentName: parsed.agent_name,
|
|
853
|
-
toolCallId: parsed.tool_call_id,
|
|
854
|
-
text: parsed.delta.text
|
|
855
|
-
});
|
|
856
|
-
break;
|
|
857
|
-
}
|
|
858
|
-
case "thinking_delta":
|
|
859
|
-
this.thinkingContent += parsed.delta.text;
|
|
860
|
-
this.isThinking = true;
|
|
861
|
-
this.emit({ type: "thinking_delta", text: parsed.delta.text });
|
|
862
|
-
break;
|
|
863
|
-
case "thinking_complete":
|
|
864
|
-
this.isThinking = false;
|
|
865
|
-
this.emit({ type: "thinking_complete" });
|
|
866
|
-
break;
|
|
867
|
-
case "retry":
|
|
868
|
-
this.emit({
|
|
869
|
-
type: "retry",
|
|
870
|
-
attempt: parsed.attempt,
|
|
871
|
-
maxAttempts: parsed.max_attempts,
|
|
872
|
-
delaySeconds: parsed.delay_seconds
|
|
873
|
-
});
|
|
874
|
-
break;
|
|
875
|
-
case "message_stop":
|
|
876
|
-
await this.completeStream(
|
|
877
|
-
conversationId,
|
|
878
|
-
messageId,
|
|
879
|
-
parsed.title,
|
|
880
|
-
parsed.metrics,
|
|
881
|
-
parsed.job_id
|
|
882
|
-
);
|
|
883
|
-
this.isStreaming = false;
|
|
884
|
-
this.currentJobId = null;
|
|
885
|
-
break;
|
|
886
|
-
case "error":
|
|
887
|
-
this.emit({
|
|
888
|
-
type: "error",
|
|
889
|
-
error: new AstralformError(parsed.message, parsed.code)
|
|
890
|
-
});
|
|
891
|
-
break;
|
|
892
|
-
default:
|
|
893
|
-
this.applyEvent(parsed);
|
|
894
|
-
}
|
|
1398
|
+
await this.dispatchWireEvent(
|
|
1399
|
+
parsed,
|
|
1400
|
+
conversationId,
|
|
1401
|
+
messageId,
|
|
1402
|
+
executeClientTools
|
|
1403
|
+
);
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
async dispatchWireEvent(wire, conversationId, messageId, executeClientTools) {
|
|
1407
|
+
this.applyWireSideEffects(wire, conversationId, messageId);
|
|
1408
|
+
const event = translateWireEvent(wire);
|
|
1409
|
+
if (event) {
|
|
1410
|
+
this.emit(event);
|
|
1411
|
+
}
|
|
1412
|
+
if (executeClientTools && wire.type === "block_stop" && wire.status === "awaiting_client_result" && wire.final?.call_id) {
|
|
1413
|
+
const f = wire.final;
|
|
1414
|
+
const request = {
|
|
1415
|
+
callId: f.call_id ?? "",
|
|
1416
|
+
toolName: f.tool_name ?? "",
|
|
1417
|
+
arguments: f.input ?? {},
|
|
1418
|
+
isClientTool: true
|
|
1419
|
+
};
|
|
1420
|
+
const results = await this.executeClientTools([request]);
|
|
1421
|
+
await this.client.submitToolResult({
|
|
1422
|
+
conversation_id: conversationId,
|
|
1423
|
+
message_id: messageId,
|
|
1424
|
+
tool_results: results
|
|
1425
|
+
});
|
|
895
1426
|
}
|
|
896
1427
|
}
|
|
897
1428
|
/**
|
|
898
|
-
*
|
|
899
|
-
*
|
|
1429
|
+
* State mutations driven by wire events. Kept separate from translation so
|
|
1430
|
+
* the pure wire → ChatEvent mapping can live in translate.ts and be reused
|
|
1431
|
+
* by the replay path.
|
|
1432
|
+
*
|
|
1433
|
+
* ``messageId`` is the server-assigned assistant message id for the current
|
|
1434
|
+
* turn; empty in the reconnect and conversation-switch replay paths where
|
|
1435
|
+
* messages have already been loaded from REST and shouldn't be re-pushed.
|
|
900
1436
|
*/
|
|
901
|
-
|
|
902
|
-
switch (
|
|
903
|
-
case "user_message":
|
|
904
|
-
this.emit({
|
|
905
|
-
type: "user_message",
|
|
906
|
-
content: event.content,
|
|
907
|
-
createdAt: event.created_at
|
|
908
|
-
});
|
|
909
|
-
break;
|
|
910
|
-
case "title_generated": {
|
|
911
|
-
if (this.conversationId && event.title) {
|
|
912
|
-
const conv = this.conversations.find(
|
|
913
|
-
(c) => c.id === this.conversationId
|
|
914
|
-
);
|
|
915
|
-
if (conv) {
|
|
916
|
-
conv.title = event.title;
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
this.emit({ type: "title_generated", title: event.title });
|
|
920
|
-
break;
|
|
921
|
-
}
|
|
1437
|
+
applyWireSideEffects(wire, conversationId, messageId) {
|
|
1438
|
+
switch (wire.type) {
|
|
922
1439
|
case "message_start":
|
|
923
|
-
|
|
924
|
-
|
|
1440
|
+
this.resetStreamingState();
|
|
1441
|
+
if (wire.model) {
|
|
1442
|
+
this.modelDisplayName = wire.model;
|
|
925
1443
|
}
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1444
|
+
return;
|
|
1445
|
+
case "block_start":
|
|
1446
|
+
if (wire.kind === "text" && (!wire.parent_path || wire.parent_path.length === 0)) {
|
|
1447
|
+
this.currentTextPath = wire.path;
|
|
929
1448
|
}
|
|
930
|
-
|
|
931
|
-
case "
|
|
932
|
-
this.
|
|
933
|
-
|
|
934
|
-
break;
|
|
935
|
-
case "thinking_delta":
|
|
936
|
-
this.thinkingContent += event.delta.text;
|
|
937
|
-
this.isThinking = true;
|
|
938
|
-
this.emit({ type: "thinking_delta", text: event.delta.text });
|
|
939
|
-
break;
|
|
940
|
-
case "thinking_complete":
|
|
941
|
-
this.isThinking = false;
|
|
942
|
-
this.emit({ type: "thinking_complete" });
|
|
943
|
-
break;
|
|
944
|
-
case "tool_executing":
|
|
945
|
-
this.emit({
|
|
946
|
-
type: "tool_executing",
|
|
947
|
-
name: event.tool,
|
|
948
|
-
call_id: event.call_id
|
|
949
|
-
});
|
|
950
|
-
break;
|
|
951
|
-
case "tool_progress":
|
|
952
|
-
this.emit({
|
|
953
|
-
type: "tool_progress",
|
|
954
|
-
callId: event.call_id,
|
|
955
|
-
tool: event.tool,
|
|
956
|
-
index: event.index,
|
|
957
|
-
total: event.total,
|
|
958
|
-
item: event.item
|
|
959
|
-
});
|
|
960
|
-
break;
|
|
961
|
-
case "message_stop":
|
|
962
|
-
this.emit({
|
|
963
|
-
type: "complete",
|
|
964
|
-
content: this.streamingContent,
|
|
965
|
-
conversationId: this.conversationId ?? "",
|
|
966
|
-
messageId: event.job_id ?? "",
|
|
967
|
-
title: event.title,
|
|
968
|
-
metrics: event.metrics,
|
|
969
|
-
job_id: event.job_id
|
|
970
|
-
});
|
|
971
|
-
break;
|
|
972
|
-
case "tool_use_start": {
|
|
973
|
-
const request = {
|
|
974
|
-
callId: event.call_id,
|
|
975
|
-
toolName: event.tool,
|
|
976
|
-
displayName: event.display_name,
|
|
977
|
-
description: event.description,
|
|
978
|
-
arguments: event.arguments,
|
|
979
|
-
isClientTool: event.is_client_tool,
|
|
980
|
-
toolCategory: event.tool_category,
|
|
981
|
-
iconUrl: event.icon_url
|
|
982
|
-
};
|
|
983
|
-
this.activeTools.set(event.call_id, {
|
|
984
|
-
...request,
|
|
985
|
-
status: event.is_client_tool ? "calling" : "executing"
|
|
986
|
-
});
|
|
987
|
-
this.emit({ type: "tool_call", request });
|
|
988
|
-
break;
|
|
989
|
-
}
|
|
990
|
-
case "tool_use_end": {
|
|
991
|
-
const toolState = this.activeTools.get(event.call_id);
|
|
992
|
-
if (toolState) {
|
|
993
|
-
toolState.status = "completed";
|
|
1449
|
+
return;
|
|
1450
|
+
case "block_delta":
|
|
1451
|
+
if (wire.delta.channel === "text" && this.currentTextPath !== null && pathEquals(this.currentTextPath, wire.path)) {
|
|
1452
|
+
this.accumulatedText += wire.delta.text;
|
|
994
1453
|
}
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
result: event.result,
|
|
1000
|
-
sources: event.sources,
|
|
1001
|
-
durationMs: event.duration_ms
|
|
1002
|
-
});
|
|
1003
|
-
break;
|
|
1004
|
-
}
|
|
1005
|
-
case "agent_start":
|
|
1006
|
-
this.emit({
|
|
1007
|
-
type: "agent_start",
|
|
1008
|
-
agentName: event.agent_name,
|
|
1009
|
-
agentDisplayName: event.agent_display_name,
|
|
1010
|
-
avatarUrl: event.avatar_url
|
|
1011
|
-
});
|
|
1012
|
-
break;
|
|
1013
|
-
case "agent_end":
|
|
1014
|
-
this.emit({ type: "agent_end", agentName: event.agent_name });
|
|
1015
|
-
break;
|
|
1016
|
-
case "subagent_start":
|
|
1017
|
-
this.activeSubagents.set(event.tool_call_id, {
|
|
1018
|
-
agentName: event.agent_name,
|
|
1019
|
-
displayName: event.display_name,
|
|
1020
|
-
avatarUrl: event.avatar_url,
|
|
1021
|
-
description: event.description,
|
|
1022
|
-
content: "",
|
|
1023
|
-
isActive: true
|
|
1024
|
-
});
|
|
1025
|
-
this.emit({
|
|
1026
|
-
type: "subagent_start",
|
|
1027
|
-
agentName: event.agent_name,
|
|
1028
|
-
displayName: event.display_name,
|
|
1029
|
-
toolCallId: event.tool_call_id,
|
|
1030
|
-
avatarUrl: event.avatar_url,
|
|
1031
|
-
description: event.description
|
|
1032
|
-
});
|
|
1033
|
-
break;
|
|
1034
|
-
case "subagent_update": {
|
|
1035
|
-
const sub = this.activeSubagents.get(event.tool_call_id);
|
|
1036
|
-
if (sub) {
|
|
1037
|
-
sub.agentName = event.agent_name;
|
|
1038
|
-
sub.displayName = event.display_name;
|
|
1454
|
+
return;
|
|
1455
|
+
case "block_stop":
|
|
1456
|
+
if (this.currentTextPath !== null && pathEquals(this.currentTextPath, wire.path)) {
|
|
1457
|
+
this.currentTextPath = null;
|
|
1039
1458
|
}
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1459
|
+
return;
|
|
1460
|
+
case "message_stop":
|
|
1461
|
+
if (messageId) {
|
|
1462
|
+
const assistantMessage = {
|
|
1463
|
+
id: messageId,
|
|
1464
|
+
conversationId,
|
|
1465
|
+
role: "assistant",
|
|
1466
|
+
content: this.accumulatedText,
|
|
1467
|
+
status: "complete",
|
|
1468
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1469
|
+
};
|
|
1470
|
+
this.messages.push(assistantMessage);
|
|
1471
|
+
this.storage.addMessage(assistantMessage, conversationId).catch(() => {
|
|
1472
|
+
});
|
|
1052
1473
|
}
|
|
1053
|
-
this.
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
agentName: event.agent_name,
|
|
1074
|
-
command: event.command,
|
|
1075
|
-
output: event.output,
|
|
1076
|
-
durationMs: event.duration_ms,
|
|
1077
|
-
callId: event.call_id
|
|
1078
|
-
};
|
|
1079
|
-
this.capsuleOutputs.push(capsule);
|
|
1080
|
-
this.emit({ type: "capsule_output", ...capsule });
|
|
1081
|
-
break;
|
|
1082
|
-
}
|
|
1083
|
-
case "capsule_output_chunk":
|
|
1084
|
-
this.emit({
|
|
1085
|
-
type: "capsule_output_chunk",
|
|
1086
|
-
callId: event.call_id,
|
|
1087
|
-
stream: event.stream,
|
|
1088
|
-
chunk: event.chunk
|
|
1089
|
-
});
|
|
1090
|
-
break;
|
|
1091
|
-
case "todo_update":
|
|
1092
|
-
this.todos = event.todos;
|
|
1093
|
-
this.emit({ type: "todo_update", todos: event.todos });
|
|
1094
|
-
break;
|
|
1095
|
-
case "context_update":
|
|
1096
|
-
this.emit({
|
|
1097
|
-
type: "context_update",
|
|
1098
|
-
context: event.context,
|
|
1099
|
-
phase: event.phase,
|
|
1100
|
-
updatedAt: event.updated_at
|
|
1101
|
-
});
|
|
1102
|
-
break;
|
|
1103
|
-
case "desktop_stream":
|
|
1104
|
-
this.emit({
|
|
1105
|
-
type: "desktop_stream",
|
|
1106
|
-
url: event.url,
|
|
1107
|
-
authKey: event.auth_key,
|
|
1108
|
-
sandboxId: event.sandbox_id
|
|
1109
|
-
});
|
|
1110
|
-
break;
|
|
1111
|
-
case "attachment_staged":
|
|
1112
|
-
this.emit({
|
|
1113
|
-
type: "attachment_staged",
|
|
1114
|
-
files: (event.files || []).map((f) => ({
|
|
1115
|
-
name: f.name,
|
|
1116
|
-
path: f.path,
|
|
1117
|
-
mediaType: f.media_type,
|
|
1118
|
-
sizeBytes: f.size_bytes
|
|
1119
|
-
}))
|
|
1120
|
-
});
|
|
1121
|
-
break;
|
|
1122
|
-
case "workspace_ready":
|
|
1123
|
-
this.emit({
|
|
1124
|
-
type: "workspace_ready",
|
|
1125
|
-
conversationId: event.conversation_id,
|
|
1126
|
-
sandboxId: event.sandbox_id
|
|
1127
|
-
});
|
|
1128
|
-
break;
|
|
1129
|
-
case "asset_created":
|
|
1130
|
-
this.emit({
|
|
1131
|
-
type: "asset_created",
|
|
1132
|
-
assetId: event.asset_id,
|
|
1133
|
-
name: event.name,
|
|
1134
|
-
url: event.url,
|
|
1135
|
-
mediaType: event.media_type,
|
|
1136
|
-
sizeBytes: event.size_bytes
|
|
1137
|
-
});
|
|
1138
|
-
break;
|
|
1139
|
-
case "editor_content_start":
|
|
1140
|
-
this.emit({
|
|
1141
|
-
type: "editor_content_start",
|
|
1142
|
-
callId: event.call_id,
|
|
1143
|
-
path: event.path,
|
|
1144
|
-
language: event.language
|
|
1145
|
-
});
|
|
1146
|
-
break;
|
|
1147
|
-
case "editor_content_delta":
|
|
1148
|
-
this.emit({
|
|
1149
|
-
type: "editor_content_delta",
|
|
1150
|
-
callId: event.call_id,
|
|
1151
|
-
path: event.path,
|
|
1152
|
-
delta: event.delta
|
|
1153
|
-
});
|
|
1154
|
-
break;
|
|
1155
|
-
case "editor_content_end":
|
|
1156
|
-
this.emit({
|
|
1157
|
-
type: "editor_content_end",
|
|
1158
|
-
callId: event.call_id
|
|
1159
|
-
});
|
|
1160
|
-
break;
|
|
1474
|
+
this.isStreaming = false;
|
|
1475
|
+
this.currentJobId = null;
|
|
1476
|
+
return;
|
|
1477
|
+
case "custom":
|
|
1478
|
+
if (wire.name === "title_generated") {
|
|
1479
|
+
const title = wire.data.title ?? "";
|
|
1480
|
+
if (this.conversationId && title) {
|
|
1481
|
+
const conv = this.conversations.find(
|
|
1482
|
+
(c) => c.id === this.conversationId
|
|
1483
|
+
);
|
|
1484
|
+
if (conv) {
|
|
1485
|
+
conv.title = title;
|
|
1486
|
+
}
|
|
1487
|
+
this.storage.updateConversationTitle(this.conversationId, title).catch(() => {
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
return;
|
|
1492
|
+
default:
|
|
1493
|
+
return;
|
|
1161
1494
|
}
|
|
1162
1495
|
}
|
|
1163
1496
|
async executeClientTools(toolCalls) {
|
|
1164
1497
|
const results = [];
|
|
1165
1498
|
for (const call of toolCalls) {
|
|
1166
|
-
this.executingTool = call.toolName;
|
|
1167
|
-
const toolState = this.activeTools.get(call.callId);
|
|
1168
|
-
if (toolState) {
|
|
1169
|
-
toolState.status = "executing";
|
|
1170
|
-
}
|
|
1171
|
-
this.emit({ type: "tool_executing", name: call.toolName });
|
|
1172
1499
|
const result = await this.toolRegistry.executeTool(call);
|
|
1173
1500
|
results.push(result);
|
|
1174
|
-
if (toolState) {
|
|
1175
|
-
toolState.status = "completed";
|
|
1176
|
-
}
|
|
1177
|
-
this.emit({
|
|
1178
|
-
type: "tool_completed",
|
|
1179
|
-
name: call.toolName,
|
|
1180
|
-
result: result.result
|
|
1181
|
-
});
|
|
1182
1501
|
}
|
|
1183
|
-
this.executingTool = null;
|
|
1184
1502
|
return results;
|
|
1185
1503
|
}
|
|
1186
|
-
async completeStream(conversationId, messageId, title, metrics, jobId) {
|
|
1187
|
-
const assistantMessage = {
|
|
1188
|
-
id: messageId || generateId(),
|
|
1189
|
-
conversationId,
|
|
1190
|
-
role: "assistant",
|
|
1191
|
-
content: this.streamingContent,
|
|
1192
|
-
status: "complete",
|
|
1193
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1194
|
-
};
|
|
1195
|
-
this.messages.push(assistantMessage);
|
|
1196
|
-
await this.storage.addMessage(assistantMessage, conversationId);
|
|
1197
|
-
if (title && conversationId) {
|
|
1198
|
-
await this.storage.updateConversationTitle(conversationId, title);
|
|
1199
|
-
const conv = this.conversations.find((c) => c.id === conversationId);
|
|
1200
|
-
if (conv) {
|
|
1201
|
-
conv.title = title;
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
this.emit({
|
|
1205
|
-
type: "complete",
|
|
1206
|
-
content: this.streamingContent,
|
|
1207
|
-
conversationId,
|
|
1208
|
-
messageId: assistantMessage.id,
|
|
1209
|
-
title,
|
|
1210
|
-
metrics,
|
|
1211
|
-
job_id: jobId
|
|
1212
|
-
});
|
|
1213
|
-
}
|
|
1214
1504
|
/**
|
|
1215
1505
|
* Load conversation context (messages) without replaying events.
|
|
1216
1506
|
* Used before reconnectToJob — SSE replay handles event replay.
|
|
@@ -1218,13 +1508,11 @@ var ChatSession = class {
|
|
|
1218
1508
|
async loadConversation(id) {
|
|
1219
1509
|
this.conversationId = id;
|
|
1220
1510
|
this.resetStreamingState();
|
|
1221
|
-
this.blockBuilder.reset();
|
|
1222
1511
|
this.messages = await this.client.getMessages(id).catch(() => this.storage.fetchMessages(id));
|
|
1223
1512
|
}
|
|
1224
1513
|
/**
|
|
1225
1514
|
* Reconnect to a running job's SSE stream (e.g. after page reload).
|
|
1226
1515
|
* Replays all events from the beginning and continues live.
|
|
1227
|
-
* Does NOT reset BlockBuilder — caller controls reset.
|
|
1228
1516
|
*/
|
|
1229
1517
|
async reconnectToJob(jobId) {
|
|
1230
1518
|
if (this.isStreaming) return;
|
|
@@ -1249,23 +1537,21 @@ var ChatSession = class {
|
|
|
1249
1537
|
} catch (err) {
|
|
1250
1538
|
this.emit({
|
|
1251
1539
|
type: "error",
|
|
1252
|
-
|
|
1540
|
+
code: "connection_error",
|
|
1541
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1542
|
+
blockPath: null
|
|
1253
1543
|
});
|
|
1254
1544
|
} finally {
|
|
1255
1545
|
this.isStreaming = false;
|
|
1256
|
-
this.executingTool = null;
|
|
1257
1546
|
this.abortController = null;
|
|
1258
1547
|
}
|
|
1259
1548
|
}
|
|
1260
|
-
/** Detach from the SSE stream without cancelling the job.
|
|
1261
|
-
* The backend job keeps running — caller can reconnect later. */
|
|
1549
|
+
/** Detach from the SSE stream without cancelling the job. */
|
|
1262
1550
|
detach() {
|
|
1263
1551
|
this.abortController?.abort();
|
|
1264
1552
|
this.abortController = null;
|
|
1265
1553
|
this.isStreaming = false;
|
|
1266
|
-
this.
|
|
1267
|
-
this.executingTool = null;
|
|
1268
|
-
this.blockBuilder.reset();
|
|
1554
|
+
this.resetStreamingState();
|
|
1269
1555
|
this.emit({ type: "disconnected" });
|
|
1270
1556
|
}
|
|
1271
1557
|
/** Stop the job and disconnect (explicit user action). */
|
|
@@ -1276,6 +1562,7 @@ var ChatSession = class {
|
|
|
1276
1562
|
}
|
|
1277
1563
|
this.detach();
|
|
1278
1564
|
this.currentJobId = null;
|
|
1565
|
+
this.protocols.clear();
|
|
1279
1566
|
}
|
|
1280
1567
|
async createNewConversation() {
|
|
1281
1568
|
const id = generateId();
|
|
@@ -1286,21 +1573,39 @@ var ChatSession = class {
|
|
|
1286
1573
|
this.conversations.unshift(conversation);
|
|
1287
1574
|
this.conversationId = id;
|
|
1288
1575
|
this.messages = [];
|
|
1289
|
-
this.streamingContent = "";
|
|
1290
1576
|
return id;
|
|
1291
1577
|
}
|
|
1292
|
-
async switchConversation(id, jobId) {
|
|
1578
|
+
async switchConversation(id, jobId, userMessageContent) {
|
|
1293
1579
|
this.conversationId = id;
|
|
1294
1580
|
this.resetStreamingState();
|
|
1295
|
-
this.blockBuilder.reset();
|
|
1296
1581
|
const [messagesResult, eventsResult] = await Promise.allSettled([
|
|
1297
1582
|
this.client.getMessages(id).catch(() => this.storage.fetchMessages(id)),
|
|
1298
1583
|
this.client.getConversationEvents(id, jobId)
|
|
1299
1584
|
]);
|
|
1300
1585
|
this.messages = messagesResult.status === "fulfilled" ? messagesResult.value : [];
|
|
1301
1586
|
if (eventsResult.status === "fulfilled") {
|
|
1587
|
+
let userMessageEmitted = !userMessageContent;
|
|
1302
1588
|
for (const ev of eventsResult.value) {
|
|
1303
|
-
|
|
1589
|
+
const type = ev.data.type || ev.event;
|
|
1590
|
+
if (!type || type === "done") continue;
|
|
1591
|
+
if (!userMessageEmitted && type === "message_start") {
|
|
1592
|
+
this.emit({
|
|
1593
|
+
type: "user_message",
|
|
1594
|
+
content: userMessageContent
|
|
1595
|
+
});
|
|
1596
|
+
userMessageEmitted = true;
|
|
1597
|
+
}
|
|
1598
|
+
const wire = { ...ev.data, type };
|
|
1599
|
+
try {
|
|
1600
|
+
await this.dispatchWireEvent(
|
|
1601
|
+
wire,
|
|
1602
|
+
id,
|
|
1603
|
+
"",
|
|
1604
|
+
false
|
|
1605
|
+
// don't execute client tools on replay
|
|
1606
|
+
);
|
|
1607
|
+
} catch {
|
|
1608
|
+
}
|
|
1304
1609
|
}
|
|
1305
1610
|
}
|
|
1306
1611
|
}
|
|
@@ -1326,456 +1631,46 @@ var ChatSession = class {
|
|
|
1326
1631
|
}
|
|
1327
1632
|
};
|
|
1328
1633
|
|
|
1329
|
-
// src/standard-handlers.ts
|
|
1330
|
-
function finalizeText(builder) {
|
|
1331
|
-
if (builder.activeTextId) {
|
|
1332
|
-
builder.patchBlock(builder.activeTextId, {
|
|
1333
|
-
isStreaming: false
|
|
1334
|
-
});
|
|
1335
|
-
builder.activeTextId = null;
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
|
-
function finalizeThinking(builder) {
|
|
1339
|
-
if (builder.activeThinkingId) {
|
|
1340
|
-
builder.patchBlock(builder.activeThinkingId, {
|
|
1341
|
-
isActive: false
|
|
1342
|
-
});
|
|
1343
|
-
builder.activeThinkingId = null;
|
|
1344
|
-
builder.thinkingStartMs = null;
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
function finalizeEditor(builder) {
|
|
1348
|
-
if (builder.activeEditorId) {
|
|
1349
|
-
builder.patchBlock(builder.activeEditorId, {
|
|
1350
|
-
isStreaming: false
|
|
1351
|
-
});
|
|
1352
|
-
builder.activeEditorId = null;
|
|
1353
|
-
}
|
|
1354
|
-
}
|
|
1355
|
-
var handleUserMessage = (event, builder) => {
|
|
1356
|
-
const e = event;
|
|
1357
|
-
const existing = builder.findBlock((b) => b.type === "user");
|
|
1358
|
-
if (existing) {
|
|
1359
|
-
if (e.createdAt) {
|
|
1360
|
-
builder.patchBlock(existing.id, {
|
|
1361
|
-
createdAt: e.createdAt
|
|
1362
|
-
});
|
|
1363
|
-
}
|
|
1364
|
-
return;
|
|
1365
|
-
}
|
|
1366
|
-
builder.addBlock({
|
|
1367
|
-
type: "user",
|
|
1368
|
-
id: builder.nextId(),
|
|
1369
|
-
content: e.content,
|
|
1370
|
-
createdAt: e.createdAt
|
|
1371
|
-
});
|
|
1372
|
-
};
|
|
1373
|
-
var handleChunk = (event, builder) => {
|
|
1374
|
-
const e = event;
|
|
1375
|
-
if (!builder.activeTextId) {
|
|
1376
|
-
const id = builder.nextId();
|
|
1377
|
-
builder.activeTextId = id;
|
|
1378
|
-
builder.addBlock({
|
|
1379
|
-
type: "text",
|
|
1380
|
-
id,
|
|
1381
|
-
content: e.text,
|
|
1382
|
-
isStreaming: true
|
|
1383
|
-
});
|
|
1384
|
-
} else {
|
|
1385
|
-
const id = builder.activeTextId;
|
|
1386
|
-
const existing = builder.findBlock((b) => b.id === id);
|
|
1387
|
-
if (existing && existing.type === "text") {
|
|
1388
|
-
builder.patchBlock(id, {
|
|
1389
|
-
content: existing.content + e.text
|
|
1390
|
-
});
|
|
1391
|
-
}
|
|
1392
|
-
}
|
|
1393
|
-
};
|
|
1394
|
-
var handleToolCall = (event, builder) => {
|
|
1395
|
-
const e = event;
|
|
1396
|
-
finalizeText(builder);
|
|
1397
|
-
builder.addBlock({
|
|
1398
|
-
type: "tool",
|
|
1399
|
-
id: builder.nextId(),
|
|
1400
|
-
callId: e.request.callId,
|
|
1401
|
-
toolName: e.request.toolName,
|
|
1402
|
-
displayName: e.request.displayName,
|
|
1403
|
-
description: e.request.description,
|
|
1404
|
-
arguments: e.request.arguments,
|
|
1405
|
-
toolCategory: e.request.toolCategory,
|
|
1406
|
-
iconUrl: e.request.iconUrl,
|
|
1407
|
-
status: "calling"
|
|
1408
|
-
});
|
|
1409
|
-
};
|
|
1410
|
-
var handleToolExecuting = (event, builder) => {
|
|
1411
|
-
const e = event;
|
|
1412
|
-
const block = builder.findBlock(
|
|
1413
|
-
(b) => b.type === "tool" && (e.call_id ? b.callId === e.call_id : b.toolName === e.name) && b.status === "calling"
|
|
1414
|
-
);
|
|
1415
|
-
if (block) {
|
|
1416
|
-
builder.patchBlock(block.id, { status: "executing" });
|
|
1417
|
-
}
|
|
1418
|
-
};
|
|
1419
|
-
var handleToolProgress = (event, builder) => {
|
|
1420
|
-
const e = event;
|
|
1421
|
-
const block = builder.findBlock(
|
|
1422
|
-
(b) => b.type === "tool" && b.callId === e.callId
|
|
1423
|
-
);
|
|
1424
|
-
if (block && block.type === "tool") {
|
|
1425
|
-
const sources = block.sources ? [...block.sources] : [];
|
|
1426
|
-
sources.push(e.item);
|
|
1427
|
-
builder.patchBlock(block.id, {
|
|
1428
|
-
sources,
|
|
1429
|
-
status: "executing"
|
|
1430
|
-
});
|
|
1431
|
-
}
|
|
1432
|
-
};
|
|
1433
|
-
var handleToolEnd = (event, builder) => {
|
|
1434
|
-
const e = event;
|
|
1435
|
-
const callId = e.type === "tool_end" ? e.callId : void 0;
|
|
1436
|
-
const name = e.type === "tool_end" ? e.toolName : e.name;
|
|
1437
|
-
const block = builder.findBlock(
|
|
1438
|
-
(b) => b.type === "tool" && (callId ? b.callId === callId : b.toolName === name) && b.status !== "completed"
|
|
1439
|
-
);
|
|
1440
|
-
if (block) {
|
|
1441
|
-
const toolEnd = e.type === "tool_end" ? e : null;
|
|
1442
|
-
builder.patchBlock(block.id, {
|
|
1443
|
-
status: "completed",
|
|
1444
|
-
...toolEnd?.sources ? { sources: toolEnd.sources } : {},
|
|
1445
|
-
...toolEnd?.durationMs != null ? { durationMs: toolEnd.durationMs } : {},
|
|
1446
|
-
...toolEnd?.result ? { result: toolEnd.result } : {}
|
|
1447
|
-
});
|
|
1448
|
-
}
|
|
1449
|
-
};
|
|
1450
|
-
var handleAgentStart = (event, builder) => {
|
|
1451
|
-
const e = event;
|
|
1452
|
-
finalizeText(builder);
|
|
1453
|
-
builder.addBlock({
|
|
1454
|
-
type: "agent",
|
|
1455
|
-
id: builder.nextId(),
|
|
1456
|
-
agentName: e.agentName,
|
|
1457
|
-
displayName: e.agentDisplayName,
|
|
1458
|
-
avatarUrl: e.avatarUrl
|
|
1459
|
-
});
|
|
1460
|
-
};
|
|
1461
|
-
var handleThinkingDelta = (event, builder) => {
|
|
1462
|
-
const e = event;
|
|
1463
|
-
if (!builder.activeThinkingId) {
|
|
1464
|
-
const id = builder.nextId();
|
|
1465
|
-
builder.activeThinkingId = id;
|
|
1466
|
-
builder.thinkingStartMs = Date.now();
|
|
1467
|
-
builder.addBlock({
|
|
1468
|
-
type: "thinking",
|
|
1469
|
-
id,
|
|
1470
|
-
content: e.text,
|
|
1471
|
-
isActive: true
|
|
1472
|
-
});
|
|
1473
|
-
} else {
|
|
1474
|
-
const id = builder.activeThinkingId;
|
|
1475
|
-
const existing = builder.findBlock((b) => b.id === id);
|
|
1476
|
-
if (existing && existing.type === "thinking") {
|
|
1477
|
-
builder.patchBlock(id, {
|
|
1478
|
-
content: existing.content + e.text
|
|
1479
|
-
});
|
|
1480
|
-
}
|
|
1481
|
-
}
|
|
1482
|
-
};
|
|
1483
|
-
var handleThinkingComplete = (_event, builder) => {
|
|
1484
|
-
if (builder.activeThinkingId) {
|
|
1485
|
-
const durationMs = builder.thinkingStartMs ? Math.max(0, Date.now() - builder.thinkingStartMs) : void 0;
|
|
1486
|
-
builder.patchBlock(builder.activeThinkingId, {
|
|
1487
|
-
isActive: false,
|
|
1488
|
-
durationMs
|
|
1489
|
-
});
|
|
1490
|
-
builder.activeThinkingId = null;
|
|
1491
|
-
builder.thinkingStartMs = null;
|
|
1492
|
-
}
|
|
1493
|
-
};
|
|
1494
|
-
var handleSubagentStart = (event, builder) => {
|
|
1495
|
-
const e = event;
|
|
1496
|
-
finalizeText(builder);
|
|
1497
|
-
builder.addBlock({
|
|
1498
|
-
type: "subagent",
|
|
1499
|
-
id: builder.nextId(),
|
|
1500
|
-
agentName: e.agentName,
|
|
1501
|
-
displayName: e.displayName,
|
|
1502
|
-
toolCallId: e.toolCallId,
|
|
1503
|
-
avatarUrl: e.avatarUrl,
|
|
1504
|
-
description: e.description,
|
|
1505
|
-
content: "",
|
|
1506
|
-
isActive: true
|
|
1507
|
-
});
|
|
1508
|
-
};
|
|
1509
|
-
var handleSubagentChunk = (event, builder) => {
|
|
1510
|
-
const e = event;
|
|
1511
|
-
const block = builder.findBlock(
|
|
1512
|
-
(b) => b.type === "subagent" && b.toolCallId === e.toolCallId
|
|
1513
|
-
);
|
|
1514
|
-
if (block && block.type === "subagent") {
|
|
1515
|
-
builder.patchBlock(block.id, {
|
|
1516
|
-
content: block.content + e.text
|
|
1517
|
-
});
|
|
1518
|
-
}
|
|
1519
|
-
};
|
|
1520
|
-
var handleSubagentUpdate = (event, builder) => {
|
|
1521
|
-
const e = event;
|
|
1522
|
-
const block = builder.findBlock(
|
|
1523
|
-
(b) => b.type === "subagent" && b.toolCallId === e.toolCallId
|
|
1524
|
-
);
|
|
1525
|
-
if (block) {
|
|
1526
|
-
builder.patchBlock(block.id, {
|
|
1527
|
-
displayName: e.displayName
|
|
1528
|
-
});
|
|
1529
|
-
}
|
|
1530
|
-
};
|
|
1531
|
-
var handleSubagentEnd = (event, builder) => {
|
|
1532
|
-
const e = event;
|
|
1533
|
-
const block = builder.findBlock(
|
|
1534
|
-
(b) => b.type === "subagent" && b.toolCallId === e.toolCallId
|
|
1535
|
-
);
|
|
1536
|
-
if (block) {
|
|
1537
|
-
builder.patchBlock(block.id, {
|
|
1538
|
-
isActive: false
|
|
1539
|
-
});
|
|
1540
|
-
}
|
|
1541
|
-
};
|
|
1542
|
-
var handleComplete = (_event, builder) => {
|
|
1543
|
-
finalizeText(builder);
|
|
1544
|
-
finalizeThinking(builder);
|
|
1545
|
-
finalizeEditor(builder);
|
|
1546
|
-
for (const b of builder.getBlocks()) {
|
|
1547
|
-
if (b.type === "tool" && b.status !== "completed") {
|
|
1548
|
-
builder.patchBlock(b.id, { status: "completed" });
|
|
1549
|
-
}
|
|
1550
|
-
}
|
|
1551
|
-
};
|
|
1552
|
-
var handleError = (event, builder) => {
|
|
1553
|
-
const e = event;
|
|
1554
|
-
finalizeText(builder);
|
|
1555
|
-
finalizeThinking(builder);
|
|
1556
|
-
builder.addBlock({
|
|
1557
|
-
type: "error",
|
|
1558
|
-
id: builder.nextId(),
|
|
1559
|
-
message: e.error.message
|
|
1560
|
-
});
|
|
1561
|
-
};
|
|
1562
|
-
var handleDisconnected = (_event, builder) => {
|
|
1563
|
-
finalizeText(builder);
|
|
1564
|
-
finalizeThinking(builder);
|
|
1565
|
-
finalizeEditor(builder);
|
|
1566
|
-
};
|
|
1567
|
-
var handleCapsuleOutputChunk = (event, builder) => {
|
|
1568
|
-
const e = event;
|
|
1569
|
-
const block = builder.findBlock(
|
|
1570
|
-
(b) => b.type === "capsule" && b.callId === e.callId
|
|
1571
|
-
);
|
|
1572
|
-
if (block && block.type === "capsule") {
|
|
1573
|
-
builder.patchBlock(block.id, {
|
|
1574
|
-
output: block.output + e.chunk
|
|
1575
|
-
});
|
|
1576
|
-
} else {
|
|
1577
|
-
builder.addBlock({
|
|
1578
|
-
type: "capsule",
|
|
1579
|
-
id: builder.nextId(),
|
|
1580
|
-
callId: e.callId,
|
|
1581
|
-
toolName: "",
|
|
1582
|
-
output: e.chunk,
|
|
1583
|
-
isActive: true
|
|
1584
|
-
});
|
|
1585
|
-
}
|
|
1586
|
-
};
|
|
1587
|
-
var handleCapsuleOutput = (event, builder) => {
|
|
1588
|
-
const e = event;
|
|
1589
|
-
const block = builder.findBlock(
|
|
1590
|
-
(b) => b.type === "capsule" && b.callId === (e.callId ?? "")
|
|
1591
|
-
);
|
|
1592
|
-
if (block) {
|
|
1593
|
-
builder.patchBlock(block.id, {
|
|
1594
|
-
output: e.output,
|
|
1595
|
-
command: e.command,
|
|
1596
|
-
toolName: e.toolName,
|
|
1597
|
-
durationMs: e.durationMs,
|
|
1598
|
-
isActive: false
|
|
1599
|
-
});
|
|
1600
|
-
} else {
|
|
1601
|
-
builder.addBlock({
|
|
1602
|
-
type: "capsule",
|
|
1603
|
-
id: builder.nextId(),
|
|
1604
|
-
callId: e.callId ?? "",
|
|
1605
|
-
toolName: e.toolName,
|
|
1606
|
-
command: e.command,
|
|
1607
|
-
output: e.output,
|
|
1608
|
-
durationMs: e.durationMs,
|
|
1609
|
-
isActive: false
|
|
1610
|
-
});
|
|
1611
|
-
}
|
|
1612
|
-
};
|
|
1613
|
-
var handleAssetCreated = (event, builder) => {
|
|
1614
|
-
const e = event;
|
|
1615
|
-
builder.addBlock({
|
|
1616
|
-
type: "asset",
|
|
1617
|
-
id: builder.nextId(),
|
|
1618
|
-
assetId: e.assetId,
|
|
1619
|
-
name: e.name,
|
|
1620
|
-
url: e.url,
|
|
1621
|
-
mediaType: e.mediaType,
|
|
1622
|
-
sizeBytes: e.sizeBytes
|
|
1623
|
-
});
|
|
1624
|
-
};
|
|
1625
|
-
var handleTodoUpdate = (event, builder) => {
|
|
1626
|
-
const e = event;
|
|
1627
|
-
if (builder.activeTodoId) {
|
|
1628
|
-
builder.patchBlock(builder.activeTodoId, {
|
|
1629
|
-
todos: e.todos
|
|
1630
|
-
});
|
|
1631
|
-
} else {
|
|
1632
|
-
const id = builder.nextId();
|
|
1633
|
-
builder.activeTodoId = id;
|
|
1634
|
-
builder.addBlock({
|
|
1635
|
-
type: "todo",
|
|
1636
|
-
id,
|
|
1637
|
-
todos: e.todos
|
|
1638
|
-
});
|
|
1639
|
-
}
|
|
1640
|
-
};
|
|
1641
|
-
var handleEditorContentStart = (event, builder) => {
|
|
1642
|
-
const e = event;
|
|
1643
|
-
const id = builder.nextId();
|
|
1644
|
-
builder.activeEditorId = id;
|
|
1645
|
-
builder.addBlock({
|
|
1646
|
-
type: "editor",
|
|
1647
|
-
id,
|
|
1648
|
-
callId: e.callId,
|
|
1649
|
-
path: e.path,
|
|
1650
|
-
language: e.language,
|
|
1651
|
-
content: "",
|
|
1652
|
-
isStreaming: true
|
|
1653
|
-
});
|
|
1654
|
-
};
|
|
1655
|
-
var handleEditorContentDelta = (event, builder) => {
|
|
1656
|
-
const e = event;
|
|
1657
|
-
const block = builder.findBlock(
|
|
1658
|
-
(b) => b.type === "editor" && b.callId === e.callId
|
|
1659
|
-
);
|
|
1660
|
-
if (block && block.type === "editor") {
|
|
1661
|
-
builder.patchBlock(block.id, {
|
|
1662
|
-
content: block.content + e.delta
|
|
1663
|
-
});
|
|
1664
|
-
}
|
|
1665
|
-
};
|
|
1666
|
-
var handleEditorContentEnd = (event, builder) => {
|
|
1667
|
-
const e = event;
|
|
1668
|
-
const block = builder.findBlock(
|
|
1669
|
-
(b) => b.type === "editor" && b.callId === e.callId
|
|
1670
|
-
);
|
|
1671
|
-
if (block) {
|
|
1672
|
-
builder.patchBlock(block.id, {
|
|
1673
|
-
isStreaming: false
|
|
1674
|
-
});
|
|
1675
|
-
}
|
|
1676
|
-
builder.activeEditorId = null;
|
|
1677
|
-
};
|
|
1678
|
-
var handleDesktopStream = (event, builder) => {
|
|
1679
|
-
const e = event;
|
|
1680
|
-
if (!e.url) return;
|
|
1681
|
-
const existing = builder.findBlock((b) => b.type === "desktop_stream");
|
|
1682
|
-
if (existing) {
|
|
1683
|
-
builder.patchBlock(existing.id, {
|
|
1684
|
-
url: e.url,
|
|
1685
|
-
authKey: e.authKey,
|
|
1686
|
-
sandboxId: e.sandboxId
|
|
1687
|
-
});
|
|
1688
|
-
} else {
|
|
1689
|
-
builder.addBlock({
|
|
1690
|
-
type: "desktop_stream",
|
|
1691
|
-
id: builder.nextId(),
|
|
1692
|
-
url: e.url,
|
|
1693
|
-
authKey: e.authKey,
|
|
1694
|
-
sandboxId: e.sandboxId
|
|
1695
|
-
});
|
|
1696
|
-
}
|
|
1697
|
-
};
|
|
1698
|
-
var handleAttachmentStaged = (event, builder) => {
|
|
1699
|
-
const e = event;
|
|
1700
|
-
if (!e.files || e.files.length === 0) return;
|
|
1701
|
-
builder.addBlock({
|
|
1702
|
-
type: "attachment",
|
|
1703
|
-
id: builder.nextId(),
|
|
1704
|
-
files: e.files
|
|
1705
|
-
});
|
|
1706
|
-
};
|
|
1707
|
-
var noop = () => {
|
|
1708
|
-
};
|
|
1709
|
-
var standardHandlers = {
|
|
1710
|
-
user_message: handleUserMessage,
|
|
1711
|
-
chunk: handleChunk,
|
|
1712
|
-
tool_call: handleToolCall,
|
|
1713
|
-
tool_executing: handleToolExecuting,
|
|
1714
|
-
tool_progress: handleToolProgress,
|
|
1715
|
-
tool_completed: handleToolEnd,
|
|
1716
|
-
tool_end: handleToolEnd,
|
|
1717
|
-
agent_start: handleAgentStart,
|
|
1718
|
-
agent_end: noop,
|
|
1719
|
-
thinking_delta: handleThinkingDelta,
|
|
1720
|
-
thinking_complete: handleThinkingComplete,
|
|
1721
|
-
subagent_start: handleSubagentStart,
|
|
1722
|
-
subagent_chunk: handleSubagentChunk,
|
|
1723
|
-
subagent_update: handleSubagentUpdate,
|
|
1724
|
-
subagent_end: handleSubagentEnd,
|
|
1725
|
-
subagent_tool_use: noop,
|
|
1726
|
-
capsule_output: handleCapsuleOutput,
|
|
1727
|
-
capsule_output_chunk: handleCapsuleOutputChunk,
|
|
1728
|
-
asset_created: handleAssetCreated,
|
|
1729
|
-
todo_update: handleTodoUpdate,
|
|
1730
|
-
editor_content_start: handleEditorContentStart,
|
|
1731
|
-
editor_content_delta: handleEditorContentDelta,
|
|
1732
|
-
editor_content_end: handleEditorContentEnd,
|
|
1733
|
-
desktop_stream: handleDesktopStream,
|
|
1734
|
-
attachment_staged: handleAttachmentStaged,
|
|
1735
|
-
workspace_ready: noop,
|
|
1736
|
-
retry: noop,
|
|
1737
|
-
complete: handleComplete,
|
|
1738
|
-
error: handleError,
|
|
1739
|
-
disconnected: handleDisconnected
|
|
1740
|
-
};
|
|
1741
|
-
|
|
1742
1634
|
// src/types.ts
|
|
1743
1635
|
var ChatEventType = {
|
|
1636
|
+
// Connection lifecycle (SDK-local, not wire)
|
|
1744
1637
|
Connected: "connected",
|
|
1745
|
-
|
|
1638
|
+
Disconnected: "disconnected",
|
|
1639
|
+
// Turn lifecycle
|
|
1640
|
+
MessageStart: "message_start",
|
|
1641
|
+
MessageStop: "message_stop",
|
|
1642
|
+
// Block lifecycle
|
|
1643
|
+
BlockStart: "block_start",
|
|
1644
|
+
BlockDelta: "block_delta",
|
|
1645
|
+
BlockStop: "block_stop",
|
|
1646
|
+
// Reliability
|
|
1647
|
+
Stall: "stall",
|
|
1648
|
+
Retry: "retry",
|
|
1649
|
+
Error: "error",
|
|
1650
|
+
Keepalive: "keepalive",
|
|
1651
|
+
// Conversation-level (typed custom events)
|
|
1746
1652
|
UserMessage: "user_message",
|
|
1747
1653
|
TitleGenerated: "title_generated",
|
|
1748
|
-
ModelInfo: "model_info",
|
|
1749
|
-
Chunk: "chunk",
|
|
1750
|
-
ToolCall: "tool_call",
|
|
1751
|
-
ToolExecuting: "tool_executing",
|
|
1752
|
-
ToolProgress: "tool_progress",
|
|
1753
|
-
ToolCompleted: "tool_completed",
|
|
1754
|
-
ToolEnd: "tool_end",
|
|
1755
|
-
AgentStart: "agent_start",
|
|
1756
|
-
AgentEnd: "agent_end",
|
|
1757
|
-
ThinkingDelta: "thinking_delta",
|
|
1758
|
-
ThinkingComplete: "thinking_complete",
|
|
1759
|
-
SubagentStart: "subagent_start",
|
|
1760
|
-
SubagentChunk: "subagent_chunk",
|
|
1761
|
-
SubagentUpdate: "subagent_update",
|
|
1762
|
-
SubagentEnd: "subagent_end",
|
|
1763
|
-
SubagentToolUse: "subagent_tool_use",
|
|
1764
|
-
CapsuleOutput: "capsule_output",
|
|
1765
|
-
CapsuleOutputChunk: "capsule_output_chunk",
|
|
1766
|
-
AssetCreated: "asset_created",
|
|
1767
1654
|
TodoUpdate: "todo_update",
|
|
1768
|
-
EditorContentStart: "editor_content_start",
|
|
1769
|
-
EditorContentDelta: "editor_content_delta",
|
|
1770
|
-
EditorContentEnd: "editor_content_end",
|
|
1771
|
-
Complete: "complete",
|
|
1772
|
-
Error: "error",
|
|
1773
|
-
Disconnected: "disconnected",
|
|
1774
|
-
Retry: "retry",
|
|
1775
1655
|
ContextUpdate: "context_update",
|
|
1656
|
+
SubagentStart: "subagent_start",
|
|
1657
|
+
SubagentStop: "subagent_stop",
|
|
1658
|
+
ContextWarning: "context_warning",
|
|
1659
|
+
MemoryRecall: "memory_recall",
|
|
1660
|
+
MemoryUpdate: "memory_update",
|
|
1776
1661
|
DesktopStream: "desktop_stream",
|
|
1777
1662
|
AttachmentStaged: "attachment_staged",
|
|
1778
|
-
WorkspaceReady: "workspace_ready"
|
|
1663
|
+
WorkspaceReady: "workspace_ready",
|
|
1664
|
+
AssetCreated: "asset_created",
|
|
1665
|
+
ToolApprovalRequested: "tool_approval_requested",
|
|
1666
|
+
ToolApprovalGranted: "tool_approval_granted",
|
|
1667
|
+
ToolPermissionDenied: "tool_permission_denied",
|
|
1668
|
+
ToolHarnessWarning: "tool_harness_warning",
|
|
1669
|
+
UserUnavailable: "user_unavailable",
|
|
1670
|
+
PromptSuggestion: "prompt_suggestion",
|
|
1671
|
+
StateChanged: "state_changed",
|
|
1672
|
+
// Generic fallthrough for unknown custom events
|
|
1673
|
+
Custom: "custom"
|
|
1779
1674
|
};
|
|
1780
1675
|
|
|
1781
1676
|
// src/stream-manager.ts
|
|
@@ -1830,22 +1725,12 @@ var StreamManager = class {
|
|
|
1830
1725
|
}
|
|
1831
1726
|
onSessionEvent(event) {
|
|
1832
1727
|
const convId = this.session.conversationId;
|
|
1833
|
-
if (event.type === ChatEventType.BlocksChanged) {
|
|
1834
|
-
if (this._state === "streaming" && convId) {
|
|
1835
|
-
this.emit({
|
|
1836
|
-
type: "blocksChanged",
|
|
1837
|
-
conversationId: convId,
|
|
1838
|
-
blocks: event.blocks
|
|
1839
|
-
});
|
|
1840
|
-
}
|
|
1841
|
-
return;
|
|
1842
|
-
}
|
|
1843
1728
|
this.emit({
|
|
1844
1729
|
type: "event",
|
|
1845
1730
|
conversationId: convId,
|
|
1846
1731
|
event
|
|
1847
1732
|
});
|
|
1848
|
-
if (event.type === ChatEventType.
|
|
1733
|
+
if (event.type === ChatEventType.MessageStop) {
|
|
1849
1734
|
if (this._state === "streaming") {
|
|
1850
1735
|
this.setState("idle");
|
|
1851
1736
|
}
|
|
@@ -1858,13 +1743,13 @@ var StreamManager = class {
|
|
|
1858
1743
|
const id = await this.session.createNewConversation();
|
|
1859
1744
|
this.setActiveConversation(id);
|
|
1860
1745
|
}
|
|
1861
|
-
this.prepareUserBlock(content);
|
|
1862
1746
|
this.setState("streaming");
|
|
1863
1747
|
try {
|
|
1864
1748
|
await this.session.send(content, {
|
|
1865
1749
|
enableSearch: options?.enableSearch,
|
|
1866
1750
|
agentName: options?.agentName,
|
|
1867
|
-
uploadIds: options?.uploadIds
|
|
1751
|
+
uploadIds: options?.uploadIds,
|
|
1752
|
+
planMode: options?.planMode
|
|
1868
1753
|
});
|
|
1869
1754
|
} catch {
|
|
1870
1755
|
}
|
|
@@ -1878,7 +1763,6 @@ var StreamManager = class {
|
|
|
1878
1763
|
);
|
|
1879
1764
|
const lastUserMsg = userMsgs[userMsgs.length - 1];
|
|
1880
1765
|
if (!lastUserMsg) return;
|
|
1881
|
-
this.prepareUserBlock(lastUserMsg.content);
|
|
1882
1766
|
this.setState("streaming");
|
|
1883
1767
|
try {
|
|
1884
1768
|
await this.session.resendFromCheckpoint(
|
|
@@ -1942,14 +1826,6 @@ var StreamManager = class {
|
|
|
1942
1826
|
this.handlers = [];
|
|
1943
1827
|
}
|
|
1944
1828
|
// ── Internal: helpers ──────────────────────────────────────────
|
|
1945
|
-
prepareUserBlock(content) {
|
|
1946
|
-
this.session.blockBuilder.reset();
|
|
1947
|
-
this.session.blockBuilder.addBlock({
|
|
1948
|
-
type: "user",
|
|
1949
|
-
id: this.session.blockBuilder.nextId(),
|
|
1950
|
-
content
|
|
1951
|
-
});
|
|
1952
|
-
}
|
|
1953
1829
|
finalizeStream() {
|
|
1954
1830
|
if (this._state === "streaming") {
|
|
1955
1831
|
this.setState("idle");
|
|
@@ -1960,8 +1836,8 @@ var StreamManager = class {
|
|
|
1960
1836
|
this.setState("restoring");
|
|
1961
1837
|
let activeJobId = null;
|
|
1962
1838
|
try {
|
|
1963
|
-
const res = await this.session.client.
|
|
1964
|
-
activeJobId = res.
|
|
1839
|
+
const res = await this.session.client.getActiveJob(conversationId);
|
|
1840
|
+
activeJobId = res.jobId;
|
|
1965
1841
|
} catch {
|
|
1966
1842
|
}
|
|
1967
1843
|
if (activeJobId) {
|
|
@@ -1981,15 +1857,19 @@ var StreamManager = class {
|
|
|
1981
1857
|
const completedJobs = jobs.filter(
|
|
1982
1858
|
(j) => j.status === "completed"
|
|
1983
1859
|
);
|
|
1984
|
-
|
|
1985
|
-
|
|
1860
|
+
const userMessages = this.session.messages.filter(
|
|
1861
|
+
(m) => m.role === "user"
|
|
1862
|
+
);
|
|
1863
|
+
for (let i = 0; i < completedJobs.length; i++) {
|
|
1864
|
+
const job = completedJobs[i];
|
|
1865
|
+
const userContent = userMessages[i]?.content;
|
|
1866
|
+
await this.session.switchConversation(
|
|
1867
|
+
conversationId,
|
|
1868
|
+
job.job_id,
|
|
1869
|
+
userContent
|
|
1870
|
+
);
|
|
1986
1871
|
}
|
|
1987
1872
|
if (completedJobs.length > 0) {
|
|
1988
|
-
this.emit({
|
|
1989
|
-
type: "blocksChanged",
|
|
1990
|
-
conversationId,
|
|
1991
|
-
blocks: this.session.blockBuilder.getBlocks()
|
|
1992
|
-
});
|
|
1993
1873
|
this.emit({
|
|
1994
1874
|
type: "versionsReady",
|
|
1995
1875
|
conversationId,
|
|
@@ -2007,24 +1887,94 @@ var StreamManager = class {
|
|
|
2007
1887
|
this.emit({ type: "conversationChanged", conversationId: id });
|
|
2008
1888
|
}
|
|
2009
1889
|
};
|
|
1890
|
+
|
|
1891
|
+
// src/replay.ts
|
|
1892
|
+
function toWireEvent(raw) {
|
|
1893
|
+
const type = raw.data.type || raw.event;
|
|
1894
|
+
if (!type || type === "done") return null;
|
|
1895
|
+
return { ...raw.data, type };
|
|
1896
|
+
}
|
|
1897
|
+
function mapSseToChat(raw) {
|
|
1898
|
+
const wire = toWireEvent(raw);
|
|
1899
|
+
if (!wire) return [];
|
|
1900
|
+
const event = translateWireEvent(wire);
|
|
1901
|
+
return event ? [event] : [];
|
|
1902
|
+
}
|
|
1903
|
+
function replayEvents(sseEvents, userMessages, handleEvent, addBlock) {
|
|
1904
|
+
const userMsgs = userMessages.filter((m) => m.role === "user");
|
|
1905
|
+
let userIdx = 0;
|
|
1906
|
+
let expectingUserMessage = true;
|
|
1907
|
+
for (const raw of sseEvents) {
|
|
1908
|
+
const type = raw.data.type || raw.event;
|
|
1909
|
+
if (type === "message_stop") {
|
|
1910
|
+
expectingUserMessage = true;
|
|
1911
|
+
}
|
|
1912
|
+
if (type === "message_start" && expectingUserMessage && userIdx < userMsgs.length) {
|
|
1913
|
+
const userMsg = userMsgs[userIdx];
|
|
1914
|
+
addBlock({
|
|
1915
|
+
type: "user",
|
|
1916
|
+
id: `replay_user_${userIdx}`,
|
|
1917
|
+
content: userMsg.content
|
|
1918
|
+
});
|
|
1919
|
+
userIdx++;
|
|
1920
|
+
expectingUserMessage = false;
|
|
1921
|
+
}
|
|
1922
|
+
for (const ce of mapSseToChat(raw)) {
|
|
1923
|
+
handleEvent(ce);
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
// src/embedded-resource.ts
|
|
1929
|
+
function isEmbeddedResource(value) {
|
|
1930
|
+
return typeof value === "object" && value !== null && value._embedded_resource === true;
|
|
1931
|
+
}
|
|
1932
|
+
function parseEmbeddedResource(value) {
|
|
1933
|
+
let candidate = value;
|
|
1934
|
+
if (typeof candidate === "string") {
|
|
1935
|
+
const trimmed = candidate.trim();
|
|
1936
|
+
if (!trimmed.startsWith("{")) return null;
|
|
1937
|
+
try {
|
|
1938
|
+
candidate = JSON.parse(trimmed);
|
|
1939
|
+
} catch {
|
|
1940
|
+
return null;
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
if (!isEmbeddedResource(candidate)) return null;
|
|
1944
|
+
const mimeType = candidate.mime_type;
|
|
1945
|
+
const uri = candidate.uri;
|
|
1946
|
+
const payload = candidate.payload;
|
|
1947
|
+
if (typeof mimeType !== "string" || !mimeType) return null;
|
|
1948
|
+
if (typeof uri !== "string" || !uri) return null;
|
|
1949
|
+
if (!payload || typeof payload !== "object") return null;
|
|
1950
|
+
return {
|
|
1951
|
+
mimeType,
|
|
1952
|
+
uri,
|
|
1953
|
+
payload
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
2010
1956
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2011
1957
|
0 && (module.exports = {
|
|
2012
1958
|
AstralformClient,
|
|
2013
1959
|
AstralformError,
|
|
2014
1960
|
AuthenticationError,
|
|
2015
|
-
BlockBuilder,
|
|
2016
1961
|
ChatEventType,
|
|
2017
1962
|
ChatSession,
|
|
2018
1963
|
ConnectionError,
|
|
2019
1964
|
InMemoryStorage,
|
|
2020
1965
|
LLMNotConfiguredError,
|
|
1966
|
+
ProtocolRegistry,
|
|
2021
1967
|
RateLimitError,
|
|
2022
1968
|
ServerError,
|
|
2023
1969
|
StreamAbortedError,
|
|
2024
1970
|
StreamManager,
|
|
2025
1971
|
ToolRegistry,
|
|
2026
1972
|
generateId,
|
|
2027
|
-
|
|
2028
|
-
|
|
1973
|
+
isEmbeddedResource,
|
|
1974
|
+
mapSseToChat,
|
|
1975
|
+
parseEmbeddedResource,
|
|
1976
|
+
replayEvents,
|
|
1977
|
+
streamJobSSE,
|
|
1978
|
+
translateDelta
|
|
2029
1979
|
});
|
|
2030
1980
|
//# sourceMappingURL=index.cjs.map
|