@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.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",
|
|
@@ -14017,7 +14192,8 @@ function normalizeEnvironmentSetupSummary(setup) {
|
|
|
14017
14192
|
environmentId: String(setup.environmentId || ""),
|
|
14018
14193
|
sandboxId: String(setup.sandboxId || ""),
|
|
14019
14194
|
subjectId: String(setup.subjectId || ""),
|
|
14020
|
-
triggerReason: setup.triggerReason === "fresh_after_version_update" ? "fresh_after_version_update" : "new_environment",
|
|
14195
|
+
triggerReason: setup.triggerReason === "fresh_after_version_update" ? "fresh_after_version_update" : setup.triggerReason === "explicit_reset" ? "explicit_reset" : "new_environment",
|
|
14196
|
+
operationKey: typeof setup.operationKey === "string" ? setup.operationKey : null,
|
|
14021
14197
|
lifecycleStatus: setup.lifecycleStatus === "completed" || setup.lifecycleStatus === "failed" ? setup.lifecycleStatus : "running",
|
|
14022
14198
|
stage: typeof setup.stage === "string" ? setup.stage : null,
|
|
14023
14199
|
totalObjectsToImport: Number(setup.totalObjectsToImport || 0),
|
|
@@ -14138,7 +14314,7 @@ var Environment = class _Environment {
|
|
|
14138
14314
|
}
|
|
14139
14315
|
get sessions() {
|
|
14140
14316
|
return {
|
|
14141
|
-
list: async (options) => this.listSessions(options
|
|
14317
|
+
list: async (options = {}) => this.listSessions(options),
|
|
14142
14318
|
create: async (options) => this.createSession(options),
|
|
14143
14319
|
connect: async (sessionId, options) => this.connectSession(sessionId, options),
|
|
14144
14320
|
reopen: async (sessionId, options) => this.reopenSession(sessionId, options),
|
|
@@ -14273,17 +14449,12 @@ var Environment = class _Environment {
|
|
|
14273
14449
|
*/
|
|
14274
14450
|
async disconnect() {
|
|
14275
14451
|
}
|
|
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 });
|
|
14452
|
+
async listSessions(optionsOrStatus = {}) {
|
|
14453
|
+
const options = typeof optionsOrStatus === "string" ? { status: optionsOrStatus } : optionsOrStatus;
|
|
14454
|
+
return this.granular.listSessions({
|
|
14455
|
+
...options,
|
|
14456
|
+
environmentId: this.environmentId
|
|
14457
|
+
});
|
|
14287
14458
|
}
|
|
14288
14459
|
async getUserEnvironmentState(options = {}) {
|
|
14289
14460
|
return this.granular.getUserEnvironmentState({
|
|
@@ -14301,6 +14472,7 @@ var Environment = class _Environment {
|
|
|
14301
14472
|
return this.granular.createSession({
|
|
14302
14473
|
environmentId: this.environmentId,
|
|
14303
14474
|
clientId: options?.clientId,
|
|
14475
|
+
sessionScope: options?.sessionScope,
|
|
14304
14476
|
initialHeap: options?.initialHeap
|
|
14305
14477
|
});
|
|
14306
14478
|
}
|
|
@@ -15303,6 +15475,7 @@ var Environment = class _Environment {
|
|
|
15303
15475
|
records: recordsToImport,
|
|
15304
15476
|
batchSize: options.batchSize,
|
|
15305
15477
|
setupRunId: options.setupRunId,
|
|
15478
|
+
operationKey: options.operationKey,
|
|
15306
15479
|
writeMode: options.writeMode
|
|
15307
15480
|
})
|
|
15308
15481
|
}
|
|
@@ -15863,7 +16036,7 @@ var EnvironmentSession = class extends Session {
|
|
|
15863
16036
|
* Close only the socket transport without sending `client.goodbye`.
|
|
15864
16037
|
*/
|
|
15865
16038
|
disconnectTransport() {
|
|
15866
|
-
this.client.disconnect();
|
|
16039
|
+
this.client.disconnect({ reason: "Transport detach" });
|
|
15867
16040
|
}
|
|
15868
16041
|
/**
|
|
15869
16042
|
* Backwards-compatible alias for `disconnect()`.
|
|
@@ -16107,6 +16280,107 @@ var Granular = class _Granular {
|
|
|
16107
16280
|
await this.maybeRunEnvironmentImporter(resolved, environment);
|
|
16108
16281
|
return environment;
|
|
16109
16282
|
}
|
|
16283
|
+
/**
|
|
16284
|
+
* Read the active environment selected by Granular for an already-recorded
|
|
16285
|
+
* external user. This is intentionally read-only: browser/login code must
|
|
16286
|
+
* not create subjects or environments as a side effect.
|
|
16287
|
+
*/
|
|
16288
|
+
async getActiveEnvironmentForUser(options) {
|
|
16289
|
+
const sandboxId = options.sandboxId.trim();
|
|
16290
|
+
const tagName = options.tag.trim();
|
|
16291
|
+
const userId = options.userId.trim();
|
|
16292
|
+
if (!sandboxId || !tagName || !userId) {
|
|
16293
|
+
throw new Error(
|
|
16294
|
+
"getActiveEnvironmentForUser() requires sandboxId, tag, and userId."
|
|
16295
|
+
);
|
|
16296
|
+
}
|
|
16297
|
+
const subjects = await this.request(
|
|
16298
|
+
`/control/subjects?identityId=${encodeURIComponent(userId)}`
|
|
16299
|
+
);
|
|
16300
|
+
const subject = (subjects.items || []).find(
|
|
16301
|
+
(item) => item.identityId === userId || item.userId === userId
|
|
16302
|
+
);
|
|
16303
|
+
if (!subject?.subjectId && !subject?.granularId) {
|
|
16304
|
+
return null;
|
|
16305
|
+
}
|
|
16306
|
+
const subjectId = subject.subjectId || subject.granularId;
|
|
16307
|
+
const tags = await this.request(
|
|
16308
|
+
`/control/sandboxes/${encodeURIComponent(sandboxId)}/tags`
|
|
16309
|
+
);
|
|
16310
|
+
const tag = (tags.items || []).find(
|
|
16311
|
+
(item) => item?.name === tagName
|
|
16312
|
+
);
|
|
16313
|
+
if (!tag) return null;
|
|
16314
|
+
const query = new URLSearchParams({
|
|
16315
|
+
tagId: tag.tagId,
|
|
16316
|
+
slot: options.slot?.trim() || "default"
|
|
16317
|
+
});
|
|
16318
|
+
try {
|
|
16319
|
+
const payload = await this.request(
|
|
16320
|
+
`/control/sandboxes/${encodeURIComponent(sandboxId)}/subjects/${encodeURIComponent(subjectId)}/active-environment?${query.toString()}`
|
|
16321
|
+
);
|
|
16322
|
+
return payload.environment ? this.bindEnvironmentHandle(
|
|
16323
|
+
normalizeEnvironmentData(payload.environment)
|
|
16324
|
+
) : null;
|
|
16325
|
+
} catch (error) {
|
|
16326
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16327
|
+
if (message.includes("404") || message.includes("not found")) {
|
|
16328
|
+
return null;
|
|
16329
|
+
}
|
|
16330
|
+
throw error;
|
|
16331
|
+
}
|
|
16332
|
+
}
|
|
16333
|
+
/**
|
|
16334
|
+
* Register one reviewed, pre-existing environment as the active workspace
|
|
16335
|
+
* for an external user. This is for a controlled migration only: it does
|
|
16336
|
+
* not create an environment and it does not run an importer.
|
|
16337
|
+
*/
|
|
16338
|
+
async adoptEnvironmentForUser(options) {
|
|
16339
|
+
const sandboxId = options.sandboxId.trim();
|
|
16340
|
+
const tagName = options.tag.trim();
|
|
16341
|
+
const userId = options.userId.trim();
|
|
16342
|
+
const environmentId = options.environmentId.trim();
|
|
16343
|
+
if (!sandboxId || !tagName || !userId || !environmentId) {
|
|
16344
|
+
throw new Error(
|
|
16345
|
+
"adoptEnvironmentForUser() requires sandboxId, tag, userId, and environmentId."
|
|
16346
|
+
);
|
|
16347
|
+
}
|
|
16348
|
+
const subjects = await this.request(
|
|
16349
|
+
`/control/subjects?identityId=${encodeURIComponent(userId)}`
|
|
16350
|
+
);
|
|
16351
|
+
const subject = (subjects.items || []).find(
|
|
16352
|
+
(item) => item.identityId === userId || item.userId === userId
|
|
16353
|
+
);
|
|
16354
|
+
if (!subject?.subjectId && !subject?.granularId) {
|
|
16355
|
+
throw new Error(`No Granular subject exists for user ${userId}.`);
|
|
16356
|
+
}
|
|
16357
|
+
const tags = await this.request(`/control/sandboxes/${encodeURIComponent(sandboxId)}/tags`);
|
|
16358
|
+
const tag = (tags.items || []).find(
|
|
16359
|
+
(item) => item?.name === tagName
|
|
16360
|
+
);
|
|
16361
|
+
if (!tag) {
|
|
16362
|
+
throw new Error(`Tag ${tagName} was not found in sandbox ${sandboxId}.`);
|
|
16363
|
+
}
|
|
16364
|
+
const payload = await this.request(
|
|
16365
|
+
"/control/environment-activations/adopt",
|
|
16366
|
+
{
|
|
16367
|
+
method: "POST",
|
|
16368
|
+
body: JSON.stringify({
|
|
16369
|
+
environmentId,
|
|
16370
|
+
subjectId: subject.subjectId || subject.granularId,
|
|
16371
|
+
tagId: tag.tagId,
|
|
16372
|
+
slot: options.slot?.trim() || "default",
|
|
16373
|
+
confirmExistingData: true
|
|
16374
|
+
})
|
|
16375
|
+
}
|
|
16376
|
+
);
|
|
16377
|
+
if (!payload.environment) {
|
|
16378
|
+
throw new Error("Granular did not return the adopted environment.");
|
|
16379
|
+
}
|
|
16380
|
+
return this.bindEnvironmentHandle(
|
|
16381
|
+
normalizeEnvironmentData(payload.environment)
|
|
16382
|
+
);
|
|
16383
|
+
}
|
|
16110
16384
|
/**
|
|
16111
16385
|
* Deprecated compatibility alias for `openEnvironment()`.
|
|
16112
16386
|
*
|
|
@@ -16138,7 +16412,9 @@ var Granular = class _Granular {
|
|
|
16138
16412
|
requestedOntology,
|
|
16139
16413
|
sandboxId: environmentData.sandboxId,
|
|
16140
16414
|
subjectId: environmentData.subjectId,
|
|
16141
|
-
|
|
16415
|
+
externalUserId: environmentData.subjectId,
|
|
16416
|
+
setupTriggerReason: options.reason || "new_environment",
|
|
16417
|
+
setupOperationKey: options.operationKey
|
|
16142
16418
|
},
|
|
16143
16419
|
environment
|
|
16144
16420
|
);
|
|
@@ -16150,20 +16426,17 @@ var Granular = class _Granular {
|
|
|
16150
16426
|
}
|
|
16151
16427
|
return tag;
|
|
16152
16428
|
}
|
|
16153
|
-
buildManagedEnvironmentName(tag, versionId) {
|
|
16154
|
-
|
|
16429
|
+
buildManagedEnvironmentName(tag, versionId, resetKey) {
|
|
16430
|
+
if (!resetKey) {
|
|
16431
|
+
return `__sdk__${tag}__${versionId}__tracked`;
|
|
16432
|
+
}
|
|
16433
|
+
const safeResetKey = resetKey.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 80);
|
|
16434
|
+
return `__sdk__${tag}__${versionId}__reset__${safeResetKey}`;
|
|
16155
16435
|
}
|
|
16156
16436
|
isManagedEnvironmentName(environment, tagName) {
|
|
16157
16437
|
const name = environment.environment || environment.envName || "";
|
|
16158
16438
|
return name.startsWith(`__sdk__${tagName}__`);
|
|
16159
16439
|
}
|
|
16160
|
-
isPinnedToVersion(environment, versionId) {
|
|
16161
|
-
return environment.buildPolicy.mode === "pinned" && (environment.versionId === versionId || environment.buildPolicy.versionId === versionId || environment.buildPolicy.buildId === versionId);
|
|
16162
|
-
}
|
|
16163
|
-
matchesTagTrackedEnvironment(environment, tagName, tagId) {
|
|
16164
|
-
const environmentTagName = environment.tag?.name || environment.buildPolicy.tagName || null;
|
|
16165
|
-
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);
|
|
16166
|
-
}
|
|
16167
16440
|
sortEnvironmentsByRecency(environments) {
|
|
16168
16441
|
return [...environments].sort(
|
|
16169
16442
|
(left, right) => right.updatedAt - left.updatedAt
|
|
@@ -16213,61 +16486,187 @@ var Granular = class _Granular {
|
|
|
16213
16486
|
`Tag "${tagName}" does not currently point to a build/version.`
|
|
16214
16487
|
);
|
|
16215
16488
|
}
|
|
16489
|
+
const slot = options.slot?.trim() || "default";
|
|
16490
|
+
const resetKey = options.resetKey?.trim() || void 0;
|
|
16491
|
+
const resolveActive = async (operationKey) => {
|
|
16492
|
+
const query = new URLSearchParams({ tagId: tag.tagId, slot });
|
|
16493
|
+
if (operationKey) query.set("operationKey", operationKey);
|
|
16494
|
+
try {
|
|
16495
|
+
const payload = await this.request(
|
|
16496
|
+
`/control/sandboxes/${encodeURIComponent(sandbox.sandboxId)}/subjects/${encodeURIComponent(user.granularId)}/active-environment?${query.toString()}`
|
|
16497
|
+
);
|
|
16498
|
+
return payload.environment ? normalizeEnvironmentData(payload.environment) : null;
|
|
16499
|
+
} catch (error) {
|
|
16500
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16501
|
+
if (message.includes("404") || message.includes("not found")) {
|
|
16502
|
+
return null;
|
|
16503
|
+
}
|
|
16504
|
+
throw error;
|
|
16505
|
+
}
|
|
16506
|
+
};
|
|
16507
|
+
const activate = async (environment2) => {
|
|
16508
|
+
const payload = await this.request(
|
|
16509
|
+
"/control/environment-activations",
|
|
16510
|
+
{
|
|
16511
|
+
method: "POST",
|
|
16512
|
+
body: JSON.stringify({
|
|
16513
|
+
environmentId: environment2.environmentId,
|
|
16514
|
+
tagId: tag.tagId,
|
|
16515
|
+
slot,
|
|
16516
|
+
operationKey: resetKey,
|
|
16517
|
+
operation: resetKey ? "explicit_reset" : void 0
|
|
16518
|
+
})
|
|
16519
|
+
}
|
|
16520
|
+
);
|
|
16521
|
+
return normalizeEnvironmentData(payload.environment);
|
|
16522
|
+
};
|
|
16523
|
+
if (resetKey) {
|
|
16524
|
+
const resetEnvironment = await resolveActive(resetKey);
|
|
16525
|
+
if (resetEnvironment) {
|
|
16526
|
+
return {
|
|
16527
|
+
environment: resetEnvironment,
|
|
16528
|
+
requestedOntology: ontology,
|
|
16529
|
+
sandboxId: sandbox.sandboxId,
|
|
16530
|
+
subjectId: user.granularId,
|
|
16531
|
+
externalUserId: user.userId,
|
|
16532
|
+
// Retrying an explicit reset must also resume its durable setup run.
|
|
16533
|
+
// Otherwise a Container crash after queue submission would leave a
|
|
16534
|
+
// valid environment permanently marked as "running".
|
|
16535
|
+
setupTriggerReason: "explicit_reset",
|
|
16536
|
+
setupOperationKey: resetKey
|
|
16537
|
+
};
|
|
16538
|
+
}
|
|
16539
|
+
} else {
|
|
16540
|
+
const active = await resolveActive();
|
|
16541
|
+
if (active && (active.versionId === targetVersionId || options.createFreshIfOutdated !== true)) {
|
|
16542
|
+
return {
|
|
16543
|
+
environment: active,
|
|
16544
|
+
requestedOntology: ontology,
|
|
16545
|
+
sandboxId: sandbox.sandboxId,
|
|
16546
|
+
subjectId: user.granularId,
|
|
16547
|
+
externalUserId: user.userId
|
|
16548
|
+
};
|
|
16549
|
+
}
|
|
16550
|
+
}
|
|
16216
16551
|
const allEnvironments = await this.environments.list(sandbox.sandboxId);
|
|
16217
16552
|
const userEnvironments = allEnvironments.filter(
|
|
16218
|
-
(
|
|
16553
|
+
(environment2) => environment2.subjectId === user.granularId
|
|
16219
16554
|
);
|
|
16220
16555
|
const currentMatches = this.sortEnvironmentsByRecency(
|
|
16221
16556
|
userEnvironments.filter(
|
|
16222
|
-
(
|
|
16557
|
+
(environment2) => environment2.tagId === tag.tagId && environment2.versionId === targetVersionId
|
|
16223
16558
|
)
|
|
16224
16559
|
);
|
|
16225
|
-
if (currentMatches.length > 0) {
|
|
16560
|
+
if (!resetKey && currentMatches.length > 0) {
|
|
16561
|
+
const environment2 = await activate(currentMatches[0]);
|
|
16226
16562
|
return {
|
|
16227
|
-
environment:
|
|
16563
|
+
environment: environment2,
|
|
16228
16564
|
requestedOntology: ontology,
|
|
16229
16565
|
sandboxId: sandbox.sandboxId,
|
|
16230
|
-
subjectId: user.granularId
|
|
16566
|
+
subjectId: user.granularId,
|
|
16567
|
+
externalUserId: user.userId
|
|
16231
16568
|
};
|
|
16232
16569
|
}
|
|
16233
16570
|
const outdatedMatches = this.sortEnvironmentsByRecency(
|
|
16234
|
-
userEnvironments.filter(
|
|
16235
|
-
(environment) => this.matchesTagTrackedEnvironment(environment, tagName, tag.tagId)
|
|
16236
|
-
)
|
|
16571
|
+
userEnvironments.filter((environment2) => environment2.tagId === tag.tagId)
|
|
16237
16572
|
);
|
|
16238
16573
|
if (outdatedMatches.length > 0 && options.createFreshIfOutdated !== true) {
|
|
16574
|
+
const environment2 = await activate(outdatedMatches[0]);
|
|
16239
16575
|
return {
|
|
16240
|
-
environment:
|
|
16576
|
+
environment: environment2,
|
|
16241
16577
|
requestedOntology: ontology,
|
|
16242
16578
|
sandboxId: sandbox.sandboxId,
|
|
16243
|
-
subjectId: user.granularId
|
|
16579
|
+
subjectId: user.granularId,
|
|
16580
|
+
externalUserId: user.userId
|
|
16244
16581
|
};
|
|
16245
16582
|
}
|
|
16583
|
+
const created = await this.environments.create(sandbox.sandboxId, {
|
|
16584
|
+
subjectId: user.granularId,
|
|
16585
|
+
environment: this.buildManagedEnvironmentName(
|
|
16586
|
+
tagName,
|
|
16587
|
+
targetVersionId,
|
|
16588
|
+
resetKey
|
|
16589
|
+
),
|
|
16590
|
+
tagId: tag.tagId,
|
|
16591
|
+
permissionProfileId: null
|
|
16592
|
+
});
|
|
16593
|
+
const environment = await activate(created);
|
|
16246
16594
|
return {
|
|
16247
|
-
environment
|
|
16248
|
-
subjectId: user.granularId,
|
|
16249
|
-
environment: this.buildManagedEnvironmentName(tagName, targetVersionId),
|
|
16250
|
-
tagId: tag.tagId,
|
|
16251
|
-
versionId: targetVersionId,
|
|
16252
|
-
permissionProfileId: null
|
|
16253
|
-
}),
|
|
16595
|
+
environment,
|
|
16254
16596
|
requestedOntology: ontology,
|
|
16255
16597
|
sandboxId: sandbox.sandboxId,
|
|
16256
16598
|
subjectId: user.granularId,
|
|
16257
|
-
|
|
16599
|
+
externalUserId: user.userId,
|
|
16600
|
+
setupTriggerReason: resetKey ? "explicit_reset" : outdatedMatches.length > 0 ? "fresh_after_version_update" : "new_environment",
|
|
16601
|
+
setupOperationKey: resetKey
|
|
16258
16602
|
};
|
|
16259
16603
|
}
|
|
16260
16604
|
/**
|
|
16261
|
-
* List
|
|
16605
|
+
* List indexed sessions using ownership filters and bounded pagination.
|
|
16606
|
+
*/
|
|
16607
|
+
async listSessions(options) {
|
|
16608
|
+
const environmentId = options.environmentId?.trim();
|
|
16609
|
+
const sandboxId = options.sandboxId?.trim();
|
|
16610
|
+
const subjectId = options.subjectId?.trim();
|
|
16611
|
+
if (!environmentId && !sandboxId && !subjectId) {
|
|
16612
|
+
throw new Error(
|
|
16613
|
+
"listSessions() requires environmentId, sandboxId, or subjectId so history cannot be scanned accidentally."
|
|
16614
|
+
);
|
|
16615
|
+
}
|
|
16616
|
+
const status = options.status || "active";
|
|
16617
|
+
const allowedStatuses = /* @__PURE__ */ new Set([
|
|
16618
|
+
"active",
|
|
16619
|
+
"closed",
|
|
16620
|
+
"expired",
|
|
16621
|
+
"failed",
|
|
16622
|
+
"timeout",
|
|
16623
|
+
"all"
|
|
16624
|
+
]);
|
|
16625
|
+
if (!allowedStatuses.has(status)) {
|
|
16626
|
+
throw new Error(`Unsupported session status: ${String(status)}`);
|
|
16627
|
+
}
|
|
16628
|
+
const limit = boundedSessionListInteger(
|
|
16629
|
+
options.limit,
|
|
16630
|
+
"limit",
|
|
16631
|
+
DEFAULT_CONVERSATION_SESSION_LIST_LIMIT,
|
|
16632
|
+
1,
|
|
16633
|
+
MAX_CONVERSATION_SESSION_LIST_LIMIT
|
|
16634
|
+
);
|
|
16635
|
+
const offset = boundedSessionListInteger(
|
|
16636
|
+
options.offset,
|
|
16637
|
+
"offset",
|
|
16638
|
+
0,
|
|
16639
|
+
0,
|
|
16640
|
+
MAX_CONVERSATION_SESSION_LIST_OFFSET
|
|
16641
|
+
);
|
|
16642
|
+
const query = new URLSearchParams({
|
|
16643
|
+
limit: String(limit),
|
|
16644
|
+
offset: String(offset)
|
|
16645
|
+
});
|
|
16646
|
+
if (environmentId) query.set("environmentId", environmentId);
|
|
16647
|
+
if (sandboxId) query.set("sandboxId", sandboxId);
|
|
16648
|
+
if (subjectId) query.set("userId", subjectId);
|
|
16649
|
+
if (options.sessionScope?.trim()) {
|
|
16650
|
+
query.set("sessionScope", options.sessionScope.trim());
|
|
16651
|
+
}
|
|
16652
|
+
if (status !== "all") query.set("status", status);
|
|
16653
|
+
const res = await this.request(
|
|
16654
|
+
`/control/sessions?${query.toString()}`
|
|
16655
|
+
);
|
|
16656
|
+
const items = Array.isArray(res.items) ? res.items : [];
|
|
16657
|
+
return items.map((row) => this.normalizeConversationSession(row));
|
|
16658
|
+
}
|
|
16659
|
+
/**
|
|
16660
|
+
* List active (open) sessions for an environment.
|
|
16262
16661
|
*/
|
|
16263
16662
|
async listOpenSessions(filters) {
|
|
16264
|
-
return this.
|
|
16663
|
+
return this.listSessions({ ...filters, status: "active" });
|
|
16265
16664
|
}
|
|
16266
16665
|
/**
|
|
16267
16666
|
* List closed sessions for an environment (conversations that have disconnected).
|
|
16268
16667
|
*/
|
|
16269
16668
|
async listClosedSessions(filters) {
|
|
16270
|
-
return this.
|
|
16669
|
+
return this.listSessions({ ...filters, status: "closed" });
|
|
16271
16670
|
}
|
|
16272
16671
|
async getUserEnvironmentState(options) {
|
|
16273
16672
|
const query = new URLSearchParams({
|
|
@@ -16302,14 +16701,6 @@ var Granular = class _Granular {
|
|
|
16302
16701
|
});
|
|
16303
16702
|
return result.readAtBySessionId || {};
|
|
16304
16703
|
}
|
|
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
16704
|
normalizeConversationSession(row) {
|
|
16314
16705
|
const sessionId = String(row.sessionId ?? row.session_id ?? "");
|
|
16315
16706
|
const environmentId = String(row.environmentId ?? row.environment_id ?? "");
|
|
@@ -16368,6 +16759,7 @@ var Granular = class _Granular {
|
|
|
16368
16759
|
*/
|
|
16369
16760
|
async createSession(options) {
|
|
16370
16761
|
const clientId = options.clientId || `client_${Date.now()}`;
|
|
16762
|
+
const sessionScope = options.sessionScope?.trim() || void 0;
|
|
16371
16763
|
await this.activateEnvironment(options.environmentId);
|
|
16372
16764
|
const envData = await this.environments.get(options.environmentId);
|
|
16373
16765
|
const environment = this.bindEnvironmentHandle(envData);
|
|
@@ -16376,6 +16768,8 @@ var Granular = class _Granular {
|
|
|
16376
16768
|
body: JSON.stringify({
|
|
16377
16769
|
environmentId: options.environmentId,
|
|
16378
16770
|
clientId,
|
|
16771
|
+
sessionScope,
|
|
16772
|
+
capabilities: sessionScope ? { sessionScope } : void 0,
|
|
16379
16773
|
initialHeap: options.initialHeap
|
|
16380
16774
|
})
|
|
16381
16775
|
});
|
|
@@ -16483,11 +16877,24 @@ var Granular = class _Granular {
|
|
|
16483
16877
|
{
|
|
16484
16878
|
method: "POST",
|
|
16485
16879
|
body: JSON.stringify({
|
|
16486
|
-
triggerReason: resolved.setupTriggerReason
|
|
16880
|
+
triggerReason: resolved.setupTriggerReason,
|
|
16881
|
+
operationKey: resolved.setupOperationKey
|
|
16487
16882
|
})
|
|
16488
16883
|
}
|
|
16489
16884
|
);
|
|
16490
16885
|
const setupRunId = setupRun.setupRunId;
|
|
16886
|
+
let claim = null;
|
|
16887
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
16888
|
+
claim = await this.request(
|
|
16889
|
+
`/control/environment-setup-runs/${setupRunId}/importer-claim`,
|
|
16890
|
+
{ method: "POST", body: JSON.stringify({}) }
|
|
16891
|
+
);
|
|
16892
|
+
if (claim.action !== "busy") break;
|
|
16893
|
+
await sleep(Math.min(3e4, Math.max(250, claim.retryAfterMs || 1e3)));
|
|
16894
|
+
}
|
|
16895
|
+
if (!claim) {
|
|
16896
|
+
throw new Error(`Unable to claim environment setup run ${setupRunId}.`);
|
|
16897
|
+
}
|
|
16491
16898
|
const updateSetupRun = async (patch) => {
|
|
16492
16899
|
await this.request(
|
|
16493
16900
|
`/control/environment-setup-runs/${setupRunId}`,
|
|
@@ -16497,10 +16904,26 @@ var Granular = class _Granular {
|
|
|
16497
16904
|
}
|
|
16498
16905
|
);
|
|
16499
16906
|
};
|
|
16907
|
+
if (claim.action === "submitted") {
|
|
16908
|
+
const completedSetupRun = await this.request(
|
|
16909
|
+
`/control/environment-setup-runs/${setupRunId}`,
|
|
16910
|
+
{ method: "PATCH", body: JSON.stringify({ markHookCompleted: true }) }
|
|
16911
|
+
);
|
|
16912
|
+
const refreshedEnvironment = await this.environments.get(
|
|
16913
|
+
environment.environmentId
|
|
16914
|
+
);
|
|
16915
|
+
environment.syncEnvironmentData(refreshedEnvironment);
|
|
16916
|
+
return completedSetupRun;
|
|
16917
|
+
}
|
|
16918
|
+
if (claim.action === "busy" || claim.action === "terminal") {
|
|
16919
|
+
return claim.summary;
|
|
16920
|
+
}
|
|
16921
|
+
let importSequence = 0;
|
|
16500
16922
|
const importerContext = {
|
|
16501
16923
|
environmentId: environment.environmentId,
|
|
16502
16924
|
sandboxId: environment.sandboxId,
|
|
16503
16925
|
subjectId: environment.subjectId,
|
|
16926
|
+
externalUserId: resolved.externalUserId,
|
|
16504
16927
|
reason: resolved.setupTriggerReason,
|
|
16505
16928
|
incrementTotalObjectsToImportCount: async (n) => {
|
|
16506
16929
|
const safeIncrement = Math.max(0, Math.trunc(n));
|
|
@@ -16517,7 +16940,10 @@ var Granular = class _Granular {
|
|
|
16517
16940
|
importRecords: async (records, options) => environment.enqueueRecordImport(records, {
|
|
16518
16941
|
batchSize: options?.batchSize,
|
|
16519
16942
|
writeMode: options?.writeMode,
|
|
16520
|
-
setupRunId
|
|
16943
|
+
setupRunId,
|
|
16944
|
+
// Sequence is deterministic for a retry of one importer hook. It
|
|
16945
|
+
// prevents a Container restart from creating a second queue import.
|
|
16946
|
+
operationKey: `${setupRunId}:import:${importSequence++}`
|
|
16521
16947
|
})
|
|
16522
16948
|
};
|
|
16523
16949
|
try {
|
|
@@ -19556,6 +19982,7 @@ function buildGranularAgentSystemPrompt(input) {
|
|
|
19556
19982
|
- \`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
19983
|
- 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
19984
|
- 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()\`.
|
|
19985
|
+
- 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
19986
|
- 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
19987
|
- 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
19988
|
- 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 +20253,12 @@ ${actionIndex}
|
|
|
19826
20253
|
- 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
20254
|
- 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
20255
|
- 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
|
-
-
|
|
20256
|
+
- 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.
|
|
20257
|
+
- 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
20258
|
- 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
20259
|
- 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
20260
|
- 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
|
|
20261
|
+
- 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
20262
|
- 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
20263
|
- 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
20264
|
- 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 +20450,9 @@ function resolveHarnessTemplate(templateId = "stable", options) {
|
|
|
20022
20450
|
}
|
|
20023
20451
|
|
|
20024
20452
|
// src/openai-usage.ts
|
|
20025
|
-
var
|
|
20026
|
-
var
|
|
20453
|
+
var OPENAI_GPT_5_4_PRICING_SOURCE_URL = "https://developers.openai.com/api/docs/models/gpt-5.4/";
|
|
20454
|
+
var OPENAI_GPT_5_6_PRICING_SOURCE_URL = "https://developers.openai.com/api/docs/pricing";
|
|
20455
|
+
var OPENAI_LONG_CONTEXT_THRESHOLD_TOKENS = 272e3;
|
|
20027
20456
|
var OPENAI_MODEL_PRICING_USD_PER_MILLION = {
|
|
20028
20457
|
"gpt-5.4": {
|
|
20029
20458
|
provider: "openai",
|
|
@@ -20032,8 +20461,26 @@ var OPENAI_MODEL_PRICING_USD_PER_MILLION = {
|
|
|
20032
20461
|
inputUsdPerMillion: 2.5,
|
|
20033
20462
|
cachedInputUsdPerMillion: 0.25,
|
|
20034
20463
|
outputUsdPerMillion: 15,
|
|
20035
|
-
sourceUrl:
|
|
20036
|
-
effectiveDate:
|
|
20464
|
+
sourceUrl: OPENAI_GPT_5_4_PRICING_SOURCE_URL,
|
|
20465
|
+
effectiveDate: "2026-05-19"
|
|
20466
|
+
},
|
|
20467
|
+
"gpt-5.6-luna": {
|
|
20468
|
+
provider: "openai",
|
|
20469
|
+
model: "gpt-5.6-luna",
|
|
20470
|
+
currency: "USD",
|
|
20471
|
+
inputUsdPerMillion: 1,
|
|
20472
|
+
cachedInputUsdPerMillion: 0.1,
|
|
20473
|
+
cacheWriteUsdPerMillion: 1.25,
|
|
20474
|
+
outputUsdPerMillion: 6,
|
|
20475
|
+
sourceUrl: OPENAI_GPT_5_6_PRICING_SOURCE_URL,
|
|
20476
|
+
effectiveDate: "2026-07-11",
|
|
20477
|
+
longContextThresholdTokens: OPENAI_LONG_CONTEXT_THRESHOLD_TOKENS,
|
|
20478
|
+
longContextPricing: {
|
|
20479
|
+
inputUsdPerMillion: 2,
|
|
20480
|
+
cachedInputUsdPerMillion: 0.2,
|
|
20481
|
+
cacheWriteUsdPerMillion: 2.5,
|
|
20482
|
+
outputUsdPerMillion: 9
|
|
20483
|
+
}
|
|
20037
20484
|
}
|
|
20038
20485
|
};
|
|
20039
20486
|
function asRecord5(value) {
|
|
@@ -20046,8 +20493,18 @@ function numberField(record, key) {
|
|
|
20046
20493
|
function microsPerMillion(usdPerMillion) {
|
|
20047
20494
|
return Math.round(usdPerMillion * 1e6);
|
|
20048
20495
|
}
|
|
20049
|
-
function getOpenAIModelPricing(model) {
|
|
20050
|
-
|
|
20496
|
+
function getOpenAIModelPricing(model, inputTokens = 0) {
|
|
20497
|
+
const pricing = OPENAI_MODEL_PRICING_USD_PER_MILLION[model];
|
|
20498
|
+
if (!pricing) return null;
|
|
20499
|
+
const threshold = pricing.longContextThresholdTokens ?? null;
|
|
20500
|
+
if (pricing.longContextPricing && typeof threshold === "number" && inputTokens > threshold) {
|
|
20501
|
+
return {
|
|
20502
|
+
...pricing,
|
|
20503
|
+
...pricing.longContextPricing,
|
|
20504
|
+
contextTier: "long"
|
|
20505
|
+
};
|
|
20506
|
+
}
|
|
20507
|
+
return { ...pricing, contextTier: "short" };
|
|
20051
20508
|
}
|
|
20052
20509
|
function normalizeOpenAIUsage(rawUsage) {
|
|
20053
20510
|
const usage = asRecord5(rawUsage);
|
|
@@ -20055,6 +20512,7 @@ function normalizeOpenAIUsage(rawUsage) {
|
|
|
20055
20512
|
return {
|
|
20056
20513
|
inputTokens: 0,
|
|
20057
20514
|
cachedInputTokens: 0,
|
|
20515
|
+
cacheWriteTokens: 0,
|
|
20058
20516
|
uncachedInputTokens: 0,
|
|
20059
20517
|
outputTokens: 0,
|
|
20060
20518
|
reasoningTokens: 0,
|
|
@@ -20070,20 +20528,28 @@ function normalizeOpenAIUsage(rawUsage) {
|
|
|
20070
20528
|
inputTokens,
|
|
20071
20529
|
numberField(inputDetails, "cached_tokens") || numberField(inputDetails, "cached_input_tokens")
|
|
20072
20530
|
);
|
|
20531
|
+
const cacheWriteTokens = Math.min(
|
|
20532
|
+
Math.max(inputTokens - cachedInputTokens, 0),
|
|
20533
|
+
numberField(inputDetails, "cache_write_tokens")
|
|
20534
|
+
);
|
|
20073
20535
|
const reasoningTokens = numberField(outputDetails, "reasoning_tokens") || numberField(outputDetails, "reasoning_output_tokens");
|
|
20074
20536
|
return {
|
|
20075
20537
|
inputTokens,
|
|
20076
20538
|
cachedInputTokens,
|
|
20077
|
-
|
|
20539
|
+
cacheWriteTokens,
|
|
20540
|
+
uncachedInputTokens: Math.max(
|
|
20541
|
+
inputTokens - cachedInputTokens - cacheWriteTokens,
|
|
20542
|
+
0
|
|
20543
|
+
),
|
|
20078
20544
|
outputTokens,
|
|
20079
20545
|
reasoningTokens,
|
|
20080
20546
|
totalTokens
|
|
20081
20547
|
};
|
|
20082
20548
|
}
|
|
20083
20549
|
function calculateOpenAITokenSpend(model, rawUsage) {
|
|
20084
|
-
const pricing = getOpenAIModelPricing(model);
|
|
20085
|
-
if (!pricing) return null;
|
|
20086
20550
|
const usage = normalizeOpenAIUsage(rawUsage);
|
|
20551
|
+
const pricing = getOpenAIModelPricing(model, usage.inputTokens);
|
|
20552
|
+
if (!pricing) return null;
|
|
20087
20553
|
const inputPricePerMillionMicros = microsPerMillion(
|
|
20088
20554
|
pricing.inputUsdPerMillion
|
|
20089
20555
|
);
|
|
@@ -20093,14 +20559,19 @@ function calculateOpenAITokenSpend(model, rawUsage) {
|
|
|
20093
20559
|
const outputPricePerMillionMicros = microsPerMillion(
|
|
20094
20560
|
pricing.outputUsdPerMillion
|
|
20095
20561
|
);
|
|
20562
|
+
const cacheWritePricePerMillionMicros = typeof pricing.cacheWriteUsdPerMillion === "number" ? microsPerMillion(pricing.cacheWriteUsdPerMillion) : null;
|
|
20563
|
+
const cacheWriteCostMicros = Math.round(
|
|
20564
|
+
usage.cacheWriteTokens * (cacheWritePricePerMillionMicros ?? inputPricePerMillionMicros) / 1e6
|
|
20565
|
+
);
|
|
20096
20566
|
const amountMicros = Math.round(
|
|
20097
|
-
(usage.uncachedInputTokens * inputPricePerMillionMicros + usage.cachedInputTokens * cachedInputPricePerMillionMicros + usage.outputTokens * outputPricePerMillionMicros) / 1e6
|
|
20567
|
+
(usage.uncachedInputTokens * inputPricePerMillionMicros + usage.cachedInputTokens * cachedInputPricePerMillionMicros + usage.cacheWriteTokens * (cacheWritePricePerMillionMicros ?? inputPricePerMillionMicros) + usage.outputTokens * outputPricePerMillionMicros) / 1e6
|
|
20098
20568
|
);
|
|
20099
20569
|
return {
|
|
20100
20570
|
provider: "openai",
|
|
20101
20571
|
model,
|
|
20102
20572
|
inputTokens: usage.inputTokens,
|
|
20103
20573
|
cachedInputTokens: usage.cachedInputTokens,
|
|
20574
|
+
cacheWriteTokens: usage.cacheWriteTokens,
|
|
20104
20575
|
uncachedInputTokens: usage.uncachedInputTokens,
|
|
20105
20576
|
outputTokens: usage.outputTokens,
|
|
20106
20577
|
reasoningTokens: usage.reasoningTokens,
|
|
@@ -20109,7 +20580,11 @@ function calculateOpenAITokenSpend(model, rawUsage) {
|
|
|
20109
20580
|
currency: "USD",
|
|
20110
20581
|
inputPricePerMillionMicros,
|
|
20111
20582
|
cachedInputPricePerMillionMicros,
|
|
20583
|
+
cacheWritePricePerMillionMicros,
|
|
20584
|
+
cacheWriteCostMicros,
|
|
20112
20585
|
outputPricePerMillionMicros,
|
|
20586
|
+
pricingContextTier: pricing.contextTier || "short",
|
|
20587
|
+
longContextThresholdTokens: pricing.longContextThresholdTokens ?? null,
|
|
20113
20588
|
pricingSource: pricing.sourceUrl,
|
|
20114
20589
|
pricingEffectiveAt: pricing.effectiveDate,
|
|
20115
20590
|
usage
|