@granular-software/sdk 0.4.50 → 0.4.52
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 +685 -210
- package/dist/agent-evals.js.map +1 -1
- package/dist/agent-evals.mjs +685 -210
- 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 +754 -253
- package/dist/{client-DaYFTHG8.d.mts → client-B2OyGOHE.d.mts} +44 -13
- package/dist/{client-BbI7ThzU.d.ts → client-BMfjRJTs.d.ts} +44 -13
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +685 -210
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +685 -210
- package/dist/index.mjs.map +1 -1
- package/dist/{spend-RpJikX9w.d.mts → spend-DpRRCrAr.d.mts} +161 -5
- package/dist/{spend-RpJikX9w.d.ts → spend-DpRRCrAr.d.ts} +161 -5
- 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.js
CHANGED
|
@@ -51,11 +51,11 @@ var __export = (target, all) => {
|
|
|
51
51
|
for (var name in all)
|
|
52
52
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
53
53
|
};
|
|
54
|
-
var __copyProps = (to,
|
|
55
|
-
if (
|
|
56
|
-
for (let key of __getOwnPropNames(
|
|
54
|
+
var __copyProps = (to, from2, except, desc) => {
|
|
55
|
+
if (from2 && typeof from2 === "object" || typeof from2 === "function") {
|
|
56
|
+
for (let key of __getOwnPropNames(from2))
|
|
57
57
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
58
|
-
__defProp(to, key, { get: () =>
|
|
58
|
+
__defProp(to, key, { get: () => from2[key], enumerable: !(desc = __getOwnPropDesc(from2, key)) || desc.enumerable });
|
|
59
59
|
}
|
|
60
60
|
return to;
|
|
61
61
|
};
|
|
@@ -4072,16 +4072,37 @@ var WSClient = class {
|
|
|
4072
4072
|
tokenRefreshTimer = null;
|
|
4073
4073
|
isExplicitlyDisconnected = false;
|
|
4074
4074
|
reconnectAttempts = 0;
|
|
4075
|
+
connectPromise = null;
|
|
4076
|
+
connectionEpoch = 0;
|
|
4077
|
+
cancelConnectAttempt = null;
|
|
4075
4078
|
options;
|
|
4076
4079
|
constructor(options) {
|
|
4077
4080
|
this.options = options;
|
|
4078
4081
|
this.url = options.url;
|
|
4079
4082
|
this.sessionId = options.sessionId;
|
|
4080
4083
|
this.token = options.token;
|
|
4084
|
+
if (options.initialDocumentSnapshot) {
|
|
4085
|
+
this.seedDocumentSnapshot(options.initialDocumentSnapshot);
|
|
4086
|
+
}
|
|
4081
4087
|
}
|
|
4082
4088
|
get currentSessionId() {
|
|
4083
4089
|
return this.sessionId;
|
|
4084
4090
|
}
|
|
4091
|
+
seedDocumentSnapshot(document) {
|
|
4092
|
+
if (!document || typeof document !== "object" || Array.isArray(document)) {
|
|
4093
|
+
return;
|
|
4094
|
+
}
|
|
4095
|
+
try {
|
|
4096
|
+
this.doc = document instanceof Uint8Array ? Automerge__namespace.load(document) : Automerge__namespace.from(document);
|
|
4097
|
+
this.syncState = Automerge__namespace.initSyncState();
|
|
4098
|
+
this.emit("sync", this.doc);
|
|
4099
|
+
} catch (error) {
|
|
4100
|
+
console.warn("[Granular] Failed to seed cached session document", error);
|
|
4101
|
+
}
|
|
4102
|
+
}
|
|
4103
|
+
saveDocumentSnapshot() {
|
|
4104
|
+
return Automerge__namespace.save(this.doc);
|
|
4105
|
+
}
|
|
4085
4106
|
clearTokenRefreshTimer() {
|
|
4086
4107
|
if (this.tokenRefreshTimer) {
|
|
4087
4108
|
clearTimeout(this.tokenRefreshTimer);
|
|
@@ -4191,8 +4212,23 @@ var WSClient = class {
|
|
|
4191
4212
|
* Connect to the WebSocket server
|
|
4192
4213
|
* @returns {Promise<void>} Resolves when connection is open
|
|
4193
4214
|
*/
|
|
4194
|
-
async connect() {
|
|
4215
|
+
async connect(options = {}) {
|
|
4216
|
+
if (this.ws?.readyState === READY_STATE_OPEN) return;
|
|
4217
|
+
if (this.connectPromise) return this.connectPromise;
|
|
4218
|
+
const connectPromise = this.connectAttempt(options.signal);
|
|
4219
|
+
this.connectPromise = connectPromise;
|
|
4220
|
+
try {
|
|
4221
|
+
await connectPromise;
|
|
4222
|
+
} finally {
|
|
4223
|
+
if (this.connectPromise === connectPromise) {
|
|
4224
|
+
this.connectPromise = null;
|
|
4225
|
+
}
|
|
4226
|
+
}
|
|
4227
|
+
}
|
|
4228
|
+
async connectAttempt(signal) {
|
|
4229
|
+
if (signal?.aborted) throw new Error("WebSocket connect aborted");
|
|
4195
4230
|
const token = await this.resolveTokenForConnect();
|
|
4231
|
+
if (signal?.aborted) throw new Error("WebSocket connect aborted");
|
|
4196
4232
|
this.isExplicitlyDisconnected = false;
|
|
4197
4233
|
this.scheduleTokenRefresh();
|
|
4198
4234
|
if (this.reconnectTimer) {
|
|
@@ -4204,7 +4240,7 @@ var WSClient = class {
|
|
|
4204
4240
|
try {
|
|
4205
4241
|
const wsModule = await Promise.resolve().then(() => (init_wrapper(), wrapper_exports));
|
|
4206
4242
|
WebSocketClass = wsModule.default || wsModule;
|
|
4207
|
-
} catch
|
|
4243
|
+
} catch {
|
|
4208
4244
|
}
|
|
4209
4245
|
}
|
|
4210
4246
|
if (!WebSocketClass) {
|
|
@@ -4212,83 +4248,97 @@ var WSClient = class {
|
|
|
4212
4248
|
'No WebSocket implementation found. If using Node.js, please install "ws" and pass the constructor to the SDK options: { WebSocketCtor: WebSocket }.'
|
|
4213
4249
|
);
|
|
4214
4250
|
}
|
|
4251
|
+
const epoch = ++this.connectionEpoch;
|
|
4252
|
+
const wsUrl = new URL(this.url);
|
|
4253
|
+
wsUrl.searchParams.set("sessionId", this.sessionId);
|
|
4254
|
+
wsUrl.searchParams.set("token", token);
|
|
4255
|
+
const socket = new WebSocketClass(wsUrl.toString());
|
|
4256
|
+
this.ws = socket;
|
|
4215
4257
|
return new Promise((resolve, reject) => {
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
if (
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
this.reconnectTimer = null;
|
|
4228
|
-
}
|
|
4229
|
-
this.reconnectAttempts = 0;
|
|
4230
|
-
this.emit("open", {});
|
|
4231
|
-
resolve();
|
|
4232
|
-
});
|
|
4233
|
-
socket.on("message", (data) => {
|
|
4234
|
-
try {
|
|
4235
|
-
const message = JSON.parse(data.toString());
|
|
4236
|
-
this.handleMessage(message);
|
|
4237
|
-
} catch (error) {
|
|
4238
|
-
console.error("[Granular] Failed to parse message:", error);
|
|
4239
|
-
}
|
|
4240
|
-
});
|
|
4241
|
-
socket.on("error", (error) => {
|
|
4242
|
-
this.emit("error", error);
|
|
4243
|
-
if (socket.readyState !== READY_STATE_OPEN) {
|
|
4244
|
-
reject(error);
|
|
4245
|
-
}
|
|
4246
|
-
});
|
|
4247
|
-
socket.on("close", (code, reason) => {
|
|
4248
|
-
this.handleDisconnect({
|
|
4249
|
-
code,
|
|
4250
|
-
reason: this.normalizeReason(reason),
|
|
4251
|
-
// ws does not provide wasClean on Node-style close callback
|
|
4252
|
-
wasClean: code === 1e3
|
|
4253
|
-
});
|
|
4254
|
-
});
|
|
4258
|
+
let settled = false;
|
|
4259
|
+
const isCurrent = () => this.connectionEpoch === epoch && this.ws === socket;
|
|
4260
|
+
const finish = (error) => {
|
|
4261
|
+
if (settled) return;
|
|
4262
|
+
settled = true;
|
|
4263
|
+
if (this.cancelConnectAttempt === handleAbort) {
|
|
4264
|
+
this.cancelConnectAttempt = null;
|
|
4265
|
+
}
|
|
4266
|
+
signal?.removeEventListener("abort", handleAbort);
|
|
4267
|
+
if (error) {
|
|
4268
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
4255
4269
|
} else {
|
|
4256
|
-
|
|
4257
|
-
if (this.reconnectTimer) {
|
|
4258
|
-
clearTimeout(this.reconnectTimer);
|
|
4259
|
-
this.reconnectTimer = null;
|
|
4260
|
-
}
|
|
4261
|
-
this.reconnectAttempts = 0;
|
|
4262
|
-
this.emit("open", {});
|
|
4263
|
-
resolve();
|
|
4264
|
-
};
|
|
4265
|
-
this.ws.onmessage = (event) => {
|
|
4266
|
-
try {
|
|
4267
|
-
const data = event.data;
|
|
4268
|
-
const message = JSON.parse(data.toString());
|
|
4269
|
-
this.handleMessage(message);
|
|
4270
|
-
} catch (error) {
|
|
4271
|
-
console.error("[Granular] Failed to parse message:", error);
|
|
4272
|
-
}
|
|
4273
|
-
};
|
|
4274
|
-
this.ws.onerror = (event) => {
|
|
4275
|
-
const error = new Error("WebSocket error");
|
|
4276
|
-
error.event = event;
|
|
4277
|
-
this.emit("error", error);
|
|
4278
|
-
if (this.ws?.readyState !== READY_STATE_OPEN) {
|
|
4279
|
-
reject(error);
|
|
4280
|
-
}
|
|
4281
|
-
};
|
|
4282
|
-
this.ws.onclose = (event) => {
|
|
4283
|
-
this.handleDisconnect({
|
|
4284
|
-
code: event.code,
|
|
4285
|
-
reason: event.reason,
|
|
4286
|
-
wasClean: event.wasClean
|
|
4287
|
-
});
|
|
4288
|
-
};
|
|
4270
|
+
resolve();
|
|
4289
4271
|
}
|
|
4290
|
-
}
|
|
4291
|
-
|
|
4272
|
+
};
|
|
4273
|
+
const closeStaleSocket = () => {
|
|
4274
|
+
try {
|
|
4275
|
+
socket.close(1e3, "Stale connection attempt");
|
|
4276
|
+
} catch {
|
|
4277
|
+
}
|
|
4278
|
+
};
|
|
4279
|
+
const handleAbort = () => {
|
|
4280
|
+
if (isCurrent()) {
|
|
4281
|
+
this.connectionEpoch += 1;
|
|
4282
|
+
this.ws = null;
|
|
4283
|
+
}
|
|
4284
|
+
closeStaleSocket();
|
|
4285
|
+
finish(new Error("WebSocket connect aborted"));
|
|
4286
|
+
};
|
|
4287
|
+
this.cancelConnectAttempt = handleAbort;
|
|
4288
|
+
const handleOpen = () => {
|
|
4289
|
+
if (!isCurrent()) {
|
|
4290
|
+
closeStaleSocket();
|
|
4291
|
+
return;
|
|
4292
|
+
}
|
|
4293
|
+
this.reconnectAttempts = 0;
|
|
4294
|
+
this.emit("open", {});
|
|
4295
|
+
finish();
|
|
4296
|
+
};
|
|
4297
|
+
const handleMessage = (data) => {
|
|
4298
|
+
if (!isCurrent()) return;
|
|
4299
|
+
try {
|
|
4300
|
+
const text = typeof data === "string" ? data : data && typeof data === "object" && "toString" in data ? String(data.toString()) : "";
|
|
4301
|
+
this.handleMessage(JSON.parse(text));
|
|
4302
|
+
} catch (error) {
|
|
4303
|
+
console.error("[Granular] Failed to parse message:", error);
|
|
4304
|
+
}
|
|
4305
|
+
};
|
|
4306
|
+
const handleError = (error) => {
|
|
4307
|
+
if (!isCurrent()) return;
|
|
4308
|
+
const typedError = error instanceof Error ? error : new Error("WebSocket error");
|
|
4309
|
+
this.emit("error", typedError);
|
|
4310
|
+
if (socket.readyState !== READY_STATE_OPEN) finish(typedError);
|
|
4311
|
+
};
|
|
4312
|
+
const handleClose = (close) => {
|
|
4313
|
+
if (!isCurrent()) return;
|
|
4314
|
+
if (!settled) {
|
|
4315
|
+
finish(
|
|
4316
|
+
new Error(
|
|
4317
|
+
`WebSocket closed before ready${close.code ? ` (code=${close.code})` : ""}`
|
|
4318
|
+
)
|
|
4319
|
+
);
|
|
4320
|
+
}
|
|
4321
|
+
this.handleDisconnect({
|
|
4322
|
+
code: close.code,
|
|
4323
|
+
reason: this.normalizeReason(close.reason),
|
|
4324
|
+
wasClean: close.wasClean
|
|
4325
|
+
});
|
|
4326
|
+
};
|
|
4327
|
+
signal?.addEventListener("abort", handleAbort, { once: true });
|
|
4328
|
+
const nodeSocket = socket;
|
|
4329
|
+
if (typeof nodeSocket.on === "function") {
|
|
4330
|
+
nodeSocket.on("open", handleOpen);
|
|
4331
|
+
nodeSocket.on("message", handleMessage);
|
|
4332
|
+
nodeSocket.on("error", handleError);
|
|
4333
|
+
nodeSocket.on(
|
|
4334
|
+
"close",
|
|
4335
|
+
(code, reason) => handleClose({ code, reason, wasClean: code === 1e3 })
|
|
4336
|
+
);
|
|
4337
|
+
} else {
|
|
4338
|
+
socket.onopen = handleOpen;
|
|
4339
|
+
socket.onmessage = (event) => handleMessage(event.data);
|
|
4340
|
+
socket.onerror = handleError;
|
|
4341
|
+
socket.onclose = (event) => handleClose(event);
|
|
4292
4342
|
}
|
|
4293
4343
|
});
|
|
4294
4344
|
}
|
|
@@ -4306,9 +4356,58 @@ var WSClient = class {
|
|
|
4306
4356
|
return void 0;
|
|
4307
4357
|
}
|
|
4308
4358
|
rejectPending(error) {
|
|
4309
|
-
this.messageQueue.forEach((pending) =>
|
|
4359
|
+
this.messageQueue.forEach((pending) => {
|
|
4360
|
+
clearTimeout(pending.timeout);
|
|
4361
|
+
pending.reject(error);
|
|
4362
|
+
});
|
|
4310
4363
|
this.messageQueue = [];
|
|
4311
4364
|
}
|
|
4365
|
+
emitReconnectErrorMessage(error) {
|
|
4366
|
+
const reconnectInfo = {
|
|
4367
|
+
error,
|
|
4368
|
+
sessionId: this.sessionId,
|
|
4369
|
+
timestamp: Date.now()
|
|
4370
|
+
};
|
|
4371
|
+
this.emit("reconnect_error", reconnectInfo);
|
|
4372
|
+
if (this.options.onReconnectError) {
|
|
4373
|
+
try {
|
|
4374
|
+
this.options.onReconnectError(reconnectInfo);
|
|
4375
|
+
} catch (callbackError) {
|
|
4376
|
+
console.error(
|
|
4377
|
+
"[Granular] onReconnectError callback failed:",
|
|
4378
|
+
callbackError
|
|
4379
|
+
);
|
|
4380
|
+
}
|
|
4381
|
+
}
|
|
4382
|
+
}
|
|
4383
|
+
scheduleReconnectAttempt() {
|
|
4384
|
+
if (this.isExplicitlyDisconnected || this.reconnectTimer) return null;
|
|
4385
|
+
const baseReconnectDelayMs = typeof this.options.reconnectDelayMs === "number" && Number.isFinite(this.options.reconnectDelayMs) && this.options.reconnectDelayMs > 0 ? this.options.reconnectDelayMs : DEFAULT_RECONNECT_DELAY_MS;
|
|
4386
|
+
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;
|
|
4387
|
+
if (this.reconnectAttempts >= maxReconnectAttempts) {
|
|
4388
|
+
this.emitReconnectErrorMessage(
|
|
4389
|
+
`WebSocket reconnect attempts exhausted after ${maxReconnectAttempts} attempt(s).`
|
|
4390
|
+
);
|
|
4391
|
+
return null;
|
|
4392
|
+
}
|
|
4393
|
+
this.reconnectAttempts += 1;
|
|
4394
|
+
const reconnectDelayMs = Math.min(
|
|
4395
|
+
3e4,
|
|
4396
|
+
baseReconnectDelayMs * 2 ** Math.max(0, this.reconnectAttempts - 1)
|
|
4397
|
+
);
|
|
4398
|
+
this.reconnectTimer = setTimeout(() => {
|
|
4399
|
+
this.reconnectTimer = null;
|
|
4400
|
+
console.log("[Granular] Attempting reconnect...");
|
|
4401
|
+
this.connect().catch((error) => {
|
|
4402
|
+
console.error("[Granular] Reconnect failed:", error);
|
|
4403
|
+
this.emitReconnectErrorMessage(
|
|
4404
|
+
error instanceof Error ? error.message : String(error)
|
|
4405
|
+
);
|
|
4406
|
+
this.scheduleReconnectAttempt();
|
|
4407
|
+
});
|
|
4408
|
+
}, reconnectDelayMs);
|
|
4409
|
+
return reconnectDelayMs;
|
|
4410
|
+
}
|
|
4312
4411
|
buildDisconnectError(info) {
|
|
4313
4412
|
const details = [
|
|
4314
4413
|
info.code !== void 0 ? `code=${info.code}` : void 0,
|
|
@@ -4318,8 +4417,6 @@ var WSClient = class {
|
|
|
4318
4417
|
return new Error(`WebSocket disconnected${suffix}`);
|
|
4319
4418
|
}
|
|
4320
4419
|
handleDisconnect(close = {}) {
|
|
4321
|
-
const baseReconnectDelayMs = typeof this.options.reconnectDelayMs === "number" && Number.isFinite(this.options.reconnectDelayMs) && this.options.reconnectDelayMs > 0 ? this.options.reconnectDelayMs : DEFAULT_RECONNECT_DELAY_MS;
|
|
4322
|
-
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;
|
|
4323
4420
|
const unexpected = !this.isExplicitlyDisconnected;
|
|
4324
4421
|
const info = {
|
|
4325
4422
|
code: close.code,
|
|
@@ -4339,32 +4436,9 @@ var WSClient = class {
|
|
|
4339
4436
|
const disconnectError = this.buildDisconnectError(info);
|
|
4340
4437
|
this.rejectPending(disconnectError);
|
|
4341
4438
|
this.emit("disconnect", info);
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
sessionId: this.sessionId,
|
|
4346
|
-
timestamp: Date.now()
|
|
4347
|
-
};
|
|
4348
|
-
this.emit("reconnect_error", reconnectInfo);
|
|
4349
|
-
if (this.options.onReconnectError) {
|
|
4350
|
-
try {
|
|
4351
|
-
this.options.onReconnectError(reconnectInfo);
|
|
4352
|
-
} catch (callbackError) {
|
|
4353
|
-
console.error(
|
|
4354
|
-
"[Granular] onReconnectError callback failed:",
|
|
4355
|
-
callbackError
|
|
4356
|
-
);
|
|
4357
|
-
}
|
|
4358
|
-
}
|
|
4359
|
-
return;
|
|
4360
|
-
}
|
|
4361
|
-
this.reconnectAttempts += 1;
|
|
4362
|
-
const reconnectDelayMs = Math.min(
|
|
4363
|
-
3e4,
|
|
4364
|
-
baseReconnectDelayMs * 2 ** Math.max(0, this.reconnectAttempts - 1)
|
|
4365
|
-
);
|
|
4366
|
-
info.reconnectScheduled = true;
|
|
4367
|
-
info.reconnectDelayMs = reconnectDelayMs;
|
|
4439
|
+
const reconnectDelayMs = this.scheduleReconnectAttempt();
|
|
4440
|
+
info.reconnectScheduled = reconnectDelayMs !== null;
|
|
4441
|
+
if (reconnectDelayMs !== null) info.reconnectDelayMs = reconnectDelayMs;
|
|
4368
4442
|
if (this.options.onUnexpectedClose) {
|
|
4369
4443
|
try {
|
|
4370
4444
|
this.options.onUnexpectedClose(info);
|
|
@@ -4375,28 +4449,6 @@ var WSClient = class {
|
|
|
4375
4449
|
);
|
|
4376
4450
|
}
|
|
4377
4451
|
}
|
|
4378
|
-
this.reconnectTimer = setTimeout(() => {
|
|
4379
|
-
console.log("[Granular] Attempting reconnect...");
|
|
4380
|
-
this.connect().catch((error) => {
|
|
4381
|
-
console.error("[Granular] Reconnect failed:", error);
|
|
4382
|
-
const reconnectInfo = {
|
|
4383
|
-
error: error instanceof Error ? error.message : String(error),
|
|
4384
|
-
sessionId: this.sessionId,
|
|
4385
|
-
timestamp: Date.now()
|
|
4386
|
-
};
|
|
4387
|
-
this.emit("reconnect_error", reconnectInfo);
|
|
4388
|
-
if (this.options.onReconnectError) {
|
|
4389
|
-
try {
|
|
4390
|
-
this.options.onReconnectError(reconnectInfo);
|
|
4391
|
-
} catch (callbackError) {
|
|
4392
|
-
console.error(
|
|
4393
|
-
"[Granular] onReconnectError callback failed:",
|
|
4394
|
-
callbackError
|
|
4395
|
-
);
|
|
4396
|
-
}
|
|
4397
|
-
}
|
|
4398
|
-
});
|
|
4399
|
-
}, reconnectDelayMs);
|
|
4400
4452
|
}
|
|
4401
4453
|
}
|
|
4402
4454
|
handleMessage(message) {
|
|
@@ -4507,6 +4559,7 @@ var WSClient = class {
|
|
|
4507
4559
|
const response = message;
|
|
4508
4560
|
const pending = this.messageQueue.find((q) => q.id === response.id);
|
|
4509
4561
|
if (pending) {
|
|
4562
|
+
clearTimeout(pending.timeout);
|
|
4510
4563
|
if (response.type === "rpc_error") {
|
|
4511
4564
|
pending.reject(
|
|
4512
4565
|
new Error(
|
|
@@ -4552,16 +4605,22 @@ var WSClient = class {
|
|
|
4552
4605
|
id
|
|
4553
4606
|
};
|
|
4554
4607
|
return new Promise((resolve, reject) => {
|
|
4555
|
-
this.messageQueue.push({ resolve, reject, id });
|
|
4556
|
-
this.ws.send(JSON.stringify(request));
|
|
4557
4608
|
const timeoutMs = rpcTimeoutMsForMethod(method);
|
|
4558
|
-
setTimeout(() => {
|
|
4609
|
+
const timeout = setTimeout(() => {
|
|
4559
4610
|
const pending = this.messageQueue.find((q) => q.id === id);
|
|
4560
4611
|
if (pending) {
|
|
4561
4612
|
this.messageQueue = this.messageQueue.filter((q) => q.id !== id);
|
|
4562
4613
|
reject(new Error(`RPC timeout: ${method}`));
|
|
4563
4614
|
}
|
|
4564
4615
|
}, timeoutMs);
|
|
4616
|
+
this.messageQueue.push({ resolve, reject, id, timeout });
|
|
4617
|
+
try {
|
|
4618
|
+
this.ws.send(JSON.stringify(request));
|
|
4619
|
+
} catch (error) {
|
|
4620
|
+
clearTimeout(timeout);
|
|
4621
|
+
this.messageQueue = this.messageQueue.filter((q) => q.id !== id);
|
|
4622
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
4623
|
+
}
|
|
4565
4624
|
});
|
|
4566
4625
|
}
|
|
4567
4626
|
async handleIncomingRpc(request) {
|
|
@@ -4647,15 +4706,18 @@ var WSClient = class {
|
|
|
4647
4706
|
/**
|
|
4648
4707
|
* Disconnect the WebSocket and clear state
|
|
4649
4708
|
*/
|
|
4650
|
-
disconnect() {
|
|
4709
|
+
disconnect(options = {}) {
|
|
4651
4710
|
this.isExplicitlyDisconnected = true;
|
|
4711
|
+
this.cancelConnectAttempt?.();
|
|
4712
|
+
this.cancelConnectAttempt = null;
|
|
4713
|
+
this.connectionEpoch += 1;
|
|
4652
4714
|
if (this.reconnectTimer) {
|
|
4653
4715
|
clearTimeout(this.reconnectTimer);
|
|
4654
4716
|
this.reconnectTimer = null;
|
|
4655
4717
|
}
|
|
4656
4718
|
this.clearTokenRefreshTimer();
|
|
4657
4719
|
if (this.ws) {
|
|
4658
|
-
this.ws.close(1e3, "Client disconnect");
|
|
4720
|
+
this.ws.close(1e3, options.reason || "Client disconnect");
|
|
4659
4721
|
this.ws = null;
|
|
4660
4722
|
}
|
|
4661
4723
|
this.rejectPending(new Error("Client explicitly disconnected"));
|
|
@@ -4751,8 +4813,12 @@ function normalizePrompt(rawValue) {
|
|
|
4751
4813
|
const source = promptRecord || raw;
|
|
4752
4814
|
const id = typeof source.id === "string" ? source.id : typeof raw.id === "string" ? raw.id : typeof raw.promptId === "string" ? raw.promptId : "";
|
|
4753
4815
|
if (!id) return null;
|
|
4816
|
+
const jobId = typeof source.jobId === "string" && source.jobId.trim() ? source.jobId.trim() : typeof raw.jobId === "string" && raw.jobId.trim() ? raw.jobId.trim() : void 0;
|
|
4817
|
+
const turnId = typeof source.turnId === "string" && source.turnId.trim() ? source.turnId.trim() : typeof raw.turnId === "string" && raw.turnId.trim() ? raw.turnId.trim() : void 0;
|
|
4754
4818
|
return {
|
|
4755
4819
|
id,
|
|
4820
|
+
...jobId ? { jobId } : {},
|
|
4821
|
+
...turnId ? { turnId } : {},
|
|
4756
4822
|
type: normalizePromptType(source === raw ? raw : { ...raw, ...source }),
|
|
4757
4823
|
title: typeof source.title === "string" ? source.title : "Input required",
|
|
4758
4824
|
message: typeof source.message === "string" ? source.message : "",
|
|
@@ -4839,6 +4905,9 @@ var Session = class {
|
|
|
4839
4905
|
this.initialQuota = options.initialQuota || null;
|
|
4840
4906
|
this.setupEventHandlers();
|
|
4841
4907
|
this.setupToolInvokeHandler();
|
|
4908
|
+
this.currentDomainRevision = this.extractDomainRevisionFromDoc(
|
|
4909
|
+
this.client.doc
|
|
4910
|
+
);
|
|
4842
4911
|
}
|
|
4843
4912
|
extractDomainRevisionFromDoc(doc) {
|
|
4844
4913
|
const domain = doc?.domain;
|
|
@@ -5744,6 +5813,7 @@ function normalizeJobAgentMessageEnvelope(data) {
|
|
|
5744
5813
|
}
|
|
5745
5814
|
return {
|
|
5746
5815
|
jobId: d.jobId,
|
|
5816
|
+
...typeof d.turnId === "string" && d.turnId.trim() ? { turnId: d.turnId.trim() } : {},
|
|
5747
5817
|
message: {
|
|
5748
5818
|
messageId: d.messageId,
|
|
5749
5819
|
kind: d.kind === "artifacts" ? "artifacts" : "text",
|
|
@@ -6402,9 +6472,14 @@ function normalizeShowRefs(value) {
|
|
|
6402
6472
|
variableNames: normalizeRefs(record.variableNames),
|
|
6403
6473
|
fileIds: normalizeRefs(record.fileIds),
|
|
6404
6474
|
sessionArtifactIds: normalizeRefs(record.sessionArtifactIds),
|
|
6405
|
-
actionSuggestions: normalizeActionSuggestions(record.actionSuggestions)
|
|
6475
|
+
actionSuggestions: normalizeActionSuggestions(record.actionSuggestions),
|
|
6476
|
+
tables: Array.isArray(record.tables) ? record.tables.filter(
|
|
6477
|
+
(table) => Boolean(
|
|
6478
|
+
table && typeof table === "object" && !Array.isArray(table) && Array.isArray(table.columns) && Array.isArray(table.rows)
|
|
6479
|
+
)
|
|
6480
|
+
) : void 0
|
|
6406
6481
|
};
|
|
6407
|
-
return show.entryPaths || show.listNames || show.variableNames || show.fileIds || show.sessionArtifactIds || show.actionSuggestions ? show : void 0;
|
|
6482
|
+
return show.entryPaths || show.listNames || show.variableNames || show.fileIds || show.sessionArtifactIds || show.actionSuggestions || show.tables ? show : void 0;
|
|
6408
6483
|
}
|
|
6409
6484
|
function normalizeActionSuggestions(value) {
|
|
6410
6485
|
if (!Array.isArray(value)) return void 0;
|
|
@@ -6426,6 +6501,76 @@ function normalizeActionSuggestions(value) {
|
|
|
6426
6501
|
}
|
|
6427
6502
|
return suggestions.length ? suggestions : void 0;
|
|
6428
6503
|
}
|
|
6504
|
+
var TRANSCRIPT_MESSAGE_PART_LIMIT = 128;
|
|
6505
|
+
var TRANSCRIPT_MESSAGE_PART_TEXT_LIMIT = 2e5;
|
|
6506
|
+
var TRANSCRIPT_MESSAGE_PART_ACTION_LABEL_LIMIT = 1e3;
|
|
6507
|
+
var TRANSCRIPT_MESSAGE_ACTION_LIMIT = 64;
|
|
6508
|
+
function normalizeConversationMessageActions(value) {
|
|
6509
|
+
if (!Array.isArray(value) || value.length === 0) return void 0;
|
|
6510
|
+
const actions = [];
|
|
6511
|
+
for (const item of value.slice(0, TRANSCRIPT_MESSAGE_ACTION_LIMIT)) {
|
|
6512
|
+
const record = asRecord3(item);
|
|
6513
|
+
const kind = record?.kind;
|
|
6514
|
+
const label = trimString(record?.label ?? record?.title);
|
|
6515
|
+
const status = record?.status;
|
|
6516
|
+
if (kind !== "frontend" && kind !== "backend" && kind !== "system" || !label || label.length > TRANSCRIPT_MESSAGE_PART_ACTION_LABEL_LIMIT) {
|
|
6517
|
+
continue;
|
|
6518
|
+
}
|
|
6519
|
+
actions.push({
|
|
6520
|
+
kind,
|
|
6521
|
+
label,
|
|
6522
|
+
...status === "done" || status === "queued" || status === "failed" ? { status } : {}
|
|
6523
|
+
});
|
|
6524
|
+
}
|
|
6525
|
+
return actions.length ? actions : void 0;
|
|
6526
|
+
}
|
|
6527
|
+
function normalizeConversationMessageParts(value, canonicalContent, canonicalActions) {
|
|
6528
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > TRANSCRIPT_MESSAGE_PART_LIMIT) {
|
|
6529
|
+
return void 0;
|
|
6530
|
+
}
|
|
6531
|
+
const parts = [];
|
|
6532
|
+
const canonicalActionsById = new Map(
|
|
6533
|
+
(canonicalActions || []).map((action) => [
|
|
6534
|
+
`${action.kind}:${action.label}`,
|
|
6535
|
+
action
|
|
6536
|
+
])
|
|
6537
|
+
);
|
|
6538
|
+
const seenActionIds = /* @__PURE__ */ new Set();
|
|
6539
|
+
let textLength = 0;
|
|
6540
|
+
for (const item of value) {
|
|
6541
|
+
const record = asRecord3(item);
|
|
6542
|
+
if (!record) return void 0;
|
|
6543
|
+
if (record.type === "text") {
|
|
6544
|
+
if (typeof record.text !== "string" || record.text.length === 0) {
|
|
6545
|
+
return void 0;
|
|
6546
|
+
}
|
|
6547
|
+
textLength += record.text.length;
|
|
6548
|
+
if (textLength > TRANSCRIPT_MESSAGE_PART_TEXT_LIMIT) return void 0;
|
|
6549
|
+
parts.push({ type: "text", text: record.text });
|
|
6550
|
+
continue;
|
|
6551
|
+
}
|
|
6552
|
+
if (record.type !== "action") return void 0;
|
|
6553
|
+
const action = asRecord3(record.action);
|
|
6554
|
+
const kind = action?.kind;
|
|
6555
|
+
const label = trimString(action?.label);
|
|
6556
|
+
if (kind !== "frontend" && kind !== "backend" && kind !== "system" || !label || label.length > TRANSCRIPT_MESSAGE_PART_ACTION_LABEL_LIMIT) {
|
|
6557
|
+
return void 0;
|
|
6558
|
+
}
|
|
6559
|
+
const actionId = `${kind}:${label}`;
|
|
6560
|
+
const canonicalAction = canonicalActionsById.get(actionId);
|
|
6561
|
+
if (!canonicalAction) return void 0;
|
|
6562
|
+
if (seenActionIds.has(actionId)) continue;
|
|
6563
|
+
seenActionIds.add(actionId);
|
|
6564
|
+
parts.push({
|
|
6565
|
+
type: "action",
|
|
6566
|
+
action: canonicalAction
|
|
6567
|
+
});
|
|
6568
|
+
}
|
|
6569
|
+
const orderedText = parts.filter(
|
|
6570
|
+
(part) => part.type === "text"
|
|
6571
|
+
).map((part) => part.text).join("");
|
|
6572
|
+
return orderedText === canonicalContent ? parts : void 0;
|
|
6573
|
+
}
|
|
6429
6574
|
function stringifyTranscriptValue(value, fallback = "") {
|
|
6430
6575
|
if (typeof value === "string") {
|
|
6431
6576
|
return value.trim() || fallback;
|
|
@@ -6584,10 +6729,12 @@ function normalizeConversationMessage(raw, artifactsById) {
|
|
|
6584
6729
|
const content = trimString(
|
|
6585
6730
|
record.content ?? record.reply ?? record.message ?? record.text
|
|
6586
6731
|
);
|
|
6732
|
+
const actions = role === "assistant" ? normalizeConversationMessageActions(record.actions) : void 0;
|
|
6733
|
+
const parts = role === "assistant" ? normalizeConversationMessageParts(record.parts, content, actions) : void 0;
|
|
6587
6734
|
const show = normalizeShowRefs(record.show);
|
|
6588
6735
|
const id = asString(record.id) || crypto.randomUUID();
|
|
6589
6736
|
const timestamp = asNumber(record.timestamp) || asNumber(record.ts) || 0;
|
|
6590
|
-
if (!content && !show) return null;
|
|
6737
|
+
if (!content && !show && !actions?.length) return null;
|
|
6591
6738
|
const artifactHistory = buildArtifactHistory(show, artifactsById);
|
|
6592
6739
|
const historyContent = role === "assistant" ? content && artifactHistory ? `[Assistant reply]
|
|
6593
6740
|
${content}
|
|
@@ -6602,6 +6749,8 @@ ${content}` : artifactHistory : void 0;
|
|
|
6602
6749
|
jobId: asString(record.jobId),
|
|
6603
6750
|
promptId: asString(record.promptId),
|
|
6604
6751
|
show,
|
|
6752
|
+
actions,
|
|
6753
|
+
parts,
|
|
6605
6754
|
historyContent,
|
|
6606
6755
|
source: "conversation"
|
|
6607
6756
|
};
|
|
@@ -12179,7 +12328,12 @@ async function recordOpenAIUsageSpend(options) {
|
|
|
12179
12328
|
const metadata = {
|
|
12180
12329
|
...options.metadata || {},
|
|
12181
12330
|
...options.usage.rawUsage !== void 0 ? { openaiUsage: options.usage.rawUsage } : {},
|
|
12182
|
-
usageContext: context
|
|
12331
|
+
usageContext: context,
|
|
12332
|
+
pricingContextTier: options.usage.pricingContextTier,
|
|
12333
|
+
cacheWritePricePerMillionMicros: options.usage.cacheWritePricePerMillionMicros,
|
|
12334
|
+
cacheWriteTokens: options.usage.cacheWriteTokens,
|
|
12335
|
+
cacheWriteCostMicros: options.usage.cacheWriteCostMicros,
|
|
12336
|
+
longContextThresholdTokens: options.usage.longContextThresholdTokens
|
|
12183
12337
|
};
|
|
12184
12338
|
const response = await fetch(
|
|
12185
12339
|
`${toGranularHttpBase(options.apiUrl)}/control/spend/events`,
|
|
@@ -13107,11 +13261,20 @@ function buildStateMachineModelMutations(modelPath, machines) {
|
|
|
13107
13261
|
return mutations;
|
|
13108
13262
|
}
|
|
13109
13263
|
function buildMachineTypes(classSummary, machine) {
|
|
13264
|
+
const stateGlossary = machine.states.map((state) => {
|
|
13265
|
+
const label = state.label && state.label !== state.name ? state.label : null;
|
|
13266
|
+
const meaning = [label, state.description].filter(Boolean).join(" \u2014 ");
|
|
13267
|
+
const finalMarker = state.isFinal ? " Final state." : "";
|
|
13268
|
+
return `${state.name}${meaning ? `: ${meaning}` : "."}${finalMarker}`;
|
|
13269
|
+
});
|
|
13110
13270
|
return [
|
|
13111
13271
|
{
|
|
13112
13272
|
kind: "union",
|
|
13113
13273
|
name: stateTypeName(classSummary.name, machine.name),
|
|
13114
|
-
docs: [
|
|
13274
|
+
docs: [
|
|
13275
|
+
`Allowed states for ${classSummary.name}.${machine.name}.`,
|
|
13276
|
+
...stateGlossary
|
|
13277
|
+
],
|
|
13115
13278
|
members: machine.states.map((state) => state.name)
|
|
13116
13279
|
},
|
|
13117
13280
|
{
|
|
@@ -13459,7 +13622,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
|
|
|
13459
13622
|
},
|
|
13460
13623
|
add_transition: async (value, {
|
|
13461
13624
|
name,
|
|
13462
|
-
from,
|
|
13625
|
+
from: from2,
|
|
13463
13626
|
to,
|
|
13464
13627
|
label,
|
|
13465
13628
|
description,
|
|
@@ -13474,7 +13637,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
|
|
|
13474
13637
|
value.target.add_state_machine_transition(
|
|
13475
13638
|
value.name,
|
|
13476
13639
|
name,
|
|
13477
|
-
|
|
13640
|
+
from2,
|
|
13478
13641
|
to,
|
|
13479
13642
|
{
|
|
13480
13643
|
label,
|
|
@@ -13826,6 +13989,18 @@ function buildEffectMetamodelMutations(toolPath, spec) {
|
|
|
13826
13989
|
}
|
|
13827
13990
|
|
|
13828
13991
|
// src/client.ts
|
|
13992
|
+
var DEFAULT_CONVERSATION_SESSION_LIST_LIMIT = 100;
|
|
13993
|
+
var MAX_CONVERSATION_SESSION_LIST_LIMIT = 500;
|
|
13994
|
+
var MAX_CONVERSATION_SESSION_LIST_OFFSET = 1e5;
|
|
13995
|
+
function boundedSessionListInteger(value, name, fallback, minimum, maximum) {
|
|
13996
|
+
if (value === void 0) return fallback;
|
|
13997
|
+
if (!Number.isInteger(value) || value < minimum || value > maximum) {
|
|
13998
|
+
throw new RangeError(
|
|
13999
|
+
`Session list ${name} must be an integer between ${minimum} and ${maximum}.`
|
|
14000
|
+
);
|
|
14001
|
+
}
|
|
14002
|
+
return value;
|
|
14003
|
+
}
|
|
13829
14004
|
var STANDARD_MODULES_OPERATIONS = [
|
|
13830
14005
|
{
|
|
13831
14006
|
create: "entity",
|
|
@@ -14043,7 +14218,8 @@ function normalizeEnvironmentSetupSummary(setup) {
|
|
|
14043
14218
|
environmentId: String(setup.environmentId || ""),
|
|
14044
14219
|
sandboxId: String(setup.sandboxId || ""),
|
|
14045
14220
|
subjectId: String(setup.subjectId || ""),
|
|
14046
|
-
triggerReason: setup.triggerReason === "fresh_after_version_update" ? "fresh_after_version_update" : "new_environment",
|
|
14221
|
+
triggerReason: setup.triggerReason === "fresh_after_version_update" ? "fresh_after_version_update" : setup.triggerReason === "explicit_reset" ? "explicit_reset" : "new_environment",
|
|
14222
|
+
operationKey: typeof setup.operationKey === "string" ? setup.operationKey : null,
|
|
14047
14223
|
lifecycleStatus: setup.lifecycleStatus === "completed" || setup.lifecycleStatus === "failed" ? setup.lifecycleStatus : "running",
|
|
14048
14224
|
stage: typeof setup.stage === "string" ? setup.stage : null,
|
|
14049
14225
|
totalObjectsToImport: Number(setup.totalObjectsToImport || 0),
|
|
@@ -14164,7 +14340,7 @@ var Environment = class _Environment {
|
|
|
14164
14340
|
}
|
|
14165
14341
|
get sessions() {
|
|
14166
14342
|
return {
|
|
14167
|
-
list: async (options) => this.listSessions(options
|
|
14343
|
+
list: async (options = {}) => this.listSessions(options),
|
|
14168
14344
|
create: async (options) => this.createSession(options),
|
|
14169
14345
|
connect: async (sessionId, options) => this.connectSession(sessionId, options),
|
|
14170
14346
|
reopen: async (sessionId, options) => this.reopenSession(sessionId, options),
|
|
@@ -14299,17 +14475,12 @@ var Environment = class _Environment {
|
|
|
14299
14475
|
*/
|
|
14300
14476
|
async disconnect() {
|
|
14301
14477
|
}
|
|
14302
|
-
async listSessions(
|
|
14303
|
-
|
|
14304
|
-
|
|
14305
|
-
|
|
14306
|
-
|
|
14307
|
-
|
|
14308
|
-
return [...active, ...closed].sort(
|
|
14309
|
-
(left, right) => Date.parse(right.lastSeenAt) - Date.parse(left.lastSeenAt)
|
|
14310
|
-
);
|
|
14311
|
-
}
|
|
14312
|
-
return status === "closed" ? this.granular.listClosedSessions({ environmentId: this.environmentId }) : this.granular.listOpenSessions({ environmentId: this.environmentId });
|
|
14478
|
+
async listSessions(optionsOrStatus = {}) {
|
|
14479
|
+
const options = typeof optionsOrStatus === "string" ? { status: optionsOrStatus } : optionsOrStatus;
|
|
14480
|
+
return this.granular.listSessions({
|
|
14481
|
+
...options,
|
|
14482
|
+
environmentId: this.environmentId
|
|
14483
|
+
});
|
|
14313
14484
|
}
|
|
14314
14485
|
async getUserEnvironmentState(options = {}) {
|
|
14315
14486
|
return this.granular.getUserEnvironmentState({
|
|
@@ -14327,6 +14498,7 @@ var Environment = class _Environment {
|
|
|
14327
14498
|
return this.granular.createSession({
|
|
14328
14499
|
environmentId: this.environmentId,
|
|
14329
14500
|
clientId: options?.clientId,
|
|
14501
|
+
sessionScope: options?.sessionScope,
|
|
14330
14502
|
initialHeap: options?.initialHeap
|
|
14331
14503
|
});
|
|
14332
14504
|
}
|
|
@@ -15329,6 +15501,7 @@ var Environment = class _Environment {
|
|
|
15329
15501
|
records: recordsToImport,
|
|
15330
15502
|
batchSize: options.batchSize,
|
|
15331
15503
|
setupRunId: options.setupRunId,
|
|
15504
|
+
operationKey: options.operationKey,
|
|
15332
15505
|
writeMode: options.writeMode
|
|
15333
15506
|
})
|
|
15334
15507
|
}
|
|
@@ -15889,7 +16062,7 @@ var EnvironmentSession = class extends Session {
|
|
|
15889
16062
|
* Close only the socket transport without sending `client.goodbye`.
|
|
15890
16063
|
*/
|
|
15891
16064
|
disconnectTransport() {
|
|
15892
|
-
this.client.disconnect();
|
|
16065
|
+
this.client.disconnect({ reason: "Transport detach" });
|
|
15893
16066
|
}
|
|
15894
16067
|
/**
|
|
15895
16068
|
* Backwards-compatible alias for `disconnect()`.
|
|
@@ -16133,6 +16306,107 @@ var Granular = class _Granular {
|
|
|
16133
16306
|
await this.maybeRunEnvironmentImporter(resolved, environment);
|
|
16134
16307
|
return environment;
|
|
16135
16308
|
}
|
|
16309
|
+
/**
|
|
16310
|
+
* Read the active environment selected by Granular for an already-recorded
|
|
16311
|
+
* external user. This is intentionally read-only: browser/login code must
|
|
16312
|
+
* not create subjects or environments as a side effect.
|
|
16313
|
+
*/
|
|
16314
|
+
async getActiveEnvironmentForUser(options) {
|
|
16315
|
+
const sandboxId = options.sandboxId.trim();
|
|
16316
|
+
const tagName = options.tag.trim();
|
|
16317
|
+
const userId = options.userId.trim();
|
|
16318
|
+
if (!sandboxId || !tagName || !userId) {
|
|
16319
|
+
throw new Error(
|
|
16320
|
+
"getActiveEnvironmentForUser() requires sandboxId, tag, and userId."
|
|
16321
|
+
);
|
|
16322
|
+
}
|
|
16323
|
+
const subjects = await this.request(
|
|
16324
|
+
`/control/subjects?identityId=${encodeURIComponent(userId)}`
|
|
16325
|
+
);
|
|
16326
|
+
const subject = (subjects.items || []).find(
|
|
16327
|
+
(item) => item.identityId === userId || item.userId === userId
|
|
16328
|
+
);
|
|
16329
|
+
if (!subject?.subjectId && !subject?.granularId) {
|
|
16330
|
+
return null;
|
|
16331
|
+
}
|
|
16332
|
+
const subjectId = subject.subjectId || subject.granularId;
|
|
16333
|
+
const tags = await this.request(
|
|
16334
|
+
`/control/sandboxes/${encodeURIComponent(sandboxId)}/tags`
|
|
16335
|
+
);
|
|
16336
|
+
const tag = (tags.items || []).find(
|
|
16337
|
+
(item) => item?.name === tagName
|
|
16338
|
+
);
|
|
16339
|
+
if (!tag) return null;
|
|
16340
|
+
const query = new URLSearchParams({
|
|
16341
|
+
tagId: tag.tagId,
|
|
16342
|
+
slot: options.slot?.trim() || "default"
|
|
16343
|
+
});
|
|
16344
|
+
try {
|
|
16345
|
+
const payload = await this.request(
|
|
16346
|
+
`/control/sandboxes/${encodeURIComponent(sandboxId)}/subjects/${encodeURIComponent(subjectId)}/active-environment?${query.toString()}`
|
|
16347
|
+
);
|
|
16348
|
+
return payload.environment ? this.bindEnvironmentHandle(
|
|
16349
|
+
normalizeEnvironmentData(payload.environment)
|
|
16350
|
+
) : null;
|
|
16351
|
+
} catch (error) {
|
|
16352
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16353
|
+
if (message.includes("404") || message.includes("not found")) {
|
|
16354
|
+
return null;
|
|
16355
|
+
}
|
|
16356
|
+
throw error;
|
|
16357
|
+
}
|
|
16358
|
+
}
|
|
16359
|
+
/**
|
|
16360
|
+
* Register one reviewed, pre-existing environment as the active workspace
|
|
16361
|
+
* for an external user. This is for a controlled migration only: it does
|
|
16362
|
+
* not create an environment and it does not run an importer.
|
|
16363
|
+
*/
|
|
16364
|
+
async adoptEnvironmentForUser(options) {
|
|
16365
|
+
const sandboxId = options.sandboxId.trim();
|
|
16366
|
+
const tagName = options.tag.trim();
|
|
16367
|
+
const userId = options.userId.trim();
|
|
16368
|
+
const environmentId = options.environmentId.trim();
|
|
16369
|
+
if (!sandboxId || !tagName || !userId || !environmentId) {
|
|
16370
|
+
throw new Error(
|
|
16371
|
+
"adoptEnvironmentForUser() requires sandboxId, tag, userId, and environmentId."
|
|
16372
|
+
);
|
|
16373
|
+
}
|
|
16374
|
+
const subjects = await this.request(
|
|
16375
|
+
`/control/subjects?identityId=${encodeURIComponent(userId)}`
|
|
16376
|
+
);
|
|
16377
|
+
const subject = (subjects.items || []).find(
|
|
16378
|
+
(item) => item.identityId === userId || item.userId === userId
|
|
16379
|
+
);
|
|
16380
|
+
if (!subject?.subjectId && !subject?.granularId) {
|
|
16381
|
+
throw new Error(`No Granular subject exists for user ${userId}.`);
|
|
16382
|
+
}
|
|
16383
|
+
const tags = await this.request(`/control/sandboxes/${encodeURIComponent(sandboxId)}/tags`);
|
|
16384
|
+
const tag = (tags.items || []).find(
|
|
16385
|
+
(item) => item?.name === tagName
|
|
16386
|
+
);
|
|
16387
|
+
if (!tag) {
|
|
16388
|
+
throw new Error(`Tag ${tagName} was not found in sandbox ${sandboxId}.`);
|
|
16389
|
+
}
|
|
16390
|
+
const payload = await this.request(
|
|
16391
|
+
"/control/environment-activations/adopt",
|
|
16392
|
+
{
|
|
16393
|
+
method: "POST",
|
|
16394
|
+
body: JSON.stringify({
|
|
16395
|
+
environmentId,
|
|
16396
|
+
subjectId: subject.subjectId || subject.granularId,
|
|
16397
|
+
tagId: tag.tagId,
|
|
16398
|
+
slot: options.slot?.trim() || "default",
|
|
16399
|
+
confirmExistingData: true
|
|
16400
|
+
})
|
|
16401
|
+
}
|
|
16402
|
+
);
|
|
16403
|
+
if (!payload.environment) {
|
|
16404
|
+
throw new Error("Granular did not return the adopted environment.");
|
|
16405
|
+
}
|
|
16406
|
+
return this.bindEnvironmentHandle(
|
|
16407
|
+
normalizeEnvironmentData(payload.environment)
|
|
16408
|
+
);
|
|
16409
|
+
}
|
|
16136
16410
|
/**
|
|
16137
16411
|
* Deprecated compatibility alias for `openEnvironment()`.
|
|
16138
16412
|
*
|
|
@@ -16164,7 +16438,9 @@ var Granular = class _Granular {
|
|
|
16164
16438
|
requestedOntology,
|
|
16165
16439
|
sandboxId: environmentData.sandboxId,
|
|
16166
16440
|
subjectId: environmentData.subjectId,
|
|
16167
|
-
|
|
16441
|
+
externalUserId: environmentData.subjectId,
|
|
16442
|
+
setupTriggerReason: options.reason || "new_environment",
|
|
16443
|
+
setupOperationKey: options.operationKey
|
|
16168
16444
|
},
|
|
16169
16445
|
environment
|
|
16170
16446
|
);
|
|
@@ -16176,20 +16452,17 @@ var Granular = class _Granular {
|
|
|
16176
16452
|
}
|
|
16177
16453
|
return tag;
|
|
16178
16454
|
}
|
|
16179
|
-
buildManagedEnvironmentName(tag, versionId) {
|
|
16180
|
-
|
|
16455
|
+
buildManagedEnvironmentName(tag, versionId, resetKey) {
|
|
16456
|
+
if (!resetKey) {
|
|
16457
|
+
return `__sdk__${tag}__${versionId}__tracked`;
|
|
16458
|
+
}
|
|
16459
|
+
const safeResetKey = resetKey.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 80);
|
|
16460
|
+
return `__sdk__${tag}__${versionId}__reset__${safeResetKey}`;
|
|
16181
16461
|
}
|
|
16182
16462
|
isManagedEnvironmentName(environment, tagName) {
|
|
16183
16463
|
const name = environment.environment || environment.envName || "";
|
|
16184
16464
|
return name.startsWith(`__sdk__${tagName}__`);
|
|
16185
16465
|
}
|
|
16186
|
-
isPinnedToVersion(environment, versionId) {
|
|
16187
|
-
return environment.buildPolicy.mode === "pinned" && (environment.versionId === versionId || environment.buildPolicy.versionId === versionId || environment.buildPolicy.buildId === versionId);
|
|
16188
|
-
}
|
|
16189
|
-
matchesTagTrackedEnvironment(environment, tagName, tagId) {
|
|
16190
|
-
const environmentTagName = environment.tag?.name || environment.buildPolicy.tagName || null;
|
|
16191
|
-
return environment.tagId === tagId || environmentTagName === tagName || environment.environment === tagName || environment.envName === tagName || environment.environment === this.buildManagedEnvironmentName(tagName, environment.versionId) || environment.envName === this.buildManagedEnvironmentName(tagName, environment.versionId);
|
|
16192
|
-
}
|
|
16193
16466
|
sortEnvironmentsByRecency(environments) {
|
|
16194
16467
|
return [...environments].sort(
|
|
16195
16468
|
(left, right) => right.updatedAt - left.updatedAt
|
|
@@ -16239,61 +16512,187 @@ var Granular = class _Granular {
|
|
|
16239
16512
|
`Tag "${tagName}" does not currently point to a build/version.`
|
|
16240
16513
|
);
|
|
16241
16514
|
}
|
|
16515
|
+
const slot = options.slot?.trim() || "default";
|
|
16516
|
+
const resetKey = options.resetKey?.trim() || void 0;
|
|
16517
|
+
const resolveActive = async (operationKey) => {
|
|
16518
|
+
const query = new URLSearchParams({ tagId: tag.tagId, slot });
|
|
16519
|
+
if (operationKey) query.set("operationKey", operationKey);
|
|
16520
|
+
try {
|
|
16521
|
+
const payload = await this.request(
|
|
16522
|
+
`/control/sandboxes/${encodeURIComponent(sandbox.sandboxId)}/subjects/${encodeURIComponent(user.granularId)}/active-environment?${query.toString()}`
|
|
16523
|
+
);
|
|
16524
|
+
return payload.environment ? normalizeEnvironmentData(payload.environment) : null;
|
|
16525
|
+
} catch (error) {
|
|
16526
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16527
|
+
if (message.includes("404") || message.includes("not found")) {
|
|
16528
|
+
return null;
|
|
16529
|
+
}
|
|
16530
|
+
throw error;
|
|
16531
|
+
}
|
|
16532
|
+
};
|
|
16533
|
+
const activate = async (environment2) => {
|
|
16534
|
+
const payload = await this.request(
|
|
16535
|
+
"/control/environment-activations",
|
|
16536
|
+
{
|
|
16537
|
+
method: "POST",
|
|
16538
|
+
body: JSON.stringify({
|
|
16539
|
+
environmentId: environment2.environmentId,
|
|
16540
|
+
tagId: tag.tagId,
|
|
16541
|
+
slot,
|
|
16542
|
+
operationKey: resetKey,
|
|
16543
|
+
operation: resetKey ? "explicit_reset" : void 0
|
|
16544
|
+
})
|
|
16545
|
+
}
|
|
16546
|
+
);
|
|
16547
|
+
return normalizeEnvironmentData(payload.environment);
|
|
16548
|
+
};
|
|
16549
|
+
if (resetKey) {
|
|
16550
|
+
const resetEnvironment = await resolveActive(resetKey);
|
|
16551
|
+
if (resetEnvironment) {
|
|
16552
|
+
return {
|
|
16553
|
+
environment: resetEnvironment,
|
|
16554
|
+
requestedOntology: ontology,
|
|
16555
|
+
sandboxId: sandbox.sandboxId,
|
|
16556
|
+
subjectId: user.granularId,
|
|
16557
|
+
externalUserId: user.userId,
|
|
16558
|
+
// Retrying an explicit reset must also resume its durable setup run.
|
|
16559
|
+
// Otherwise a Container crash after queue submission would leave a
|
|
16560
|
+
// valid environment permanently marked as "running".
|
|
16561
|
+
setupTriggerReason: "explicit_reset",
|
|
16562
|
+
setupOperationKey: resetKey
|
|
16563
|
+
};
|
|
16564
|
+
}
|
|
16565
|
+
} else {
|
|
16566
|
+
const active = await resolveActive();
|
|
16567
|
+
if (active && (active.versionId === targetVersionId || options.createFreshIfOutdated !== true)) {
|
|
16568
|
+
return {
|
|
16569
|
+
environment: active,
|
|
16570
|
+
requestedOntology: ontology,
|
|
16571
|
+
sandboxId: sandbox.sandboxId,
|
|
16572
|
+
subjectId: user.granularId,
|
|
16573
|
+
externalUserId: user.userId
|
|
16574
|
+
};
|
|
16575
|
+
}
|
|
16576
|
+
}
|
|
16242
16577
|
const allEnvironments = await this.environments.list(sandbox.sandboxId);
|
|
16243
16578
|
const userEnvironments = allEnvironments.filter(
|
|
16244
|
-
(
|
|
16579
|
+
(environment2) => environment2.subjectId === user.granularId
|
|
16245
16580
|
);
|
|
16246
16581
|
const currentMatches = this.sortEnvironmentsByRecency(
|
|
16247
16582
|
userEnvironments.filter(
|
|
16248
|
-
(
|
|
16583
|
+
(environment2) => environment2.tagId === tag.tagId && environment2.versionId === targetVersionId
|
|
16249
16584
|
)
|
|
16250
16585
|
);
|
|
16251
|
-
if (currentMatches.length > 0) {
|
|
16586
|
+
if (!resetKey && currentMatches.length > 0) {
|
|
16587
|
+
const environment2 = await activate(currentMatches[0]);
|
|
16252
16588
|
return {
|
|
16253
|
-
environment:
|
|
16589
|
+
environment: environment2,
|
|
16254
16590
|
requestedOntology: ontology,
|
|
16255
16591
|
sandboxId: sandbox.sandboxId,
|
|
16256
|
-
subjectId: user.granularId
|
|
16592
|
+
subjectId: user.granularId,
|
|
16593
|
+
externalUserId: user.userId
|
|
16257
16594
|
};
|
|
16258
16595
|
}
|
|
16259
16596
|
const outdatedMatches = this.sortEnvironmentsByRecency(
|
|
16260
|
-
userEnvironments.filter(
|
|
16261
|
-
(environment) => this.matchesTagTrackedEnvironment(environment, tagName, tag.tagId)
|
|
16262
|
-
)
|
|
16597
|
+
userEnvironments.filter((environment2) => environment2.tagId === tag.tagId)
|
|
16263
16598
|
);
|
|
16264
16599
|
if (outdatedMatches.length > 0 && options.createFreshIfOutdated !== true) {
|
|
16600
|
+
const environment2 = await activate(outdatedMatches[0]);
|
|
16265
16601
|
return {
|
|
16266
|
-
environment:
|
|
16602
|
+
environment: environment2,
|
|
16267
16603
|
requestedOntology: ontology,
|
|
16268
16604
|
sandboxId: sandbox.sandboxId,
|
|
16269
|
-
subjectId: user.granularId
|
|
16605
|
+
subjectId: user.granularId,
|
|
16606
|
+
externalUserId: user.userId
|
|
16270
16607
|
};
|
|
16271
16608
|
}
|
|
16609
|
+
const created = await this.environments.create(sandbox.sandboxId, {
|
|
16610
|
+
subjectId: user.granularId,
|
|
16611
|
+
environment: this.buildManagedEnvironmentName(
|
|
16612
|
+
tagName,
|
|
16613
|
+
targetVersionId,
|
|
16614
|
+
resetKey
|
|
16615
|
+
),
|
|
16616
|
+
tagId: tag.tagId,
|
|
16617
|
+
permissionProfileId: null
|
|
16618
|
+
});
|
|
16619
|
+
const environment = await activate(created);
|
|
16272
16620
|
return {
|
|
16273
|
-
environment
|
|
16274
|
-
subjectId: user.granularId,
|
|
16275
|
-
environment: this.buildManagedEnvironmentName(tagName, targetVersionId),
|
|
16276
|
-
tagId: tag.tagId,
|
|
16277
|
-
versionId: targetVersionId,
|
|
16278
|
-
permissionProfileId: null
|
|
16279
|
-
}),
|
|
16621
|
+
environment,
|
|
16280
16622
|
requestedOntology: ontology,
|
|
16281
16623
|
sandboxId: sandbox.sandboxId,
|
|
16282
16624
|
subjectId: user.granularId,
|
|
16283
|
-
|
|
16625
|
+
externalUserId: user.userId,
|
|
16626
|
+
setupTriggerReason: resetKey ? "explicit_reset" : outdatedMatches.length > 0 ? "fresh_after_version_update" : "new_environment",
|
|
16627
|
+
setupOperationKey: resetKey
|
|
16284
16628
|
};
|
|
16285
16629
|
}
|
|
16286
16630
|
/**
|
|
16287
|
-
* List
|
|
16631
|
+
* List indexed sessions using ownership filters and bounded pagination.
|
|
16632
|
+
*/
|
|
16633
|
+
async listSessions(options) {
|
|
16634
|
+
const environmentId = options.environmentId?.trim();
|
|
16635
|
+
const sandboxId = options.sandboxId?.trim();
|
|
16636
|
+
const subjectId = options.subjectId?.trim();
|
|
16637
|
+
if (!environmentId && !sandboxId && !subjectId) {
|
|
16638
|
+
throw new Error(
|
|
16639
|
+
"listSessions() requires environmentId, sandboxId, or subjectId so history cannot be scanned accidentally."
|
|
16640
|
+
);
|
|
16641
|
+
}
|
|
16642
|
+
const status = options.status || "active";
|
|
16643
|
+
const allowedStatuses = /* @__PURE__ */ new Set([
|
|
16644
|
+
"active",
|
|
16645
|
+
"closed",
|
|
16646
|
+
"expired",
|
|
16647
|
+
"failed",
|
|
16648
|
+
"timeout",
|
|
16649
|
+
"all"
|
|
16650
|
+
]);
|
|
16651
|
+
if (!allowedStatuses.has(status)) {
|
|
16652
|
+
throw new Error(`Unsupported session status: ${String(status)}`);
|
|
16653
|
+
}
|
|
16654
|
+
const limit = boundedSessionListInteger(
|
|
16655
|
+
options.limit,
|
|
16656
|
+
"limit",
|
|
16657
|
+
DEFAULT_CONVERSATION_SESSION_LIST_LIMIT,
|
|
16658
|
+
1,
|
|
16659
|
+
MAX_CONVERSATION_SESSION_LIST_LIMIT
|
|
16660
|
+
);
|
|
16661
|
+
const offset = boundedSessionListInteger(
|
|
16662
|
+
options.offset,
|
|
16663
|
+
"offset",
|
|
16664
|
+
0,
|
|
16665
|
+
0,
|
|
16666
|
+
MAX_CONVERSATION_SESSION_LIST_OFFSET
|
|
16667
|
+
);
|
|
16668
|
+
const query = new URLSearchParams({
|
|
16669
|
+
limit: String(limit),
|
|
16670
|
+
offset: String(offset)
|
|
16671
|
+
});
|
|
16672
|
+
if (environmentId) query.set("environmentId", environmentId);
|
|
16673
|
+
if (sandboxId) query.set("sandboxId", sandboxId);
|
|
16674
|
+
if (subjectId) query.set("userId", subjectId);
|
|
16675
|
+
if (options.sessionScope?.trim()) {
|
|
16676
|
+
query.set("sessionScope", options.sessionScope.trim());
|
|
16677
|
+
}
|
|
16678
|
+
if (status !== "all") query.set("status", status);
|
|
16679
|
+
const res = await this.request(
|
|
16680
|
+
`/control/sessions?${query.toString()}`
|
|
16681
|
+
);
|
|
16682
|
+
const items = Array.isArray(res.items) ? res.items : [];
|
|
16683
|
+
return items.map((row) => this.normalizeConversationSession(row));
|
|
16684
|
+
}
|
|
16685
|
+
/**
|
|
16686
|
+
* List active (open) sessions for an environment.
|
|
16288
16687
|
*/
|
|
16289
16688
|
async listOpenSessions(filters) {
|
|
16290
|
-
return this.
|
|
16689
|
+
return this.listSessions({ ...filters, status: "active" });
|
|
16291
16690
|
}
|
|
16292
16691
|
/**
|
|
16293
16692
|
* List closed sessions for an environment (conversations that have disconnected).
|
|
16294
16693
|
*/
|
|
16295
16694
|
async listClosedSessions(filters) {
|
|
16296
|
-
return this.
|
|
16695
|
+
return this.listSessions({ ...filters, status: "closed" });
|
|
16297
16696
|
}
|
|
16298
16697
|
async getUserEnvironmentState(options) {
|
|
16299
16698
|
const query = new URLSearchParams({
|
|
@@ -16328,14 +16727,6 @@ var Granular = class _Granular {
|
|
|
16328
16727
|
});
|
|
16329
16728
|
return result.readAtBySessionId || {};
|
|
16330
16729
|
}
|
|
16331
|
-
async listSessionsForEnvironment(environmentId, status) {
|
|
16332
|
-
const query = new URLSearchParams({ environmentId, status });
|
|
16333
|
-
const res = await this.request(
|
|
16334
|
-
`/control/sessions?${query.toString()}`
|
|
16335
|
-
);
|
|
16336
|
-
const items = Array.isArray(res.items) ? res.items : [];
|
|
16337
|
-
return items.map((row) => this.normalizeConversationSession(row));
|
|
16338
|
-
}
|
|
16339
16730
|
normalizeConversationSession(row) {
|
|
16340
16731
|
const sessionId = String(row.sessionId ?? row.session_id ?? "");
|
|
16341
16732
|
const environmentId = String(row.environmentId ?? row.environment_id ?? "");
|
|
@@ -16394,6 +16785,7 @@ var Granular = class _Granular {
|
|
|
16394
16785
|
*/
|
|
16395
16786
|
async createSession(options) {
|
|
16396
16787
|
const clientId = options.clientId || `client_${Date.now()}`;
|
|
16788
|
+
const sessionScope = options.sessionScope?.trim() || void 0;
|
|
16397
16789
|
await this.activateEnvironment(options.environmentId);
|
|
16398
16790
|
const envData = await this.environments.get(options.environmentId);
|
|
16399
16791
|
const environment = this.bindEnvironmentHandle(envData);
|
|
@@ -16402,6 +16794,8 @@ var Granular = class _Granular {
|
|
|
16402
16794
|
body: JSON.stringify({
|
|
16403
16795
|
environmentId: options.environmentId,
|
|
16404
16796
|
clientId,
|
|
16797
|
+
sessionScope,
|
|
16798
|
+
capabilities: sessionScope ? { sessionScope } : void 0,
|
|
16405
16799
|
initialHeap: options.initialHeap
|
|
16406
16800
|
})
|
|
16407
16801
|
});
|
|
@@ -16509,11 +16903,24 @@ var Granular = class _Granular {
|
|
|
16509
16903
|
{
|
|
16510
16904
|
method: "POST",
|
|
16511
16905
|
body: JSON.stringify({
|
|
16512
|
-
triggerReason: resolved.setupTriggerReason
|
|
16906
|
+
triggerReason: resolved.setupTriggerReason,
|
|
16907
|
+
operationKey: resolved.setupOperationKey
|
|
16513
16908
|
})
|
|
16514
16909
|
}
|
|
16515
16910
|
);
|
|
16516
16911
|
const setupRunId = setupRun.setupRunId;
|
|
16912
|
+
let claim = null;
|
|
16913
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
16914
|
+
claim = await this.request(
|
|
16915
|
+
`/control/environment-setup-runs/${setupRunId}/importer-claim`,
|
|
16916
|
+
{ method: "POST", body: JSON.stringify({}) }
|
|
16917
|
+
);
|
|
16918
|
+
if (claim.action !== "busy") break;
|
|
16919
|
+
await sleep(Math.min(3e4, Math.max(250, claim.retryAfterMs || 1e3)));
|
|
16920
|
+
}
|
|
16921
|
+
if (!claim) {
|
|
16922
|
+
throw new Error(`Unable to claim environment setup run ${setupRunId}.`);
|
|
16923
|
+
}
|
|
16517
16924
|
const updateSetupRun = async (patch) => {
|
|
16518
16925
|
await this.request(
|
|
16519
16926
|
`/control/environment-setup-runs/${setupRunId}`,
|
|
@@ -16523,10 +16930,26 @@ var Granular = class _Granular {
|
|
|
16523
16930
|
}
|
|
16524
16931
|
);
|
|
16525
16932
|
};
|
|
16933
|
+
if (claim.action === "submitted") {
|
|
16934
|
+
const completedSetupRun = await this.request(
|
|
16935
|
+
`/control/environment-setup-runs/${setupRunId}`,
|
|
16936
|
+
{ method: "PATCH", body: JSON.stringify({ markHookCompleted: true }) }
|
|
16937
|
+
);
|
|
16938
|
+
const refreshedEnvironment = await this.environments.get(
|
|
16939
|
+
environment.environmentId
|
|
16940
|
+
);
|
|
16941
|
+
environment.syncEnvironmentData(refreshedEnvironment);
|
|
16942
|
+
return completedSetupRun;
|
|
16943
|
+
}
|
|
16944
|
+
if (claim.action === "busy" || claim.action === "terminal") {
|
|
16945
|
+
return claim.summary;
|
|
16946
|
+
}
|
|
16947
|
+
let importSequence = 0;
|
|
16526
16948
|
const importerContext = {
|
|
16527
16949
|
environmentId: environment.environmentId,
|
|
16528
16950
|
sandboxId: environment.sandboxId,
|
|
16529
16951
|
subjectId: environment.subjectId,
|
|
16952
|
+
externalUserId: resolved.externalUserId,
|
|
16530
16953
|
reason: resolved.setupTriggerReason,
|
|
16531
16954
|
incrementTotalObjectsToImportCount: async (n) => {
|
|
16532
16955
|
const safeIncrement = Math.max(0, Math.trunc(n));
|
|
@@ -16543,7 +16966,10 @@ var Granular = class _Granular {
|
|
|
16543
16966
|
importRecords: async (records, options) => environment.enqueueRecordImport(records, {
|
|
16544
16967
|
batchSize: options?.batchSize,
|
|
16545
16968
|
writeMode: options?.writeMode,
|
|
16546
|
-
setupRunId
|
|
16969
|
+
setupRunId,
|
|
16970
|
+
// Sequence is deterministic for a retry of one importer hook. It
|
|
16971
|
+
// prevents a Container restart from creating a second queue import.
|
|
16972
|
+
operationKey: `${setupRunId}:import:${importSequence++}`
|
|
16547
16973
|
})
|
|
16548
16974
|
};
|
|
16549
16975
|
try {
|
|
@@ -19582,6 +20008,7 @@ function buildGranularAgentSystemPrompt(input) {
|
|
|
19582
20008
|
- \`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(...)\`.
|
|
19583
20009
|
- 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.
|
|
19584
20010
|
- 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()\`.
|
|
20011
|
+
- 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.
|
|
19585
20012
|
- 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.
|
|
19586
20013
|
- 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.
|
|
19587
20014
|
- 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.
|
|
@@ -19852,11 +20279,12 @@ ${actionIndex}
|
|
|
19852
20279
|
- 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.
|
|
19853
20280
|
- 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()\`.
|
|
19854
20281
|
- 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.
|
|
19855
|
-
-
|
|
20282
|
+
- 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.
|
|
20283
|
+
- 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.
|
|
19856
20284
|
- 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.
|
|
19857
20285
|
- 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.
|
|
19858
20286
|
- 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.
|
|
19859
|
-
- Do not ask for confirmation before opening a prepared action
|
|
20287
|
+
- 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.
|
|
19860
20288
|
- 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.
|
|
19861
20289
|
- 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.
|
|
19862
20290
|
- 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.
|
|
@@ -20048,8 +20476,9 @@ function resolveHarnessTemplate(templateId = "stable", options) {
|
|
|
20048
20476
|
}
|
|
20049
20477
|
|
|
20050
20478
|
// src/openai-usage.ts
|
|
20051
|
-
var
|
|
20052
|
-
var
|
|
20479
|
+
var OPENAI_GPT_5_4_PRICING_SOURCE_URL = "https://developers.openai.com/api/docs/models/gpt-5.4/";
|
|
20480
|
+
var OPENAI_GPT_5_6_PRICING_SOURCE_URL = "https://developers.openai.com/api/docs/pricing";
|
|
20481
|
+
var OPENAI_LONG_CONTEXT_THRESHOLD_TOKENS = 272e3;
|
|
20053
20482
|
var OPENAI_MODEL_PRICING_USD_PER_MILLION = {
|
|
20054
20483
|
"gpt-5.4": {
|
|
20055
20484
|
provider: "openai",
|
|
@@ -20058,8 +20487,26 @@ var OPENAI_MODEL_PRICING_USD_PER_MILLION = {
|
|
|
20058
20487
|
inputUsdPerMillion: 2.5,
|
|
20059
20488
|
cachedInputUsdPerMillion: 0.25,
|
|
20060
20489
|
outputUsdPerMillion: 15,
|
|
20061
|
-
sourceUrl:
|
|
20062
|
-
effectiveDate:
|
|
20490
|
+
sourceUrl: OPENAI_GPT_5_4_PRICING_SOURCE_URL,
|
|
20491
|
+
effectiveDate: "2026-05-19"
|
|
20492
|
+
},
|
|
20493
|
+
"gpt-5.6-luna": {
|
|
20494
|
+
provider: "openai",
|
|
20495
|
+
model: "gpt-5.6-luna",
|
|
20496
|
+
currency: "USD",
|
|
20497
|
+
inputUsdPerMillion: 1,
|
|
20498
|
+
cachedInputUsdPerMillion: 0.1,
|
|
20499
|
+
cacheWriteUsdPerMillion: 1.25,
|
|
20500
|
+
outputUsdPerMillion: 6,
|
|
20501
|
+
sourceUrl: OPENAI_GPT_5_6_PRICING_SOURCE_URL,
|
|
20502
|
+
effectiveDate: "2026-07-11",
|
|
20503
|
+
longContextThresholdTokens: OPENAI_LONG_CONTEXT_THRESHOLD_TOKENS,
|
|
20504
|
+
longContextPricing: {
|
|
20505
|
+
inputUsdPerMillion: 2,
|
|
20506
|
+
cachedInputUsdPerMillion: 0.2,
|
|
20507
|
+
cacheWriteUsdPerMillion: 2.5,
|
|
20508
|
+
outputUsdPerMillion: 9
|
|
20509
|
+
}
|
|
20063
20510
|
}
|
|
20064
20511
|
};
|
|
20065
20512
|
function asRecord5(value) {
|
|
@@ -20072,8 +20519,18 @@ function numberField(record, key) {
|
|
|
20072
20519
|
function microsPerMillion(usdPerMillion) {
|
|
20073
20520
|
return Math.round(usdPerMillion * 1e6);
|
|
20074
20521
|
}
|
|
20075
|
-
function getOpenAIModelPricing(model) {
|
|
20076
|
-
|
|
20522
|
+
function getOpenAIModelPricing(model, inputTokens = 0) {
|
|
20523
|
+
const pricing = OPENAI_MODEL_PRICING_USD_PER_MILLION[model];
|
|
20524
|
+
if (!pricing) return null;
|
|
20525
|
+
const threshold = pricing.longContextThresholdTokens ?? null;
|
|
20526
|
+
if (pricing.longContextPricing && typeof threshold === "number" && inputTokens > threshold) {
|
|
20527
|
+
return {
|
|
20528
|
+
...pricing,
|
|
20529
|
+
...pricing.longContextPricing,
|
|
20530
|
+
contextTier: "long"
|
|
20531
|
+
};
|
|
20532
|
+
}
|
|
20533
|
+
return { ...pricing, contextTier: "short" };
|
|
20077
20534
|
}
|
|
20078
20535
|
function normalizeOpenAIUsage(rawUsage) {
|
|
20079
20536
|
const usage = asRecord5(rawUsage);
|
|
@@ -20081,6 +20538,7 @@ function normalizeOpenAIUsage(rawUsage) {
|
|
|
20081
20538
|
return {
|
|
20082
20539
|
inputTokens: 0,
|
|
20083
20540
|
cachedInputTokens: 0,
|
|
20541
|
+
cacheWriteTokens: 0,
|
|
20084
20542
|
uncachedInputTokens: 0,
|
|
20085
20543
|
outputTokens: 0,
|
|
20086
20544
|
reasoningTokens: 0,
|
|
@@ -20096,20 +20554,28 @@ function normalizeOpenAIUsage(rawUsage) {
|
|
|
20096
20554
|
inputTokens,
|
|
20097
20555
|
numberField(inputDetails, "cached_tokens") || numberField(inputDetails, "cached_input_tokens")
|
|
20098
20556
|
);
|
|
20557
|
+
const cacheWriteTokens = Math.min(
|
|
20558
|
+
Math.max(inputTokens - cachedInputTokens, 0),
|
|
20559
|
+
numberField(inputDetails, "cache_write_tokens")
|
|
20560
|
+
);
|
|
20099
20561
|
const reasoningTokens = numberField(outputDetails, "reasoning_tokens") || numberField(outputDetails, "reasoning_output_tokens");
|
|
20100
20562
|
return {
|
|
20101
20563
|
inputTokens,
|
|
20102
20564
|
cachedInputTokens,
|
|
20103
|
-
|
|
20565
|
+
cacheWriteTokens,
|
|
20566
|
+
uncachedInputTokens: Math.max(
|
|
20567
|
+
inputTokens - cachedInputTokens - cacheWriteTokens,
|
|
20568
|
+
0
|
|
20569
|
+
),
|
|
20104
20570
|
outputTokens,
|
|
20105
20571
|
reasoningTokens,
|
|
20106
20572
|
totalTokens
|
|
20107
20573
|
};
|
|
20108
20574
|
}
|
|
20109
20575
|
function calculateOpenAITokenSpend(model, rawUsage) {
|
|
20110
|
-
const pricing = getOpenAIModelPricing(model);
|
|
20111
|
-
if (!pricing) return null;
|
|
20112
20576
|
const usage = normalizeOpenAIUsage(rawUsage);
|
|
20577
|
+
const pricing = getOpenAIModelPricing(model, usage.inputTokens);
|
|
20578
|
+
if (!pricing) return null;
|
|
20113
20579
|
const inputPricePerMillionMicros = microsPerMillion(
|
|
20114
20580
|
pricing.inputUsdPerMillion
|
|
20115
20581
|
);
|
|
@@ -20119,14 +20585,19 @@ function calculateOpenAITokenSpend(model, rawUsage) {
|
|
|
20119
20585
|
const outputPricePerMillionMicros = microsPerMillion(
|
|
20120
20586
|
pricing.outputUsdPerMillion
|
|
20121
20587
|
);
|
|
20588
|
+
const cacheWritePricePerMillionMicros = typeof pricing.cacheWriteUsdPerMillion === "number" ? microsPerMillion(pricing.cacheWriteUsdPerMillion) : null;
|
|
20589
|
+
const cacheWriteCostMicros = Math.round(
|
|
20590
|
+
usage.cacheWriteTokens * (cacheWritePricePerMillionMicros ?? inputPricePerMillionMicros) / 1e6
|
|
20591
|
+
);
|
|
20122
20592
|
const amountMicros = Math.round(
|
|
20123
|
-
(usage.uncachedInputTokens * inputPricePerMillionMicros + usage.cachedInputTokens * cachedInputPricePerMillionMicros + usage.outputTokens * outputPricePerMillionMicros) / 1e6
|
|
20593
|
+
(usage.uncachedInputTokens * inputPricePerMillionMicros + usage.cachedInputTokens * cachedInputPricePerMillionMicros + usage.cacheWriteTokens * (cacheWritePricePerMillionMicros ?? inputPricePerMillionMicros) + usage.outputTokens * outputPricePerMillionMicros) / 1e6
|
|
20124
20594
|
);
|
|
20125
20595
|
return {
|
|
20126
20596
|
provider: "openai",
|
|
20127
20597
|
model,
|
|
20128
20598
|
inputTokens: usage.inputTokens,
|
|
20129
20599
|
cachedInputTokens: usage.cachedInputTokens,
|
|
20600
|
+
cacheWriteTokens: usage.cacheWriteTokens,
|
|
20130
20601
|
uncachedInputTokens: usage.uncachedInputTokens,
|
|
20131
20602
|
outputTokens: usage.outputTokens,
|
|
20132
20603
|
reasoningTokens: usage.reasoningTokens,
|
|
@@ -20135,7 +20606,11 @@ function calculateOpenAITokenSpend(model, rawUsage) {
|
|
|
20135
20606
|
currency: "USD",
|
|
20136
20607
|
inputPricePerMillionMicros,
|
|
20137
20608
|
cachedInputPricePerMillionMicros,
|
|
20609
|
+
cacheWritePricePerMillionMicros,
|
|
20610
|
+
cacheWriteCostMicros,
|
|
20138
20611
|
outputPricePerMillionMicros,
|
|
20612
|
+
pricingContextTier: pricing.contextTier || "short",
|
|
20613
|
+
longContextThresholdTokens: pricing.longContextThresholdTokens ?? null,
|
|
20139
20614
|
pricingSource: pricing.sourceUrl,
|
|
20140
20615
|
pricingEffectiveAt: pricing.effectiveDate,
|
|
20141
20616
|
usage
|