@astralform/js 0.2.2 → 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 +931 -954
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +707 -500
- package/dist/index.d.ts +707 -500
- package/dist/index.js +924 -951
- 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,385 +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 "asset_created":
|
|
1096
|
-
this.emit({
|
|
1097
|
-
type: "asset_created",
|
|
1098
|
-
assetId: event.asset_id,
|
|
1099
|
-
name: event.name,
|
|
1100
|
-
url: event.url,
|
|
1101
|
-
mediaType: event.media_type,
|
|
1102
|
-
sizeBytes: event.size_bytes
|
|
1103
|
-
});
|
|
1104
|
-
break;
|
|
1105
|
-
case "editor_content_start":
|
|
1106
|
-
this.emit({
|
|
1107
|
-
type: "editor_content_start",
|
|
1108
|
-
callId: event.call_id,
|
|
1109
|
-
path: event.path,
|
|
1110
|
-
language: event.language
|
|
1111
|
-
});
|
|
1112
|
-
break;
|
|
1113
|
-
case "editor_content_delta":
|
|
1114
|
-
this.emit({
|
|
1115
|
-
type: "editor_content_delta",
|
|
1116
|
-
callId: event.call_id,
|
|
1117
|
-
path: event.path,
|
|
1118
|
-
delta: event.delta
|
|
1119
|
-
});
|
|
1120
|
-
break;
|
|
1121
|
-
case "editor_content_end":
|
|
1122
|
-
this.emit({
|
|
1123
|
-
type: "editor_content_end",
|
|
1124
|
-
callId: event.call_id
|
|
1125
|
-
});
|
|
1126
|
-
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;
|
|
1127
1451
|
}
|
|
1128
1452
|
}
|
|
1129
1453
|
async executeClientTools(toolCalls) {
|
|
1130
1454
|
const results = [];
|
|
1131
1455
|
for (const call of toolCalls) {
|
|
1132
|
-
this.executingTool = call.toolName;
|
|
1133
|
-
const toolState = this.activeTools.get(call.callId);
|
|
1134
|
-
if (toolState) {
|
|
1135
|
-
toolState.status = "executing";
|
|
1136
|
-
}
|
|
1137
|
-
this.emit({ type: "tool_executing", name: call.toolName });
|
|
1138
1456
|
const result = await this.toolRegistry.executeTool(call);
|
|
1139
1457
|
results.push(result);
|
|
1140
|
-
if (toolState) {
|
|
1141
|
-
toolState.status = "completed";
|
|
1142
|
-
}
|
|
1143
|
-
this.emit({
|
|
1144
|
-
type: "tool_completed",
|
|
1145
|
-
name: call.toolName,
|
|
1146
|
-
result: result.result
|
|
1147
|
-
});
|
|
1148
1458
|
}
|
|
1149
|
-
this.executingTool = null;
|
|
1150
1459
|
return results;
|
|
1151
1460
|
}
|
|
1152
|
-
async completeStream(conversationId, messageId, title, metrics, jobId) {
|
|
1153
|
-
const assistantMessage = {
|
|
1154
|
-
id: messageId || generateId(),
|
|
1155
|
-
conversationId,
|
|
1156
|
-
role: "assistant",
|
|
1157
|
-
content: this.streamingContent,
|
|
1158
|
-
status: "complete",
|
|
1159
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1160
|
-
};
|
|
1161
|
-
this.messages.push(assistantMessage);
|
|
1162
|
-
await this.storage.addMessage(assistantMessage, conversationId);
|
|
1163
|
-
if (title && conversationId) {
|
|
1164
|
-
await this.storage.updateConversationTitle(conversationId, title);
|
|
1165
|
-
const conv = this.conversations.find((c) => c.id === conversationId);
|
|
1166
|
-
if (conv) {
|
|
1167
|
-
conv.title = title;
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
this.emit({
|
|
1171
|
-
type: "complete",
|
|
1172
|
-
content: this.streamingContent,
|
|
1173
|
-
conversationId,
|
|
1174
|
-
messageId: assistantMessage.id,
|
|
1175
|
-
title,
|
|
1176
|
-
metrics,
|
|
1177
|
-
job_id: jobId
|
|
1178
|
-
});
|
|
1179
|
-
}
|
|
1180
1461
|
/**
|
|
1181
1462
|
* Load conversation context (messages) without replaying events.
|
|
1182
1463
|
* Used before reconnectToJob — SSE replay handles event replay.
|
|
@@ -1184,13 +1465,11 @@ var ChatSession = class {
|
|
|
1184
1465
|
async loadConversation(id) {
|
|
1185
1466
|
this.conversationId = id;
|
|
1186
1467
|
this.resetStreamingState();
|
|
1187
|
-
this.blockBuilder.reset();
|
|
1188
1468
|
this.messages = await this.client.getMessages(id).catch(() => this.storage.fetchMessages(id));
|
|
1189
1469
|
}
|
|
1190
1470
|
/**
|
|
1191
1471
|
* Reconnect to a running job's SSE stream (e.g. after page reload).
|
|
1192
1472
|
* Replays all events from the beginning and continues live.
|
|
1193
|
-
* Does NOT reset BlockBuilder — caller controls reset.
|
|
1194
1473
|
*/
|
|
1195
1474
|
async reconnectToJob(jobId) {
|
|
1196
1475
|
if (this.isStreaming) return;
|
|
@@ -1215,23 +1494,21 @@ var ChatSession = class {
|
|
|
1215
1494
|
} catch (err) {
|
|
1216
1495
|
this.emit({
|
|
1217
1496
|
type: "error",
|
|
1218
|
-
|
|
1497
|
+
code: "connection_error",
|
|
1498
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1499
|
+
blockPath: null
|
|
1219
1500
|
});
|
|
1220
1501
|
} finally {
|
|
1221
1502
|
this.isStreaming = false;
|
|
1222
|
-
this.executingTool = null;
|
|
1223
1503
|
this.abortController = null;
|
|
1224
1504
|
}
|
|
1225
1505
|
}
|
|
1226
|
-
/** Detach from the SSE stream without cancelling the job.
|
|
1227
|
-
* The backend job keeps running — caller can reconnect later. */
|
|
1506
|
+
/** Detach from the SSE stream without cancelling the job. */
|
|
1228
1507
|
detach() {
|
|
1229
1508
|
this.abortController?.abort();
|
|
1230
1509
|
this.abortController = null;
|
|
1231
1510
|
this.isStreaming = false;
|
|
1232
|
-
this.
|
|
1233
|
-
this.executingTool = null;
|
|
1234
|
-
this.blockBuilder.reset();
|
|
1511
|
+
this.resetStreamingState();
|
|
1235
1512
|
this.emit({ type: "disconnected" });
|
|
1236
1513
|
}
|
|
1237
1514
|
/** Stop the job and disconnect (explicit user action). */
|
|
@@ -1242,6 +1519,7 @@ var ChatSession = class {
|
|
|
1242
1519
|
}
|
|
1243
1520
|
this.detach();
|
|
1244
1521
|
this.currentJobId = null;
|
|
1522
|
+
this.protocols.clear();
|
|
1245
1523
|
}
|
|
1246
1524
|
async createNewConversation() {
|
|
1247
1525
|
const id = generateId();
|
|
@@ -1252,21 +1530,39 @@ var ChatSession = class {
|
|
|
1252
1530
|
this.conversations.unshift(conversation);
|
|
1253
1531
|
this.conversationId = id;
|
|
1254
1532
|
this.messages = [];
|
|
1255
|
-
this.streamingContent = "";
|
|
1256
1533
|
return id;
|
|
1257
1534
|
}
|
|
1258
|
-
async switchConversation(id, jobId) {
|
|
1535
|
+
async switchConversation(id, jobId, userMessageContent) {
|
|
1259
1536
|
this.conversationId = id;
|
|
1260
1537
|
this.resetStreamingState();
|
|
1261
|
-
this.blockBuilder.reset();
|
|
1262
1538
|
const [messagesResult, eventsResult] = await Promise.allSettled([
|
|
1263
1539
|
this.client.getMessages(id).catch(() => this.storage.fetchMessages(id)),
|
|
1264
1540
|
this.client.getConversationEvents(id, jobId)
|
|
1265
1541
|
]);
|
|
1266
1542
|
this.messages = messagesResult.status === "fulfilled" ? messagesResult.value : [];
|
|
1267
1543
|
if (eventsResult.status === "fulfilled") {
|
|
1544
|
+
let userMessageEmitted = !userMessageContent;
|
|
1268
1545
|
for (const ev of eventsResult.value) {
|
|
1269
|
-
|
|
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
|
+
}
|
|
1270
1566
|
}
|
|
1271
1567
|
}
|
|
1272
1568
|
}
|
|
@@ -1292,420 +1588,46 @@ var ChatSession = class {
|
|
|
1292
1588
|
}
|
|
1293
1589
|
};
|
|
1294
1590
|
|
|
1295
|
-
// src/standard-handlers.ts
|
|
1296
|
-
function finalizeText(builder) {
|
|
1297
|
-
if (builder.activeTextId) {
|
|
1298
|
-
builder.patchBlock(builder.activeTextId, {
|
|
1299
|
-
isStreaming: false
|
|
1300
|
-
});
|
|
1301
|
-
builder.activeTextId = null;
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
function finalizeThinking(builder) {
|
|
1305
|
-
if (builder.activeThinkingId) {
|
|
1306
|
-
builder.patchBlock(builder.activeThinkingId, {
|
|
1307
|
-
isActive: false
|
|
1308
|
-
});
|
|
1309
|
-
builder.activeThinkingId = null;
|
|
1310
|
-
builder.thinkingStartMs = null;
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
function finalizeEditor(builder) {
|
|
1314
|
-
if (builder.activeEditorId) {
|
|
1315
|
-
builder.patchBlock(builder.activeEditorId, {
|
|
1316
|
-
isStreaming: false
|
|
1317
|
-
});
|
|
1318
|
-
builder.activeEditorId = null;
|
|
1319
|
-
}
|
|
1320
|
-
}
|
|
1321
|
-
var handleUserMessage = (event, builder) => {
|
|
1322
|
-
const e = event;
|
|
1323
|
-
const existing = builder.findBlock((b) => b.type === "user");
|
|
1324
|
-
if (existing) {
|
|
1325
|
-
if (e.createdAt) {
|
|
1326
|
-
builder.patchBlock(existing.id, {
|
|
1327
|
-
createdAt: e.createdAt
|
|
1328
|
-
});
|
|
1329
|
-
}
|
|
1330
|
-
return;
|
|
1331
|
-
}
|
|
1332
|
-
builder.addBlock({
|
|
1333
|
-
type: "user",
|
|
1334
|
-
id: builder.nextId(),
|
|
1335
|
-
content: e.content,
|
|
1336
|
-
createdAt: e.createdAt
|
|
1337
|
-
});
|
|
1338
|
-
};
|
|
1339
|
-
var handleChunk = (event, builder) => {
|
|
1340
|
-
const e = event;
|
|
1341
|
-
if (!builder.activeTextId) {
|
|
1342
|
-
const id = builder.nextId();
|
|
1343
|
-
builder.activeTextId = id;
|
|
1344
|
-
builder.addBlock({
|
|
1345
|
-
type: "text",
|
|
1346
|
-
id,
|
|
1347
|
-
content: e.text,
|
|
1348
|
-
isStreaming: true
|
|
1349
|
-
});
|
|
1350
|
-
} else {
|
|
1351
|
-
const id = builder.activeTextId;
|
|
1352
|
-
const existing = builder.findBlock((b) => b.id === id);
|
|
1353
|
-
if (existing && existing.type === "text") {
|
|
1354
|
-
builder.patchBlock(id, {
|
|
1355
|
-
content: existing.content + e.text
|
|
1356
|
-
});
|
|
1357
|
-
}
|
|
1358
|
-
}
|
|
1359
|
-
};
|
|
1360
|
-
var handleToolCall = (event, builder) => {
|
|
1361
|
-
const e = event;
|
|
1362
|
-
finalizeText(builder);
|
|
1363
|
-
builder.addBlock({
|
|
1364
|
-
type: "tool",
|
|
1365
|
-
id: builder.nextId(),
|
|
1366
|
-
callId: e.request.callId,
|
|
1367
|
-
toolName: e.request.toolName,
|
|
1368
|
-
displayName: e.request.displayName,
|
|
1369
|
-
description: e.request.description,
|
|
1370
|
-
arguments: e.request.arguments,
|
|
1371
|
-
toolCategory: e.request.toolCategory,
|
|
1372
|
-
iconUrl: e.request.iconUrl,
|
|
1373
|
-
status: "calling"
|
|
1374
|
-
});
|
|
1375
|
-
};
|
|
1376
|
-
var handleToolExecuting = (event, builder) => {
|
|
1377
|
-
const e = event;
|
|
1378
|
-
const block = builder.findBlock(
|
|
1379
|
-
(b) => b.type === "tool" && (e.call_id ? b.callId === e.call_id : b.toolName === e.name) && b.status === "calling"
|
|
1380
|
-
);
|
|
1381
|
-
if (block) {
|
|
1382
|
-
builder.patchBlock(block.id, { status: "executing" });
|
|
1383
|
-
}
|
|
1384
|
-
};
|
|
1385
|
-
var handleToolProgress = (event, builder) => {
|
|
1386
|
-
const e = event;
|
|
1387
|
-
const block = builder.findBlock(
|
|
1388
|
-
(b) => b.type === "tool" && b.callId === e.callId
|
|
1389
|
-
);
|
|
1390
|
-
if (block && block.type === "tool") {
|
|
1391
|
-
const sources = block.sources ? [...block.sources] : [];
|
|
1392
|
-
sources.push(e.item);
|
|
1393
|
-
builder.patchBlock(block.id, {
|
|
1394
|
-
sources,
|
|
1395
|
-
status: "executing"
|
|
1396
|
-
});
|
|
1397
|
-
}
|
|
1398
|
-
};
|
|
1399
|
-
var handleToolEnd = (event, builder) => {
|
|
1400
|
-
const e = event;
|
|
1401
|
-
const callId = e.type === "tool_end" ? e.callId : void 0;
|
|
1402
|
-
const name = e.type === "tool_end" ? e.toolName : e.name;
|
|
1403
|
-
const block = builder.findBlock(
|
|
1404
|
-
(b) => b.type === "tool" && (callId ? b.callId === callId : b.toolName === name) && b.status !== "completed"
|
|
1405
|
-
);
|
|
1406
|
-
if (block) {
|
|
1407
|
-
const toolEnd = e.type === "tool_end" ? e : null;
|
|
1408
|
-
builder.patchBlock(block.id, {
|
|
1409
|
-
status: "completed",
|
|
1410
|
-
...toolEnd?.sources ? { sources: toolEnd.sources } : {},
|
|
1411
|
-
...toolEnd?.durationMs != null ? { durationMs: toolEnd.durationMs } : {},
|
|
1412
|
-
...toolEnd?.result ? { result: toolEnd.result } : {}
|
|
1413
|
-
});
|
|
1414
|
-
}
|
|
1415
|
-
};
|
|
1416
|
-
var handleAgentStart = (event, builder) => {
|
|
1417
|
-
const e = event;
|
|
1418
|
-
finalizeText(builder);
|
|
1419
|
-
builder.addBlock({
|
|
1420
|
-
type: "agent",
|
|
1421
|
-
id: builder.nextId(),
|
|
1422
|
-
agentName: e.agentName,
|
|
1423
|
-
displayName: e.agentDisplayName,
|
|
1424
|
-
avatarUrl: e.avatarUrl
|
|
1425
|
-
});
|
|
1426
|
-
};
|
|
1427
|
-
var handleThinkingDelta = (event, builder) => {
|
|
1428
|
-
const e = event;
|
|
1429
|
-
if (!builder.activeThinkingId) {
|
|
1430
|
-
const id = builder.nextId();
|
|
1431
|
-
builder.activeThinkingId = id;
|
|
1432
|
-
builder.thinkingStartMs = Date.now();
|
|
1433
|
-
builder.addBlock({
|
|
1434
|
-
type: "thinking",
|
|
1435
|
-
id,
|
|
1436
|
-
content: e.text,
|
|
1437
|
-
isActive: true
|
|
1438
|
-
});
|
|
1439
|
-
} else {
|
|
1440
|
-
const id = builder.activeThinkingId;
|
|
1441
|
-
const existing = builder.findBlock((b) => b.id === id);
|
|
1442
|
-
if (existing && existing.type === "thinking") {
|
|
1443
|
-
builder.patchBlock(id, {
|
|
1444
|
-
content: existing.content + e.text
|
|
1445
|
-
});
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
};
|
|
1449
|
-
var handleThinkingComplete = (_event, builder) => {
|
|
1450
|
-
if (builder.activeThinkingId) {
|
|
1451
|
-
const durationMs = builder.thinkingStartMs ? Math.max(0, Date.now() - builder.thinkingStartMs) : void 0;
|
|
1452
|
-
builder.patchBlock(builder.activeThinkingId, {
|
|
1453
|
-
isActive: false,
|
|
1454
|
-
durationMs
|
|
1455
|
-
});
|
|
1456
|
-
builder.activeThinkingId = null;
|
|
1457
|
-
builder.thinkingStartMs = null;
|
|
1458
|
-
}
|
|
1459
|
-
};
|
|
1460
|
-
var handleSubagentStart = (event, builder) => {
|
|
1461
|
-
const e = event;
|
|
1462
|
-
finalizeText(builder);
|
|
1463
|
-
builder.addBlock({
|
|
1464
|
-
type: "subagent",
|
|
1465
|
-
id: builder.nextId(),
|
|
1466
|
-
agentName: e.agentName,
|
|
1467
|
-
displayName: e.displayName,
|
|
1468
|
-
toolCallId: e.toolCallId,
|
|
1469
|
-
avatarUrl: e.avatarUrl,
|
|
1470
|
-
description: e.description,
|
|
1471
|
-
content: "",
|
|
1472
|
-
isActive: true
|
|
1473
|
-
});
|
|
1474
|
-
};
|
|
1475
|
-
var handleSubagentChunk = (event, builder) => {
|
|
1476
|
-
const e = event;
|
|
1477
|
-
const block = builder.findBlock(
|
|
1478
|
-
(b) => b.type === "subagent" && b.toolCallId === e.toolCallId
|
|
1479
|
-
);
|
|
1480
|
-
if (block && block.type === "subagent") {
|
|
1481
|
-
builder.patchBlock(block.id, {
|
|
1482
|
-
content: block.content + e.text
|
|
1483
|
-
});
|
|
1484
|
-
}
|
|
1485
|
-
};
|
|
1486
|
-
var handleSubagentUpdate = (event, builder) => {
|
|
1487
|
-
const e = event;
|
|
1488
|
-
const block = builder.findBlock(
|
|
1489
|
-
(b) => b.type === "subagent" && b.toolCallId === e.toolCallId
|
|
1490
|
-
);
|
|
1491
|
-
if (block) {
|
|
1492
|
-
builder.patchBlock(block.id, {
|
|
1493
|
-
displayName: e.displayName
|
|
1494
|
-
});
|
|
1495
|
-
}
|
|
1496
|
-
};
|
|
1497
|
-
var handleSubagentEnd = (event, builder) => {
|
|
1498
|
-
const e = event;
|
|
1499
|
-
const block = builder.findBlock(
|
|
1500
|
-
(b) => b.type === "subagent" && b.toolCallId === e.toolCallId
|
|
1501
|
-
);
|
|
1502
|
-
if (block) {
|
|
1503
|
-
builder.patchBlock(block.id, {
|
|
1504
|
-
isActive: false
|
|
1505
|
-
});
|
|
1506
|
-
}
|
|
1507
|
-
};
|
|
1508
|
-
var handleComplete = (_event, builder) => {
|
|
1509
|
-
finalizeText(builder);
|
|
1510
|
-
finalizeThinking(builder);
|
|
1511
|
-
finalizeEditor(builder);
|
|
1512
|
-
for (const b of builder.getBlocks()) {
|
|
1513
|
-
if (b.type === "tool" && b.status !== "completed") {
|
|
1514
|
-
builder.patchBlock(b.id, { status: "completed" });
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
};
|
|
1518
|
-
var handleError = (event, builder) => {
|
|
1519
|
-
const e = event;
|
|
1520
|
-
finalizeText(builder);
|
|
1521
|
-
finalizeThinking(builder);
|
|
1522
|
-
builder.addBlock({
|
|
1523
|
-
type: "error",
|
|
1524
|
-
id: builder.nextId(),
|
|
1525
|
-
message: e.error.message
|
|
1526
|
-
});
|
|
1527
|
-
};
|
|
1528
|
-
var handleDisconnected = (_event, builder) => {
|
|
1529
|
-
finalizeText(builder);
|
|
1530
|
-
finalizeThinking(builder);
|
|
1531
|
-
finalizeEditor(builder);
|
|
1532
|
-
};
|
|
1533
|
-
var handleCapsuleOutputChunk = (event, builder) => {
|
|
1534
|
-
const e = event;
|
|
1535
|
-
const block = builder.findBlock(
|
|
1536
|
-
(b) => b.type === "capsule" && b.callId === e.callId
|
|
1537
|
-
);
|
|
1538
|
-
if (block && block.type === "capsule") {
|
|
1539
|
-
builder.patchBlock(block.id, {
|
|
1540
|
-
output: block.output + e.chunk
|
|
1541
|
-
});
|
|
1542
|
-
} else {
|
|
1543
|
-
builder.addBlock({
|
|
1544
|
-
type: "capsule",
|
|
1545
|
-
id: builder.nextId(),
|
|
1546
|
-
callId: e.callId,
|
|
1547
|
-
toolName: "",
|
|
1548
|
-
output: e.chunk,
|
|
1549
|
-
isActive: true
|
|
1550
|
-
});
|
|
1551
|
-
}
|
|
1552
|
-
};
|
|
1553
|
-
var handleCapsuleOutput = (event, builder) => {
|
|
1554
|
-
const e = event;
|
|
1555
|
-
const block = builder.findBlock(
|
|
1556
|
-
(b) => b.type === "capsule" && b.callId === (e.callId ?? "")
|
|
1557
|
-
);
|
|
1558
|
-
if (block) {
|
|
1559
|
-
builder.patchBlock(block.id, {
|
|
1560
|
-
output: e.output,
|
|
1561
|
-
command: e.command,
|
|
1562
|
-
toolName: e.toolName,
|
|
1563
|
-
durationMs: e.durationMs,
|
|
1564
|
-
isActive: false
|
|
1565
|
-
});
|
|
1566
|
-
} else {
|
|
1567
|
-
builder.addBlock({
|
|
1568
|
-
type: "capsule",
|
|
1569
|
-
id: builder.nextId(),
|
|
1570
|
-
callId: e.callId ?? "",
|
|
1571
|
-
toolName: e.toolName,
|
|
1572
|
-
command: e.command,
|
|
1573
|
-
output: e.output,
|
|
1574
|
-
durationMs: e.durationMs,
|
|
1575
|
-
isActive: false
|
|
1576
|
-
});
|
|
1577
|
-
}
|
|
1578
|
-
};
|
|
1579
|
-
var handleAssetCreated = (event, builder) => {
|
|
1580
|
-
const e = event;
|
|
1581
|
-
builder.addBlock({
|
|
1582
|
-
type: "asset",
|
|
1583
|
-
id: builder.nextId(),
|
|
1584
|
-
assetId: e.assetId,
|
|
1585
|
-
name: e.name,
|
|
1586
|
-
url: e.url,
|
|
1587
|
-
mediaType: e.mediaType,
|
|
1588
|
-
sizeBytes: e.sizeBytes
|
|
1589
|
-
});
|
|
1590
|
-
};
|
|
1591
|
-
var handleTodoUpdate = (event, builder) => {
|
|
1592
|
-
const e = event;
|
|
1593
|
-
if (builder.activeTodoId) {
|
|
1594
|
-
builder.patchBlock(builder.activeTodoId, {
|
|
1595
|
-
todos: e.todos
|
|
1596
|
-
});
|
|
1597
|
-
} else {
|
|
1598
|
-
const id = builder.nextId();
|
|
1599
|
-
builder.activeTodoId = id;
|
|
1600
|
-
builder.addBlock({
|
|
1601
|
-
type: "todo",
|
|
1602
|
-
id,
|
|
1603
|
-
todos: e.todos
|
|
1604
|
-
});
|
|
1605
|
-
}
|
|
1606
|
-
};
|
|
1607
|
-
var handleEditorContentStart = (event, builder) => {
|
|
1608
|
-
const e = event;
|
|
1609
|
-
const id = builder.nextId();
|
|
1610
|
-
builder.activeEditorId = id;
|
|
1611
|
-
builder.addBlock({
|
|
1612
|
-
type: "editor",
|
|
1613
|
-
id,
|
|
1614
|
-
callId: e.callId,
|
|
1615
|
-
path: e.path,
|
|
1616
|
-
language: e.language,
|
|
1617
|
-
content: "",
|
|
1618
|
-
isStreaming: true
|
|
1619
|
-
});
|
|
1620
|
-
};
|
|
1621
|
-
var handleEditorContentDelta = (event, builder) => {
|
|
1622
|
-
const e = event;
|
|
1623
|
-
const block = builder.findBlock(
|
|
1624
|
-
(b) => b.type === "editor" && b.callId === e.callId
|
|
1625
|
-
);
|
|
1626
|
-
if (block && block.type === "editor") {
|
|
1627
|
-
builder.patchBlock(block.id, {
|
|
1628
|
-
content: block.content + e.delta
|
|
1629
|
-
});
|
|
1630
|
-
}
|
|
1631
|
-
};
|
|
1632
|
-
var handleEditorContentEnd = (event, builder) => {
|
|
1633
|
-
const e = event;
|
|
1634
|
-
const block = builder.findBlock(
|
|
1635
|
-
(b) => b.type === "editor" && b.callId === e.callId
|
|
1636
|
-
);
|
|
1637
|
-
if (block) {
|
|
1638
|
-
builder.patchBlock(block.id, {
|
|
1639
|
-
isStreaming: false
|
|
1640
|
-
});
|
|
1641
|
-
}
|
|
1642
|
-
builder.activeEditorId = null;
|
|
1643
|
-
};
|
|
1644
|
-
var noop = () => {
|
|
1645
|
-
};
|
|
1646
|
-
var standardHandlers = {
|
|
1647
|
-
user_message: handleUserMessage,
|
|
1648
|
-
chunk: handleChunk,
|
|
1649
|
-
tool_call: handleToolCall,
|
|
1650
|
-
tool_executing: handleToolExecuting,
|
|
1651
|
-
tool_progress: handleToolProgress,
|
|
1652
|
-
tool_completed: handleToolEnd,
|
|
1653
|
-
tool_end: handleToolEnd,
|
|
1654
|
-
agent_start: handleAgentStart,
|
|
1655
|
-
agent_end: noop,
|
|
1656
|
-
thinking_delta: handleThinkingDelta,
|
|
1657
|
-
thinking_complete: handleThinkingComplete,
|
|
1658
|
-
subagent_start: handleSubagentStart,
|
|
1659
|
-
subagent_chunk: handleSubagentChunk,
|
|
1660
|
-
subagent_update: handleSubagentUpdate,
|
|
1661
|
-
subagent_end: handleSubagentEnd,
|
|
1662
|
-
subagent_tool_use: noop,
|
|
1663
|
-
capsule_output: handleCapsuleOutput,
|
|
1664
|
-
capsule_output_chunk: handleCapsuleOutputChunk,
|
|
1665
|
-
asset_created: handleAssetCreated,
|
|
1666
|
-
todo_update: handleTodoUpdate,
|
|
1667
|
-
editor_content_start: handleEditorContentStart,
|
|
1668
|
-
editor_content_delta: handleEditorContentDelta,
|
|
1669
|
-
editor_content_end: handleEditorContentEnd,
|
|
1670
|
-
retry: noop,
|
|
1671
|
-
complete: handleComplete,
|
|
1672
|
-
error: handleError,
|
|
1673
|
-
disconnected: handleDisconnected
|
|
1674
|
-
};
|
|
1675
|
-
|
|
1676
1591
|
// src/types.ts
|
|
1677
1592
|
var ChatEventType = {
|
|
1593
|
+
// Connection lifecycle (SDK-local, not wire)
|
|
1678
1594
|
Connected: "connected",
|
|
1679
|
-
|
|
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)
|
|
1680
1609
|
UserMessage: "user_message",
|
|
1681
1610
|
TitleGenerated: "title_generated",
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
ToolCall: "tool_call",
|
|
1685
|
-
ToolExecuting: "tool_executing",
|
|
1686
|
-
ToolProgress: "tool_progress",
|
|
1687
|
-
ToolCompleted: "tool_completed",
|
|
1688
|
-
ToolEnd: "tool_end",
|
|
1689
|
-
AgentStart: "agent_start",
|
|
1690
|
-
AgentEnd: "agent_end",
|
|
1691
|
-
ThinkingDelta: "thinking_delta",
|
|
1692
|
-
ThinkingComplete: "thinking_complete",
|
|
1611
|
+
TodoUpdate: "todo_update",
|
|
1612
|
+
ContextUpdate: "context_update",
|
|
1693
1613
|
SubagentStart: "subagent_start",
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1614
|
+
SubagentStop: "subagent_stop",
|
|
1615
|
+
ContextWarning: "context_warning",
|
|
1616
|
+
MemoryRecall: "memory_recall",
|
|
1617
|
+
MemoryUpdate: "memory_update",
|
|
1618
|
+
DesktopStream: "desktop_stream",
|
|
1619
|
+
AttachmentStaged: "attachment_staged",
|
|
1620
|
+
WorkspaceReady: "workspace_ready",
|
|
1700
1621
|
AssetCreated: "asset_created",
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
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"
|
|
1709
1631
|
};
|
|
1710
1632
|
|
|
1711
1633
|
// src/stream-manager.ts
|
|
@@ -1760,22 +1682,12 @@ var StreamManager = class {
|
|
|
1760
1682
|
}
|
|
1761
1683
|
onSessionEvent(event) {
|
|
1762
1684
|
const convId = this.session.conversationId;
|
|
1763
|
-
if (event.type === ChatEventType.BlocksChanged) {
|
|
1764
|
-
if (this._state === "streaming" && convId) {
|
|
1765
|
-
this.emit({
|
|
1766
|
-
type: "blocksChanged",
|
|
1767
|
-
conversationId: convId,
|
|
1768
|
-
blocks: event.blocks
|
|
1769
|
-
});
|
|
1770
|
-
}
|
|
1771
|
-
return;
|
|
1772
|
-
}
|
|
1773
1685
|
this.emit({
|
|
1774
1686
|
type: "event",
|
|
1775
1687
|
conversationId: convId,
|
|
1776
1688
|
event
|
|
1777
1689
|
});
|
|
1778
|
-
if (event.type === ChatEventType.
|
|
1690
|
+
if (event.type === ChatEventType.MessageStop) {
|
|
1779
1691
|
if (this._state === "streaming") {
|
|
1780
1692
|
this.setState("idle");
|
|
1781
1693
|
}
|
|
@@ -1788,13 +1700,13 @@ var StreamManager = class {
|
|
|
1788
1700
|
const id = await this.session.createNewConversation();
|
|
1789
1701
|
this.setActiveConversation(id);
|
|
1790
1702
|
}
|
|
1791
|
-
this.prepareUserBlock(content);
|
|
1792
1703
|
this.setState("streaming");
|
|
1793
1704
|
try {
|
|
1794
1705
|
await this.session.send(content, {
|
|
1795
1706
|
enableSearch: options?.enableSearch,
|
|
1796
1707
|
agentName: options?.agentName,
|
|
1797
|
-
uploadIds: options?.uploadIds
|
|
1708
|
+
uploadIds: options?.uploadIds,
|
|
1709
|
+
planMode: options?.planMode
|
|
1798
1710
|
});
|
|
1799
1711
|
} catch {
|
|
1800
1712
|
}
|
|
@@ -1808,7 +1720,6 @@ var StreamManager = class {
|
|
|
1808
1720
|
);
|
|
1809
1721
|
const lastUserMsg = userMsgs[userMsgs.length - 1];
|
|
1810
1722
|
if (!lastUserMsg) return;
|
|
1811
|
-
this.prepareUserBlock(lastUserMsg.content);
|
|
1812
1723
|
this.setState("streaming");
|
|
1813
1724
|
try {
|
|
1814
1725
|
await this.session.resendFromCheckpoint(
|
|
@@ -1872,14 +1783,6 @@ var StreamManager = class {
|
|
|
1872
1783
|
this.handlers = [];
|
|
1873
1784
|
}
|
|
1874
1785
|
// ── Internal: helpers ──────────────────────────────────────────
|
|
1875
|
-
prepareUserBlock(content) {
|
|
1876
|
-
this.session.blockBuilder.reset();
|
|
1877
|
-
this.session.blockBuilder.addBlock({
|
|
1878
|
-
type: "user",
|
|
1879
|
-
id: this.session.blockBuilder.nextId(),
|
|
1880
|
-
content
|
|
1881
|
-
});
|
|
1882
|
-
}
|
|
1883
1786
|
finalizeStream() {
|
|
1884
1787
|
if (this._state === "streaming") {
|
|
1885
1788
|
this.setState("idle");
|
|
@@ -1890,8 +1793,8 @@ var StreamManager = class {
|
|
|
1890
1793
|
this.setState("restoring");
|
|
1891
1794
|
let activeJobId = null;
|
|
1892
1795
|
try {
|
|
1893
|
-
const res = await this.session.client.
|
|
1894
|
-
activeJobId = res.
|
|
1796
|
+
const res = await this.session.client.getActiveJob(conversationId);
|
|
1797
|
+
activeJobId = res.jobId;
|
|
1895
1798
|
} catch {
|
|
1896
1799
|
}
|
|
1897
1800
|
if (activeJobId) {
|
|
@@ -1911,15 +1814,19 @@ var StreamManager = class {
|
|
|
1911
1814
|
const completedJobs = jobs.filter(
|
|
1912
1815
|
(j) => j.status === "completed"
|
|
1913
1816
|
);
|
|
1914
|
-
|
|
1915
|
-
|
|
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
|
+
);
|
|
1916
1828
|
}
|
|
1917
1829
|
if (completedJobs.length > 0) {
|
|
1918
|
-
this.emit({
|
|
1919
|
-
type: "blocksChanged",
|
|
1920
|
-
conversationId,
|
|
1921
|
-
blocks: this.session.blockBuilder.getBlocks()
|
|
1922
|
-
});
|
|
1923
1830
|
this.emit({
|
|
1924
1831
|
type: "versionsReady",
|
|
1925
1832
|
conversationId,
|
|
@@ -1937,24 +1844,94 @@ var StreamManager = class {
|
|
|
1937
1844
|
this.emit({ type: "conversationChanged", conversationId: id });
|
|
1938
1845
|
}
|
|
1939
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
|
+
}
|
|
1940
1913
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1941
1914
|
0 && (module.exports = {
|
|
1942
1915
|
AstralformClient,
|
|
1943
1916
|
AstralformError,
|
|
1944
1917
|
AuthenticationError,
|
|
1945
|
-
BlockBuilder,
|
|
1946
1918
|
ChatEventType,
|
|
1947
1919
|
ChatSession,
|
|
1948
1920
|
ConnectionError,
|
|
1949
1921
|
InMemoryStorage,
|
|
1950
1922
|
LLMNotConfiguredError,
|
|
1923
|
+
ProtocolRegistry,
|
|
1951
1924
|
RateLimitError,
|
|
1952
1925
|
ServerError,
|
|
1953
1926
|
StreamAbortedError,
|
|
1954
1927
|
StreamManager,
|
|
1955
1928
|
ToolRegistry,
|
|
1956
1929
|
generateId,
|
|
1957
|
-
|
|
1958
|
-
|
|
1930
|
+
isEmbeddedResource,
|
|
1931
|
+
mapSseToChat,
|
|
1932
|
+
parseEmbeddedResource,
|
|
1933
|
+
replayEvents,
|
|
1934
|
+
streamJobSSE,
|
|
1935
|
+
translateDelta
|
|
1959
1936
|
});
|
|
1960
1937
|
//# sourceMappingURL=index.cjs.map
|