@astralform/js 0.2.3 → 1.0.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 +929 -1022
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +702 -564
- package/dist/index.d.ts +702 -564
- package/dist/index.js +922 -1019
- package/dist/index.js.map +1 -1
- package/package.json +7 -1
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,9 @@ 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
|
+
}
|
|
300
557
|
// --- Conversation Assets ---
|
|
301
558
|
mapAsset(raw) {
|
|
302
559
|
return {
|
|
@@ -318,10 +575,7 @@ var AstralformClient = class {
|
|
|
318
575
|
`${this.baseURL}/v1/conversations/${encodeURIComponent(conversationId)}/uploads`,
|
|
319
576
|
{
|
|
320
577
|
method: "POST",
|
|
321
|
-
headers:
|
|
322
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
323
|
-
"X-End-User-ID": this.userId
|
|
324
|
-
},
|
|
578
|
+
headers: this.authHeaders,
|
|
325
579
|
body: formData
|
|
326
580
|
}
|
|
327
581
|
).catch((err) => {
|
|
@@ -345,6 +599,31 @@ var AstralformClient = class {
|
|
|
345
599
|
);
|
|
346
600
|
return raw.map((r) => this.mapAsset(r));
|
|
347
601
|
}
|
|
602
|
+
// --- Account-scoped discovery (user-token mode) ---
|
|
603
|
+
//
|
|
604
|
+
// Lets a signed-in user pick which team/project they want to act on.
|
|
605
|
+
// Backend gates these on OIDC user context (no X-Project-ID required) —
|
|
606
|
+
// sending them in API-key mode yields 401.
|
|
607
|
+
async listTeams() {
|
|
608
|
+
const raw = await this.get("/v1/teams");
|
|
609
|
+
return raw.map((t) => ({
|
|
610
|
+
id: t.id,
|
|
611
|
+
name: t.name,
|
|
612
|
+
slug: t.slug,
|
|
613
|
+
isDefault: t.is_default,
|
|
614
|
+
role: t.role
|
|
615
|
+
}));
|
|
616
|
+
}
|
|
617
|
+
async listProjects(teamId) {
|
|
618
|
+
const raw = await this.get(`/v1/teams/${encodeURIComponent(teamId)}/projects`);
|
|
619
|
+
return raw.map((p) => ({
|
|
620
|
+
id: p.id,
|
|
621
|
+
name: p.name,
|
|
622
|
+
teamId: p.team_id,
|
|
623
|
+
createdAt: p.created_at,
|
|
624
|
+
updatedAt: p.updated_at
|
|
625
|
+
}));
|
|
626
|
+
}
|
|
348
627
|
// --- Jobs API ---
|
|
349
628
|
async createJob(request) {
|
|
350
629
|
return this.post("/v1/jobs", request);
|
|
@@ -361,90 +640,50 @@ var AstralformClient = class {
|
|
|
361
640
|
async cancelJob(jobId) {
|
|
362
641
|
await this.post(`/v1/jobs/${encodeURIComponent(jobId)}/cancel`, {});
|
|
363
642
|
}
|
|
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();
|
|
643
|
+
async getJob(jobId) {
|
|
644
|
+
const raw = await this.get(`/v1/jobs/${encodeURIComponent(jobId)}`);
|
|
645
|
+
return {
|
|
646
|
+
jobId: raw.job_id,
|
|
647
|
+
status: raw.status,
|
|
648
|
+
createdAt: raw.created_at ?? null,
|
|
649
|
+
startedAt: raw.started_at ?? null,
|
|
650
|
+
completedAt: raw.completed_at ?? null,
|
|
651
|
+
errorMessage: raw.error_message ?? null,
|
|
652
|
+
inputTokens: raw.input_tokens ?? 0,
|
|
653
|
+
outputTokens: raw.output_tokens ?? 0
|
|
654
|
+
};
|
|
434
655
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
656
|
+
async submitFeedback(jobId, request) {
|
|
657
|
+
const body = {
|
|
658
|
+
rating: request.rating
|
|
659
|
+
};
|
|
660
|
+
if (request.comment != null) body.comment = request.comment;
|
|
661
|
+
const raw = await this.post(`/v1/jobs/${encodeURIComponent(jobId)}/feedback`, body);
|
|
662
|
+
return {
|
|
663
|
+
id: raw.id,
|
|
664
|
+
jobId: raw.job_id,
|
|
665
|
+
rating: raw.rating,
|
|
666
|
+
comment: raw.comment,
|
|
667
|
+
createdAt: raw.created_at
|
|
668
|
+
};
|
|
441
669
|
}
|
|
442
|
-
|
|
443
|
-
|
|
670
|
+
async getActiveJob(conversationId) {
|
|
671
|
+
const raw = await this.get(`/v1/conversations/${encodeURIComponent(conversationId)}/active-job`);
|
|
672
|
+
return {
|
|
673
|
+
jobId: raw.job_id ?? null,
|
|
674
|
+
status: raw.status
|
|
675
|
+
};
|
|
444
676
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
677
|
+
async listJobs(conversationId) {
|
|
678
|
+
const raw = await this.get(`/v1/conversations/${encodeURIComponent(conversationId)}/jobs`);
|
|
679
|
+
return raw.map((j) => ({
|
|
680
|
+
jobId: j.job_id,
|
|
681
|
+
status: j.status,
|
|
682
|
+
replacesJobId: j.replaces_job_id ?? null,
|
|
683
|
+
responseContent: j.response_content ?? null,
|
|
684
|
+
metrics: j.metrics ?? null,
|
|
685
|
+
createdAt: j.created_at ?? null
|
|
686
|
+
}));
|
|
448
687
|
}
|
|
449
688
|
};
|
|
450
689
|
|
|
@@ -598,40 +837,348 @@ var ToolRegistry = class {
|
|
|
598
837
|
}
|
|
599
838
|
};
|
|
600
839
|
|
|
601
|
-
// src/
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
840
|
+
// src/protocol-registry.ts
|
|
841
|
+
var ProtocolRegistry = class {
|
|
842
|
+
constructor() {
|
|
843
|
+
this.adapters = /* @__PURE__ */ new Map();
|
|
844
|
+
}
|
|
845
|
+
/** Register or replace the adapter for a MIME type. */
|
|
846
|
+
register(adapter) {
|
|
847
|
+
this.adapters.set(adapter.mimeType, adapter);
|
|
848
|
+
}
|
|
849
|
+
/** Remove the adapter for a MIME type. No-op if not registered. */
|
|
850
|
+
unregister(mimeType) {
|
|
851
|
+
this.adapters.delete(mimeType);
|
|
852
|
+
}
|
|
853
|
+
/** Returns the adapter for a MIME type, or ``null`` if none is registered. */
|
|
854
|
+
get(mimeType) {
|
|
855
|
+
return this.adapters.get(mimeType) ?? null;
|
|
856
|
+
}
|
|
857
|
+
has(mimeType) {
|
|
858
|
+
return this.adapters.has(mimeType);
|
|
859
|
+
}
|
|
860
|
+
/** Drop every adapter. Called when a session disconnects. */
|
|
861
|
+
clear() {
|
|
862
|
+
this.adapters.clear();
|
|
863
|
+
}
|
|
864
|
+
listMimeTypes() {
|
|
865
|
+
return Array.from(this.adapters.keys());
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
|
|
869
|
+
// src/translate.ts
|
|
870
|
+
function translateDelta(wire) {
|
|
871
|
+
switch (wire.channel) {
|
|
872
|
+
case "text":
|
|
873
|
+
return { channel: "text", text: wire.text };
|
|
874
|
+
case "thinking":
|
|
875
|
+
return { channel: "thinking", text: wire.text };
|
|
876
|
+
case "signature":
|
|
877
|
+
return { channel: "signature", signature: wire.signature };
|
|
878
|
+
case "input":
|
|
879
|
+
return { channel: "input", partialJson: wire.partial_json };
|
|
880
|
+
case "input_arg":
|
|
881
|
+
return {
|
|
882
|
+
channel: "inputArg",
|
|
883
|
+
argName: wire.arg_name,
|
|
884
|
+
text: wire.text
|
|
885
|
+
};
|
|
886
|
+
case "output":
|
|
887
|
+
return { channel: "output", stream: wire.stream, chunk: wire.chunk };
|
|
888
|
+
case "status":
|
|
889
|
+
return {
|
|
890
|
+
channel: "status",
|
|
891
|
+
status: wire.status,
|
|
892
|
+
note: wire.note
|
|
893
|
+
};
|
|
894
|
+
default:
|
|
895
|
+
return null;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
function translateAgentIdentity(raw) {
|
|
899
|
+
return {
|
|
900
|
+
name: raw.name ?? "",
|
|
901
|
+
displayName: raw.display_name ?? null,
|
|
902
|
+
avatarUrl: raw.avatar_url ?? null,
|
|
903
|
+
description: raw.description ?? null
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
function translateCustomEvent(name, data) {
|
|
907
|
+
switch (name) {
|
|
908
|
+
case "user_message":
|
|
909
|
+
return {
|
|
910
|
+
type: "user_message",
|
|
911
|
+
content: data.content ?? "",
|
|
912
|
+
createdAt: data.created_at
|
|
913
|
+
};
|
|
914
|
+
case "title_generated":
|
|
915
|
+
return {
|
|
916
|
+
type: "title_generated",
|
|
917
|
+
title: data.title ?? ""
|
|
918
|
+
};
|
|
919
|
+
case "todo_update":
|
|
920
|
+
return {
|
|
921
|
+
type: "todo_update",
|
|
922
|
+
todos: data.todos ?? []
|
|
923
|
+
};
|
|
924
|
+
case "context_update":
|
|
925
|
+
return {
|
|
926
|
+
type: "context_update",
|
|
927
|
+
context: data.context ?? {},
|
|
928
|
+
phase: data.phase ?? null,
|
|
929
|
+
updatedAt: data.updated_at ?? null
|
|
930
|
+
};
|
|
931
|
+
case "subagent_start":
|
|
932
|
+
return {
|
|
933
|
+
type: "subagent_start",
|
|
934
|
+
agent: translateAgentIdentity(
|
|
935
|
+
data.agent ?? {}
|
|
936
|
+
),
|
|
937
|
+
taskCallId: data.task_call_id ?? null
|
|
938
|
+
};
|
|
939
|
+
case "subagent_stop":
|
|
940
|
+
return {
|
|
941
|
+
type: "subagent_stop",
|
|
942
|
+
agent: translateAgentIdentity(
|
|
943
|
+
data.agent ?? {}
|
|
944
|
+
),
|
|
945
|
+
taskCallId: data.task_call_id ?? null
|
|
946
|
+
};
|
|
947
|
+
case "context_warning":
|
|
948
|
+
return {
|
|
949
|
+
type: "context_warning",
|
|
950
|
+
severity: data.severity ?? "warning",
|
|
951
|
+
utilizationPct: data.utilization_pct ?? 0,
|
|
952
|
+
remainingTokens: data.remaining_tokens ?? 0,
|
|
953
|
+
windowTokens: data.window_tokens ?? 0,
|
|
954
|
+
inputTokens: data.input_tokens ?? 0,
|
|
955
|
+
message: data.message ?? ""
|
|
956
|
+
};
|
|
957
|
+
case "memory_recall":
|
|
958
|
+
return {
|
|
959
|
+
type: "memory_recall",
|
|
960
|
+
memories: data.memories ?? []
|
|
961
|
+
};
|
|
962
|
+
case "memory_update":
|
|
963
|
+
return {
|
|
964
|
+
type: "memory_update",
|
|
965
|
+
action: data.action ?? "",
|
|
966
|
+
memoryId: data.memory_id ?? null,
|
|
967
|
+
key: data.key ?? null,
|
|
968
|
+
namespace: data.namespace ?? null
|
|
969
|
+
};
|
|
970
|
+
case "desktop_stream":
|
|
971
|
+
return {
|
|
972
|
+
type: "desktop_stream",
|
|
973
|
+
url: data.url ?? "",
|
|
974
|
+
sandboxId: data.sandbox_id ?? null
|
|
975
|
+
};
|
|
976
|
+
case "attachment_staged":
|
|
977
|
+
return {
|
|
978
|
+
type: "attachment_staged",
|
|
979
|
+
attachmentId: data.attachment_id ?? "",
|
|
980
|
+
filename: data.filename ?? "",
|
|
981
|
+
contentType: data.content_type ?? null,
|
|
982
|
+
sizeBytes: data.size_bytes ?? null
|
|
983
|
+
};
|
|
984
|
+
case "workspace_ready":
|
|
985
|
+
return {
|
|
986
|
+
type: "workspace_ready",
|
|
987
|
+
sandboxId: data.sandbox_id ?? "",
|
|
988
|
+
workspacePath: data.workspace_path ?? null
|
|
989
|
+
};
|
|
990
|
+
case "asset_created":
|
|
991
|
+
return {
|
|
992
|
+
type: "asset_created",
|
|
993
|
+
assetId: data.asset_id ?? "",
|
|
994
|
+
filename: data.filename ?? "",
|
|
995
|
+
url: data.url ?? null,
|
|
996
|
+
contentType: data.content_type ?? null
|
|
997
|
+
};
|
|
998
|
+
case "tool_approval_requested":
|
|
999
|
+
return {
|
|
1000
|
+
type: "tool_approval_requested",
|
|
1001
|
+
toolName: data.tool_name ?? "",
|
|
1002
|
+
callId: data.call_id ?? "",
|
|
1003
|
+
arguments: data.arguments ?? {},
|
|
1004
|
+
riskLevel: data.risk_level ?? null,
|
|
1005
|
+
reason: data.reason ?? null
|
|
1006
|
+
};
|
|
1007
|
+
case "tool_approval_granted":
|
|
1008
|
+
return {
|
|
1009
|
+
type: "tool_approval_granted",
|
|
1010
|
+
toolName: data.tool_name ?? "",
|
|
1011
|
+
callId: data.call_id ?? ""
|
|
1012
|
+
};
|
|
1013
|
+
case "tool_permission_denied":
|
|
1014
|
+
return {
|
|
1015
|
+
type: "tool_permission_denied",
|
|
1016
|
+
toolName: data.tool_name ?? "",
|
|
1017
|
+
callId: data.call_id ?? "",
|
|
1018
|
+
reason: data.reason ?? null,
|
|
1019
|
+
deniedBy: data.denied_by ?? null
|
|
1020
|
+
};
|
|
1021
|
+
case "tool_harness_warning":
|
|
1022
|
+
return {
|
|
1023
|
+
type: "tool_harness_warning",
|
|
1024
|
+
toolName: data.tool_name ?? "",
|
|
1025
|
+
callId: data.call_id ?? "",
|
|
1026
|
+
message: data.message ?? null,
|
|
1027
|
+
details: data.details ?? null
|
|
1028
|
+
};
|
|
1029
|
+
case "user_unavailable":
|
|
1030
|
+
return {
|
|
1031
|
+
type: "user_unavailable",
|
|
1032
|
+
consecutiveTimeouts: data.consecutive_timeouts ?? 0,
|
|
1033
|
+
toolName: data.tool_name ?? null
|
|
1034
|
+
};
|
|
1035
|
+
case "prompt_suggestion":
|
|
1036
|
+
return {
|
|
1037
|
+
type: "prompt_suggestion",
|
|
1038
|
+
suggestions: data.suggestions ?? []
|
|
1039
|
+
};
|
|
1040
|
+
case "state_changed":
|
|
1041
|
+
return {
|
|
1042
|
+
type: "state_changed",
|
|
1043
|
+
state: data.state ?? ""
|
|
1044
|
+
};
|
|
1045
|
+
default:
|
|
1046
|
+
return { type: "custom", name, data };
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
function legacyCustomEventData(wire) {
|
|
1050
|
+
return wire;
|
|
1051
|
+
}
|
|
1052
|
+
function translateWireEvent(wire) {
|
|
1053
|
+
if (wire.type === "prompt_suggestion") {
|
|
1054
|
+
return translateCustomEvent(
|
|
1055
|
+
"prompt_suggestion",
|
|
1056
|
+
legacyCustomEventData(wire)
|
|
1057
|
+
);
|
|
1058
|
+
}
|
|
1059
|
+
switch (wire.type) {
|
|
1060
|
+
case "message_start":
|
|
1061
|
+
return {
|
|
1062
|
+
type: "message_start",
|
|
1063
|
+
turnId: wire.turn_id,
|
|
1064
|
+
model: wire.model,
|
|
1065
|
+
agentName: wire.agent_name,
|
|
1066
|
+
agentDisplayName: wire.agent_display_name,
|
|
1067
|
+
agentAvatarUrl: wire.agent_avatar_url
|
|
1068
|
+
};
|
|
1069
|
+
case "block_start":
|
|
1070
|
+
return {
|
|
1071
|
+
type: "block_start",
|
|
1072
|
+
turnId: wire.turn_id,
|
|
1073
|
+
path: wire.path,
|
|
1074
|
+
parentPath: wire.parent_path ?? null,
|
|
1075
|
+
kind: wire.kind,
|
|
1076
|
+
metadata: wire.metadata
|
|
1077
|
+
};
|
|
1078
|
+
case "block_delta": {
|
|
1079
|
+
const delta = translateDelta(wire.delta);
|
|
1080
|
+
if (!delta) return null;
|
|
1081
|
+
return {
|
|
1082
|
+
type: "block_delta",
|
|
1083
|
+
turnId: wire.turn_id,
|
|
1084
|
+
path: wire.path,
|
|
1085
|
+
delta
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
case "block_stop":
|
|
1089
|
+
return {
|
|
1090
|
+
type: "block_stop",
|
|
1091
|
+
turnId: wire.turn_id,
|
|
1092
|
+
path: wire.path,
|
|
1093
|
+
status: wire.status,
|
|
1094
|
+
final: wire.final
|
|
1095
|
+
};
|
|
1096
|
+
case "message_stop":
|
|
1097
|
+
return {
|
|
1098
|
+
type: "message_stop",
|
|
1099
|
+
turnId: wire.turn_id,
|
|
1100
|
+
jobId: wire.job_id,
|
|
1101
|
+
stopReason: wire.stop_reason,
|
|
1102
|
+
usage: {
|
|
1103
|
+
inputTokens: wire.usage.input_tokens ?? 0,
|
|
1104
|
+
outputTokens: wire.usage.output_tokens ?? 0,
|
|
1105
|
+
cachedTokens: wire.usage.cached_tokens ?? 0
|
|
1106
|
+
},
|
|
1107
|
+
ttfbMs: wire.ttfb_ms,
|
|
1108
|
+
totalMs: wire.total_ms,
|
|
1109
|
+
stallCount: wire.stall_count
|
|
1110
|
+
};
|
|
1111
|
+
case "stall":
|
|
1112
|
+
return {
|
|
1113
|
+
type: "stall",
|
|
1114
|
+
sinceLastEventMs: wire.since_last_event_ms,
|
|
1115
|
+
stallCount: wire.stall_count
|
|
1116
|
+
};
|
|
1117
|
+
case "retry":
|
|
1118
|
+
return {
|
|
1119
|
+
type: "retry",
|
|
1120
|
+
attempt: wire.attempt,
|
|
1121
|
+
reason: wire.reason,
|
|
1122
|
+
backoffMs: wire.backoff_ms,
|
|
1123
|
+
strategy: wire.strategy ?? null,
|
|
1124
|
+
maxAttempts: wire.max_attempts ?? null,
|
|
1125
|
+
contextRecovery: wire.context_recovery ?? null
|
|
1126
|
+
};
|
|
1127
|
+
case "error":
|
|
1128
|
+
return {
|
|
1129
|
+
type: "error",
|
|
1130
|
+
code: wire.code,
|
|
1131
|
+
message: wire.message,
|
|
1132
|
+
blockPath: wire.block_path ?? null
|
|
1133
|
+
};
|
|
1134
|
+
case "keepalive":
|
|
1135
|
+
return {
|
|
1136
|
+
type: "keepalive",
|
|
1137
|
+
sinceLastEventMs: wire.since_last_event_ms
|
|
1138
|
+
};
|
|
1139
|
+
case "custom":
|
|
1140
|
+
return translateCustomEvent(wire.name, wire.data);
|
|
1141
|
+
default: {
|
|
1142
|
+
const _exhaustive = wire;
|
|
1143
|
+
void _exhaustive;
|
|
1144
|
+
return null;
|
|
1145
|
+
}
|
|
605
1146
|
}
|
|
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
1147
|
}
|
|
612
1148
|
|
|
613
1149
|
// src/session.ts
|
|
1150
|
+
function pathEquals(a, b) {
|
|
1151
|
+
if (a.length !== b.length) return false;
|
|
1152
|
+
for (let i = 0; i < a.length; i++) {
|
|
1153
|
+
if (a[i] !== b[i]) return false;
|
|
1154
|
+
}
|
|
1155
|
+
return true;
|
|
1156
|
+
}
|
|
614
1157
|
var ChatSession = class {
|
|
615
|
-
constructor(config, storage
|
|
1158
|
+
constructor(config, storage) {
|
|
1159
|
+
/**
|
|
1160
|
+
* Pluggable UI protocol adapters. Consumers register a framework-
|
|
1161
|
+
* specific adapter (e.g. React) for each MIME type they can render,
|
|
1162
|
+
* typically gated on ``session.projectStatus.uiComponents.protocol``.
|
|
1163
|
+
* ``ToolBlock``-style consumers look up the adapter for an incoming
|
|
1164
|
+
* embedded resource and hand off rendering.
|
|
1165
|
+
*/
|
|
1166
|
+
this.protocols = new ProtocolRegistry();
|
|
616
1167
|
// State
|
|
617
1168
|
this.conversationId = null;
|
|
618
1169
|
this.conversations = [];
|
|
619
1170
|
this.messages = [];
|
|
620
|
-
this.streamingContent = "";
|
|
621
1171
|
this.isStreaming = false;
|
|
622
|
-
this.executingTool = null;
|
|
623
1172
|
this.projectStatus = null;
|
|
624
1173
|
this.agents = [];
|
|
625
1174
|
this.skills = [];
|
|
626
1175
|
this.enabledClientTools = /* @__PURE__ */ new Set();
|
|
627
1176
|
this.modelDisplayName = null;
|
|
628
|
-
//
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
this.
|
|
632
|
-
this.
|
|
633
|
-
this.todos = [];
|
|
634
|
-
this.activeTools = /* @__PURE__ */ new Map();
|
|
1177
|
+
// Minimal in-session accumulation for the assistant message record.
|
|
1178
|
+
// Only top-level ``text`` blocks contribute; subagent / tool output
|
|
1179
|
+
// is tracked by the consumer's own block store.
|
|
1180
|
+
this.accumulatedText = "";
|
|
1181
|
+
this.currentTextPath = null;
|
|
635
1182
|
this.handlers = /* @__PURE__ */ new Set();
|
|
636
1183
|
this.abortController = null;
|
|
637
1184
|
/** Last received sequence number for resumable reconnection */
|
|
@@ -641,13 +1188,6 @@ var ChatSession = class {
|
|
|
641
1188
|
this.client = new AstralformClient(config);
|
|
642
1189
|
this.toolRegistry = new ToolRegistry();
|
|
643
1190
|
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
1191
|
}
|
|
652
1192
|
on(handler) {
|
|
653
1193
|
this.handlers.add(handler);
|
|
@@ -656,9 +1196,6 @@ var ChatSession = class {
|
|
|
656
1196
|
};
|
|
657
1197
|
}
|
|
658
1198
|
emit(event) {
|
|
659
|
-
if (event.type !== "blocks_changed" && event.type !== "connected") {
|
|
660
|
-
this.blockBuilder.processEvent(event);
|
|
661
|
-
}
|
|
662
1199
|
for (const handler of this.handlers) {
|
|
663
1200
|
try {
|
|
664
1201
|
handler(event);
|
|
@@ -711,7 +1248,8 @@ var ChatSession = class {
|
|
|
711
1248
|
),
|
|
712
1249
|
upload_ids: options?.uploadIds,
|
|
713
1250
|
agent_name: options?.agentName,
|
|
714
|
-
enable_search: options?.enableSearch
|
|
1251
|
+
enable_search: options?.enableSearch,
|
|
1252
|
+
plan_mode: options?.planMode
|
|
715
1253
|
};
|
|
716
1254
|
await this.processStream(request);
|
|
717
1255
|
}
|
|
@@ -728,13 +1266,8 @@ var ChatSession = class {
|
|
|
728
1266
|
await this.processStream(request);
|
|
729
1267
|
}
|
|
730
1268
|
resetStreamingState() {
|
|
731
|
-
this.
|
|
732
|
-
this.
|
|
733
|
-
this.isThinking = false;
|
|
734
|
-
this.activeSubagents.clear();
|
|
735
|
-
this.capsuleOutputs = [];
|
|
736
|
-
this.todos = [];
|
|
737
|
-
this.activeTools.clear();
|
|
1269
|
+
this.accumulatedText = "";
|
|
1270
|
+
this.currentTextPath = null;
|
|
738
1271
|
}
|
|
739
1272
|
async processStream(request) {
|
|
740
1273
|
this.isStreaming = true;
|
|
@@ -746,22 +1279,42 @@ var ChatSession = class {
|
|
|
746
1279
|
if (!(err instanceof DOMException && err.name === "AbortError")) {
|
|
747
1280
|
this.emit({
|
|
748
1281
|
type: "error",
|
|
749
|
-
|
|
1282
|
+
code: "connection_error",
|
|
1283
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1284
|
+
blockPath: null
|
|
750
1285
|
});
|
|
751
1286
|
}
|
|
752
1287
|
} finally {
|
|
753
1288
|
this.isStreaming = false;
|
|
754
|
-
this.executingTool = null;
|
|
755
1289
|
this.abortController = null;
|
|
756
1290
|
}
|
|
757
1291
|
}
|
|
758
1292
|
async consumeJobStream(request) {
|
|
759
1293
|
const job = await this.client.createJob(request);
|
|
760
1294
|
this.currentJobId = job.job_id;
|
|
761
|
-
|
|
1295
|
+
const conversationId = job.conversation_id;
|
|
762
1296
|
if (!this.conversationId) {
|
|
763
1297
|
this.conversationId = conversationId;
|
|
764
1298
|
}
|
|
1299
|
+
if (!this.conversations.some((c) => c.id === conversationId)) {
|
|
1300
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1301
|
+
const conv = {
|
|
1302
|
+
id: conversationId,
|
|
1303
|
+
title: "",
|
|
1304
|
+
messageCount: 0,
|
|
1305
|
+
createdAt: now,
|
|
1306
|
+
updatedAt: now
|
|
1307
|
+
};
|
|
1308
|
+
this.conversations.unshift(conv);
|
|
1309
|
+
await this.storage.createConversation(conversationId, "").catch(() => {
|
|
1310
|
+
});
|
|
1311
|
+
}
|
|
1312
|
+
const lastMsg = this.messages[this.messages.length - 1];
|
|
1313
|
+
if (lastMsg?.role === "user" && !lastMsg.conversationId) {
|
|
1314
|
+
lastMsg.conversationId = conversationId;
|
|
1315
|
+
await this.storage.addMessage(lastMsg, conversationId).catch(() => {
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
765
1318
|
const messageId = job.message_id;
|
|
766
1319
|
this.lastSeq = -1;
|
|
767
1320
|
const stream = this.client.streamJobEvents(
|
|
@@ -778,7 +1331,8 @@ var ChatSession = class {
|
|
|
778
1331
|
);
|
|
779
1332
|
}
|
|
780
1333
|
/**
|
|
781
|
-
* Shared event consumption loop
|
|
1334
|
+
* Shared event consumption loop. Parses each wire event, updates
|
|
1335
|
+
* minimal session state, and emits typed ChatEvents to consumers.
|
|
782
1336
|
*/
|
|
783
1337
|
async consumeEventStream(stream, conversationId, messageId, executeClientTools) {
|
|
784
1338
|
for await (const raw of stream) {
|
|
@@ -798,419 +1352,112 @@ var ChatSession = class {
|
|
|
798
1352
|
} catch {
|
|
799
1353
|
continue;
|
|
800
1354
|
}
|
|
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
|
-
}
|
|
1355
|
+
await this.dispatchWireEvent(
|
|
1356
|
+
parsed,
|
|
1357
|
+
conversationId,
|
|
1358
|
+
messageId,
|
|
1359
|
+
executeClientTools
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
async dispatchWireEvent(wire, conversationId, messageId, executeClientTools) {
|
|
1364
|
+
this.applyWireSideEffects(wire, conversationId, messageId);
|
|
1365
|
+
const event = translateWireEvent(wire);
|
|
1366
|
+
if (event) {
|
|
1367
|
+
this.emit(event);
|
|
1368
|
+
}
|
|
1369
|
+
if (executeClientTools && wire.type === "block_stop" && wire.status === "awaiting_client_result" && wire.final?.call_id) {
|
|
1370
|
+
const f = wire.final;
|
|
1371
|
+
const request = {
|
|
1372
|
+
callId: f.call_id ?? "",
|
|
1373
|
+
toolName: f.tool_name ?? "",
|
|
1374
|
+
arguments: f.input ?? {},
|
|
1375
|
+
isClientTool: true
|
|
1376
|
+
};
|
|
1377
|
+
const results = await this.executeClientTools([request]);
|
|
1378
|
+
await this.client.submitToolResult({
|
|
1379
|
+
conversation_id: conversationId,
|
|
1380
|
+
message_id: messageId,
|
|
1381
|
+
tool_results: results
|
|
1382
|
+
});
|
|
895
1383
|
}
|
|
896
1384
|
}
|
|
897
1385
|
/**
|
|
898
|
-
*
|
|
899
|
-
*
|
|
1386
|
+
* State mutations driven by wire events. Kept separate from translation so
|
|
1387
|
+
* the pure wire → ChatEvent mapping can live in translate.ts and be reused
|
|
1388
|
+
* by the replay path.
|
|
1389
|
+
*
|
|
1390
|
+
* ``messageId`` is the server-assigned assistant message id for the current
|
|
1391
|
+
* turn; empty in the reconnect and conversation-switch replay paths where
|
|
1392
|
+
* messages have already been loaded from REST and shouldn't be re-pushed.
|
|
900
1393
|
*/
|
|
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
|
-
}
|
|
1394
|
+
applyWireSideEffects(wire, conversationId, messageId) {
|
|
1395
|
+
switch (wire.type) {
|
|
922
1396
|
case "message_start":
|
|
923
|
-
|
|
924
|
-
|
|
1397
|
+
this.resetStreamingState();
|
|
1398
|
+
if (wire.model) {
|
|
1399
|
+
this.modelDisplayName = wire.model;
|
|
925
1400
|
}
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1401
|
+
return;
|
|
1402
|
+
case "block_start":
|
|
1403
|
+
if (wire.kind === "text" && (!wire.parent_path || wire.parent_path.length === 0)) {
|
|
1404
|
+
this.currentTextPath = wire.path;
|
|
929
1405
|
}
|
|
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";
|
|
1406
|
+
return;
|
|
1407
|
+
case "block_delta":
|
|
1408
|
+
if (wire.delta.channel === "text" && this.currentTextPath !== null && pathEquals(this.currentTextPath, wire.path)) {
|
|
1409
|
+
this.accumulatedText += wire.delta.text;
|
|
994
1410
|
}
|
|
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;
|
|
1411
|
+
return;
|
|
1412
|
+
case "block_stop":
|
|
1413
|
+
if (this.currentTextPath !== null && pathEquals(this.currentTextPath, wire.path)) {
|
|
1414
|
+
this.currentTextPath = null;
|
|
1039
1415
|
}
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1416
|
+
return;
|
|
1417
|
+
case "message_stop":
|
|
1418
|
+
if (messageId) {
|
|
1419
|
+
const assistantMessage = {
|
|
1420
|
+
id: messageId,
|
|
1421
|
+
conversationId,
|
|
1422
|
+
role: "assistant",
|
|
1423
|
+
content: this.accumulatedText,
|
|
1424
|
+
status: "complete",
|
|
1425
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1426
|
+
};
|
|
1427
|
+
this.messages.push(assistantMessage);
|
|
1428
|
+
this.storage.addMessage(assistantMessage, conversationId).catch(() => {
|
|
1429
|
+
});
|
|
1052
1430
|
}
|
|
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;
|
|
1431
|
+
this.isStreaming = false;
|
|
1432
|
+
this.currentJobId = null;
|
|
1433
|
+
return;
|
|
1434
|
+
case "custom":
|
|
1435
|
+
if (wire.name === "title_generated") {
|
|
1436
|
+
const title = wire.data.title ?? "";
|
|
1437
|
+
if (this.conversationId && title) {
|
|
1438
|
+
const conv = this.conversations.find(
|
|
1439
|
+
(c) => c.id === this.conversationId
|
|
1440
|
+
);
|
|
1441
|
+
if (conv) {
|
|
1442
|
+
conv.title = title;
|
|
1443
|
+
}
|
|
1444
|
+
this.storage.updateConversationTitle(this.conversationId, title).catch(() => {
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
return;
|
|
1449
|
+
default:
|
|
1450
|
+
return;
|
|
1161
1451
|
}
|
|
1162
1452
|
}
|
|
1163
1453
|
async executeClientTools(toolCalls) {
|
|
1164
1454
|
const results = [];
|
|
1165
1455
|
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
1456
|
const result = await this.toolRegistry.executeTool(call);
|
|
1173
1457
|
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
1458
|
}
|
|
1183
|
-
this.executingTool = null;
|
|
1184
1459
|
return results;
|
|
1185
1460
|
}
|
|
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
1461
|
/**
|
|
1215
1462
|
* Load conversation context (messages) without replaying events.
|
|
1216
1463
|
* Used before reconnectToJob — SSE replay handles event replay.
|
|
@@ -1218,13 +1465,11 @@ var ChatSession = class {
|
|
|
1218
1465
|
async loadConversation(id) {
|
|
1219
1466
|
this.conversationId = id;
|
|
1220
1467
|
this.resetStreamingState();
|
|
1221
|
-
this.blockBuilder.reset();
|
|
1222
1468
|
this.messages = await this.client.getMessages(id).catch(() => this.storage.fetchMessages(id));
|
|
1223
1469
|
}
|
|
1224
1470
|
/**
|
|
1225
1471
|
* Reconnect to a running job's SSE stream (e.g. after page reload).
|
|
1226
1472
|
* Replays all events from the beginning and continues live.
|
|
1227
|
-
* Does NOT reset BlockBuilder — caller controls reset.
|
|
1228
1473
|
*/
|
|
1229
1474
|
async reconnectToJob(jobId) {
|
|
1230
1475
|
if (this.isStreaming) return;
|
|
@@ -1249,23 +1494,21 @@ var ChatSession = class {
|
|
|
1249
1494
|
} catch (err) {
|
|
1250
1495
|
this.emit({
|
|
1251
1496
|
type: "error",
|
|
1252
|
-
|
|
1497
|
+
code: "connection_error",
|
|
1498
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1499
|
+
blockPath: null
|
|
1253
1500
|
});
|
|
1254
1501
|
} finally {
|
|
1255
1502
|
this.isStreaming = false;
|
|
1256
|
-
this.executingTool = null;
|
|
1257
1503
|
this.abortController = null;
|
|
1258
1504
|
}
|
|
1259
1505
|
}
|
|
1260
|
-
/** Detach from the SSE stream without cancelling the job.
|
|
1261
|
-
* The backend job keeps running — caller can reconnect later. */
|
|
1506
|
+
/** Detach from the SSE stream without cancelling the job. */
|
|
1262
1507
|
detach() {
|
|
1263
1508
|
this.abortController?.abort();
|
|
1264
1509
|
this.abortController = null;
|
|
1265
1510
|
this.isStreaming = false;
|
|
1266
|
-
this.
|
|
1267
|
-
this.executingTool = null;
|
|
1268
|
-
this.blockBuilder.reset();
|
|
1511
|
+
this.resetStreamingState();
|
|
1269
1512
|
this.emit({ type: "disconnected" });
|
|
1270
1513
|
}
|
|
1271
1514
|
/** Stop the job and disconnect (explicit user action). */
|
|
@@ -1276,6 +1519,7 @@ var ChatSession = class {
|
|
|
1276
1519
|
}
|
|
1277
1520
|
this.detach();
|
|
1278
1521
|
this.currentJobId = null;
|
|
1522
|
+
this.protocols.clear();
|
|
1279
1523
|
}
|
|
1280
1524
|
async createNewConversation() {
|
|
1281
1525
|
const id = generateId();
|
|
@@ -1286,21 +1530,39 @@ var ChatSession = class {
|
|
|
1286
1530
|
this.conversations.unshift(conversation);
|
|
1287
1531
|
this.conversationId = id;
|
|
1288
1532
|
this.messages = [];
|
|
1289
|
-
this.streamingContent = "";
|
|
1290
1533
|
return id;
|
|
1291
1534
|
}
|
|
1292
|
-
async switchConversation(id, jobId) {
|
|
1535
|
+
async switchConversation(id, jobId, userMessageContent) {
|
|
1293
1536
|
this.conversationId = id;
|
|
1294
1537
|
this.resetStreamingState();
|
|
1295
|
-
this.blockBuilder.reset();
|
|
1296
1538
|
const [messagesResult, eventsResult] = await Promise.allSettled([
|
|
1297
1539
|
this.client.getMessages(id).catch(() => this.storage.fetchMessages(id)),
|
|
1298
1540
|
this.client.getConversationEvents(id, jobId)
|
|
1299
1541
|
]);
|
|
1300
1542
|
this.messages = messagesResult.status === "fulfilled" ? messagesResult.value : [];
|
|
1301
1543
|
if (eventsResult.status === "fulfilled") {
|
|
1544
|
+
let userMessageEmitted = !userMessageContent;
|
|
1302
1545
|
for (const ev of eventsResult.value) {
|
|
1303
|
-
|
|
1546
|
+
const type = ev.data.type || ev.event;
|
|
1547
|
+
if (!type || type === "done") continue;
|
|
1548
|
+
if (!userMessageEmitted && type === "message_start") {
|
|
1549
|
+
this.emit({
|
|
1550
|
+
type: "user_message",
|
|
1551
|
+
content: userMessageContent
|
|
1552
|
+
});
|
|
1553
|
+
userMessageEmitted = true;
|
|
1554
|
+
}
|
|
1555
|
+
const wire = { ...ev.data, type };
|
|
1556
|
+
try {
|
|
1557
|
+
await this.dispatchWireEvent(
|
|
1558
|
+
wire,
|
|
1559
|
+
id,
|
|
1560
|
+
"",
|
|
1561
|
+
false
|
|
1562
|
+
// don't execute client tools on replay
|
|
1563
|
+
);
|
|
1564
|
+
} catch {
|
|
1565
|
+
}
|
|
1304
1566
|
}
|
|
1305
1567
|
}
|
|
1306
1568
|
}
|
|
@@ -1326,456 +1588,46 @@ var ChatSession = class {
|
|
|
1326
1588
|
}
|
|
1327
1589
|
};
|
|
1328
1590
|
|
|
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
1591
|
// src/types.ts
|
|
1743
1592
|
var ChatEventType = {
|
|
1593
|
+
// Connection lifecycle (SDK-local, not wire)
|
|
1744
1594
|
Connected: "connected",
|
|
1745
|
-
|
|
1595
|
+
Disconnected: "disconnected",
|
|
1596
|
+
// Turn lifecycle
|
|
1597
|
+
MessageStart: "message_start",
|
|
1598
|
+
MessageStop: "message_stop",
|
|
1599
|
+
// Block lifecycle
|
|
1600
|
+
BlockStart: "block_start",
|
|
1601
|
+
BlockDelta: "block_delta",
|
|
1602
|
+
BlockStop: "block_stop",
|
|
1603
|
+
// Reliability
|
|
1604
|
+
Stall: "stall",
|
|
1605
|
+
Retry: "retry",
|
|
1606
|
+
Error: "error",
|
|
1607
|
+
Keepalive: "keepalive",
|
|
1608
|
+
// Conversation-level (typed custom events)
|
|
1746
1609
|
UserMessage: "user_message",
|
|
1747
1610
|
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
1611
|
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
1612
|
ContextUpdate: "context_update",
|
|
1613
|
+
SubagentStart: "subagent_start",
|
|
1614
|
+
SubagentStop: "subagent_stop",
|
|
1615
|
+
ContextWarning: "context_warning",
|
|
1616
|
+
MemoryRecall: "memory_recall",
|
|
1617
|
+
MemoryUpdate: "memory_update",
|
|
1776
1618
|
DesktopStream: "desktop_stream",
|
|
1777
1619
|
AttachmentStaged: "attachment_staged",
|
|
1778
|
-
WorkspaceReady: "workspace_ready"
|
|
1620
|
+
WorkspaceReady: "workspace_ready",
|
|
1621
|
+
AssetCreated: "asset_created",
|
|
1622
|
+
ToolApprovalRequested: "tool_approval_requested",
|
|
1623
|
+
ToolApprovalGranted: "tool_approval_granted",
|
|
1624
|
+
ToolPermissionDenied: "tool_permission_denied",
|
|
1625
|
+
ToolHarnessWarning: "tool_harness_warning",
|
|
1626
|
+
UserUnavailable: "user_unavailable",
|
|
1627
|
+
PromptSuggestion: "prompt_suggestion",
|
|
1628
|
+
StateChanged: "state_changed",
|
|
1629
|
+
// Generic fallthrough for unknown custom events
|
|
1630
|
+
Custom: "custom"
|
|
1779
1631
|
};
|
|
1780
1632
|
|
|
1781
1633
|
// src/stream-manager.ts
|
|
@@ -1830,22 +1682,12 @@ var StreamManager = class {
|
|
|
1830
1682
|
}
|
|
1831
1683
|
onSessionEvent(event) {
|
|
1832
1684
|
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
1685
|
this.emit({
|
|
1844
1686
|
type: "event",
|
|
1845
1687
|
conversationId: convId,
|
|
1846
1688
|
event
|
|
1847
1689
|
});
|
|
1848
|
-
if (event.type === ChatEventType.
|
|
1690
|
+
if (event.type === ChatEventType.MessageStop) {
|
|
1849
1691
|
if (this._state === "streaming") {
|
|
1850
1692
|
this.setState("idle");
|
|
1851
1693
|
}
|
|
@@ -1858,13 +1700,13 @@ var StreamManager = class {
|
|
|
1858
1700
|
const id = await this.session.createNewConversation();
|
|
1859
1701
|
this.setActiveConversation(id);
|
|
1860
1702
|
}
|
|
1861
|
-
this.prepareUserBlock(content);
|
|
1862
1703
|
this.setState("streaming");
|
|
1863
1704
|
try {
|
|
1864
1705
|
await this.session.send(content, {
|
|
1865
1706
|
enableSearch: options?.enableSearch,
|
|
1866
1707
|
agentName: options?.agentName,
|
|
1867
|
-
uploadIds: options?.uploadIds
|
|
1708
|
+
uploadIds: options?.uploadIds,
|
|
1709
|
+
planMode: options?.planMode
|
|
1868
1710
|
});
|
|
1869
1711
|
} catch {
|
|
1870
1712
|
}
|
|
@@ -1878,7 +1720,6 @@ var StreamManager = class {
|
|
|
1878
1720
|
);
|
|
1879
1721
|
const lastUserMsg = userMsgs[userMsgs.length - 1];
|
|
1880
1722
|
if (!lastUserMsg) return;
|
|
1881
|
-
this.prepareUserBlock(lastUserMsg.content);
|
|
1882
1723
|
this.setState("streaming");
|
|
1883
1724
|
try {
|
|
1884
1725
|
await this.session.resendFromCheckpoint(
|
|
@@ -1942,14 +1783,6 @@ var StreamManager = class {
|
|
|
1942
1783
|
this.handlers = [];
|
|
1943
1784
|
}
|
|
1944
1785
|
// ── 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
1786
|
finalizeStream() {
|
|
1954
1787
|
if (this._state === "streaming") {
|
|
1955
1788
|
this.setState("idle");
|
|
@@ -1960,8 +1793,8 @@ var StreamManager = class {
|
|
|
1960
1793
|
this.setState("restoring");
|
|
1961
1794
|
let activeJobId = null;
|
|
1962
1795
|
try {
|
|
1963
|
-
const res = await this.session.client.
|
|
1964
|
-
activeJobId = res.
|
|
1796
|
+
const res = await this.session.client.getActiveJob(conversationId);
|
|
1797
|
+
activeJobId = res.jobId;
|
|
1965
1798
|
} catch {
|
|
1966
1799
|
}
|
|
1967
1800
|
if (activeJobId) {
|
|
@@ -1981,15 +1814,19 @@ var StreamManager = class {
|
|
|
1981
1814
|
const completedJobs = jobs.filter(
|
|
1982
1815
|
(j) => j.status === "completed"
|
|
1983
1816
|
);
|
|
1984
|
-
|
|
1985
|
-
|
|
1817
|
+
const userMessages = this.session.messages.filter(
|
|
1818
|
+
(m) => m.role === "user"
|
|
1819
|
+
);
|
|
1820
|
+
for (let i = 0; i < completedJobs.length; i++) {
|
|
1821
|
+
const job = completedJobs[i];
|
|
1822
|
+
const userContent = userMessages[i]?.content;
|
|
1823
|
+
await this.session.switchConversation(
|
|
1824
|
+
conversationId,
|
|
1825
|
+
job.job_id,
|
|
1826
|
+
userContent
|
|
1827
|
+
);
|
|
1986
1828
|
}
|
|
1987
1829
|
if (completedJobs.length > 0) {
|
|
1988
|
-
this.emit({
|
|
1989
|
-
type: "blocksChanged",
|
|
1990
|
-
conversationId,
|
|
1991
|
-
blocks: this.session.blockBuilder.getBlocks()
|
|
1992
|
-
});
|
|
1993
1830
|
this.emit({
|
|
1994
1831
|
type: "versionsReady",
|
|
1995
1832
|
conversationId,
|
|
@@ -2007,24 +1844,94 @@ var StreamManager = class {
|
|
|
2007
1844
|
this.emit({ type: "conversationChanged", conversationId: id });
|
|
2008
1845
|
}
|
|
2009
1846
|
};
|
|
1847
|
+
|
|
1848
|
+
// src/replay.ts
|
|
1849
|
+
function toWireEvent(raw) {
|
|
1850
|
+
const type = raw.data.type || raw.event;
|
|
1851
|
+
if (!type || type === "done") return null;
|
|
1852
|
+
return { ...raw.data, type };
|
|
1853
|
+
}
|
|
1854
|
+
function mapSseToChat(raw) {
|
|
1855
|
+
const wire = toWireEvent(raw);
|
|
1856
|
+
if (!wire) return [];
|
|
1857
|
+
const event = translateWireEvent(wire);
|
|
1858
|
+
return event ? [event] : [];
|
|
1859
|
+
}
|
|
1860
|
+
function replayEvents(sseEvents, userMessages, handleEvent, addBlock) {
|
|
1861
|
+
const userMsgs = userMessages.filter((m) => m.role === "user");
|
|
1862
|
+
let userIdx = 0;
|
|
1863
|
+
let expectingUserMessage = true;
|
|
1864
|
+
for (const raw of sseEvents) {
|
|
1865
|
+
const type = raw.data.type || raw.event;
|
|
1866
|
+
if (type === "message_stop") {
|
|
1867
|
+
expectingUserMessage = true;
|
|
1868
|
+
}
|
|
1869
|
+
if (type === "message_start" && expectingUserMessage && userIdx < userMsgs.length) {
|
|
1870
|
+
const userMsg = userMsgs[userIdx];
|
|
1871
|
+
addBlock({
|
|
1872
|
+
type: "user",
|
|
1873
|
+
id: `replay_user_${userIdx}`,
|
|
1874
|
+
content: userMsg.content
|
|
1875
|
+
});
|
|
1876
|
+
userIdx++;
|
|
1877
|
+
expectingUserMessage = false;
|
|
1878
|
+
}
|
|
1879
|
+
for (const ce of mapSseToChat(raw)) {
|
|
1880
|
+
handleEvent(ce);
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
// src/embedded-resource.ts
|
|
1886
|
+
function isEmbeddedResource(value) {
|
|
1887
|
+
return typeof value === "object" && value !== null && value._embedded_resource === true;
|
|
1888
|
+
}
|
|
1889
|
+
function parseEmbeddedResource(value) {
|
|
1890
|
+
let candidate = value;
|
|
1891
|
+
if (typeof candidate === "string") {
|
|
1892
|
+
const trimmed = candidate.trim();
|
|
1893
|
+
if (!trimmed.startsWith("{")) return null;
|
|
1894
|
+
try {
|
|
1895
|
+
candidate = JSON.parse(trimmed);
|
|
1896
|
+
} catch {
|
|
1897
|
+
return null;
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
if (!isEmbeddedResource(candidate)) return null;
|
|
1901
|
+
const mimeType = candidate.mime_type;
|
|
1902
|
+
const uri = candidate.uri;
|
|
1903
|
+
const payload = candidate.payload;
|
|
1904
|
+
if (typeof mimeType !== "string" || !mimeType) return null;
|
|
1905
|
+
if (typeof uri !== "string" || !uri) return null;
|
|
1906
|
+
if (!payload || typeof payload !== "object") return null;
|
|
1907
|
+
return {
|
|
1908
|
+
mimeType,
|
|
1909
|
+
uri,
|
|
1910
|
+
payload
|
|
1911
|
+
};
|
|
1912
|
+
}
|
|
2010
1913
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2011
1914
|
0 && (module.exports = {
|
|
2012
1915
|
AstralformClient,
|
|
2013
1916
|
AstralformError,
|
|
2014
1917
|
AuthenticationError,
|
|
2015
|
-
BlockBuilder,
|
|
2016
1918
|
ChatEventType,
|
|
2017
1919
|
ChatSession,
|
|
2018
1920
|
ConnectionError,
|
|
2019
1921
|
InMemoryStorage,
|
|
2020
1922
|
LLMNotConfiguredError,
|
|
1923
|
+
ProtocolRegistry,
|
|
2021
1924
|
RateLimitError,
|
|
2022
1925
|
ServerError,
|
|
2023
1926
|
StreamAbortedError,
|
|
2024
1927
|
StreamManager,
|
|
2025
1928
|
ToolRegistry,
|
|
2026
1929
|
generateId,
|
|
2027
|
-
|
|
2028
|
-
|
|
1930
|
+
isEmbeddedResource,
|
|
1931
|
+
mapSseToChat,
|
|
1932
|
+
parseEmbeddedResource,
|
|
1933
|
+
replayEvents,
|
|
1934
|
+
streamJobSSE,
|
|
1935
|
+
translateDelta
|
|
2029
1936
|
});
|
|
2030
1937
|
//# sourceMappingURL=index.cjs.map
|