@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/index.js
CHANGED
|
@@ -4682,10 +4682,15 @@ var Session = class {
|
|
|
4682
4682
|
* ```typescript
|
|
4683
4683
|
* import { Author, Book, global_search } from './sandbox-tools';
|
|
4684
4684
|
*
|
|
4685
|
-
* const
|
|
4685
|
+
* const totalAuthors = await Author.count();
|
|
4686
|
+
* const firstAuthorsPage = await Author.page({ page: 1, perPage: 10, saveAs: 'recent_authors' });
|
|
4687
|
+
* const authors = firstAuthorsPage.items;
|
|
4686
4688
|
* const tolkien = await Author.get({ path: 'author_tolkien' });
|
|
4687
4689
|
* const bio = await tolkien.get_bio({ detailed: true });
|
|
4688
4690
|
* const books = await tolkien.get_books();
|
|
4691
|
+
* for await (const author of Author.iterate({ perPage: 100, maxItems: 500 })) {
|
|
4692
|
+
* console.log(author.id);
|
|
4693
|
+
* }
|
|
4689
4694
|
* ```
|
|
4690
4695
|
*
|
|
4691
4696
|
* Effect calls (instance methods, static methods, global functions) trigger
|
|
@@ -11243,14 +11248,49 @@ var STANDARD_MODULES_OPERATIONS = [
|
|
|
11243
11248
|
{ create: "class", extends: "entity", has: {} },
|
|
11244
11249
|
{ create: "user", extends: "entity", has: { email: { value: void 0 }, firstName: { value: void 0 }, lastName: { value: void 0 } } },
|
|
11245
11250
|
{ create: "company", extends: "entity", has: { name: { value: void 0 }, website: { value: void 0 } } },
|
|
11246
|
-
{ create: "string", has: {
|
|
11247
|
-
{ create: "number", has: {
|
|
11248
|
-
{ create: "boolean", has: {
|
|
11251
|
+
{ create: "string", has: {} },
|
|
11252
|
+
{ create: "number", has: {} },
|
|
11253
|
+
{ create: "boolean", has: {} },
|
|
11249
11254
|
{ create: "tool_parameter", has: { name: { value: void 0 }, type: { value: "string" }, description: { value: void 0 }, required: { value: false } } }
|
|
11250
11255
|
];
|
|
11251
11256
|
var BUILTIN_MODULES = {
|
|
11252
11257
|
"standard_modules": STANDARD_MODULES_OPERATIONS
|
|
11253
11258
|
};
|
|
11259
|
+
var DEFAULT_DIRECT_RECORD_OBJECTS_REQUEST_BATCH_SIZE = 100;
|
|
11260
|
+
var MAX_RECORD_OBJECTS_CONCURRENCY = 16;
|
|
11261
|
+
var DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_COUNT = 3;
|
|
11262
|
+
var DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_DELAY_MS = 1e3;
|
|
11263
|
+
var LOCAL_CONTROL_REQUEST_RETRY_COUNT = 4;
|
|
11264
|
+
var LOCAL_CONTROL_REQUEST_RETRY_DELAY_MS = 500;
|
|
11265
|
+
function planRecordObjectsChunks(records, batchSize) {
|
|
11266
|
+
const total = records.length;
|
|
11267
|
+
const size = Math.max(1, Math.min(batchSize, total));
|
|
11268
|
+
const chunkCount = Math.ceil(total / size);
|
|
11269
|
+
const plans = [];
|
|
11270
|
+
for (let offset = 0, chunkIndex = 0; offset < total; offset += size, chunkIndex += 1) {
|
|
11271
|
+
const slice = records.slice(offset, offset + size);
|
|
11272
|
+
plans.push({ chunkIndex, chunkCount, offset, slice });
|
|
11273
|
+
}
|
|
11274
|
+
return plans;
|
|
11275
|
+
}
|
|
11276
|
+
function sleep(ms) {
|
|
11277
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
11278
|
+
}
|
|
11279
|
+
function isLocalControlUrl(url) {
|
|
11280
|
+
try {
|
|
11281
|
+
const parsed = new URL(url);
|
|
11282
|
+
return parsed.hostname === "127.0.0.1" || parsed.hostname === "localhost" || parsed.hostname === "::1";
|
|
11283
|
+
} catch {
|
|
11284
|
+
return false;
|
|
11285
|
+
}
|
|
11286
|
+
}
|
|
11287
|
+
function isRetryableLocalWorkerRestart(status, body, url) {
|
|
11288
|
+
return isLocalControlUrl(url) && (status === 503 && body.includes("Your worker restarted mid-request") || status === 500 && body.includes("Network connection lost"));
|
|
11289
|
+
}
|
|
11290
|
+
function isRetryableRecordObjectsError(error) {
|
|
11291
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
11292
|
+
return /worker restarted mid-request|econnreset|network|socket connection was closed unexpectedly|timed out/i.test(message);
|
|
11293
|
+
}
|
|
11254
11294
|
function computeEffectKey2(effect) {
|
|
11255
11295
|
const attachedClass = effect.className?.trim();
|
|
11256
11296
|
if (!attachedClass) {
|
|
@@ -12208,24 +12248,101 @@ var Environment = class extends Session {
|
|
|
12208
12248
|
/**
|
|
12209
12249
|
* Batch version of `recordObject()`.
|
|
12210
12250
|
*
|
|
12211
|
-
* Sends
|
|
12212
|
-
*
|
|
12251
|
+
* Sends rows through the control-plane **`/records/batch`** endpoint in **chunks** (default
|
|
12252
|
+
* **100** records per HTTP request) so individual requests stay bounded and gateway timeouts are
|
|
12253
|
+
* unlikely. Each chunk is retried on transient network / worker errors.
|
|
12254
|
+
*
|
|
12255
|
+
* Use the optional second argument to:
|
|
12256
|
+
* - **`batchSize`** — rows per POST (smaller = more progress events; larger = fewer round trips).
|
|
12257
|
+
* - **`concurrency`** — run up to N chunk POSTs in parallel (capped at 16) when you want lower wall time.
|
|
12258
|
+
* - **`onChunkComplete`** — hook for UIs after each chunk succeeds (row order in the returned array
|
|
12259
|
+
* always matches `records`; chunk **completion** order may differ when `concurrency > 1`).
|
|
12260
|
+
*
|
|
12261
|
+
* For **asynchronous** ingestion with worker-side batching and aggregate counters (`queued`,
|
|
12262
|
+
* `completed`, …), use **`enqueueRecordImport`** and poll **`getRecordImport`** /
|
|
12263
|
+
* **`getRecordImportSummary`** — best for very large fire-and-forget loads when immediate
|
|
12264
|
+
* synchronous commit of every row is not required.
|
|
12213
12265
|
*/
|
|
12214
|
-
async recordObjects(records) {
|
|
12266
|
+
async recordObjects(records, options) {
|
|
12215
12267
|
if (!Array.isArray(records) || records.length === 0) {
|
|
12216
12268
|
return [];
|
|
12217
12269
|
}
|
|
12218
|
-
const
|
|
12219
|
-
|
|
12220
|
-
|
|
12221
|
-
|
|
12222
|
-
|
|
12223
|
-
|
|
12270
|
+
const batchSize = Math.max(
|
|
12271
|
+
1,
|
|
12272
|
+
Math.min(
|
|
12273
|
+
records.length,
|
|
12274
|
+
options?.batchSize ?? DEFAULT_DIRECT_RECORD_OBJECTS_REQUEST_BATCH_SIZE
|
|
12275
|
+
)
|
|
12224
12276
|
);
|
|
12225
|
-
|
|
12277
|
+
const concurrency = Math.min(
|
|
12278
|
+
MAX_RECORD_OBJECTS_CONCURRENCY,
|
|
12279
|
+
Math.max(1, options?.concurrency ?? 1)
|
|
12280
|
+
);
|
|
12281
|
+
const plans = planRecordObjectsChunks(records, batchSize);
|
|
12282
|
+
const total = records.length;
|
|
12283
|
+
const results = new Array(total);
|
|
12284
|
+
const onChunk = options?.onChunkComplete;
|
|
12285
|
+
for (let waveStart = 0; waveStart < plans.length; waveStart += concurrency) {
|
|
12286
|
+
const wave = plans.slice(waveStart, waveStart + concurrency);
|
|
12287
|
+
await Promise.all(
|
|
12288
|
+
wave.map(async (plan) => {
|
|
12289
|
+
const { items, durationMs } = await this.executeRecordObjectsChunk(plan.slice);
|
|
12290
|
+
if (items.length !== plan.slice.length) {
|
|
12291
|
+
throw new Error(
|
|
12292
|
+
`recordObjects: chunk ${plan.chunkIndex + 1} returned ${items.length} results, expected ${plan.slice.length}`
|
|
12293
|
+
);
|
|
12294
|
+
}
|
|
12295
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
12296
|
+
results[plan.offset + index] = items[index];
|
|
12297
|
+
}
|
|
12298
|
+
if (onChunk) {
|
|
12299
|
+
const info = {
|
|
12300
|
+
chunkIndex: plan.chunkIndex,
|
|
12301
|
+
totalChunks: plan.chunkCount,
|
|
12302
|
+
offset: plan.offset,
|
|
12303
|
+
recordCount: plan.slice.length,
|
|
12304
|
+
durationMs,
|
|
12305
|
+
results: items
|
|
12306
|
+
};
|
|
12307
|
+
await onChunk(info);
|
|
12308
|
+
}
|
|
12309
|
+
})
|
|
12310
|
+
);
|
|
12311
|
+
}
|
|
12312
|
+
return results;
|
|
12313
|
+
}
|
|
12314
|
+
async executeRecordObjectsChunk(chunk) {
|
|
12315
|
+
const wallStart = Date.now();
|
|
12316
|
+
let lastError;
|
|
12317
|
+
for (let attempt = 1; attempt <= DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_COUNT; attempt += 1) {
|
|
12318
|
+
try {
|
|
12319
|
+
const response = await this.controlPlaneRequest(
|
|
12320
|
+
`/control/environments/${this.environmentId}/records/batch`,
|
|
12321
|
+
{
|
|
12322
|
+
method: "POST",
|
|
12323
|
+
body: JSON.stringify({ records: chunk })
|
|
12324
|
+
}
|
|
12325
|
+
);
|
|
12326
|
+
const items = Array.isArray(response.items) ? response.items : [];
|
|
12327
|
+
return { items, durationMs: Date.now() - wallStart };
|
|
12328
|
+
} catch (error) {
|
|
12329
|
+
lastError = error;
|
|
12330
|
+
if (!isRetryableRecordObjectsError(error) || attempt === DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_COUNT) {
|
|
12331
|
+
throw error;
|
|
12332
|
+
}
|
|
12333
|
+
await sleep(DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_DELAY_MS * attempt);
|
|
12334
|
+
}
|
|
12335
|
+
}
|
|
12336
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
12226
12337
|
}
|
|
12227
12338
|
/**
|
|
12228
|
-
* Queue a background record import for this environment.
|
|
12339
|
+
* Queue a background record import for this environment (async worker pipeline).
|
|
12340
|
+
*
|
|
12341
|
+
* **vs `recordObjects`:** this path accepts the full payload in one request, returns an
|
|
12342
|
+
* **`importId`**, and processes rows in the background — use **`getRecordImport`** /
|
|
12343
|
+
* **`getRecordImportSummary`** for progress. Choose it for large bulk loads where you do not
|
|
12344
|
+
* need every row committed before the HTTP call returns. Use **`recordObjects`** when you need
|
|
12345
|
+
* synchronous upserts and fine-grained chunk progress via **`onChunkComplete`**.
|
|
12229
12346
|
*/
|
|
12230
12347
|
async enqueueRecordImport(records, options = {}) {
|
|
12231
12348
|
return this.controlPlaneRequest(
|
|
@@ -13193,23 +13310,36 @@ var Granular = class _Granular {
|
|
|
13193
13310
|
if (this.debugHttp) {
|
|
13194
13311
|
console.log(`[SDK] Requesting: ${url}`);
|
|
13195
13312
|
}
|
|
13196
|
-
|
|
13197
|
-
|
|
13198
|
-
|
|
13199
|
-
|
|
13200
|
-
|
|
13201
|
-
|
|
13202
|
-
|
|
13313
|
+
for (let attempt = 1; attempt <= LOCAL_CONTROL_REQUEST_RETRY_COUNT; attempt += 1) {
|
|
13314
|
+
const response = await fetch(url, {
|
|
13315
|
+
...options,
|
|
13316
|
+
headers: {
|
|
13317
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
13318
|
+
"Content-Type": "application/json",
|
|
13319
|
+
"Connection": "close",
|
|
13320
|
+
...options.headers
|
|
13321
|
+
}
|
|
13322
|
+
});
|
|
13323
|
+
if (response.ok) {
|
|
13324
|
+
if (response.status === 204) {
|
|
13325
|
+
return { deleted: true };
|
|
13326
|
+
}
|
|
13327
|
+
return response.json();
|
|
13203
13328
|
}
|
|
13204
|
-
});
|
|
13205
|
-
if (!response.ok) {
|
|
13206
13329
|
const errorText = await response.text();
|
|
13330
|
+
const retryable = isRetryableLocalWorkerRestart(response.status, errorText, url);
|
|
13331
|
+
if (retryable && attempt < LOCAL_CONTROL_REQUEST_RETRY_COUNT) {
|
|
13332
|
+
if (this.debugHttp) {
|
|
13333
|
+
console.warn(
|
|
13334
|
+
`[SDK] Retrying local control request after worker restart (${attempt}/${LOCAL_CONTROL_REQUEST_RETRY_COUNT - 1} retries used): ${url}`
|
|
13335
|
+
);
|
|
13336
|
+
}
|
|
13337
|
+
await sleep(LOCAL_CONTROL_REQUEST_RETRY_DELAY_MS * attempt);
|
|
13338
|
+
continue;
|
|
13339
|
+
}
|
|
13207
13340
|
throw new Error(`Granular API Error (${response.status}): ${errorText}`);
|
|
13208
13341
|
}
|
|
13209
|
-
|
|
13210
|
-
return { deleted: true };
|
|
13211
|
-
}
|
|
13212
|
-
return response.json();
|
|
13342
|
+
throw new Error(`Granular API Error: exhausted retries for ${url}`);
|
|
13213
13343
|
}
|
|
13214
13344
|
};
|
|
13215
13345
|
|
|
@@ -14135,7 +14265,10 @@ ${loopBlock}
|
|
|
14135
14265
|
- Only use classes, methods, and parameter shapes that are explicitly declared in those typedefs.
|
|
14136
14266
|
- Never invent helper methods such as \`find(...)\` or unsupported parameters such as \`id\` when the typedefs require \`path\`.
|
|
14137
14267
|
- Use \`ClassName.get({ path })\` only when you already know an object's graph path.
|
|
14138
|
-
- Use \`ClassName.
|
|
14268
|
+
- Use \`ClassName.count()\` when you only need a total.
|
|
14269
|
+
- Use \`ClassName.page({ page, perPage, saveAs })\` when you need both records and pagination metadata like \`totalCount\` or \`hasMore\`.
|
|
14270
|
+
- Use \`ClassName.list({ page, perPage, saveAs })\` to load one typed page of records. \`limit\` is only a legacy alias for \`perPage\`.
|
|
14271
|
+
- 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.
|
|
14139
14272
|
- Instance methods: \`await instance.method_name(params)\`.
|
|
14140
14273
|
- Static methods: \`await ClassName.static_method(params)\`.
|
|
14141
14274
|
- Global effects: \`await effect_name(params)\`.
|
|
@@ -14147,7 +14280,7 @@ ${loopBlock}
|
|
|
14147
14280
|
- 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\`.
|
|
14148
14281
|
- 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.
|
|
14149
14282
|
- 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.
|
|
14150
|
-
- Prefer \`heap.setVar(name, value)\` for scalars or one selected instance. Prefer \`ClassName.list({ saveAs })\` for
|
|
14283
|
+
- 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)\`.
|
|
14151
14284
|
- 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)\`.
|
|
14152
14285
|
- 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.
|
|
14153
14286
|
- 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.
|
|
@@ -14403,6 +14536,7 @@ exports.getCurrentClosureId = getCurrentClosureId;
|
|
|
14403
14536
|
exports.getExclusivePromptTarget = getExclusivePromptTarget;
|
|
14404
14537
|
exports.hasOpenPrompt = hasOpenPrompt;
|
|
14405
14538
|
exports.invokeRegisteredEffect = invokeRegisteredEffect;
|
|
14539
|
+
exports.isLocalApiUrl = isLocalApiUrl;
|
|
14406
14540
|
exports.normalizeEffectBehaviors = normalizeEffectBehaviors;
|
|
14407
14541
|
exports.normalizePrompt = normalizePrompt;
|
|
14408
14542
|
exports.normalizePromptText = normalizePromptText;
|
|
@@ -14411,6 +14545,8 @@ exports.projectHeapSummary = projectHeapSummary;
|
|
|
14411
14545
|
exports.projectLoopSummary = projectLoopSummary;
|
|
14412
14546
|
exports.projectWorkflowFocus = projectWorkflowFocus;
|
|
14413
14547
|
exports.projectWorkflowSummary = projectWorkflowSummary;
|
|
14548
|
+
exports.resolveApiUrl = resolveApiUrl;
|
|
14549
|
+
exports.resolveAuthTokenForApiUrl = resolveAuthTokenForApiUrl;
|
|
14414
14550
|
exports.resolveJobPresentation = resolveJobPresentation;
|
|
14415
14551
|
exports.resolvePromptAnswer = resolvePromptAnswer;
|
|
14416
14552
|
exports.reviewGeneratedJobCode = reviewGeneratedJobCode;
|