@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/index.mjs
CHANGED
|
@@ -22,11 +22,11 @@ var __export = (target, all) => {
|
|
|
22
22
|
for (var name in all)
|
|
23
23
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
24
24
|
};
|
|
25
|
-
var __copyProps = (to,
|
|
26
|
-
if (
|
|
27
|
-
for (let key of __getOwnPropNames(
|
|
25
|
+
var __copyProps = (to, from2, except, desc) => {
|
|
26
|
+
if (from2 && typeof from2 === "object" || typeof from2 === "function") {
|
|
27
|
+
for (let key of __getOwnPropNames(from2))
|
|
28
28
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
29
|
-
__defProp(to, key, { get: () =>
|
|
29
|
+
__defProp(to, key, { get: () => from2[key], enumerable: !(desc = __getOwnPropDesc(from2, key)) || desc.enumerable });
|
|
30
30
|
}
|
|
31
31
|
return to;
|
|
32
32
|
};
|
|
@@ -4043,16 +4043,37 @@ var WSClient = class {
|
|
|
4043
4043
|
tokenRefreshTimer = null;
|
|
4044
4044
|
isExplicitlyDisconnected = false;
|
|
4045
4045
|
reconnectAttempts = 0;
|
|
4046
|
+
connectPromise = null;
|
|
4047
|
+
connectionEpoch = 0;
|
|
4048
|
+
cancelConnectAttempt = null;
|
|
4046
4049
|
options;
|
|
4047
4050
|
constructor(options) {
|
|
4048
4051
|
this.options = options;
|
|
4049
4052
|
this.url = options.url;
|
|
4050
4053
|
this.sessionId = options.sessionId;
|
|
4051
4054
|
this.token = options.token;
|
|
4055
|
+
if (options.initialDocumentSnapshot) {
|
|
4056
|
+
this.seedDocumentSnapshot(options.initialDocumentSnapshot);
|
|
4057
|
+
}
|
|
4052
4058
|
}
|
|
4053
4059
|
get currentSessionId() {
|
|
4054
4060
|
return this.sessionId;
|
|
4055
4061
|
}
|
|
4062
|
+
seedDocumentSnapshot(document) {
|
|
4063
|
+
if (!document || typeof document !== "object" || Array.isArray(document)) {
|
|
4064
|
+
return;
|
|
4065
|
+
}
|
|
4066
|
+
try {
|
|
4067
|
+
this.doc = document instanceof Uint8Array ? Automerge.load(document) : Automerge.from(document);
|
|
4068
|
+
this.syncState = Automerge.initSyncState();
|
|
4069
|
+
this.emit("sync", this.doc);
|
|
4070
|
+
} catch (error) {
|
|
4071
|
+
console.warn("[Granular] Failed to seed cached session document", error);
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
saveDocumentSnapshot() {
|
|
4075
|
+
return Automerge.save(this.doc);
|
|
4076
|
+
}
|
|
4056
4077
|
clearTokenRefreshTimer() {
|
|
4057
4078
|
if (this.tokenRefreshTimer) {
|
|
4058
4079
|
clearTimeout(this.tokenRefreshTimer);
|
|
@@ -4162,8 +4183,23 @@ var WSClient = class {
|
|
|
4162
4183
|
* Connect to the WebSocket server
|
|
4163
4184
|
* @returns {Promise<void>} Resolves when connection is open
|
|
4164
4185
|
*/
|
|
4165
|
-
async connect() {
|
|
4186
|
+
async connect(options = {}) {
|
|
4187
|
+
if (this.ws?.readyState === READY_STATE_OPEN) return;
|
|
4188
|
+
if (this.connectPromise) return this.connectPromise;
|
|
4189
|
+
const connectPromise = this.connectAttempt(options.signal);
|
|
4190
|
+
this.connectPromise = connectPromise;
|
|
4191
|
+
try {
|
|
4192
|
+
await connectPromise;
|
|
4193
|
+
} finally {
|
|
4194
|
+
if (this.connectPromise === connectPromise) {
|
|
4195
|
+
this.connectPromise = null;
|
|
4196
|
+
}
|
|
4197
|
+
}
|
|
4198
|
+
}
|
|
4199
|
+
async connectAttempt(signal) {
|
|
4200
|
+
if (signal?.aborted) throw new Error("WebSocket connect aborted");
|
|
4166
4201
|
const token = await this.resolveTokenForConnect();
|
|
4202
|
+
if (signal?.aborted) throw new Error("WebSocket connect aborted");
|
|
4167
4203
|
this.isExplicitlyDisconnected = false;
|
|
4168
4204
|
this.scheduleTokenRefresh();
|
|
4169
4205
|
if (this.reconnectTimer) {
|
|
@@ -4175,7 +4211,7 @@ var WSClient = class {
|
|
|
4175
4211
|
try {
|
|
4176
4212
|
const wsModule = await Promise.resolve().then(() => (init_wrapper(), wrapper_exports));
|
|
4177
4213
|
WebSocketClass = wsModule.default || wsModule;
|
|
4178
|
-
} catch
|
|
4214
|
+
} catch {
|
|
4179
4215
|
}
|
|
4180
4216
|
}
|
|
4181
4217
|
if (!WebSocketClass) {
|
|
@@ -4183,83 +4219,97 @@ var WSClient = class {
|
|
|
4183
4219
|
'No WebSocket implementation found. If using Node.js, please install "ws" and pass the constructor to the SDK options: { WebSocketCtor: WebSocket }.'
|
|
4184
4220
|
);
|
|
4185
4221
|
}
|
|
4222
|
+
const epoch = ++this.connectionEpoch;
|
|
4223
|
+
const wsUrl = new URL(this.url);
|
|
4224
|
+
wsUrl.searchParams.set("sessionId", this.sessionId);
|
|
4225
|
+
wsUrl.searchParams.set("token", token);
|
|
4226
|
+
const socket = new WebSocketClass(wsUrl.toString());
|
|
4227
|
+
this.ws = socket;
|
|
4186
4228
|
return new Promise((resolve, reject) => {
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
if (
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
this.reconnectTimer = null;
|
|
4199
|
-
}
|
|
4200
|
-
this.reconnectAttempts = 0;
|
|
4201
|
-
this.emit("open", {});
|
|
4202
|
-
resolve();
|
|
4203
|
-
});
|
|
4204
|
-
socket.on("message", (data) => {
|
|
4205
|
-
try {
|
|
4206
|
-
const message = JSON.parse(data.toString());
|
|
4207
|
-
this.handleMessage(message);
|
|
4208
|
-
} catch (error) {
|
|
4209
|
-
console.error("[Granular] Failed to parse message:", error);
|
|
4210
|
-
}
|
|
4211
|
-
});
|
|
4212
|
-
socket.on("error", (error) => {
|
|
4213
|
-
this.emit("error", error);
|
|
4214
|
-
if (socket.readyState !== READY_STATE_OPEN) {
|
|
4215
|
-
reject(error);
|
|
4216
|
-
}
|
|
4217
|
-
});
|
|
4218
|
-
socket.on("close", (code, reason) => {
|
|
4219
|
-
this.handleDisconnect({
|
|
4220
|
-
code,
|
|
4221
|
-
reason: this.normalizeReason(reason),
|
|
4222
|
-
// ws does not provide wasClean on Node-style close callback
|
|
4223
|
-
wasClean: code === 1e3
|
|
4224
|
-
});
|
|
4225
|
-
});
|
|
4229
|
+
let settled = false;
|
|
4230
|
+
const isCurrent = () => this.connectionEpoch === epoch && this.ws === socket;
|
|
4231
|
+
const finish = (error) => {
|
|
4232
|
+
if (settled) return;
|
|
4233
|
+
settled = true;
|
|
4234
|
+
if (this.cancelConnectAttempt === handleAbort) {
|
|
4235
|
+
this.cancelConnectAttempt = null;
|
|
4236
|
+
}
|
|
4237
|
+
signal?.removeEventListener("abort", handleAbort);
|
|
4238
|
+
if (error) {
|
|
4239
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
4226
4240
|
} else {
|
|
4227
|
-
|
|
4228
|
-
if (this.reconnectTimer) {
|
|
4229
|
-
clearTimeout(this.reconnectTimer);
|
|
4230
|
-
this.reconnectTimer = null;
|
|
4231
|
-
}
|
|
4232
|
-
this.reconnectAttempts = 0;
|
|
4233
|
-
this.emit("open", {});
|
|
4234
|
-
resolve();
|
|
4235
|
-
};
|
|
4236
|
-
this.ws.onmessage = (event) => {
|
|
4237
|
-
try {
|
|
4238
|
-
const data = event.data;
|
|
4239
|
-
const message = JSON.parse(data.toString());
|
|
4240
|
-
this.handleMessage(message);
|
|
4241
|
-
} catch (error) {
|
|
4242
|
-
console.error("[Granular] Failed to parse message:", error);
|
|
4243
|
-
}
|
|
4244
|
-
};
|
|
4245
|
-
this.ws.onerror = (event) => {
|
|
4246
|
-
const error = new Error("WebSocket error");
|
|
4247
|
-
error.event = event;
|
|
4248
|
-
this.emit("error", error);
|
|
4249
|
-
if (this.ws?.readyState !== READY_STATE_OPEN) {
|
|
4250
|
-
reject(error);
|
|
4251
|
-
}
|
|
4252
|
-
};
|
|
4253
|
-
this.ws.onclose = (event) => {
|
|
4254
|
-
this.handleDisconnect({
|
|
4255
|
-
code: event.code,
|
|
4256
|
-
reason: event.reason,
|
|
4257
|
-
wasClean: event.wasClean
|
|
4258
|
-
});
|
|
4259
|
-
};
|
|
4241
|
+
resolve();
|
|
4260
4242
|
}
|
|
4261
|
-
}
|
|
4262
|
-
|
|
4243
|
+
};
|
|
4244
|
+
const closeStaleSocket = () => {
|
|
4245
|
+
try {
|
|
4246
|
+
socket.close(1e3, "Stale connection attempt");
|
|
4247
|
+
} catch {
|
|
4248
|
+
}
|
|
4249
|
+
};
|
|
4250
|
+
const handleAbort = () => {
|
|
4251
|
+
if (isCurrent()) {
|
|
4252
|
+
this.connectionEpoch += 1;
|
|
4253
|
+
this.ws = null;
|
|
4254
|
+
}
|
|
4255
|
+
closeStaleSocket();
|
|
4256
|
+
finish(new Error("WebSocket connect aborted"));
|
|
4257
|
+
};
|
|
4258
|
+
this.cancelConnectAttempt = handleAbort;
|
|
4259
|
+
const handleOpen = () => {
|
|
4260
|
+
if (!isCurrent()) {
|
|
4261
|
+
closeStaleSocket();
|
|
4262
|
+
return;
|
|
4263
|
+
}
|
|
4264
|
+
this.reconnectAttempts = 0;
|
|
4265
|
+
this.emit("open", {});
|
|
4266
|
+
finish();
|
|
4267
|
+
};
|
|
4268
|
+
const handleMessage = (data) => {
|
|
4269
|
+
if (!isCurrent()) return;
|
|
4270
|
+
try {
|
|
4271
|
+
const text = typeof data === "string" ? data : data && typeof data === "object" && "toString" in data ? String(data.toString()) : "";
|
|
4272
|
+
this.handleMessage(JSON.parse(text));
|
|
4273
|
+
} catch (error) {
|
|
4274
|
+
console.error("[Granular] Failed to parse message:", error);
|
|
4275
|
+
}
|
|
4276
|
+
};
|
|
4277
|
+
const handleError = (error) => {
|
|
4278
|
+
if (!isCurrent()) return;
|
|
4279
|
+
const typedError = error instanceof Error ? error : new Error("WebSocket error");
|
|
4280
|
+
this.emit("error", typedError);
|
|
4281
|
+
if (socket.readyState !== READY_STATE_OPEN) finish(typedError);
|
|
4282
|
+
};
|
|
4283
|
+
const handleClose = (close) => {
|
|
4284
|
+
if (!isCurrent()) return;
|
|
4285
|
+
if (!settled) {
|
|
4286
|
+
finish(
|
|
4287
|
+
new Error(
|
|
4288
|
+
`WebSocket closed before ready${close.code ? ` (code=${close.code})` : ""}`
|
|
4289
|
+
)
|
|
4290
|
+
);
|
|
4291
|
+
}
|
|
4292
|
+
this.handleDisconnect({
|
|
4293
|
+
code: close.code,
|
|
4294
|
+
reason: this.normalizeReason(close.reason),
|
|
4295
|
+
wasClean: close.wasClean
|
|
4296
|
+
});
|
|
4297
|
+
};
|
|
4298
|
+
signal?.addEventListener("abort", handleAbort, { once: true });
|
|
4299
|
+
const nodeSocket = socket;
|
|
4300
|
+
if (typeof nodeSocket.on === "function") {
|
|
4301
|
+
nodeSocket.on("open", handleOpen);
|
|
4302
|
+
nodeSocket.on("message", handleMessage);
|
|
4303
|
+
nodeSocket.on("error", handleError);
|
|
4304
|
+
nodeSocket.on(
|
|
4305
|
+
"close",
|
|
4306
|
+
(code, reason) => handleClose({ code, reason, wasClean: code === 1e3 })
|
|
4307
|
+
);
|
|
4308
|
+
} else {
|
|
4309
|
+
socket.onopen = handleOpen;
|
|
4310
|
+
socket.onmessage = (event) => handleMessage(event.data);
|
|
4311
|
+
socket.onerror = handleError;
|
|
4312
|
+
socket.onclose = (event) => handleClose(event);
|
|
4263
4313
|
}
|
|
4264
4314
|
});
|
|
4265
4315
|
}
|
|
@@ -4277,9 +4327,58 @@ var WSClient = class {
|
|
|
4277
4327
|
return void 0;
|
|
4278
4328
|
}
|
|
4279
4329
|
rejectPending(error) {
|
|
4280
|
-
this.messageQueue.forEach((pending) =>
|
|
4330
|
+
this.messageQueue.forEach((pending) => {
|
|
4331
|
+
clearTimeout(pending.timeout);
|
|
4332
|
+
pending.reject(error);
|
|
4333
|
+
});
|
|
4281
4334
|
this.messageQueue = [];
|
|
4282
4335
|
}
|
|
4336
|
+
emitReconnectErrorMessage(error) {
|
|
4337
|
+
const reconnectInfo = {
|
|
4338
|
+
error,
|
|
4339
|
+
sessionId: this.sessionId,
|
|
4340
|
+
timestamp: Date.now()
|
|
4341
|
+
};
|
|
4342
|
+
this.emit("reconnect_error", reconnectInfo);
|
|
4343
|
+
if (this.options.onReconnectError) {
|
|
4344
|
+
try {
|
|
4345
|
+
this.options.onReconnectError(reconnectInfo);
|
|
4346
|
+
} catch (callbackError) {
|
|
4347
|
+
console.error(
|
|
4348
|
+
"[Granular] onReconnectError callback failed:",
|
|
4349
|
+
callbackError
|
|
4350
|
+
);
|
|
4351
|
+
}
|
|
4352
|
+
}
|
|
4353
|
+
}
|
|
4354
|
+
scheduleReconnectAttempt() {
|
|
4355
|
+
if (this.isExplicitlyDisconnected || this.reconnectTimer) return null;
|
|
4356
|
+
const baseReconnectDelayMs = typeof this.options.reconnectDelayMs === "number" && Number.isFinite(this.options.reconnectDelayMs) && this.options.reconnectDelayMs > 0 ? this.options.reconnectDelayMs : DEFAULT_RECONNECT_DELAY_MS;
|
|
4357
|
+
const maxReconnectAttempts = typeof this.options.maxReconnectAttempts === "number" && Number.isFinite(this.options.maxReconnectAttempts) && this.options.maxReconnectAttempts >= 0 ? Math.floor(this.options.maxReconnectAttempts) : DEFAULT_MAX_RECONNECT_ATTEMPTS;
|
|
4358
|
+
if (this.reconnectAttempts >= maxReconnectAttempts) {
|
|
4359
|
+
this.emitReconnectErrorMessage(
|
|
4360
|
+
`WebSocket reconnect attempts exhausted after ${maxReconnectAttempts} attempt(s).`
|
|
4361
|
+
);
|
|
4362
|
+
return null;
|
|
4363
|
+
}
|
|
4364
|
+
this.reconnectAttempts += 1;
|
|
4365
|
+
const reconnectDelayMs = Math.min(
|
|
4366
|
+
3e4,
|
|
4367
|
+
baseReconnectDelayMs * 2 ** Math.max(0, this.reconnectAttempts - 1)
|
|
4368
|
+
);
|
|
4369
|
+
this.reconnectTimer = setTimeout(() => {
|
|
4370
|
+
this.reconnectTimer = null;
|
|
4371
|
+
console.log("[Granular] Attempting reconnect...");
|
|
4372
|
+
this.connect().catch((error) => {
|
|
4373
|
+
console.error("[Granular] Reconnect failed:", error);
|
|
4374
|
+
this.emitReconnectErrorMessage(
|
|
4375
|
+
error instanceof Error ? error.message : String(error)
|
|
4376
|
+
);
|
|
4377
|
+
this.scheduleReconnectAttempt();
|
|
4378
|
+
});
|
|
4379
|
+
}, reconnectDelayMs);
|
|
4380
|
+
return reconnectDelayMs;
|
|
4381
|
+
}
|
|
4283
4382
|
buildDisconnectError(info) {
|
|
4284
4383
|
const details = [
|
|
4285
4384
|
info.code !== void 0 ? `code=${info.code}` : void 0,
|
|
@@ -4289,8 +4388,6 @@ var WSClient = class {
|
|
|
4289
4388
|
return new Error(`WebSocket disconnected${suffix}`);
|
|
4290
4389
|
}
|
|
4291
4390
|
handleDisconnect(close = {}) {
|
|
4292
|
-
const baseReconnectDelayMs = typeof this.options.reconnectDelayMs === "number" && Number.isFinite(this.options.reconnectDelayMs) && this.options.reconnectDelayMs > 0 ? this.options.reconnectDelayMs : DEFAULT_RECONNECT_DELAY_MS;
|
|
4293
|
-
const maxReconnectAttempts = typeof this.options.maxReconnectAttempts === "number" && Number.isFinite(this.options.maxReconnectAttempts) && this.options.maxReconnectAttempts >= 0 ? Math.floor(this.options.maxReconnectAttempts) : DEFAULT_MAX_RECONNECT_ATTEMPTS;
|
|
4294
4391
|
const unexpected = !this.isExplicitlyDisconnected;
|
|
4295
4392
|
const info = {
|
|
4296
4393
|
code: close.code,
|
|
@@ -4310,32 +4407,9 @@ var WSClient = class {
|
|
|
4310
4407
|
const disconnectError = this.buildDisconnectError(info);
|
|
4311
4408
|
this.rejectPending(disconnectError);
|
|
4312
4409
|
this.emit("disconnect", info);
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
sessionId: this.sessionId,
|
|
4317
|
-
timestamp: Date.now()
|
|
4318
|
-
};
|
|
4319
|
-
this.emit("reconnect_error", reconnectInfo);
|
|
4320
|
-
if (this.options.onReconnectError) {
|
|
4321
|
-
try {
|
|
4322
|
-
this.options.onReconnectError(reconnectInfo);
|
|
4323
|
-
} catch (callbackError) {
|
|
4324
|
-
console.error(
|
|
4325
|
-
"[Granular] onReconnectError callback failed:",
|
|
4326
|
-
callbackError
|
|
4327
|
-
);
|
|
4328
|
-
}
|
|
4329
|
-
}
|
|
4330
|
-
return;
|
|
4331
|
-
}
|
|
4332
|
-
this.reconnectAttempts += 1;
|
|
4333
|
-
const reconnectDelayMs = Math.min(
|
|
4334
|
-
3e4,
|
|
4335
|
-
baseReconnectDelayMs * 2 ** Math.max(0, this.reconnectAttempts - 1)
|
|
4336
|
-
);
|
|
4337
|
-
info.reconnectScheduled = true;
|
|
4338
|
-
info.reconnectDelayMs = reconnectDelayMs;
|
|
4410
|
+
const reconnectDelayMs = this.scheduleReconnectAttempt();
|
|
4411
|
+
info.reconnectScheduled = reconnectDelayMs !== null;
|
|
4412
|
+
if (reconnectDelayMs !== null) info.reconnectDelayMs = reconnectDelayMs;
|
|
4339
4413
|
if (this.options.onUnexpectedClose) {
|
|
4340
4414
|
try {
|
|
4341
4415
|
this.options.onUnexpectedClose(info);
|
|
@@ -4346,28 +4420,6 @@ var WSClient = class {
|
|
|
4346
4420
|
);
|
|
4347
4421
|
}
|
|
4348
4422
|
}
|
|
4349
|
-
this.reconnectTimer = setTimeout(() => {
|
|
4350
|
-
console.log("[Granular] Attempting reconnect...");
|
|
4351
|
-
this.connect().catch((error) => {
|
|
4352
|
-
console.error("[Granular] Reconnect failed:", error);
|
|
4353
|
-
const reconnectInfo = {
|
|
4354
|
-
error: error instanceof Error ? error.message : String(error),
|
|
4355
|
-
sessionId: this.sessionId,
|
|
4356
|
-
timestamp: Date.now()
|
|
4357
|
-
};
|
|
4358
|
-
this.emit("reconnect_error", reconnectInfo);
|
|
4359
|
-
if (this.options.onReconnectError) {
|
|
4360
|
-
try {
|
|
4361
|
-
this.options.onReconnectError(reconnectInfo);
|
|
4362
|
-
} catch (callbackError) {
|
|
4363
|
-
console.error(
|
|
4364
|
-
"[Granular] onReconnectError callback failed:",
|
|
4365
|
-
callbackError
|
|
4366
|
-
);
|
|
4367
|
-
}
|
|
4368
|
-
}
|
|
4369
|
-
});
|
|
4370
|
-
}, reconnectDelayMs);
|
|
4371
4423
|
}
|
|
4372
4424
|
}
|
|
4373
4425
|
handleMessage(message) {
|
|
@@ -4478,6 +4530,7 @@ var WSClient = class {
|
|
|
4478
4530
|
const response = message;
|
|
4479
4531
|
const pending = this.messageQueue.find((q) => q.id === response.id);
|
|
4480
4532
|
if (pending) {
|
|
4533
|
+
clearTimeout(pending.timeout);
|
|
4481
4534
|
if (response.type === "rpc_error") {
|
|
4482
4535
|
pending.reject(
|
|
4483
4536
|
new Error(
|
|
@@ -4523,16 +4576,22 @@ var WSClient = class {
|
|
|
4523
4576
|
id
|
|
4524
4577
|
};
|
|
4525
4578
|
return new Promise((resolve, reject) => {
|
|
4526
|
-
this.messageQueue.push({ resolve, reject, id });
|
|
4527
|
-
this.ws.send(JSON.stringify(request));
|
|
4528
4579
|
const timeoutMs = rpcTimeoutMsForMethod(method);
|
|
4529
|
-
setTimeout(() => {
|
|
4580
|
+
const timeout = setTimeout(() => {
|
|
4530
4581
|
const pending = this.messageQueue.find((q) => q.id === id);
|
|
4531
4582
|
if (pending) {
|
|
4532
4583
|
this.messageQueue = this.messageQueue.filter((q) => q.id !== id);
|
|
4533
4584
|
reject(new Error(`RPC timeout: ${method}`));
|
|
4534
4585
|
}
|
|
4535
4586
|
}, timeoutMs);
|
|
4587
|
+
this.messageQueue.push({ resolve, reject, id, timeout });
|
|
4588
|
+
try {
|
|
4589
|
+
this.ws.send(JSON.stringify(request));
|
|
4590
|
+
} catch (error) {
|
|
4591
|
+
clearTimeout(timeout);
|
|
4592
|
+
this.messageQueue = this.messageQueue.filter((q) => q.id !== id);
|
|
4593
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
4594
|
+
}
|
|
4536
4595
|
});
|
|
4537
4596
|
}
|
|
4538
4597
|
async handleIncomingRpc(request) {
|
|
@@ -4618,15 +4677,18 @@ var WSClient = class {
|
|
|
4618
4677
|
/**
|
|
4619
4678
|
* Disconnect the WebSocket and clear state
|
|
4620
4679
|
*/
|
|
4621
|
-
disconnect() {
|
|
4680
|
+
disconnect(options = {}) {
|
|
4622
4681
|
this.isExplicitlyDisconnected = true;
|
|
4682
|
+
this.cancelConnectAttempt?.();
|
|
4683
|
+
this.cancelConnectAttempt = null;
|
|
4684
|
+
this.connectionEpoch += 1;
|
|
4623
4685
|
if (this.reconnectTimer) {
|
|
4624
4686
|
clearTimeout(this.reconnectTimer);
|
|
4625
4687
|
this.reconnectTimer = null;
|
|
4626
4688
|
}
|
|
4627
4689
|
this.clearTokenRefreshTimer();
|
|
4628
4690
|
if (this.ws) {
|
|
4629
|
-
this.ws.close(1e3, "Client disconnect");
|
|
4691
|
+
this.ws.close(1e3, options.reason || "Client disconnect");
|
|
4630
4692
|
this.ws = null;
|
|
4631
4693
|
}
|
|
4632
4694
|
this.rejectPending(new Error("Client explicitly disconnected"));
|
|
@@ -4722,8 +4784,12 @@ function normalizePrompt(rawValue) {
|
|
|
4722
4784
|
const source = promptRecord || raw;
|
|
4723
4785
|
const id = typeof source.id === "string" ? source.id : typeof raw.id === "string" ? raw.id : typeof raw.promptId === "string" ? raw.promptId : "";
|
|
4724
4786
|
if (!id) return null;
|
|
4787
|
+
const jobId = typeof source.jobId === "string" && source.jobId.trim() ? source.jobId.trim() : typeof raw.jobId === "string" && raw.jobId.trim() ? raw.jobId.trim() : void 0;
|
|
4788
|
+
const turnId = typeof source.turnId === "string" && source.turnId.trim() ? source.turnId.trim() : typeof raw.turnId === "string" && raw.turnId.trim() ? raw.turnId.trim() : void 0;
|
|
4725
4789
|
return {
|
|
4726
4790
|
id,
|
|
4791
|
+
...jobId ? { jobId } : {},
|
|
4792
|
+
...turnId ? { turnId } : {},
|
|
4727
4793
|
type: normalizePromptType(source === raw ? raw : { ...raw, ...source }),
|
|
4728
4794
|
title: typeof source.title === "string" ? source.title : "Input required",
|
|
4729
4795
|
message: typeof source.message === "string" ? source.message : "",
|
|
@@ -4810,6 +4876,9 @@ var Session = class {
|
|
|
4810
4876
|
this.initialQuota = options.initialQuota || null;
|
|
4811
4877
|
this.setupEventHandlers();
|
|
4812
4878
|
this.setupToolInvokeHandler();
|
|
4879
|
+
this.currentDomainRevision = this.extractDomainRevisionFromDoc(
|
|
4880
|
+
this.client.doc
|
|
4881
|
+
);
|
|
4813
4882
|
}
|
|
4814
4883
|
extractDomainRevisionFromDoc(doc) {
|
|
4815
4884
|
const domain = doc?.domain;
|
|
@@ -5715,6 +5784,7 @@ function normalizeJobAgentMessageEnvelope(data) {
|
|
|
5715
5784
|
}
|
|
5716
5785
|
return {
|
|
5717
5786
|
jobId: d.jobId,
|
|
5787
|
+
...typeof d.turnId === "string" && d.turnId.trim() ? { turnId: d.turnId.trim() } : {},
|
|
5718
5788
|
message: {
|
|
5719
5789
|
messageId: d.messageId,
|
|
5720
5790
|
kind: d.kind === "artifacts" ? "artifacts" : "text",
|
|
@@ -6373,9 +6443,14 @@ function normalizeShowRefs(value) {
|
|
|
6373
6443
|
variableNames: normalizeRefs(record.variableNames),
|
|
6374
6444
|
fileIds: normalizeRefs(record.fileIds),
|
|
6375
6445
|
sessionArtifactIds: normalizeRefs(record.sessionArtifactIds),
|
|
6376
|
-
actionSuggestions: normalizeActionSuggestions(record.actionSuggestions)
|
|
6446
|
+
actionSuggestions: normalizeActionSuggestions(record.actionSuggestions),
|
|
6447
|
+
tables: Array.isArray(record.tables) ? record.tables.filter(
|
|
6448
|
+
(table) => Boolean(
|
|
6449
|
+
table && typeof table === "object" && !Array.isArray(table) && Array.isArray(table.columns) && Array.isArray(table.rows)
|
|
6450
|
+
)
|
|
6451
|
+
) : void 0
|
|
6377
6452
|
};
|
|
6378
|
-
return show.entryPaths || show.listNames || show.variableNames || show.fileIds || show.sessionArtifactIds || show.actionSuggestions ? show : void 0;
|
|
6453
|
+
return show.entryPaths || show.listNames || show.variableNames || show.fileIds || show.sessionArtifactIds || show.actionSuggestions || show.tables ? show : void 0;
|
|
6379
6454
|
}
|
|
6380
6455
|
function normalizeActionSuggestions(value) {
|
|
6381
6456
|
if (!Array.isArray(value)) return void 0;
|
|
@@ -6397,6 +6472,76 @@ function normalizeActionSuggestions(value) {
|
|
|
6397
6472
|
}
|
|
6398
6473
|
return suggestions.length ? suggestions : void 0;
|
|
6399
6474
|
}
|
|
6475
|
+
var TRANSCRIPT_MESSAGE_PART_LIMIT = 128;
|
|
6476
|
+
var TRANSCRIPT_MESSAGE_PART_TEXT_LIMIT = 2e5;
|
|
6477
|
+
var TRANSCRIPT_MESSAGE_PART_ACTION_LABEL_LIMIT = 1e3;
|
|
6478
|
+
var TRANSCRIPT_MESSAGE_ACTION_LIMIT = 64;
|
|
6479
|
+
function normalizeConversationMessageActions(value) {
|
|
6480
|
+
if (!Array.isArray(value) || value.length === 0) return void 0;
|
|
6481
|
+
const actions = [];
|
|
6482
|
+
for (const item of value.slice(0, TRANSCRIPT_MESSAGE_ACTION_LIMIT)) {
|
|
6483
|
+
const record = asRecord3(item);
|
|
6484
|
+
const kind = record?.kind;
|
|
6485
|
+
const label = trimString(record?.label ?? record?.title);
|
|
6486
|
+
const status = record?.status;
|
|
6487
|
+
if (kind !== "frontend" && kind !== "backend" && kind !== "system" || !label || label.length > TRANSCRIPT_MESSAGE_PART_ACTION_LABEL_LIMIT) {
|
|
6488
|
+
continue;
|
|
6489
|
+
}
|
|
6490
|
+
actions.push({
|
|
6491
|
+
kind,
|
|
6492
|
+
label,
|
|
6493
|
+
...status === "done" || status === "queued" || status === "failed" ? { status } : {}
|
|
6494
|
+
});
|
|
6495
|
+
}
|
|
6496
|
+
return actions.length ? actions : void 0;
|
|
6497
|
+
}
|
|
6498
|
+
function normalizeConversationMessageParts(value, canonicalContent, canonicalActions) {
|
|
6499
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > TRANSCRIPT_MESSAGE_PART_LIMIT) {
|
|
6500
|
+
return void 0;
|
|
6501
|
+
}
|
|
6502
|
+
const parts = [];
|
|
6503
|
+
const canonicalActionsById = new Map(
|
|
6504
|
+
(canonicalActions || []).map((action) => [
|
|
6505
|
+
`${action.kind}:${action.label}`,
|
|
6506
|
+
action
|
|
6507
|
+
])
|
|
6508
|
+
);
|
|
6509
|
+
const seenActionIds = /* @__PURE__ */ new Set();
|
|
6510
|
+
let textLength = 0;
|
|
6511
|
+
for (const item of value) {
|
|
6512
|
+
const record = asRecord3(item);
|
|
6513
|
+
if (!record) return void 0;
|
|
6514
|
+
if (record.type === "text") {
|
|
6515
|
+
if (typeof record.text !== "string" || record.text.length === 0) {
|
|
6516
|
+
return void 0;
|
|
6517
|
+
}
|
|
6518
|
+
textLength += record.text.length;
|
|
6519
|
+
if (textLength > TRANSCRIPT_MESSAGE_PART_TEXT_LIMIT) return void 0;
|
|
6520
|
+
parts.push({ type: "text", text: record.text });
|
|
6521
|
+
continue;
|
|
6522
|
+
}
|
|
6523
|
+
if (record.type !== "action") return void 0;
|
|
6524
|
+
const action = asRecord3(record.action);
|
|
6525
|
+
const kind = action?.kind;
|
|
6526
|
+
const label = trimString(action?.label);
|
|
6527
|
+
if (kind !== "frontend" && kind !== "backend" && kind !== "system" || !label || label.length > TRANSCRIPT_MESSAGE_PART_ACTION_LABEL_LIMIT) {
|
|
6528
|
+
return void 0;
|
|
6529
|
+
}
|
|
6530
|
+
const actionId = `${kind}:${label}`;
|
|
6531
|
+
const canonicalAction = canonicalActionsById.get(actionId);
|
|
6532
|
+
if (!canonicalAction) return void 0;
|
|
6533
|
+
if (seenActionIds.has(actionId)) continue;
|
|
6534
|
+
seenActionIds.add(actionId);
|
|
6535
|
+
parts.push({
|
|
6536
|
+
type: "action",
|
|
6537
|
+
action: canonicalAction
|
|
6538
|
+
});
|
|
6539
|
+
}
|
|
6540
|
+
const orderedText = parts.filter(
|
|
6541
|
+
(part) => part.type === "text"
|
|
6542
|
+
).map((part) => part.text).join("");
|
|
6543
|
+
return orderedText === canonicalContent ? parts : void 0;
|
|
6544
|
+
}
|
|
6400
6545
|
function stringifyTranscriptValue(value, fallback = "") {
|
|
6401
6546
|
if (typeof value === "string") {
|
|
6402
6547
|
return value.trim() || fallback;
|
|
@@ -6555,10 +6700,12 @@ function normalizeConversationMessage(raw, artifactsById) {
|
|
|
6555
6700
|
const content = trimString(
|
|
6556
6701
|
record.content ?? record.reply ?? record.message ?? record.text
|
|
6557
6702
|
);
|
|
6703
|
+
const actions = role === "assistant" ? normalizeConversationMessageActions(record.actions) : void 0;
|
|
6704
|
+
const parts = role === "assistant" ? normalizeConversationMessageParts(record.parts, content, actions) : void 0;
|
|
6558
6705
|
const show = normalizeShowRefs(record.show);
|
|
6559
6706
|
const id = asString(record.id) || crypto.randomUUID();
|
|
6560
6707
|
const timestamp = asNumber(record.timestamp) || asNumber(record.ts) || 0;
|
|
6561
|
-
if (!content && !show) return null;
|
|
6708
|
+
if (!content && !show && !actions?.length) return null;
|
|
6562
6709
|
const artifactHistory = buildArtifactHistory(show, artifactsById);
|
|
6563
6710
|
const historyContent = role === "assistant" ? content && artifactHistory ? `[Assistant reply]
|
|
6564
6711
|
${content}
|
|
@@ -6573,6 +6720,8 @@ ${content}` : artifactHistory : void 0;
|
|
|
6573
6720
|
jobId: asString(record.jobId),
|
|
6574
6721
|
promptId: asString(record.promptId),
|
|
6575
6722
|
show,
|
|
6723
|
+
actions,
|
|
6724
|
+
parts,
|
|
6576
6725
|
historyContent,
|
|
6577
6726
|
source: "conversation"
|
|
6578
6727
|
};
|
|
@@ -12150,7 +12299,12 @@ async function recordOpenAIUsageSpend(options) {
|
|
|
12150
12299
|
const metadata = {
|
|
12151
12300
|
...options.metadata || {},
|
|
12152
12301
|
...options.usage.rawUsage !== void 0 ? { openaiUsage: options.usage.rawUsage } : {},
|
|
12153
|
-
usageContext: context
|
|
12302
|
+
usageContext: context,
|
|
12303
|
+
pricingContextTier: options.usage.pricingContextTier,
|
|
12304
|
+
cacheWritePricePerMillionMicros: options.usage.cacheWritePricePerMillionMicros,
|
|
12305
|
+
cacheWriteTokens: options.usage.cacheWriteTokens,
|
|
12306
|
+
cacheWriteCostMicros: options.usage.cacheWriteCostMicros,
|
|
12307
|
+
longContextThresholdTokens: options.usage.longContextThresholdTokens
|
|
12154
12308
|
};
|
|
12155
12309
|
const response = await fetch(
|
|
12156
12310
|
`${toGranularHttpBase(options.apiUrl)}/control/spend/events`,
|
|
@@ -13078,11 +13232,20 @@ function buildStateMachineModelMutations(modelPath, machines) {
|
|
|
13078
13232
|
return mutations;
|
|
13079
13233
|
}
|
|
13080
13234
|
function buildMachineTypes(classSummary, machine) {
|
|
13235
|
+
const stateGlossary = machine.states.map((state) => {
|
|
13236
|
+
const label = state.label && state.label !== state.name ? state.label : null;
|
|
13237
|
+
const meaning = [label, state.description].filter(Boolean).join(" \u2014 ");
|
|
13238
|
+
const finalMarker = state.isFinal ? " Final state." : "";
|
|
13239
|
+
return `${state.name}${meaning ? `: ${meaning}` : "."}${finalMarker}`;
|
|
13240
|
+
});
|
|
13081
13241
|
return [
|
|
13082
13242
|
{
|
|
13083
13243
|
kind: "union",
|
|
13084
13244
|
name: stateTypeName(classSummary.name, machine.name),
|
|
13085
|
-
docs: [
|
|
13245
|
+
docs: [
|
|
13246
|
+
`Allowed states for ${classSummary.name}.${machine.name}.`,
|
|
13247
|
+
...stateGlossary
|
|
13248
|
+
],
|
|
13086
13249
|
members: machine.states.map((state) => state.name)
|
|
13087
13250
|
},
|
|
13088
13251
|
{
|
|
@@ -13430,7 +13593,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
|
|
|
13430
13593
|
},
|
|
13431
13594
|
add_transition: async (value, {
|
|
13432
13595
|
name,
|
|
13433
|
-
from,
|
|
13596
|
+
from: from2,
|
|
13434
13597
|
to,
|
|
13435
13598
|
label,
|
|
13436
13599
|
description,
|
|
@@ -13445,7 +13608,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
|
|
|
13445
13608
|
value.target.add_state_machine_transition(
|
|
13446
13609
|
value.name,
|
|
13447
13610
|
name,
|
|
13448
|
-
|
|
13611
|
+
from2,
|
|
13449
13612
|
to,
|
|
13450
13613
|
{
|
|
13451
13614
|
label,
|
|
@@ -13895,6 +14058,18 @@ function buildEffectMetamodelMutations(toolPath, spec) {
|
|
|
13895
14058
|
}
|
|
13896
14059
|
|
|
13897
14060
|
// src/client.ts
|
|
14061
|
+
var DEFAULT_CONVERSATION_SESSION_LIST_LIMIT = 100;
|
|
14062
|
+
var MAX_CONVERSATION_SESSION_LIST_LIMIT = 500;
|
|
14063
|
+
var MAX_CONVERSATION_SESSION_LIST_OFFSET = 1e5;
|
|
14064
|
+
function boundedSessionListInteger(value, name, fallback, minimum, maximum) {
|
|
14065
|
+
if (value === void 0) return fallback;
|
|
14066
|
+
if (!Number.isInteger(value) || value < minimum || value > maximum) {
|
|
14067
|
+
throw new RangeError(
|
|
14068
|
+
`Session list ${name} must be an integer between ${minimum} and ${maximum}.`
|
|
14069
|
+
);
|
|
14070
|
+
}
|
|
14071
|
+
return value;
|
|
14072
|
+
}
|
|
13898
14073
|
var STANDARD_MODULES_OPERATIONS = [
|
|
13899
14074
|
{
|
|
13900
14075
|
create: "entity",
|
|
@@ -14112,7 +14287,8 @@ function normalizeEnvironmentSetupSummary(setup) {
|
|
|
14112
14287
|
environmentId: String(setup.environmentId || ""),
|
|
14113
14288
|
sandboxId: String(setup.sandboxId || ""),
|
|
14114
14289
|
subjectId: String(setup.subjectId || ""),
|
|
14115
|
-
triggerReason: setup.triggerReason === "fresh_after_version_update" ? "fresh_after_version_update" : "new_environment",
|
|
14290
|
+
triggerReason: setup.triggerReason === "fresh_after_version_update" ? "fresh_after_version_update" : setup.triggerReason === "explicit_reset" ? "explicit_reset" : "new_environment",
|
|
14291
|
+
operationKey: typeof setup.operationKey === "string" ? setup.operationKey : null,
|
|
14116
14292
|
lifecycleStatus: setup.lifecycleStatus === "completed" || setup.lifecycleStatus === "failed" ? setup.lifecycleStatus : "running",
|
|
14117
14293
|
stage: typeof setup.stage === "string" ? setup.stage : null,
|
|
14118
14294
|
totalObjectsToImport: Number(setup.totalObjectsToImport || 0),
|
|
@@ -14233,7 +14409,7 @@ var Environment = class _Environment {
|
|
|
14233
14409
|
}
|
|
14234
14410
|
get sessions() {
|
|
14235
14411
|
return {
|
|
14236
|
-
list: async (options) => this.listSessions(options
|
|
14412
|
+
list: async (options = {}) => this.listSessions(options),
|
|
14237
14413
|
create: async (options) => this.createSession(options),
|
|
14238
14414
|
connect: async (sessionId, options) => this.connectSession(sessionId, options),
|
|
14239
14415
|
reopen: async (sessionId, options) => this.reopenSession(sessionId, options),
|
|
@@ -14368,17 +14544,12 @@ var Environment = class _Environment {
|
|
|
14368
14544
|
*/
|
|
14369
14545
|
async disconnect() {
|
|
14370
14546
|
}
|
|
14371
|
-
async listSessions(
|
|
14372
|
-
|
|
14373
|
-
|
|
14374
|
-
|
|
14375
|
-
|
|
14376
|
-
|
|
14377
|
-
return [...active, ...closed].sort(
|
|
14378
|
-
(left, right) => Date.parse(right.lastSeenAt) - Date.parse(left.lastSeenAt)
|
|
14379
|
-
);
|
|
14380
|
-
}
|
|
14381
|
-
return status === "closed" ? this.granular.listClosedSessions({ environmentId: this.environmentId }) : this.granular.listOpenSessions({ environmentId: this.environmentId });
|
|
14547
|
+
async listSessions(optionsOrStatus = {}) {
|
|
14548
|
+
const options = typeof optionsOrStatus === "string" ? { status: optionsOrStatus } : optionsOrStatus;
|
|
14549
|
+
return this.granular.listSessions({
|
|
14550
|
+
...options,
|
|
14551
|
+
environmentId: this.environmentId
|
|
14552
|
+
});
|
|
14382
14553
|
}
|
|
14383
14554
|
async getUserEnvironmentState(options = {}) {
|
|
14384
14555
|
return this.granular.getUserEnvironmentState({
|
|
@@ -14396,6 +14567,7 @@ var Environment = class _Environment {
|
|
|
14396
14567
|
return this.granular.createSession({
|
|
14397
14568
|
environmentId: this.environmentId,
|
|
14398
14569
|
clientId: options?.clientId,
|
|
14570
|
+
sessionScope: options?.sessionScope,
|
|
14399
14571
|
initialHeap: options?.initialHeap
|
|
14400
14572
|
});
|
|
14401
14573
|
}
|
|
@@ -15398,6 +15570,7 @@ var Environment = class _Environment {
|
|
|
15398
15570
|
records: recordsToImport,
|
|
15399
15571
|
batchSize: options.batchSize,
|
|
15400
15572
|
setupRunId: options.setupRunId,
|
|
15573
|
+
operationKey: options.operationKey,
|
|
15401
15574
|
writeMode: options.writeMode
|
|
15402
15575
|
})
|
|
15403
15576
|
}
|
|
@@ -15958,7 +16131,7 @@ var EnvironmentSession = class extends Session {
|
|
|
15958
16131
|
* Close only the socket transport without sending `client.goodbye`.
|
|
15959
16132
|
*/
|
|
15960
16133
|
disconnectTransport() {
|
|
15961
|
-
this.client.disconnect();
|
|
16134
|
+
this.client.disconnect({ reason: "Transport detach" });
|
|
15962
16135
|
}
|
|
15963
16136
|
/**
|
|
15964
16137
|
* Backwards-compatible alias for `disconnect()`.
|
|
@@ -16202,6 +16375,107 @@ var Granular = class _Granular {
|
|
|
16202
16375
|
await this.maybeRunEnvironmentImporter(resolved, environment);
|
|
16203
16376
|
return environment;
|
|
16204
16377
|
}
|
|
16378
|
+
/**
|
|
16379
|
+
* Read the active environment selected by Granular for an already-recorded
|
|
16380
|
+
* external user. This is intentionally read-only: browser/login code must
|
|
16381
|
+
* not create subjects or environments as a side effect.
|
|
16382
|
+
*/
|
|
16383
|
+
async getActiveEnvironmentForUser(options) {
|
|
16384
|
+
const sandboxId = options.sandboxId.trim();
|
|
16385
|
+
const tagName = options.tag.trim();
|
|
16386
|
+
const userId = options.userId.trim();
|
|
16387
|
+
if (!sandboxId || !tagName || !userId) {
|
|
16388
|
+
throw new Error(
|
|
16389
|
+
"getActiveEnvironmentForUser() requires sandboxId, tag, and userId."
|
|
16390
|
+
);
|
|
16391
|
+
}
|
|
16392
|
+
const subjects = await this.request(
|
|
16393
|
+
`/control/subjects?identityId=${encodeURIComponent(userId)}`
|
|
16394
|
+
);
|
|
16395
|
+
const subject = (subjects.items || []).find(
|
|
16396
|
+
(item) => item.identityId === userId || item.userId === userId
|
|
16397
|
+
);
|
|
16398
|
+
if (!subject?.subjectId && !subject?.granularId) {
|
|
16399
|
+
return null;
|
|
16400
|
+
}
|
|
16401
|
+
const subjectId = subject.subjectId || subject.granularId;
|
|
16402
|
+
const tags = await this.request(
|
|
16403
|
+
`/control/sandboxes/${encodeURIComponent(sandboxId)}/tags`
|
|
16404
|
+
);
|
|
16405
|
+
const tag = (tags.items || []).find(
|
|
16406
|
+
(item) => item?.name === tagName
|
|
16407
|
+
);
|
|
16408
|
+
if (!tag) return null;
|
|
16409
|
+
const query = new URLSearchParams({
|
|
16410
|
+
tagId: tag.tagId,
|
|
16411
|
+
slot: options.slot?.trim() || "default"
|
|
16412
|
+
});
|
|
16413
|
+
try {
|
|
16414
|
+
const payload = await this.request(
|
|
16415
|
+
`/control/sandboxes/${encodeURIComponent(sandboxId)}/subjects/${encodeURIComponent(subjectId)}/active-environment?${query.toString()}`
|
|
16416
|
+
);
|
|
16417
|
+
return payload.environment ? this.bindEnvironmentHandle(
|
|
16418
|
+
normalizeEnvironmentData(payload.environment)
|
|
16419
|
+
) : null;
|
|
16420
|
+
} catch (error) {
|
|
16421
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16422
|
+
if (message.includes("404") || message.includes("not found")) {
|
|
16423
|
+
return null;
|
|
16424
|
+
}
|
|
16425
|
+
throw error;
|
|
16426
|
+
}
|
|
16427
|
+
}
|
|
16428
|
+
/**
|
|
16429
|
+
* Register one reviewed, pre-existing environment as the active workspace
|
|
16430
|
+
* for an external user. This is for a controlled migration only: it does
|
|
16431
|
+
* not create an environment and it does not run an importer.
|
|
16432
|
+
*/
|
|
16433
|
+
async adoptEnvironmentForUser(options) {
|
|
16434
|
+
const sandboxId = options.sandboxId.trim();
|
|
16435
|
+
const tagName = options.tag.trim();
|
|
16436
|
+
const userId = options.userId.trim();
|
|
16437
|
+
const environmentId = options.environmentId.trim();
|
|
16438
|
+
if (!sandboxId || !tagName || !userId || !environmentId) {
|
|
16439
|
+
throw new Error(
|
|
16440
|
+
"adoptEnvironmentForUser() requires sandboxId, tag, userId, and environmentId."
|
|
16441
|
+
);
|
|
16442
|
+
}
|
|
16443
|
+
const subjects = await this.request(
|
|
16444
|
+
`/control/subjects?identityId=${encodeURIComponent(userId)}`
|
|
16445
|
+
);
|
|
16446
|
+
const subject = (subjects.items || []).find(
|
|
16447
|
+
(item) => item.identityId === userId || item.userId === userId
|
|
16448
|
+
);
|
|
16449
|
+
if (!subject?.subjectId && !subject?.granularId) {
|
|
16450
|
+
throw new Error(`No Granular subject exists for user ${userId}.`);
|
|
16451
|
+
}
|
|
16452
|
+
const tags = await this.request(`/control/sandboxes/${encodeURIComponent(sandboxId)}/tags`);
|
|
16453
|
+
const tag = (tags.items || []).find(
|
|
16454
|
+
(item) => item?.name === tagName
|
|
16455
|
+
);
|
|
16456
|
+
if (!tag) {
|
|
16457
|
+
throw new Error(`Tag ${tagName} was not found in sandbox ${sandboxId}.`);
|
|
16458
|
+
}
|
|
16459
|
+
const payload = await this.request(
|
|
16460
|
+
"/control/environment-activations/adopt",
|
|
16461
|
+
{
|
|
16462
|
+
method: "POST",
|
|
16463
|
+
body: JSON.stringify({
|
|
16464
|
+
environmentId,
|
|
16465
|
+
subjectId: subject.subjectId || subject.granularId,
|
|
16466
|
+
tagId: tag.tagId,
|
|
16467
|
+
slot: options.slot?.trim() || "default",
|
|
16468
|
+
confirmExistingData: true
|
|
16469
|
+
})
|
|
16470
|
+
}
|
|
16471
|
+
);
|
|
16472
|
+
if (!payload.environment) {
|
|
16473
|
+
throw new Error("Granular did not return the adopted environment.");
|
|
16474
|
+
}
|
|
16475
|
+
return this.bindEnvironmentHandle(
|
|
16476
|
+
normalizeEnvironmentData(payload.environment)
|
|
16477
|
+
);
|
|
16478
|
+
}
|
|
16205
16479
|
/**
|
|
16206
16480
|
* Deprecated compatibility alias for `openEnvironment()`.
|
|
16207
16481
|
*
|
|
@@ -16233,7 +16507,9 @@ var Granular = class _Granular {
|
|
|
16233
16507
|
requestedOntology,
|
|
16234
16508
|
sandboxId: environmentData.sandboxId,
|
|
16235
16509
|
subjectId: environmentData.subjectId,
|
|
16236
|
-
|
|
16510
|
+
externalUserId: environmentData.subjectId,
|
|
16511
|
+
setupTriggerReason: options.reason || "new_environment",
|
|
16512
|
+
setupOperationKey: options.operationKey
|
|
16237
16513
|
},
|
|
16238
16514
|
environment
|
|
16239
16515
|
);
|
|
@@ -16245,20 +16521,17 @@ var Granular = class _Granular {
|
|
|
16245
16521
|
}
|
|
16246
16522
|
return tag;
|
|
16247
16523
|
}
|
|
16248
|
-
buildManagedEnvironmentName(tag, versionId) {
|
|
16249
|
-
|
|
16524
|
+
buildManagedEnvironmentName(tag, versionId, resetKey) {
|
|
16525
|
+
if (!resetKey) {
|
|
16526
|
+
return `__sdk__${tag}__${versionId}__tracked`;
|
|
16527
|
+
}
|
|
16528
|
+
const safeResetKey = resetKey.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 80);
|
|
16529
|
+
return `__sdk__${tag}__${versionId}__reset__${safeResetKey}`;
|
|
16250
16530
|
}
|
|
16251
16531
|
isManagedEnvironmentName(environment, tagName) {
|
|
16252
16532
|
const name = environment.environment || environment.envName || "";
|
|
16253
16533
|
return name.startsWith(`__sdk__${tagName}__`);
|
|
16254
16534
|
}
|
|
16255
|
-
isPinnedToVersion(environment, versionId) {
|
|
16256
|
-
return environment.buildPolicy.mode === "pinned" && (environment.versionId === versionId || environment.buildPolicy.versionId === versionId || environment.buildPolicy.buildId === versionId);
|
|
16257
|
-
}
|
|
16258
|
-
matchesTagTrackedEnvironment(environment, tagName, tagId) {
|
|
16259
|
-
const environmentTagName = environment.tag?.name || environment.buildPolicy.tagName || null;
|
|
16260
|
-
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);
|
|
16261
|
-
}
|
|
16262
16535
|
sortEnvironmentsByRecency(environments) {
|
|
16263
16536
|
return [...environments].sort(
|
|
16264
16537
|
(left, right) => right.updatedAt - left.updatedAt
|
|
@@ -16308,61 +16581,187 @@ var Granular = class _Granular {
|
|
|
16308
16581
|
`Tag "${tagName}" does not currently point to a build/version.`
|
|
16309
16582
|
);
|
|
16310
16583
|
}
|
|
16584
|
+
const slot = options.slot?.trim() || "default";
|
|
16585
|
+
const resetKey = options.resetKey?.trim() || void 0;
|
|
16586
|
+
const resolveActive = async (operationKey) => {
|
|
16587
|
+
const query = new URLSearchParams({ tagId: tag.tagId, slot });
|
|
16588
|
+
if (operationKey) query.set("operationKey", operationKey);
|
|
16589
|
+
try {
|
|
16590
|
+
const payload = await this.request(
|
|
16591
|
+
`/control/sandboxes/${encodeURIComponent(sandbox.sandboxId)}/subjects/${encodeURIComponent(user.granularId)}/active-environment?${query.toString()}`
|
|
16592
|
+
);
|
|
16593
|
+
return payload.environment ? normalizeEnvironmentData(payload.environment) : null;
|
|
16594
|
+
} catch (error) {
|
|
16595
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16596
|
+
if (message.includes("404") || message.includes("not found")) {
|
|
16597
|
+
return null;
|
|
16598
|
+
}
|
|
16599
|
+
throw error;
|
|
16600
|
+
}
|
|
16601
|
+
};
|
|
16602
|
+
const activate = async (environment2) => {
|
|
16603
|
+
const payload = await this.request(
|
|
16604
|
+
"/control/environment-activations",
|
|
16605
|
+
{
|
|
16606
|
+
method: "POST",
|
|
16607
|
+
body: JSON.stringify({
|
|
16608
|
+
environmentId: environment2.environmentId,
|
|
16609
|
+
tagId: tag.tagId,
|
|
16610
|
+
slot,
|
|
16611
|
+
operationKey: resetKey,
|
|
16612
|
+
operation: resetKey ? "explicit_reset" : void 0
|
|
16613
|
+
})
|
|
16614
|
+
}
|
|
16615
|
+
);
|
|
16616
|
+
return normalizeEnvironmentData(payload.environment);
|
|
16617
|
+
};
|
|
16618
|
+
if (resetKey) {
|
|
16619
|
+
const resetEnvironment = await resolveActive(resetKey);
|
|
16620
|
+
if (resetEnvironment) {
|
|
16621
|
+
return {
|
|
16622
|
+
environment: resetEnvironment,
|
|
16623
|
+
requestedOntology: ontology,
|
|
16624
|
+
sandboxId: sandbox.sandboxId,
|
|
16625
|
+
subjectId: user.granularId,
|
|
16626
|
+
externalUserId: user.userId,
|
|
16627
|
+
// Retrying an explicit reset must also resume its durable setup run.
|
|
16628
|
+
// Otherwise a Container crash after queue submission would leave a
|
|
16629
|
+
// valid environment permanently marked as "running".
|
|
16630
|
+
setupTriggerReason: "explicit_reset",
|
|
16631
|
+
setupOperationKey: resetKey
|
|
16632
|
+
};
|
|
16633
|
+
}
|
|
16634
|
+
} else {
|
|
16635
|
+
const active = await resolveActive();
|
|
16636
|
+
if (active && (active.versionId === targetVersionId || options.createFreshIfOutdated !== true)) {
|
|
16637
|
+
return {
|
|
16638
|
+
environment: active,
|
|
16639
|
+
requestedOntology: ontology,
|
|
16640
|
+
sandboxId: sandbox.sandboxId,
|
|
16641
|
+
subjectId: user.granularId,
|
|
16642
|
+
externalUserId: user.userId
|
|
16643
|
+
};
|
|
16644
|
+
}
|
|
16645
|
+
}
|
|
16311
16646
|
const allEnvironments = await this.environments.list(sandbox.sandboxId);
|
|
16312
16647
|
const userEnvironments = allEnvironments.filter(
|
|
16313
|
-
(
|
|
16648
|
+
(environment2) => environment2.subjectId === user.granularId
|
|
16314
16649
|
);
|
|
16315
16650
|
const currentMatches = this.sortEnvironmentsByRecency(
|
|
16316
16651
|
userEnvironments.filter(
|
|
16317
|
-
(
|
|
16652
|
+
(environment2) => environment2.tagId === tag.tagId && environment2.versionId === targetVersionId
|
|
16318
16653
|
)
|
|
16319
16654
|
);
|
|
16320
|
-
if (currentMatches.length > 0) {
|
|
16655
|
+
if (!resetKey && currentMatches.length > 0) {
|
|
16656
|
+
const environment2 = await activate(currentMatches[0]);
|
|
16321
16657
|
return {
|
|
16322
|
-
environment:
|
|
16658
|
+
environment: environment2,
|
|
16323
16659
|
requestedOntology: ontology,
|
|
16324
16660
|
sandboxId: sandbox.sandboxId,
|
|
16325
|
-
subjectId: user.granularId
|
|
16661
|
+
subjectId: user.granularId,
|
|
16662
|
+
externalUserId: user.userId
|
|
16326
16663
|
};
|
|
16327
16664
|
}
|
|
16328
16665
|
const outdatedMatches = this.sortEnvironmentsByRecency(
|
|
16329
|
-
userEnvironments.filter(
|
|
16330
|
-
(environment) => this.matchesTagTrackedEnvironment(environment, tagName, tag.tagId)
|
|
16331
|
-
)
|
|
16666
|
+
userEnvironments.filter((environment2) => environment2.tagId === tag.tagId)
|
|
16332
16667
|
);
|
|
16333
16668
|
if (outdatedMatches.length > 0 && options.createFreshIfOutdated !== true) {
|
|
16669
|
+
const environment2 = await activate(outdatedMatches[0]);
|
|
16334
16670
|
return {
|
|
16335
|
-
environment:
|
|
16671
|
+
environment: environment2,
|
|
16336
16672
|
requestedOntology: ontology,
|
|
16337
16673
|
sandboxId: sandbox.sandboxId,
|
|
16338
|
-
subjectId: user.granularId
|
|
16674
|
+
subjectId: user.granularId,
|
|
16675
|
+
externalUserId: user.userId
|
|
16339
16676
|
};
|
|
16340
16677
|
}
|
|
16678
|
+
const created = await this.environments.create(sandbox.sandboxId, {
|
|
16679
|
+
subjectId: user.granularId,
|
|
16680
|
+
environment: this.buildManagedEnvironmentName(
|
|
16681
|
+
tagName,
|
|
16682
|
+
targetVersionId,
|
|
16683
|
+
resetKey
|
|
16684
|
+
),
|
|
16685
|
+
tagId: tag.tagId,
|
|
16686
|
+
permissionProfileId: null
|
|
16687
|
+
});
|
|
16688
|
+
const environment = await activate(created);
|
|
16341
16689
|
return {
|
|
16342
|
-
environment
|
|
16343
|
-
subjectId: user.granularId,
|
|
16344
|
-
environment: this.buildManagedEnvironmentName(tagName, targetVersionId),
|
|
16345
|
-
tagId: tag.tagId,
|
|
16346
|
-
versionId: targetVersionId,
|
|
16347
|
-
permissionProfileId: null
|
|
16348
|
-
}),
|
|
16690
|
+
environment,
|
|
16349
16691
|
requestedOntology: ontology,
|
|
16350
16692
|
sandboxId: sandbox.sandboxId,
|
|
16351
16693
|
subjectId: user.granularId,
|
|
16352
|
-
|
|
16694
|
+
externalUserId: user.userId,
|
|
16695
|
+
setupTriggerReason: resetKey ? "explicit_reset" : outdatedMatches.length > 0 ? "fresh_after_version_update" : "new_environment",
|
|
16696
|
+
setupOperationKey: resetKey
|
|
16353
16697
|
};
|
|
16354
16698
|
}
|
|
16355
16699
|
/**
|
|
16356
|
-
* List
|
|
16700
|
+
* List indexed sessions using ownership filters and bounded pagination.
|
|
16701
|
+
*/
|
|
16702
|
+
async listSessions(options) {
|
|
16703
|
+
const environmentId = options.environmentId?.trim();
|
|
16704
|
+
const sandboxId = options.sandboxId?.trim();
|
|
16705
|
+
const subjectId = options.subjectId?.trim();
|
|
16706
|
+
if (!environmentId && !sandboxId && !subjectId) {
|
|
16707
|
+
throw new Error(
|
|
16708
|
+
"listSessions() requires environmentId, sandboxId, or subjectId so history cannot be scanned accidentally."
|
|
16709
|
+
);
|
|
16710
|
+
}
|
|
16711
|
+
const status = options.status || "active";
|
|
16712
|
+
const allowedStatuses = /* @__PURE__ */ new Set([
|
|
16713
|
+
"active",
|
|
16714
|
+
"closed",
|
|
16715
|
+
"expired",
|
|
16716
|
+
"failed",
|
|
16717
|
+
"timeout",
|
|
16718
|
+
"all"
|
|
16719
|
+
]);
|
|
16720
|
+
if (!allowedStatuses.has(status)) {
|
|
16721
|
+
throw new Error(`Unsupported session status: ${String(status)}`);
|
|
16722
|
+
}
|
|
16723
|
+
const limit = boundedSessionListInteger(
|
|
16724
|
+
options.limit,
|
|
16725
|
+
"limit",
|
|
16726
|
+
DEFAULT_CONVERSATION_SESSION_LIST_LIMIT,
|
|
16727
|
+
1,
|
|
16728
|
+
MAX_CONVERSATION_SESSION_LIST_LIMIT
|
|
16729
|
+
);
|
|
16730
|
+
const offset = boundedSessionListInteger(
|
|
16731
|
+
options.offset,
|
|
16732
|
+
"offset",
|
|
16733
|
+
0,
|
|
16734
|
+
0,
|
|
16735
|
+
MAX_CONVERSATION_SESSION_LIST_OFFSET
|
|
16736
|
+
);
|
|
16737
|
+
const query = new URLSearchParams({
|
|
16738
|
+
limit: String(limit),
|
|
16739
|
+
offset: String(offset)
|
|
16740
|
+
});
|
|
16741
|
+
if (environmentId) query.set("environmentId", environmentId);
|
|
16742
|
+
if (sandboxId) query.set("sandboxId", sandboxId);
|
|
16743
|
+
if (subjectId) query.set("userId", subjectId);
|
|
16744
|
+
if (options.sessionScope?.trim()) {
|
|
16745
|
+
query.set("sessionScope", options.sessionScope.trim());
|
|
16746
|
+
}
|
|
16747
|
+
if (status !== "all") query.set("status", status);
|
|
16748
|
+
const res = await this.request(
|
|
16749
|
+
`/control/sessions?${query.toString()}`
|
|
16750
|
+
);
|
|
16751
|
+
const items = Array.isArray(res.items) ? res.items : [];
|
|
16752
|
+
return items.map((row) => this.normalizeConversationSession(row));
|
|
16753
|
+
}
|
|
16754
|
+
/**
|
|
16755
|
+
* List active (open) sessions for an environment.
|
|
16357
16756
|
*/
|
|
16358
16757
|
async listOpenSessions(filters) {
|
|
16359
|
-
return this.
|
|
16758
|
+
return this.listSessions({ ...filters, status: "active" });
|
|
16360
16759
|
}
|
|
16361
16760
|
/**
|
|
16362
16761
|
* List closed sessions for an environment (conversations that have disconnected).
|
|
16363
16762
|
*/
|
|
16364
16763
|
async listClosedSessions(filters) {
|
|
16365
|
-
return this.
|
|
16764
|
+
return this.listSessions({ ...filters, status: "closed" });
|
|
16366
16765
|
}
|
|
16367
16766
|
async getUserEnvironmentState(options) {
|
|
16368
16767
|
const query = new URLSearchParams({
|
|
@@ -16397,14 +16796,6 @@ var Granular = class _Granular {
|
|
|
16397
16796
|
});
|
|
16398
16797
|
return result.readAtBySessionId || {};
|
|
16399
16798
|
}
|
|
16400
|
-
async listSessionsForEnvironment(environmentId, status) {
|
|
16401
|
-
const query = new URLSearchParams({ environmentId, status });
|
|
16402
|
-
const res = await this.request(
|
|
16403
|
-
`/control/sessions?${query.toString()}`
|
|
16404
|
-
);
|
|
16405
|
-
const items = Array.isArray(res.items) ? res.items : [];
|
|
16406
|
-
return items.map((row) => this.normalizeConversationSession(row));
|
|
16407
|
-
}
|
|
16408
16799
|
normalizeConversationSession(row) {
|
|
16409
16800
|
const sessionId = String(row.sessionId ?? row.session_id ?? "");
|
|
16410
16801
|
const environmentId = String(row.environmentId ?? row.environment_id ?? "");
|
|
@@ -16463,6 +16854,7 @@ var Granular = class _Granular {
|
|
|
16463
16854
|
*/
|
|
16464
16855
|
async createSession(options) {
|
|
16465
16856
|
const clientId = options.clientId || `client_${Date.now()}`;
|
|
16857
|
+
const sessionScope = options.sessionScope?.trim() || void 0;
|
|
16466
16858
|
await this.activateEnvironment(options.environmentId);
|
|
16467
16859
|
const envData = await this.environments.get(options.environmentId);
|
|
16468
16860
|
const environment = this.bindEnvironmentHandle(envData);
|
|
@@ -16471,6 +16863,8 @@ var Granular = class _Granular {
|
|
|
16471
16863
|
body: JSON.stringify({
|
|
16472
16864
|
environmentId: options.environmentId,
|
|
16473
16865
|
clientId,
|
|
16866
|
+
sessionScope,
|
|
16867
|
+
capabilities: sessionScope ? { sessionScope } : void 0,
|
|
16474
16868
|
initialHeap: options.initialHeap
|
|
16475
16869
|
})
|
|
16476
16870
|
});
|
|
@@ -16578,11 +16972,24 @@ var Granular = class _Granular {
|
|
|
16578
16972
|
{
|
|
16579
16973
|
method: "POST",
|
|
16580
16974
|
body: JSON.stringify({
|
|
16581
|
-
triggerReason: resolved.setupTriggerReason
|
|
16975
|
+
triggerReason: resolved.setupTriggerReason,
|
|
16976
|
+
operationKey: resolved.setupOperationKey
|
|
16582
16977
|
})
|
|
16583
16978
|
}
|
|
16584
16979
|
);
|
|
16585
16980
|
const setupRunId = setupRun.setupRunId;
|
|
16981
|
+
let claim = null;
|
|
16982
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
16983
|
+
claim = await this.request(
|
|
16984
|
+
`/control/environment-setup-runs/${setupRunId}/importer-claim`,
|
|
16985
|
+
{ method: "POST", body: JSON.stringify({}) }
|
|
16986
|
+
);
|
|
16987
|
+
if (claim.action !== "busy") break;
|
|
16988
|
+
await sleep(Math.min(3e4, Math.max(250, claim.retryAfterMs || 1e3)));
|
|
16989
|
+
}
|
|
16990
|
+
if (!claim) {
|
|
16991
|
+
throw new Error(`Unable to claim environment setup run ${setupRunId}.`);
|
|
16992
|
+
}
|
|
16586
16993
|
const updateSetupRun = async (patch) => {
|
|
16587
16994
|
await this.request(
|
|
16588
16995
|
`/control/environment-setup-runs/${setupRunId}`,
|
|
@@ -16592,10 +16999,26 @@ var Granular = class _Granular {
|
|
|
16592
16999
|
}
|
|
16593
17000
|
);
|
|
16594
17001
|
};
|
|
17002
|
+
if (claim.action === "submitted") {
|
|
17003
|
+
const completedSetupRun = await this.request(
|
|
17004
|
+
`/control/environment-setup-runs/${setupRunId}`,
|
|
17005
|
+
{ method: "PATCH", body: JSON.stringify({ markHookCompleted: true }) }
|
|
17006
|
+
);
|
|
17007
|
+
const refreshedEnvironment = await this.environments.get(
|
|
17008
|
+
environment.environmentId
|
|
17009
|
+
);
|
|
17010
|
+
environment.syncEnvironmentData(refreshedEnvironment);
|
|
17011
|
+
return completedSetupRun;
|
|
17012
|
+
}
|
|
17013
|
+
if (claim.action === "busy" || claim.action === "terminal") {
|
|
17014
|
+
return claim.summary;
|
|
17015
|
+
}
|
|
17016
|
+
let importSequence = 0;
|
|
16595
17017
|
const importerContext = {
|
|
16596
17018
|
environmentId: environment.environmentId,
|
|
16597
17019
|
sandboxId: environment.sandboxId,
|
|
16598
17020
|
subjectId: environment.subjectId,
|
|
17021
|
+
externalUserId: resolved.externalUserId,
|
|
16599
17022
|
reason: resolved.setupTriggerReason,
|
|
16600
17023
|
incrementTotalObjectsToImportCount: async (n) => {
|
|
16601
17024
|
const safeIncrement = Math.max(0, Math.trunc(n));
|
|
@@ -16612,7 +17035,10 @@ var Granular = class _Granular {
|
|
|
16612
17035
|
importRecords: async (records, options) => environment.enqueueRecordImport(records, {
|
|
16613
17036
|
batchSize: options?.batchSize,
|
|
16614
17037
|
writeMode: options?.writeMode,
|
|
16615
|
-
setupRunId
|
|
17038
|
+
setupRunId,
|
|
17039
|
+
// Sequence is deterministic for a retry of one importer hook. It
|
|
17040
|
+
// prevents a Container restart from creating a second queue import.
|
|
17041
|
+
operationKey: `${setupRunId}:import:${importSequence++}`
|
|
16616
17042
|
})
|
|
16617
17043
|
};
|
|
16618
17044
|
try {
|
|
@@ -19662,6 +20088,7 @@ function buildGranularAgentSystemPrompt(input) {
|
|
|
19662
20088
|
- \`groundedObjects.save(...)\` only accepts scalar values, session files, runtime records/sandbox instances, or arrays of runtime records/sandbox instances from one class. Do not save plain action/effect result objects; fetch affected records first or answer from summaries with \`replyToUser(...)\`.
|
|
19663
20089
|
- Do not use \`showObjects({ entries: [...] })\` or \`showObjects({ saveAs, entries })\`. Save ordered pages, queues, search results, or ranked lists with \`groundedObjects.save(...)\`, then call \`showObjects({ variableNames: [...] })\` once.
|
|
19664
20090
|
- 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()\`.
|
|
20091
|
+
- Pass grounded records directly in \`show\` when the default record presentation answers the request. When the user asks for particular columns, comparisons, or computed values, import \`table\` (and \`relativeTime\` when useful) from \`@granular/agent\` and call \`showAgentResponse({ reply, show: table(records, [{ label: "Object", value: record => record.label }, { label: "When", value: record => relativeTime(record.timestamp) }]) })\`. Column callbacks must be synchronous and return a scalar, \`Date\`, or \`relativeTime(...)\`; they run inside the job and only resolved cells are persisted.
|
|
19665
20092
|
- Do not use deprecated side-channel helpers such as \`agent_text_message(...)\`, \`agent_heap_objects(...)\`, or \`agent_message(...)\` unless the generated types expose no Harness v3 helper alternative.
|
|
19666
20093
|
- When the user asks to show, list, display, open, or "show them" for records you found, call \`showObjects(...)\`; do not answer only with a count or text summary.
|
|
19667
20094
|
- For count-only questions such as "how many", "how many X do I have", or "what is the total number of X", call the entity \`.count(...)\` or use page \`totalCount\` only when a page is already needed for other reasons. Answer with \`replyToUser(...)\` only. Do not call \`showObjects(...)\`, \`saveAs\`, or \`groundedObjects.save(...)\` unless the user also asked to see records or a later requested action needs a reusable record selection.
|
|
@@ -19932,11 +20359,12 @@ ${actionIndex}
|
|
|
19932
20359
|
- Use typed plan fields and helpers directly. For visible blocker text, use the declared blocker-formatting helper when available instead of hand-written object casts.
|
|
19933
20360
|
- A suggestion is a recommendation button: \`await stateHandle.suggest(message)\`. A prepared action is the editable form the user can review/run: \`const action = stateHandle.reach(); const prepared = await action.open()\`.
|
|
19934
20361
|
- Use suggestions when the user asks "what can I do next?", asks for options, or gives an unclear intent. Prefer 2 to 5 concrete suggestions and keep text short.
|
|
19935
|
-
-
|
|
20362
|
+
- 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.
|
|
20363
|
+
- When the user explicitly commands an existing-record mutation such as approve, reject, block, route, send, or update, and one visible domain action uniquely matches, call that domain action directly. Do not substitute a state-handle \`.open()\` artifact for execution. If runtime policy requires confirmation, invoke the action once and let the runtime pause and resume that same invocation.
|
|
19936
20364
|
- When a grounded related/context record can satisfy the prepared action through declared relationships, use those relationships to fill required relationship inputs before opening or updating the action. If a required relationship remains empty, continue through declared relationship chains from the grounded object when the next hop can fill that slot. Do not only save the context record in memory while leaving derivable relationship slots blank.
|
|
19937
20365
|
- A derived intermediate relationship is not enough when another required relationship is still reachable from it. For example, if a team gives a cost center and the action also requires a budget, traverse the cost center's declared budget relationship before opening the action.
|
|
19938
20366
|
- If the user says they have a document/work item/event but no matching record is found, check for a declared class-level new-record state/action handle or importable backend create/preparation action for that named class before giving up. Use grounded required fields to open the prepared action or call the create/preparation action; if required values are still missing and no prepared action can collect them, ask only for those values. Do not claim the record already exists.
|
|
19939
|
-
- Do not ask for confirmation before opening a prepared action
|
|
20367
|
+
- Do not ask for confirmation before opening a prepared action requested for review; the artifact is itself reviewable. For a direct mutation command, do not open an artifact merely to obtain confirmation. Use \`userInteraction.askConfirmation\` only when the user explicitly asks for a separate yes/no step, material ambiguity remains after grounding, or policy requires confirmation outside the invoked action runtime.
|
|
19940
20368
|
- If the action cannot continue because of missing input, stale state, missing relationships, related-state requirements, or permissions, keep/show the prepared action at that blocker and explain the next needed person, record, or value. Do not skip workflow steps or target a later state.
|
|
19941
20369
|
- Reuse an already-open prepared action for the same target/action when available: update it, show it again, or explain what is still needed instead of creating a duplicate.
|
|
19942
20370
|
- If an open prepared action needs edits, prefer the returned record helper: \`const prepared = await action.open(); await prepared.updateInputs({ inputValues, relationships });\`. Use \`artifacts.updateInputs(id, patch)\` only when you only have an id.
|
|
@@ -20166,8 +20594,9 @@ function buildContinuationInstructionFromTemplate(resultPreview, options) {
|
|
|
20166
20594
|
}
|
|
20167
20595
|
|
|
20168
20596
|
// src/openai-usage.ts
|
|
20169
|
-
var
|
|
20170
|
-
var
|
|
20597
|
+
var OPENAI_GPT_5_4_PRICING_SOURCE_URL = "https://developers.openai.com/api/docs/models/gpt-5.4/";
|
|
20598
|
+
var OPENAI_GPT_5_6_PRICING_SOURCE_URL = "https://developers.openai.com/api/docs/pricing";
|
|
20599
|
+
var OPENAI_LONG_CONTEXT_THRESHOLD_TOKENS = 272e3;
|
|
20171
20600
|
var OPENAI_MODEL_PRICING_USD_PER_MILLION = {
|
|
20172
20601
|
"gpt-5.4": {
|
|
20173
20602
|
provider: "openai",
|
|
@@ -20176,8 +20605,26 @@ var OPENAI_MODEL_PRICING_USD_PER_MILLION = {
|
|
|
20176
20605
|
inputUsdPerMillion: 2.5,
|
|
20177
20606
|
cachedInputUsdPerMillion: 0.25,
|
|
20178
20607
|
outputUsdPerMillion: 15,
|
|
20179
|
-
sourceUrl:
|
|
20180
|
-
effectiveDate:
|
|
20608
|
+
sourceUrl: OPENAI_GPT_5_4_PRICING_SOURCE_URL,
|
|
20609
|
+
effectiveDate: "2026-05-19"
|
|
20610
|
+
},
|
|
20611
|
+
"gpt-5.6-luna": {
|
|
20612
|
+
provider: "openai",
|
|
20613
|
+
model: "gpt-5.6-luna",
|
|
20614
|
+
currency: "USD",
|
|
20615
|
+
inputUsdPerMillion: 1,
|
|
20616
|
+
cachedInputUsdPerMillion: 0.1,
|
|
20617
|
+
cacheWriteUsdPerMillion: 1.25,
|
|
20618
|
+
outputUsdPerMillion: 6,
|
|
20619
|
+
sourceUrl: OPENAI_GPT_5_6_PRICING_SOURCE_URL,
|
|
20620
|
+
effectiveDate: "2026-07-11",
|
|
20621
|
+
longContextThresholdTokens: OPENAI_LONG_CONTEXT_THRESHOLD_TOKENS,
|
|
20622
|
+
longContextPricing: {
|
|
20623
|
+
inputUsdPerMillion: 2,
|
|
20624
|
+
cachedInputUsdPerMillion: 0.2,
|
|
20625
|
+
cacheWriteUsdPerMillion: 2.5,
|
|
20626
|
+
outputUsdPerMillion: 9
|
|
20627
|
+
}
|
|
20181
20628
|
}
|
|
20182
20629
|
};
|
|
20183
20630
|
function asRecord5(value) {
|
|
@@ -20190,8 +20637,18 @@ function numberField(record, key) {
|
|
|
20190
20637
|
function microsPerMillion(usdPerMillion) {
|
|
20191
20638
|
return Math.round(usdPerMillion * 1e6);
|
|
20192
20639
|
}
|
|
20193
|
-
function getOpenAIModelPricing(model) {
|
|
20194
|
-
|
|
20640
|
+
function getOpenAIModelPricing(model, inputTokens = 0) {
|
|
20641
|
+
const pricing = OPENAI_MODEL_PRICING_USD_PER_MILLION[model];
|
|
20642
|
+
if (!pricing) return null;
|
|
20643
|
+
const threshold = pricing.longContextThresholdTokens ?? null;
|
|
20644
|
+
if (pricing.longContextPricing && typeof threshold === "number" && inputTokens > threshold) {
|
|
20645
|
+
return {
|
|
20646
|
+
...pricing,
|
|
20647
|
+
...pricing.longContextPricing,
|
|
20648
|
+
contextTier: "long"
|
|
20649
|
+
};
|
|
20650
|
+
}
|
|
20651
|
+
return { ...pricing, contextTier: "short" };
|
|
20195
20652
|
}
|
|
20196
20653
|
function normalizeOpenAIUsage(rawUsage) {
|
|
20197
20654
|
const usage = asRecord5(rawUsage);
|
|
@@ -20199,6 +20656,7 @@ function normalizeOpenAIUsage(rawUsage) {
|
|
|
20199
20656
|
return {
|
|
20200
20657
|
inputTokens: 0,
|
|
20201
20658
|
cachedInputTokens: 0,
|
|
20659
|
+
cacheWriteTokens: 0,
|
|
20202
20660
|
uncachedInputTokens: 0,
|
|
20203
20661
|
outputTokens: 0,
|
|
20204
20662
|
reasoningTokens: 0,
|
|
@@ -20214,20 +20672,28 @@ function normalizeOpenAIUsage(rawUsage) {
|
|
|
20214
20672
|
inputTokens,
|
|
20215
20673
|
numberField(inputDetails, "cached_tokens") || numberField(inputDetails, "cached_input_tokens")
|
|
20216
20674
|
);
|
|
20675
|
+
const cacheWriteTokens = Math.min(
|
|
20676
|
+
Math.max(inputTokens - cachedInputTokens, 0),
|
|
20677
|
+
numberField(inputDetails, "cache_write_tokens")
|
|
20678
|
+
);
|
|
20217
20679
|
const reasoningTokens = numberField(outputDetails, "reasoning_tokens") || numberField(outputDetails, "reasoning_output_tokens");
|
|
20218
20680
|
return {
|
|
20219
20681
|
inputTokens,
|
|
20220
20682
|
cachedInputTokens,
|
|
20221
|
-
|
|
20683
|
+
cacheWriteTokens,
|
|
20684
|
+
uncachedInputTokens: Math.max(
|
|
20685
|
+
inputTokens - cachedInputTokens - cacheWriteTokens,
|
|
20686
|
+
0
|
|
20687
|
+
),
|
|
20222
20688
|
outputTokens,
|
|
20223
20689
|
reasoningTokens,
|
|
20224
20690
|
totalTokens
|
|
20225
20691
|
};
|
|
20226
20692
|
}
|
|
20227
20693
|
function calculateOpenAITokenSpend(model, rawUsage) {
|
|
20228
|
-
const pricing = getOpenAIModelPricing(model);
|
|
20229
|
-
if (!pricing) return null;
|
|
20230
20694
|
const usage = normalizeOpenAIUsage(rawUsage);
|
|
20695
|
+
const pricing = getOpenAIModelPricing(model, usage.inputTokens);
|
|
20696
|
+
if (!pricing) return null;
|
|
20231
20697
|
const inputPricePerMillionMicros = microsPerMillion(
|
|
20232
20698
|
pricing.inputUsdPerMillion
|
|
20233
20699
|
);
|
|
@@ -20237,14 +20703,19 @@ function calculateOpenAITokenSpend(model, rawUsage) {
|
|
|
20237
20703
|
const outputPricePerMillionMicros = microsPerMillion(
|
|
20238
20704
|
pricing.outputUsdPerMillion
|
|
20239
20705
|
);
|
|
20706
|
+
const cacheWritePricePerMillionMicros = typeof pricing.cacheWriteUsdPerMillion === "number" ? microsPerMillion(pricing.cacheWriteUsdPerMillion) : null;
|
|
20707
|
+
const cacheWriteCostMicros = Math.round(
|
|
20708
|
+
usage.cacheWriteTokens * (cacheWritePricePerMillionMicros ?? inputPricePerMillionMicros) / 1e6
|
|
20709
|
+
);
|
|
20240
20710
|
const amountMicros = Math.round(
|
|
20241
|
-
(usage.uncachedInputTokens * inputPricePerMillionMicros + usage.cachedInputTokens * cachedInputPricePerMillionMicros + usage.outputTokens * outputPricePerMillionMicros) / 1e6
|
|
20711
|
+
(usage.uncachedInputTokens * inputPricePerMillionMicros + usage.cachedInputTokens * cachedInputPricePerMillionMicros + usage.cacheWriteTokens * (cacheWritePricePerMillionMicros ?? inputPricePerMillionMicros) + usage.outputTokens * outputPricePerMillionMicros) / 1e6
|
|
20242
20712
|
);
|
|
20243
20713
|
return {
|
|
20244
20714
|
provider: "openai",
|
|
20245
20715
|
model,
|
|
20246
20716
|
inputTokens: usage.inputTokens,
|
|
20247
20717
|
cachedInputTokens: usage.cachedInputTokens,
|
|
20718
|
+
cacheWriteTokens: usage.cacheWriteTokens,
|
|
20248
20719
|
uncachedInputTokens: usage.uncachedInputTokens,
|
|
20249
20720
|
outputTokens: usage.outputTokens,
|
|
20250
20721
|
reasoningTokens: usage.reasoningTokens,
|
|
@@ -20253,7 +20724,11 @@ function calculateOpenAITokenSpend(model, rawUsage) {
|
|
|
20253
20724
|
currency: "USD",
|
|
20254
20725
|
inputPricePerMillionMicros,
|
|
20255
20726
|
cachedInputPricePerMillionMicros,
|
|
20727
|
+
cacheWritePricePerMillionMicros,
|
|
20728
|
+
cacheWriteCostMicros,
|
|
20256
20729
|
outputPricePerMillionMicros,
|
|
20730
|
+
pricingContextTier: pricing.contextTier || "short",
|
|
20731
|
+
longContextThresholdTokens: pricing.longContextThresholdTokens ?? null,
|
|
20257
20732
|
pricingSource: pricing.sourceUrl,
|
|
20258
20733
|
pricingEffectiveAt: pricing.effectiveDate,
|
|
20259
20734
|
usage
|