@granular-software/sdk 0.4.50 → 0.4.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -0
- package/dist/agent-evals.d.mts +2 -2
- package/dist/agent-evals.d.ts +2 -2
- package/dist/agent-evals.js +449 -179
- package/dist/agent-evals.js.map +1 -1
- package/dist/agent-evals.mjs +449 -179
- package/dist/agent-evals.mjs.map +1 -1
- package/dist/agent-harness.js +4 -2
- package/dist/agent-harness.js.map +1 -1
- package/dist/agent-harness.mjs +4 -2
- package/dist/agent-harness.mjs.map +1 -1
- package/dist/cli/index.js +487 -203
- package/dist/{client-BbI7ThzU.d.ts → client-NSH-tpNU.d.ts} +27 -11
- package/dist/{client-DaYFTHG8.d.mts → client-djorlOpn.d.mts} +27 -11
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +449 -179
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +449 -179
- package/dist/index.mjs.map +1 -1
- package/dist/{spend-RpJikX9w.d.mts → spend-BA-jZwZ0.d.mts} +125 -4
- package/dist/{spend-RpJikX9w.d.ts → spend-BA-jZwZ0.d.ts} +125 -4
- package/dist/spend.d.mts +1 -1
- package/dist/spend.d.ts +1 -1
- package/dist/spend.js +6 -1
- package/dist/spend.js.map +1 -1
- package/dist/spend.mjs +6 -1
- package/dist/spend.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -22,11 +22,11 @@ var __export = (target, all) => {
|
|
|
22
22
|
for (var name in all)
|
|
23
23
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
24
24
|
};
|
|
25
|
-
var __copyProps = (to,
|
|
26
|
-
if (
|
|
27
|
-
for (let key of __getOwnPropNames(
|
|
25
|
+
var __copyProps = (to, from2, except, desc) => {
|
|
26
|
+
if (from2 && typeof from2 === "object" || typeof from2 === "function") {
|
|
27
|
+
for (let key of __getOwnPropNames(from2))
|
|
28
28
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
29
|
-
__defProp(to, key, { get: () =>
|
|
29
|
+
__defProp(to, key, { get: () => from2[key], enumerable: !(desc = __getOwnPropDesc(from2, key)) || desc.enumerable });
|
|
30
30
|
}
|
|
31
31
|
return to;
|
|
32
32
|
};
|
|
@@ -4043,16 +4043,37 @@ var WSClient = class {
|
|
|
4043
4043
|
tokenRefreshTimer = null;
|
|
4044
4044
|
isExplicitlyDisconnected = false;
|
|
4045
4045
|
reconnectAttempts = 0;
|
|
4046
|
+
connectPromise = null;
|
|
4047
|
+
connectionEpoch = 0;
|
|
4048
|
+
cancelConnectAttempt = null;
|
|
4046
4049
|
options;
|
|
4047
4050
|
constructor(options) {
|
|
4048
4051
|
this.options = options;
|
|
4049
4052
|
this.url = options.url;
|
|
4050
4053
|
this.sessionId = options.sessionId;
|
|
4051
4054
|
this.token = options.token;
|
|
4055
|
+
if (options.initialDocumentSnapshot) {
|
|
4056
|
+
this.seedDocumentSnapshot(options.initialDocumentSnapshot);
|
|
4057
|
+
}
|
|
4052
4058
|
}
|
|
4053
4059
|
get currentSessionId() {
|
|
4054
4060
|
return this.sessionId;
|
|
4055
4061
|
}
|
|
4062
|
+
seedDocumentSnapshot(document) {
|
|
4063
|
+
if (!document || typeof document !== "object" || Array.isArray(document)) {
|
|
4064
|
+
return;
|
|
4065
|
+
}
|
|
4066
|
+
try {
|
|
4067
|
+
this.doc = document instanceof Uint8Array ? Automerge.load(document) : Automerge.from(document);
|
|
4068
|
+
this.syncState = Automerge.initSyncState();
|
|
4069
|
+
this.emit("sync", this.doc);
|
|
4070
|
+
} catch (error) {
|
|
4071
|
+
console.warn("[Granular] Failed to seed cached session document", error);
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
saveDocumentSnapshot() {
|
|
4075
|
+
return Automerge.save(this.doc);
|
|
4076
|
+
}
|
|
4056
4077
|
clearTokenRefreshTimer() {
|
|
4057
4078
|
if (this.tokenRefreshTimer) {
|
|
4058
4079
|
clearTimeout(this.tokenRefreshTimer);
|
|
@@ -4162,8 +4183,23 @@ var WSClient = class {
|
|
|
4162
4183
|
* Connect to the WebSocket server
|
|
4163
4184
|
* @returns {Promise<void>} Resolves when connection is open
|
|
4164
4185
|
*/
|
|
4165
|
-
async connect() {
|
|
4186
|
+
async connect(options = {}) {
|
|
4187
|
+
if (this.ws?.readyState === READY_STATE_OPEN) return;
|
|
4188
|
+
if (this.connectPromise) return this.connectPromise;
|
|
4189
|
+
const connectPromise = this.connectAttempt(options.signal);
|
|
4190
|
+
this.connectPromise = connectPromise;
|
|
4191
|
+
try {
|
|
4192
|
+
await connectPromise;
|
|
4193
|
+
} finally {
|
|
4194
|
+
if (this.connectPromise === connectPromise) {
|
|
4195
|
+
this.connectPromise = null;
|
|
4196
|
+
}
|
|
4197
|
+
}
|
|
4198
|
+
}
|
|
4199
|
+
async connectAttempt(signal) {
|
|
4200
|
+
if (signal?.aborted) throw new Error("WebSocket connect aborted");
|
|
4166
4201
|
const token = await this.resolveTokenForConnect();
|
|
4202
|
+
if (signal?.aborted) throw new Error("WebSocket connect aborted");
|
|
4167
4203
|
this.isExplicitlyDisconnected = false;
|
|
4168
4204
|
this.scheduleTokenRefresh();
|
|
4169
4205
|
if (this.reconnectTimer) {
|
|
@@ -4175,7 +4211,7 @@ var WSClient = class {
|
|
|
4175
4211
|
try {
|
|
4176
4212
|
const wsModule = await Promise.resolve().then(() => (init_wrapper(), wrapper_exports));
|
|
4177
4213
|
WebSocketClass = wsModule.default || wsModule;
|
|
4178
|
-
} catch
|
|
4214
|
+
} catch {
|
|
4179
4215
|
}
|
|
4180
4216
|
}
|
|
4181
4217
|
if (!WebSocketClass) {
|
|
@@ -4183,83 +4219,97 @@ var WSClient = class {
|
|
|
4183
4219
|
'No WebSocket implementation found. If using Node.js, please install "ws" and pass the constructor to the SDK options: { WebSocketCtor: WebSocket }.'
|
|
4184
4220
|
);
|
|
4185
4221
|
}
|
|
4222
|
+
const epoch = ++this.connectionEpoch;
|
|
4223
|
+
const wsUrl = new URL(this.url);
|
|
4224
|
+
wsUrl.searchParams.set("sessionId", this.sessionId);
|
|
4225
|
+
wsUrl.searchParams.set("token", token);
|
|
4226
|
+
const socket = new WebSocketClass(wsUrl.toString());
|
|
4227
|
+
this.ws = socket;
|
|
4186
4228
|
return new Promise((resolve, reject) => {
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
if (
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
this.reconnectTimer = null;
|
|
4199
|
-
}
|
|
4200
|
-
this.reconnectAttempts = 0;
|
|
4201
|
-
this.emit("open", {});
|
|
4202
|
-
resolve();
|
|
4203
|
-
});
|
|
4204
|
-
socket.on("message", (data) => {
|
|
4205
|
-
try {
|
|
4206
|
-
const message = JSON.parse(data.toString());
|
|
4207
|
-
this.handleMessage(message);
|
|
4208
|
-
} catch (error) {
|
|
4209
|
-
console.error("[Granular] Failed to parse message:", error);
|
|
4210
|
-
}
|
|
4211
|
-
});
|
|
4212
|
-
socket.on("error", (error) => {
|
|
4213
|
-
this.emit("error", error);
|
|
4214
|
-
if (socket.readyState !== READY_STATE_OPEN) {
|
|
4215
|
-
reject(error);
|
|
4216
|
-
}
|
|
4217
|
-
});
|
|
4218
|
-
socket.on("close", (code, reason) => {
|
|
4219
|
-
this.handleDisconnect({
|
|
4220
|
-
code,
|
|
4221
|
-
reason: this.normalizeReason(reason),
|
|
4222
|
-
// ws does not provide wasClean on Node-style close callback
|
|
4223
|
-
wasClean: code === 1e3
|
|
4224
|
-
});
|
|
4225
|
-
});
|
|
4229
|
+
let settled = false;
|
|
4230
|
+
const isCurrent = () => this.connectionEpoch === epoch && this.ws === socket;
|
|
4231
|
+
const finish = (error) => {
|
|
4232
|
+
if (settled) return;
|
|
4233
|
+
settled = true;
|
|
4234
|
+
if (this.cancelConnectAttempt === handleAbort) {
|
|
4235
|
+
this.cancelConnectAttempt = null;
|
|
4236
|
+
}
|
|
4237
|
+
signal?.removeEventListener("abort", handleAbort);
|
|
4238
|
+
if (error) {
|
|
4239
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
4226
4240
|
} else {
|
|
4227
|
-
|
|
4228
|
-
if (this.reconnectTimer) {
|
|
4229
|
-
clearTimeout(this.reconnectTimer);
|
|
4230
|
-
this.reconnectTimer = null;
|
|
4231
|
-
}
|
|
4232
|
-
this.reconnectAttempts = 0;
|
|
4233
|
-
this.emit("open", {});
|
|
4234
|
-
resolve();
|
|
4235
|
-
};
|
|
4236
|
-
this.ws.onmessage = (event) => {
|
|
4237
|
-
try {
|
|
4238
|
-
const data = event.data;
|
|
4239
|
-
const message = JSON.parse(data.toString());
|
|
4240
|
-
this.handleMessage(message);
|
|
4241
|
-
} catch (error) {
|
|
4242
|
-
console.error("[Granular] Failed to parse message:", error);
|
|
4243
|
-
}
|
|
4244
|
-
};
|
|
4245
|
-
this.ws.onerror = (event) => {
|
|
4246
|
-
const error = new Error("WebSocket error");
|
|
4247
|
-
error.event = event;
|
|
4248
|
-
this.emit("error", error);
|
|
4249
|
-
if (this.ws?.readyState !== READY_STATE_OPEN) {
|
|
4250
|
-
reject(error);
|
|
4251
|
-
}
|
|
4252
|
-
};
|
|
4253
|
-
this.ws.onclose = (event) => {
|
|
4254
|
-
this.handleDisconnect({
|
|
4255
|
-
code: event.code,
|
|
4256
|
-
reason: event.reason,
|
|
4257
|
-
wasClean: event.wasClean
|
|
4258
|
-
});
|
|
4259
|
-
};
|
|
4241
|
+
resolve();
|
|
4260
4242
|
}
|
|
4261
|
-
}
|
|
4262
|
-
|
|
4243
|
+
};
|
|
4244
|
+
const closeStaleSocket = () => {
|
|
4245
|
+
try {
|
|
4246
|
+
socket.close(1e3, "Stale connection attempt");
|
|
4247
|
+
} catch {
|
|
4248
|
+
}
|
|
4249
|
+
};
|
|
4250
|
+
const handleAbort = () => {
|
|
4251
|
+
if (isCurrent()) {
|
|
4252
|
+
this.connectionEpoch += 1;
|
|
4253
|
+
this.ws = null;
|
|
4254
|
+
}
|
|
4255
|
+
closeStaleSocket();
|
|
4256
|
+
finish(new Error("WebSocket connect aborted"));
|
|
4257
|
+
};
|
|
4258
|
+
this.cancelConnectAttempt = handleAbort;
|
|
4259
|
+
const handleOpen = () => {
|
|
4260
|
+
if (!isCurrent()) {
|
|
4261
|
+
closeStaleSocket();
|
|
4262
|
+
return;
|
|
4263
|
+
}
|
|
4264
|
+
this.reconnectAttempts = 0;
|
|
4265
|
+
this.emit("open", {});
|
|
4266
|
+
finish();
|
|
4267
|
+
};
|
|
4268
|
+
const handleMessage = (data) => {
|
|
4269
|
+
if (!isCurrent()) return;
|
|
4270
|
+
try {
|
|
4271
|
+
const text = typeof data === "string" ? data : data && typeof data === "object" && "toString" in data ? String(data.toString()) : "";
|
|
4272
|
+
this.handleMessage(JSON.parse(text));
|
|
4273
|
+
} catch (error) {
|
|
4274
|
+
console.error("[Granular] Failed to parse message:", error);
|
|
4275
|
+
}
|
|
4276
|
+
};
|
|
4277
|
+
const handleError = (error) => {
|
|
4278
|
+
if (!isCurrent()) return;
|
|
4279
|
+
const typedError = error instanceof Error ? error : new Error("WebSocket error");
|
|
4280
|
+
this.emit("error", typedError);
|
|
4281
|
+
if (socket.readyState !== READY_STATE_OPEN) finish(typedError);
|
|
4282
|
+
};
|
|
4283
|
+
const handleClose = (close) => {
|
|
4284
|
+
if (!isCurrent()) return;
|
|
4285
|
+
if (!settled) {
|
|
4286
|
+
finish(
|
|
4287
|
+
new Error(
|
|
4288
|
+
`WebSocket closed before ready${close.code ? ` (code=${close.code})` : ""}`
|
|
4289
|
+
)
|
|
4290
|
+
);
|
|
4291
|
+
}
|
|
4292
|
+
this.handleDisconnect({
|
|
4293
|
+
code: close.code,
|
|
4294
|
+
reason: this.normalizeReason(close.reason),
|
|
4295
|
+
wasClean: close.wasClean
|
|
4296
|
+
});
|
|
4297
|
+
};
|
|
4298
|
+
signal?.addEventListener("abort", handleAbort, { once: true });
|
|
4299
|
+
const nodeSocket = socket;
|
|
4300
|
+
if (typeof nodeSocket.on === "function") {
|
|
4301
|
+
nodeSocket.on("open", handleOpen);
|
|
4302
|
+
nodeSocket.on("message", handleMessage);
|
|
4303
|
+
nodeSocket.on("error", handleError);
|
|
4304
|
+
nodeSocket.on(
|
|
4305
|
+
"close",
|
|
4306
|
+
(code, reason) => handleClose({ code, reason, wasClean: code === 1e3 })
|
|
4307
|
+
);
|
|
4308
|
+
} else {
|
|
4309
|
+
socket.onopen = handleOpen;
|
|
4310
|
+
socket.onmessage = (event) => handleMessage(event.data);
|
|
4311
|
+
socket.onerror = handleError;
|
|
4312
|
+
socket.onclose = (event) => handleClose(event);
|
|
4263
4313
|
}
|
|
4264
4314
|
});
|
|
4265
4315
|
}
|
|
@@ -4277,9 +4327,58 @@ var WSClient = class {
|
|
|
4277
4327
|
return void 0;
|
|
4278
4328
|
}
|
|
4279
4329
|
rejectPending(error) {
|
|
4280
|
-
this.messageQueue.forEach((pending) =>
|
|
4330
|
+
this.messageQueue.forEach((pending) => {
|
|
4331
|
+
clearTimeout(pending.timeout);
|
|
4332
|
+
pending.reject(error);
|
|
4333
|
+
});
|
|
4281
4334
|
this.messageQueue = [];
|
|
4282
4335
|
}
|
|
4336
|
+
emitReconnectErrorMessage(error) {
|
|
4337
|
+
const reconnectInfo = {
|
|
4338
|
+
error,
|
|
4339
|
+
sessionId: this.sessionId,
|
|
4340
|
+
timestamp: Date.now()
|
|
4341
|
+
};
|
|
4342
|
+
this.emit("reconnect_error", reconnectInfo);
|
|
4343
|
+
if (this.options.onReconnectError) {
|
|
4344
|
+
try {
|
|
4345
|
+
this.options.onReconnectError(reconnectInfo);
|
|
4346
|
+
} catch (callbackError) {
|
|
4347
|
+
console.error(
|
|
4348
|
+
"[Granular] onReconnectError callback failed:",
|
|
4349
|
+
callbackError
|
|
4350
|
+
);
|
|
4351
|
+
}
|
|
4352
|
+
}
|
|
4353
|
+
}
|
|
4354
|
+
scheduleReconnectAttempt() {
|
|
4355
|
+
if (this.isExplicitlyDisconnected || this.reconnectTimer) return null;
|
|
4356
|
+
const baseReconnectDelayMs = typeof this.options.reconnectDelayMs === "number" && Number.isFinite(this.options.reconnectDelayMs) && this.options.reconnectDelayMs > 0 ? this.options.reconnectDelayMs : DEFAULT_RECONNECT_DELAY_MS;
|
|
4357
|
+
const maxReconnectAttempts = typeof this.options.maxReconnectAttempts === "number" && Number.isFinite(this.options.maxReconnectAttempts) && this.options.maxReconnectAttempts >= 0 ? Math.floor(this.options.maxReconnectAttempts) : DEFAULT_MAX_RECONNECT_ATTEMPTS;
|
|
4358
|
+
if (this.reconnectAttempts >= maxReconnectAttempts) {
|
|
4359
|
+
this.emitReconnectErrorMessage(
|
|
4360
|
+
`WebSocket reconnect attempts exhausted after ${maxReconnectAttempts} attempt(s).`
|
|
4361
|
+
);
|
|
4362
|
+
return null;
|
|
4363
|
+
}
|
|
4364
|
+
this.reconnectAttempts += 1;
|
|
4365
|
+
const reconnectDelayMs = Math.min(
|
|
4366
|
+
3e4,
|
|
4367
|
+
baseReconnectDelayMs * 2 ** Math.max(0, this.reconnectAttempts - 1)
|
|
4368
|
+
);
|
|
4369
|
+
this.reconnectTimer = setTimeout(() => {
|
|
4370
|
+
this.reconnectTimer = null;
|
|
4371
|
+
console.log("[Granular] Attempting reconnect...");
|
|
4372
|
+
this.connect().catch((error) => {
|
|
4373
|
+
console.error("[Granular] Reconnect failed:", error);
|
|
4374
|
+
this.emitReconnectErrorMessage(
|
|
4375
|
+
error instanceof Error ? error.message : String(error)
|
|
4376
|
+
);
|
|
4377
|
+
this.scheduleReconnectAttempt();
|
|
4378
|
+
});
|
|
4379
|
+
}, reconnectDelayMs);
|
|
4380
|
+
return reconnectDelayMs;
|
|
4381
|
+
}
|
|
4283
4382
|
buildDisconnectError(info) {
|
|
4284
4383
|
const details = [
|
|
4285
4384
|
info.code !== void 0 ? `code=${info.code}` : void 0,
|
|
@@ -4289,8 +4388,6 @@ var WSClient = class {
|
|
|
4289
4388
|
return new Error(`WebSocket disconnected${suffix}`);
|
|
4290
4389
|
}
|
|
4291
4390
|
handleDisconnect(close = {}) {
|
|
4292
|
-
const baseReconnectDelayMs = typeof this.options.reconnectDelayMs === "number" && Number.isFinite(this.options.reconnectDelayMs) && this.options.reconnectDelayMs > 0 ? this.options.reconnectDelayMs : DEFAULT_RECONNECT_DELAY_MS;
|
|
4293
|
-
const maxReconnectAttempts = typeof this.options.maxReconnectAttempts === "number" && Number.isFinite(this.options.maxReconnectAttempts) && this.options.maxReconnectAttempts >= 0 ? Math.floor(this.options.maxReconnectAttempts) : DEFAULT_MAX_RECONNECT_ATTEMPTS;
|
|
4294
4391
|
const unexpected = !this.isExplicitlyDisconnected;
|
|
4295
4392
|
const info = {
|
|
4296
4393
|
code: close.code,
|
|
@@ -4310,32 +4407,9 @@ var WSClient = class {
|
|
|
4310
4407
|
const disconnectError = this.buildDisconnectError(info);
|
|
4311
4408
|
this.rejectPending(disconnectError);
|
|
4312
4409
|
this.emit("disconnect", info);
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
sessionId: this.sessionId,
|
|
4317
|
-
timestamp: Date.now()
|
|
4318
|
-
};
|
|
4319
|
-
this.emit("reconnect_error", reconnectInfo);
|
|
4320
|
-
if (this.options.onReconnectError) {
|
|
4321
|
-
try {
|
|
4322
|
-
this.options.onReconnectError(reconnectInfo);
|
|
4323
|
-
} catch (callbackError) {
|
|
4324
|
-
console.error(
|
|
4325
|
-
"[Granular] onReconnectError callback failed:",
|
|
4326
|
-
callbackError
|
|
4327
|
-
);
|
|
4328
|
-
}
|
|
4329
|
-
}
|
|
4330
|
-
return;
|
|
4331
|
-
}
|
|
4332
|
-
this.reconnectAttempts += 1;
|
|
4333
|
-
const reconnectDelayMs = Math.min(
|
|
4334
|
-
3e4,
|
|
4335
|
-
baseReconnectDelayMs * 2 ** Math.max(0, this.reconnectAttempts - 1)
|
|
4336
|
-
);
|
|
4337
|
-
info.reconnectScheduled = true;
|
|
4338
|
-
info.reconnectDelayMs = reconnectDelayMs;
|
|
4410
|
+
const reconnectDelayMs = this.scheduleReconnectAttempt();
|
|
4411
|
+
info.reconnectScheduled = reconnectDelayMs !== null;
|
|
4412
|
+
if (reconnectDelayMs !== null) info.reconnectDelayMs = reconnectDelayMs;
|
|
4339
4413
|
if (this.options.onUnexpectedClose) {
|
|
4340
4414
|
try {
|
|
4341
4415
|
this.options.onUnexpectedClose(info);
|
|
@@ -4346,28 +4420,6 @@ var WSClient = class {
|
|
|
4346
4420
|
);
|
|
4347
4421
|
}
|
|
4348
4422
|
}
|
|
4349
|
-
this.reconnectTimer = setTimeout(() => {
|
|
4350
|
-
console.log("[Granular] Attempting reconnect...");
|
|
4351
|
-
this.connect().catch((error) => {
|
|
4352
|
-
console.error("[Granular] Reconnect failed:", error);
|
|
4353
|
-
const reconnectInfo = {
|
|
4354
|
-
error: error instanceof Error ? error.message : String(error),
|
|
4355
|
-
sessionId: this.sessionId,
|
|
4356
|
-
timestamp: Date.now()
|
|
4357
|
-
};
|
|
4358
|
-
this.emit("reconnect_error", reconnectInfo);
|
|
4359
|
-
if (this.options.onReconnectError) {
|
|
4360
|
-
try {
|
|
4361
|
-
this.options.onReconnectError(reconnectInfo);
|
|
4362
|
-
} catch (callbackError) {
|
|
4363
|
-
console.error(
|
|
4364
|
-
"[Granular] onReconnectError callback failed:",
|
|
4365
|
-
callbackError
|
|
4366
|
-
);
|
|
4367
|
-
}
|
|
4368
|
-
}
|
|
4369
|
-
});
|
|
4370
|
-
}, reconnectDelayMs);
|
|
4371
4423
|
}
|
|
4372
4424
|
}
|
|
4373
4425
|
handleMessage(message) {
|
|
@@ -4478,6 +4530,7 @@ var WSClient = class {
|
|
|
4478
4530
|
const response = message;
|
|
4479
4531
|
const pending = this.messageQueue.find((q) => q.id === response.id);
|
|
4480
4532
|
if (pending) {
|
|
4533
|
+
clearTimeout(pending.timeout);
|
|
4481
4534
|
if (response.type === "rpc_error") {
|
|
4482
4535
|
pending.reject(
|
|
4483
4536
|
new Error(
|
|
@@ -4523,16 +4576,22 @@ var WSClient = class {
|
|
|
4523
4576
|
id
|
|
4524
4577
|
};
|
|
4525
4578
|
return new Promise((resolve, reject) => {
|
|
4526
|
-
this.messageQueue.push({ resolve, reject, id });
|
|
4527
|
-
this.ws.send(JSON.stringify(request));
|
|
4528
4579
|
const timeoutMs = rpcTimeoutMsForMethod(method);
|
|
4529
|
-
setTimeout(() => {
|
|
4580
|
+
const timeout = setTimeout(() => {
|
|
4530
4581
|
const pending = this.messageQueue.find((q) => q.id === id);
|
|
4531
4582
|
if (pending) {
|
|
4532
4583
|
this.messageQueue = this.messageQueue.filter((q) => q.id !== id);
|
|
4533
4584
|
reject(new Error(`RPC timeout: ${method}`));
|
|
4534
4585
|
}
|
|
4535
4586
|
}, timeoutMs);
|
|
4587
|
+
this.messageQueue.push({ resolve, reject, id, timeout });
|
|
4588
|
+
try {
|
|
4589
|
+
this.ws.send(JSON.stringify(request));
|
|
4590
|
+
} catch (error) {
|
|
4591
|
+
clearTimeout(timeout);
|
|
4592
|
+
this.messageQueue = this.messageQueue.filter((q) => q.id !== id);
|
|
4593
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
4594
|
+
}
|
|
4536
4595
|
});
|
|
4537
4596
|
}
|
|
4538
4597
|
async handleIncomingRpc(request) {
|
|
@@ -4618,15 +4677,18 @@ var WSClient = class {
|
|
|
4618
4677
|
/**
|
|
4619
4678
|
* Disconnect the WebSocket and clear state
|
|
4620
4679
|
*/
|
|
4621
|
-
disconnect() {
|
|
4680
|
+
disconnect(options = {}) {
|
|
4622
4681
|
this.isExplicitlyDisconnected = true;
|
|
4682
|
+
this.cancelConnectAttempt?.();
|
|
4683
|
+
this.cancelConnectAttempt = null;
|
|
4684
|
+
this.connectionEpoch += 1;
|
|
4623
4685
|
if (this.reconnectTimer) {
|
|
4624
4686
|
clearTimeout(this.reconnectTimer);
|
|
4625
4687
|
this.reconnectTimer = null;
|
|
4626
4688
|
}
|
|
4627
4689
|
this.clearTokenRefreshTimer();
|
|
4628
4690
|
if (this.ws) {
|
|
4629
|
-
this.ws.close(1e3, "Client disconnect");
|
|
4691
|
+
this.ws.close(1e3, options.reason || "Client disconnect");
|
|
4630
4692
|
this.ws = null;
|
|
4631
4693
|
}
|
|
4632
4694
|
this.rejectPending(new Error("Client explicitly disconnected"));
|
|
@@ -4722,8 +4784,12 @@ function normalizePrompt(rawValue) {
|
|
|
4722
4784
|
const source = promptRecord || raw;
|
|
4723
4785
|
const id = typeof source.id === "string" ? source.id : typeof raw.id === "string" ? raw.id : typeof raw.promptId === "string" ? raw.promptId : "";
|
|
4724
4786
|
if (!id) return null;
|
|
4787
|
+
const jobId = typeof source.jobId === "string" && source.jobId.trim() ? source.jobId.trim() : typeof raw.jobId === "string" && raw.jobId.trim() ? raw.jobId.trim() : void 0;
|
|
4788
|
+
const turnId = typeof source.turnId === "string" && source.turnId.trim() ? source.turnId.trim() : typeof raw.turnId === "string" && raw.turnId.trim() ? raw.turnId.trim() : void 0;
|
|
4725
4789
|
return {
|
|
4726
4790
|
id,
|
|
4791
|
+
...jobId ? { jobId } : {},
|
|
4792
|
+
...turnId ? { turnId } : {},
|
|
4727
4793
|
type: normalizePromptType(source === raw ? raw : { ...raw, ...source }),
|
|
4728
4794
|
title: typeof source.title === "string" ? source.title : "Input required",
|
|
4729
4795
|
message: typeof source.message === "string" ? source.message : "",
|
|
@@ -4810,6 +4876,9 @@ var Session = class {
|
|
|
4810
4876
|
this.initialQuota = options.initialQuota || null;
|
|
4811
4877
|
this.setupEventHandlers();
|
|
4812
4878
|
this.setupToolInvokeHandler();
|
|
4879
|
+
this.currentDomainRevision = this.extractDomainRevisionFromDoc(
|
|
4880
|
+
this.client.doc
|
|
4881
|
+
);
|
|
4813
4882
|
}
|
|
4814
4883
|
extractDomainRevisionFromDoc(doc) {
|
|
4815
4884
|
const domain = doc?.domain;
|
|
@@ -5715,6 +5784,7 @@ function normalizeJobAgentMessageEnvelope(data) {
|
|
|
5715
5784
|
}
|
|
5716
5785
|
return {
|
|
5717
5786
|
jobId: d.jobId,
|
|
5787
|
+
...typeof d.turnId === "string" && d.turnId.trim() ? { turnId: d.turnId.trim() } : {},
|
|
5718
5788
|
message: {
|
|
5719
5789
|
messageId: d.messageId,
|
|
5720
5790
|
kind: d.kind === "artifacts" ? "artifacts" : "text",
|
|
@@ -6373,9 +6443,14 @@ function normalizeShowRefs(value) {
|
|
|
6373
6443
|
variableNames: normalizeRefs(record.variableNames),
|
|
6374
6444
|
fileIds: normalizeRefs(record.fileIds),
|
|
6375
6445
|
sessionArtifactIds: normalizeRefs(record.sessionArtifactIds),
|
|
6376
|
-
actionSuggestions: normalizeActionSuggestions(record.actionSuggestions)
|
|
6446
|
+
actionSuggestions: normalizeActionSuggestions(record.actionSuggestions),
|
|
6447
|
+
tables: Array.isArray(record.tables) ? record.tables.filter(
|
|
6448
|
+
(table) => Boolean(
|
|
6449
|
+
table && typeof table === "object" && !Array.isArray(table) && Array.isArray(table.columns) && Array.isArray(table.rows)
|
|
6450
|
+
)
|
|
6451
|
+
) : void 0
|
|
6377
6452
|
};
|
|
6378
|
-
return show.entryPaths || show.listNames || show.variableNames || show.fileIds || show.sessionArtifactIds || show.actionSuggestions ? show : void 0;
|
|
6453
|
+
return show.entryPaths || show.listNames || show.variableNames || show.fileIds || show.sessionArtifactIds || show.actionSuggestions || show.tables ? show : void 0;
|
|
6379
6454
|
}
|
|
6380
6455
|
function normalizeActionSuggestions(value) {
|
|
6381
6456
|
if (!Array.isArray(value)) return void 0;
|
|
@@ -6397,6 +6472,76 @@ function normalizeActionSuggestions(value) {
|
|
|
6397
6472
|
}
|
|
6398
6473
|
return suggestions.length ? suggestions : void 0;
|
|
6399
6474
|
}
|
|
6475
|
+
var TRANSCRIPT_MESSAGE_PART_LIMIT = 128;
|
|
6476
|
+
var TRANSCRIPT_MESSAGE_PART_TEXT_LIMIT = 2e5;
|
|
6477
|
+
var TRANSCRIPT_MESSAGE_PART_ACTION_LABEL_LIMIT = 1e3;
|
|
6478
|
+
var TRANSCRIPT_MESSAGE_ACTION_LIMIT = 64;
|
|
6479
|
+
function normalizeConversationMessageActions(value) {
|
|
6480
|
+
if (!Array.isArray(value) || value.length === 0) return void 0;
|
|
6481
|
+
const actions = [];
|
|
6482
|
+
for (const item of value.slice(0, TRANSCRIPT_MESSAGE_ACTION_LIMIT)) {
|
|
6483
|
+
const record = asRecord3(item);
|
|
6484
|
+
const kind = record?.kind;
|
|
6485
|
+
const label = trimString(record?.label ?? record?.title);
|
|
6486
|
+
const status = record?.status;
|
|
6487
|
+
if (kind !== "frontend" && kind !== "backend" && kind !== "system" || !label || label.length > TRANSCRIPT_MESSAGE_PART_ACTION_LABEL_LIMIT) {
|
|
6488
|
+
continue;
|
|
6489
|
+
}
|
|
6490
|
+
actions.push({
|
|
6491
|
+
kind,
|
|
6492
|
+
label,
|
|
6493
|
+
...status === "done" || status === "queued" || status === "failed" ? { status } : {}
|
|
6494
|
+
});
|
|
6495
|
+
}
|
|
6496
|
+
return actions.length ? actions : void 0;
|
|
6497
|
+
}
|
|
6498
|
+
function normalizeConversationMessageParts(value, canonicalContent, canonicalActions) {
|
|
6499
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > TRANSCRIPT_MESSAGE_PART_LIMIT) {
|
|
6500
|
+
return void 0;
|
|
6501
|
+
}
|
|
6502
|
+
const parts = [];
|
|
6503
|
+
const canonicalActionsById = new Map(
|
|
6504
|
+
(canonicalActions || []).map((action) => [
|
|
6505
|
+
`${action.kind}:${action.label}`,
|
|
6506
|
+
action
|
|
6507
|
+
])
|
|
6508
|
+
);
|
|
6509
|
+
const seenActionIds = /* @__PURE__ */ new Set();
|
|
6510
|
+
let textLength = 0;
|
|
6511
|
+
for (const item of value) {
|
|
6512
|
+
const record = asRecord3(item);
|
|
6513
|
+
if (!record) return void 0;
|
|
6514
|
+
if (record.type === "text") {
|
|
6515
|
+
if (typeof record.text !== "string" || record.text.length === 0) {
|
|
6516
|
+
return void 0;
|
|
6517
|
+
}
|
|
6518
|
+
textLength += record.text.length;
|
|
6519
|
+
if (textLength > TRANSCRIPT_MESSAGE_PART_TEXT_LIMIT) return void 0;
|
|
6520
|
+
parts.push({ type: "text", text: record.text });
|
|
6521
|
+
continue;
|
|
6522
|
+
}
|
|
6523
|
+
if (record.type !== "action") return void 0;
|
|
6524
|
+
const action = asRecord3(record.action);
|
|
6525
|
+
const kind = action?.kind;
|
|
6526
|
+
const label = trimString(action?.label);
|
|
6527
|
+
if (kind !== "frontend" && kind !== "backend" && kind !== "system" || !label || label.length > TRANSCRIPT_MESSAGE_PART_ACTION_LABEL_LIMIT) {
|
|
6528
|
+
return void 0;
|
|
6529
|
+
}
|
|
6530
|
+
const actionId = `${kind}:${label}`;
|
|
6531
|
+
const canonicalAction = canonicalActionsById.get(actionId);
|
|
6532
|
+
if (!canonicalAction) return void 0;
|
|
6533
|
+
if (seenActionIds.has(actionId)) continue;
|
|
6534
|
+
seenActionIds.add(actionId);
|
|
6535
|
+
parts.push({
|
|
6536
|
+
type: "action",
|
|
6537
|
+
action: canonicalAction
|
|
6538
|
+
});
|
|
6539
|
+
}
|
|
6540
|
+
const orderedText = parts.filter(
|
|
6541
|
+
(part) => part.type === "text"
|
|
6542
|
+
).map((part) => part.text).join("");
|
|
6543
|
+
return orderedText === canonicalContent ? parts : void 0;
|
|
6544
|
+
}
|
|
6400
6545
|
function stringifyTranscriptValue(value, fallback = "") {
|
|
6401
6546
|
if (typeof value === "string") {
|
|
6402
6547
|
return value.trim() || fallback;
|
|
@@ -6555,10 +6700,12 @@ function normalizeConversationMessage(raw, artifactsById) {
|
|
|
6555
6700
|
const content = trimString(
|
|
6556
6701
|
record.content ?? record.reply ?? record.message ?? record.text
|
|
6557
6702
|
);
|
|
6703
|
+
const actions = role === "assistant" ? normalizeConversationMessageActions(record.actions) : void 0;
|
|
6704
|
+
const parts = role === "assistant" ? normalizeConversationMessageParts(record.parts, content, actions) : void 0;
|
|
6558
6705
|
const show = normalizeShowRefs(record.show);
|
|
6559
6706
|
const id = asString(record.id) || crypto.randomUUID();
|
|
6560
6707
|
const timestamp = asNumber(record.timestamp) || asNumber(record.ts) || 0;
|
|
6561
|
-
if (!content && !show) return null;
|
|
6708
|
+
if (!content && !show && !actions?.length) return null;
|
|
6562
6709
|
const artifactHistory = buildArtifactHistory(show, artifactsById);
|
|
6563
6710
|
const historyContent = role === "assistant" ? content && artifactHistory ? `[Assistant reply]
|
|
6564
6711
|
${content}
|
|
@@ -6573,6 +6720,8 @@ ${content}` : artifactHistory : void 0;
|
|
|
6573
6720
|
jobId: asString(record.jobId),
|
|
6574
6721
|
promptId: asString(record.promptId),
|
|
6575
6722
|
show,
|
|
6723
|
+
actions,
|
|
6724
|
+
parts,
|
|
6576
6725
|
historyContent,
|
|
6577
6726
|
source: "conversation"
|
|
6578
6727
|
};
|
|
@@ -12150,7 +12299,12 @@ async function recordOpenAIUsageSpend(options) {
|
|
|
12150
12299
|
const metadata = {
|
|
12151
12300
|
...options.metadata || {},
|
|
12152
12301
|
...options.usage.rawUsage !== void 0 ? { openaiUsage: options.usage.rawUsage } : {},
|
|
12153
|
-
usageContext: context
|
|
12302
|
+
usageContext: context,
|
|
12303
|
+
pricingContextTier: options.usage.pricingContextTier,
|
|
12304
|
+
cacheWritePricePerMillionMicros: options.usage.cacheWritePricePerMillionMicros,
|
|
12305
|
+
cacheWriteTokens: options.usage.cacheWriteTokens,
|
|
12306
|
+
cacheWriteCostMicros: options.usage.cacheWriteCostMicros,
|
|
12307
|
+
longContextThresholdTokens: options.usage.longContextThresholdTokens
|
|
12154
12308
|
};
|
|
12155
12309
|
const response = await fetch(
|
|
12156
12310
|
`${toGranularHttpBase(options.apiUrl)}/control/spend/events`,
|
|
@@ -13078,11 +13232,20 @@ function buildStateMachineModelMutations(modelPath, machines) {
|
|
|
13078
13232
|
return mutations;
|
|
13079
13233
|
}
|
|
13080
13234
|
function buildMachineTypes(classSummary, machine) {
|
|
13235
|
+
const stateGlossary = machine.states.map((state) => {
|
|
13236
|
+
const label = state.label && state.label !== state.name ? state.label : null;
|
|
13237
|
+
const meaning = [label, state.description].filter(Boolean).join(" \u2014 ");
|
|
13238
|
+
const finalMarker = state.isFinal ? " Final state." : "";
|
|
13239
|
+
return `${state.name}${meaning ? `: ${meaning}` : "."}${finalMarker}`;
|
|
13240
|
+
});
|
|
13081
13241
|
return [
|
|
13082
13242
|
{
|
|
13083
13243
|
kind: "union",
|
|
13084
13244
|
name: stateTypeName(classSummary.name, machine.name),
|
|
13085
|
-
docs: [
|
|
13245
|
+
docs: [
|
|
13246
|
+
`Allowed states for ${classSummary.name}.${machine.name}.`,
|
|
13247
|
+
...stateGlossary
|
|
13248
|
+
],
|
|
13086
13249
|
members: machine.states.map((state) => state.name)
|
|
13087
13250
|
},
|
|
13088
13251
|
{
|
|
@@ -13430,7 +13593,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
|
|
|
13430
13593
|
},
|
|
13431
13594
|
add_transition: async (value, {
|
|
13432
13595
|
name,
|
|
13433
|
-
from,
|
|
13596
|
+
from: from2,
|
|
13434
13597
|
to,
|
|
13435
13598
|
label,
|
|
13436
13599
|
description,
|
|
@@ -13445,7 +13608,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
|
|
|
13445
13608
|
value.target.add_state_machine_transition(
|
|
13446
13609
|
value.name,
|
|
13447
13610
|
name,
|
|
13448
|
-
|
|
13611
|
+
from2,
|
|
13449
13612
|
to,
|
|
13450
13613
|
{
|
|
13451
13614
|
label,
|
|
@@ -13895,6 +14058,18 @@ function buildEffectMetamodelMutations(toolPath, spec) {
|
|
|
13895
14058
|
}
|
|
13896
14059
|
|
|
13897
14060
|
// src/client.ts
|
|
14061
|
+
var DEFAULT_CONVERSATION_SESSION_LIST_LIMIT = 100;
|
|
14062
|
+
var MAX_CONVERSATION_SESSION_LIST_LIMIT = 500;
|
|
14063
|
+
var MAX_CONVERSATION_SESSION_LIST_OFFSET = 1e5;
|
|
14064
|
+
function boundedSessionListInteger(value, name, fallback, minimum, maximum) {
|
|
14065
|
+
if (value === void 0) return fallback;
|
|
14066
|
+
if (!Number.isInteger(value) || value < minimum || value > maximum) {
|
|
14067
|
+
throw new RangeError(
|
|
14068
|
+
`Session list ${name} must be an integer between ${minimum} and ${maximum}.`
|
|
14069
|
+
);
|
|
14070
|
+
}
|
|
14071
|
+
return value;
|
|
14072
|
+
}
|
|
13898
14073
|
var STANDARD_MODULES_OPERATIONS = [
|
|
13899
14074
|
{
|
|
13900
14075
|
create: "entity",
|
|
@@ -14233,7 +14408,7 @@ var Environment = class _Environment {
|
|
|
14233
14408
|
}
|
|
14234
14409
|
get sessions() {
|
|
14235
14410
|
return {
|
|
14236
|
-
list: async (options) => this.listSessions(options
|
|
14411
|
+
list: async (options = {}) => this.listSessions(options),
|
|
14237
14412
|
create: async (options) => this.createSession(options),
|
|
14238
14413
|
connect: async (sessionId, options) => this.connectSession(sessionId, options),
|
|
14239
14414
|
reopen: async (sessionId, options) => this.reopenSession(sessionId, options),
|
|
@@ -14368,17 +14543,12 @@ var Environment = class _Environment {
|
|
|
14368
14543
|
*/
|
|
14369
14544
|
async disconnect() {
|
|
14370
14545
|
}
|
|
14371
|
-
async listSessions(
|
|
14372
|
-
|
|
14373
|
-
|
|
14374
|
-
|
|
14375
|
-
|
|
14376
|
-
|
|
14377
|
-
return [...active, ...closed].sort(
|
|
14378
|
-
(left, right) => Date.parse(right.lastSeenAt) - Date.parse(left.lastSeenAt)
|
|
14379
|
-
);
|
|
14380
|
-
}
|
|
14381
|
-
return status === "closed" ? this.granular.listClosedSessions({ environmentId: this.environmentId }) : this.granular.listOpenSessions({ environmentId: this.environmentId });
|
|
14546
|
+
async listSessions(optionsOrStatus = {}) {
|
|
14547
|
+
const options = typeof optionsOrStatus === "string" ? { status: optionsOrStatus } : optionsOrStatus;
|
|
14548
|
+
return this.granular.listSessions({
|
|
14549
|
+
...options,
|
|
14550
|
+
environmentId: this.environmentId
|
|
14551
|
+
});
|
|
14382
14552
|
}
|
|
14383
14553
|
async getUserEnvironmentState(options = {}) {
|
|
14384
14554
|
return this.granular.getUserEnvironmentState({
|
|
@@ -14396,6 +14566,7 @@ var Environment = class _Environment {
|
|
|
14396
14566
|
return this.granular.createSession({
|
|
14397
14567
|
environmentId: this.environmentId,
|
|
14398
14568
|
clientId: options?.clientId,
|
|
14569
|
+
sessionScope: options?.sessionScope,
|
|
14399
14570
|
initialHeap: options?.initialHeap
|
|
14400
14571
|
});
|
|
14401
14572
|
}
|
|
@@ -15958,7 +16129,7 @@ var EnvironmentSession = class extends Session {
|
|
|
15958
16129
|
* Close only the socket transport without sending `client.goodbye`.
|
|
15959
16130
|
*/
|
|
15960
16131
|
disconnectTransport() {
|
|
15961
|
-
this.client.disconnect();
|
|
16132
|
+
this.client.disconnect({ reason: "Transport detach" });
|
|
15962
16133
|
}
|
|
15963
16134
|
/**
|
|
15964
16135
|
* Backwards-compatible alias for `disconnect()`.
|
|
@@ -16353,16 +16524,71 @@ var Granular = class _Granular {
|
|
|
16353
16524
|
};
|
|
16354
16525
|
}
|
|
16355
16526
|
/**
|
|
16356
|
-
* List
|
|
16527
|
+
* List indexed sessions using ownership filters and bounded pagination.
|
|
16528
|
+
*/
|
|
16529
|
+
async listSessions(options) {
|
|
16530
|
+
const environmentId = options.environmentId?.trim();
|
|
16531
|
+
const sandboxId = options.sandboxId?.trim();
|
|
16532
|
+
const subjectId = options.subjectId?.trim();
|
|
16533
|
+
if (!environmentId && !sandboxId && !subjectId) {
|
|
16534
|
+
throw new Error(
|
|
16535
|
+
"listSessions() requires environmentId, sandboxId, or subjectId so history cannot be scanned accidentally."
|
|
16536
|
+
);
|
|
16537
|
+
}
|
|
16538
|
+
const status = options.status || "active";
|
|
16539
|
+
const allowedStatuses = /* @__PURE__ */ new Set([
|
|
16540
|
+
"active",
|
|
16541
|
+
"closed",
|
|
16542
|
+
"expired",
|
|
16543
|
+
"failed",
|
|
16544
|
+
"timeout",
|
|
16545
|
+
"all"
|
|
16546
|
+
]);
|
|
16547
|
+
if (!allowedStatuses.has(status)) {
|
|
16548
|
+
throw new Error(`Unsupported session status: ${String(status)}`);
|
|
16549
|
+
}
|
|
16550
|
+
const limit = boundedSessionListInteger(
|
|
16551
|
+
options.limit,
|
|
16552
|
+
"limit",
|
|
16553
|
+
DEFAULT_CONVERSATION_SESSION_LIST_LIMIT,
|
|
16554
|
+
1,
|
|
16555
|
+
MAX_CONVERSATION_SESSION_LIST_LIMIT
|
|
16556
|
+
);
|
|
16557
|
+
const offset = boundedSessionListInteger(
|
|
16558
|
+
options.offset,
|
|
16559
|
+
"offset",
|
|
16560
|
+
0,
|
|
16561
|
+
0,
|
|
16562
|
+
MAX_CONVERSATION_SESSION_LIST_OFFSET
|
|
16563
|
+
);
|
|
16564
|
+
const query = new URLSearchParams({
|
|
16565
|
+
limit: String(limit),
|
|
16566
|
+
offset: String(offset)
|
|
16567
|
+
});
|
|
16568
|
+
if (environmentId) query.set("environmentId", environmentId);
|
|
16569
|
+
if (sandboxId) query.set("sandboxId", sandboxId);
|
|
16570
|
+
if (subjectId) query.set("userId", subjectId);
|
|
16571
|
+
if (options.sessionScope?.trim()) {
|
|
16572
|
+
query.set("sessionScope", options.sessionScope.trim());
|
|
16573
|
+
}
|
|
16574
|
+
if (status !== "all") query.set("status", status);
|
|
16575
|
+
const res = await this.request(
|
|
16576
|
+
`/control/sessions?${query.toString()}`
|
|
16577
|
+
);
|
|
16578
|
+
const items = Array.isArray(res.items) ? res.items : [];
|
|
16579
|
+
return items.map((row) => this.normalizeConversationSession(row));
|
|
16580
|
+
}
|
|
16581
|
+
/**
|
|
16582
|
+
* List active (open) sessions for an environment.
|
|
16357
16583
|
*/
|
|
16358
16584
|
async listOpenSessions(filters) {
|
|
16359
|
-
return this.
|
|
16585
|
+
return this.listSessions({ ...filters, status: "active" });
|
|
16360
16586
|
}
|
|
16361
16587
|
/**
|
|
16362
16588
|
* List closed sessions for an environment (conversations that have disconnected).
|
|
16363
16589
|
*/
|
|
16364
16590
|
async listClosedSessions(filters) {
|
|
16365
|
-
return this.
|
|
16591
|
+
return this.listSessions({ ...filters, status: "closed" });
|
|
16366
16592
|
}
|
|
16367
16593
|
async getUserEnvironmentState(options) {
|
|
16368
16594
|
const query = new URLSearchParams({
|
|
@@ -16397,14 +16623,6 @@ var Granular = class _Granular {
|
|
|
16397
16623
|
});
|
|
16398
16624
|
return result.readAtBySessionId || {};
|
|
16399
16625
|
}
|
|
16400
|
-
async listSessionsForEnvironment(environmentId, status) {
|
|
16401
|
-
const query = new URLSearchParams({ environmentId, status });
|
|
16402
|
-
const res = await this.request(
|
|
16403
|
-
`/control/sessions?${query.toString()}`
|
|
16404
|
-
);
|
|
16405
|
-
const items = Array.isArray(res.items) ? res.items : [];
|
|
16406
|
-
return items.map((row) => this.normalizeConversationSession(row));
|
|
16407
|
-
}
|
|
16408
16626
|
normalizeConversationSession(row) {
|
|
16409
16627
|
const sessionId = String(row.sessionId ?? row.session_id ?? "");
|
|
16410
16628
|
const environmentId = String(row.environmentId ?? row.environment_id ?? "");
|
|
@@ -16463,6 +16681,7 @@ var Granular = class _Granular {
|
|
|
16463
16681
|
*/
|
|
16464
16682
|
async createSession(options) {
|
|
16465
16683
|
const clientId = options.clientId || `client_${Date.now()}`;
|
|
16684
|
+
const sessionScope = options.sessionScope?.trim() || void 0;
|
|
16466
16685
|
await this.activateEnvironment(options.environmentId);
|
|
16467
16686
|
const envData = await this.environments.get(options.environmentId);
|
|
16468
16687
|
const environment = this.bindEnvironmentHandle(envData);
|
|
@@ -16471,6 +16690,8 @@ var Granular = class _Granular {
|
|
|
16471
16690
|
body: JSON.stringify({
|
|
16472
16691
|
environmentId: options.environmentId,
|
|
16473
16692
|
clientId,
|
|
16693
|
+
sessionScope,
|
|
16694
|
+
capabilities: sessionScope ? { sessionScope } : void 0,
|
|
16474
16695
|
initialHeap: options.initialHeap
|
|
16475
16696
|
})
|
|
16476
16697
|
});
|
|
@@ -19662,6 +19883,7 @@ function buildGranularAgentSystemPrompt(input) {
|
|
|
19662
19883
|
- \`groundedObjects.save(...)\` only accepts scalar values, session files, runtime records/sandbox instances, or arrays of runtime records/sandbox instances from one class. Do not save plain action/effect result objects; fetch affected records first or answer from summaries with \`replyToUser(...)\`.
|
|
19663
19884
|
- Do not use \`showObjects({ entries: [...] })\` or \`showObjects({ saveAs, entries })\`. Save ordered pages, queues, search results, or ranked lists with \`groundedObjects.save(...)\`, then call \`showObjects({ variableNames: [...] })\` once.
|
|
19664
19885
|
- Use \`showAgentResponse({ reply, show: [record, action] })\` when one assistant message should combine text, grounded records, files, prepared actions, or action suggestions. The \`action\` can be a state handle such as \`record.lifecycle.approved\` or an action handle such as \`record.lifecycle.approved.reach()\`.
|
|
19886
|
+
- Pass grounded records directly in \`show\` when the default record presentation answers the request. When the user asks for particular columns, comparisons, or computed values, import \`table\` (and \`relativeTime\` when useful) from \`@granular/agent\` and call \`showAgentResponse({ reply, show: table(records, [{ label: "Object", value: record => record.label }, { label: "When", value: record => relativeTime(record.timestamp) }]) })\`. Column callbacks must be synchronous and return a scalar, \`Date\`, or \`relativeTime(...)\`; they run inside the job and only resolved cells are persisted.
|
|
19665
19887
|
- Do not use deprecated side-channel helpers such as \`agent_text_message(...)\`, \`agent_heap_objects(...)\`, or \`agent_message(...)\` unless the generated types expose no Harness v3 helper alternative.
|
|
19666
19888
|
- When the user asks to show, list, display, open, or "show them" for records you found, call \`showObjects(...)\`; do not answer only with a count or text summary.
|
|
19667
19889
|
- For count-only questions such as "how many", "how many X do I have", or "what is the total number of X", call the entity \`.count(...)\` or use page \`totalCount\` only when a page is already needed for other reasons. Answer with \`replyToUser(...)\` only. Do not call \`showObjects(...)\`, \`saveAs\`, or \`groundedObjects.save(...)\` unless the user also asked to see records or a later requested action needs a reusable record selection.
|
|
@@ -19932,11 +20154,12 @@ ${actionIndex}
|
|
|
19932
20154
|
- Use typed plan fields and helpers directly. For visible blocker text, use the declared blocker-formatting helper when available instead of hand-written object casts.
|
|
19933
20155
|
- A suggestion is a recommendation button: \`await stateHandle.suggest(message)\`. A prepared action is the editable form the user can review/run: \`const action = stateHandle.reach(); const prepared = await action.open()\`.
|
|
19934
20156
|
- Use suggestions when the user asks "what can I do next?", asks for options, or gives an unclear intent. Prefer 2 to 5 concrete suggestions and keep text short.
|
|
19935
|
-
-
|
|
20157
|
+
- Opening a prepared action does not execute the underlying mutation. Use \`.open()\` only when the user asks to prepare, review, show, or edit an action before running it, when a new record must be prepared, or when a reviewable form must collect missing editable inputs. Prefill only grounded values and leave unknown fields empty.
|
|
20158
|
+
- When the user explicitly commands an existing-record mutation such as approve, reject, block, route, send, or update, and one visible domain action uniquely matches, call that domain action directly. Do not substitute a state-handle \`.open()\` artifact for execution. If runtime policy requires confirmation, invoke the action once and let the runtime pause and resume that same invocation.
|
|
19936
20159
|
- When a grounded related/context record can satisfy the prepared action through declared relationships, use those relationships to fill required relationship inputs before opening or updating the action. If a required relationship remains empty, continue through declared relationship chains from the grounded object when the next hop can fill that slot. Do not only save the context record in memory while leaving derivable relationship slots blank.
|
|
19937
20160
|
- A derived intermediate relationship is not enough when another required relationship is still reachable from it. For example, if a team gives a cost center and the action also requires a budget, traverse the cost center's declared budget relationship before opening the action.
|
|
19938
20161
|
- If the user says they have a document/work item/event but no matching record is found, check for a declared class-level new-record state/action handle or importable backend create/preparation action for that named class before giving up. Use grounded required fields to open the prepared action or call the create/preparation action; if required values are still missing and no prepared action can collect them, ask only for those values. Do not claim the record already exists.
|
|
19939
|
-
- Do not ask for confirmation before opening a prepared action
|
|
20162
|
+
- Do not ask for confirmation before opening a prepared action requested for review; the artifact is itself reviewable. For a direct mutation command, do not open an artifact merely to obtain confirmation. Use \`userInteraction.askConfirmation\` only when the user explicitly asks for a separate yes/no step, material ambiguity remains after grounding, or policy requires confirmation outside the invoked action runtime.
|
|
19940
20163
|
- If the action cannot continue because of missing input, stale state, missing relationships, related-state requirements, or permissions, keep/show the prepared action at that blocker and explain the next needed person, record, or value. Do not skip workflow steps or target a later state.
|
|
19941
20164
|
- Reuse an already-open prepared action for the same target/action when available: update it, show it again, or explain what is still needed instead of creating a duplicate.
|
|
19942
20165
|
- If an open prepared action needs edits, prefer the returned record helper: \`const prepared = await action.open(); await prepared.updateInputs({ inputValues, relationships });\`. Use \`artifacts.updateInputs(id, patch)\` only when you only have an id.
|
|
@@ -20166,8 +20389,9 @@ function buildContinuationInstructionFromTemplate(resultPreview, options) {
|
|
|
20166
20389
|
}
|
|
20167
20390
|
|
|
20168
20391
|
// src/openai-usage.ts
|
|
20169
|
-
var
|
|
20170
|
-
var
|
|
20392
|
+
var OPENAI_GPT_5_4_PRICING_SOURCE_URL = "https://developers.openai.com/api/docs/models/gpt-5.4/";
|
|
20393
|
+
var OPENAI_GPT_5_6_PRICING_SOURCE_URL = "https://developers.openai.com/api/docs/pricing";
|
|
20394
|
+
var OPENAI_LONG_CONTEXT_THRESHOLD_TOKENS = 272e3;
|
|
20171
20395
|
var OPENAI_MODEL_PRICING_USD_PER_MILLION = {
|
|
20172
20396
|
"gpt-5.4": {
|
|
20173
20397
|
provider: "openai",
|
|
@@ -20176,8 +20400,26 @@ var OPENAI_MODEL_PRICING_USD_PER_MILLION = {
|
|
|
20176
20400
|
inputUsdPerMillion: 2.5,
|
|
20177
20401
|
cachedInputUsdPerMillion: 0.25,
|
|
20178
20402
|
outputUsdPerMillion: 15,
|
|
20179
|
-
sourceUrl:
|
|
20180
|
-
effectiveDate:
|
|
20403
|
+
sourceUrl: OPENAI_GPT_5_4_PRICING_SOURCE_URL,
|
|
20404
|
+
effectiveDate: "2026-05-19"
|
|
20405
|
+
},
|
|
20406
|
+
"gpt-5.6-luna": {
|
|
20407
|
+
provider: "openai",
|
|
20408
|
+
model: "gpt-5.6-luna",
|
|
20409
|
+
currency: "USD",
|
|
20410
|
+
inputUsdPerMillion: 1,
|
|
20411
|
+
cachedInputUsdPerMillion: 0.1,
|
|
20412
|
+
cacheWriteUsdPerMillion: 1.25,
|
|
20413
|
+
outputUsdPerMillion: 6,
|
|
20414
|
+
sourceUrl: OPENAI_GPT_5_6_PRICING_SOURCE_URL,
|
|
20415
|
+
effectiveDate: "2026-07-11",
|
|
20416
|
+
longContextThresholdTokens: OPENAI_LONG_CONTEXT_THRESHOLD_TOKENS,
|
|
20417
|
+
longContextPricing: {
|
|
20418
|
+
inputUsdPerMillion: 2,
|
|
20419
|
+
cachedInputUsdPerMillion: 0.2,
|
|
20420
|
+
cacheWriteUsdPerMillion: 2.5,
|
|
20421
|
+
outputUsdPerMillion: 9
|
|
20422
|
+
}
|
|
20181
20423
|
}
|
|
20182
20424
|
};
|
|
20183
20425
|
function asRecord5(value) {
|
|
@@ -20190,8 +20432,18 @@ function numberField(record, key) {
|
|
|
20190
20432
|
function microsPerMillion(usdPerMillion) {
|
|
20191
20433
|
return Math.round(usdPerMillion * 1e6);
|
|
20192
20434
|
}
|
|
20193
|
-
function getOpenAIModelPricing(model) {
|
|
20194
|
-
|
|
20435
|
+
function getOpenAIModelPricing(model, inputTokens = 0) {
|
|
20436
|
+
const pricing = OPENAI_MODEL_PRICING_USD_PER_MILLION[model];
|
|
20437
|
+
if (!pricing) return null;
|
|
20438
|
+
const threshold = pricing.longContextThresholdTokens ?? null;
|
|
20439
|
+
if (pricing.longContextPricing && typeof threshold === "number" && inputTokens > threshold) {
|
|
20440
|
+
return {
|
|
20441
|
+
...pricing,
|
|
20442
|
+
...pricing.longContextPricing,
|
|
20443
|
+
contextTier: "long"
|
|
20444
|
+
};
|
|
20445
|
+
}
|
|
20446
|
+
return { ...pricing, contextTier: "short" };
|
|
20195
20447
|
}
|
|
20196
20448
|
function normalizeOpenAIUsage(rawUsage) {
|
|
20197
20449
|
const usage = asRecord5(rawUsage);
|
|
@@ -20199,6 +20451,7 @@ function normalizeOpenAIUsage(rawUsage) {
|
|
|
20199
20451
|
return {
|
|
20200
20452
|
inputTokens: 0,
|
|
20201
20453
|
cachedInputTokens: 0,
|
|
20454
|
+
cacheWriteTokens: 0,
|
|
20202
20455
|
uncachedInputTokens: 0,
|
|
20203
20456
|
outputTokens: 0,
|
|
20204
20457
|
reasoningTokens: 0,
|
|
@@ -20214,20 +20467,28 @@ function normalizeOpenAIUsage(rawUsage) {
|
|
|
20214
20467
|
inputTokens,
|
|
20215
20468
|
numberField(inputDetails, "cached_tokens") || numberField(inputDetails, "cached_input_tokens")
|
|
20216
20469
|
);
|
|
20470
|
+
const cacheWriteTokens = Math.min(
|
|
20471
|
+
Math.max(inputTokens - cachedInputTokens, 0),
|
|
20472
|
+
numberField(inputDetails, "cache_write_tokens")
|
|
20473
|
+
);
|
|
20217
20474
|
const reasoningTokens = numberField(outputDetails, "reasoning_tokens") || numberField(outputDetails, "reasoning_output_tokens");
|
|
20218
20475
|
return {
|
|
20219
20476
|
inputTokens,
|
|
20220
20477
|
cachedInputTokens,
|
|
20221
|
-
|
|
20478
|
+
cacheWriteTokens,
|
|
20479
|
+
uncachedInputTokens: Math.max(
|
|
20480
|
+
inputTokens - cachedInputTokens - cacheWriteTokens,
|
|
20481
|
+
0
|
|
20482
|
+
),
|
|
20222
20483
|
outputTokens,
|
|
20223
20484
|
reasoningTokens,
|
|
20224
20485
|
totalTokens
|
|
20225
20486
|
};
|
|
20226
20487
|
}
|
|
20227
20488
|
function calculateOpenAITokenSpend(model, rawUsage) {
|
|
20228
|
-
const pricing = getOpenAIModelPricing(model);
|
|
20229
|
-
if (!pricing) return null;
|
|
20230
20489
|
const usage = normalizeOpenAIUsage(rawUsage);
|
|
20490
|
+
const pricing = getOpenAIModelPricing(model, usage.inputTokens);
|
|
20491
|
+
if (!pricing) return null;
|
|
20231
20492
|
const inputPricePerMillionMicros = microsPerMillion(
|
|
20232
20493
|
pricing.inputUsdPerMillion
|
|
20233
20494
|
);
|
|
@@ -20237,14 +20498,19 @@ function calculateOpenAITokenSpend(model, rawUsage) {
|
|
|
20237
20498
|
const outputPricePerMillionMicros = microsPerMillion(
|
|
20238
20499
|
pricing.outputUsdPerMillion
|
|
20239
20500
|
);
|
|
20501
|
+
const cacheWritePricePerMillionMicros = typeof pricing.cacheWriteUsdPerMillion === "number" ? microsPerMillion(pricing.cacheWriteUsdPerMillion) : null;
|
|
20502
|
+
const cacheWriteCostMicros = Math.round(
|
|
20503
|
+
usage.cacheWriteTokens * (cacheWritePricePerMillionMicros ?? inputPricePerMillionMicros) / 1e6
|
|
20504
|
+
);
|
|
20240
20505
|
const amountMicros = Math.round(
|
|
20241
|
-
(usage.uncachedInputTokens * inputPricePerMillionMicros + usage.cachedInputTokens * cachedInputPricePerMillionMicros + usage.outputTokens * outputPricePerMillionMicros) / 1e6
|
|
20506
|
+
(usage.uncachedInputTokens * inputPricePerMillionMicros + usage.cachedInputTokens * cachedInputPricePerMillionMicros + usage.cacheWriteTokens * (cacheWritePricePerMillionMicros ?? inputPricePerMillionMicros) + usage.outputTokens * outputPricePerMillionMicros) / 1e6
|
|
20242
20507
|
);
|
|
20243
20508
|
return {
|
|
20244
20509
|
provider: "openai",
|
|
20245
20510
|
model,
|
|
20246
20511
|
inputTokens: usage.inputTokens,
|
|
20247
20512
|
cachedInputTokens: usage.cachedInputTokens,
|
|
20513
|
+
cacheWriteTokens: usage.cacheWriteTokens,
|
|
20248
20514
|
uncachedInputTokens: usage.uncachedInputTokens,
|
|
20249
20515
|
outputTokens: usage.outputTokens,
|
|
20250
20516
|
reasoningTokens: usage.reasoningTokens,
|
|
@@ -20253,7 +20519,11 @@ function calculateOpenAITokenSpend(model, rawUsage) {
|
|
|
20253
20519
|
currency: "USD",
|
|
20254
20520
|
inputPricePerMillionMicros,
|
|
20255
20521
|
cachedInputPricePerMillionMicros,
|
|
20522
|
+
cacheWritePricePerMillionMicros,
|
|
20523
|
+
cacheWriteCostMicros,
|
|
20256
20524
|
outputPricePerMillionMicros,
|
|
20525
|
+
pricingContextTier: pricing.contextTier || "short",
|
|
20526
|
+
longContextThresholdTokens: pricing.longContextThresholdTokens ?? null,
|
|
20257
20527
|
pricingSource: pricing.sourceUrl,
|
|
20258
20528
|
pricingEffectiveAt: pricing.effectiveDate,
|
|
20259
20529
|
usage
|