@granular-software/sdk 0.4.19 → 0.4.20
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 +10 -0
- package/dist/agent-evals.d.mts +1 -1
- package/dist/agent-evals.d.ts +1 -1
- package/dist/agent-evals.js +116 -17
- package/dist/agent-evals.js.map +1 -1
- package/dist/agent-evals.mjs +116 -17
- package/dist/agent-evals.mjs.map +1 -1
- package/dist/cli/index.js +114 -14
- package/dist/{client-BQw_gUK3.d.mts → client-DWYdWpS-.d.mts} +61 -5
- package/dist/{client-BQw_gUK3.d.ts → client-DWYdWpS-.d.ts} +61 -5
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +110 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +110 -11
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -456,6 +456,16 @@ Defines domain ontology: classes (with typed properties), and relationships (wit
|
|
|
456
456
|
### `environment.recordObject(options)`
|
|
457
457
|
Creates or updates a class instance with fields and relationships. Returns `{ path, id, created }`.
|
|
458
458
|
|
|
459
|
+
### `environment.recordObjects(records, options?)`
|
|
460
|
+
Batch upsert for many instances. The SDK sends **chunks** (default **100** rows per HTTP `POST` to `/records/batch`) with **retries** on transient failures, so large arrays do not time out as a single oversized request.
|
|
461
|
+
|
|
462
|
+
Optional **`options`**:
|
|
463
|
+
- **`batchSize`** — max rows per request (default 100).
|
|
464
|
+
- **`concurrency`** — how many chunk requests may run in parallel (default 1, max 16); can reduce wall time when the server can overlap work.
|
|
465
|
+
- **`onChunkComplete`** — async-friendly hook after each chunk for progress UIs; the returned array is always ordered like `records`.
|
|
466
|
+
|
|
467
|
+
**Sync batch vs queued import:** use **`recordObjects`** when you need **synchronous** commits and/or per-chunk feedback. Use **`enqueueRecordImport`** + **`getRecordImport` / `getRecordImportSummary`** for **background** ingestion with aggregate counters when admission latency matters more than immediate row-by-row completion.
|
|
468
|
+
|
|
459
469
|
### `environment.getRelationships(modelPath)`
|
|
460
470
|
Returns relationship definitions for a given class.
|
|
461
471
|
|
package/dist/agent-evals.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { P as Prompt, c as Environment, b as SessionHeapSnapshot,
|
|
1
|
+
import { P as Prompt, c as Environment, b as SessionHeapSnapshot, a$ as ManifestContent, ax as RecordObjectOptions, T as ToolWithHandler, G as Granular, C as ConnectOptions, y as CreateEnvironmentData, i as GranularOptions } from './client-DWYdWpS-.mjs';
|
|
2
2
|
import { GeneratedJobCodeIssue, HarnessControllerBudgets } from './agent-harness.mjs';
|
|
3
3
|
import '@automerge/automerge';
|
|
4
4
|
import '@automerge/automerge/slim';
|
package/dist/agent-evals.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { P as Prompt, c as Environment, b as SessionHeapSnapshot,
|
|
1
|
+
import { P as Prompt, c as Environment, b as SessionHeapSnapshot, a$ as ManifestContent, ax as RecordObjectOptions, T as ToolWithHandler, G as Granular, C as ConnectOptions, y as CreateEnvironmentData, i as GranularOptions } from './client-DWYdWpS-.js';
|
|
2
2
|
import { GeneratedJobCodeIssue, HarnessControllerBudgets } from './agent-harness.js';
|
|
3
3
|
import '@automerge/automerge';
|
|
4
4
|
import '@automerge/automerge/slim';
|
package/dist/agent-evals.js
CHANGED
|
@@ -11256,6 +11256,28 @@ var STANDARD_MODULES_OPERATIONS = [
|
|
|
11256
11256
|
var BUILTIN_MODULES = {
|
|
11257
11257
|
"standard_modules": STANDARD_MODULES_OPERATIONS
|
|
11258
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
|
+
function planRecordObjectsChunks(records, batchSize) {
|
|
11264
|
+
const total = records.length;
|
|
11265
|
+
const size = Math.max(1, Math.min(batchSize, total));
|
|
11266
|
+
const chunkCount = Math.ceil(total / size);
|
|
11267
|
+
const plans = [];
|
|
11268
|
+
for (let offset = 0, chunkIndex = 0; offset < total; offset += size, chunkIndex += 1) {
|
|
11269
|
+
const slice = records.slice(offset, offset + size);
|
|
11270
|
+
plans.push({ chunkIndex, chunkCount, offset, slice });
|
|
11271
|
+
}
|
|
11272
|
+
return plans;
|
|
11273
|
+
}
|
|
11274
|
+
function sleep(ms) {
|
|
11275
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
11276
|
+
}
|
|
11277
|
+
function isRetryableRecordObjectsError(error) {
|
|
11278
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
11279
|
+
return /worker restarted mid-request|econnreset|network|socket connection was closed unexpectedly|timed out/i.test(message);
|
|
11280
|
+
}
|
|
11259
11281
|
function computeEffectKey2(effect) {
|
|
11260
11282
|
const attachedClass = effect.className?.trim();
|
|
11261
11283
|
if (!attachedClass) {
|
|
@@ -12213,24 +12235,101 @@ var Environment = class extends Session {
|
|
|
12213
12235
|
/**
|
|
12214
12236
|
* Batch version of `recordObject()`.
|
|
12215
12237
|
*
|
|
12216
|
-
* Sends
|
|
12217
|
-
*
|
|
12238
|
+
* Sends rows through the control-plane **`/records/batch`** endpoint in **chunks** (default
|
|
12239
|
+
* **100** records per HTTP request) so individual requests stay bounded and gateway timeouts are
|
|
12240
|
+
* unlikely. Each chunk is retried on transient network / worker errors.
|
|
12241
|
+
*
|
|
12242
|
+
* Use the optional second argument to:
|
|
12243
|
+
* - **`batchSize`** — rows per POST (smaller = more progress events; larger = fewer round trips).
|
|
12244
|
+
* - **`concurrency`** — run up to N chunk POSTs in parallel (capped at 16) when you want lower wall time.
|
|
12245
|
+
* - **`onChunkComplete`** — hook for UIs after each chunk succeeds (row order in the returned array
|
|
12246
|
+
* always matches `records`; chunk **completion** order may differ when `concurrency > 1`).
|
|
12247
|
+
*
|
|
12248
|
+
* For **asynchronous** ingestion with worker-side batching and aggregate counters (`queued`,
|
|
12249
|
+
* `completed`, …), use **`enqueueRecordImport`** and poll **`getRecordImport`** /
|
|
12250
|
+
* **`getRecordImportSummary`** — best for very large fire-and-forget loads when immediate
|
|
12251
|
+
* synchronous commit of every row is not required.
|
|
12218
12252
|
*/
|
|
12219
|
-
async recordObjects(records) {
|
|
12253
|
+
async recordObjects(records, options) {
|
|
12220
12254
|
if (!Array.isArray(records) || records.length === 0) {
|
|
12221
12255
|
return [];
|
|
12222
12256
|
}
|
|
12223
|
-
const
|
|
12224
|
-
|
|
12225
|
-
|
|
12226
|
-
|
|
12227
|
-
|
|
12228
|
-
|
|
12257
|
+
const batchSize = Math.max(
|
|
12258
|
+
1,
|
|
12259
|
+
Math.min(
|
|
12260
|
+
records.length,
|
|
12261
|
+
options?.batchSize ?? DEFAULT_DIRECT_RECORD_OBJECTS_REQUEST_BATCH_SIZE
|
|
12262
|
+
)
|
|
12229
12263
|
);
|
|
12230
|
-
|
|
12264
|
+
const concurrency = Math.min(
|
|
12265
|
+
MAX_RECORD_OBJECTS_CONCURRENCY,
|
|
12266
|
+
Math.max(1, options?.concurrency ?? 1)
|
|
12267
|
+
);
|
|
12268
|
+
const plans = planRecordObjectsChunks(records, batchSize);
|
|
12269
|
+
const total = records.length;
|
|
12270
|
+
const results = new Array(total);
|
|
12271
|
+
const onChunk = options?.onChunkComplete;
|
|
12272
|
+
for (let waveStart = 0; waveStart < plans.length; waveStart += concurrency) {
|
|
12273
|
+
const wave = plans.slice(waveStart, waveStart + concurrency);
|
|
12274
|
+
await Promise.all(
|
|
12275
|
+
wave.map(async (plan) => {
|
|
12276
|
+
const { items, durationMs } = await this.executeRecordObjectsChunk(plan.slice);
|
|
12277
|
+
if (items.length !== plan.slice.length) {
|
|
12278
|
+
throw new Error(
|
|
12279
|
+
`recordObjects: chunk ${plan.chunkIndex + 1} returned ${items.length} results, expected ${plan.slice.length}`
|
|
12280
|
+
);
|
|
12281
|
+
}
|
|
12282
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
12283
|
+
results[plan.offset + index] = items[index];
|
|
12284
|
+
}
|
|
12285
|
+
if (onChunk) {
|
|
12286
|
+
const info = {
|
|
12287
|
+
chunkIndex: plan.chunkIndex,
|
|
12288
|
+
totalChunks: plan.chunkCount,
|
|
12289
|
+
offset: plan.offset,
|
|
12290
|
+
recordCount: plan.slice.length,
|
|
12291
|
+
durationMs,
|
|
12292
|
+
results: items
|
|
12293
|
+
};
|
|
12294
|
+
await onChunk(info);
|
|
12295
|
+
}
|
|
12296
|
+
})
|
|
12297
|
+
);
|
|
12298
|
+
}
|
|
12299
|
+
return results;
|
|
12300
|
+
}
|
|
12301
|
+
async executeRecordObjectsChunk(chunk) {
|
|
12302
|
+
const wallStart = Date.now();
|
|
12303
|
+
let lastError;
|
|
12304
|
+
for (let attempt = 1; attempt <= DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_COUNT; attempt += 1) {
|
|
12305
|
+
try {
|
|
12306
|
+
const response = await this.controlPlaneRequest(
|
|
12307
|
+
`/control/environments/${this.environmentId}/records/batch`,
|
|
12308
|
+
{
|
|
12309
|
+
method: "POST",
|
|
12310
|
+
body: JSON.stringify({ records: chunk })
|
|
12311
|
+
}
|
|
12312
|
+
);
|
|
12313
|
+
const items = Array.isArray(response.items) ? response.items : [];
|
|
12314
|
+
return { items, durationMs: Date.now() - wallStart };
|
|
12315
|
+
} catch (error) {
|
|
12316
|
+
lastError = error;
|
|
12317
|
+
if (!isRetryableRecordObjectsError(error) || attempt === DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_COUNT) {
|
|
12318
|
+
throw error;
|
|
12319
|
+
}
|
|
12320
|
+
await sleep(DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_DELAY_MS * attempt);
|
|
12321
|
+
}
|
|
12322
|
+
}
|
|
12323
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
12231
12324
|
}
|
|
12232
12325
|
/**
|
|
12233
|
-
* Queue a background record import for this environment.
|
|
12326
|
+
* Queue a background record import for this environment (async worker pipeline).
|
|
12327
|
+
*
|
|
12328
|
+
* **vs `recordObjects`:** this path accepts the full payload in one request, returns an
|
|
12329
|
+
* **`importId`**, and processes rows in the background — use **`getRecordImport`** /
|
|
12330
|
+
* **`getRecordImportSummary`** for progress. Choose it for large bulk loads where you do not
|
|
12331
|
+
* need every row committed before the HTTP call returns. Use **`recordObjects`** when you need
|
|
12332
|
+
* synchronous upserts and fine-grained chunk progress via **`onChunkComplete`**.
|
|
12234
12333
|
*/
|
|
12235
12334
|
async enqueueRecordImport(records, options = {}) {
|
|
12236
12335
|
return this.controlPlaneRequest(
|
|
@@ -14589,7 +14688,7 @@ ${modelOutputInstruction()}` },
|
|
|
14589
14688
|
if (!response.ok) {
|
|
14590
14689
|
const errorText = await response.text();
|
|
14591
14690
|
if (attempt < 3 && (response.status >= 500 || response.status === 429)) {
|
|
14592
|
-
await
|
|
14691
|
+
await sleep2(500 * attempt);
|
|
14593
14692
|
continue;
|
|
14594
14693
|
}
|
|
14595
14694
|
throw new Error(`OpenAI chat generation failed: ${response.status} ${errorText}`);
|
|
@@ -14600,7 +14699,7 @@ ${modelOutputInstruction()}` },
|
|
|
14600
14699
|
const parsed = extractJsonObject(text);
|
|
14601
14700
|
if (!parsed) {
|
|
14602
14701
|
if (attempt < 3) {
|
|
14603
|
-
await
|
|
14702
|
+
await sleep2(300 * attempt);
|
|
14604
14703
|
continue;
|
|
14605
14704
|
}
|
|
14606
14705
|
throw new Error(`Model output was not valid JSON:
|
|
@@ -14614,7 +14713,7 @@ ${text}`);
|
|
|
14614
14713
|
} catch (error) {
|
|
14615
14714
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
14616
14715
|
if (attempt < 3 && /socket connection was closed unexpectedly|ECONNRESET|network|timed out/i.test(lastError.message)) {
|
|
14617
|
-
await
|
|
14716
|
+
await sleep2(500 * attempt);
|
|
14618
14717
|
continue;
|
|
14619
14718
|
}
|
|
14620
14719
|
throw lastError;
|
|
@@ -14626,7 +14725,7 @@ ${text}`);
|
|
|
14626
14725
|
async function ensureDir(dir) {
|
|
14627
14726
|
await promises.mkdir(dir, { recursive: true });
|
|
14628
14727
|
}
|
|
14629
|
-
async function
|
|
14728
|
+
async function sleep2(ms) {
|
|
14630
14729
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
14631
14730
|
}
|
|
14632
14731
|
async function withTimeout(promise, ms, label) {
|
|
@@ -15069,7 +15168,7 @@ function createAgentEvalHarness(options) {
|
|
|
15069
15168
|
stderr: [...pending.stderr, ...resumed.stderr]
|
|
15070
15169
|
};
|
|
15071
15170
|
}
|
|
15072
|
-
await
|
|
15171
|
+
await sleep2(350);
|
|
15073
15172
|
const liveDoc = cloneJson(pending.conversation.environment.document);
|
|
15074
15173
|
const presentation = resolveJobPresentation({
|
|
15075
15174
|
jobId: pending.job.id,
|
|
@@ -15238,7 +15337,7 @@ function createAgentEvalHarness(options) {
|
|
|
15238
15337
|
if (outcome.kind !== "completed") {
|
|
15239
15338
|
throw new Error("Unexpected non-completed outcome after prompt handling");
|
|
15240
15339
|
}
|
|
15241
|
-
await
|
|
15340
|
+
await sleep2(350);
|
|
15242
15341
|
const settledLiveDoc = cloneJson(conversation.environment.document);
|
|
15243
15342
|
const sessionHeap = normalizeHeapSnapshot2(asRecord4(settledLiveDoc?.heap));
|
|
15244
15343
|
const presentation = resolveJobPresentation({
|