@granular-software/sdk 0.4.19 → 0.4.21
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 +44 -4
- package/dist/agent-evals.d.mts +1 -1
- package/dist/agent-evals.d.ts +1 -1
- package/dist/agent-evals.js +169 -36
- package/dist/agent-evals.js.map +1 -1
- package/dist/agent-evals.mjs +169 -36
- package/dist/agent-evals.mjs.map +1 -1
- package/dist/agent-harness.js +5 -2
- package/dist/agent-harness.js.map +1 -1
- package/dist/agent-harness.mjs +5 -2
- package/dist/agent-harness.mjs.map +1 -1
- package/dist/cli/index.js +162 -31
- package/dist/{client-BQw_gUK3.d.mts → client-DLGC0mJk.d.mts} +67 -6
- package/dist/{client-BQw_gUK3.d.ts → client-DLGC0mJk.d.ts} +67 -6
- package/dist/index.d.mts +18 -3
- package/dist/index.d.ts +18 -3
- package/dist/index.js +166 -30
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +164 -31
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/agent-evals.mjs
CHANGED
|
@@ -4662,10 +4662,15 @@ var Session = class {
|
|
|
4662
4662
|
* ```typescript
|
|
4663
4663
|
* import { Author, Book, global_search } from './sandbox-tools';
|
|
4664
4664
|
*
|
|
4665
|
-
* const
|
|
4665
|
+
* const totalAuthors = await Author.count();
|
|
4666
|
+
* const firstAuthorsPage = await Author.page({ page: 1, perPage: 10, saveAs: 'recent_authors' });
|
|
4667
|
+
* const authors = firstAuthorsPage.items;
|
|
4666
4668
|
* const tolkien = await Author.get({ path: 'author_tolkien' });
|
|
4667
4669
|
* const bio = await tolkien.get_bio({ detailed: true });
|
|
4668
4670
|
* const books = await tolkien.get_books();
|
|
4671
|
+
* for await (const author of Author.iterate({ perPage: 100, maxItems: 500 })) {
|
|
4672
|
+
* console.log(author.id);
|
|
4673
|
+
* }
|
|
4669
4674
|
* ```
|
|
4670
4675
|
*
|
|
4671
4676
|
* Effect calls (instance methods, static methods, global functions) trigger
|
|
@@ -11223,14 +11228,49 @@ var STANDARD_MODULES_OPERATIONS = [
|
|
|
11223
11228
|
{ create: "class", extends: "entity", has: {} },
|
|
11224
11229
|
{ create: "user", extends: "entity", has: { email: { value: void 0 }, firstName: { value: void 0 }, lastName: { value: void 0 } } },
|
|
11225
11230
|
{ create: "company", extends: "entity", has: { name: { value: void 0 }, website: { value: void 0 } } },
|
|
11226
|
-
{ create: "string", has: {
|
|
11227
|
-
{ create: "number", has: {
|
|
11228
|
-
{ create: "boolean", has: {
|
|
11231
|
+
{ create: "string", has: {} },
|
|
11232
|
+
{ create: "number", has: {} },
|
|
11233
|
+
{ create: "boolean", has: {} },
|
|
11229
11234
|
{ create: "tool_parameter", has: { name: { value: void 0 }, type: { value: "string" }, description: { value: void 0 }, required: { value: false } } }
|
|
11230
11235
|
];
|
|
11231
11236
|
var BUILTIN_MODULES = {
|
|
11232
11237
|
"standard_modules": STANDARD_MODULES_OPERATIONS
|
|
11233
11238
|
};
|
|
11239
|
+
var DEFAULT_DIRECT_RECORD_OBJECTS_REQUEST_BATCH_SIZE = 100;
|
|
11240
|
+
var MAX_RECORD_OBJECTS_CONCURRENCY = 16;
|
|
11241
|
+
var DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_COUNT = 3;
|
|
11242
|
+
var DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_DELAY_MS = 1e3;
|
|
11243
|
+
var LOCAL_CONTROL_REQUEST_RETRY_COUNT = 4;
|
|
11244
|
+
var LOCAL_CONTROL_REQUEST_RETRY_DELAY_MS = 500;
|
|
11245
|
+
function planRecordObjectsChunks(records, batchSize) {
|
|
11246
|
+
const total = records.length;
|
|
11247
|
+
const size = Math.max(1, Math.min(batchSize, total));
|
|
11248
|
+
const chunkCount = Math.ceil(total / size);
|
|
11249
|
+
const plans = [];
|
|
11250
|
+
for (let offset = 0, chunkIndex = 0; offset < total; offset += size, chunkIndex += 1) {
|
|
11251
|
+
const slice = records.slice(offset, offset + size);
|
|
11252
|
+
plans.push({ chunkIndex, chunkCount, offset, slice });
|
|
11253
|
+
}
|
|
11254
|
+
return plans;
|
|
11255
|
+
}
|
|
11256
|
+
function sleep(ms) {
|
|
11257
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
11258
|
+
}
|
|
11259
|
+
function isLocalControlUrl(url) {
|
|
11260
|
+
try {
|
|
11261
|
+
const parsed = new URL(url);
|
|
11262
|
+
return parsed.hostname === "127.0.0.1" || parsed.hostname === "localhost" || parsed.hostname === "::1";
|
|
11263
|
+
} catch {
|
|
11264
|
+
return false;
|
|
11265
|
+
}
|
|
11266
|
+
}
|
|
11267
|
+
function isRetryableLocalWorkerRestart(status, body, url) {
|
|
11268
|
+
return isLocalControlUrl(url) && (status === 503 && body.includes("Your worker restarted mid-request") || status === 500 && body.includes("Network connection lost"));
|
|
11269
|
+
}
|
|
11270
|
+
function isRetryableRecordObjectsError(error) {
|
|
11271
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
11272
|
+
return /worker restarted mid-request|econnreset|network|socket connection was closed unexpectedly|timed out/i.test(message);
|
|
11273
|
+
}
|
|
11234
11274
|
function computeEffectKey2(effect) {
|
|
11235
11275
|
const attachedClass = effect.className?.trim();
|
|
11236
11276
|
if (!attachedClass) {
|
|
@@ -12188,24 +12228,101 @@ var Environment = class extends Session {
|
|
|
12188
12228
|
/**
|
|
12189
12229
|
* Batch version of `recordObject()`.
|
|
12190
12230
|
*
|
|
12191
|
-
* Sends
|
|
12192
|
-
*
|
|
12231
|
+
* Sends rows through the control-plane **`/records/batch`** endpoint in **chunks** (default
|
|
12232
|
+
* **100** records per HTTP request) so individual requests stay bounded and gateway timeouts are
|
|
12233
|
+
* unlikely. Each chunk is retried on transient network / worker errors.
|
|
12234
|
+
*
|
|
12235
|
+
* Use the optional second argument to:
|
|
12236
|
+
* - **`batchSize`** — rows per POST (smaller = more progress events; larger = fewer round trips).
|
|
12237
|
+
* - **`concurrency`** — run up to N chunk POSTs in parallel (capped at 16) when you want lower wall time.
|
|
12238
|
+
* - **`onChunkComplete`** — hook for UIs after each chunk succeeds (row order in the returned array
|
|
12239
|
+
* always matches `records`; chunk **completion** order may differ when `concurrency > 1`).
|
|
12240
|
+
*
|
|
12241
|
+
* For **asynchronous** ingestion with worker-side batching and aggregate counters (`queued`,
|
|
12242
|
+
* `completed`, …), use **`enqueueRecordImport`** and poll **`getRecordImport`** /
|
|
12243
|
+
* **`getRecordImportSummary`** — best for very large fire-and-forget loads when immediate
|
|
12244
|
+
* synchronous commit of every row is not required.
|
|
12193
12245
|
*/
|
|
12194
|
-
async recordObjects(records) {
|
|
12246
|
+
async recordObjects(records, options) {
|
|
12195
12247
|
if (!Array.isArray(records) || records.length === 0) {
|
|
12196
12248
|
return [];
|
|
12197
12249
|
}
|
|
12198
|
-
const
|
|
12199
|
-
|
|
12200
|
-
|
|
12201
|
-
|
|
12202
|
-
|
|
12203
|
-
|
|
12250
|
+
const batchSize = Math.max(
|
|
12251
|
+
1,
|
|
12252
|
+
Math.min(
|
|
12253
|
+
records.length,
|
|
12254
|
+
options?.batchSize ?? DEFAULT_DIRECT_RECORD_OBJECTS_REQUEST_BATCH_SIZE
|
|
12255
|
+
)
|
|
12204
12256
|
);
|
|
12205
|
-
|
|
12257
|
+
const concurrency = Math.min(
|
|
12258
|
+
MAX_RECORD_OBJECTS_CONCURRENCY,
|
|
12259
|
+
Math.max(1, options?.concurrency ?? 1)
|
|
12260
|
+
);
|
|
12261
|
+
const plans = planRecordObjectsChunks(records, batchSize);
|
|
12262
|
+
const total = records.length;
|
|
12263
|
+
const results = new Array(total);
|
|
12264
|
+
const onChunk = options?.onChunkComplete;
|
|
12265
|
+
for (let waveStart = 0; waveStart < plans.length; waveStart += concurrency) {
|
|
12266
|
+
const wave = plans.slice(waveStart, waveStart + concurrency);
|
|
12267
|
+
await Promise.all(
|
|
12268
|
+
wave.map(async (plan) => {
|
|
12269
|
+
const { items, durationMs } = await this.executeRecordObjectsChunk(plan.slice);
|
|
12270
|
+
if (items.length !== plan.slice.length) {
|
|
12271
|
+
throw new Error(
|
|
12272
|
+
`recordObjects: chunk ${plan.chunkIndex + 1} returned ${items.length} results, expected ${plan.slice.length}`
|
|
12273
|
+
);
|
|
12274
|
+
}
|
|
12275
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
12276
|
+
results[plan.offset + index] = items[index];
|
|
12277
|
+
}
|
|
12278
|
+
if (onChunk) {
|
|
12279
|
+
const info = {
|
|
12280
|
+
chunkIndex: plan.chunkIndex,
|
|
12281
|
+
totalChunks: plan.chunkCount,
|
|
12282
|
+
offset: plan.offset,
|
|
12283
|
+
recordCount: plan.slice.length,
|
|
12284
|
+
durationMs,
|
|
12285
|
+
results: items
|
|
12286
|
+
};
|
|
12287
|
+
await onChunk(info);
|
|
12288
|
+
}
|
|
12289
|
+
})
|
|
12290
|
+
);
|
|
12291
|
+
}
|
|
12292
|
+
return results;
|
|
12293
|
+
}
|
|
12294
|
+
async executeRecordObjectsChunk(chunk) {
|
|
12295
|
+
const wallStart = Date.now();
|
|
12296
|
+
let lastError;
|
|
12297
|
+
for (let attempt = 1; attempt <= DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_COUNT; attempt += 1) {
|
|
12298
|
+
try {
|
|
12299
|
+
const response = await this.controlPlaneRequest(
|
|
12300
|
+
`/control/environments/${this.environmentId}/records/batch`,
|
|
12301
|
+
{
|
|
12302
|
+
method: "POST",
|
|
12303
|
+
body: JSON.stringify({ records: chunk })
|
|
12304
|
+
}
|
|
12305
|
+
);
|
|
12306
|
+
const items = Array.isArray(response.items) ? response.items : [];
|
|
12307
|
+
return { items, durationMs: Date.now() - wallStart };
|
|
12308
|
+
} catch (error) {
|
|
12309
|
+
lastError = error;
|
|
12310
|
+
if (!isRetryableRecordObjectsError(error) || attempt === DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_COUNT) {
|
|
12311
|
+
throw error;
|
|
12312
|
+
}
|
|
12313
|
+
await sleep(DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_DELAY_MS * attempt);
|
|
12314
|
+
}
|
|
12315
|
+
}
|
|
12316
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
12206
12317
|
}
|
|
12207
12318
|
/**
|
|
12208
|
-
* Queue a background record import for this environment.
|
|
12319
|
+
* Queue a background record import for this environment (async worker pipeline).
|
|
12320
|
+
*
|
|
12321
|
+
* **vs `recordObjects`:** this path accepts the full payload in one request, returns an
|
|
12322
|
+
* **`importId`**, and processes rows in the background — use **`getRecordImport`** /
|
|
12323
|
+
* **`getRecordImportSummary`** for progress. Choose it for large bulk loads where you do not
|
|
12324
|
+
* need every row committed before the HTTP call returns. Use **`recordObjects`** when you need
|
|
12325
|
+
* synchronous upserts and fine-grained chunk progress via **`onChunkComplete`**.
|
|
12209
12326
|
*/
|
|
12210
12327
|
async enqueueRecordImport(records, options = {}) {
|
|
12211
12328
|
return this.controlPlaneRequest(
|
|
@@ -13173,23 +13290,36 @@ var Granular = class _Granular {
|
|
|
13173
13290
|
if (this.debugHttp) {
|
|
13174
13291
|
console.log(`[SDK] Requesting: ${url}`);
|
|
13175
13292
|
}
|
|
13176
|
-
|
|
13177
|
-
|
|
13178
|
-
|
|
13179
|
-
|
|
13180
|
-
|
|
13181
|
-
|
|
13182
|
-
|
|
13293
|
+
for (let attempt = 1; attempt <= LOCAL_CONTROL_REQUEST_RETRY_COUNT; attempt += 1) {
|
|
13294
|
+
const response = await fetch(url, {
|
|
13295
|
+
...options,
|
|
13296
|
+
headers: {
|
|
13297
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
13298
|
+
"Content-Type": "application/json",
|
|
13299
|
+
"Connection": "close",
|
|
13300
|
+
...options.headers
|
|
13301
|
+
}
|
|
13302
|
+
});
|
|
13303
|
+
if (response.ok) {
|
|
13304
|
+
if (response.status === 204) {
|
|
13305
|
+
return { deleted: true };
|
|
13306
|
+
}
|
|
13307
|
+
return response.json();
|
|
13183
13308
|
}
|
|
13184
|
-
});
|
|
13185
|
-
if (!response.ok) {
|
|
13186
13309
|
const errorText = await response.text();
|
|
13310
|
+
const retryable = isRetryableLocalWorkerRestart(response.status, errorText, url);
|
|
13311
|
+
if (retryable && attempt < LOCAL_CONTROL_REQUEST_RETRY_COUNT) {
|
|
13312
|
+
if (this.debugHttp) {
|
|
13313
|
+
console.warn(
|
|
13314
|
+
`[SDK] Retrying local control request after worker restart (${attempt}/${LOCAL_CONTROL_REQUEST_RETRY_COUNT - 1} retries used): ${url}`
|
|
13315
|
+
);
|
|
13316
|
+
}
|
|
13317
|
+
await sleep(LOCAL_CONTROL_REQUEST_RETRY_DELAY_MS * attempt);
|
|
13318
|
+
continue;
|
|
13319
|
+
}
|
|
13187
13320
|
throw new Error(`Granular API Error (${response.status}): ${errorText}`);
|
|
13188
13321
|
}
|
|
13189
|
-
|
|
13190
|
-
return { deleted: true };
|
|
13191
|
-
}
|
|
13192
|
-
return response.json();
|
|
13322
|
+
throw new Error(`Granular API Error: exhausted retries for ${url}`);
|
|
13193
13323
|
}
|
|
13194
13324
|
};
|
|
13195
13325
|
|
|
@@ -14110,7 +14240,10 @@ ${loopBlock}
|
|
|
14110
14240
|
- Only use classes, methods, and parameter shapes that are explicitly declared in those typedefs.
|
|
14111
14241
|
- Never invent helper methods such as \`find(...)\` or unsupported parameters such as \`id\` when the typedefs require \`path\`.
|
|
14112
14242
|
- Use \`ClassName.get({ path })\` only when you already know an object's graph path.
|
|
14113
|
-
- Use \`ClassName.
|
|
14243
|
+
- Use \`ClassName.count()\` when you only need a total.
|
|
14244
|
+
- Use \`ClassName.page({ page, perPage, saveAs })\` when you need both records and pagination metadata like \`totalCount\` or \`hasMore\`.
|
|
14245
|
+
- Use \`ClassName.list({ page, perPage, saveAs })\` to load one typed page of records. \`limit\` is only a legacy alias for \`perPage\`.
|
|
14246
|
+
- Use \`for await (const item of ClassName.iterate({ perPage, maxItems }))\` for large batch jobs so you do not materialize the whole result set at once.
|
|
14114
14247
|
- Instance methods: \`await instance.method_name(params)\`.
|
|
14115
14248
|
- Static methods: \`await ClassName.static_method(params)\`.
|
|
14116
14249
|
- Global effects: \`await effect_name(params)\`.
|
|
@@ -14122,7 +14255,7 @@ ${loopBlock}
|
|
|
14122
14255
|
- Status fields are free-form operational strings, not strict enums. Normalize spelling mentally and do not rely on brittle hard-coded sets that miss variants like \`in-progress\`, \`in_progress\`, \`awaiting-part\`, or \`approval-submitted\`.
|
|
14123
14256
|
- Do not discard a case, work order, part request, or shipment only because its status string does not match your preferred "open" spelling. If the record is otherwise the clear match, inspect it.
|
|
14124
14257
|
- Reuse \`heap.getVar(name)\`, \`heap.setVar(name, value)\`, and \`heap.deleteVar(name)\` only when it clearly helps the next step. Do not mirror data into the heap just for completeness.
|
|
14125
|
-
- Prefer \`heap.setVar(name, value)\` for scalars or one selected instance. Prefer \`ClassName.list({ saveAs })\` for
|
|
14258
|
+
- Prefer \`heap.setVar(name, value)\` for scalars or one selected instance. Prefer \`ClassName.list({ page, perPage, saveAs })\` for reusable list pages instead of \`heap.setVar(name, array)\`.
|
|
14126
14259
|
- Never write an empty array into the heap. If a filtered list is empty, keep it local or clear the previous heap value with \`heap.deleteVar(name)\`.
|
|
14127
14260
|
- Prefer heap-backed state that represents the current choice or recommendation. Avoid storing extra scalar bookkeeping unless it is needed for the next concrete step.
|
|
14128
14261
|
- Only store true sandbox instances, typed lists of sandbox instances, or scalars in the heap. Results returned by static effects like availability/search helpers are often plain JSON, not sandbox instances.
|
|
@@ -14564,7 +14697,7 @@ ${modelOutputInstruction()}` },
|
|
|
14564
14697
|
if (!response.ok) {
|
|
14565
14698
|
const errorText = await response.text();
|
|
14566
14699
|
if (attempt < 3 && (response.status >= 500 || response.status === 429)) {
|
|
14567
|
-
await
|
|
14700
|
+
await sleep2(500 * attempt);
|
|
14568
14701
|
continue;
|
|
14569
14702
|
}
|
|
14570
14703
|
throw new Error(`OpenAI chat generation failed: ${response.status} ${errorText}`);
|
|
@@ -14575,7 +14708,7 @@ ${modelOutputInstruction()}` },
|
|
|
14575
14708
|
const parsed = extractJsonObject(text);
|
|
14576
14709
|
if (!parsed) {
|
|
14577
14710
|
if (attempt < 3) {
|
|
14578
|
-
await
|
|
14711
|
+
await sleep2(300 * attempt);
|
|
14579
14712
|
continue;
|
|
14580
14713
|
}
|
|
14581
14714
|
throw new Error(`Model output was not valid JSON:
|
|
@@ -14589,7 +14722,7 @@ ${text}`);
|
|
|
14589
14722
|
} catch (error) {
|
|
14590
14723
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
14591
14724
|
if (attempt < 3 && /socket connection was closed unexpectedly|ECONNRESET|network|timed out/i.test(lastError.message)) {
|
|
14592
|
-
await
|
|
14725
|
+
await sleep2(500 * attempt);
|
|
14593
14726
|
continue;
|
|
14594
14727
|
}
|
|
14595
14728
|
throw lastError;
|
|
@@ -14601,7 +14734,7 @@ ${text}`);
|
|
|
14601
14734
|
async function ensureDir(dir) {
|
|
14602
14735
|
await mkdir(dir, { recursive: true });
|
|
14603
14736
|
}
|
|
14604
|
-
async function
|
|
14737
|
+
async function sleep2(ms) {
|
|
14605
14738
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
14606
14739
|
}
|
|
14607
14740
|
async function withTimeout(promise, ms, label) {
|
|
@@ -15044,7 +15177,7 @@ function createAgentEvalHarness(options) {
|
|
|
15044
15177
|
stderr: [...pending.stderr, ...resumed.stderr]
|
|
15045
15178
|
};
|
|
15046
15179
|
}
|
|
15047
|
-
await
|
|
15180
|
+
await sleep2(350);
|
|
15048
15181
|
const liveDoc = cloneJson(pending.conversation.environment.document);
|
|
15049
15182
|
const presentation = resolveJobPresentation({
|
|
15050
15183
|
jobId: pending.job.id,
|
|
@@ -15213,7 +15346,7 @@ function createAgentEvalHarness(options) {
|
|
|
15213
15346
|
if (outcome.kind !== "completed") {
|
|
15214
15347
|
throw new Error("Unexpected non-completed outcome after prompt handling");
|
|
15215
15348
|
}
|
|
15216
|
-
await
|
|
15349
|
+
await sleep2(350);
|
|
15217
15350
|
const settledLiveDoc = cloneJson(conversation.environment.document);
|
|
15218
15351
|
const sessionHeap = normalizeHeapSnapshot2(asRecord4(settledLiveDoc?.heap));
|
|
15219
15352
|
const presentation = resolveJobPresentation({
|