@granular-software/sdk 0.4.29 → 0.4.30
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/dist/agent-evals.d.mts +1 -1
- package/dist/agent-evals.d.ts +1 -1
- package/dist/agent-evals.js +347 -274
- package/dist/agent-evals.js.map +1 -1
- package/dist/agent-evals.mjs +347 -274
- package/dist/agent-evals.mjs.map +1 -1
- package/dist/agent-harness.d.mts +11 -2
- package/dist/agent-harness.d.ts +11 -2
- package/dist/agent-harness.js +248 -125
- package/dist/agent-harness.js.map +1 -1
- package/dist/agent-harness.mjs +246 -126
- package/dist/agent-harness.mjs.map +1 -1
- package/dist/cli/index.js +273 -149
- package/dist/{client-CigjaeO8.d.mts → client-BVvOMfln.d.mts} +11 -4
- package/dist/{client-CigjaeO8.d.ts → client-BVvOMfln.d.ts} +11 -4
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +525 -274
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +523 -275
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -14230,17 +14230,8 @@ var searchableMetamodelPackage = defineMetamodelPackage({
|
|
|
14230
14230
|
}
|
|
14231
14231
|
},
|
|
14232
14232
|
domain: {
|
|
14233
|
-
applyToPropertyIR(propertyIR,
|
|
14234
|
-
|
|
14235
|
-
return propertyIR;
|
|
14236
|
-
}
|
|
14237
|
-
return {
|
|
14238
|
-
...propertyIR,
|
|
14239
|
-
docs: [
|
|
14240
|
-
...propertyIR.docs,
|
|
14241
|
-
propertySummary.searchablePhonetic ? "Searchable via `search` using FalkorDB full-text query syntax with phonetic matching enabled." : "Searchable via `search` using FalkorDB full-text query syntax."
|
|
14242
|
-
]
|
|
14243
|
-
};
|
|
14233
|
+
applyToPropertyIR(propertyIR, _propertySummary) {
|
|
14234
|
+
return propertyIR;
|
|
14244
14235
|
}
|
|
14245
14236
|
}
|
|
14246
14237
|
});
|
|
@@ -19520,17 +19511,40 @@ function buildEffectMetamodelMutations(toolPath, spec) {
|
|
|
19520
19511
|
|
|
19521
19512
|
// src/client.ts
|
|
19522
19513
|
var STANDARD_MODULES_OPERATIONS = [
|
|
19523
|
-
{
|
|
19514
|
+
{
|
|
19515
|
+
create: "entity",
|
|
19516
|
+
has: { id: { value: "auto-generated" }, createdAt: { value: void 0 } }
|
|
19517
|
+
},
|
|
19524
19518
|
{ create: "class", extends: "entity", has: {} },
|
|
19525
|
-
{
|
|
19526
|
-
|
|
19519
|
+
{
|
|
19520
|
+
create: "user",
|
|
19521
|
+
extends: "entity",
|
|
19522
|
+
has: {
|
|
19523
|
+
email: { value: void 0 },
|
|
19524
|
+
firstName: { value: void 0 },
|
|
19525
|
+
lastName: { value: void 0 }
|
|
19526
|
+
}
|
|
19527
|
+
},
|
|
19528
|
+
{
|
|
19529
|
+
create: "company",
|
|
19530
|
+
extends: "entity",
|
|
19531
|
+
has: { name: { value: void 0 }, website: { value: void 0 } }
|
|
19532
|
+
},
|
|
19527
19533
|
{ create: "string", has: {} },
|
|
19528
19534
|
{ create: "number", has: {} },
|
|
19529
19535
|
{ create: "boolean", has: {} },
|
|
19530
|
-
{
|
|
19536
|
+
{
|
|
19537
|
+
create: "tool_parameter",
|
|
19538
|
+
has: {
|
|
19539
|
+
name: { value: void 0 },
|
|
19540
|
+
type: { value: "string" },
|
|
19541
|
+
description: { value: void 0 },
|
|
19542
|
+
required: { value: false }
|
|
19543
|
+
}
|
|
19544
|
+
}
|
|
19531
19545
|
];
|
|
19532
19546
|
var BUILTIN_MODULES = {
|
|
19533
|
-
|
|
19547
|
+
standard_modules: STANDARD_MODULES_OPERATIONS
|
|
19534
19548
|
};
|
|
19535
19549
|
var DEFAULT_DIRECT_RECORD_OBJECTS_REQUEST_BATCH_SIZE = 100;
|
|
19536
19550
|
var MAX_RECORD_OBJECTS_CONCURRENCY = 16;
|
|
@@ -19565,7 +19579,9 @@ function isRetryableLocalWorkerRestart(status, body, url) {
|
|
|
19565
19579
|
}
|
|
19566
19580
|
function isRetryableRecordObjectsError(error2) {
|
|
19567
19581
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
19568
|
-
return /worker restarted mid-request|econnreset|network|socket connection was closed unexpectedly|timed out/i.test(
|
|
19582
|
+
return /worker restarted mid-request|econnreset|network|socket connection was closed unexpectedly|timed out/i.test(
|
|
19583
|
+
message
|
|
19584
|
+
);
|
|
19569
19585
|
}
|
|
19570
19586
|
function computeEffectKey2(effect) {
|
|
19571
19587
|
const attachedClass = effect.className?.trim();
|
|
@@ -19642,7 +19658,10 @@ function normalizeUser(user) {
|
|
|
19642
19658
|
};
|
|
19643
19659
|
}
|
|
19644
19660
|
function normalizeEnvironmentData(environment) {
|
|
19645
|
-
const buildPolicy = environment.buildPolicy || environment.tracking || (environment.tagId ? { mode: "tag", tagId: environment.tagId } : {
|
|
19661
|
+
const buildPolicy = environment.buildPolicy || environment.tracking || (environment.tagId ? { mode: "tag", tagId: environment.tagId } : {
|
|
19662
|
+
mode: "pinned",
|
|
19663
|
+
versionId: environment.versionId || environment.buildId
|
|
19664
|
+
});
|
|
19646
19665
|
const environmentName = environment.environment || environment.envName || "prod";
|
|
19647
19666
|
return {
|
|
19648
19667
|
...environment,
|
|
@@ -19736,14 +19755,16 @@ var Environment = class extends Session {
|
|
|
19736
19755
|
const response = await fetch(url, {
|
|
19737
19756
|
...options,
|
|
19738
19757
|
headers: {
|
|
19739
|
-
|
|
19758
|
+
Authorization: `Bearer ${this._apiKey}`,
|
|
19740
19759
|
"Content-Type": "application/json",
|
|
19741
|
-
|
|
19760
|
+
Connection: "close",
|
|
19742
19761
|
...options.headers
|
|
19743
19762
|
}
|
|
19744
19763
|
});
|
|
19745
19764
|
if (!response.ok) {
|
|
19746
|
-
throw new Error(
|
|
19765
|
+
throw new Error(
|
|
19766
|
+
`Control Plane API Error (${response.status}): ${await response.text()}`
|
|
19767
|
+
);
|
|
19747
19768
|
}
|
|
19748
19769
|
return response.json();
|
|
19749
19770
|
}
|
|
@@ -19757,9 +19778,12 @@ var Environment = class extends Session {
|
|
|
19757
19778
|
async disconnect() {
|
|
19758
19779
|
let wsNotifiedRuntime = false;
|
|
19759
19780
|
try {
|
|
19760
|
-
const goodbye = await this.rpc(
|
|
19761
|
-
|
|
19762
|
-
|
|
19781
|
+
const goodbye = await this.rpc(
|
|
19782
|
+
"client.goodbye",
|
|
19783
|
+
{
|
|
19784
|
+
timestamp: Date.now()
|
|
19785
|
+
}
|
|
19786
|
+
);
|
|
19763
19787
|
wsNotifiedRuntime = Boolean(goodbye?.ok && goodbye?.via);
|
|
19764
19788
|
} catch {
|
|
19765
19789
|
wsNotifiedRuntime = false;
|
|
@@ -19773,8 +19797,8 @@ var Environment = class extends Session {
|
|
|
19773
19797
|
method: "POST",
|
|
19774
19798
|
headers: {
|
|
19775
19799
|
"Content-Type": "application/json",
|
|
19776
|
-
|
|
19777
|
-
|
|
19800
|
+
Authorization: `Bearer ${this._apiKey}`,
|
|
19801
|
+
Connection: "close"
|
|
19778
19802
|
},
|
|
19779
19803
|
body: JSON.stringify({
|
|
19780
19804
|
reason: "sdk_disconnect_http_fallback",
|
|
@@ -19787,6 +19811,15 @@ var Environment = class extends Session {
|
|
|
19787
19811
|
}
|
|
19788
19812
|
this.client.disconnect();
|
|
19789
19813
|
}
|
|
19814
|
+
/**
|
|
19815
|
+
* Close only the socket transport without sending `client.goodbye`.
|
|
19816
|
+
*
|
|
19817
|
+
* Use this when the caller intends to immediately reattach to the same
|
|
19818
|
+
* session after an unexpected disconnect.
|
|
19819
|
+
*/
|
|
19820
|
+
disconnectTransport() {
|
|
19821
|
+
this.client.disconnect();
|
|
19822
|
+
}
|
|
19790
19823
|
// ==================== GRAPH CONTAINER READINESS ====================
|
|
19791
19824
|
/** The last known graph container status, updated by checkReadiness() or on heartbeat */
|
|
19792
19825
|
graphContainerStatus = null;
|
|
@@ -19852,14 +19885,14 @@ var Environment = class extends Session {
|
|
|
19852
19885
|
}
|
|
19853
19886
|
/**
|
|
19854
19887
|
* Execute a GraphQL query against the environment's graph.
|
|
19855
|
-
*
|
|
19888
|
+
*
|
|
19856
19889
|
* The query uses the Granular graph query language (based on Cypher/GraphQL).
|
|
19857
19890
|
* Authentication is handled automatically using the SDK's API key.
|
|
19858
|
-
*
|
|
19891
|
+
*
|
|
19859
19892
|
* @param query - The GraphQL query string
|
|
19860
19893
|
* @param variables - Optional variables for the query
|
|
19861
19894
|
* @returns The query result data
|
|
19862
|
-
*
|
|
19895
|
+
*
|
|
19863
19896
|
* @example
|
|
19864
19897
|
* ```typescript
|
|
19865
19898
|
* // Read the workspace
|
|
@@ -19867,7 +19900,7 @@ var Environment = class extends Session {
|
|
|
19867
19900
|
* `query { model(path: "workspace") { path label submodels { path label } } }`
|
|
19868
19901
|
* );
|
|
19869
19902
|
* console.log(result.data);
|
|
19870
|
-
*
|
|
19903
|
+
*
|
|
19871
19904
|
* // Create a model
|
|
19872
19905
|
* const created = await env.graphql(
|
|
19873
19906
|
* `mutation { at(path: "workspace") { create_submodel(subpath: "my_node", label: "My Node", prototype: "Model") { model { path label } } } }`
|
|
@@ -19879,7 +19912,7 @@ var Environment = class extends Session {
|
|
|
19879
19912
|
method: "POST",
|
|
19880
19913
|
headers: {
|
|
19881
19914
|
"Content-Type": "application/json",
|
|
19882
|
-
|
|
19915
|
+
Authorization: `Bearer ${this._apiKey}`
|
|
19883
19916
|
},
|
|
19884
19917
|
body: JSON.stringify({
|
|
19885
19918
|
environmentId: this.environmentId,
|
|
@@ -19896,10 +19929,10 @@ var Environment = class extends Session {
|
|
|
19896
19929
|
// ==================== RELATIONSHIP METHODS ====================
|
|
19897
19930
|
/**
|
|
19898
19931
|
* Define a relationship between two model types.
|
|
19899
|
-
*
|
|
19932
|
+
*
|
|
19900
19933
|
* Creates both submodels (if they don't exist) and links them with
|
|
19901
19934
|
* a RelationshipDef node that encodes cardinality.
|
|
19902
|
-
*
|
|
19935
|
+
*
|
|
19903
19936
|
* @example
|
|
19904
19937
|
* ```typescript
|
|
19905
19938
|
* // Author has many Books, Book has one Author
|
|
@@ -19959,10 +19992,10 @@ var Environment = class extends Session {
|
|
|
19959
19992
|
}
|
|
19960
19993
|
/**
|
|
19961
19994
|
* Get all relationships for a model type.
|
|
19962
|
-
*
|
|
19995
|
+
*
|
|
19963
19996
|
* @param modelPath - The model type path (e.g., "author")
|
|
19964
19997
|
* @returns Array of relationships from this model's perspective
|
|
19965
|
-
*
|
|
19998
|
+
*
|
|
19966
19999
|
* @example
|
|
19967
20000
|
* ```typescript
|
|
19968
20001
|
* const rels = await env.getRelationships('author');
|
|
@@ -19995,18 +20028,18 @@ var Environment = class extends Session {
|
|
|
19995
20028
|
}
|
|
19996
20029
|
/**
|
|
19997
20030
|
* Attach a target model to a relationship submodel.
|
|
19998
|
-
*
|
|
20031
|
+
*
|
|
19999
20032
|
* Handles cardinality automatically:
|
|
20000
20033
|
* - "One" side: sets/replaces the reference
|
|
20001
20034
|
* - "Many" side: adds the target to the collection
|
|
20002
|
-
*
|
|
20035
|
+
*
|
|
20003
20036
|
* If the target model doesn't exist, it's created as an instance of the foreign type.
|
|
20004
20037
|
* Bidirectional sync is automatic.
|
|
20005
|
-
*
|
|
20038
|
+
*
|
|
20006
20039
|
* @param modelPath - The model instance path (e.g., "tolkien")
|
|
20007
20040
|
* @param submodelPath - The relationship submodel (e.g., "books")
|
|
20008
20041
|
* @param targetPath - The target model to attach (e.g., "lord_of_the_rings")
|
|
20009
|
-
*
|
|
20042
|
+
*
|
|
20010
20043
|
* @example
|
|
20011
20044
|
* ```typescript
|
|
20012
20045
|
* // Attach a book to an author (many side)
|
|
@@ -20033,18 +20066,18 @@ var Environment = class extends Session {
|
|
|
20033
20066
|
}
|
|
20034
20067
|
/**
|
|
20035
20068
|
* Detach a target model from a relationship submodel.
|
|
20036
|
-
*
|
|
20069
|
+
*
|
|
20037
20070
|
* Handles bidirectional cleanup automatically.
|
|
20038
|
-
*
|
|
20071
|
+
*
|
|
20039
20072
|
* @param modelPath - The model instance path
|
|
20040
20073
|
* @param submodelPath - The relationship submodel
|
|
20041
20074
|
* @param targetPath - The target to detach (optional for "one" side; omit on "many" side to detach all)
|
|
20042
|
-
*
|
|
20075
|
+
*
|
|
20043
20076
|
* @example
|
|
20044
20077
|
* ```typescript
|
|
20045
20078
|
* // Detach a specific book
|
|
20046
20079
|
* await env.detach('tolkien', 'books', 'lord_of_the_rings');
|
|
20047
|
-
*
|
|
20080
|
+
*
|
|
20048
20081
|
* // Detach all books
|
|
20049
20082
|
* await env.detach('tolkien', 'books');
|
|
20050
20083
|
* ```
|
|
@@ -20068,11 +20101,11 @@ var Environment = class extends Session {
|
|
|
20068
20101
|
}
|
|
20069
20102
|
/**
|
|
20070
20103
|
* List all related models through a relationship submodel.
|
|
20071
|
-
*
|
|
20104
|
+
*
|
|
20072
20105
|
* @param modelPath - The model instance path
|
|
20073
20106
|
* @param submodelPath - The relationship submodel
|
|
20074
20107
|
* @returns Array of related model references
|
|
20075
|
-
*
|
|
20108
|
+
*
|
|
20076
20109
|
* @example
|
|
20077
20110
|
* ```typescript
|
|
20078
20111
|
* const books = await env.listRelated('tolkien', 'books');
|
|
@@ -20096,14 +20129,14 @@ var Environment = class extends Session {
|
|
|
20096
20129
|
}
|
|
20097
20130
|
/**
|
|
20098
20131
|
* Apply a manifest to the current environment's graph.
|
|
20099
|
-
*
|
|
20132
|
+
*
|
|
20100
20133
|
* Translates each manifest operation into GraphQL mutations and executes them
|
|
20101
20134
|
* in order. This is the core mechanism for creating classes, fields, and
|
|
20102
20135
|
* relationships from a declarative manifest.
|
|
20103
|
-
*
|
|
20136
|
+
*
|
|
20104
20137
|
* @param manifest - The manifest content to apply
|
|
20105
20138
|
* @returns Summary of applied operations
|
|
20106
|
-
*
|
|
20139
|
+
*
|
|
20107
20140
|
* @example
|
|
20108
20141
|
* ```typescript
|
|
20109
20142
|
* await environment.applyManifest({
|
|
@@ -20141,12 +20174,16 @@ var Environment = class extends Session {
|
|
|
20141
20174
|
applied++;
|
|
20142
20175
|
} catch (err) {
|
|
20143
20176
|
if (!err.message?.includes("already exists")) {
|
|
20144
|
-
errors.push(
|
|
20177
|
+
errors.push(
|
|
20178
|
+
`Import ${imp.name} operation failed: ${err.message}`
|
|
20179
|
+
);
|
|
20145
20180
|
}
|
|
20146
20181
|
}
|
|
20147
20182
|
}
|
|
20148
20183
|
} else {
|
|
20149
|
-
errors.push(
|
|
20184
|
+
errors.push(
|
|
20185
|
+
`Unknown module: "${imp.name}" (only built-in modules are supported)`
|
|
20186
|
+
);
|
|
20150
20187
|
}
|
|
20151
20188
|
}
|
|
20152
20189
|
}
|
|
@@ -20230,7 +20267,10 @@ var Environment = class extends Session {
|
|
|
20230
20267
|
}
|
|
20231
20268
|
}
|
|
20232
20269
|
async _applyEffectMetamodels(toolPath, metamodels) {
|
|
20233
|
-
for (const mutation of buildEffectMetamodelMutations(
|
|
20270
|
+
for (const mutation of buildEffectMetamodelMutations(
|
|
20271
|
+
toolPath,
|
|
20272
|
+
metamodels
|
|
20273
|
+
)) {
|
|
20234
20274
|
await this._runGraphql(mutation.query, mutation.label);
|
|
20235
20275
|
}
|
|
20236
20276
|
}
|
|
@@ -20279,7 +20319,9 @@ var Environment = class extends Session {
|
|
|
20279
20319
|
}
|
|
20280
20320
|
if (eventType.payloadSchema?.properties) {
|
|
20281
20321
|
const fieldSpecs = {};
|
|
20282
|
-
for (const [propName, propSchema] of Object.entries(
|
|
20322
|
+
for (const [propName, propSchema] of Object.entries(
|
|
20323
|
+
eventType.payloadSchema.properties
|
|
20324
|
+
)) {
|
|
20283
20325
|
const schema = propSchema;
|
|
20284
20326
|
fieldSpecs[propName] = {
|
|
20285
20327
|
type: schema.type ?? "string",
|
|
@@ -20562,7 +20604,9 @@ var Environment = class extends Session {
|
|
|
20562
20604
|
const wave = plans.slice(waveStart, waveStart + concurrency);
|
|
20563
20605
|
await Promise.all(
|
|
20564
20606
|
wave.map(async (plan) => {
|
|
20565
|
-
const { items, durationMs } = await this.executeRecordObjectsChunk(
|
|
20607
|
+
const { items, durationMs } = await this.executeRecordObjectsChunk(
|
|
20608
|
+
plan.slice
|
|
20609
|
+
);
|
|
20566
20610
|
if (items.length !== plan.slice.length) {
|
|
20567
20611
|
throw new Error(
|
|
20568
20612
|
`recordObjects: chunk ${plan.chunkIndex + 1} returned ${items.length} results, expected ${plan.slice.length}`
|
|
@@ -20592,13 +20636,10 @@ var Environment = class extends Session {
|
|
|
20592
20636
|
let lastError;
|
|
20593
20637
|
for (let attempt = 1; attempt <= DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_COUNT; attempt += 1) {
|
|
20594
20638
|
try {
|
|
20595
|
-
const response = await this.controlPlaneRequest(
|
|
20596
|
-
|
|
20597
|
-
{
|
|
20598
|
-
|
|
20599
|
-
body: JSON.stringify({ records: chunk })
|
|
20600
|
-
}
|
|
20601
|
-
);
|
|
20639
|
+
const response = await this.controlPlaneRequest(`/control/environments/${this.environmentId}/records/batch`, {
|
|
20640
|
+
method: "POST",
|
|
20641
|
+
body: JSON.stringify({ records: chunk })
|
|
20642
|
+
});
|
|
20602
20643
|
const items = Array.isArray(response.items) ? response.items : [];
|
|
20603
20644
|
return { items, durationMs: Date.now() - wallStart };
|
|
20604
20645
|
} catch (error2) {
|
|
@@ -20661,7 +20702,9 @@ var Environment = class extends Session {
|
|
|
20661
20702
|
* Fetch a single record import by id.
|
|
20662
20703
|
*/
|
|
20663
20704
|
async getRecordImport(importId) {
|
|
20664
|
-
return this.controlPlaneRequest(
|
|
20705
|
+
return this.controlPlaneRequest(
|
|
20706
|
+
`/control/record-imports/${importId}`
|
|
20707
|
+
);
|
|
20665
20708
|
}
|
|
20666
20709
|
/**
|
|
20667
20710
|
* Cancel a queued/background record import.
|
|
@@ -20728,7 +20771,9 @@ var Granular = class _Granular {
|
|
|
20728
20771
|
constructor(options) {
|
|
20729
20772
|
const auth = options.token ?? options.apiKey;
|
|
20730
20773
|
if (!auth) {
|
|
20731
|
-
throw new Error(
|
|
20774
|
+
throw new Error(
|
|
20775
|
+
"Granular client requires either apiKey or token. Set GRANULAR_API_KEY or GRANULAR_TOKEN, or pass one in options."
|
|
20776
|
+
);
|
|
20732
20777
|
}
|
|
20733
20778
|
this.apiUrl = resolveApiUrl(options.apiUrl, options.endpointMode);
|
|
20734
20779
|
this.apiKey = resolveAuthTokenForApiUrl(auth, this.apiUrl);
|
|
@@ -20740,10 +20785,10 @@ var Granular = class _Granular {
|
|
|
20740
20785
|
}
|
|
20741
20786
|
/**
|
|
20742
20787
|
* Records/upserts a user and prepares them for sandbox connections
|
|
20743
|
-
*
|
|
20788
|
+
*
|
|
20744
20789
|
* @param options - User options
|
|
20745
20790
|
* @returns The recorded user with both `userId` and `granularId`
|
|
20746
|
-
*
|
|
20791
|
+
*
|
|
20747
20792
|
* @example
|
|
20748
20793
|
* ```typescript
|
|
20749
20794
|
* const user = await granular.recordUser({
|
|
@@ -20754,14 +20799,16 @@ var Granular = class _Granular {
|
|
|
20754
20799
|
* ```
|
|
20755
20800
|
*/
|
|
20756
20801
|
async recordUser(options) {
|
|
20757
|
-
const subject = normalizeSubject(
|
|
20758
|
-
|
|
20759
|
-
|
|
20760
|
-
|
|
20761
|
-
|
|
20762
|
-
|
|
20802
|
+
const subject = normalizeSubject(
|
|
20803
|
+
await this.request("/control/subjects", {
|
|
20804
|
+
method: "POST",
|
|
20805
|
+
body: JSON.stringify({
|
|
20806
|
+
identityId: options.userId,
|
|
20807
|
+
name: options.name,
|
|
20808
|
+
email: options.email
|
|
20809
|
+
})
|
|
20763
20810
|
})
|
|
20764
|
-
|
|
20811
|
+
);
|
|
20765
20812
|
return normalizeUser({
|
|
20766
20813
|
granularId: subject.granularId,
|
|
20767
20814
|
userId: options.userId,
|
|
@@ -20808,18 +20855,20 @@ var Granular = class _Granular {
|
|
|
20808
20855
|
permissions: options.permissions || []
|
|
20809
20856
|
};
|
|
20810
20857
|
}
|
|
20811
|
-
throw new Error(
|
|
20858
|
+
throw new Error(
|
|
20859
|
+
"connect() requires either userId, granularId, or a user object returned by recordUser()."
|
|
20860
|
+
);
|
|
20812
20861
|
}
|
|
20813
20862
|
/**
|
|
20814
20863
|
* Connect to an ontology environment and establish a real-time session.
|
|
20815
|
-
*
|
|
20864
|
+
*
|
|
20816
20865
|
* Effects are registered at the sandbox level via `granular.registerEffect()`
|
|
20817
20866
|
* or `granular.registerEffects()`. Sessions pick up live availability from
|
|
20818
20867
|
* the sandbox registry automatically.
|
|
20819
|
-
*
|
|
20868
|
+
*
|
|
20820
20869
|
* @param options - Connection options
|
|
20821
20870
|
* @returns An active environment session
|
|
20822
|
-
*
|
|
20871
|
+
*
|
|
20823
20872
|
* @example
|
|
20824
20873
|
* ```typescript
|
|
20825
20874
|
* const environment = await granular.connect({
|
|
@@ -20828,23 +20877,23 @@ var Granular = class _Granular {
|
|
|
20828
20877
|
* userId: 'user_123',
|
|
20829
20878
|
* permissions: ['agent'],
|
|
20830
20879
|
* });
|
|
20831
|
-
*
|
|
20880
|
+
*
|
|
20832
20881
|
* await granular.registerEffect('my-sandbox', {
|
|
20833
20882
|
* name: 'greet',
|
|
20834
20883
|
* description: 'Say hello',
|
|
20835
20884
|
* inputSchema: { type: 'object', properties: {} },
|
|
20836
20885
|
* handler: async () => 'Hello!',
|
|
20837
20886
|
* });
|
|
20838
|
-
*
|
|
20887
|
+
*
|
|
20839
20888
|
* // Submit job
|
|
20840
20889
|
* const job = await environment.submitJob(`
|
|
20841
20890
|
* import { tools } from './sandbox-tools';
|
|
20842
20891
|
* return await tools.greet({});
|
|
20843
20892
|
* `);
|
|
20844
|
-
*
|
|
20893
|
+
*
|
|
20845
20894
|
* console.log(await job.result); // 'Hello!'
|
|
20846
20895
|
* ```
|
|
20847
|
-
|
|
20896
|
+
*/
|
|
20848
20897
|
async connect(options) {
|
|
20849
20898
|
const clientId = options.clientId || `client_${Date.now()}`;
|
|
20850
20899
|
const ontology = options.ontology;
|
|
@@ -20859,8 +20908,15 @@ var Granular = class _Granular {
|
|
|
20859
20908
|
const user = await this.resolveConnectUser(options);
|
|
20860
20909
|
const sandbox = await this.findOrCreateSandbox(ontology);
|
|
20861
20910
|
for (const profileName of user.permissions) {
|
|
20862
|
-
const profileId = await this.ensurePermissionProfile(
|
|
20863
|
-
|
|
20911
|
+
const profileId = await this.ensurePermissionProfile(
|
|
20912
|
+
sandbox.sandboxId,
|
|
20913
|
+
profileName
|
|
20914
|
+
);
|
|
20915
|
+
await this.ensureAssignment(
|
|
20916
|
+
user.granularId,
|
|
20917
|
+
sandbox.sandboxId,
|
|
20918
|
+
profileId
|
|
20919
|
+
);
|
|
20864
20920
|
}
|
|
20865
20921
|
const envData = await this.environments.create(sandbox.sandboxId, {
|
|
20866
20922
|
subjectId: user.granularId,
|
|
@@ -20912,7 +20968,9 @@ var Granular = class _Granular {
|
|
|
20912
20968
|
createdAt: _Granular.coerceIsoDate(row.createdAt ?? row.created_at),
|
|
20913
20969
|
lastSeenAt: _Granular.coerceIsoDate(row.lastSeenAt ?? row.last_seen_at),
|
|
20914
20970
|
summary: row.summary != null ? String(row.summary) : null,
|
|
20915
|
-
summaryUpdatedAt: row.summaryUpdatedAt != null || row.summary_updated_at != null ? _Granular.coerceIsoDate(
|
|
20971
|
+
summaryUpdatedAt: row.summaryUpdatedAt != null || row.summary_updated_at != null ? _Granular.coerceIsoDate(
|
|
20972
|
+
row.summaryUpdatedAt ?? row.summary_updated_at
|
|
20973
|
+
) : null,
|
|
20916
20974
|
subjectId: row.subjectId != null ? String(row.subjectId) : null,
|
|
20917
20975
|
jobCount: typeof row.jobCount === "number" ? row.jobCount : void 0,
|
|
20918
20976
|
toolCallCount: typeof row.toolCallCount === "number" ? row.toolCallCount : void 0
|
|
@@ -21002,7 +21060,13 @@ var Granular = class _Granular {
|
|
|
21002
21060
|
});
|
|
21003
21061
|
await client.connect();
|
|
21004
21062
|
const graphqlEndpoint = `${this.httpUrl}/orchestrator/graphql`;
|
|
21005
|
-
const environment = new Environment(
|
|
21063
|
+
const environment = new Environment(
|
|
21064
|
+
client,
|
|
21065
|
+
envData,
|
|
21066
|
+
clientId,
|
|
21067
|
+
this.apiKey,
|
|
21068
|
+
graphqlEndpoint
|
|
21069
|
+
);
|
|
21006
21070
|
await environment.hello();
|
|
21007
21071
|
return environment;
|
|
21008
21072
|
}
|
|
@@ -21036,14 +21100,18 @@ var Granular = class _Granular {
|
|
|
21036
21100
|
};
|
|
21037
21101
|
}
|
|
21038
21102
|
async publishSandboxEffectCatalog(host) {
|
|
21039
|
-
const effects2 = Array.from(
|
|
21040
|
-
(
|
|
21041
|
-
);
|
|
21042
|
-
const result = await host.wsClient.call("effects.publishCatalog", {
|
|
21103
|
+
const effects2 = Array.from(
|
|
21104
|
+
this.getSandboxEffectMap(host.sandboxId).values()
|
|
21105
|
+
).map((effect) => this.serializeEffect(effect));
|
|
21106
|
+
const result = await host.wsClient.call("effects.publishCatalog", {
|
|
21107
|
+
effects: effects2
|
|
21108
|
+
});
|
|
21043
21109
|
const acceptedCount = typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
|
|
21044
21110
|
const rejected = Array.isArray(result?.rejected) ? result.rejected : [];
|
|
21045
21111
|
if (acceptedCount === 0 && rejected.length > 0) {
|
|
21046
|
-
const detail = rejected.map(
|
|
21112
|
+
const detail = rejected.map(
|
|
21113
|
+
(entry) => `${entry.name || "unknown"}: ${entry.reason || "rejected"}`
|
|
21114
|
+
).join("; ");
|
|
21047
21115
|
throw new Error(
|
|
21048
21116
|
`Failed to publish live effects for sandbox ${host.sandboxId}: ${detail}`
|
|
21049
21117
|
);
|
|
@@ -21077,13 +21145,15 @@ var Granular = class _Granular {
|
|
|
21077
21145
|
disconnectError
|
|
21078
21146
|
);
|
|
21079
21147
|
}
|
|
21080
|
-
void this.ensureSandboxEffectHost(host.sandboxId).catch(
|
|
21081
|
-
|
|
21082
|
-
|
|
21083
|
-
|
|
21084
|
-
|
|
21085
|
-
|
|
21086
|
-
|
|
21148
|
+
void this.ensureSandboxEffectHost(host.sandboxId).catch(
|
|
21149
|
+
(reconnectError) => {
|
|
21150
|
+
console.error(
|
|
21151
|
+
`[Granular] Failed to recover effect host for sandbox ${host.sandboxId} after heartbeat failure:`,
|
|
21152
|
+
reconnectError
|
|
21153
|
+
);
|
|
21154
|
+
console.error("[Granular] Original heartbeat failure:", error2);
|
|
21155
|
+
}
|
|
21156
|
+
);
|
|
21087
21157
|
}
|
|
21088
21158
|
startEffectHostHeartbeat(host) {
|
|
21089
21159
|
if (host.heartbeatTimer) {
|
|
@@ -21104,9 +21174,15 @@ var Granular = class _Granular {
|
|
|
21104
21174
|
host.heartbeatInFlight = false;
|
|
21105
21175
|
});
|
|
21106
21176
|
};
|
|
21107
|
-
sendHeartbeat(
|
|
21177
|
+
sendHeartbeat(
|
|
21178
|
+
"[Granular] Initial effect host heartbeat failed for sandbox",
|
|
21179
|
+
false
|
|
21180
|
+
);
|
|
21108
21181
|
host.heartbeatTimer = setInterval(() => {
|
|
21109
|
-
sendHeartbeat(
|
|
21182
|
+
sendHeartbeat(
|
|
21183
|
+
"[Granular] Effect host heartbeat failed for sandbox",
|
|
21184
|
+
true
|
|
21185
|
+
);
|
|
21110
21186
|
}, 1e4);
|
|
21111
21187
|
}
|
|
21112
21188
|
stopEffectHostHeartbeat(host) {
|
|
@@ -21138,7 +21214,12 @@ var Granular = class _Granular {
|
|
|
21138
21214
|
const effectClientId = crypto.randomUUID();
|
|
21139
21215
|
const clientId = `effect-host:${sandboxId}:${effectClientId}`;
|
|
21140
21216
|
const wsClient = new WSClient({
|
|
21141
|
-
url: buildEffectHostUrl(
|
|
21217
|
+
url: buildEffectHostUrl(
|
|
21218
|
+
this.apiUrl,
|
|
21219
|
+
sandboxId,
|
|
21220
|
+
effectClientId,
|
|
21221
|
+
clientId
|
|
21222
|
+
),
|
|
21142
21223
|
sessionId: `effect-host:${effectClientId}`,
|
|
21143
21224
|
token: this.apiKey,
|
|
21144
21225
|
tokenProvider: this.tokenProvider,
|
|
@@ -21157,7 +21238,10 @@ var Granular = class _Granular {
|
|
|
21157
21238
|
};
|
|
21158
21239
|
wsClient.registerRpcHandler("effect.invoke", async (params) => {
|
|
21159
21240
|
const request = params;
|
|
21160
|
-
return invokeRegisteredEffect(
|
|
21241
|
+
return invokeRegisteredEffect(
|
|
21242
|
+
this.getSandboxEffectMap(sandboxId),
|
|
21243
|
+
request
|
|
21244
|
+
);
|
|
21161
21245
|
});
|
|
21162
21246
|
wsClient.on("open", () => {
|
|
21163
21247
|
void this.synchronizeEffectHost(host).catch((error2) => {
|
|
@@ -21205,7 +21289,7 @@ var Granular = class _Granular {
|
|
|
21205
21289
|
}
|
|
21206
21290
|
/**
|
|
21207
21291
|
* Register multiple effects (tools) for a specific sandbox.
|
|
21208
|
-
*
|
|
21292
|
+
*
|
|
21209
21293
|
* batch version of `registerEffect`.
|
|
21210
21294
|
*/
|
|
21211
21295
|
async registerEffects(sandboxNameOrId, effects2) {
|
|
@@ -21219,7 +21303,7 @@ var Granular = class _Granular {
|
|
|
21219
21303
|
}
|
|
21220
21304
|
/**
|
|
21221
21305
|
* Unregister an effect from a sandbox.
|
|
21222
|
-
*
|
|
21306
|
+
*
|
|
21223
21307
|
* Removes it from the local sandbox registry and updates the
|
|
21224
21308
|
* sandbox-scoped live catalog.
|
|
21225
21309
|
*/
|
|
@@ -21318,27 +21402,31 @@ var Granular = class _Granular {
|
|
|
21318
21402
|
const assignments = await this.request(
|
|
21319
21403
|
`/control/subjects/${subjectId}/assignments`
|
|
21320
21404
|
);
|
|
21321
|
-
const existing = assignments.items.find(
|
|
21322
|
-
(a) => a.sandboxId === sandboxId
|
|
21323
|
-
);
|
|
21405
|
+
const existing = assignments.items.find((a) => a.sandboxId === sandboxId);
|
|
21324
21406
|
if (existing) {
|
|
21325
21407
|
if (existing.permissionProfileId === permissionProfileId) {
|
|
21326
21408
|
return;
|
|
21327
21409
|
}
|
|
21328
|
-
await this.request(
|
|
21329
|
-
|
|
21330
|
-
|
|
21410
|
+
await this.request(
|
|
21411
|
+
`/control/assignments/${existing.assignmentId}`,
|
|
21412
|
+
{
|
|
21413
|
+
method: "DELETE"
|
|
21414
|
+
}
|
|
21415
|
+
);
|
|
21331
21416
|
}
|
|
21332
21417
|
} catch {
|
|
21333
21418
|
}
|
|
21334
|
-
await this.request(
|
|
21335
|
-
|
|
21336
|
-
|
|
21337
|
-
|
|
21338
|
-
|
|
21339
|
-
|
|
21340
|
-
|
|
21341
|
-
|
|
21419
|
+
await this.request(
|
|
21420
|
+
`/control/subjects/${subjectId}/assignments`,
|
|
21421
|
+
{
|
|
21422
|
+
method: "POST",
|
|
21423
|
+
body: JSON.stringify({
|
|
21424
|
+
sandboxId,
|
|
21425
|
+
subjectId,
|
|
21426
|
+
permissionProfileId
|
|
21427
|
+
})
|
|
21428
|
+
}
|
|
21429
|
+
);
|
|
21342
21430
|
}
|
|
21343
21431
|
/**
|
|
21344
21432
|
* Sandbox management API
|
|
@@ -21418,23 +21506,33 @@ var Granular = class _Granular {
|
|
|
21418
21506
|
},
|
|
21419
21507
|
get: async (environmentId) => {
|
|
21420
21508
|
return normalizeEnvironmentData(
|
|
21421
|
-
await this.request(
|
|
21509
|
+
await this.request(
|
|
21510
|
+
`/control/environments/${environmentId}`
|
|
21511
|
+
)
|
|
21422
21512
|
);
|
|
21423
21513
|
},
|
|
21424
21514
|
create: async (sandboxId, data) => {
|
|
21425
21515
|
const environmentName = data.environment || data.envName;
|
|
21426
|
-
return normalizeEnvironmentData(
|
|
21427
|
-
|
|
21428
|
-
|
|
21429
|
-
|
|
21430
|
-
|
|
21431
|
-
|
|
21432
|
-
|
|
21516
|
+
return normalizeEnvironmentData(
|
|
21517
|
+
await this.request(
|
|
21518
|
+
`/control/sandboxes/${sandboxId}/environments`,
|
|
21519
|
+
{
|
|
21520
|
+
method: "POST",
|
|
21521
|
+
body: JSON.stringify({
|
|
21522
|
+
...data,
|
|
21523
|
+
envName: environmentName
|
|
21524
|
+
})
|
|
21525
|
+
}
|
|
21526
|
+
)
|
|
21527
|
+
);
|
|
21433
21528
|
},
|
|
21434
21529
|
delete: async (environmentId) => {
|
|
21435
|
-
return this.request(
|
|
21436
|
-
|
|
21437
|
-
|
|
21530
|
+
return this.request(
|
|
21531
|
+
`/control/environments/${environmentId}`,
|
|
21532
|
+
{
|
|
21533
|
+
method: "DELETE"
|
|
21534
|
+
}
|
|
21535
|
+
);
|
|
21438
21536
|
}
|
|
21439
21537
|
};
|
|
21440
21538
|
}
|
|
@@ -21454,10 +21552,13 @@ var Granular = class _Granular {
|
|
|
21454
21552
|
}
|
|
21455
21553
|
if (params.since) query.set("since", params.since.toISOString());
|
|
21456
21554
|
if (params.until) query.set("until", params.until.toISOString());
|
|
21457
|
-
if (params.isAcked !== void 0)
|
|
21555
|
+
if (params.isAcked !== void 0)
|
|
21556
|
+
query.set("isAcked", params.isAcked ? "1" : "0");
|
|
21458
21557
|
if (params.limit) query.set("limit", String(params.limit));
|
|
21459
21558
|
if (params.offset) query.set("offset", String(params.offset));
|
|
21460
|
-
const result = await this.request(
|
|
21559
|
+
const result = await this.request(
|
|
21560
|
+
`/control/stream-events?${query.toString()}`
|
|
21561
|
+
);
|
|
21461
21562
|
return (result.items || []).map((row) => ({
|
|
21462
21563
|
eventId: row.event_id,
|
|
21463
21564
|
streamName: row.stream_name,
|
|
@@ -21488,7 +21589,9 @@ var Granular = class _Granular {
|
|
|
21488
21589
|
since: cursor,
|
|
21489
21590
|
limit: 100
|
|
21490
21591
|
});
|
|
21491
|
-
const orderedEvents = [...events].sort(
|
|
21592
|
+
const orderedEvents = [...events].sort(
|
|
21593
|
+
(a, b) => a.createdAt - b.createdAt
|
|
21594
|
+
);
|
|
21492
21595
|
for (const event of orderedEvents) {
|
|
21493
21596
|
if (seenEventIds.has(event.eventId)) {
|
|
21494
21597
|
continue;
|
|
@@ -21501,15 +21604,19 @@ var Granular = class _Granular {
|
|
|
21501
21604
|
params.onEvent(event);
|
|
21502
21605
|
}
|
|
21503
21606
|
} catch (err) {
|
|
21504
|
-
params.onError?.(
|
|
21607
|
+
params.onError?.(
|
|
21608
|
+
err instanceof Error ? err : new Error(String(err))
|
|
21609
|
+
);
|
|
21505
21610
|
}
|
|
21506
21611
|
await new Promise((resolve2) => setTimeout(resolve2, interval));
|
|
21507
21612
|
}
|
|
21508
21613
|
};
|
|
21509
21614
|
poll();
|
|
21510
|
-
return {
|
|
21511
|
-
|
|
21512
|
-
|
|
21615
|
+
return {
|
|
21616
|
+
unsubscribe: () => {
|
|
21617
|
+
running = false;
|
|
21618
|
+
}
|
|
21619
|
+
};
|
|
21513
21620
|
},
|
|
21514
21621
|
ack: async (eventId) => {
|
|
21515
21622
|
await this.request("/control/stream-events/ack", {
|
|
@@ -21527,7 +21634,9 @@ var Granular = class _Granular {
|
|
|
21527
21634
|
const sandbox = await this._resolveSandboxId(params.ontology);
|
|
21528
21635
|
const query = new URLSearchParams({ sandboxId: sandbox });
|
|
21529
21636
|
if (params.environment) query.set("environmentId", params.environment);
|
|
21530
|
-
const result = await this.request(
|
|
21637
|
+
const result = await this.request(
|
|
21638
|
+
`/control/stream-events/stats?${query.toString()}`
|
|
21639
|
+
);
|
|
21531
21640
|
return (result.items || []).map((row) => ({
|
|
21532
21641
|
streamName: row.stream_name,
|
|
21533
21642
|
eventType: row.event_type,
|
|
@@ -21545,10 +21654,14 @@ var Granular = class _Granular {
|
|
|
21545
21654
|
get subjects() {
|
|
21546
21655
|
return {
|
|
21547
21656
|
get: async (subjectId) => {
|
|
21548
|
-
return normalizeSubject(
|
|
21657
|
+
return normalizeSubject(
|
|
21658
|
+
await this.request(`/control/subjects/${subjectId}`)
|
|
21659
|
+
);
|
|
21549
21660
|
},
|
|
21550
21661
|
listAssignments: async (subjectId) => {
|
|
21551
|
-
return this.request(
|
|
21662
|
+
return this.request(
|
|
21663
|
+
`/control/subjects/${subjectId}/assignments`
|
|
21664
|
+
);
|
|
21552
21665
|
}
|
|
21553
21666
|
};
|
|
21554
21667
|
}
|
|
@@ -21558,24 +21671,31 @@ var Granular = class _Granular {
|
|
|
21558
21671
|
get users() {
|
|
21559
21672
|
return {
|
|
21560
21673
|
create: async (data) => {
|
|
21561
|
-
return normalizeSubject(
|
|
21562
|
-
|
|
21563
|
-
|
|
21564
|
-
|
|
21565
|
-
|
|
21566
|
-
|
|
21674
|
+
return normalizeSubject(
|
|
21675
|
+
await this.request("/control/subjects", {
|
|
21676
|
+
method: "POST",
|
|
21677
|
+
body: JSON.stringify({
|
|
21678
|
+
identityId: data.id,
|
|
21679
|
+
name: data.name,
|
|
21680
|
+
email: data.email
|
|
21681
|
+
})
|
|
21567
21682
|
})
|
|
21568
|
-
|
|
21683
|
+
);
|
|
21569
21684
|
},
|
|
21570
21685
|
get: async (id) => {
|
|
21571
|
-
return normalizeSubject(
|
|
21686
|
+
return normalizeSubject(
|
|
21687
|
+
await this.request(`/control/subjects/${id}`)
|
|
21688
|
+
);
|
|
21572
21689
|
}
|
|
21573
21690
|
};
|
|
21574
21691
|
}
|
|
21575
21692
|
async _resolveSandboxId(ontologyNameOrId) {
|
|
21576
21693
|
if (ontologyNameOrId.startsWith("sbx_")) return ontologyNameOrId;
|
|
21577
|
-
const result = await this.request(
|
|
21578
|
-
|
|
21694
|
+
const result = await this.request(
|
|
21695
|
+
`/control/sandboxes?name=${encodeURIComponent(ontologyNameOrId)}`
|
|
21696
|
+
);
|
|
21697
|
+
if (result.items.length === 0)
|
|
21698
|
+
throw new Error(`Ontology not found: ${ontologyNameOrId}`);
|
|
21579
21699
|
return result.items[0].sandboxId;
|
|
21580
21700
|
}
|
|
21581
21701
|
/**
|
|
@@ -21590,9 +21710,9 @@ var Granular = class _Granular {
|
|
|
21590
21710
|
const response = await fetch(url, {
|
|
21591
21711
|
...options,
|
|
21592
21712
|
headers: {
|
|
21593
|
-
|
|
21713
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
21594
21714
|
"Content-Type": "application/json",
|
|
21595
|
-
|
|
21715
|
+
Connection: "close",
|
|
21596
21716
|
...options.headers
|
|
21597
21717
|
}
|
|
21598
21718
|
});
|
|
@@ -21603,7 +21723,11 @@ var Granular = class _Granular {
|
|
|
21603
21723
|
return response.json();
|
|
21604
21724
|
}
|
|
21605
21725
|
const errorText = await response.text();
|
|
21606
|
-
const retryable = isRetryableLocalWorkerRestart(
|
|
21726
|
+
const retryable = isRetryableLocalWorkerRestart(
|
|
21727
|
+
response.status,
|
|
21728
|
+
errorText,
|
|
21729
|
+
url
|
|
21730
|
+
);
|
|
21607
21731
|
if (retryable && attempt < LOCAL_CONTROL_REQUEST_RETRY_COUNT) {
|
|
21608
21732
|
if (this.debugHttp) {
|
|
21609
21733
|
console.warn(
|