@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/agent-evals.mjs
CHANGED
|
@@ -25,11 +25,11 @@ var __export = (target, all) => {
|
|
|
25
25
|
for (var name in all)
|
|
26
26
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
27
27
|
};
|
|
28
|
-
var __copyProps = (to,
|
|
29
|
-
if (
|
|
30
|
-
for (let key of __getOwnPropNames(
|
|
28
|
+
var __copyProps = (to, from2, except, desc) => {
|
|
29
|
+
if (from2 && typeof from2 === "object" || typeof from2 === "function") {
|
|
30
|
+
for (let key of __getOwnPropNames(from2))
|
|
31
31
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
32
|
-
__defProp(to, key, { get: () =>
|
|
32
|
+
__defProp(to, key, { get: () => from2[key], enumerable: !(desc = __getOwnPropDesc(from2, key)) || desc.enumerable });
|
|
33
33
|
}
|
|
34
34
|
return to;
|
|
35
35
|
};
|
|
@@ -4046,16 +4046,37 @@ var WSClient = class {
|
|
|
4046
4046
|
tokenRefreshTimer = null;
|
|
4047
4047
|
isExplicitlyDisconnected = false;
|
|
4048
4048
|
reconnectAttempts = 0;
|
|
4049
|
+
connectPromise = null;
|
|
4050
|
+
connectionEpoch = 0;
|
|
4051
|
+
cancelConnectAttempt = null;
|
|
4049
4052
|
options;
|
|
4050
4053
|
constructor(options) {
|
|
4051
4054
|
this.options = options;
|
|
4052
4055
|
this.url = options.url;
|
|
4053
4056
|
this.sessionId = options.sessionId;
|
|
4054
4057
|
this.token = options.token;
|
|
4058
|
+
if (options.initialDocumentSnapshot) {
|
|
4059
|
+
this.seedDocumentSnapshot(options.initialDocumentSnapshot);
|
|
4060
|
+
}
|
|
4055
4061
|
}
|
|
4056
4062
|
get currentSessionId() {
|
|
4057
4063
|
return this.sessionId;
|
|
4058
4064
|
}
|
|
4065
|
+
seedDocumentSnapshot(document) {
|
|
4066
|
+
if (!document || typeof document !== "object" || Array.isArray(document)) {
|
|
4067
|
+
return;
|
|
4068
|
+
}
|
|
4069
|
+
try {
|
|
4070
|
+
this.doc = document instanceof Uint8Array ? Automerge.load(document) : Automerge.from(document);
|
|
4071
|
+
this.syncState = Automerge.initSyncState();
|
|
4072
|
+
this.emit("sync", this.doc);
|
|
4073
|
+
} catch (error) {
|
|
4074
|
+
console.warn("[Granular] Failed to seed cached session document", error);
|
|
4075
|
+
}
|
|
4076
|
+
}
|
|
4077
|
+
saveDocumentSnapshot() {
|
|
4078
|
+
return Automerge.save(this.doc);
|
|
4079
|
+
}
|
|
4059
4080
|
clearTokenRefreshTimer() {
|
|
4060
4081
|
if (this.tokenRefreshTimer) {
|
|
4061
4082
|
clearTimeout(this.tokenRefreshTimer);
|
|
@@ -4165,8 +4186,23 @@ var WSClient = class {
|
|
|
4165
4186
|
* Connect to the WebSocket server
|
|
4166
4187
|
* @returns {Promise<void>} Resolves when connection is open
|
|
4167
4188
|
*/
|
|
4168
|
-
async connect() {
|
|
4189
|
+
async connect(options = {}) {
|
|
4190
|
+
if (this.ws?.readyState === READY_STATE_OPEN) return;
|
|
4191
|
+
if (this.connectPromise) return this.connectPromise;
|
|
4192
|
+
const connectPromise = this.connectAttempt(options.signal);
|
|
4193
|
+
this.connectPromise = connectPromise;
|
|
4194
|
+
try {
|
|
4195
|
+
await connectPromise;
|
|
4196
|
+
} finally {
|
|
4197
|
+
if (this.connectPromise === connectPromise) {
|
|
4198
|
+
this.connectPromise = null;
|
|
4199
|
+
}
|
|
4200
|
+
}
|
|
4201
|
+
}
|
|
4202
|
+
async connectAttempt(signal) {
|
|
4203
|
+
if (signal?.aborted) throw new Error("WebSocket connect aborted");
|
|
4169
4204
|
const token = await this.resolveTokenForConnect();
|
|
4205
|
+
if (signal?.aborted) throw new Error("WebSocket connect aborted");
|
|
4170
4206
|
this.isExplicitlyDisconnected = false;
|
|
4171
4207
|
this.scheduleTokenRefresh();
|
|
4172
4208
|
if (this.reconnectTimer) {
|
|
@@ -4178,7 +4214,7 @@ var WSClient = class {
|
|
|
4178
4214
|
try {
|
|
4179
4215
|
const wsModule = await Promise.resolve().then(() => (init_wrapper(), wrapper_exports));
|
|
4180
4216
|
WebSocketClass = wsModule.default || wsModule;
|
|
4181
|
-
} catch
|
|
4217
|
+
} catch {
|
|
4182
4218
|
}
|
|
4183
4219
|
}
|
|
4184
4220
|
if (!WebSocketClass) {
|
|
@@ -4186,83 +4222,97 @@ var WSClient = class {
|
|
|
4186
4222
|
'No WebSocket implementation found. If using Node.js, please install "ws" and pass the constructor to the SDK options: { WebSocketCtor: WebSocket }.'
|
|
4187
4223
|
);
|
|
4188
4224
|
}
|
|
4225
|
+
const epoch = ++this.connectionEpoch;
|
|
4226
|
+
const wsUrl = new URL(this.url);
|
|
4227
|
+
wsUrl.searchParams.set("sessionId", this.sessionId);
|
|
4228
|
+
wsUrl.searchParams.set("token", token);
|
|
4229
|
+
const socket = new WebSocketClass(wsUrl.toString());
|
|
4230
|
+
this.ws = socket;
|
|
4189
4231
|
return new Promise((resolve, reject) => {
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
if (
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
this.reconnectTimer = null;
|
|
4202
|
-
}
|
|
4203
|
-
this.reconnectAttempts = 0;
|
|
4204
|
-
this.emit("open", {});
|
|
4205
|
-
resolve();
|
|
4206
|
-
});
|
|
4207
|
-
socket.on("message", (data) => {
|
|
4208
|
-
try {
|
|
4209
|
-
const message = JSON.parse(data.toString());
|
|
4210
|
-
this.handleMessage(message);
|
|
4211
|
-
} catch (error) {
|
|
4212
|
-
console.error("[Granular] Failed to parse message:", error);
|
|
4213
|
-
}
|
|
4214
|
-
});
|
|
4215
|
-
socket.on("error", (error) => {
|
|
4216
|
-
this.emit("error", error);
|
|
4217
|
-
if (socket.readyState !== READY_STATE_OPEN) {
|
|
4218
|
-
reject(error);
|
|
4219
|
-
}
|
|
4220
|
-
});
|
|
4221
|
-
socket.on("close", (code, reason) => {
|
|
4222
|
-
this.handleDisconnect({
|
|
4223
|
-
code,
|
|
4224
|
-
reason: this.normalizeReason(reason),
|
|
4225
|
-
// ws does not provide wasClean on Node-style close callback
|
|
4226
|
-
wasClean: code === 1e3
|
|
4227
|
-
});
|
|
4228
|
-
});
|
|
4232
|
+
let settled = false;
|
|
4233
|
+
const isCurrent = () => this.connectionEpoch === epoch && this.ws === socket;
|
|
4234
|
+
const finish = (error) => {
|
|
4235
|
+
if (settled) return;
|
|
4236
|
+
settled = true;
|
|
4237
|
+
if (this.cancelConnectAttempt === handleAbort) {
|
|
4238
|
+
this.cancelConnectAttempt = null;
|
|
4239
|
+
}
|
|
4240
|
+
signal?.removeEventListener("abort", handleAbort);
|
|
4241
|
+
if (error) {
|
|
4242
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
4229
4243
|
} else {
|
|
4230
|
-
|
|
4231
|
-
if (this.reconnectTimer) {
|
|
4232
|
-
clearTimeout(this.reconnectTimer);
|
|
4233
|
-
this.reconnectTimer = null;
|
|
4234
|
-
}
|
|
4235
|
-
this.reconnectAttempts = 0;
|
|
4236
|
-
this.emit("open", {});
|
|
4237
|
-
resolve();
|
|
4238
|
-
};
|
|
4239
|
-
this.ws.onmessage = (event) => {
|
|
4240
|
-
try {
|
|
4241
|
-
const data = event.data;
|
|
4242
|
-
const message = JSON.parse(data.toString());
|
|
4243
|
-
this.handleMessage(message);
|
|
4244
|
-
} catch (error) {
|
|
4245
|
-
console.error("[Granular] Failed to parse message:", error);
|
|
4246
|
-
}
|
|
4247
|
-
};
|
|
4248
|
-
this.ws.onerror = (event) => {
|
|
4249
|
-
const error = new Error("WebSocket error");
|
|
4250
|
-
error.event = event;
|
|
4251
|
-
this.emit("error", error);
|
|
4252
|
-
if (this.ws?.readyState !== READY_STATE_OPEN) {
|
|
4253
|
-
reject(error);
|
|
4254
|
-
}
|
|
4255
|
-
};
|
|
4256
|
-
this.ws.onclose = (event) => {
|
|
4257
|
-
this.handleDisconnect({
|
|
4258
|
-
code: event.code,
|
|
4259
|
-
reason: event.reason,
|
|
4260
|
-
wasClean: event.wasClean
|
|
4261
|
-
});
|
|
4262
|
-
};
|
|
4244
|
+
resolve();
|
|
4263
4245
|
}
|
|
4264
|
-
}
|
|
4265
|
-
|
|
4246
|
+
};
|
|
4247
|
+
const closeStaleSocket = () => {
|
|
4248
|
+
try {
|
|
4249
|
+
socket.close(1e3, "Stale connection attempt");
|
|
4250
|
+
} catch {
|
|
4251
|
+
}
|
|
4252
|
+
};
|
|
4253
|
+
const handleAbort = () => {
|
|
4254
|
+
if (isCurrent()) {
|
|
4255
|
+
this.connectionEpoch += 1;
|
|
4256
|
+
this.ws = null;
|
|
4257
|
+
}
|
|
4258
|
+
closeStaleSocket();
|
|
4259
|
+
finish(new Error("WebSocket connect aborted"));
|
|
4260
|
+
};
|
|
4261
|
+
this.cancelConnectAttempt = handleAbort;
|
|
4262
|
+
const handleOpen = () => {
|
|
4263
|
+
if (!isCurrent()) {
|
|
4264
|
+
closeStaleSocket();
|
|
4265
|
+
return;
|
|
4266
|
+
}
|
|
4267
|
+
this.reconnectAttempts = 0;
|
|
4268
|
+
this.emit("open", {});
|
|
4269
|
+
finish();
|
|
4270
|
+
};
|
|
4271
|
+
const handleMessage = (data) => {
|
|
4272
|
+
if (!isCurrent()) return;
|
|
4273
|
+
try {
|
|
4274
|
+
const text = typeof data === "string" ? data : data && typeof data === "object" && "toString" in data ? String(data.toString()) : "";
|
|
4275
|
+
this.handleMessage(JSON.parse(text));
|
|
4276
|
+
} catch (error) {
|
|
4277
|
+
console.error("[Granular] Failed to parse message:", error);
|
|
4278
|
+
}
|
|
4279
|
+
};
|
|
4280
|
+
const handleError = (error) => {
|
|
4281
|
+
if (!isCurrent()) return;
|
|
4282
|
+
const typedError = error instanceof Error ? error : new Error("WebSocket error");
|
|
4283
|
+
this.emit("error", typedError);
|
|
4284
|
+
if (socket.readyState !== READY_STATE_OPEN) finish(typedError);
|
|
4285
|
+
};
|
|
4286
|
+
const handleClose = (close) => {
|
|
4287
|
+
if (!isCurrent()) return;
|
|
4288
|
+
if (!settled) {
|
|
4289
|
+
finish(
|
|
4290
|
+
new Error(
|
|
4291
|
+
`WebSocket closed before ready${close.code ? ` (code=${close.code})` : ""}`
|
|
4292
|
+
)
|
|
4293
|
+
);
|
|
4294
|
+
}
|
|
4295
|
+
this.handleDisconnect({
|
|
4296
|
+
code: close.code,
|
|
4297
|
+
reason: this.normalizeReason(close.reason),
|
|
4298
|
+
wasClean: close.wasClean
|
|
4299
|
+
});
|
|
4300
|
+
};
|
|
4301
|
+
signal?.addEventListener("abort", handleAbort, { once: true });
|
|
4302
|
+
const nodeSocket = socket;
|
|
4303
|
+
if (typeof nodeSocket.on === "function") {
|
|
4304
|
+
nodeSocket.on("open", handleOpen);
|
|
4305
|
+
nodeSocket.on("message", handleMessage);
|
|
4306
|
+
nodeSocket.on("error", handleError);
|
|
4307
|
+
nodeSocket.on(
|
|
4308
|
+
"close",
|
|
4309
|
+
(code, reason) => handleClose({ code, reason, wasClean: code === 1e3 })
|
|
4310
|
+
);
|
|
4311
|
+
} else {
|
|
4312
|
+
socket.onopen = handleOpen;
|
|
4313
|
+
socket.onmessage = (event) => handleMessage(event.data);
|
|
4314
|
+
socket.onerror = handleError;
|
|
4315
|
+
socket.onclose = (event) => handleClose(event);
|
|
4266
4316
|
}
|
|
4267
4317
|
});
|
|
4268
4318
|
}
|
|
@@ -4280,9 +4330,58 @@ var WSClient = class {
|
|
|
4280
4330
|
return void 0;
|
|
4281
4331
|
}
|
|
4282
4332
|
rejectPending(error) {
|
|
4283
|
-
this.messageQueue.forEach((pending) =>
|
|
4333
|
+
this.messageQueue.forEach((pending) => {
|
|
4334
|
+
clearTimeout(pending.timeout);
|
|
4335
|
+
pending.reject(error);
|
|
4336
|
+
});
|
|
4284
4337
|
this.messageQueue = [];
|
|
4285
4338
|
}
|
|
4339
|
+
emitReconnectErrorMessage(error) {
|
|
4340
|
+
const reconnectInfo = {
|
|
4341
|
+
error,
|
|
4342
|
+
sessionId: this.sessionId,
|
|
4343
|
+
timestamp: Date.now()
|
|
4344
|
+
};
|
|
4345
|
+
this.emit("reconnect_error", reconnectInfo);
|
|
4346
|
+
if (this.options.onReconnectError) {
|
|
4347
|
+
try {
|
|
4348
|
+
this.options.onReconnectError(reconnectInfo);
|
|
4349
|
+
} catch (callbackError) {
|
|
4350
|
+
console.error(
|
|
4351
|
+
"[Granular] onReconnectError callback failed:",
|
|
4352
|
+
callbackError
|
|
4353
|
+
);
|
|
4354
|
+
}
|
|
4355
|
+
}
|
|
4356
|
+
}
|
|
4357
|
+
scheduleReconnectAttempt() {
|
|
4358
|
+
if (this.isExplicitlyDisconnected || this.reconnectTimer) return null;
|
|
4359
|
+
const baseReconnectDelayMs = typeof this.options.reconnectDelayMs === "number" && Number.isFinite(this.options.reconnectDelayMs) && this.options.reconnectDelayMs > 0 ? this.options.reconnectDelayMs : DEFAULT_RECONNECT_DELAY_MS;
|
|
4360
|
+
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;
|
|
4361
|
+
if (this.reconnectAttempts >= maxReconnectAttempts) {
|
|
4362
|
+
this.emitReconnectErrorMessage(
|
|
4363
|
+
`WebSocket reconnect attempts exhausted after ${maxReconnectAttempts} attempt(s).`
|
|
4364
|
+
);
|
|
4365
|
+
return null;
|
|
4366
|
+
}
|
|
4367
|
+
this.reconnectAttempts += 1;
|
|
4368
|
+
const reconnectDelayMs = Math.min(
|
|
4369
|
+
3e4,
|
|
4370
|
+
baseReconnectDelayMs * 2 ** Math.max(0, this.reconnectAttempts - 1)
|
|
4371
|
+
);
|
|
4372
|
+
this.reconnectTimer = setTimeout(() => {
|
|
4373
|
+
this.reconnectTimer = null;
|
|
4374
|
+
console.log("[Granular] Attempting reconnect...");
|
|
4375
|
+
this.connect().catch((error) => {
|
|
4376
|
+
console.error("[Granular] Reconnect failed:", error);
|
|
4377
|
+
this.emitReconnectErrorMessage(
|
|
4378
|
+
error instanceof Error ? error.message : String(error)
|
|
4379
|
+
);
|
|
4380
|
+
this.scheduleReconnectAttempt();
|
|
4381
|
+
});
|
|
4382
|
+
}, reconnectDelayMs);
|
|
4383
|
+
return reconnectDelayMs;
|
|
4384
|
+
}
|
|
4286
4385
|
buildDisconnectError(info) {
|
|
4287
4386
|
const details = [
|
|
4288
4387
|
info.code !== void 0 ? `code=${info.code}` : void 0,
|
|
@@ -4292,8 +4391,6 @@ var WSClient = class {
|
|
|
4292
4391
|
return new Error(`WebSocket disconnected${suffix}`);
|
|
4293
4392
|
}
|
|
4294
4393
|
handleDisconnect(close = {}) {
|
|
4295
|
-
const baseReconnectDelayMs = typeof this.options.reconnectDelayMs === "number" && Number.isFinite(this.options.reconnectDelayMs) && this.options.reconnectDelayMs > 0 ? this.options.reconnectDelayMs : DEFAULT_RECONNECT_DELAY_MS;
|
|
4296
|
-
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;
|
|
4297
4394
|
const unexpected = !this.isExplicitlyDisconnected;
|
|
4298
4395
|
const info = {
|
|
4299
4396
|
code: close.code,
|
|
@@ -4313,32 +4410,9 @@ var WSClient = class {
|
|
|
4313
4410
|
const disconnectError = this.buildDisconnectError(info);
|
|
4314
4411
|
this.rejectPending(disconnectError);
|
|
4315
4412
|
this.emit("disconnect", info);
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
sessionId: this.sessionId,
|
|
4320
|
-
timestamp: Date.now()
|
|
4321
|
-
};
|
|
4322
|
-
this.emit("reconnect_error", reconnectInfo);
|
|
4323
|
-
if (this.options.onReconnectError) {
|
|
4324
|
-
try {
|
|
4325
|
-
this.options.onReconnectError(reconnectInfo);
|
|
4326
|
-
} catch (callbackError) {
|
|
4327
|
-
console.error(
|
|
4328
|
-
"[Granular] onReconnectError callback failed:",
|
|
4329
|
-
callbackError
|
|
4330
|
-
);
|
|
4331
|
-
}
|
|
4332
|
-
}
|
|
4333
|
-
return;
|
|
4334
|
-
}
|
|
4335
|
-
this.reconnectAttempts += 1;
|
|
4336
|
-
const reconnectDelayMs = Math.min(
|
|
4337
|
-
3e4,
|
|
4338
|
-
baseReconnectDelayMs * 2 ** Math.max(0, this.reconnectAttempts - 1)
|
|
4339
|
-
);
|
|
4340
|
-
info.reconnectScheduled = true;
|
|
4341
|
-
info.reconnectDelayMs = reconnectDelayMs;
|
|
4413
|
+
const reconnectDelayMs = this.scheduleReconnectAttempt();
|
|
4414
|
+
info.reconnectScheduled = reconnectDelayMs !== null;
|
|
4415
|
+
if (reconnectDelayMs !== null) info.reconnectDelayMs = reconnectDelayMs;
|
|
4342
4416
|
if (this.options.onUnexpectedClose) {
|
|
4343
4417
|
try {
|
|
4344
4418
|
this.options.onUnexpectedClose(info);
|
|
@@ -4349,28 +4423,6 @@ var WSClient = class {
|
|
|
4349
4423
|
);
|
|
4350
4424
|
}
|
|
4351
4425
|
}
|
|
4352
|
-
this.reconnectTimer = setTimeout(() => {
|
|
4353
|
-
console.log("[Granular] Attempting reconnect...");
|
|
4354
|
-
this.connect().catch((error) => {
|
|
4355
|
-
console.error("[Granular] Reconnect failed:", error);
|
|
4356
|
-
const reconnectInfo = {
|
|
4357
|
-
error: error instanceof Error ? error.message : String(error),
|
|
4358
|
-
sessionId: this.sessionId,
|
|
4359
|
-
timestamp: Date.now()
|
|
4360
|
-
};
|
|
4361
|
-
this.emit("reconnect_error", reconnectInfo);
|
|
4362
|
-
if (this.options.onReconnectError) {
|
|
4363
|
-
try {
|
|
4364
|
-
this.options.onReconnectError(reconnectInfo);
|
|
4365
|
-
} catch (callbackError) {
|
|
4366
|
-
console.error(
|
|
4367
|
-
"[Granular] onReconnectError callback failed:",
|
|
4368
|
-
callbackError
|
|
4369
|
-
);
|
|
4370
|
-
}
|
|
4371
|
-
}
|
|
4372
|
-
});
|
|
4373
|
-
}, reconnectDelayMs);
|
|
4374
4426
|
}
|
|
4375
4427
|
}
|
|
4376
4428
|
handleMessage(message) {
|
|
@@ -4481,6 +4533,7 @@ var WSClient = class {
|
|
|
4481
4533
|
const response = message;
|
|
4482
4534
|
const pending = this.messageQueue.find((q) => q.id === response.id);
|
|
4483
4535
|
if (pending) {
|
|
4536
|
+
clearTimeout(pending.timeout);
|
|
4484
4537
|
if (response.type === "rpc_error") {
|
|
4485
4538
|
pending.reject(
|
|
4486
4539
|
new Error(
|
|
@@ -4526,16 +4579,22 @@ var WSClient = class {
|
|
|
4526
4579
|
id
|
|
4527
4580
|
};
|
|
4528
4581
|
return new Promise((resolve, reject) => {
|
|
4529
|
-
this.messageQueue.push({ resolve, reject, id });
|
|
4530
|
-
this.ws.send(JSON.stringify(request));
|
|
4531
4582
|
const timeoutMs = rpcTimeoutMsForMethod(method);
|
|
4532
|
-
setTimeout(() => {
|
|
4583
|
+
const timeout = setTimeout(() => {
|
|
4533
4584
|
const pending = this.messageQueue.find((q) => q.id === id);
|
|
4534
4585
|
if (pending) {
|
|
4535
4586
|
this.messageQueue = this.messageQueue.filter((q) => q.id !== id);
|
|
4536
4587
|
reject(new Error(`RPC timeout: ${method}`));
|
|
4537
4588
|
}
|
|
4538
4589
|
}, timeoutMs);
|
|
4590
|
+
this.messageQueue.push({ resolve, reject, id, timeout });
|
|
4591
|
+
try {
|
|
4592
|
+
this.ws.send(JSON.stringify(request));
|
|
4593
|
+
} catch (error) {
|
|
4594
|
+
clearTimeout(timeout);
|
|
4595
|
+
this.messageQueue = this.messageQueue.filter((q) => q.id !== id);
|
|
4596
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
4597
|
+
}
|
|
4539
4598
|
});
|
|
4540
4599
|
}
|
|
4541
4600
|
async handleIncomingRpc(request) {
|
|
@@ -4621,15 +4680,18 @@ var WSClient = class {
|
|
|
4621
4680
|
/**
|
|
4622
4681
|
* Disconnect the WebSocket and clear state
|
|
4623
4682
|
*/
|
|
4624
|
-
disconnect() {
|
|
4683
|
+
disconnect(options = {}) {
|
|
4625
4684
|
this.isExplicitlyDisconnected = true;
|
|
4685
|
+
this.cancelConnectAttempt?.();
|
|
4686
|
+
this.cancelConnectAttempt = null;
|
|
4687
|
+
this.connectionEpoch += 1;
|
|
4626
4688
|
if (this.reconnectTimer) {
|
|
4627
4689
|
clearTimeout(this.reconnectTimer);
|
|
4628
4690
|
this.reconnectTimer = null;
|
|
4629
4691
|
}
|
|
4630
4692
|
this.clearTokenRefreshTimer();
|
|
4631
4693
|
if (this.ws) {
|
|
4632
|
-
this.ws.close(1e3, "Client disconnect");
|
|
4694
|
+
this.ws.close(1e3, options.reason || "Client disconnect");
|
|
4633
4695
|
this.ws = null;
|
|
4634
4696
|
}
|
|
4635
4697
|
this.rejectPending(new Error("Client explicitly disconnected"));
|
|
@@ -4725,8 +4787,12 @@ function normalizePrompt(rawValue) {
|
|
|
4725
4787
|
const source = promptRecord || raw;
|
|
4726
4788
|
const id = typeof source.id === "string" ? source.id : typeof raw.id === "string" ? raw.id : typeof raw.promptId === "string" ? raw.promptId : "";
|
|
4727
4789
|
if (!id) return null;
|
|
4790
|
+
const jobId = typeof source.jobId === "string" && source.jobId.trim() ? source.jobId.trim() : typeof raw.jobId === "string" && raw.jobId.trim() ? raw.jobId.trim() : void 0;
|
|
4791
|
+
const turnId = typeof source.turnId === "string" && source.turnId.trim() ? source.turnId.trim() : typeof raw.turnId === "string" && raw.turnId.trim() ? raw.turnId.trim() : void 0;
|
|
4728
4792
|
return {
|
|
4729
4793
|
id,
|
|
4794
|
+
...jobId ? { jobId } : {},
|
|
4795
|
+
...turnId ? { turnId } : {},
|
|
4730
4796
|
type: normalizePromptType(source === raw ? raw : { ...raw, ...source }),
|
|
4731
4797
|
title: typeof source.title === "string" ? source.title : "Input required",
|
|
4732
4798
|
message: typeof source.message === "string" ? source.message : "",
|
|
@@ -4813,6 +4879,9 @@ var Session = class {
|
|
|
4813
4879
|
this.initialQuota = options.initialQuota || null;
|
|
4814
4880
|
this.setupEventHandlers();
|
|
4815
4881
|
this.setupToolInvokeHandler();
|
|
4882
|
+
this.currentDomainRevision = this.extractDomainRevisionFromDoc(
|
|
4883
|
+
this.client.doc
|
|
4884
|
+
);
|
|
4816
4885
|
}
|
|
4817
4886
|
extractDomainRevisionFromDoc(doc) {
|
|
4818
4887
|
const domain = doc?.domain;
|
|
@@ -5718,6 +5787,7 @@ function normalizeJobAgentMessageEnvelope(data) {
|
|
|
5718
5787
|
}
|
|
5719
5788
|
return {
|
|
5720
5789
|
jobId: d.jobId,
|
|
5790
|
+
...typeof d.turnId === "string" && d.turnId.trim() ? { turnId: d.turnId.trim() } : {},
|
|
5721
5791
|
message: {
|
|
5722
5792
|
messageId: d.messageId,
|
|
5723
5793
|
kind: d.kind === "artifacts" ? "artifacts" : "text",
|
|
@@ -6376,9 +6446,14 @@ function normalizeShowRefs(value) {
|
|
|
6376
6446
|
variableNames: normalizeRefs(record.variableNames),
|
|
6377
6447
|
fileIds: normalizeRefs(record.fileIds),
|
|
6378
6448
|
sessionArtifactIds: normalizeRefs(record.sessionArtifactIds),
|
|
6379
|
-
actionSuggestions: normalizeActionSuggestions(record.actionSuggestions)
|
|
6449
|
+
actionSuggestions: normalizeActionSuggestions(record.actionSuggestions),
|
|
6450
|
+
tables: Array.isArray(record.tables) ? record.tables.filter(
|
|
6451
|
+
(table) => Boolean(
|
|
6452
|
+
table && typeof table === "object" && !Array.isArray(table) && Array.isArray(table.columns) && Array.isArray(table.rows)
|
|
6453
|
+
)
|
|
6454
|
+
) : void 0
|
|
6380
6455
|
};
|
|
6381
|
-
return show.entryPaths || show.listNames || show.variableNames || show.fileIds || show.sessionArtifactIds || show.actionSuggestions ? show : void 0;
|
|
6456
|
+
return show.entryPaths || show.listNames || show.variableNames || show.fileIds || show.sessionArtifactIds || show.actionSuggestions || show.tables ? show : void 0;
|
|
6382
6457
|
}
|
|
6383
6458
|
function normalizeActionSuggestions(value) {
|
|
6384
6459
|
if (!Array.isArray(value)) return void 0;
|
|
@@ -6400,6 +6475,76 @@ function normalizeActionSuggestions(value) {
|
|
|
6400
6475
|
}
|
|
6401
6476
|
return suggestions.length ? suggestions : void 0;
|
|
6402
6477
|
}
|
|
6478
|
+
var TRANSCRIPT_MESSAGE_PART_LIMIT = 128;
|
|
6479
|
+
var TRANSCRIPT_MESSAGE_PART_TEXT_LIMIT = 2e5;
|
|
6480
|
+
var TRANSCRIPT_MESSAGE_PART_ACTION_LABEL_LIMIT = 1e3;
|
|
6481
|
+
var TRANSCRIPT_MESSAGE_ACTION_LIMIT = 64;
|
|
6482
|
+
function normalizeConversationMessageActions(value) {
|
|
6483
|
+
if (!Array.isArray(value) || value.length === 0) return void 0;
|
|
6484
|
+
const actions = [];
|
|
6485
|
+
for (const item of value.slice(0, TRANSCRIPT_MESSAGE_ACTION_LIMIT)) {
|
|
6486
|
+
const record = asRecord3(item);
|
|
6487
|
+
const kind = record?.kind;
|
|
6488
|
+
const label = trimString(record?.label ?? record?.title);
|
|
6489
|
+
const status = record?.status;
|
|
6490
|
+
if (kind !== "frontend" && kind !== "backend" && kind !== "system" || !label || label.length > TRANSCRIPT_MESSAGE_PART_ACTION_LABEL_LIMIT) {
|
|
6491
|
+
continue;
|
|
6492
|
+
}
|
|
6493
|
+
actions.push({
|
|
6494
|
+
kind,
|
|
6495
|
+
label,
|
|
6496
|
+
...status === "done" || status === "queued" || status === "failed" ? { status } : {}
|
|
6497
|
+
});
|
|
6498
|
+
}
|
|
6499
|
+
return actions.length ? actions : void 0;
|
|
6500
|
+
}
|
|
6501
|
+
function normalizeConversationMessageParts(value, canonicalContent, canonicalActions) {
|
|
6502
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > TRANSCRIPT_MESSAGE_PART_LIMIT) {
|
|
6503
|
+
return void 0;
|
|
6504
|
+
}
|
|
6505
|
+
const parts = [];
|
|
6506
|
+
const canonicalActionsById = new Map(
|
|
6507
|
+
(canonicalActions || []).map((action) => [
|
|
6508
|
+
`${action.kind}:${action.label}`,
|
|
6509
|
+
action
|
|
6510
|
+
])
|
|
6511
|
+
);
|
|
6512
|
+
const seenActionIds = /* @__PURE__ */ new Set();
|
|
6513
|
+
let textLength = 0;
|
|
6514
|
+
for (const item of value) {
|
|
6515
|
+
const record = asRecord3(item);
|
|
6516
|
+
if (!record) return void 0;
|
|
6517
|
+
if (record.type === "text") {
|
|
6518
|
+
if (typeof record.text !== "string" || record.text.length === 0) {
|
|
6519
|
+
return void 0;
|
|
6520
|
+
}
|
|
6521
|
+
textLength += record.text.length;
|
|
6522
|
+
if (textLength > TRANSCRIPT_MESSAGE_PART_TEXT_LIMIT) return void 0;
|
|
6523
|
+
parts.push({ type: "text", text: record.text });
|
|
6524
|
+
continue;
|
|
6525
|
+
}
|
|
6526
|
+
if (record.type !== "action") return void 0;
|
|
6527
|
+
const action = asRecord3(record.action);
|
|
6528
|
+
const kind = action?.kind;
|
|
6529
|
+
const label = trimString(action?.label);
|
|
6530
|
+
if (kind !== "frontend" && kind !== "backend" && kind !== "system" || !label || label.length > TRANSCRIPT_MESSAGE_PART_ACTION_LABEL_LIMIT) {
|
|
6531
|
+
return void 0;
|
|
6532
|
+
}
|
|
6533
|
+
const actionId = `${kind}:${label}`;
|
|
6534
|
+
const canonicalAction = canonicalActionsById.get(actionId);
|
|
6535
|
+
if (!canonicalAction) return void 0;
|
|
6536
|
+
if (seenActionIds.has(actionId)) continue;
|
|
6537
|
+
seenActionIds.add(actionId);
|
|
6538
|
+
parts.push({
|
|
6539
|
+
type: "action",
|
|
6540
|
+
action: canonicalAction
|
|
6541
|
+
});
|
|
6542
|
+
}
|
|
6543
|
+
const orderedText = parts.filter(
|
|
6544
|
+
(part) => part.type === "text"
|
|
6545
|
+
).map((part) => part.text).join("");
|
|
6546
|
+
return orderedText === canonicalContent ? parts : void 0;
|
|
6547
|
+
}
|
|
6403
6548
|
function stringifyTranscriptValue(value, fallback = "") {
|
|
6404
6549
|
if (typeof value === "string") {
|
|
6405
6550
|
return value.trim() || fallback;
|
|
@@ -6558,10 +6703,12 @@ function normalizeConversationMessage(raw, artifactsById) {
|
|
|
6558
6703
|
const content = trimString(
|
|
6559
6704
|
record.content ?? record.reply ?? record.message ?? record.text
|
|
6560
6705
|
);
|
|
6706
|
+
const actions = role === "assistant" ? normalizeConversationMessageActions(record.actions) : void 0;
|
|
6707
|
+
const parts = role === "assistant" ? normalizeConversationMessageParts(record.parts, content, actions) : void 0;
|
|
6561
6708
|
const show = normalizeShowRefs(record.show);
|
|
6562
6709
|
const id = asString(record.id) || crypto.randomUUID();
|
|
6563
6710
|
const timestamp = asNumber(record.timestamp) || asNumber(record.ts) || 0;
|
|
6564
|
-
if (!content && !show) return null;
|
|
6711
|
+
if (!content && !show && !actions?.length) return null;
|
|
6565
6712
|
const artifactHistory = buildArtifactHistory(show, artifactsById);
|
|
6566
6713
|
const historyContent = role === "assistant" ? content && artifactHistory ? `[Assistant reply]
|
|
6567
6714
|
${content}
|
|
@@ -6576,6 +6723,8 @@ ${content}` : artifactHistory : void 0;
|
|
|
6576
6723
|
jobId: asString(record.jobId),
|
|
6577
6724
|
promptId: asString(record.promptId),
|
|
6578
6725
|
show,
|
|
6726
|
+
actions,
|
|
6727
|
+
parts,
|
|
6579
6728
|
historyContent,
|
|
6580
6729
|
source: "conversation"
|
|
6581
6730
|
};
|
|
@@ -12153,7 +12302,12 @@ async function recordOpenAIUsageSpend(options) {
|
|
|
12153
12302
|
const metadata = {
|
|
12154
12303
|
...options.metadata || {},
|
|
12155
12304
|
...options.usage.rawUsage !== void 0 ? { openaiUsage: options.usage.rawUsage } : {},
|
|
12156
|
-
usageContext: context
|
|
12305
|
+
usageContext: context,
|
|
12306
|
+
pricingContextTier: options.usage.pricingContextTier,
|
|
12307
|
+
cacheWritePricePerMillionMicros: options.usage.cacheWritePricePerMillionMicros,
|
|
12308
|
+
cacheWriteTokens: options.usage.cacheWriteTokens,
|
|
12309
|
+
cacheWriteCostMicros: options.usage.cacheWriteCostMicros,
|
|
12310
|
+
longContextThresholdTokens: options.usage.longContextThresholdTokens
|
|
12157
12311
|
};
|
|
12158
12312
|
const response = await fetch(
|
|
12159
12313
|
`${toGranularHttpBase(options.apiUrl)}/control/spend/events`,
|
|
@@ -13081,11 +13235,20 @@ function buildStateMachineModelMutations(modelPath, machines) {
|
|
|
13081
13235
|
return mutations;
|
|
13082
13236
|
}
|
|
13083
13237
|
function buildMachineTypes(classSummary, machine) {
|
|
13238
|
+
const stateGlossary = machine.states.map((state) => {
|
|
13239
|
+
const label = state.label && state.label !== state.name ? state.label : null;
|
|
13240
|
+
const meaning = [label, state.description].filter(Boolean).join(" \u2014 ");
|
|
13241
|
+
const finalMarker = state.isFinal ? " Final state." : "";
|
|
13242
|
+
return `${state.name}${meaning ? `: ${meaning}` : "."}${finalMarker}`;
|
|
13243
|
+
});
|
|
13084
13244
|
return [
|
|
13085
13245
|
{
|
|
13086
13246
|
kind: "union",
|
|
13087
13247
|
name: stateTypeName(classSummary.name, machine.name),
|
|
13088
|
-
docs: [
|
|
13248
|
+
docs: [
|
|
13249
|
+
`Allowed states for ${classSummary.name}.${machine.name}.`,
|
|
13250
|
+
...stateGlossary
|
|
13251
|
+
],
|
|
13089
13252
|
members: machine.states.map((state) => state.name)
|
|
13090
13253
|
},
|
|
13091
13254
|
{
|
|
@@ -13433,7 +13596,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
|
|
|
13433
13596
|
},
|
|
13434
13597
|
add_transition: async (value, {
|
|
13435
13598
|
name,
|
|
13436
|
-
from,
|
|
13599
|
+
from: from2,
|
|
13437
13600
|
to,
|
|
13438
13601
|
label,
|
|
13439
13602
|
description,
|
|
@@ -13448,7 +13611,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
|
|
|
13448
13611
|
value.target.add_state_machine_transition(
|
|
13449
13612
|
value.name,
|
|
13450
13613
|
name,
|
|
13451
|
-
|
|
13614
|
+
from2,
|
|
13452
13615
|
to,
|
|
13453
13616
|
{
|
|
13454
13617
|
label,
|
|
@@ -13800,6 +13963,18 @@ function buildEffectMetamodelMutations(toolPath, spec) {
|
|
|
13800
13963
|
}
|
|
13801
13964
|
|
|
13802
13965
|
// src/client.ts
|
|
13966
|
+
var DEFAULT_CONVERSATION_SESSION_LIST_LIMIT = 100;
|
|
13967
|
+
var MAX_CONVERSATION_SESSION_LIST_LIMIT = 500;
|
|
13968
|
+
var MAX_CONVERSATION_SESSION_LIST_OFFSET = 1e5;
|
|
13969
|
+
function boundedSessionListInteger(value, name, fallback, minimum, maximum) {
|
|
13970
|
+
if (value === void 0) return fallback;
|
|
13971
|
+
if (!Number.isInteger(value) || value < minimum || value > maximum) {
|
|
13972
|
+
throw new RangeError(
|
|
13973
|
+
`Session list ${name} must be an integer between ${minimum} and ${maximum}.`
|
|
13974
|
+
);
|
|
13975
|
+
}
|
|
13976
|
+
return value;
|
|
13977
|
+
}
|
|
13803
13978
|
var STANDARD_MODULES_OPERATIONS = [
|
|
13804
13979
|
{
|
|
13805
13980
|
create: "entity",
|
|
@@ -14138,7 +14313,7 @@ var Environment = class _Environment {
|
|
|
14138
14313
|
}
|
|
14139
14314
|
get sessions() {
|
|
14140
14315
|
return {
|
|
14141
|
-
list: async (options) => this.listSessions(options
|
|
14316
|
+
list: async (options = {}) => this.listSessions(options),
|
|
14142
14317
|
create: async (options) => this.createSession(options),
|
|
14143
14318
|
connect: async (sessionId, options) => this.connectSession(sessionId, options),
|
|
14144
14319
|
reopen: async (sessionId, options) => this.reopenSession(sessionId, options),
|
|
@@ -14273,17 +14448,12 @@ var Environment = class _Environment {
|
|
|
14273
14448
|
*/
|
|
14274
14449
|
async disconnect() {
|
|
14275
14450
|
}
|
|
14276
|
-
async listSessions(
|
|
14277
|
-
|
|
14278
|
-
|
|
14279
|
-
|
|
14280
|
-
|
|
14281
|
-
|
|
14282
|
-
return [...active, ...closed].sort(
|
|
14283
|
-
(left, right) => Date.parse(right.lastSeenAt) - Date.parse(left.lastSeenAt)
|
|
14284
|
-
);
|
|
14285
|
-
}
|
|
14286
|
-
return status === "closed" ? this.granular.listClosedSessions({ environmentId: this.environmentId }) : this.granular.listOpenSessions({ environmentId: this.environmentId });
|
|
14451
|
+
async listSessions(optionsOrStatus = {}) {
|
|
14452
|
+
const options = typeof optionsOrStatus === "string" ? { status: optionsOrStatus } : optionsOrStatus;
|
|
14453
|
+
return this.granular.listSessions({
|
|
14454
|
+
...options,
|
|
14455
|
+
environmentId: this.environmentId
|
|
14456
|
+
});
|
|
14287
14457
|
}
|
|
14288
14458
|
async getUserEnvironmentState(options = {}) {
|
|
14289
14459
|
return this.granular.getUserEnvironmentState({
|
|
@@ -14301,6 +14471,7 @@ var Environment = class _Environment {
|
|
|
14301
14471
|
return this.granular.createSession({
|
|
14302
14472
|
environmentId: this.environmentId,
|
|
14303
14473
|
clientId: options?.clientId,
|
|
14474
|
+
sessionScope: options?.sessionScope,
|
|
14304
14475
|
initialHeap: options?.initialHeap
|
|
14305
14476
|
});
|
|
14306
14477
|
}
|
|
@@ -15863,7 +16034,7 @@ var EnvironmentSession = class extends Session {
|
|
|
15863
16034
|
* Close only the socket transport without sending `client.goodbye`.
|
|
15864
16035
|
*/
|
|
15865
16036
|
disconnectTransport() {
|
|
15866
|
-
this.client.disconnect();
|
|
16037
|
+
this.client.disconnect({ reason: "Transport detach" });
|
|
15867
16038
|
}
|
|
15868
16039
|
/**
|
|
15869
16040
|
* Backwards-compatible alias for `disconnect()`.
|
|
@@ -16258,16 +16429,71 @@ var Granular = class _Granular {
|
|
|
16258
16429
|
};
|
|
16259
16430
|
}
|
|
16260
16431
|
/**
|
|
16261
|
-
* List
|
|
16432
|
+
* List indexed sessions using ownership filters and bounded pagination.
|
|
16433
|
+
*/
|
|
16434
|
+
async listSessions(options) {
|
|
16435
|
+
const environmentId = options.environmentId?.trim();
|
|
16436
|
+
const sandboxId = options.sandboxId?.trim();
|
|
16437
|
+
const subjectId = options.subjectId?.trim();
|
|
16438
|
+
if (!environmentId && !sandboxId && !subjectId) {
|
|
16439
|
+
throw new Error(
|
|
16440
|
+
"listSessions() requires environmentId, sandboxId, or subjectId so history cannot be scanned accidentally."
|
|
16441
|
+
);
|
|
16442
|
+
}
|
|
16443
|
+
const status = options.status || "active";
|
|
16444
|
+
const allowedStatuses = /* @__PURE__ */ new Set([
|
|
16445
|
+
"active",
|
|
16446
|
+
"closed",
|
|
16447
|
+
"expired",
|
|
16448
|
+
"failed",
|
|
16449
|
+
"timeout",
|
|
16450
|
+
"all"
|
|
16451
|
+
]);
|
|
16452
|
+
if (!allowedStatuses.has(status)) {
|
|
16453
|
+
throw new Error(`Unsupported session status: ${String(status)}`);
|
|
16454
|
+
}
|
|
16455
|
+
const limit = boundedSessionListInteger(
|
|
16456
|
+
options.limit,
|
|
16457
|
+
"limit",
|
|
16458
|
+
DEFAULT_CONVERSATION_SESSION_LIST_LIMIT,
|
|
16459
|
+
1,
|
|
16460
|
+
MAX_CONVERSATION_SESSION_LIST_LIMIT
|
|
16461
|
+
);
|
|
16462
|
+
const offset = boundedSessionListInteger(
|
|
16463
|
+
options.offset,
|
|
16464
|
+
"offset",
|
|
16465
|
+
0,
|
|
16466
|
+
0,
|
|
16467
|
+
MAX_CONVERSATION_SESSION_LIST_OFFSET
|
|
16468
|
+
);
|
|
16469
|
+
const query = new URLSearchParams({
|
|
16470
|
+
limit: String(limit),
|
|
16471
|
+
offset: String(offset)
|
|
16472
|
+
});
|
|
16473
|
+
if (environmentId) query.set("environmentId", environmentId);
|
|
16474
|
+
if (sandboxId) query.set("sandboxId", sandboxId);
|
|
16475
|
+
if (subjectId) query.set("userId", subjectId);
|
|
16476
|
+
if (options.sessionScope?.trim()) {
|
|
16477
|
+
query.set("sessionScope", options.sessionScope.trim());
|
|
16478
|
+
}
|
|
16479
|
+
if (status !== "all") query.set("status", status);
|
|
16480
|
+
const res = await this.request(
|
|
16481
|
+
`/control/sessions?${query.toString()}`
|
|
16482
|
+
);
|
|
16483
|
+
const items = Array.isArray(res.items) ? res.items : [];
|
|
16484
|
+
return items.map((row) => this.normalizeConversationSession(row));
|
|
16485
|
+
}
|
|
16486
|
+
/**
|
|
16487
|
+
* List active (open) sessions for an environment.
|
|
16262
16488
|
*/
|
|
16263
16489
|
async listOpenSessions(filters) {
|
|
16264
|
-
return this.
|
|
16490
|
+
return this.listSessions({ ...filters, status: "active" });
|
|
16265
16491
|
}
|
|
16266
16492
|
/**
|
|
16267
16493
|
* List closed sessions for an environment (conversations that have disconnected).
|
|
16268
16494
|
*/
|
|
16269
16495
|
async listClosedSessions(filters) {
|
|
16270
|
-
return this.
|
|
16496
|
+
return this.listSessions({ ...filters, status: "closed" });
|
|
16271
16497
|
}
|
|
16272
16498
|
async getUserEnvironmentState(options) {
|
|
16273
16499
|
const query = new URLSearchParams({
|
|
@@ -16302,14 +16528,6 @@ var Granular = class _Granular {
|
|
|
16302
16528
|
});
|
|
16303
16529
|
return result.readAtBySessionId || {};
|
|
16304
16530
|
}
|
|
16305
|
-
async listSessionsForEnvironment(environmentId, status) {
|
|
16306
|
-
const query = new URLSearchParams({ environmentId, status });
|
|
16307
|
-
const res = await this.request(
|
|
16308
|
-
`/control/sessions?${query.toString()}`
|
|
16309
|
-
);
|
|
16310
|
-
const items = Array.isArray(res.items) ? res.items : [];
|
|
16311
|
-
return items.map((row) => this.normalizeConversationSession(row));
|
|
16312
|
-
}
|
|
16313
16531
|
normalizeConversationSession(row) {
|
|
16314
16532
|
const sessionId = String(row.sessionId ?? row.session_id ?? "");
|
|
16315
16533
|
const environmentId = String(row.environmentId ?? row.environment_id ?? "");
|
|
@@ -16368,6 +16586,7 @@ var Granular = class _Granular {
|
|
|
16368
16586
|
*/
|
|
16369
16587
|
async createSession(options) {
|
|
16370
16588
|
const clientId = options.clientId || `client_${Date.now()}`;
|
|
16589
|
+
const sessionScope = options.sessionScope?.trim() || void 0;
|
|
16371
16590
|
await this.activateEnvironment(options.environmentId);
|
|
16372
16591
|
const envData = await this.environments.get(options.environmentId);
|
|
16373
16592
|
const environment = this.bindEnvironmentHandle(envData);
|
|
@@ -16376,6 +16595,8 @@ var Granular = class _Granular {
|
|
|
16376
16595
|
body: JSON.stringify({
|
|
16377
16596
|
environmentId: options.environmentId,
|
|
16378
16597
|
clientId,
|
|
16598
|
+
sessionScope,
|
|
16599
|
+
capabilities: sessionScope ? { sessionScope } : void 0,
|
|
16379
16600
|
initialHeap: options.initialHeap
|
|
16380
16601
|
})
|
|
16381
16602
|
});
|
|
@@ -19556,6 +19777,7 @@ function buildGranularAgentSystemPrompt(input) {
|
|
|
19556
19777
|
- \`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(...)\`.
|
|
19557
19778
|
- 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.
|
|
19558
19779
|
- 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()\`.
|
|
19780
|
+
- 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.
|
|
19559
19781
|
- 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.
|
|
19560
19782
|
- 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.
|
|
19561
19783
|
- 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.
|
|
@@ -19826,11 +20048,12 @@ ${actionIndex}
|
|
|
19826
20048
|
- 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.
|
|
19827
20049
|
- 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()\`.
|
|
19828
20050
|
- 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.
|
|
19829
|
-
-
|
|
20051
|
+
- 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.
|
|
20052
|
+
- 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.
|
|
19830
20053
|
- 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.
|
|
19831
20054
|
- 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.
|
|
19832
20055
|
- 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.
|
|
19833
|
-
- Do not ask for confirmation before opening a prepared action
|
|
20056
|
+
- 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.
|
|
19834
20057
|
- 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.
|
|
19835
20058
|
- 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.
|
|
19836
20059
|
- 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.
|
|
@@ -20022,8 +20245,9 @@ function resolveHarnessTemplate(templateId = "stable", options) {
|
|
|
20022
20245
|
}
|
|
20023
20246
|
|
|
20024
20247
|
// src/openai-usage.ts
|
|
20025
|
-
var
|
|
20026
|
-
var
|
|
20248
|
+
var OPENAI_GPT_5_4_PRICING_SOURCE_URL = "https://developers.openai.com/api/docs/models/gpt-5.4/";
|
|
20249
|
+
var OPENAI_GPT_5_6_PRICING_SOURCE_URL = "https://developers.openai.com/api/docs/pricing";
|
|
20250
|
+
var OPENAI_LONG_CONTEXT_THRESHOLD_TOKENS = 272e3;
|
|
20027
20251
|
var OPENAI_MODEL_PRICING_USD_PER_MILLION = {
|
|
20028
20252
|
"gpt-5.4": {
|
|
20029
20253
|
provider: "openai",
|
|
@@ -20032,8 +20256,26 @@ var OPENAI_MODEL_PRICING_USD_PER_MILLION = {
|
|
|
20032
20256
|
inputUsdPerMillion: 2.5,
|
|
20033
20257
|
cachedInputUsdPerMillion: 0.25,
|
|
20034
20258
|
outputUsdPerMillion: 15,
|
|
20035
|
-
sourceUrl:
|
|
20036
|
-
effectiveDate:
|
|
20259
|
+
sourceUrl: OPENAI_GPT_5_4_PRICING_SOURCE_URL,
|
|
20260
|
+
effectiveDate: "2026-05-19"
|
|
20261
|
+
},
|
|
20262
|
+
"gpt-5.6-luna": {
|
|
20263
|
+
provider: "openai",
|
|
20264
|
+
model: "gpt-5.6-luna",
|
|
20265
|
+
currency: "USD",
|
|
20266
|
+
inputUsdPerMillion: 1,
|
|
20267
|
+
cachedInputUsdPerMillion: 0.1,
|
|
20268
|
+
cacheWriteUsdPerMillion: 1.25,
|
|
20269
|
+
outputUsdPerMillion: 6,
|
|
20270
|
+
sourceUrl: OPENAI_GPT_5_6_PRICING_SOURCE_URL,
|
|
20271
|
+
effectiveDate: "2026-07-11",
|
|
20272
|
+
longContextThresholdTokens: OPENAI_LONG_CONTEXT_THRESHOLD_TOKENS,
|
|
20273
|
+
longContextPricing: {
|
|
20274
|
+
inputUsdPerMillion: 2,
|
|
20275
|
+
cachedInputUsdPerMillion: 0.2,
|
|
20276
|
+
cacheWriteUsdPerMillion: 2.5,
|
|
20277
|
+
outputUsdPerMillion: 9
|
|
20278
|
+
}
|
|
20037
20279
|
}
|
|
20038
20280
|
};
|
|
20039
20281
|
function asRecord5(value) {
|
|
@@ -20046,8 +20288,18 @@ function numberField(record, key) {
|
|
|
20046
20288
|
function microsPerMillion(usdPerMillion) {
|
|
20047
20289
|
return Math.round(usdPerMillion * 1e6);
|
|
20048
20290
|
}
|
|
20049
|
-
function getOpenAIModelPricing(model) {
|
|
20050
|
-
|
|
20291
|
+
function getOpenAIModelPricing(model, inputTokens = 0) {
|
|
20292
|
+
const pricing = OPENAI_MODEL_PRICING_USD_PER_MILLION[model];
|
|
20293
|
+
if (!pricing) return null;
|
|
20294
|
+
const threshold = pricing.longContextThresholdTokens ?? null;
|
|
20295
|
+
if (pricing.longContextPricing && typeof threshold === "number" && inputTokens > threshold) {
|
|
20296
|
+
return {
|
|
20297
|
+
...pricing,
|
|
20298
|
+
...pricing.longContextPricing,
|
|
20299
|
+
contextTier: "long"
|
|
20300
|
+
};
|
|
20301
|
+
}
|
|
20302
|
+
return { ...pricing, contextTier: "short" };
|
|
20051
20303
|
}
|
|
20052
20304
|
function normalizeOpenAIUsage(rawUsage) {
|
|
20053
20305
|
const usage = asRecord5(rawUsage);
|
|
@@ -20055,6 +20307,7 @@ function normalizeOpenAIUsage(rawUsage) {
|
|
|
20055
20307
|
return {
|
|
20056
20308
|
inputTokens: 0,
|
|
20057
20309
|
cachedInputTokens: 0,
|
|
20310
|
+
cacheWriteTokens: 0,
|
|
20058
20311
|
uncachedInputTokens: 0,
|
|
20059
20312
|
outputTokens: 0,
|
|
20060
20313
|
reasoningTokens: 0,
|
|
@@ -20070,20 +20323,28 @@ function normalizeOpenAIUsage(rawUsage) {
|
|
|
20070
20323
|
inputTokens,
|
|
20071
20324
|
numberField(inputDetails, "cached_tokens") || numberField(inputDetails, "cached_input_tokens")
|
|
20072
20325
|
);
|
|
20326
|
+
const cacheWriteTokens = Math.min(
|
|
20327
|
+
Math.max(inputTokens - cachedInputTokens, 0),
|
|
20328
|
+
numberField(inputDetails, "cache_write_tokens")
|
|
20329
|
+
);
|
|
20073
20330
|
const reasoningTokens = numberField(outputDetails, "reasoning_tokens") || numberField(outputDetails, "reasoning_output_tokens");
|
|
20074
20331
|
return {
|
|
20075
20332
|
inputTokens,
|
|
20076
20333
|
cachedInputTokens,
|
|
20077
|
-
|
|
20334
|
+
cacheWriteTokens,
|
|
20335
|
+
uncachedInputTokens: Math.max(
|
|
20336
|
+
inputTokens - cachedInputTokens - cacheWriteTokens,
|
|
20337
|
+
0
|
|
20338
|
+
),
|
|
20078
20339
|
outputTokens,
|
|
20079
20340
|
reasoningTokens,
|
|
20080
20341
|
totalTokens
|
|
20081
20342
|
};
|
|
20082
20343
|
}
|
|
20083
20344
|
function calculateOpenAITokenSpend(model, rawUsage) {
|
|
20084
|
-
const pricing = getOpenAIModelPricing(model);
|
|
20085
|
-
if (!pricing) return null;
|
|
20086
20345
|
const usage = normalizeOpenAIUsage(rawUsage);
|
|
20346
|
+
const pricing = getOpenAIModelPricing(model, usage.inputTokens);
|
|
20347
|
+
if (!pricing) return null;
|
|
20087
20348
|
const inputPricePerMillionMicros = microsPerMillion(
|
|
20088
20349
|
pricing.inputUsdPerMillion
|
|
20089
20350
|
);
|
|
@@ -20093,14 +20354,19 @@ function calculateOpenAITokenSpend(model, rawUsage) {
|
|
|
20093
20354
|
const outputPricePerMillionMicros = microsPerMillion(
|
|
20094
20355
|
pricing.outputUsdPerMillion
|
|
20095
20356
|
);
|
|
20357
|
+
const cacheWritePricePerMillionMicros = typeof pricing.cacheWriteUsdPerMillion === "number" ? microsPerMillion(pricing.cacheWriteUsdPerMillion) : null;
|
|
20358
|
+
const cacheWriteCostMicros = Math.round(
|
|
20359
|
+
usage.cacheWriteTokens * (cacheWritePricePerMillionMicros ?? inputPricePerMillionMicros) / 1e6
|
|
20360
|
+
);
|
|
20096
20361
|
const amountMicros = Math.round(
|
|
20097
|
-
(usage.uncachedInputTokens * inputPricePerMillionMicros + usage.cachedInputTokens * cachedInputPricePerMillionMicros + usage.outputTokens * outputPricePerMillionMicros) / 1e6
|
|
20362
|
+
(usage.uncachedInputTokens * inputPricePerMillionMicros + usage.cachedInputTokens * cachedInputPricePerMillionMicros + usage.cacheWriteTokens * (cacheWritePricePerMillionMicros ?? inputPricePerMillionMicros) + usage.outputTokens * outputPricePerMillionMicros) / 1e6
|
|
20098
20363
|
);
|
|
20099
20364
|
return {
|
|
20100
20365
|
provider: "openai",
|
|
20101
20366
|
model,
|
|
20102
20367
|
inputTokens: usage.inputTokens,
|
|
20103
20368
|
cachedInputTokens: usage.cachedInputTokens,
|
|
20369
|
+
cacheWriteTokens: usage.cacheWriteTokens,
|
|
20104
20370
|
uncachedInputTokens: usage.uncachedInputTokens,
|
|
20105
20371
|
outputTokens: usage.outputTokens,
|
|
20106
20372
|
reasoningTokens: usage.reasoningTokens,
|
|
@@ -20109,7 +20375,11 @@ function calculateOpenAITokenSpend(model, rawUsage) {
|
|
|
20109
20375
|
currency: "USD",
|
|
20110
20376
|
inputPricePerMillionMicros,
|
|
20111
20377
|
cachedInputPricePerMillionMicros,
|
|
20378
|
+
cacheWritePricePerMillionMicros,
|
|
20379
|
+
cacheWriteCostMicros,
|
|
20112
20380
|
outputPricePerMillionMicros,
|
|
20381
|
+
pricingContextTier: pricing.contextTier || "short",
|
|
20382
|
+
longContextThresholdTokens: pricing.longContextThresholdTokens ?? null,
|
|
20113
20383
|
pricingSource: pricing.sourceUrl,
|
|
20114
20384
|
pricingEffectiveAt: pricing.effectiveDate,
|
|
20115
20385
|
usage
|