@agentknight/sdk 0.9.35
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/LICENSE +201 -0
- package/dist/chunks/acp-adapter-4N2QCTAL.js +936 -0
- package/dist/chunks/chunk-AHJSR7OY.js +24 -0
- package/dist/chunks/chunk-ATMWDEPF.js +1017 -0
- package/dist/chunks/chunk-C7XDS2KG.js +24 -0
- package/dist/chunks/chunk-JFHUAIRY.js +2659 -0
- package/dist/chunks/chunk-MVUOCZFL.js +47 -0
- package/dist/chunks/chunk-SJHJER3B.js +340 -0
- package/dist/chunks/chunk-W3PG2W3C.js +139 -0
- package/dist/chunks/chunk-ZKAN2FO7.js +2654 -0
- package/dist/chunks/copilot-adapter-CLIDIK2S.js +5179 -0
- package/dist/chunks/managed-agent-adapter-HP5VE3AH.js +1252 -0
- package/dist/chunks/pi-adapter-GSDVA7EG.js +1177 -0
- package/dist/index.d.ts +1962 -0
- package/dist/index.js +9187 -0
- package/dist/runtime.d.ts +3 -0
- package/dist/runtime.js +13 -0
- package/package.json +65 -0
|
@@ -0,0 +1,2659 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isContentfulAssistant
|
|
3
|
+
} from "./chunk-MVUOCZFL.js";
|
|
4
|
+
import {
|
|
5
|
+
SessionOperationError
|
|
6
|
+
} from "./chunk-SJHJER3B.js";
|
|
7
|
+
import {
|
|
8
|
+
CONVERSATION_SOURCE_ID_LIMIT,
|
|
9
|
+
SESSION_DIRECTORY_LIST_OPERATION,
|
|
10
|
+
SESSION_FILE_READ_OPERATION,
|
|
11
|
+
SESSION_PROJECT_IDENTITY_OPERATION,
|
|
12
|
+
SESSION_RESOURCE_READ_OPERATION,
|
|
13
|
+
SessionGitProjectSchema,
|
|
14
|
+
TIMELINE_IMAGE_OPERATION,
|
|
15
|
+
TIMELINE_SYNC_OPERATION,
|
|
16
|
+
WORKBENCH_OPERATIONS,
|
|
17
|
+
WorkbenchAskResultSchema,
|
|
18
|
+
WorkbenchCommandResultSchema,
|
|
19
|
+
WorkbenchCommandSchema,
|
|
20
|
+
WorkbenchCompactResultSchema,
|
|
21
|
+
WorkbenchInsightsSchema,
|
|
22
|
+
WorkbenchQueueMutationResultSchema,
|
|
23
|
+
WorkbenchQueueSnapshotSchema
|
|
24
|
+
} from "./chunk-ZKAN2FO7.js";
|
|
25
|
+
|
|
26
|
+
// src/operation-diagnostics.ts
|
|
27
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
28
|
+
import { createHash } from "node:crypto";
|
|
29
|
+
import { performance } from "node:perf_hooks";
|
|
30
|
+
import { getSystemErrorMap } from "node:util";
|
|
31
|
+
import { ZodError } from "zod";
|
|
32
|
+
|
|
33
|
+
// src/copilot-connection-errors.ts
|
|
34
|
+
function isClosedCopilotConnection(error) {
|
|
35
|
+
if (!(error instanceof Error)) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
switch (error.message) {
|
|
39
|
+
case "Connection is closed.":
|
|
40
|
+
return !("code" in error) || error.code === 1;
|
|
41
|
+
case "Connection is disposed.":
|
|
42
|
+
return !("code" in error) || error.code === 2;
|
|
43
|
+
case "Pending response rejected since connection got disposed":
|
|
44
|
+
return !("code" in error) || error.code === -32097;
|
|
45
|
+
default:
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function isDisposedCopilotSession(error) {
|
|
50
|
+
return error instanceof Error && (error.message === "Session has been disposed" || (!("code" in error) || error.code === -32603) && /^Request session\.[A-Za-z0-9.]+ failed with message: Session has been disposed$/u.test(error.message));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/session-restore-error.ts
|
|
54
|
+
var restoreMessages = /* @__PURE__ */ new Set([
|
|
55
|
+
"First event must be session.start or session.resume",
|
|
56
|
+
"session resume failed: First event must be session.start or session.resume",
|
|
57
|
+
"Request session.resume failed with message: session resume failed: First event must be session.start or session.resume"
|
|
58
|
+
]);
|
|
59
|
+
function isSessionRestoreFailure(error) {
|
|
60
|
+
for (let depth = 0; depth < 8 && error instanceof Error; depth++) {
|
|
61
|
+
if ("code" in error && error.code === "SESSION_RESTORE_FAILED" || restoreMessages.has(error.message)) return true;
|
|
62
|
+
error = error.cause;
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
function sessionRestoreFailure(error) {
|
|
67
|
+
return isSessionRestoreFailure(error) ? {
|
|
68
|
+
code: "SESSION_RESTORE_FAILED",
|
|
69
|
+
message: "The session could not be restored. Retry to reconnect."
|
|
70
|
+
} : void 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/operation-diagnostics.ts
|
|
74
|
+
var current = new AsyncLocalStorage();
|
|
75
|
+
var systemCodes = new Set([...getSystemErrorMap().values()].map(([code]) => code));
|
|
76
|
+
var errorTypes = /* @__PURE__ */ new Set(["Error", "TypeError", "RangeError", "SyntaxError", "AbortError", "TimeoutError", "AggregateError"]);
|
|
77
|
+
var operations = /* @__PURE__ */ new Set([
|
|
78
|
+
TIMELINE_SYNC_OPERATION,
|
|
79
|
+
TIMELINE_IMAGE_OPERATION,
|
|
80
|
+
SESSION_DIRECTORY_LIST_OPERATION,
|
|
81
|
+
SESSION_FILE_READ_OPERATION,
|
|
82
|
+
SESSION_PROJECT_IDENTITY_OPERATION,
|
|
83
|
+
SESSION_RESOURCE_READ_OPERATION,
|
|
84
|
+
...WORKBENCH_OPERATIONS
|
|
85
|
+
]);
|
|
86
|
+
var operationName = (request) => operations.has(request.operation) ? request.operation : "unknown";
|
|
87
|
+
function diagnosticReference(kind, value) {
|
|
88
|
+
return `${kind}:${createHash("sha256").update(`${kind}:${value}`).digest("hex").slice(0, 16)}`;
|
|
89
|
+
}
|
|
90
|
+
function diagnosticRequest(request) {
|
|
91
|
+
return {
|
|
92
|
+
requestRef: diagnosticReference("request", request.requestId),
|
|
93
|
+
machineRef: diagnosticReference("machine", request.machineId),
|
|
94
|
+
...request.sessionId === void 0 ? {} : { sessionRef: diagnosticReference("session", request.sessionId) },
|
|
95
|
+
...request.operationId === void 0 ? {} : { operationRef: diagnosticReference("operation", request.operationId) }
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function diagnosticError(error) {
|
|
99
|
+
const fields = {
|
|
100
|
+
errorType: error instanceof ZodError ? "ZodError" : error instanceof Error ? errorTypes.has(error.name) ? error.name : "CustomError" : "NonError"
|
|
101
|
+
};
|
|
102
|
+
fields.errorCategory = errorCategory(error);
|
|
103
|
+
if (error instanceof Error && "code" in error && typeof error.code === "string" && systemCodes.has(error.code)) fields.errorCode = error.code;
|
|
104
|
+
if (error instanceof Error && "code" in error && typeof error.code === "number" && [-32700, -32600, -32601, -32602, -32603, -32097].includes(error.code)) fields.rpcCode = error.code;
|
|
105
|
+
if (error instanceof ZodError) {
|
|
106
|
+
fields.issueCount = error.issues.length;
|
|
107
|
+
fields.issueCodes = [...new Set(error.issues.map((issue) => issue.code))].slice(0, 8);
|
|
108
|
+
}
|
|
109
|
+
if (error instanceof Error && error.cause !== void 0 && error.cause !== error) {
|
|
110
|
+
const cause = error.cause;
|
|
111
|
+
fields.causeType = cause instanceof Error && errorTypes.has(cause.name) ? cause.name : "Other";
|
|
112
|
+
fields.causeCategory = errorCategory(cause);
|
|
113
|
+
if (cause instanceof Error && "code" in cause && typeof cause.code === "string" && systemCodes.has(cause.code)) fields.causeCode = cause.code;
|
|
114
|
+
}
|
|
115
|
+
if (error instanceof AggregateError) {
|
|
116
|
+
fields.childCount = error.errors.length;
|
|
117
|
+
fields.childCategories = error.errors.slice(0, 2).map((child) => errorCategory(child));
|
|
118
|
+
}
|
|
119
|
+
return fields;
|
|
120
|
+
}
|
|
121
|
+
function errorCategory(error) {
|
|
122
|
+
if (isSessionRestoreFailure(error)) return "SESSION_RESTORE_FAILED";
|
|
123
|
+
if (isClosedCopilotConnection(error)) return "COPILOT_CONNECTION_CLOSED";
|
|
124
|
+
if (isDisposedCopilotSession(error)) return "COPILOT_SESSION_DISPOSED";
|
|
125
|
+
if (error instanceof ZodError) return "SCHEMA_VALIDATION";
|
|
126
|
+
if (error instanceof Error && "code" in error && (error.code === "SESSION_NOT_SHARED" || error.code === "AGENT_OPERATION_UNSUPPORTED")) return error.code;
|
|
127
|
+
return "OTHER";
|
|
128
|
+
}
|
|
129
|
+
function recordOperationFailure(error, phase) {
|
|
130
|
+
const diagnostics = current.getStore();
|
|
131
|
+
if (diagnostics?.running === true) diagnostics.recordFailure(error, phase);
|
|
132
|
+
}
|
|
133
|
+
function validateOperationResponse(action) {
|
|
134
|
+
try {
|
|
135
|
+
return action();
|
|
136
|
+
} catch (error) {
|
|
137
|
+
recordOperationFailure(error, "validate-response");
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async function measureOperationPhase(phase, action) {
|
|
142
|
+
const diagnostics = current.getStore();
|
|
143
|
+
if (diagnostics?.running !== true) return action();
|
|
144
|
+
const start = performance.now();
|
|
145
|
+
try {
|
|
146
|
+
return await action();
|
|
147
|
+
} catch (error) {
|
|
148
|
+
diagnostics.recordFailure(error, phase);
|
|
149
|
+
throw error;
|
|
150
|
+
} finally {
|
|
151
|
+
diagnostics.durations[phase] += performance.now() - start;
|
|
152
|
+
if (phase === "read") diagnostics.readCount += 1;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async function measureOperationStage(stage, action) {
|
|
156
|
+
const diagnostics = current.getStore();
|
|
157
|
+
if (diagnostics?.running !== true) return action();
|
|
158
|
+
const start = performance.now();
|
|
159
|
+
let failed = false;
|
|
160
|
+
try {
|
|
161
|
+
return await action();
|
|
162
|
+
} catch (error) {
|
|
163
|
+
failed = true;
|
|
164
|
+
diagnostics.recordStageFailure(error, stage);
|
|
165
|
+
throw error;
|
|
166
|
+
} finally {
|
|
167
|
+
diagnostics.recordStage(stage, performance.now() - start, failed);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function recordOperationTimelineRead(diagnostic) {
|
|
171
|
+
const diagnostics = current.getStore();
|
|
172
|
+
if (diagnostics?.running === true) diagnostics.recordTimelineRead(diagnostic);
|
|
173
|
+
}
|
|
174
|
+
var OperationDiagnostics = class {
|
|
175
|
+
durations = { handle: 0, read: 0, wait: 0, emit: 0 };
|
|
176
|
+
readCount = 0;
|
|
177
|
+
#failure;
|
|
178
|
+
#running = false;
|
|
179
|
+
#stages = /* @__PURE__ */ new Map();
|
|
180
|
+
#stageFailures = /* @__PURE__ */ new Map();
|
|
181
|
+
#timelineReads = [];
|
|
182
|
+
async run(action) {
|
|
183
|
+
this.#running = true;
|
|
184
|
+
try {
|
|
185
|
+
return await current.run(this, () => measureOperationPhase("handle", action));
|
|
186
|
+
} finally {
|
|
187
|
+
this.#running = false;
|
|
188
|
+
this.#stageFailures.clear();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
get running() {
|
|
192
|
+
return this.#running;
|
|
193
|
+
}
|
|
194
|
+
recordFailure(error, phase) {
|
|
195
|
+
const stage = [...this.#stageFailures].find(([, failure]) => Object.is(error, failure))?.[0];
|
|
196
|
+
this.#failure ??= { phase, ...diagnosticError(error), ...stage === void 0 ? {} : { stage } };
|
|
197
|
+
}
|
|
198
|
+
recordStageFailure(error, stage) {
|
|
199
|
+
if (!this.#running) return;
|
|
200
|
+
if (![...this.#stageFailures.values()].some((failure) => Object.is(error, failure))) {
|
|
201
|
+
this.#stageFailures.set(stage, error);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
recordStage(stage, elapsedMs, failed) {
|
|
205
|
+
if (!this.#running) return;
|
|
206
|
+
const sample = this.#stages.get(stage) ?? { calls: 0, elapsedMs: 0, failedCalls: 0 };
|
|
207
|
+
sample.calls++;
|
|
208
|
+
sample.elapsedMs += elapsedMs;
|
|
209
|
+
sample.failedCalls += failed ? 1 : 0;
|
|
210
|
+
this.#stages.set(stage, sample);
|
|
211
|
+
}
|
|
212
|
+
recordTimelineRead(diagnostic) {
|
|
213
|
+
if (this.#timelineReads.length < 8) {
|
|
214
|
+
this.#timelineReads.push({ readRef: diagnostic.readRef, joined: diagnostic.readJoins > 0 });
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
get activeMs() {
|
|
218
|
+
return Math.max(0, this.durations.handle - this.durations.wait) + this.durations.emit;
|
|
219
|
+
}
|
|
220
|
+
fields(request) {
|
|
221
|
+
return {
|
|
222
|
+
operation: operationName(request),
|
|
223
|
+
version: request.version,
|
|
224
|
+
...diagnosticRequest(request),
|
|
225
|
+
...this.details()
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
details() {
|
|
229
|
+
return {
|
|
230
|
+
diagnosticVersion: 2,
|
|
231
|
+
handleMs: Math.round(this.durations.handle),
|
|
232
|
+
readMs: Math.round(this.durations.read),
|
|
233
|
+
readCount: this.readCount,
|
|
234
|
+
waitMs: Math.round(this.durations.wait),
|
|
235
|
+
emitMs: Math.round(this.durations.emit),
|
|
236
|
+
activeMs: Math.round(this.activeMs),
|
|
237
|
+
phase: "handle",
|
|
238
|
+
...this.#failure,
|
|
239
|
+
// Nested or parallel stage durations overlap; they must not be summed as wall-clock time.
|
|
240
|
+
...this.#stages.size === 0 ? {} : { stages: [...this.#stages].map(([stage, sample]) => ({
|
|
241
|
+
stage,
|
|
242
|
+
...sample,
|
|
243
|
+
elapsedMs: Math.round(sample.elapsedMs)
|
|
244
|
+
})) },
|
|
245
|
+
...this.#timelineReads.length === 0 ? {} : { timelineReads: [...this.#timelineReads] }
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
var OperationMetrics = class {
|
|
250
|
+
#samples = /* @__PURE__ */ new Map();
|
|
251
|
+
record(request, diagnostics, failed) {
|
|
252
|
+
const operation = operationName(request);
|
|
253
|
+
const sample = this.#samples.get(operation) ?? {
|
|
254
|
+
operation,
|
|
255
|
+
requests: 0,
|
|
256
|
+
failures: 0,
|
|
257
|
+
handleMs: 0,
|
|
258
|
+
readMs: 0,
|
|
259
|
+
waitMs: 0,
|
|
260
|
+
emitMs: 0,
|
|
261
|
+
maxActiveMs: 0,
|
|
262
|
+
maxReadMs: 0
|
|
263
|
+
};
|
|
264
|
+
sample.requests += 1;
|
|
265
|
+
sample.failures += failed ? 1 : 0;
|
|
266
|
+
for (const [field, phase] of [
|
|
267
|
+
["handleMs", "handle"],
|
|
268
|
+
["readMs", "read"],
|
|
269
|
+
["waitMs", "wait"],
|
|
270
|
+
["emitMs", "emit"]
|
|
271
|
+
]) sample[field] += Math.round(diagnostics.durations[phase]);
|
|
272
|
+
sample.maxActiveMs = Math.max(sample.maxActiveMs, Math.round(diagnostics.activeMs));
|
|
273
|
+
sample.maxReadMs = Math.max(sample.maxReadMs, Math.round(diagnostics.durations.read));
|
|
274
|
+
this.#samples.set(operation, sample);
|
|
275
|
+
}
|
|
276
|
+
drain() {
|
|
277
|
+
const samples = [...this.#samples.values()];
|
|
278
|
+
this.#samples.clear();
|
|
279
|
+
return samples;
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// src/conversation-normalizer.ts
|
|
284
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
285
|
+
var SUMMARY_LIMIT = 4096;
|
|
286
|
+
var IDENTIFIER_LIMIT = 256;
|
|
287
|
+
var TASK_COMPLETE_TOOL_NAME = "task_complete";
|
|
288
|
+
var MAX_INLINE_IMAGE_BYTES = 3 * 1024 * 1024;
|
|
289
|
+
var MAX_INLINE_IMAGE_MESSAGE_BYTES = 3 * 1024 * 1024;
|
|
290
|
+
var MAX_INLINE_IMAGES_PER_MESSAGE = 4;
|
|
291
|
+
var SUPPORTED_IMAGE_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
292
|
+
"image/gif",
|
|
293
|
+
"image/jpeg",
|
|
294
|
+
"image/png",
|
|
295
|
+
"image/webp"
|
|
296
|
+
]);
|
|
297
|
+
function splitToolImages(images) {
|
|
298
|
+
const groups = [];
|
|
299
|
+
let group = [];
|
|
300
|
+
let groupBytes = 0;
|
|
301
|
+
for (const image of images) {
|
|
302
|
+
const imageBytes = image.attachment.byteLength ?? 0;
|
|
303
|
+
if (group.length > 0 && (group.length >= MAX_INLINE_IMAGES_PER_MESSAGE || groupBytes + imageBytes > MAX_INLINE_IMAGE_MESSAGE_BYTES)) {
|
|
304
|
+
groups.push(group);
|
|
305
|
+
group = [];
|
|
306
|
+
groupBytes = 0;
|
|
307
|
+
}
|
|
308
|
+
group.push(image);
|
|
309
|
+
groupBytes += imageBytes;
|
|
310
|
+
}
|
|
311
|
+
if (group.length > 0) {
|
|
312
|
+
groups.push(group);
|
|
313
|
+
}
|
|
314
|
+
return groups;
|
|
315
|
+
}
|
|
316
|
+
function protocolId(value) {
|
|
317
|
+
if (value.length <= IDENTIFIER_LIMIT) {
|
|
318
|
+
return value;
|
|
319
|
+
}
|
|
320
|
+
const digest = createHash2("sha256").update(value).digest("hex");
|
|
321
|
+
return `sha256:${digest}`;
|
|
322
|
+
}
|
|
323
|
+
function scopedId(scope, value) {
|
|
324
|
+
const id = `${scope}:${value}`;
|
|
325
|
+
if (id.length <= IDENTIFIER_LIMIT) {
|
|
326
|
+
return id;
|
|
327
|
+
}
|
|
328
|
+
const digest = createHash2("sha256").update(value).digest("hex");
|
|
329
|
+
return `${scope}:sha256:${digest}`;
|
|
330
|
+
}
|
|
331
|
+
function assistantMessageId(data) {
|
|
332
|
+
return scopedId("assistant", data.apiCallId ?? data.messageId);
|
|
333
|
+
}
|
|
334
|
+
function eventRevision(timestamp, previousRevision) {
|
|
335
|
+
const timestampRevision = Date.parse(timestamp);
|
|
336
|
+
if (!Number.isSafeInteger(timestampRevision) || timestampRevision < 0) {
|
|
337
|
+
throw new Error(`Invalid SDK event timestamp: ${timestamp}`);
|
|
338
|
+
}
|
|
339
|
+
const revision = previousRevision === void 0 || timestampRevision > previousRevision ? timestampRevision : previousRevision + 1;
|
|
340
|
+
if (!Number.isSafeInteger(revision)) {
|
|
341
|
+
throw new Error(`SDK event revision exceeds the safe integer range: ${timestamp}`);
|
|
342
|
+
}
|
|
343
|
+
return revision;
|
|
344
|
+
}
|
|
345
|
+
function removeInternalMarkup(value) {
|
|
346
|
+
return value.replace(
|
|
347
|
+
/<skill-context\b[^>]*>[\s\S]*?<\/skill-context\s*>/giu,
|
|
348
|
+
" "
|
|
349
|
+
).replace(/<skill-context\b[^>]*>[\s\S]*/giu, " ").replace(/<\/?system_notification\b[^>]*>/giu, " ");
|
|
350
|
+
}
|
|
351
|
+
function stripInternalMarkup(value) {
|
|
352
|
+
return removeInternalMarkup(value).replace(/\s+/gu, " ").trim();
|
|
353
|
+
}
|
|
354
|
+
function limitSummary(value, limit = SUMMARY_LIMIT) {
|
|
355
|
+
if (value.length <= limit) {
|
|
356
|
+
return value;
|
|
357
|
+
}
|
|
358
|
+
return `${value.slice(0, limit - 1)}\u2026`;
|
|
359
|
+
}
|
|
360
|
+
function boundedText(value, limit, fallback) {
|
|
361
|
+
const normalized = stripInternalMarkup(value);
|
|
362
|
+
return limitSummary(normalized.length === 0 ? fallback : normalized, limit);
|
|
363
|
+
}
|
|
364
|
+
function boundedOptionalText(value, limit) {
|
|
365
|
+
if (value === void 0) {
|
|
366
|
+
return void 0;
|
|
367
|
+
}
|
|
368
|
+
const normalized = stripInternalMarkup(value);
|
|
369
|
+
return normalized.length === 0 ? void 0 : limitSummary(normalized, limit);
|
|
370
|
+
}
|
|
371
|
+
function boundedOptionalMessageContent(value, limit) {
|
|
372
|
+
if (value === void 0) {
|
|
373
|
+
return void 0;
|
|
374
|
+
}
|
|
375
|
+
const normalized = removeInternalMarkup(value).trim();
|
|
376
|
+
return normalized.length === 0 ? void 0 : limitSummary(normalized, limit);
|
|
377
|
+
}
|
|
378
|
+
function appendSourceEventId(sourceEventIds, eventId) {
|
|
379
|
+
return [...sourceEventIds, eventId].slice(-CONVERSATION_SOURCE_ID_LIMIT);
|
|
380
|
+
}
|
|
381
|
+
function appendOptionalSourceId(sourceIds, sourceId) {
|
|
382
|
+
if (sourceId === void 0) {
|
|
383
|
+
return sourceIds === void 0 ? void 0 : [...sourceIds];
|
|
384
|
+
}
|
|
385
|
+
return [...sourceIds ?? [], sourceId].filter((candidate, index, all) => all.indexOf(candidate) === index).slice(-CONVERSATION_SOURCE_ID_LIMIT);
|
|
386
|
+
}
|
|
387
|
+
function appendAssistantContent(existing, next) {
|
|
388
|
+
const combined = existing.length > 0 && next.length > 0 ? `${existing}
|
|
389
|
+
|
|
390
|
+
${next}` : existing || next;
|
|
391
|
+
if (combined.length <= 5e5) {
|
|
392
|
+
return combined;
|
|
393
|
+
}
|
|
394
|
+
return `\u2026${combined.slice(combined.length - 499999)}`;
|
|
395
|
+
}
|
|
396
|
+
function boundAttachment(attachment) {
|
|
397
|
+
return {
|
|
398
|
+
...attachment,
|
|
399
|
+
name: boundedText(attachment.name, 1024, "Attachment"),
|
|
400
|
+
...attachment.mimeType === void 0 ? {} : {
|
|
401
|
+
mimeType: boundedText(
|
|
402
|
+
attachment.mimeType,
|
|
403
|
+
256,
|
|
404
|
+
"application/octet-stream"
|
|
405
|
+
)
|
|
406
|
+
},
|
|
407
|
+
...attachment.uri === void 0 ? {} : { uri: boundedText(attachment.uri, 32768, "unknown") }
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
function summarizeUnknown(value) {
|
|
411
|
+
if (typeof value === "string") {
|
|
412
|
+
return limitSummary(stripInternalMarkup(value));
|
|
413
|
+
}
|
|
414
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
415
|
+
return String(value);
|
|
416
|
+
}
|
|
417
|
+
if (value === null) {
|
|
418
|
+
return "null";
|
|
419
|
+
}
|
|
420
|
+
try {
|
|
421
|
+
return limitSummary(stripInternalMarkup(JSON.stringify(value)));
|
|
422
|
+
} catch {
|
|
423
|
+
return "[unserializable]";
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
function isDisplayableHumanMessage(event) {
|
|
427
|
+
if (event.type !== "user.message" || event.agentId !== void 0) {
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
const source = event.data.source;
|
|
431
|
+
return event.data.isAutopilotContinuation !== true && (source === void 0 || source === "user" || source === "mobile");
|
|
432
|
+
}
|
|
433
|
+
function projectAttachment(attachment) {
|
|
434
|
+
switch (attachment.type) {
|
|
435
|
+
case "file":
|
|
436
|
+
return {
|
|
437
|
+
kind: attachment.mimeType?.startsWith("image/") ? "image" : "file",
|
|
438
|
+
name: attachment.displayName,
|
|
439
|
+
...attachment.mimeType === void 0 ? {} : { mimeType: attachment.mimeType },
|
|
440
|
+
uri: attachment.path
|
|
441
|
+
};
|
|
442
|
+
case "directory":
|
|
443
|
+
return {
|
|
444
|
+
kind: "file",
|
|
445
|
+
name: attachment.displayName,
|
|
446
|
+
uri: attachment.path
|
|
447
|
+
};
|
|
448
|
+
case "selection":
|
|
449
|
+
return {
|
|
450
|
+
kind: "selection",
|
|
451
|
+
name: attachment.displayName,
|
|
452
|
+
uri: attachment.filePath
|
|
453
|
+
};
|
|
454
|
+
case "github_reference":
|
|
455
|
+
return {
|
|
456
|
+
kind: "github-reference",
|
|
457
|
+
name: attachment.title,
|
|
458
|
+
uri: attachment.url
|
|
459
|
+
};
|
|
460
|
+
case "github_commit":
|
|
461
|
+
return {
|
|
462
|
+
kind: "github-reference",
|
|
463
|
+
name: attachment.message,
|
|
464
|
+
uri: attachment.url
|
|
465
|
+
};
|
|
466
|
+
case "github_release":
|
|
467
|
+
return {
|
|
468
|
+
kind: "github-reference",
|
|
469
|
+
name: attachment.name,
|
|
470
|
+
uri: attachment.url
|
|
471
|
+
};
|
|
472
|
+
case "github_actions_job":
|
|
473
|
+
return {
|
|
474
|
+
kind: "github-reference",
|
|
475
|
+
name: attachment.jobName,
|
|
476
|
+
uri: attachment.url
|
|
477
|
+
};
|
|
478
|
+
case "github_repository":
|
|
479
|
+
return {
|
|
480
|
+
kind: "github-reference",
|
|
481
|
+
name: `${attachment.repo.owner}/${attachment.repo.name}`,
|
|
482
|
+
uri: attachment.url
|
|
483
|
+
};
|
|
484
|
+
case "github_file_diff":
|
|
485
|
+
return {
|
|
486
|
+
kind: "github-reference",
|
|
487
|
+
name: "GitHub file diff",
|
|
488
|
+
uri: attachment.url
|
|
489
|
+
};
|
|
490
|
+
case "github_tree_comparison":
|
|
491
|
+
return {
|
|
492
|
+
kind: "github-reference",
|
|
493
|
+
name: "GitHub tree comparison",
|
|
494
|
+
uri: attachment.url
|
|
495
|
+
};
|
|
496
|
+
case "github_url":
|
|
497
|
+
return {
|
|
498
|
+
kind: "github-reference",
|
|
499
|
+
name: attachment.url,
|
|
500
|
+
uri: attachment.url
|
|
501
|
+
};
|
|
502
|
+
case "github_file":
|
|
503
|
+
case "github_snippet":
|
|
504
|
+
return {
|
|
505
|
+
kind: "github-reference",
|
|
506
|
+
name: attachment.path,
|
|
507
|
+
uri: attachment.url
|
|
508
|
+
};
|
|
509
|
+
case "blob":
|
|
510
|
+
return {
|
|
511
|
+
kind: attachment.mimeType.startsWith("image/") ? "image" : "file",
|
|
512
|
+
name: attachment.displayName ?? "Attachment",
|
|
513
|
+
mimeType: attachment.mimeType
|
|
514
|
+
};
|
|
515
|
+
case "extension_context":
|
|
516
|
+
return {
|
|
517
|
+
kind: "unknown",
|
|
518
|
+
name: attachment.title
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
function projectCitation(source) {
|
|
523
|
+
if (source.url !== void 0) {
|
|
524
|
+
return {
|
|
525
|
+
kind: "github-reference",
|
|
526
|
+
name: source.title ?? source.path ?? source.id,
|
|
527
|
+
uri: source.url
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
if (source.path !== void 0) {
|
|
531
|
+
return {
|
|
532
|
+
kind: "file",
|
|
533
|
+
name: source.title ?? source.path,
|
|
534
|
+
uri: source.path
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
return {
|
|
538
|
+
kind: "unknown",
|
|
539
|
+
name: source.title ?? source.id
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
function normalizeImageMimeType(value) {
|
|
543
|
+
return value.toLowerCase() === "image/jpg" ? "image/jpeg" : value.toLowerCase();
|
|
544
|
+
}
|
|
545
|
+
function hasImageSignature(bytes, mimeType) {
|
|
546
|
+
switch (mimeType) {
|
|
547
|
+
case "image/jpeg":
|
|
548
|
+
return bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255;
|
|
549
|
+
case "image/png":
|
|
550
|
+
return bytes.length >= 8 && bytes.subarray(0, 8).equals(
|
|
551
|
+
Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])
|
|
552
|
+
);
|
|
553
|
+
case "image/webp":
|
|
554
|
+
return bytes.length >= 12 && bytes.subarray(0, 4).toString("ascii") === "RIFF" && bytes.subarray(8, 12).toString("ascii") === "WEBP";
|
|
555
|
+
case "image/gif":
|
|
556
|
+
return bytes.length >= 6 && (bytes.subarray(0, 6).toString("ascii") === "GIF87a" || bytes.subarray(0, 6).toString("ascii") === "GIF89a");
|
|
557
|
+
default:
|
|
558
|
+
return false;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
function decodeImageBase64(value, expectedMimeType) {
|
|
562
|
+
const dataUrlMatch = /^data:([^;,]+);base64,(.*)$/isu.exec(value);
|
|
563
|
+
if (dataUrlMatch !== null && normalizeImageMimeType(dataUrlMatch[1] ?? "") !== expectedMimeType) {
|
|
564
|
+
return void 0;
|
|
565
|
+
}
|
|
566
|
+
const payload = dataUrlMatch?.[2] ?? value;
|
|
567
|
+
const normalized = payload.replaceAll("-", "+").replaceAll("_", "/");
|
|
568
|
+
if (normalized.length === 0 || normalized.length % 4 === 1 || !/^[A-Za-z0-9+/]*={0,2}$/u.test(normalized)) {
|
|
569
|
+
return void 0;
|
|
570
|
+
}
|
|
571
|
+
const bytes = Buffer.from(normalized, "base64");
|
|
572
|
+
return bytes.toString("base64").replace(/=+$/u, "") === normalized.replace(/=+$/u, "") ? bytes : void 0;
|
|
573
|
+
}
|
|
574
|
+
function projectInlineToolImage(data, mimeTypeValue, name, expectedByteLength, source = "binary-result") {
|
|
575
|
+
const mimeType = normalizeImageMimeType(mimeTypeValue);
|
|
576
|
+
const metadata = {
|
|
577
|
+
kind: "image",
|
|
578
|
+
name: boundedText(name, 1024, "Copilot image"),
|
|
579
|
+
mimeType: boundedText(mimeType, 256, "application/octet-stream")
|
|
580
|
+
};
|
|
581
|
+
if (!SUPPORTED_IMAGE_MIME_TYPES.has(mimeType)) {
|
|
582
|
+
return {
|
|
583
|
+
attachment: metadata,
|
|
584
|
+
failure: { source, reason: "mime-unsupported", mimeType }
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
const bytes = decodeImageBase64(data, mimeType);
|
|
588
|
+
if (bytes === void 0) {
|
|
589
|
+
return {
|
|
590
|
+
attachment: metadata,
|
|
591
|
+
failure: { source, reason: "data-invalid", mimeType }
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
if (bytes.length === 0) {
|
|
595
|
+
return {
|
|
596
|
+
attachment: metadata,
|
|
597
|
+
failure: { source, reason: "data-empty", mimeType, actualByteLength: 0 }
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
if (bytes.length > MAX_INLINE_IMAGE_BYTES) {
|
|
601
|
+
return {
|
|
602
|
+
attachment: metadata,
|
|
603
|
+
failure: {
|
|
604
|
+
source,
|
|
605
|
+
reason: "data-too-large",
|
|
606
|
+
mimeType,
|
|
607
|
+
actualByteLength: bytes.length
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
if (expectedByteLength !== void 0 && bytes.length !== expectedByteLength) {
|
|
612
|
+
return {
|
|
613
|
+
attachment: metadata,
|
|
614
|
+
failure: {
|
|
615
|
+
source,
|
|
616
|
+
reason: "length-mismatch",
|
|
617
|
+
mimeType,
|
|
618
|
+
expectedByteLength,
|
|
619
|
+
actualByteLength: bytes.length
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
if (!hasImageSignature(bytes, mimeType)) {
|
|
624
|
+
return {
|
|
625
|
+
attachment: metadata,
|
|
626
|
+
failure: {
|
|
627
|
+
source,
|
|
628
|
+
reason: "signature-mismatch",
|
|
629
|
+
mimeType,
|
|
630
|
+
actualByteLength: bytes.length
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
return {
|
|
635
|
+
attachment: {
|
|
636
|
+
...metadata,
|
|
637
|
+
data: bytes.toString("base64"),
|
|
638
|
+
byteLength: bytes.length,
|
|
639
|
+
sha256: createHash2("sha256").update(bytes).digest("hex")
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
function projectToolBinaryResult(result, name, assets) {
|
|
644
|
+
if (result.type !== "image") {
|
|
645
|
+
return void 0;
|
|
646
|
+
}
|
|
647
|
+
if ("data" in result) {
|
|
648
|
+
return projectInlineToolImage(
|
|
649
|
+
result.data,
|
|
650
|
+
result.mimeType,
|
|
651
|
+
result.description ?? name
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
if ("assetId" in result) {
|
|
655
|
+
const asset = assets.get(result.assetId);
|
|
656
|
+
if (asset === void 0 || asset.data.type !== "image") {
|
|
657
|
+
return {
|
|
658
|
+
attachment: {
|
|
659
|
+
kind: "image",
|
|
660
|
+
name: boundedText(result.description ?? name, 1024, "Copilot image"),
|
|
661
|
+
mimeType: boundedText(
|
|
662
|
+
normalizeImageMimeType(result.mimeType),
|
|
663
|
+
256,
|
|
664
|
+
"application/octet-stream"
|
|
665
|
+
)
|
|
666
|
+
},
|
|
667
|
+
failure: {
|
|
668
|
+
source: "binary-asset",
|
|
669
|
+
reason: "asset-missing",
|
|
670
|
+
mimeType: normalizeImageMimeType(result.mimeType),
|
|
671
|
+
expectedByteLength: result.byteLength
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
const projected = projectInlineToolImage(
|
|
676
|
+
asset.data.data,
|
|
677
|
+
asset.data.mimeType,
|
|
678
|
+
result.description ?? asset.data.description ?? name,
|
|
679
|
+
void 0,
|
|
680
|
+
"binary-asset"
|
|
681
|
+
);
|
|
682
|
+
const attachment = projected.attachment;
|
|
683
|
+
if (projected.failure !== void 0) {
|
|
684
|
+
return { ...projected, sourceEventId: asset.eventId, sourceTimestamp: asset.timestamp };
|
|
685
|
+
}
|
|
686
|
+
if (attachment.sha256 !== void 0 && result.assetId !== `sha256:${attachment.sha256}`) {
|
|
687
|
+
return {
|
|
688
|
+
attachment: {
|
|
689
|
+
kind: "image",
|
|
690
|
+
name: attachment.name,
|
|
691
|
+
...attachment.mimeType === void 0 ? {} : { mimeType: attachment.mimeType }
|
|
692
|
+
},
|
|
693
|
+
sourceEventId: asset.eventId,
|
|
694
|
+
sourceTimestamp: asset.timestamp,
|
|
695
|
+
failure: {
|
|
696
|
+
source: "binary-asset",
|
|
697
|
+
reason: "asset-hash-mismatch",
|
|
698
|
+
mimeType: attachment.mimeType ?? "application/octet-stream",
|
|
699
|
+
expectedByteLength: result.byteLength,
|
|
700
|
+
...attachment.byteLength === void 0 ? {} : { actualByteLength: attachment.byteLength }
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
return {
|
|
705
|
+
attachment,
|
|
706
|
+
sourceEventId: asset.eventId,
|
|
707
|
+
sourceTimestamp: asset.timestamp
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
return {
|
|
711
|
+
attachment: {
|
|
712
|
+
kind: "image",
|
|
713
|
+
name: boundedText(result.description ?? name, 1024, "Copilot image"),
|
|
714
|
+
mimeType: boundedText(
|
|
715
|
+
normalizeImageMimeType(result.mimeType),
|
|
716
|
+
256,
|
|
717
|
+
"application/octet-stream"
|
|
718
|
+
)
|
|
719
|
+
},
|
|
720
|
+
failure: {
|
|
721
|
+
source: "binary-result",
|
|
722
|
+
reason: "asset-missing",
|
|
723
|
+
mimeType: normalizeImageMimeType(result.mimeType),
|
|
724
|
+
expectedByteLength: result.byteLength
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
function projectToolContentImage(image, name) {
|
|
729
|
+
return projectInlineToolImage(
|
|
730
|
+
image.data,
|
|
731
|
+
image.mimeType,
|
|
732
|
+
name,
|
|
733
|
+
void 0,
|
|
734
|
+
"content"
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
function permissionName(kind) {
|
|
738
|
+
const words = kind.replaceAll("_", " ").replaceAll("-", " ");
|
|
739
|
+
return `${words.charAt(0).toUpperCase()}${words.slice(1)} permission`;
|
|
740
|
+
}
|
|
741
|
+
var ConversationToolLifecycles = class {
|
|
742
|
+
#starts = /* @__PURE__ */ new Map();
|
|
743
|
+
get(toolCallId) {
|
|
744
|
+
return this.#starts.get(toolCallId);
|
|
745
|
+
}
|
|
746
|
+
remember(toolCallId, start) {
|
|
747
|
+
const previous = this.#starts.get(toolCallId);
|
|
748
|
+
const lifecycle = previous?.sourceEventId === start.sourceEventId && !start.hasTurnContext ? previous : start;
|
|
749
|
+
this.#starts.delete(toolCallId);
|
|
750
|
+
this.#starts.set(toolCallId, lifecycle);
|
|
751
|
+
while (this.#starts.size > 1024) {
|
|
752
|
+
const oldest = this.#starts.keys().next().value;
|
|
753
|
+
if (oldest === void 0) break;
|
|
754
|
+
this.#starts.delete(oldest);
|
|
755
|
+
}
|
|
756
|
+
return lifecycle;
|
|
757
|
+
}
|
|
758
|
+
clear() {
|
|
759
|
+
this.#starts.clear();
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
var ConversationNormalizer = class {
|
|
763
|
+
constructor(onImageFailure, toolLifecycles = new ConversationToolLifecycles(), resolveInputProvenance) {
|
|
764
|
+
this.onImageFailure = onImageFailure;
|
|
765
|
+
this.toolLifecycles = toolLifecycles;
|
|
766
|
+
this.resolveInputProvenance = resolveInputProvenance;
|
|
767
|
+
}
|
|
768
|
+
onImageFailure;
|
|
769
|
+
toolLifecycles;
|
|
770
|
+
resolveInputProvenance;
|
|
771
|
+
#items = /* @__PURE__ */ new Map();
|
|
772
|
+
#binaryAssets = /* @__PURE__ */ new Map();
|
|
773
|
+
#pendingToolImages = /* @__PURE__ */ new Map();
|
|
774
|
+
#toolTurns = /* @__PURE__ */ new Map();
|
|
775
|
+
#taskCompletionToolCalls = /* @__PURE__ */ new Map();
|
|
776
|
+
#subagentTurns = /* @__PURE__ */ new Map();
|
|
777
|
+
#permissionTurns = /* @__PURE__ */ new Map();
|
|
778
|
+
#loopTurns = /* @__PURE__ */ new Map();
|
|
779
|
+
#currentTurnId;
|
|
780
|
+
#hasCurrentTurnContext = false;
|
|
781
|
+
#reportedImageFailures = /* @__PURE__ */ new Set();
|
|
782
|
+
apply(events) {
|
|
783
|
+
const changed = [];
|
|
784
|
+
for (const event of events) {
|
|
785
|
+
if (event.type === "session.binary_asset" && event.data.type === "image") {
|
|
786
|
+
this.#binaryAssets.set(event.data.assetId, {
|
|
787
|
+
eventId: event.id,
|
|
788
|
+
timestamp: event.timestamp,
|
|
789
|
+
data: event.data
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
if (event.type === "tool.execution_start" && event.data.toolName === TASK_COMPLETE_TOOL_NAME) {
|
|
793
|
+
this.#taskCompletionToolCalls.set(
|
|
794
|
+
event.data.toolCallId,
|
|
795
|
+
event.agentId !== void 0 || event.data.parentToolCallId !== void 0 || this.#taskCompletionToolCalls.get(event.data.toolCallId) === "subagent" ? "subagent" : "assistant"
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
for (const event of events) {
|
|
800
|
+
if (event.type === "session.binary_asset") {
|
|
801
|
+
const pending = this.#pendingToolImages.get(event.data.assetId);
|
|
802
|
+
if (pending !== void 0) {
|
|
803
|
+
for (const projection of pending.values()) {
|
|
804
|
+
if (this.#unresolvedToolImageAssetIds(projection.event).length === 0) {
|
|
805
|
+
this.#removePendingToolImage(projection.event);
|
|
806
|
+
changed.push(
|
|
807
|
+
...this.#projectToolImages(
|
|
808
|
+
projection.event,
|
|
809
|
+
projection.turnId,
|
|
810
|
+
projection.toolName
|
|
811
|
+
)
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
continue;
|
|
817
|
+
}
|
|
818
|
+
const inputProvenance = event.type === "user.message" && event.agentId === void 0 && event.data.isAutopilotContinuation !== true ? this.resolveInputProvenance?.(event) : void 0;
|
|
819
|
+
if (event.type === "user.message" && (inputProvenance !== void 0 || isDisplayableHumanMessage(event))) {
|
|
820
|
+
const turnId = protocolId(event.data.interactionId ?? event.id);
|
|
821
|
+
const item = {
|
|
822
|
+
id: scopedId("message", event.id),
|
|
823
|
+
revision: 1,
|
|
824
|
+
turnId,
|
|
825
|
+
timestamp: event.timestamp,
|
|
826
|
+
...inputProvenance?.actor === "subagent" ? {
|
|
827
|
+
kind: "assistant-message",
|
|
828
|
+
actor: "subagent",
|
|
829
|
+
agentId: inputProvenance.agentId,
|
|
830
|
+
final: true,
|
|
831
|
+
sourceMessageIds: [event.id]
|
|
832
|
+
} : { kind: "human-message", actor: "human" },
|
|
833
|
+
sourceEventIds: [event.id],
|
|
834
|
+
...event.data.interactionId === void 0 ? {} : { sourceInteractionIds: [protocolId(event.data.interactionId)] },
|
|
835
|
+
content: limitSummary(event.data.content, 1e5),
|
|
836
|
+
attachments: event.data.attachments?.slice(0, 100).map(projectAttachment).map(boundAttachment) ?? []
|
|
837
|
+
};
|
|
838
|
+
this.#currentTurnId = turnId;
|
|
839
|
+
this.#hasCurrentTurnContext = true;
|
|
840
|
+
this.#items.set(item.id, item);
|
|
841
|
+
changed.push(item);
|
|
842
|
+
continue;
|
|
843
|
+
}
|
|
844
|
+
if (event.type === "assistant.turn_start") {
|
|
845
|
+
if (event.agentId !== void 0) {
|
|
846
|
+
this.#loopTurns.set(event.data.turnId, {
|
|
847
|
+
turnId: protocolId(
|
|
848
|
+
this.#currentTurnId ?? event.data.interactionId ?? event.data.turnId
|
|
849
|
+
),
|
|
850
|
+
hasTurnContext: this.#currentTurnId === void 0 ? event.data.interactionId !== void 0 : this.#hasCurrentTurnContext
|
|
851
|
+
});
|
|
852
|
+
continue;
|
|
853
|
+
}
|
|
854
|
+
const turnId = protocolId(
|
|
855
|
+
event.data.interactionId ?? this.#currentTurnId ?? event.data.turnId
|
|
856
|
+
);
|
|
857
|
+
const hasTurnContext = event.data.interactionId !== void 0 || this.#hasCurrentTurnContext;
|
|
858
|
+
this.#loopTurns.set(event.data.turnId, { turnId, hasTurnContext });
|
|
859
|
+
this.#currentTurnId = turnId;
|
|
860
|
+
this.#hasCurrentTurnContext = hasTurnContext;
|
|
861
|
+
continue;
|
|
862
|
+
}
|
|
863
|
+
if (event.type === "skill.invoked") {
|
|
864
|
+
const turnId = this.#currentTurnId ?? protocolId(event.id);
|
|
865
|
+
const step = {
|
|
866
|
+
id: scopedId("skill", event.id),
|
|
867
|
+
kind: "skill",
|
|
868
|
+
name: boundedText(event.data.name, 256, "Skill"),
|
|
869
|
+
...boundedOptionalText(event.data.description, 2048) === void 0 ? {} : {
|
|
870
|
+
description: boundedOptionalText(
|
|
871
|
+
event.data.description,
|
|
872
|
+
2048
|
|
873
|
+
)
|
|
874
|
+
},
|
|
875
|
+
status: "completed",
|
|
876
|
+
...boundedOptionalText(event.data.source, 256) === void 0 ? {} : { source: boundedOptionalText(event.data.source, 256) }
|
|
877
|
+
};
|
|
878
|
+
const item = this.#upsertActivityStep(
|
|
879
|
+
event.id,
|
|
880
|
+
event.timestamp,
|
|
881
|
+
turnId,
|
|
882
|
+
step
|
|
883
|
+
);
|
|
884
|
+
changed.push(item);
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
887
|
+
if (event.type === "tool.execution_start") {
|
|
888
|
+
const lifecycle = this.toolLifecycles.remember(event.data.toolCallId, {
|
|
889
|
+
sourceEventId: protocolId(event.id),
|
|
890
|
+
timestamp: event.timestamp,
|
|
891
|
+
turnId: this.#resolveTurnId(
|
|
892
|
+
event.data.turnId,
|
|
893
|
+
this.#currentTurnId ?? protocolId(event.id)
|
|
894
|
+
),
|
|
895
|
+
hasTurnContext: event.data.turnId === void 0 ? this.#hasCurrentTurnContext : this.#loopTurns.get(event.data.turnId)?.hasTurnContext ?? false,
|
|
896
|
+
name: boundedText(event.data.toolName, 256, "Tool"),
|
|
897
|
+
actor: event.agentId !== void 0 || event.data.parentToolCallId !== void 0 ? "subagent" : "assistant"
|
|
898
|
+
});
|
|
899
|
+
const turnId = lifecycle.turnId;
|
|
900
|
+
this.#toolTurns.set(event.data.toolCallId, turnId);
|
|
901
|
+
if (event.data.toolName === TASK_COMPLETE_TOOL_NAME) {
|
|
902
|
+
continue;
|
|
903
|
+
}
|
|
904
|
+
const step = {
|
|
905
|
+
id: scopedId("tool", event.data.toolCallId),
|
|
906
|
+
kind: "tool",
|
|
907
|
+
name: lifecycle.name,
|
|
908
|
+
status: "running"
|
|
909
|
+
};
|
|
910
|
+
changed.push(
|
|
911
|
+
this.#upsertActivityStep(
|
|
912
|
+
protocolId(event.id),
|
|
913
|
+
event.timestamp,
|
|
914
|
+
turnId,
|
|
915
|
+
step
|
|
916
|
+
)
|
|
917
|
+
);
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
if (event.type === "tool.execution_complete") {
|
|
921
|
+
const lifecycle = this.toolLifecycles.get(event.data.toolCallId);
|
|
922
|
+
const turnId = lifecycle?.turnId ?? this.#resolveTurnId(
|
|
923
|
+
event.data.turnId,
|
|
924
|
+
this.#toolTurns.get(event.data.toolCallId) ?? this.#currentTurnId ?? event.id
|
|
925
|
+
);
|
|
926
|
+
if (this.#taskCompletionToolCalls.has(event.data.toolCallId) || lifecycle?.name === TASK_COMPLETE_TOOL_NAME || event.data.toolDescription?.name === TASK_COMPLETE_TOOL_NAME) {
|
|
927
|
+
if (lifecycle !== void 0) {
|
|
928
|
+
this.#taskCompletionToolCalls.set(event.data.toolCallId, lifecycle.actor);
|
|
929
|
+
}
|
|
930
|
+
const taskCompletion = this.#projectTaskCompletion(event, turnId);
|
|
931
|
+
if (taskCompletion !== void 0) {
|
|
932
|
+
changed.push(taskCompletion);
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
const step2 = {
|
|
936
|
+
id: scopedId("tool", event.data.toolCallId),
|
|
937
|
+
kind: "tool",
|
|
938
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
939
|
+
status: event.data.success ? "completed" : "failed"
|
|
940
|
+
};
|
|
941
|
+
changed.push(
|
|
942
|
+
this.#upsertActivityStep(
|
|
943
|
+
event.id,
|
|
944
|
+
event.timestamp,
|
|
945
|
+
turnId,
|
|
946
|
+
step2
|
|
947
|
+
)
|
|
948
|
+
);
|
|
949
|
+
continue;
|
|
950
|
+
}
|
|
951
|
+
const existingStep = this.#findActivityStep(
|
|
952
|
+
scopedId("tool", event.data.toolCallId)
|
|
953
|
+
);
|
|
954
|
+
const step = {
|
|
955
|
+
id: scopedId("tool", event.data.toolCallId),
|
|
956
|
+
kind: "tool",
|
|
957
|
+
name: lifecycle?.name ?? (existingStep?.kind === "tool" ? existingStep.name : boundedText(event.data.toolDescription?.name ?? "unknown-tool", 256, "Tool")),
|
|
958
|
+
status: event.data.success ? "completed" : "failed"
|
|
959
|
+
};
|
|
960
|
+
changed.push(
|
|
961
|
+
this.#upsertActivityStep(
|
|
962
|
+
event.id,
|
|
963
|
+
event.timestamp,
|
|
964
|
+
turnId,
|
|
965
|
+
step,
|
|
966
|
+
lifecycle
|
|
967
|
+
)
|
|
968
|
+
);
|
|
969
|
+
const toolName = lifecycle?.name ?? existingStep?.name ?? "Copilot tool";
|
|
970
|
+
const unresolvedAssetIds = this.#unresolvedToolImageAssetIds(event);
|
|
971
|
+
if (unresolvedAssetIds.length > 0) {
|
|
972
|
+
const projection = { event, turnId, toolName };
|
|
973
|
+
for (const assetId of unresolvedAssetIds) {
|
|
974
|
+
let projections = this.#pendingToolImages.get(assetId);
|
|
975
|
+
if (projections === void 0) {
|
|
976
|
+
projections = /* @__PURE__ */ new Map();
|
|
977
|
+
this.#pendingToolImages.set(assetId, projections);
|
|
978
|
+
}
|
|
979
|
+
projections.set(event.data.toolCallId, projection);
|
|
980
|
+
}
|
|
981
|
+
} else {
|
|
982
|
+
changed.push(
|
|
983
|
+
...this.#projectToolImages(event, turnId, toolName)
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
continue;
|
|
987
|
+
}
|
|
988
|
+
if (event.type === "subagent.started") {
|
|
989
|
+
const turnId = this.#currentTurnId ?? protocolId(event.id);
|
|
990
|
+
const subagentId = event.agentId ?? event.data.toolCallId;
|
|
991
|
+
this.#subagentTurns.set(subagentId, turnId);
|
|
992
|
+
const step = {
|
|
993
|
+
id: scopedId("subagent", subagentId),
|
|
994
|
+
kind: "subagent",
|
|
995
|
+
name: boundedText(event.data.agentDisplayName, 256, "Subagent"),
|
|
996
|
+
status: "running",
|
|
997
|
+
description: boundedText(
|
|
998
|
+
event.data.agentDescription,
|
|
999
|
+
2048,
|
|
1000
|
+
"Subagent task"
|
|
1001
|
+
),
|
|
1002
|
+
...boundedOptionalText(event.data.model, 256) === void 0 ? {} : { model: boundedOptionalText(event.data.model, 256) }
|
|
1003
|
+
};
|
|
1004
|
+
changed.push(
|
|
1005
|
+
this.#upsertActivityStep(
|
|
1006
|
+
event.id,
|
|
1007
|
+
event.timestamp,
|
|
1008
|
+
turnId,
|
|
1009
|
+
step
|
|
1010
|
+
)
|
|
1011
|
+
);
|
|
1012
|
+
continue;
|
|
1013
|
+
}
|
|
1014
|
+
if (event.type === "subagent.completed") {
|
|
1015
|
+
const subagentId = event.agentId ?? event.data.toolCallId;
|
|
1016
|
+
const turnId = this.#subagentTurns.get(subagentId) ?? this.#currentTurnId ?? protocolId(event.id);
|
|
1017
|
+
const existingStep = this.#findActivityStep(
|
|
1018
|
+
scopedId("subagent", subagentId)
|
|
1019
|
+
);
|
|
1020
|
+
const step = {
|
|
1021
|
+
id: scopedId("subagent", subagentId),
|
|
1022
|
+
kind: "subagent",
|
|
1023
|
+
name: boundedText(event.data.agentDisplayName, 256, "Subagent"),
|
|
1024
|
+
status: "completed",
|
|
1025
|
+
...existingStep?.kind === "subagent" && existingStep.description !== void 0 ? { description: existingStep.description } : {},
|
|
1026
|
+
...event.data.model === void 0 ? existingStep?.kind === "subagent" && existingStep.model !== void 0 ? { model: existingStep.model } : {} : {
|
|
1027
|
+
model: boundedText(event.data.model, 256, "unknown-model")
|
|
1028
|
+
},
|
|
1029
|
+
...event.data.durationMs === void 0 ? {} : { durationMs: event.data.durationMs }
|
|
1030
|
+
};
|
|
1031
|
+
changed.push(
|
|
1032
|
+
this.#upsertActivityStep(
|
|
1033
|
+
event.id,
|
|
1034
|
+
event.timestamp,
|
|
1035
|
+
turnId,
|
|
1036
|
+
step
|
|
1037
|
+
)
|
|
1038
|
+
);
|
|
1039
|
+
continue;
|
|
1040
|
+
}
|
|
1041
|
+
if (event.type === "subagent.failed") {
|
|
1042
|
+
const subagentId = event.agentId ?? event.data.toolCallId;
|
|
1043
|
+
const turnId = this.#subagentTurns.get(subagentId) ?? this.#currentTurnId ?? protocolId(event.id);
|
|
1044
|
+
const existingStep = this.#findActivityStep(
|
|
1045
|
+
scopedId("subagent", subagentId)
|
|
1046
|
+
);
|
|
1047
|
+
const step = {
|
|
1048
|
+
id: scopedId("subagent", subagentId),
|
|
1049
|
+
kind: "subagent",
|
|
1050
|
+
name: boundedText(event.data.agentDisplayName, 256, "Subagent"),
|
|
1051
|
+
status: "failed",
|
|
1052
|
+
...existingStep?.kind === "subagent" && existingStep.description !== void 0 ? { description: existingStep.description } : {},
|
|
1053
|
+
...event.data.model === void 0 ? existingStep?.kind === "subagent" && existingStep.model !== void 0 ? { model: existingStep.model } : {} : {
|
|
1054
|
+
model: boundedText(event.data.model, 256, "unknown-model")
|
|
1055
|
+
},
|
|
1056
|
+
...event.data.durationMs === void 0 ? {} : { durationMs: event.data.durationMs },
|
|
1057
|
+
error: summarizeUnknown(event.data.error)
|
|
1058
|
+
};
|
|
1059
|
+
changed.push(
|
|
1060
|
+
this.#upsertActivityStep(
|
|
1061
|
+
event.id,
|
|
1062
|
+
event.timestamp,
|
|
1063
|
+
turnId,
|
|
1064
|
+
step
|
|
1065
|
+
)
|
|
1066
|
+
);
|
|
1067
|
+
continue;
|
|
1068
|
+
}
|
|
1069
|
+
if (event.type === "permission.requested") {
|
|
1070
|
+
const turnId = this.#currentTurnId ?? protocolId(event.id);
|
|
1071
|
+
const request = event.data.permissionRequest;
|
|
1072
|
+
this.#permissionTurns.set(event.data.requestId, turnId);
|
|
1073
|
+
const step = {
|
|
1074
|
+
id: scopedId("permission", event.data.requestId),
|
|
1075
|
+
kind: "permission",
|
|
1076
|
+
name: permissionName(request.kind),
|
|
1077
|
+
status: "running",
|
|
1078
|
+
...request.kind === "shell" ? {
|
|
1079
|
+
description: boundedText(
|
|
1080
|
+
request.intention,
|
|
1081
|
+
2048,
|
|
1082
|
+
"Shell command"
|
|
1083
|
+
)
|
|
1084
|
+
} : {}
|
|
1085
|
+
};
|
|
1086
|
+
changed.push(
|
|
1087
|
+
this.#upsertActivityStep(
|
|
1088
|
+
event.id,
|
|
1089
|
+
event.timestamp,
|
|
1090
|
+
turnId,
|
|
1091
|
+
step
|
|
1092
|
+
)
|
|
1093
|
+
);
|
|
1094
|
+
continue;
|
|
1095
|
+
}
|
|
1096
|
+
if (event.type === "permission.completed") {
|
|
1097
|
+
const turnId = this.#permissionTurns.get(event.data.requestId) ?? this.#currentTurnId ?? protocolId(event.id);
|
|
1098
|
+
const stepId = scopedId("permission", event.data.requestId);
|
|
1099
|
+
const existingStep = this.#findActivityStep(stepId);
|
|
1100
|
+
const result = event.data.result.kind;
|
|
1101
|
+
const failed = result === "cancelled" || result.startsWith("denied");
|
|
1102
|
+
const step = {
|
|
1103
|
+
id: stepId,
|
|
1104
|
+
kind: "permission",
|
|
1105
|
+
name: existingStep?.kind === "permission" ? existingStep.name : "Permission",
|
|
1106
|
+
status: failed ? "failed" : "completed",
|
|
1107
|
+
...existingStep?.kind === "permission" && existingStep.description !== void 0 ? { description: existingStep.description } : {},
|
|
1108
|
+
resultSummary: result
|
|
1109
|
+
};
|
|
1110
|
+
changed.push(
|
|
1111
|
+
this.#upsertActivityStep(
|
|
1112
|
+
event.id,
|
|
1113
|
+
event.timestamp,
|
|
1114
|
+
turnId,
|
|
1115
|
+
step
|
|
1116
|
+
)
|
|
1117
|
+
);
|
|
1118
|
+
continue;
|
|
1119
|
+
}
|
|
1120
|
+
if (event.type === "user_input.requested") {
|
|
1121
|
+
const turnId = this.#currentTurnId ?? protocolId(event.id);
|
|
1122
|
+
this.#permissionTurns.set(event.data.requestId, turnId);
|
|
1123
|
+
const step = {
|
|
1124
|
+
id: scopedId("permission", event.data.requestId),
|
|
1125
|
+
kind: "permission",
|
|
1126
|
+
name: "User input",
|
|
1127
|
+
status: "running",
|
|
1128
|
+
description: boundedText(event.data.question, 2048, "User input")
|
|
1129
|
+
};
|
|
1130
|
+
changed.push(
|
|
1131
|
+
this.#upsertActivityStep(
|
|
1132
|
+
event.id,
|
|
1133
|
+
event.timestamp,
|
|
1134
|
+
turnId,
|
|
1135
|
+
step
|
|
1136
|
+
)
|
|
1137
|
+
);
|
|
1138
|
+
continue;
|
|
1139
|
+
}
|
|
1140
|
+
if (event.type === "user_input.completed") {
|
|
1141
|
+
const turnId = this.#permissionTurns.get(event.data.requestId) ?? this.#currentTurnId ?? protocolId(event.id);
|
|
1142
|
+
const stepId = scopedId("permission", event.data.requestId);
|
|
1143
|
+
const existingStep = this.#findActivityStep(stepId);
|
|
1144
|
+
const step = {
|
|
1145
|
+
id: stepId,
|
|
1146
|
+
kind: "permission",
|
|
1147
|
+
name: "User input",
|
|
1148
|
+
status: "completed",
|
|
1149
|
+
...existingStep?.kind === "permission" && existingStep.description !== void 0 ? { description: existingStep.description } : {},
|
|
1150
|
+
resultSummary: event.data.answer === void 0 ? "completed" : boundedText(event.data.answer, 4096, "completed")
|
|
1151
|
+
};
|
|
1152
|
+
changed.push(
|
|
1153
|
+
this.#upsertActivityStep(
|
|
1154
|
+
event.id,
|
|
1155
|
+
event.timestamp,
|
|
1156
|
+
turnId,
|
|
1157
|
+
step
|
|
1158
|
+
)
|
|
1159
|
+
);
|
|
1160
|
+
continue;
|
|
1161
|
+
}
|
|
1162
|
+
if (event.type === "session.warning") {
|
|
1163
|
+
const turnId = this.#currentTurnId ?? protocolId(event.id);
|
|
1164
|
+
const warningType = event.data.warningType.replaceAll("_", " ");
|
|
1165
|
+
const warningLabel = boundedText(warningType, 240, "Session");
|
|
1166
|
+
const item = {
|
|
1167
|
+
id: scopedId("notice", event.id),
|
|
1168
|
+
kind: "notice",
|
|
1169
|
+
revision: 1,
|
|
1170
|
+
turnId,
|
|
1171
|
+
timestamp: event.timestamp,
|
|
1172
|
+
actor: event.agentId === void 0 ? "system" : "subagent",
|
|
1173
|
+
sourceEventIds: [event.id],
|
|
1174
|
+
severity: "warning",
|
|
1175
|
+
title: limitSummary(
|
|
1176
|
+
`${warningLabel.charAt(0).toUpperCase()}${warningLabel.slice(1)} warning`,
|
|
1177
|
+
256
|
|
1178
|
+
),
|
|
1179
|
+
content: boundedText(event.data.message, 4096, "Session warning")
|
|
1180
|
+
};
|
|
1181
|
+
this.#items.set(item.id, item);
|
|
1182
|
+
changed.push(item);
|
|
1183
|
+
continue;
|
|
1184
|
+
}
|
|
1185
|
+
if (event.type === "session.error") {
|
|
1186
|
+
const turnId = this.#currentTurnId ?? protocolId(event.id);
|
|
1187
|
+
const errorType = event.data.errorType.replaceAll("_", " ");
|
|
1188
|
+
const errorLabel = boundedText(errorType, 240, "Session");
|
|
1189
|
+
const title = `${errorLabel.charAt(0).toUpperCase()}${errorLabel.slice(1)} error`;
|
|
1190
|
+
const item = {
|
|
1191
|
+
id: scopedId("notice", event.id),
|
|
1192
|
+
kind: "notice",
|
|
1193
|
+
revision: 1,
|
|
1194
|
+
turnId,
|
|
1195
|
+
timestamp: event.timestamp,
|
|
1196
|
+
actor: event.agentId === void 0 ? "system" : "subagent",
|
|
1197
|
+
sourceEventIds: [event.id],
|
|
1198
|
+
severity: "error",
|
|
1199
|
+
title: limitSummary(
|
|
1200
|
+
event.data.errorCode === void 0 ? title : `${title} \xB7 ${event.data.errorCode}`,
|
|
1201
|
+
256
|
|
1202
|
+
),
|
|
1203
|
+
content: boundedText(event.data.message, 4096, "Session error")
|
|
1204
|
+
};
|
|
1205
|
+
this.#items.set(item.id, item);
|
|
1206
|
+
changed.push(item);
|
|
1207
|
+
continue;
|
|
1208
|
+
}
|
|
1209
|
+
if (event.type === "system.notification" && event.data.kind.type === "shell_completed") {
|
|
1210
|
+
const turnId = this.#currentTurnId ?? protocolId(event.id);
|
|
1211
|
+
const kind = event.data.kind;
|
|
1212
|
+
const description = boundedText(
|
|
1213
|
+
kind.description ?? `Shell ${kind.shellId}`,
|
|
1214
|
+
4096,
|
|
1215
|
+
"Shell completed"
|
|
1216
|
+
);
|
|
1217
|
+
const item = {
|
|
1218
|
+
id: scopedId("notice", event.id),
|
|
1219
|
+
kind: "notice",
|
|
1220
|
+
revision: 1,
|
|
1221
|
+
turnId,
|
|
1222
|
+
timestamp: event.timestamp,
|
|
1223
|
+
actor: event.agentId === void 0 ? "system" : "subagent",
|
|
1224
|
+
sourceEventIds: [event.id],
|
|
1225
|
+
severity: kind.exitCode === void 0 || kind.exitCode === 0 ? "success" : "error",
|
|
1226
|
+
title: "Shell completed",
|
|
1227
|
+
content: kind.exitCode === void 0 ? description : limitSummary(`${description} \xB7 exit ${kind.exitCode}`, 4096)
|
|
1228
|
+
};
|
|
1229
|
+
this.#items.set(item.id, item);
|
|
1230
|
+
changed.push(item);
|
|
1231
|
+
continue;
|
|
1232
|
+
}
|
|
1233
|
+
if (event.type === "session.plan_changed") {
|
|
1234
|
+
const turnId = this.#currentTurnId ?? protocolId(event.id);
|
|
1235
|
+
const id = scopedId("plan", turnId);
|
|
1236
|
+
const existing = this.#items.get(id);
|
|
1237
|
+
const operation = event.data.operation === "create" ? "created" : event.data.operation === "update" ? "updated" : "deleted";
|
|
1238
|
+
const item = {
|
|
1239
|
+
id,
|
|
1240
|
+
kind: "plan-update",
|
|
1241
|
+
revision: eventRevision(
|
|
1242
|
+
event.timestamp,
|
|
1243
|
+
existing?.kind === "plan-update" ? existing.revision : void 0
|
|
1244
|
+
),
|
|
1245
|
+
turnId,
|
|
1246
|
+
timestamp: existing?.kind === "plan-update" ? existing.timestamp : event.timestamp,
|
|
1247
|
+
actor: event.agentId === void 0 ? "assistant" : "subagent",
|
|
1248
|
+
sourceEventIds: existing?.kind === "plan-update" ? appendSourceEventId(existing.sourceEventIds, event.id) : [event.id],
|
|
1249
|
+
operation,
|
|
1250
|
+
content: `Plan ${operation}`,
|
|
1251
|
+
todos: []
|
|
1252
|
+
};
|
|
1253
|
+
this.#items.set(id, item);
|
|
1254
|
+
changed.push(item);
|
|
1255
|
+
continue;
|
|
1256
|
+
}
|
|
1257
|
+
if (event.type === "assistant.message") {
|
|
1258
|
+
if (event.agentId !== void 0 || event.data.parentToolCallId !== void 0) {
|
|
1259
|
+
continue;
|
|
1260
|
+
}
|
|
1261
|
+
if (event.data.content.trim().length === 0 && (event.data.citations?.sources.length ?? 0) === 0) {
|
|
1262
|
+
continue;
|
|
1263
|
+
}
|
|
1264
|
+
const turnId = protocolId(
|
|
1265
|
+
event.data.interactionId ?? (event.data.turnId === void 0 ? this.#currentTurnId : this.#loopTurns.get(event.data.turnId)?.turnId) ?? event.data.turnId ?? event.id
|
|
1266
|
+
);
|
|
1267
|
+
const id = assistantMessageId(event.data);
|
|
1268
|
+
const existing = this.#items.get(id);
|
|
1269
|
+
const citationAttachments = event.data.citations?.sources.slice(0, 100).map(projectCitation).map(boundAttachment) ?? [];
|
|
1270
|
+
const attachments = [
|
|
1271
|
+
...existing?.kind === "assistant-message" ? existing.attachments ?? [] : [],
|
|
1272
|
+
...citationAttachments
|
|
1273
|
+
].filter(
|
|
1274
|
+
(attachment, index, all) => all.findIndex(
|
|
1275
|
+
(candidate) => candidate.kind === attachment.kind && candidate.name === attachment.name && candidate.uri === attachment.uri
|
|
1276
|
+
) === index
|
|
1277
|
+
).slice(-100);
|
|
1278
|
+
const sourceMessageIds = appendOptionalSourceId(
|
|
1279
|
+
existing?.kind === "assistant-message" ? existing.sourceMessageIds : void 0,
|
|
1280
|
+
protocolId(event.data.messageId)
|
|
1281
|
+
);
|
|
1282
|
+
const sourceInteractionIds = appendOptionalSourceId(
|
|
1283
|
+
existing?.kind === "assistant-message" ? existing.sourceInteractionIds : void 0,
|
|
1284
|
+
event.data.interactionId === void 0 ? void 0 : protocolId(event.data.interactionId)
|
|
1285
|
+
);
|
|
1286
|
+
const item = {
|
|
1287
|
+
id,
|
|
1288
|
+
kind: "assistant-message",
|
|
1289
|
+
revision: eventRevision(
|
|
1290
|
+
event.timestamp,
|
|
1291
|
+
existing?.kind === "assistant-message" ? existing.revision : void 0
|
|
1292
|
+
),
|
|
1293
|
+
turnId,
|
|
1294
|
+
timestamp: existing?.kind === "assistant-message" ? existing.timestamp : event.timestamp,
|
|
1295
|
+
actor: "assistant",
|
|
1296
|
+
sourceEventIds: existing?.kind === "assistant-message" ? appendSourceEventId(existing.sourceEventIds, event.id) : [event.id],
|
|
1297
|
+
...sourceMessageIds === void 0 ? {} : { sourceMessageIds },
|
|
1298
|
+
...sourceInteractionIds === void 0 ? {} : { sourceInteractionIds },
|
|
1299
|
+
content: appendAssistantContent(
|
|
1300
|
+
existing?.kind === "assistant-message" ? existing.content : "",
|
|
1301
|
+
event.data.content
|
|
1302
|
+
),
|
|
1303
|
+
...attachments.length === 0 ? {} : { attachments },
|
|
1304
|
+
...event.data.model === void 0 ? existing?.kind === "assistant-message" && existing.model !== void 0 ? { model: existing.model } : {} : {
|
|
1305
|
+
model: boundedText(event.data.model, 256, "unknown-model")
|
|
1306
|
+
},
|
|
1307
|
+
final: event.data.chunkCount === void 0 || event.data.chunkIndex === void 0 || event.data.chunkIndex + 1 >= event.data.chunkCount
|
|
1308
|
+
};
|
|
1309
|
+
this.#currentTurnId = turnId;
|
|
1310
|
+
this.#hasCurrentTurnContext = event.data.interactionId !== void 0 || (event.data.turnId === void 0 ? this.#hasCurrentTurnContext : this.#loopTurns.get(event.data.turnId)?.hasTurnContext ?? false);
|
|
1311
|
+
this.#items.set(id, item);
|
|
1312
|
+
changed.push(item);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
return changed;
|
|
1316
|
+
}
|
|
1317
|
+
snapshot() {
|
|
1318
|
+
return [...this.#items.values()];
|
|
1319
|
+
}
|
|
1320
|
+
pendingImageAssetIds() {
|
|
1321
|
+
return [...this.#pendingToolImages.keys()];
|
|
1322
|
+
}
|
|
1323
|
+
#projectTaskCompletion(event, turnId) {
|
|
1324
|
+
if (!event.data.success) {
|
|
1325
|
+
return void 0;
|
|
1326
|
+
}
|
|
1327
|
+
const content = boundedOptionalMessageContent(
|
|
1328
|
+
event.data.result?.content,
|
|
1329
|
+
4096
|
|
1330
|
+
);
|
|
1331
|
+
if (content === void 0) {
|
|
1332
|
+
return void 0;
|
|
1333
|
+
}
|
|
1334
|
+
const id = scopedId("notice", event.data.toolCallId);
|
|
1335
|
+
const existing = this.#items.get(id);
|
|
1336
|
+
const item = {
|
|
1337
|
+
id,
|
|
1338
|
+
kind: "notice",
|
|
1339
|
+
revision: eventRevision(
|
|
1340
|
+
event.timestamp,
|
|
1341
|
+
existing?.kind === "notice" ? existing.revision : void 0
|
|
1342
|
+
),
|
|
1343
|
+
turnId,
|
|
1344
|
+
timestamp: existing?.kind === "notice" ? existing.timestamp : event.timestamp,
|
|
1345
|
+
actor: event.agentId !== void 0 || event.data.parentToolCallId !== void 0 || this.#taskCompletionToolCalls.get(event.data.toolCallId) === "subagent" || existing?.kind === "notice" && existing.actor === "subagent" ? "subagent" : "assistant",
|
|
1346
|
+
sourceEventIds: existing?.kind === "notice" ? appendSourceEventId(existing.sourceEventIds, event.id) : [event.id],
|
|
1347
|
+
severity: "success",
|
|
1348
|
+
title: "Task completed",
|
|
1349
|
+
content
|
|
1350
|
+
};
|
|
1351
|
+
this.#items.set(id, item);
|
|
1352
|
+
return item;
|
|
1353
|
+
}
|
|
1354
|
+
#unresolvedToolImageAssetIds(event) {
|
|
1355
|
+
return (event.data.result?.binaryResultsForLlm?.filter(
|
|
1356
|
+
(result) => result.type === "image" && "assetId" in result
|
|
1357
|
+
).map((result) => result.assetId).filter((assetId) => !this.#binaryAssets.has(assetId)) ?? []).filter((assetId, index, all) => all.indexOf(assetId) === index);
|
|
1358
|
+
}
|
|
1359
|
+
#removePendingToolImage(event) {
|
|
1360
|
+
for (const [assetId, projections] of this.#pendingToolImages) {
|
|
1361
|
+
projections.delete(event.data.toolCallId);
|
|
1362
|
+
if (projections.size === 0) {
|
|
1363
|
+
this.#pendingToolImages.delete(assetId);
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
#projectToolImages(event, turnId, toolName) {
|
|
1368
|
+
const binaryImages = event.data.result?.binaryResultsForLlm?.map(
|
|
1369
|
+
(result, index) => projectToolBinaryResult(
|
|
1370
|
+
result,
|
|
1371
|
+
`${toolName} image ${index + 1}`,
|
|
1372
|
+
this.#binaryAssets
|
|
1373
|
+
)
|
|
1374
|
+
).filter(
|
|
1375
|
+
(image) => image !== void 0
|
|
1376
|
+
) ?? [];
|
|
1377
|
+
const contentImages = event.data.result?.contents?.filter(
|
|
1378
|
+
(content) => content.type === "image"
|
|
1379
|
+
).map(
|
|
1380
|
+
(image, index) => projectToolContentImage(
|
|
1381
|
+
image,
|
|
1382
|
+
`${toolName} image ${binaryImages.length + index + 1}`
|
|
1383
|
+
)
|
|
1384
|
+
) ?? [];
|
|
1385
|
+
const projectedImages = [...binaryImages, ...contentImages].filter(
|
|
1386
|
+
(image, index, all) => all.findIndex(
|
|
1387
|
+
(candidate) => [
|
|
1388
|
+
candidate.attachment.mimeType,
|
|
1389
|
+
candidate.attachment.sha256 ?? candidate.attachment.data ?? candidate.attachment.name
|
|
1390
|
+
].join("\0") === [
|
|
1391
|
+
image.attachment.mimeType,
|
|
1392
|
+
image.attachment.sha256 ?? image.attachment.data ?? image.attachment.name
|
|
1393
|
+
].join("\0")
|
|
1394
|
+
) === index
|
|
1395
|
+
);
|
|
1396
|
+
for (const image of projectedImages) {
|
|
1397
|
+
if (image.failure === void 0) {
|
|
1398
|
+
continue;
|
|
1399
|
+
}
|
|
1400
|
+
const fingerprint = [
|
|
1401
|
+
event.data.toolCallId,
|
|
1402
|
+
image.failure.source,
|
|
1403
|
+
image.failure.reason,
|
|
1404
|
+
image.failure.mimeType,
|
|
1405
|
+
image.failure.expectedByteLength,
|
|
1406
|
+
image.failure.actualByteLength
|
|
1407
|
+
].join("\0");
|
|
1408
|
+
if (this.#reportedImageFailures.has(fingerprint)) {
|
|
1409
|
+
continue;
|
|
1410
|
+
}
|
|
1411
|
+
this.#reportedImageFailures.add(fingerprint);
|
|
1412
|
+
this.onImageFailure?.({
|
|
1413
|
+
...image.failure,
|
|
1414
|
+
toolCallId: event.data.toolCallId,
|
|
1415
|
+
toolName
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
const imageGroups = splitToolImages(projectedImages);
|
|
1419
|
+
return imageGroups.map((imageGroup, groupIndex) => {
|
|
1420
|
+
const multipleGroups = imageGroups.length > 1;
|
|
1421
|
+
const id = scopedId(
|
|
1422
|
+
"tool-image",
|
|
1423
|
+
multipleGroups ? `${event.data.toolCallId}:${groupIndex + 1}` : event.data.toolCallId
|
|
1424
|
+
);
|
|
1425
|
+
const existing = this.#items.get(id);
|
|
1426
|
+
const imageItem = {
|
|
1427
|
+
id,
|
|
1428
|
+
kind: "assistant-message",
|
|
1429
|
+
// The same source events must produce the same revision in live and batched history reads.
|
|
1430
|
+
revision: Math.max(
|
|
1431
|
+
eventRevision(event.timestamp, existing?.kind === "assistant-message" ? existing.revision : void 0),
|
|
1432
|
+
...imageGroup.flatMap((image) => image.sourceTimestamp === void 0 ? [] : [eventRevision(image.sourceTimestamp)])
|
|
1433
|
+
),
|
|
1434
|
+
turnId,
|
|
1435
|
+
timestamp: existing?.kind === "assistant-message" ? existing.timestamp : event.timestamp,
|
|
1436
|
+
actor: event.agentId !== void 0 || event.data.parentToolCallId !== void 0 || this.toolLifecycles.get(event.data.toolCallId)?.actor === "subagent" ? "subagent" : "assistant",
|
|
1437
|
+
sourceEventIds: [
|
|
1438
|
+
...imageGroup.flatMap(
|
|
1439
|
+
(image) => image.sourceEventId === void 0 ? [] : [image.sourceEventId]
|
|
1440
|
+
),
|
|
1441
|
+
event.id
|
|
1442
|
+
].filter(
|
|
1443
|
+
(sourceEventId, index, all) => all.indexOf(sourceEventId) === index
|
|
1444
|
+
),
|
|
1445
|
+
content: "",
|
|
1446
|
+
attachments: imageGroup.map((image) => image.attachment),
|
|
1447
|
+
...event.agentId === void 0 ? {} : { agentId: protocolId(event.agentId) },
|
|
1448
|
+
final: true
|
|
1449
|
+
};
|
|
1450
|
+
this.#items.set(imageItem.id, imageItem);
|
|
1451
|
+
return imageItem;
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
#resolveTurnId(loopTurnId, fallback) {
|
|
1455
|
+
return protocolId(
|
|
1456
|
+
(loopTurnId === void 0 ? void 0 : this.#loopTurns.get(loopTurnId)?.turnId) ?? loopTurnId ?? fallback
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1459
|
+
#findActivityStep(stepId) {
|
|
1460
|
+
const activity = this.#items.get(scopedId("activity", stepId));
|
|
1461
|
+
return activity?.kind === "activity-group" ? activity.steps[0] : void 0;
|
|
1462
|
+
}
|
|
1463
|
+
#upsertActivityStep(eventId, timestamp, turnId, step, lifecycle) {
|
|
1464
|
+
const id = scopedId("activity", step.id);
|
|
1465
|
+
const existing = this.#items.get(id);
|
|
1466
|
+
const existingGroup = existing?.kind === "activity-group" ? existing : void 0;
|
|
1467
|
+
const item = {
|
|
1468
|
+
id,
|
|
1469
|
+
kind: "activity-group",
|
|
1470
|
+
revision: eventRevision(
|
|
1471
|
+
timestamp,
|
|
1472
|
+
existingGroup?.revision ?? (lifecycle === void 0 ? void 0 : Date.parse(lifecycle.timestamp))
|
|
1473
|
+
),
|
|
1474
|
+
turnId,
|
|
1475
|
+
timestamp: lifecycle?.timestamp ?? existingGroup?.timestamp ?? timestamp,
|
|
1476
|
+
actor: "assistant",
|
|
1477
|
+
sourceEventIds: existingGroup === void 0 ? lifecycle === void 0 ? [eventId] : appendSourceEventId([lifecycle.sourceEventId], eventId) : appendSourceEventId(existingGroup.sourceEventIds, eventId),
|
|
1478
|
+
status: step.status,
|
|
1479
|
+
steps: [{
|
|
1480
|
+
...step,
|
|
1481
|
+
timestamp: lifecycle?.timestamp ?? existingGroup?.steps[0]?.timestamp ?? timestamp
|
|
1482
|
+
}]
|
|
1483
|
+
};
|
|
1484
|
+
this.#items.set(id, item);
|
|
1485
|
+
return item;
|
|
1486
|
+
}
|
|
1487
|
+
};
|
|
1488
|
+
|
|
1489
|
+
// src/session-runtime.ts
|
|
1490
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
1491
|
+
|
|
1492
|
+
// src/timeline-read-flight.ts
|
|
1493
|
+
import { randomUUID } from "node:crypto";
|
|
1494
|
+
var MAX_IN_FLIGHT_KEYS = 32;
|
|
1495
|
+
var TimelineReadFlights = class {
|
|
1496
|
+
#reads = /* @__PURE__ */ new Map();
|
|
1497
|
+
get size() {
|
|
1498
|
+
return this.#reads.size;
|
|
1499
|
+
}
|
|
1500
|
+
invalidate() {
|
|
1501
|
+
this.#reads.clear();
|
|
1502
|
+
}
|
|
1503
|
+
invalidateContent() {
|
|
1504
|
+
for (const [key, flight] of this.#reads) {
|
|
1505
|
+
if (flight.state.reading) this.#reads.delete(key);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
read(options, load, report) {
|
|
1509
|
+
const key = JSON.stringify([
|
|
1510
|
+
options.cursor ?? null,
|
|
1511
|
+
options.pageSize,
|
|
1512
|
+
options.metadataOnly,
|
|
1513
|
+
options.assistantPreview === true
|
|
1514
|
+
]);
|
|
1515
|
+
const existing = this.#reads.get(key);
|
|
1516
|
+
if (existing !== void 0) {
|
|
1517
|
+
report?.({
|
|
1518
|
+
readRef: existing.readRef,
|
|
1519
|
+
readStarts: 0,
|
|
1520
|
+
readJoins: 1,
|
|
1521
|
+
untrackedReads: 0,
|
|
1522
|
+
inFlightReads: this.size
|
|
1523
|
+
});
|
|
1524
|
+
return existing.promise;
|
|
1525
|
+
}
|
|
1526
|
+
const readRef = randomUUID();
|
|
1527
|
+
const state = { reading: false };
|
|
1528
|
+
const pending = load(() => {
|
|
1529
|
+
state.reading = true;
|
|
1530
|
+
});
|
|
1531
|
+
if (this.size >= MAX_IN_FLIGHT_KEYS) {
|
|
1532
|
+
report?.({
|
|
1533
|
+
readRef,
|
|
1534
|
+
readStarts: 1,
|
|
1535
|
+
readJoins: 0,
|
|
1536
|
+
untrackedReads: 1,
|
|
1537
|
+
inFlightReads: this.size
|
|
1538
|
+
});
|
|
1539
|
+
return pending;
|
|
1540
|
+
}
|
|
1541
|
+
const read = pending.finally(() => {
|
|
1542
|
+
if (this.#reads.get(key)?.promise === read) {
|
|
1543
|
+
this.#reads.delete(key);
|
|
1544
|
+
}
|
|
1545
|
+
});
|
|
1546
|
+
this.#reads.set(key, { readRef, promise: read, state });
|
|
1547
|
+
report?.({
|
|
1548
|
+
readRef,
|
|
1549
|
+
readStarts: 1,
|
|
1550
|
+
readJoins: 0,
|
|
1551
|
+
untrackedReads: 0,
|
|
1552
|
+
inFlightReads: this.size
|
|
1553
|
+
});
|
|
1554
|
+
return read;
|
|
1555
|
+
}
|
|
1556
|
+
};
|
|
1557
|
+
|
|
1558
|
+
// src/session-runtime.ts
|
|
1559
|
+
var SessionRuntime = class {
|
|
1560
|
+
workbench = {
|
|
1561
|
+
identity: randomUUID2(),
|
|
1562
|
+
activeMutations: 0,
|
|
1563
|
+
activeQuestions: 0,
|
|
1564
|
+
compacting: false
|
|
1565
|
+
};
|
|
1566
|
+
admitMutation() {
|
|
1567
|
+
if (this.workbench.compacting) {
|
|
1568
|
+
throw new Error("Session context compaction is in progress");
|
|
1569
|
+
}
|
|
1570
|
+
this.workbench.activeMutations += 1;
|
|
1571
|
+
return () => {
|
|
1572
|
+
this.workbench.activeMutations -= 1;
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1575
|
+
timelineReads = new TimelineReadFlights();
|
|
1576
|
+
toolLifecycles = new ConversationToolLifecycles();
|
|
1577
|
+
connection = {
|
|
1578
|
+
generation: 0,
|
|
1579
|
+
session: void 0,
|
|
1580
|
+
reload: void 0,
|
|
1581
|
+
sendAwareReload: false,
|
|
1582
|
+
sendReady: false,
|
|
1583
|
+
sendInProgressCount: 0,
|
|
1584
|
+
sendCompletionWaiters: /* @__PURE__ */ new Set()
|
|
1585
|
+
};
|
|
1586
|
+
follower = {
|
|
1587
|
+
current: void 0,
|
|
1588
|
+
recoveryAttempt: 0,
|
|
1589
|
+
recoveryTimer: void 0
|
|
1590
|
+
};
|
|
1591
|
+
live = {
|
|
1592
|
+
recentEventIds: { ids: /* @__PURE__ */ new Set(), order: [] },
|
|
1593
|
+
normalizer: void 0,
|
|
1594
|
+
initializingEvents: void 0
|
|
1595
|
+
};
|
|
1596
|
+
activity = {
|
|
1597
|
+
activeRootTurn: false,
|
|
1598
|
+
busy: false,
|
|
1599
|
+
turnEndIdleTimer: void 0
|
|
1600
|
+
};
|
|
1601
|
+
plan = {
|
|
1602
|
+
timer: void 0,
|
|
1603
|
+
generation: 0,
|
|
1604
|
+
pending: void 0,
|
|
1605
|
+
run: void 0,
|
|
1606
|
+
snapshotRevision: 0
|
|
1607
|
+
};
|
|
1608
|
+
configuration = {
|
|
1609
|
+
transition: void 0
|
|
1610
|
+
};
|
|
1611
|
+
watched = false;
|
|
1612
|
+
stopFollower() {
|
|
1613
|
+
if (this.follower.current !== void 0) {
|
|
1614
|
+
this.follower.current.stopped = true;
|
|
1615
|
+
this.follower.current = void 0;
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
clearFollowerRecovery() {
|
|
1619
|
+
if (this.follower.recoveryTimer !== void 0) {
|
|
1620
|
+
clearTimeout(this.follower.recoveryTimer);
|
|
1621
|
+
this.follower.recoveryTimer = void 0;
|
|
1622
|
+
}
|
|
1623
|
+
this.follower.recoveryAttempt = 0;
|
|
1624
|
+
}
|
|
1625
|
+
clearTurnEndIdleTimer() {
|
|
1626
|
+
if (this.activity.turnEndIdleTimer !== void 0) {
|
|
1627
|
+
clearTimeout(this.activity.turnEndIdleTimer);
|
|
1628
|
+
this.activity.turnEndIdleTimer = void 0;
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
clearPlanSyncScheduling() {
|
|
1632
|
+
if (this.plan.timer !== void 0) {
|
|
1633
|
+
clearTimeout(this.plan.timer);
|
|
1634
|
+
this.plan.timer = void 0;
|
|
1635
|
+
}
|
|
1636
|
+
this.plan.pending = void 0;
|
|
1637
|
+
this.plan.generation = 0;
|
|
1638
|
+
}
|
|
1639
|
+
unwatch() {
|
|
1640
|
+
this.watched = false;
|
|
1641
|
+
this.clearPlanSyncScheduling();
|
|
1642
|
+
this.clearTurnEndIdleTimer();
|
|
1643
|
+
this.activity.activeRootTurn = false;
|
|
1644
|
+
this.stopFollower();
|
|
1645
|
+
this.clearFollowerRecovery();
|
|
1646
|
+
this.live.initializingEvents = void 0;
|
|
1647
|
+
}
|
|
1648
|
+
prepareReload(preserveTimelineState) {
|
|
1649
|
+
this.timelineReads.invalidate();
|
|
1650
|
+
this.connection.sendReady = false;
|
|
1651
|
+
this.stopFollower();
|
|
1652
|
+
if (!preserveTimelineState) {
|
|
1653
|
+
this.live.recentEventIds.ids.clear();
|
|
1654
|
+
this.live.recentEventIds.order.length = 0;
|
|
1655
|
+
this.live.normalizer = void 0;
|
|
1656
|
+
}
|
|
1657
|
+
this.clearTurnEndIdleTimer();
|
|
1658
|
+
this.live.initializingEvents = this.watched ? [] : void 0;
|
|
1659
|
+
}
|
|
1660
|
+
stopResources() {
|
|
1661
|
+
this.timelineReads.invalidate();
|
|
1662
|
+
this.stopFollower();
|
|
1663
|
+
this.clearFollowerRecovery();
|
|
1664
|
+
this.clearTurnEndIdleTimer();
|
|
1665
|
+
this.clearPlanSyncScheduling();
|
|
1666
|
+
}
|
|
1667
|
+
invalidateConnection() {
|
|
1668
|
+
this.connection.generation += 1;
|
|
1669
|
+
this.stopResources();
|
|
1670
|
+
this.prepareReload(true);
|
|
1671
|
+
this.connection.session = void 0;
|
|
1672
|
+
this.connection.reload = void 0;
|
|
1673
|
+
this.connection.sendAwareReload = false;
|
|
1674
|
+
this.activity.activeRootTurn = false;
|
|
1675
|
+
this.activity.busy = false;
|
|
1676
|
+
}
|
|
1677
|
+
canDisposeAfterConnectionFailure() {
|
|
1678
|
+
return this.connection.session === void 0 && this.connection.reload === void 0 && this.connection.sendInProgressCount === 0 && this.connection.sendCompletionWaiters.size === 0 && this.workbench.activeMutations === 0 && this.workbench.activeQuestions === 0 && !this.workbench.compacting && !this.watched && this.follower.current === void 0 && this.follower.recoveryTimer === void 0 && this.activity.turnEndIdleTimer === void 0 && this.plan.timer === void 0 && this.plan.pending === void 0 && this.plan.run === void 0 && this.timelineReads.size === 0 && this.configuration.transition === void 0;
|
|
1679
|
+
}
|
|
1680
|
+
dispose() {
|
|
1681
|
+
this.connection.generation += 1;
|
|
1682
|
+
this.stopResources();
|
|
1683
|
+
this.toolLifecycles.clear();
|
|
1684
|
+
for (const resolve2 of this.connection.sendCompletionWaiters) {
|
|
1685
|
+
resolve2();
|
|
1686
|
+
}
|
|
1687
|
+
this.connection.sendCompletionWaiters.clear();
|
|
1688
|
+
this.connection.session = void 0;
|
|
1689
|
+
this.connection.reload = void 0;
|
|
1690
|
+
this.connection.sendAwareReload = false;
|
|
1691
|
+
this.connection.sendReady = false;
|
|
1692
|
+
this.connection.sendInProgressCount = 0;
|
|
1693
|
+
this.live.recentEventIds.ids.clear();
|
|
1694
|
+
this.live.recentEventIds.order.length = 0;
|
|
1695
|
+
this.live.normalizer = void 0;
|
|
1696
|
+
this.live.initializingEvents = void 0;
|
|
1697
|
+
this.activity.activeRootTurn = false;
|
|
1698
|
+
this.activity.busy = false;
|
|
1699
|
+
this.plan.pending = void 0;
|
|
1700
|
+
this.plan.generation = 0;
|
|
1701
|
+
this.plan.run = void 0;
|
|
1702
|
+
this.plan.snapshotRevision = 0;
|
|
1703
|
+
this.configuration.transition = void 0;
|
|
1704
|
+
this.watched = false;
|
|
1705
|
+
}
|
|
1706
|
+
};
|
|
1707
|
+
var SessionRuntimeRegistry = class {
|
|
1708
|
+
#runtimes = /* @__PURE__ */ new Map();
|
|
1709
|
+
getOrCreate(sessionId) {
|
|
1710
|
+
const existing = this.#runtimes.get(sessionId);
|
|
1711
|
+
if (existing !== void 0) {
|
|
1712
|
+
return existing;
|
|
1713
|
+
}
|
|
1714
|
+
const runtime = new SessionRuntime();
|
|
1715
|
+
this.#runtimes.set(sessionId, runtime);
|
|
1716
|
+
return runtime;
|
|
1717
|
+
}
|
|
1718
|
+
peek(sessionId) {
|
|
1719
|
+
return this.#runtimes.get(sessionId);
|
|
1720
|
+
}
|
|
1721
|
+
delete(sessionId) {
|
|
1722
|
+
const runtime = this.#runtimes.get(sessionId);
|
|
1723
|
+
if (runtime !== void 0) {
|
|
1724
|
+
runtime.dispose();
|
|
1725
|
+
this.#runtimes.delete(sessionId);
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
deleteIf(sessionId, runtime) {
|
|
1729
|
+
if (this.#runtimes.get(sessionId) !== runtime) {
|
|
1730
|
+
return false;
|
|
1731
|
+
}
|
|
1732
|
+
runtime.dispose();
|
|
1733
|
+
this.#runtimes.delete(sessionId);
|
|
1734
|
+
return true;
|
|
1735
|
+
}
|
|
1736
|
+
stopResources() {
|
|
1737
|
+
for (const runtime of this.#runtimes.values()) {
|
|
1738
|
+
runtime.stopResources();
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
invalidateConnections() {
|
|
1742
|
+
const watched = [];
|
|
1743
|
+
for (const [sessionId, runtime] of this.#runtimes) {
|
|
1744
|
+
runtime.invalidateConnection();
|
|
1745
|
+
if (runtime.watched) {
|
|
1746
|
+
watched.push(sessionId);
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
return watched;
|
|
1750
|
+
}
|
|
1751
|
+
clear() {
|
|
1752
|
+
for (const runtime of this.#runtimes.values()) {
|
|
1753
|
+
runtime.dispose();
|
|
1754
|
+
}
|
|
1755
|
+
this.#runtimes.clear();
|
|
1756
|
+
}
|
|
1757
|
+
};
|
|
1758
|
+
|
|
1759
|
+
// src/adapters/copilot/timeline/live-projection.ts
|
|
1760
|
+
var RECENT_EVENT_ID_LIMIT = 4e3;
|
|
1761
|
+
var TURN_END_IDLE_FALLBACK_MS = 5e3;
|
|
1762
|
+
var LiveTimelineProjection = class {
|
|
1763
|
+
constructor(ports) {
|
|
1764
|
+
this.ports = ports;
|
|
1765
|
+
}
|
|
1766
|
+
ports;
|
|
1767
|
+
#previews = /* @__PURE__ */ new WeakMap();
|
|
1768
|
+
watch(sessionId) {
|
|
1769
|
+
const runtime = this.ports.state(sessionId);
|
|
1770
|
+
if (!runtime.watched) {
|
|
1771
|
+
runtime.live.normalizer = void 0;
|
|
1772
|
+
runtime.live.recentEventIds.ids.clear();
|
|
1773
|
+
runtime.live.recentEventIds.order.length = 0;
|
|
1774
|
+
this.#previews.delete(runtime.live);
|
|
1775
|
+
}
|
|
1776
|
+
runtime.watched = true;
|
|
1777
|
+
}
|
|
1778
|
+
queueLiveEvent(sessionId, event) {
|
|
1779
|
+
const runtime = this.ports.state(sessionId);
|
|
1780
|
+
runtime.timelineReads.invalidateContent();
|
|
1781
|
+
this.updateSessionActivity(sessionId, event);
|
|
1782
|
+
this.ports.observeLive(sessionId, event);
|
|
1783
|
+
if (runtime.watched) this.ports.directEvent(sessionId);
|
|
1784
|
+
const initializing = runtime.live.initializingEvents;
|
|
1785
|
+
if (initializing !== void 0) {
|
|
1786
|
+
initializing.push(event);
|
|
1787
|
+
return;
|
|
1788
|
+
}
|
|
1789
|
+
this.forwardEvent(sessionId, event);
|
|
1790
|
+
}
|
|
1791
|
+
previewDelta(sessionId, event) {
|
|
1792
|
+
const key = this.ports.state(sessionId).live;
|
|
1793
|
+
if (event.type !== "assistant.message_delta" || event.agentId !== void 0 || event.data.parentToolCallId !== void 0) return;
|
|
1794
|
+
if (event.data.deltaContent.length === 0) return;
|
|
1795
|
+
let state = this.#previews.get(key);
|
|
1796
|
+
if (state === void 0) {
|
|
1797
|
+
state = { deltaContent: "" };
|
|
1798
|
+
this.#previews.set(key, state);
|
|
1799
|
+
}
|
|
1800
|
+
if (state.messageId !== event.data.messageId) {
|
|
1801
|
+
state.messageId = event.data.messageId;
|
|
1802
|
+
state.deltaContent = "";
|
|
1803
|
+
delete state.streaming;
|
|
1804
|
+
}
|
|
1805
|
+
state.deltaContent = (state.deltaContent + event.data.deltaContent).slice(0, 4096);
|
|
1806
|
+
const canonical = state.canonical?.final === false ? state.canonical : void 0;
|
|
1807
|
+
const previous = state.streaming ?? canonical;
|
|
1808
|
+
const revision = Date.parse(event.timestamp) - 1;
|
|
1809
|
+
if (previous !== void 0 && revision <= previous.revision) return;
|
|
1810
|
+
const item = {
|
|
1811
|
+
id: canonical?.id ?? assistantMessageId(event.data),
|
|
1812
|
+
kind: "assistant-message",
|
|
1813
|
+
actor: "assistant",
|
|
1814
|
+
turnId: previous?.turnId ?? event.id,
|
|
1815
|
+
revision,
|
|
1816
|
+
timestamp: previous?.timestamp ?? new Date(revision).toISOString(),
|
|
1817
|
+
sourceEventIds: [...previous?.sourceEventIds ?? [], event.id].slice(-1e3),
|
|
1818
|
+
content: (canonical === void 0 ? state.deltaContent : `${canonical.content}
|
|
1819
|
+
|
|
1820
|
+
${state.deltaContent}`).slice(0, 4096),
|
|
1821
|
+
final: false
|
|
1822
|
+
};
|
|
1823
|
+
state.streaming = item;
|
|
1824
|
+
this.ports.callbacks()?.onAssistantPreview?.(sessionId, item);
|
|
1825
|
+
}
|
|
1826
|
+
forwardEvent(sessionId, event) {
|
|
1827
|
+
const runtime = this.ports.state(sessionId);
|
|
1828
|
+
const recent = runtime.live.recentEventIds;
|
|
1829
|
+
if (recent.ids.has(event.id)) {
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
runtime.timelineReads.invalidateContent();
|
|
1833
|
+
recent.ids.add(event.id);
|
|
1834
|
+
recent.order.push(event.id);
|
|
1835
|
+
while (recent.order.length > RECENT_EVENT_ID_LIMIT) {
|
|
1836
|
+
const expiredId = recent.order.shift();
|
|
1837
|
+
if (expiredId !== void 0) {
|
|
1838
|
+
recent.ids.delete(expiredId);
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
this.previewDelta(sessionId, event);
|
|
1842
|
+
if (event.type === "session.context_cleared" || event.type === "session.snapshot_rewind") {
|
|
1843
|
+
runtime.live.normalizer = void 0;
|
|
1844
|
+
this.#previews.delete(runtime.live);
|
|
1845
|
+
runtime.toolLifecycles = new ConversationToolLifecycles();
|
|
1846
|
+
this.ports.callbacks()?.onTimelineInvalidated?.(sessionId, {
|
|
1847
|
+
reason: event.type === "session.context_cleared" ? "clear" : "rewind",
|
|
1848
|
+
sourceEventId: event.id,
|
|
1849
|
+
timestamp: event.timestamp
|
|
1850
|
+
});
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1853
|
+
if (event.type === "mcp.headers_refresh_required") {
|
|
1854
|
+
if (!runtime.watched) return;
|
|
1855
|
+
void this.ports.headers(sessionId, event.data).catch(
|
|
1856
|
+
(error) => {
|
|
1857
|
+
this.ports.callbacks()?.onSyncError(sessionId, error);
|
|
1858
|
+
}
|
|
1859
|
+
);
|
|
1860
|
+
return;
|
|
1861
|
+
}
|
|
1862
|
+
if (event.type === "session_limits_exhausted.requested") {
|
|
1863
|
+
if (!runtime.watched) return;
|
|
1864
|
+
void this.ports.limits(sessionId, event.data).catch(
|
|
1865
|
+
(error) => {
|
|
1866
|
+
this.ports.callbacks()?.onSyncError(sessionId, error);
|
|
1867
|
+
}
|
|
1868
|
+
);
|
|
1869
|
+
return;
|
|
1870
|
+
}
|
|
1871
|
+
let normalizer = runtime.live.normalizer;
|
|
1872
|
+
if (normalizer === void 0) {
|
|
1873
|
+
normalizer = new ConversationNormalizer((failure) => {
|
|
1874
|
+
this.ports.callbacks()?.onImageProjectionFailure?.(sessionId, failure);
|
|
1875
|
+
}, runtime.toolLifecycles, this.ports.inputProvenance(sessionId));
|
|
1876
|
+
runtime.live.normalizer = normalizer;
|
|
1877
|
+
}
|
|
1878
|
+
this.emitNormalizedItems(sessionId, event, normalizer.apply([event]));
|
|
1879
|
+
if (runtime.watched && event.type === "tool.execution_complete" && normalizer.pendingImageAssetIds().length > 0) {
|
|
1880
|
+
this.ports.resolveImages(sessionId, normalizer);
|
|
1881
|
+
}
|
|
1882
|
+
if (runtime.watched && (event.type === "session.plan_changed" || event.type === "session.todos_changed")) {
|
|
1883
|
+
this.ports.schedulePlan(sessionId, event);
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
emitNormalizedItems(sessionId, event, items) {
|
|
1887
|
+
const runtime = this.ports.state(sessionId);
|
|
1888
|
+
for (const item of items) {
|
|
1889
|
+
if (item.kind === "assistant-message" && isContentfulAssistant(item)) {
|
|
1890
|
+
if (event.type === "assistant.message") {
|
|
1891
|
+
this.#previews.set(runtime.live, { canonical: item, deltaContent: "" });
|
|
1892
|
+
}
|
|
1893
|
+
if (!runtime.watched) this.ports.callbacks()?.onAssistantPreview?.(sessionId, item);
|
|
1894
|
+
} else if (!runtime.watched && item.kind === "human-message" && item.actor === "human") {
|
|
1895
|
+
this.ports.callbacks()?.onAssistantPreview?.(sessionId, item);
|
|
1896
|
+
}
|
|
1897
|
+
if (!runtime.watched) continue;
|
|
1898
|
+
if (item.kind === "plan-update" && (event.type === "session.plan_changed" || event.type === "session.todos_changed")) {
|
|
1899
|
+
continue;
|
|
1900
|
+
}
|
|
1901
|
+
this.ports.callbacks()?.onConversationItem(sessionId, item);
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
updateSessionActivity(sessionId, event) {
|
|
1905
|
+
this.ports.changed();
|
|
1906
|
+
if (event.agentId !== void 0) {
|
|
1907
|
+
return;
|
|
1908
|
+
}
|
|
1909
|
+
const runtime = this.ports.state(sessionId);
|
|
1910
|
+
if (event.type === "session.resume" && event.data.sessionWasActive === true) {
|
|
1911
|
+
this.#clearTurnEndIdleTimer(sessionId);
|
|
1912
|
+
if (!runtime.activity.busy) {
|
|
1913
|
+
runtime.activity.busy = true;
|
|
1914
|
+
this.ports.callbacks()?.onSessionActivity?.(
|
|
1915
|
+
sessionId,
|
|
1916
|
+
"working",
|
|
1917
|
+
event.timestamp
|
|
1918
|
+
);
|
|
1919
|
+
}
|
|
1920
|
+
return;
|
|
1921
|
+
}
|
|
1922
|
+
if (event.type === "assistant.turn_start") {
|
|
1923
|
+
this.#clearTurnEndIdleTimer(sessionId);
|
|
1924
|
+
runtime.activity.activeRootTurn = true;
|
|
1925
|
+
if (!runtime.activity.busy) {
|
|
1926
|
+
runtime.activity.busy = true;
|
|
1927
|
+
this.ports.callbacks()?.onSessionActivity?.(
|
|
1928
|
+
sessionId,
|
|
1929
|
+
"working",
|
|
1930
|
+
event.timestamp
|
|
1931
|
+
);
|
|
1932
|
+
}
|
|
1933
|
+
return;
|
|
1934
|
+
}
|
|
1935
|
+
if (event.type === "assistant.turn_end") {
|
|
1936
|
+
runtime.activity.activeRootTurn = false;
|
|
1937
|
+
this.#scheduleTurnEndIdleFallback(sessionId, event.timestamp);
|
|
1938
|
+
return;
|
|
1939
|
+
}
|
|
1940
|
+
if (event.type === "session.idle" || event.type === "session.error" || event.type === "session.shutdown") {
|
|
1941
|
+
runtime.activity.activeRootTurn = false;
|
|
1942
|
+
this.markSessionIdle(sessionId, event.timestamp);
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
#scheduleTurnEndIdleFallback(sessionId, occurredAt) {
|
|
1946
|
+
this.#clearTurnEndIdleTimer(sessionId);
|
|
1947
|
+
const runtime = this.ports.state(sessionId);
|
|
1948
|
+
const timer = setTimeout(() => {
|
|
1949
|
+
runtime.activity.turnEndIdleTimer = void 0;
|
|
1950
|
+
if (!runtime.activity.activeRootTurn) {
|
|
1951
|
+
this.markSessionIdle(sessionId, occurredAt);
|
|
1952
|
+
}
|
|
1953
|
+
}, TURN_END_IDLE_FALLBACK_MS);
|
|
1954
|
+
runtime.activity.turnEndIdleTimer = timer;
|
|
1955
|
+
}
|
|
1956
|
+
#clearTurnEndIdleTimer(sessionId) {
|
|
1957
|
+
this.ports.peek(sessionId)?.clearTurnEndIdleTimer();
|
|
1958
|
+
}
|
|
1959
|
+
markSessionIdle(sessionId, occurredAt) {
|
|
1960
|
+
this.#clearTurnEndIdleTimer(sessionId);
|
|
1961
|
+
const runtime = this.ports.state(sessionId);
|
|
1962
|
+
if (runtime.activity.busy) {
|
|
1963
|
+
runtime.activity.busy = false;
|
|
1964
|
+
this.ports.callbacks()?.onSessionActivity?.(sessionId, "idle", occurredAt);
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
};
|
|
1968
|
+
|
|
1969
|
+
// src/git-remote-url.ts
|
|
1970
|
+
function sanitizeGitRemoteUrl(value) {
|
|
1971
|
+
const remote = value.trim();
|
|
1972
|
+
if (!remote) return void 0;
|
|
1973
|
+
try {
|
|
1974
|
+
const url = new URL(remote);
|
|
1975
|
+
if (!["http:", "https:", "ssh:", "git:"].includes(url.protocol) || !url.hostname) {
|
|
1976
|
+
return void 0;
|
|
1977
|
+
}
|
|
1978
|
+
url.username = "";
|
|
1979
|
+
url.password = "";
|
|
1980
|
+
url.search = "";
|
|
1981
|
+
url.hash = "";
|
|
1982
|
+
return url.toString().replace(/\/$/, "");
|
|
1983
|
+
} catch {
|
|
1984
|
+
if (/^[A-Za-z]:[\\/]/.test(remote)) return void 0;
|
|
1985
|
+
const scp = /^(?:([^@\s]+)@)?([^:\s]+):(.+)$/.exec(remote);
|
|
1986
|
+
if (!scp) return void 0;
|
|
1987
|
+
const username = scp[1];
|
|
1988
|
+
const host = scp[2];
|
|
1989
|
+
const path = scp[3];
|
|
1990
|
+
if (!host || !path) return void 0;
|
|
1991
|
+
const safeUser = username === "git" ? "git@" : "";
|
|
1992
|
+
return `${safeUser}${host}:${path.split(/[?#]/, 1)[0]}`;
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
// src/git-project-identity.ts
|
|
1997
|
+
import { execFile } from "node:child_process";
|
|
1998
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
1999
|
+
import { realpath } from "node:fs/promises";
|
|
2000
|
+
import { isAbsolute, resolve } from "node:path";
|
|
2001
|
+
import { promisify } from "node:util";
|
|
2002
|
+
var execFileAsync = promisify(execFile);
|
|
2003
|
+
var NotGitDirectoryError = class extends Error {
|
|
2004
|
+
constructor(options) {
|
|
2005
|
+
super("The working directory is not a Git repository", options);
|
|
2006
|
+
}
|
|
2007
|
+
};
|
|
2008
|
+
function gitEnvironment() {
|
|
2009
|
+
const env = { ...process.env, LC_ALL: "C" };
|
|
2010
|
+
for (const key of [
|
|
2011
|
+
"GIT_DIR",
|
|
2012
|
+
"GIT_COMMON_DIR",
|
|
2013
|
+
"GIT_WORK_TREE",
|
|
2014
|
+
"GIT_INDEX_FILE",
|
|
2015
|
+
"GIT_PREFIX",
|
|
2016
|
+
"GIT_OBJECT_DIRECTORY",
|
|
2017
|
+
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
|
|
2018
|
+
"GIT_CONFIG",
|
|
2019
|
+
"GIT_CONFIG_PARAMETERS",
|
|
2020
|
+
"GIT_CONFIG_COUNT"
|
|
2021
|
+
]) {
|
|
2022
|
+
delete env[key];
|
|
2023
|
+
}
|
|
2024
|
+
return env;
|
|
2025
|
+
}
|
|
2026
|
+
async function git(args, env) {
|
|
2027
|
+
const { stdout } = await execFileAsync("git", args, {
|
|
2028
|
+
encoding: "utf8",
|
|
2029
|
+
windowsHide: true,
|
|
2030
|
+
timeout: 2e3,
|
|
2031
|
+
maxBuffer: 1024 * 1024,
|
|
2032
|
+
env
|
|
2033
|
+
});
|
|
2034
|
+
return stdout;
|
|
2035
|
+
}
|
|
2036
|
+
function absoluteGitPath(value) {
|
|
2037
|
+
if (!value || value.includes("\0") || !isAbsolute(value)) {
|
|
2038
|
+
throw new Error("Git did not return an absolute repository path");
|
|
2039
|
+
}
|
|
2040
|
+
return resolve(value);
|
|
2041
|
+
}
|
|
2042
|
+
async function resolveGitProjectIdentity(workingDirectory, env = gitEnvironment()) {
|
|
2043
|
+
if (!isAbsolute(workingDirectory) || workingDirectory.includes("\0")) {
|
|
2044
|
+
throw new Error("Git project inspection requires an absolute working directory");
|
|
2045
|
+
}
|
|
2046
|
+
let output;
|
|
2047
|
+
try {
|
|
2048
|
+
output = await git([
|
|
2049
|
+
"-C",
|
|
2050
|
+
workingDirectory,
|
|
2051
|
+
"rev-parse",
|
|
2052
|
+
"--path-format=absolute",
|
|
2053
|
+
"--show-toplevel",
|
|
2054
|
+
"--git-common-dir"
|
|
2055
|
+
], env);
|
|
2056
|
+
} catch (error) {
|
|
2057
|
+
if (error instanceof Error && "code" in error && error.code === 128 && "stdout" in error && error.stdout === "" && "stderr" in error && typeof error.stderr === "string" && /^fatal: not a git repository \(or any of the parent directories\): \.git\r?\n$/.test(error.stderr)) {
|
|
2058
|
+
throw new NotGitDirectoryError({ cause: error });
|
|
2059
|
+
}
|
|
2060
|
+
throw error;
|
|
2061
|
+
}
|
|
2062
|
+
const roots = output.replace(/\r?\n$/, "").split(/\r?\n/);
|
|
2063
|
+
if (roots.length !== 2) throw new Error("Git returned ambiguous repository paths");
|
|
2064
|
+
const worktreeRoot = await realpath(absoluteGitPath(roots[0]));
|
|
2065
|
+
const commonGitDirectory = await realpath(absoluteGitPath(roots[1]));
|
|
2066
|
+
const worktrees = await git(["-C", workingDirectory, "worktree", "list", "--porcelain", "-z"], env);
|
|
2067
|
+
if (!worktrees.endsWith("\0\0")) throw new Error("Git returned an incomplete worktree list");
|
|
2068
|
+
const primary = worktrees.split("\0\0", 1)[0]?.split("\0");
|
|
2069
|
+
const first = primary?.[0];
|
|
2070
|
+
if (!first?.startsWith("worktree ")) throw new Error("Git did not identify its primary worktree");
|
|
2071
|
+
const mainWorktreeRoot = primary?.includes("bare") ? void 0 : absoluteGitPath(first.slice("worktree ".length));
|
|
2072
|
+
let origin;
|
|
2073
|
+
try {
|
|
2074
|
+
origin = await git([
|
|
2075
|
+
"--git-dir",
|
|
2076
|
+
commonGitDirectory,
|
|
2077
|
+
"config",
|
|
2078
|
+
"--local",
|
|
2079
|
+
"--includes",
|
|
2080
|
+
"--get",
|
|
2081
|
+
"remote.origin.url"
|
|
2082
|
+
], env);
|
|
2083
|
+
} catch (error) {
|
|
2084
|
+
if (!(error instanceof Error && "code" in error && error.code === 1 && "stdout" in error && error.stdout === "" && "stderr" in error && error.stderr === "")) {
|
|
2085
|
+
throw error;
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
const gitRemoteUrl = origin === void 0 ? void 0 : sanitizeGitRemoteUrl(origin);
|
|
2089
|
+
return {
|
|
2090
|
+
worktreeRoot,
|
|
2091
|
+
commonGitDirectory,
|
|
2092
|
+
localRepositoryKey: createHash3("sha256").update(commonGitDirectory).digest("base64url"),
|
|
2093
|
+
...mainWorktreeRoot === void 0 ? {} : { mainWorktreeRoot },
|
|
2094
|
+
...gitRemoteUrl === void 0 ? {} : { gitRemoteUrl }
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
// src/prompt-preparation-error.ts
|
|
2099
|
+
var PromptPreparationError = class extends Error {
|
|
2100
|
+
constructor(cause) {
|
|
2101
|
+
super(cause instanceof Error ? cause.message : "Prompt preparation failed", { cause });
|
|
2102
|
+
this.name = "PromptPreparationError";
|
|
2103
|
+
}
|
|
2104
|
+
};
|
|
2105
|
+
|
|
2106
|
+
// src/session-project-identity.ts
|
|
2107
|
+
import { realpath as realpath2, stat } from "node:fs/promises";
|
|
2108
|
+
import { isAbsolute as isAbsolute2 } from "node:path";
|
|
2109
|
+
var SESSION_PROJECT_IDENTITY_CONCURRENCY = 4;
|
|
2110
|
+
var errors = {
|
|
2111
|
+
SESSION_NOT_FOUND: "The session is unavailable",
|
|
2112
|
+
SESSION_ROOT_UNAVAILABLE: "The session working directory is unavailable",
|
|
2113
|
+
SESSION_ROOT_CHANGED: "The session working directory changed",
|
|
2114
|
+
PROJECT_IDENTITY_BUSY: "Too many project identity requests",
|
|
2115
|
+
PROJECT_IDENTITY_UNAVAILABLE: "The session project identity is unavailable"
|
|
2116
|
+
};
|
|
2117
|
+
var SessionProjectIdentityError = class extends Error {
|
|
2118
|
+
constructor(code, options) {
|
|
2119
|
+
super(errors[code], options);
|
|
2120
|
+
this.code = code;
|
|
2121
|
+
}
|
|
2122
|
+
code;
|
|
2123
|
+
};
|
|
2124
|
+
async function rootIdentity(workingDirectory) {
|
|
2125
|
+
const root = await realpath2(workingDirectory);
|
|
2126
|
+
const info = await stat(root);
|
|
2127
|
+
if (!info.isDirectory()) throw new SessionProjectIdentityError("SESSION_ROOT_UNAVAILABLE");
|
|
2128
|
+
return `${root}\0${info.dev}\0${info.ino}`;
|
|
2129
|
+
}
|
|
2130
|
+
var SessionProjectIdentityReader = class {
|
|
2131
|
+
constructor(metadata, inspect = resolveGitProjectIdentity) {
|
|
2132
|
+
this.metadata = metadata;
|
|
2133
|
+
this.inspect = inspect;
|
|
2134
|
+
}
|
|
2135
|
+
metadata;
|
|
2136
|
+
inspect;
|
|
2137
|
+
#active = 0;
|
|
2138
|
+
async get(sessionId) {
|
|
2139
|
+
if (this.#active >= SESSION_PROJECT_IDENTITY_CONCURRENCY) {
|
|
2140
|
+
throw new SessionProjectIdentityError("PROJECT_IDENTITY_BUSY");
|
|
2141
|
+
}
|
|
2142
|
+
this.#active++;
|
|
2143
|
+
try {
|
|
2144
|
+
const metadata = await this.metadata(sessionId);
|
|
2145
|
+
if (metadata === void 0) throw new SessionProjectIdentityError("SESSION_NOT_FOUND");
|
|
2146
|
+
const cwd = metadata.workingDirectory;
|
|
2147
|
+
if (!cwd || !isAbsolute2(cwd) || cwd.includes("\0")) {
|
|
2148
|
+
throw new SessionProjectIdentityError("SESSION_ROOT_UNAVAILABLE");
|
|
2149
|
+
}
|
|
2150
|
+
let initialRoot;
|
|
2151
|
+
try {
|
|
2152
|
+
initialRoot = await rootIdentity(cwd);
|
|
2153
|
+
} catch (cause) {
|
|
2154
|
+
throw new SessionProjectIdentityError("SESSION_ROOT_UNAVAILABLE", { cause });
|
|
2155
|
+
}
|
|
2156
|
+
let project;
|
|
2157
|
+
let inspectionError;
|
|
2158
|
+
try {
|
|
2159
|
+
const identity = await this.inspect(cwd);
|
|
2160
|
+
project = SessionGitProjectSchema.parse({
|
|
2161
|
+
localRepositoryKey: identity.localRepositoryKey,
|
|
2162
|
+
worktreeRoot: identity.worktreeRoot,
|
|
2163
|
+
...identity.mainWorktreeRoot === void 0 ? {} : { mainWorktreeRoot: identity.mainWorktreeRoot },
|
|
2164
|
+
...identity.gitRemoteUrl === void 0 ? {} : { gitRemoteUrl: identity.gitRemoteUrl }
|
|
2165
|
+
});
|
|
2166
|
+
} catch (error) {
|
|
2167
|
+
if (!(error instanceof NotGitDirectoryError)) {
|
|
2168
|
+
inspectionError = new SessionProjectIdentityError("PROJECT_IDENTITY_UNAVAILABLE", { cause: error });
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
const latest = await this.metadata(sessionId);
|
|
2172
|
+
if (latest === void 0) throw new SessionProjectIdentityError("SESSION_NOT_FOUND");
|
|
2173
|
+
if (latest.workingDirectory !== cwd) throw new SessionProjectIdentityError("SESSION_ROOT_CHANGED");
|
|
2174
|
+
try {
|
|
2175
|
+
if (await rootIdentity(cwd) !== initialRoot) {
|
|
2176
|
+
throw new SessionProjectIdentityError("SESSION_ROOT_CHANGED");
|
|
2177
|
+
}
|
|
2178
|
+
} catch (cause) {
|
|
2179
|
+
throw new SessionProjectIdentityError("SESSION_ROOT_CHANGED", { cause });
|
|
2180
|
+
}
|
|
2181
|
+
if (inspectionError !== void 0) throw inspectionError;
|
|
2182
|
+
return project;
|
|
2183
|
+
} catch (cause) {
|
|
2184
|
+
if (cause instanceof SessionProjectIdentityError) throw cause;
|
|
2185
|
+
throw new SessionProjectIdentityError("PROJECT_IDENTITY_UNAVAILABLE", { cause });
|
|
2186
|
+
} finally {
|
|
2187
|
+
this.#active--;
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
};
|
|
2191
|
+
|
|
2192
|
+
// src/session-workbench-runtime.ts
|
|
2193
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
2194
|
+
import { z } from "zod";
|
|
2195
|
+
var WorkbenchRuntimeError = class extends SessionOperationError {
|
|
2196
|
+
constructor(code, message) {
|
|
2197
|
+
super(code, message);
|
|
2198
|
+
this.name = "WorkbenchRuntimeError";
|
|
2199
|
+
}
|
|
2200
|
+
};
|
|
2201
|
+
var WorkbenchOutcomeUncertainError = class extends Error {
|
|
2202
|
+
code = "stale-runtime";
|
|
2203
|
+
};
|
|
2204
|
+
var queueSchema = z.object({
|
|
2205
|
+
items: z.array(z.object({
|
|
2206
|
+
id: z.string().min(1).max(256),
|
|
2207
|
+
kind: z.enum(["message", "command"]),
|
|
2208
|
+
displayText: z.string().max(1e5),
|
|
2209
|
+
agentMode: z.enum(["interactive", "plan", "autopilot", "shell"])
|
|
2210
|
+
})).max(512),
|
|
2211
|
+
steeringMessages: z.array(z.string().max(1e5)).max(512)
|
|
2212
|
+
});
|
|
2213
|
+
var resultBase = { runtimeSettingsChanged: z.boolean().optional() };
|
|
2214
|
+
var invocationSchema = z.discriminatedUnion("kind", [
|
|
2215
|
+
z.object({ kind: z.literal("text"), text: z.string().max(64 * 1024), markdown: z.boolean().optional(), preserveAnsi: z.boolean().optional(), ...resultBase }),
|
|
2216
|
+
z.object({ kind: z.literal("completed"), message: z.string().max(64 * 1024).optional(), ...resultBase }),
|
|
2217
|
+
z.object({ kind: z.literal("agent-prompt"), prompt: z.string().min(1).max(64 * 1024), displayPrompt: z.string().max(64 * 1024), mode: z.enum(["interactive", "plan", "autopilot"]).optional(), notice: z.string().max(64 * 1024).optional(), ...resultBase }),
|
|
2218
|
+
z.object({ kind: z.literal("select-subcommand"), command: z.string().max(256), title: z.string().max(4096), options: z.array(z.object({ name: z.string().max(256), description: z.string().max(4096), group: z.string().max(256).optional() })).max(256), ...resultBase })
|
|
2219
|
+
]);
|
|
2220
|
+
function nanoAiu(value) {
|
|
2221
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
2222
|
+
throw new WorkbenchRuntimeError("invalid-result", "Runtime nano-AIU value cannot be represented without precision loss");
|
|
2223
|
+
}
|
|
2224
|
+
return value.toString();
|
|
2225
|
+
}
|
|
2226
|
+
async function readWorkbenchMetrics(rpc) {
|
|
2227
|
+
if (rpc.usage === void 0 || rpc.model === void 0 || rpc.metadata?.contextInfo === void 0) {
|
|
2228
|
+
throw new WorkbenchRuntimeError("unsupported", "Runtime usage/context inspection is unavailable");
|
|
2229
|
+
}
|
|
2230
|
+
const [usage, model, attribution] = await Promise.all([
|
|
2231
|
+
rpc.usage.getMetrics(),
|
|
2232
|
+
rpc.model.getCurrent(),
|
|
2233
|
+
rpc.metadata.getContextAttribution?.()
|
|
2234
|
+
]);
|
|
2235
|
+
const attributed = attribution?.contextAttribution;
|
|
2236
|
+
const modelId = attributed?.modelId ?? model.modelId;
|
|
2237
|
+
const { contextInfo } = await rpc.metadata.contextInfo({
|
|
2238
|
+
promptTokenLimit: attributed?.promptTokenLimit ?? 0,
|
|
2239
|
+
outputTokenLimit: attributed === void 0 || attributed === null ? 0 : Math.max(0, attributed.limit - attributed.promptTokenLimit),
|
|
2240
|
+
...modelId === void 0 ? {} : { selectedModel: modelId }
|
|
2241
|
+
});
|
|
2242
|
+
return {
|
|
2243
|
+
...modelId === void 0 ? {} : { modelId },
|
|
2244
|
+
totalPremiumRequestCost: usage.totalPremiumRequestCost,
|
|
2245
|
+
totalUserRequests: usage.totalUserRequests,
|
|
2246
|
+
...usage.totalNanoAiu === void 0 ? {} : { totalNanoAiu: nanoAiu(usage.totalNanoAiu) },
|
|
2247
|
+
tokens: Object.entries(usage.tokenDetails ?? {}).flatMap(([kind, detail]) => detail === void 0 ? [] : [{ kind, count: detail.tokenCount }]),
|
|
2248
|
+
models: Object.entries(usage.modelMetrics).flatMap(([modelId2, metric]) => metric === void 0 ? [] : [{
|
|
2249
|
+
modelId: modelId2,
|
|
2250
|
+
inputTokens: metric.usage.inputTokens,
|
|
2251
|
+
outputTokens: metric.usage.outputTokens,
|
|
2252
|
+
cacheReadTokens: metric.usage.cacheReadTokens,
|
|
2253
|
+
cacheWriteTokens: metric.usage.cacheWriteTokens,
|
|
2254
|
+
requests: metric.requests.count,
|
|
2255
|
+
premiumRequestCost: metric.requests.cost,
|
|
2256
|
+
...metric.totalNanoAiu === void 0 ? {} : { totalNanoAiu: nanoAiu(metric.totalNanoAiu) }
|
|
2257
|
+
}]),
|
|
2258
|
+
context: contextInfo ?? null
|
|
2259
|
+
};
|
|
2260
|
+
}
|
|
2261
|
+
function projectWorkbenchInsights(result) {
|
|
2262
|
+
return WorkbenchInsightsSchema.parse({
|
|
2263
|
+
sampledAt: result.sampledAt,
|
|
2264
|
+
generation: result.runtimeGeneration,
|
|
2265
|
+
...result.modelId === void 0 ? {} : { model: result.modelId },
|
|
2266
|
+
totalUserRequests: result.totalUserRequests,
|
|
2267
|
+
premiumRequestCost: result.totalPremiumRequestCost,
|
|
2268
|
+
...result.totalNanoAiu === void 0 ? {} : { nanoAiu: result.totalNanoAiu },
|
|
2269
|
+
tokens: result.tokens,
|
|
2270
|
+
models: result.models.map((model) => ({
|
|
2271
|
+
model: model.modelId,
|
|
2272
|
+
requests: model.requests,
|
|
2273
|
+
premiumRequestCost: model.premiumRequestCost,
|
|
2274
|
+
tokens: [
|
|
2275
|
+
{ kind: "input", count: model.inputTokens },
|
|
2276
|
+
{ kind: "output", count: model.outputTokens },
|
|
2277
|
+
{ kind: "cache-read", count: model.cacheReadTokens },
|
|
2278
|
+
{ kind: "cache-write", count: model.cacheWriteTokens }
|
|
2279
|
+
]
|
|
2280
|
+
})),
|
|
2281
|
+
...result.context === null ? {} : { context: {
|
|
2282
|
+
currentTokens: result.context.totalTokens,
|
|
2283
|
+
tokenLimit: result.context.promptTokenLimit,
|
|
2284
|
+
systemTokens: result.context.systemTokens,
|
|
2285
|
+
conversationTokens: result.context.conversationTokens,
|
|
2286
|
+
toolTokens: result.context.toolDefinitionsTokens
|
|
2287
|
+
} },
|
|
2288
|
+
canCompact: result.compactAllowed,
|
|
2289
|
+
...result.compactDisabledReason === void 0 ? {} : { compactUnavailableReason: result.compactDisabledReason }
|
|
2290
|
+
});
|
|
2291
|
+
}
|
|
2292
|
+
var SessionWorkbenchRuntime = class {
|
|
2293
|
+
constructor(dependencies) {
|
|
2294
|
+
this.dependencies = dependencies;
|
|
2295
|
+
}
|
|
2296
|
+
dependencies;
|
|
2297
|
+
#activeQuestions = 0;
|
|
2298
|
+
#queueMutations = /* @__PURE__ */ new Map();
|
|
2299
|
+
#generation(runtime) {
|
|
2300
|
+
return `${runtime.workbench.identity}:${runtime.connection.generation}`;
|
|
2301
|
+
}
|
|
2302
|
+
async #session(sessionId) {
|
|
2303
|
+
this.dependencies.assertAvailable(sessionId);
|
|
2304
|
+
const session = await this.dependencies.getSession(sessionId);
|
|
2305
|
+
this.dependencies.assertAvailable(sessionId);
|
|
2306
|
+
const runtime = this.dependencies.getRuntime(sessionId);
|
|
2307
|
+
const generation = this.#generation(runtime);
|
|
2308
|
+
return { session, runtime, generation };
|
|
2309
|
+
}
|
|
2310
|
+
#assertGeneration(sessionId, generation) {
|
|
2311
|
+
this.dependencies.assertAvailable(sessionId);
|
|
2312
|
+
if (this.#generation(this.dependencies.getRuntime(sessionId)) !== generation) {
|
|
2313
|
+
throw new WorkbenchOutcomeUncertainError("Session runtime changed during the operation; do not replay mutations");
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
async ask(sessionId, question) {
|
|
2317
|
+
z.string().trim().min(1).max(16 * 1024).parse(question);
|
|
2318
|
+
this.dependencies.assertAvailable(sessionId);
|
|
2319
|
+
const runtime = this.dependencies.getRuntime(sessionId);
|
|
2320
|
+
if (runtime.workbench.compacting || runtime.workbench.activeQuestions > 0 || this.#activeQuestions >= 2) {
|
|
2321
|
+
throw new WorkbenchRuntimeError("busy", "A side question or context compaction is already in progress");
|
|
2322
|
+
}
|
|
2323
|
+
runtime.workbench.activeQuestions += 1;
|
|
2324
|
+
this.#activeQuestions += 1;
|
|
2325
|
+
try {
|
|
2326
|
+
const { session, generation } = await this.#session(sessionId);
|
|
2327
|
+
if (session.rpc.ui.ephemeralQuery === void 0) {
|
|
2328
|
+
throw new WorkbenchRuntimeError("unsupported", "This runtime does not support side questions");
|
|
2329
|
+
}
|
|
2330
|
+
const result = z.object({ answer: z.string().max(64 * 1024) }).parse(
|
|
2331
|
+
await session.rpc.ui.ephemeralQuery({ question })
|
|
2332
|
+
);
|
|
2333
|
+
this.#assertGeneration(sessionId, generation);
|
|
2334
|
+
return WorkbenchAskResultSchema.parse({ answer: result.answer });
|
|
2335
|
+
} finally {
|
|
2336
|
+
runtime.workbench.activeQuestions -= 1;
|
|
2337
|
+
this.#activeQuestions -= 1;
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
async queueSnapshot(sessionId) {
|
|
2341
|
+
const { session, generation } = await this.#session(sessionId);
|
|
2342
|
+
const queue = session.rpc.queue;
|
|
2343
|
+
if (queue === void 0) throw new WorkbenchRuntimeError("unsupported", "Runtime queue management is unavailable");
|
|
2344
|
+
const pending = queueSchema.parse(await queue.pendingItems());
|
|
2345
|
+
this.#assertGeneration(sessionId, generation);
|
|
2346
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2347
|
+
for (const row of pending.items) {
|
|
2348
|
+
const existing = groups.get(row.id);
|
|
2349
|
+
if (existing !== void 0) {
|
|
2350
|
+
existing.displayTexts.push(row.displayText);
|
|
2351
|
+
existing.actions = existing.actions.filter((action) => action !== "edit");
|
|
2352
|
+
if (row.agentMode === "shell") existing.actions = existing.actions.filter((action) => action !== "send-now");
|
|
2353
|
+
} else {
|
|
2354
|
+
const actions = ["remove", "move"];
|
|
2355
|
+
if (row.kind === "message" && row.agentMode !== "shell") {
|
|
2356
|
+
actions.push("send-now");
|
|
2357
|
+
if (this.dependencies.supportsAttachmentPreservingQueueEdits()) actions.push("edit");
|
|
2358
|
+
}
|
|
2359
|
+
groups.set(row.id, { id: row.id, kind: row.kind, displayTexts: [row.displayText], actions });
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
return {
|
|
2363
|
+
runtimeGeneration: generation,
|
|
2364
|
+
revision: createHash4("sha256").update(JSON.stringify(pending)).digest("hex"),
|
|
2365
|
+
sampledAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2366
|
+
items: [...groups.values()],
|
|
2367
|
+
steeringMessages: pending.steeringMessages
|
|
2368
|
+
};
|
|
2369
|
+
}
|
|
2370
|
+
async mutatePendingQueue(sessionId, input) {
|
|
2371
|
+
const previous = this.#queueMutations.get(sessionId);
|
|
2372
|
+
let release = () => {
|
|
2373
|
+
};
|
|
2374
|
+
const current2 = new Promise((resolve2) => {
|
|
2375
|
+
release = resolve2;
|
|
2376
|
+
});
|
|
2377
|
+
this.#queueMutations.set(sessionId, current2);
|
|
2378
|
+
await previous;
|
|
2379
|
+
try {
|
|
2380
|
+
const { session, runtime, generation } = await this.#session(sessionId);
|
|
2381
|
+
if (input.runtimeGeneration !== generation) throw new WorkbenchRuntimeError("stale-runtime", "Queue belongs to an earlier session runtime");
|
|
2382
|
+
const releaseMutation = runtime.admitMutation();
|
|
2383
|
+
try {
|
|
2384
|
+
const snapshot = await this.queueSnapshot(sessionId);
|
|
2385
|
+
if (snapshot.revision !== input.revision) return { outcome: "stale-snapshot", snapshot };
|
|
2386
|
+
const item = snapshot.items.find((entry) => entry.id === input.id);
|
|
2387
|
+
if (item === void 0) return { outcome: "not-found", snapshot };
|
|
2388
|
+
if (!item.actions.includes(input.action)) return { outcome: "not-editable", snapshot };
|
|
2389
|
+
const queue = session.rpc.queue;
|
|
2390
|
+
if (queue === void 0) throw new WorkbenchRuntimeError("unsupported", "Runtime queue management is unavailable");
|
|
2391
|
+
let outcome;
|
|
2392
|
+
switch (input.action) {
|
|
2393
|
+
case "edit":
|
|
2394
|
+
z.string().trim().min(1).max(1e5).parse(input.text);
|
|
2395
|
+
outcome = (await queue.updateText({ id: input.id, prompt: input.text, displayPrompt: input.text })).updated ? "updated" : "unchanged";
|
|
2396
|
+
break;
|
|
2397
|
+
case "remove":
|
|
2398
|
+
outcome = (await queue.removeAt({ id: input.id })).removed ? "removed" : "not-found";
|
|
2399
|
+
break;
|
|
2400
|
+
case "move":
|
|
2401
|
+
z.number().int().nonnegative().max(511).parse(input.toPosition);
|
|
2402
|
+
outcome = (await queue.moveItem({ id: input.id, toPosition: input.toPosition })).changed ? "moved" : "unchanged";
|
|
2403
|
+
break;
|
|
2404
|
+
case "send-now":
|
|
2405
|
+
outcome = (await queue.sendNow({ id: input.id })).steered ? "sent" : "not-steered";
|
|
2406
|
+
break;
|
|
2407
|
+
}
|
|
2408
|
+
this.#assertGeneration(sessionId, generation);
|
|
2409
|
+
return { outcome, snapshot: await this.queueSnapshot(sessionId) };
|
|
2410
|
+
} finally {
|
|
2411
|
+
releaseMutation();
|
|
2412
|
+
}
|
|
2413
|
+
} finally {
|
|
2414
|
+
release();
|
|
2415
|
+
if (this.#queueMutations.get(sessionId) === current2) this.#queueMutations.delete(sessionId);
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
async listCommands(sessionId) {
|
|
2419
|
+
const { session, generation } = await this.#session(sessionId);
|
|
2420
|
+
if (session.rpc.commands === void 0) throw new WorkbenchRuntimeError("unsupported", "Runtime command discovery is unavailable");
|
|
2421
|
+
const { commands } = await session.rpc.commands.list({ includeBuiltins: true, includeSkills: true, includeClientCommands: true });
|
|
2422
|
+
if (commands.length > 256) throw new WorkbenchRuntimeError("invalid-result", "Runtime command catalog exceeds the supported size");
|
|
2423
|
+
const projected = commands.map((command) => {
|
|
2424
|
+
const name = command.name.toLowerCase();
|
|
2425
|
+
let action = command.kind === "skill" || command.kind === "builtin" && ["help", "version", "review", "diff", "skills"].includes(name) ? "invoke" : "unsupported";
|
|
2426
|
+
if (["ask", "btw"].includes(name)) action = "ask";
|
|
2427
|
+
if (["model", "mode", "plan", "autopilot"].includes(name)) action = "configure";
|
|
2428
|
+
if (["usage", "context"].includes(name)) action = "insights";
|
|
2429
|
+
if (name === "compact") action = "compact";
|
|
2430
|
+
const executable = action !== "unsupported" && (action !== "compact" || this.dependencies.supportsCoordinatedCompaction());
|
|
2431
|
+
return {
|
|
2432
|
+
name: command.name,
|
|
2433
|
+
aliases: command.aliases ?? [],
|
|
2434
|
+
description: command.description,
|
|
2435
|
+
kind: command.kind,
|
|
2436
|
+
...command.input === void 0 ? {} : { inputHint: command.input.hint },
|
|
2437
|
+
inputRequired: command.input?.required === true,
|
|
2438
|
+
allowDuringAgentExecution: command.allowDuringAgentExecution,
|
|
2439
|
+
executable,
|
|
2440
|
+
action,
|
|
2441
|
+
...executable ? {} : { disabledReason: action === "compact" ? "Runtime compaction coordination is unverified" : "This command has no supported workbench action" }
|
|
2442
|
+
};
|
|
2443
|
+
});
|
|
2444
|
+
for (const name of ["ask", "btw"]) {
|
|
2445
|
+
if (!projected.some((command) => command.name === name || command.aliases.includes(name))) {
|
|
2446
|
+
projected.push({ name, aliases: [], description: "Ask a side question", kind: "workbench", inputHint: "Question", inputRequired: true, allowDuringAgentExecution: true, executable: true, action: "ask" });
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
this.#assertGeneration(sessionId, generation);
|
|
2450
|
+
return { runtimeGeneration: generation, commands: projected };
|
|
2451
|
+
}
|
|
2452
|
+
async invokeRuntimeCommand(sessionId, input) {
|
|
2453
|
+
const catalog = await this.listCommands(sessionId);
|
|
2454
|
+
const name = input.name.replace(/^\/+/u, "").toLowerCase();
|
|
2455
|
+
const command = catalog.commands.find((candidate) => candidate.name.toLowerCase() === name || candidate.aliases.some((alias) => alias.toLowerCase() === name));
|
|
2456
|
+
if (command === void 0) throw new WorkbenchRuntimeError("unknown-command", "Unknown slash command; choose explicitly to send it as text");
|
|
2457
|
+
if (!command.executable || command.action !== "invoke") throw new WorkbenchRuntimeError("unsupported", command.disabledReason ?? `Use the ${command.action} workbench action for this command`);
|
|
2458
|
+
if (command.inputRequired && !input.input?.trim()) throw new WorkbenchRuntimeError("unsupported", "This command requires input");
|
|
2459
|
+
const { session, runtime, generation } = await this.#session(sessionId);
|
|
2460
|
+
if (catalog.runtimeGeneration !== generation) throw new WorkbenchRuntimeError("stale-runtime", "Command catalog belongs to an earlier runtime");
|
|
2461
|
+
const invoke = session.rpc.commands?.invoke;
|
|
2462
|
+
const isProcessing = session.rpc.metadata.isProcessing;
|
|
2463
|
+
if (invoke === void 0 || isProcessing === void 0) throw new WorkbenchRuntimeError("unsupported", "Runtime command invocation or activity inspection is unavailable");
|
|
2464
|
+
const release = runtime.admitMutation();
|
|
2465
|
+
try {
|
|
2466
|
+
if (!command.allowDuringAgentExecution && (runtime.activity.busy || (await isProcessing()).processing)) {
|
|
2467
|
+
throw new WorkbenchRuntimeError("busy", "This command cannot run while the main task is active");
|
|
2468
|
+
}
|
|
2469
|
+
const result = invocationSchema.parse(await invoke({ name: command.name, ...input.input === void 0 ? {} : { input: input.input } }));
|
|
2470
|
+
this.#assertGeneration(sessionId, generation);
|
|
2471
|
+
return result;
|
|
2472
|
+
} finally {
|
|
2473
|
+
release();
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
async #compactionDisabledReason(sessionId, session, runtime) {
|
|
2477
|
+
if (!this.dependencies.supportsCoordinatedCompaction()) return "Runtime compaction coordination is unverified";
|
|
2478
|
+
if (runtime.workbench.activeQuestions > 0) return "A side question is still running";
|
|
2479
|
+
if (runtime.workbench.activeMutations > 0 || runtime.connection.sendInProgressCount > 0 || runtime.configuration.transition !== void 0) return "A session mutation is in progress";
|
|
2480
|
+
if (runtime.activity.busy || runtime.activity.activeRootTurn || this.dependencies.hasPendingInteraction(sessionId)) return "The session has active work or a pending interaction";
|
|
2481
|
+
if (session.rpc.metadata.isProcessing === void 0 || session.rpc.permissions.pendingRequests === void 0 || session.rpc.queue === void 0 || session.rpc.history === void 0) return "Runtime idle-state inspection is unavailable";
|
|
2482
|
+
const [activity, pending, permissions] = await Promise.all([
|
|
2483
|
+
session.rpc.metadata.isProcessing(),
|
|
2484
|
+
session.rpc.queue.pendingItems(),
|
|
2485
|
+
session.rpc.permissions.pendingRequests()
|
|
2486
|
+
]);
|
|
2487
|
+
if (activity.processing || pending.items.length > 0 || pending.steeringMessages.length > 0 || permissions.items.length > 0) return "The runtime has active work, queued items or a pending approval";
|
|
2488
|
+
if (runtime.activity.busy || runtime.activity.activeRootTurn || this.dependencies.hasPendingInteraction(sessionId)) return "Session activity changed while checking whether compaction is safe";
|
|
2489
|
+
return void 0;
|
|
2490
|
+
}
|
|
2491
|
+
async readInsights(sessionId) {
|
|
2492
|
+
const { session, runtime, generation } = await this.#session(sessionId);
|
|
2493
|
+
const metrics = await readWorkbenchMetrics(session.rpc);
|
|
2494
|
+
const reason = runtime.workbench.compacting ? "Context compaction is in progress" : await this.#compactionDisabledReason(sessionId, session, runtime);
|
|
2495
|
+
this.#assertGeneration(sessionId, generation);
|
|
2496
|
+
return {
|
|
2497
|
+
...metrics,
|
|
2498
|
+
runtimeGeneration: generation,
|
|
2499
|
+
sampledAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2500
|
+
compactAllowed: reason === void 0,
|
|
2501
|
+
...reason === void 0 ? {} : { compactDisabledReason: reason }
|
|
2502
|
+
};
|
|
2503
|
+
}
|
|
2504
|
+
async compactContext(sessionId) {
|
|
2505
|
+
const { session, runtime, generation } = await this.#session(sessionId);
|
|
2506
|
+
if (runtime.workbench.compacting) throw new WorkbenchRuntimeError("busy", "Context compaction is already in progress");
|
|
2507
|
+
runtime.workbench.compacting = true;
|
|
2508
|
+
try {
|
|
2509
|
+
const reason = await this.#compactionDisabledReason(sessionId, session, runtime);
|
|
2510
|
+
if (reason !== void 0) throw new WorkbenchRuntimeError(this.dependencies.supportsCoordinatedCompaction() ? "busy" : "unsupported", reason);
|
|
2511
|
+
const history = session.rpc.history;
|
|
2512
|
+
if (history === void 0) throw new WorkbenchRuntimeError("unsupported", "Runtime context compaction is unavailable");
|
|
2513
|
+
this.#assertGeneration(sessionId, generation);
|
|
2514
|
+
const result = await history.compact();
|
|
2515
|
+
this.#assertGeneration(sessionId, generation);
|
|
2516
|
+
return {
|
|
2517
|
+
outcome: !result.success ? "failed" : !result.summaryContent?.trim() ? "no-summary" : result.tokensRemoved <= 0 ? "no-tokens-released" : "compacted",
|
|
2518
|
+
tokensRemoved: result.tokensRemoved,
|
|
2519
|
+
messagesRemoved: result.messagesRemoved,
|
|
2520
|
+
insights: await this.readInsights(sessionId)
|
|
2521
|
+
};
|
|
2522
|
+
} finally {
|
|
2523
|
+
runtime.workbench.compacting = false;
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
#queueOutput(snapshot) {
|
|
2527
|
+
return WorkbenchQueueSnapshotSchema.parse({
|
|
2528
|
+
generation: snapshot.runtimeGeneration,
|
|
2529
|
+
revision: snapshot.revision,
|
|
2530
|
+
items: snapshot.items.map((item) => ({ id: item.id, kind: item.kind, texts: item.displayTexts, actions: item.actions })),
|
|
2531
|
+
steeringMessages: snapshot.steeringMessages
|
|
2532
|
+
});
|
|
2533
|
+
}
|
|
2534
|
+
async queue(sessionId) {
|
|
2535
|
+
return this.#queueOutput(await this.queueSnapshot(sessionId));
|
|
2536
|
+
}
|
|
2537
|
+
async mutateQueue(sessionId, input) {
|
|
2538
|
+
const action = input.action === "edit" ? { action: input.action, text: input.prompt } : input.action === "move" ? { action: input.action, toPosition: input.toPosition } : { action: input.action };
|
|
2539
|
+
const result = await this.mutatePendingQueue(sessionId, {
|
|
2540
|
+
runtimeGeneration: input.generation,
|
|
2541
|
+
revision: input.revision,
|
|
2542
|
+
id: input.id,
|
|
2543
|
+
...action
|
|
2544
|
+
});
|
|
2545
|
+
if (["not-found", "not-steered", "stale-snapshot", "not-editable"].includes(result.outcome)) {
|
|
2546
|
+
throw new SessionOperationError(`QUEUE_${result.outcome.replaceAll("-", "_").toUpperCase()}`, `Queue action was not applied: ${result.outcome}; refresh the queue`);
|
|
2547
|
+
}
|
|
2548
|
+
return WorkbenchQueueMutationResultSchema.parse({
|
|
2549
|
+
changed: result.outcome !== "unchanged",
|
|
2550
|
+
snapshot: this.#queueOutput(result.snapshot)
|
|
2551
|
+
});
|
|
2552
|
+
}
|
|
2553
|
+
async commands(sessionId) {
|
|
2554
|
+
const catalog = await this.listCommands(sessionId);
|
|
2555
|
+
return catalog.commands.map((command) => WorkbenchCommandSchema.parse({
|
|
2556
|
+
name: command.name,
|
|
2557
|
+
description: command.description,
|
|
2558
|
+
...command.inputHint === void 0 ? {} : { argumentHint: command.inputHint },
|
|
2559
|
+
kind: command.kind === "workbench" ? "client" : command.kind,
|
|
2560
|
+
allowDuringExecution: command.allowDuringAgentExecution,
|
|
2561
|
+
supported: command.executable,
|
|
2562
|
+
...command.disabledReason === void 0 ? {} : { reason: command.disabledReason }
|
|
2563
|
+
}));
|
|
2564
|
+
}
|
|
2565
|
+
async invokeCommand(sessionId, name, input) {
|
|
2566
|
+
const catalog = await this.listCommands(sessionId);
|
|
2567
|
+
const canonical = name.replace(/^\/+/u, "").toLowerCase();
|
|
2568
|
+
const command = catalog.commands.find((entry) => entry.name.toLowerCase() === canonical || entry.aliases.some((alias) => alias.toLowerCase() === canonical));
|
|
2569
|
+
if (command === void 0) throw new WorkbenchRuntimeError("unknown-command", "Unknown slash command; choose explicitly to send it as text");
|
|
2570
|
+
if (!command.executable) throw new WorkbenchRuntimeError("unsupported", command.disabledReason ?? "This command is unavailable");
|
|
2571
|
+
if (command.action === "ask") return { kind: "text", text: (await this.ask(sessionId, input)).answer };
|
|
2572
|
+
if (command.action === "insights") return { kind: "text", text: JSON.stringify(await this.insights(sessionId), null, 2) };
|
|
2573
|
+
if (command.action === "compact") {
|
|
2574
|
+
const result = await this.compact(sessionId);
|
|
2575
|
+
return { kind: "text", text: result.success ? `Compacted ${result.tokensRemoved} tokens` : "Context compaction did not produce a smaller summary" };
|
|
2576
|
+
}
|
|
2577
|
+
if (command.action === "configure") {
|
|
2578
|
+
if (this.dependencies.configureSession === void 0) throw new WorkbenchRuntimeError("unsupported", "Session configuration is unavailable");
|
|
2579
|
+
const selected = input.trim();
|
|
2580
|
+
if (command.name === "model" && selected.length > 0) {
|
|
2581
|
+
await this.dependencies.configureSession(sessionId, { modelId: selected });
|
|
2582
|
+
} else {
|
|
2583
|
+
const mode = command.name === "plan" ? "plan" : command.name === "autopilot" ? "autopilot" : selected;
|
|
2584
|
+
const parsedMode = z.enum(["interactive", "plan", "autopilot"]).safeParse(mode);
|
|
2585
|
+
if (!parsedMode.success) throw new WorkbenchRuntimeError("unsupported", "Choose a model or mode using session configuration");
|
|
2586
|
+
await this.dependencies.configureSession(sessionId, { agentMode: parsedMode.data });
|
|
2587
|
+
}
|
|
2588
|
+
return { kind: "completed" };
|
|
2589
|
+
}
|
|
2590
|
+
const { runtime } = await this.#session(sessionId);
|
|
2591
|
+
const release = runtime.admitMutation();
|
|
2592
|
+
try {
|
|
2593
|
+
const result = await this.invokeRuntimeCommand(sessionId, { name, input });
|
|
2594
|
+
switch (result.kind) {
|
|
2595
|
+
case "text":
|
|
2596
|
+
return WorkbenchCommandResultSchema.parse({ kind: "text", text: result.text });
|
|
2597
|
+
case "completed":
|
|
2598
|
+
return result.message === void 0 ? { kind: "completed" } : WorkbenchCommandResultSchema.parse({ kind: "text", text: result.message });
|
|
2599
|
+
case "select-subcommand":
|
|
2600
|
+
return WorkbenchCommandResultSchema.parse({
|
|
2601
|
+
kind: "select-subcommand",
|
|
2602
|
+
choices: result.options.map((option) => ({ name: result.command, label: option.description || option.name, input: option.name }))
|
|
2603
|
+
});
|
|
2604
|
+
case "agent-prompt": {
|
|
2605
|
+
if (this.dependencies.sendCommandPrompt === void 0) {
|
|
2606
|
+
throw new WorkbenchRuntimeError("unsupported", "Agent-prompt commands require the main prompt delivery path");
|
|
2607
|
+
}
|
|
2608
|
+
const sent = await this.dependencies.sendCommandPrompt(sessionId, result);
|
|
2609
|
+
return WorkbenchCommandResultSchema.parse({ kind: "sent", messageId: sent.messageId });
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
} finally {
|
|
2613
|
+
release();
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
async insights(sessionId) {
|
|
2617
|
+
return projectWorkbenchInsights(await this.readInsights(sessionId));
|
|
2618
|
+
}
|
|
2619
|
+
async compact(sessionId) {
|
|
2620
|
+
const result = await this.compactContext(sessionId);
|
|
2621
|
+
return WorkbenchCompactResultSchema.parse({
|
|
2622
|
+
success: result.outcome === "compacted",
|
|
2623
|
+
tokensRemoved: result.tokensRemoved,
|
|
2624
|
+
messagesRemoved: result.messagesRemoved
|
|
2625
|
+
});
|
|
2626
|
+
}
|
|
2627
|
+
};
|
|
2628
|
+
|
|
2629
|
+
export {
|
|
2630
|
+
isClosedCopilotConnection,
|
|
2631
|
+
isDisposedCopilotSession,
|
|
2632
|
+
isSessionRestoreFailure,
|
|
2633
|
+
sessionRestoreFailure,
|
|
2634
|
+
diagnosticReference,
|
|
2635
|
+
diagnosticRequest,
|
|
2636
|
+
diagnosticError,
|
|
2637
|
+
recordOperationFailure,
|
|
2638
|
+
validateOperationResponse,
|
|
2639
|
+
measureOperationPhase,
|
|
2640
|
+
measureOperationStage,
|
|
2641
|
+
recordOperationTimelineRead,
|
|
2642
|
+
OperationDiagnostics,
|
|
2643
|
+
OperationMetrics,
|
|
2644
|
+
isDisplayableHumanMessage,
|
|
2645
|
+
ConversationNormalizer,
|
|
2646
|
+
SessionRuntime,
|
|
2647
|
+
SessionRuntimeRegistry,
|
|
2648
|
+
LiveTimelineProjection,
|
|
2649
|
+
sanitizeGitRemoteUrl,
|
|
2650
|
+
NotGitDirectoryError,
|
|
2651
|
+
resolveGitProjectIdentity,
|
|
2652
|
+
PromptPreparationError,
|
|
2653
|
+
SESSION_PROJECT_IDENTITY_CONCURRENCY,
|
|
2654
|
+
SessionProjectIdentityError,
|
|
2655
|
+
SessionProjectIdentityReader,
|
|
2656
|
+
readWorkbenchMetrics,
|
|
2657
|
+
projectWorkbenchInsights,
|
|
2658
|
+
SessionWorkbenchRuntime
|
|
2659
|
+
};
|