@granular-software/sdk 0.4.28 → 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 +275 -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
|
});
|
|
@@ -15038,6 +15029,8 @@ Runtime note: class-wide \`search\` is implemented as one denormalized full-text
|
|
|
15038
15029
|
Preferred search ladder: start with plain text such as \`find({ search: "example name" })\`. If results are empty or ambiguous, retry with RediSearch-style fuzzy / prefix / OR operators. Add \`filter\` only when you need exact structural narrowing.
|
|
15039
15030
|
Plain string \`search\` is normalized for human text before it reaches full-text search, so case, punctuation, repeated whitespace, and diacritics do not need to be manually enumerated in agent code.
|
|
15040
15031
|
Do not manually enumerate capitalization / punctuation / spacing variants in agent code. Use plain \`search\` first, then escalate to fuzzy / prefix / OR operators only if the first pass is empty or ambiguous.
|
|
15032
|
+
Sorting is first-class too: use generated runtime syntax like \`page({ perPage: 10, sort: { field: "year", order: "asc" } })\` or GraphQL syntax like \`direct_instances(per_page: 10, sort: { at: "year", order: ASC })\` when result order matters.
|
|
15033
|
+
Sorting is applied in FalkorDB before pagination and limit selection. Do not load a page and sort it afterward in agent code.
|
|
15041
15034
|
Optional indexing note: \`"searchable": { "phonetic": true }\` keeps the field searchable and enables FalkorDB phonetic matching for the class-wide search index. Query syntax does not change; agents should keep using normal \`search\` strings.
|
|
15042
15035
|
Without an explicit sort, indexed \`search\` results are returned in FalkorDB relevance order. If you pass a sort, that explicit sort takes precedence.
|
|
15043
15036
|
|
|
@@ -19518,17 +19511,40 @@ function buildEffectMetamodelMutations(toolPath, spec) {
|
|
|
19518
19511
|
|
|
19519
19512
|
// src/client.ts
|
|
19520
19513
|
var STANDARD_MODULES_OPERATIONS = [
|
|
19521
|
-
{
|
|
19514
|
+
{
|
|
19515
|
+
create: "entity",
|
|
19516
|
+
has: { id: { value: "auto-generated" }, createdAt: { value: void 0 } }
|
|
19517
|
+
},
|
|
19522
19518
|
{ create: "class", extends: "entity", has: {} },
|
|
19523
|
-
{
|
|
19524
|
-
|
|
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
|
+
},
|
|
19525
19533
|
{ create: "string", has: {} },
|
|
19526
19534
|
{ create: "number", has: {} },
|
|
19527
19535
|
{ create: "boolean", has: {} },
|
|
19528
|
-
{
|
|
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
|
+
}
|
|
19529
19545
|
];
|
|
19530
19546
|
var BUILTIN_MODULES = {
|
|
19531
|
-
|
|
19547
|
+
standard_modules: STANDARD_MODULES_OPERATIONS
|
|
19532
19548
|
};
|
|
19533
19549
|
var DEFAULT_DIRECT_RECORD_OBJECTS_REQUEST_BATCH_SIZE = 100;
|
|
19534
19550
|
var MAX_RECORD_OBJECTS_CONCURRENCY = 16;
|
|
@@ -19563,7 +19579,9 @@ function isRetryableLocalWorkerRestart(status, body, url) {
|
|
|
19563
19579
|
}
|
|
19564
19580
|
function isRetryableRecordObjectsError(error2) {
|
|
19565
19581
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
19566
|
-
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
|
+
);
|
|
19567
19585
|
}
|
|
19568
19586
|
function computeEffectKey2(effect) {
|
|
19569
19587
|
const attachedClass = effect.className?.trim();
|
|
@@ -19640,7 +19658,10 @@ function normalizeUser(user) {
|
|
|
19640
19658
|
};
|
|
19641
19659
|
}
|
|
19642
19660
|
function normalizeEnvironmentData(environment) {
|
|
19643
|
-
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
|
+
});
|
|
19644
19665
|
const environmentName = environment.environment || environment.envName || "prod";
|
|
19645
19666
|
return {
|
|
19646
19667
|
...environment,
|
|
@@ -19734,14 +19755,16 @@ var Environment = class extends Session {
|
|
|
19734
19755
|
const response = await fetch(url, {
|
|
19735
19756
|
...options,
|
|
19736
19757
|
headers: {
|
|
19737
|
-
|
|
19758
|
+
Authorization: `Bearer ${this._apiKey}`,
|
|
19738
19759
|
"Content-Type": "application/json",
|
|
19739
|
-
|
|
19760
|
+
Connection: "close",
|
|
19740
19761
|
...options.headers
|
|
19741
19762
|
}
|
|
19742
19763
|
});
|
|
19743
19764
|
if (!response.ok) {
|
|
19744
|
-
throw new Error(
|
|
19765
|
+
throw new Error(
|
|
19766
|
+
`Control Plane API Error (${response.status}): ${await response.text()}`
|
|
19767
|
+
);
|
|
19745
19768
|
}
|
|
19746
19769
|
return response.json();
|
|
19747
19770
|
}
|
|
@@ -19755,9 +19778,12 @@ var Environment = class extends Session {
|
|
|
19755
19778
|
async disconnect() {
|
|
19756
19779
|
let wsNotifiedRuntime = false;
|
|
19757
19780
|
try {
|
|
19758
|
-
const goodbye = await this.rpc(
|
|
19759
|
-
|
|
19760
|
-
|
|
19781
|
+
const goodbye = await this.rpc(
|
|
19782
|
+
"client.goodbye",
|
|
19783
|
+
{
|
|
19784
|
+
timestamp: Date.now()
|
|
19785
|
+
}
|
|
19786
|
+
);
|
|
19761
19787
|
wsNotifiedRuntime = Boolean(goodbye?.ok && goodbye?.via);
|
|
19762
19788
|
} catch {
|
|
19763
19789
|
wsNotifiedRuntime = false;
|
|
@@ -19771,8 +19797,8 @@ var Environment = class extends Session {
|
|
|
19771
19797
|
method: "POST",
|
|
19772
19798
|
headers: {
|
|
19773
19799
|
"Content-Type": "application/json",
|
|
19774
|
-
|
|
19775
|
-
|
|
19800
|
+
Authorization: `Bearer ${this._apiKey}`,
|
|
19801
|
+
Connection: "close"
|
|
19776
19802
|
},
|
|
19777
19803
|
body: JSON.stringify({
|
|
19778
19804
|
reason: "sdk_disconnect_http_fallback",
|
|
@@ -19785,6 +19811,15 @@ var Environment = class extends Session {
|
|
|
19785
19811
|
}
|
|
19786
19812
|
this.client.disconnect();
|
|
19787
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
|
+
}
|
|
19788
19823
|
// ==================== GRAPH CONTAINER READINESS ====================
|
|
19789
19824
|
/** The last known graph container status, updated by checkReadiness() or on heartbeat */
|
|
19790
19825
|
graphContainerStatus = null;
|
|
@@ -19850,14 +19885,14 @@ var Environment = class extends Session {
|
|
|
19850
19885
|
}
|
|
19851
19886
|
/**
|
|
19852
19887
|
* Execute a GraphQL query against the environment's graph.
|
|
19853
|
-
*
|
|
19888
|
+
*
|
|
19854
19889
|
* The query uses the Granular graph query language (based on Cypher/GraphQL).
|
|
19855
19890
|
* Authentication is handled automatically using the SDK's API key.
|
|
19856
|
-
*
|
|
19891
|
+
*
|
|
19857
19892
|
* @param query - The GraphQL query string
|
|
19858
19893
|
* @param variables - Optional variables for the query
|
|
19859
19894
|
* @returns The query result data
|
|
19860
|
-
*
|
|
19895
|
+
*
|
|
19861
19896
|
* @example
|
|
19862
19897
|
* ```typescript
|
|
19863
19898
|
* // Read the workspace
|
|
@@ -19865,7 +19900,7 @@ var Environment = class extends Session {
|
|
|
19865
19900
|
* `query { model(path: "workspace") { path label submodels { path label } } }`
|
|
19866
19901
|
* );
|
|
19867
19902
|
* console.log(result.data);
|
|
19868
|
-
*
|
|
19903
|
+
*
|
|
19869
19904
|
* // Create a model
|
|
19870
19905
|
* const created = await env.graphql(
|
|
19871
19906
|
* `mutation { at(path: "workspace") { create_submodel(subpath: "my_node", label: "My Node", prototype: "Model") { model { path label } } } }`
|
|
@@ -19877,7 +19912,7 @@ var Environment = class extends Session {
|
|
|
19877
19912
|
method: "POST",
|
|
19878
19913
|
headers: {
|
|
19879
19914
|
"Content-Type": "application/json",
|
|
19880
|
-
|
|
19915
|
+
Authorization: `Bearer ${this._apiKey}`
|
|
19881
19916
|
},
|
|
19882
19917
|
body: JSON.stringify({
|
|
19883
19918
|
environmentId: this.environmentId,
|
|
@@ -19894,10 +19929,10 @@ var Environment = class extends Session {
|
|
|
19894
19929
|
// ==================== RELATIONSHIP METHODS ====================
|
|
19895
19930
|
/**
|
|
19896
19931
|
* Define a relationship between two model types.
|
|
19897
|
-
*
|
|
19932
|
+
*
|
|
19898
19933
|
* Creates both submodels (if they don't exist) and links them with
|
|
19899
19934
|
* a RelationshipDef node that encodes cardinality.
|
|
19900
|
-
*
|
|
19935
|
+
*
|
|
19901
19936
|
* @example
|
|
19902
19937
|
* ```typescript
|
|
19903
19938
|
* // Author has many Books, Book has one Author
|
|
@@ -19957,10 +19992,10 @@ var Environment = class extends Session {
|
|
|
19957
19992
|
}
|
|
19958
19993
|
/**
|
|
19959
19994
|
* Get all relationships for a model type.
|
|
19960
|
-
*
|
|
19995
|
+
*
|
|
19961
19996
|
* @param modelPath - The model type path (e.g., "author")
|
|
19962
19997
|
* @returns Array of relationships from this model's perspective
|
|
19963
|
-
*
|
|
19998
|
+
*
|
|
19964
19999
|
* @example
|
|
19965
20000
|
* ```typescript
|
|
19966
20001
|
* const rels = await env.getRelationships('author');
|
|
@@ -19993,18 +20028,18 @@ var Environment = class extends Session {
|
|
|
19993
20028
|
}
|
|
19994
20029
|
/**
|
|
19995
20030
|
* Attach a target model to a relationship submodel.
|
|
19996
|
-
*
|
|
20031
|
+
*
|
|
19997
20032
|
* Handles cardinality automatically:
|
|
19998
20033
|
* - "One" side: sets/replaces the reference
|
|
19999
20034
|
* - "Many" side: adds the target to the collection
|
|
20000
|
-
*
|
|
20035
|
+
*
|
|
20001
20036
|
* If the target model doesn't exist, it's created as an instance of the foreign type.
|
|
20002
20037
|
* Bidirectional sync is automatic.
|
|
20003
|
-
*
|
|
20038
|
+
*
|
|
20004
20039
|
* @param modelPath - The model instance path (e.g., "tolkien")
|
|
20005
20040
|
* @param submodelPath - The relationship submodel (e.g., "books")
|
|
20006
20041
|
* @param targetPath - The target model to attach (e.g., "lord_of_the_rings")
|
|
20007
|
-
*
|
|
20042
|
+
*
|
|
20008
20043
|
* @example
|
|
20009
20044
|
* ```typescript
|
|
20010
20045
|
* // Attach a book to an author (many side)
|
|
@@ -20031,18 +20066,18 @@ var Environment = class extends Session {
|
|
|
20031
20066
|
}
|
|
20032
20067
|
/**
|
|
20033
20068
|
* Detach a target model from a relationship submodel.
|
|
20034
|
-
*
|
|
20069
|
+
*
|
|
20035
20070
|
* Handles bidirectional cleanup automatically.
|
|
20036
|
-
*
|
|
20071
|
+
*
|
|
20037
20072
|
* @param modelPath - The model instance path
|
|
20038
20073
|
* @param submodelPath - The relationship submodel
|
|
20039
20074
|
* @param targetPath - The target to detach (optional for "one" side; omit on "many" side to detach all)
|
|
20040
|
-
*
|
|
20075
|
+
*
|
|
20041
20076
|
* @example
|
|
20042
20077
|
* ```typescript
|
|
20043
20078
|
* // Detach a specific book
|
|
20044
20079
|
* await env.detach('tolkien', 'books', 'lord_of_the_rings');
|
|
20045
|
-
*
|
|
20080
|
+
*
|
|
20046
20081
|
* // Detach all books
|
|
20047
20082
|
* await env.detach('tolkien', 'books');
|
|
20048
20083
|
* ```
|
|
@@ -20066,11 +20101,11 @@ var Environment = class extends Session {
|
|
|
20066
20101
|
}
|
|
20067
20102
|
/**
|
|
20068
20103
|
* List all related models through a relationship submodel.
|
|
20069
|
-
*
|
|
20104
|
+
*
|
|
20070
20105
|
* @param modelPath - The model instance path
|
|
20071
20106
|
* @param submodelPath - The relationship submodel
|
|
20072
20107
|
* @returns Array of related model references
|
|
20073
|
-
*
|
|
20108
|
+
*
|
|
20074
20109
|
* @example
|
|
20075
20110
|
* ```typescript
|
|
20076
20111
|
* const books = await env.listRelated('tolkien', 'books');
|
|
@@ -20094,14 +20129,14 @@ var Environment = class extends Session {
|
|
|
20094
20129
|
}
|
|
20095
20130
|
/**
|
|
20096
20131
|
* Apply a manifest to the current environment's graph.
|
|
20097
|
-
*
|
|
20132
|
+
*
|
|
20098
20133
|
* Translates each manifest operation into GraphQL mutations and executes them
|
|
20099
20134
|
* in order. This is the core mechanism for creating classes, fields, and
|
|
20100
20135
|
* relationships from a declarative manifest.
|
|
20101
|
-
*
|
|
20136
|
+
*
|
|
20102
20137
|
* @param manifest - The manifest content to apply
|
|
20103
20138
|
* @returns Summary of applied operations
|
|
20104
|
-
*
|
|
20139
|
+
*
|
|
20105
20140
|
* @example
|
|
20106
20141
|
* ```typescript
|
|
20107
20142
|
* await environment.applyManifest({
|
|
@@ -20139,12 +20174,16 @@ var Environment = class extends Session {
|
|
|
20139
20174
|
applied++;
|
|
20140
20175
|
} catch (err) {
|
|
20141
20176
|
if (!err.message?.includes("already exists")) {
|
|
20142
|
-
errors.push(
|
|
20177
|
+
errors.push(
|
|
20178
|
+
`Import ${imp.name} operation failed: ${err.message}`
|
|
20179
|
+
);
|
|
20143
20180
|
}
|
|
20144
20181
|
}
|
|
20145
20182
|
}
|
|
20146
20183
|
} else {
|
|
20147
|
-
errors.push(
|
|
20184
|
+
errors.push(
|
|
20185
|
+
`Unknown module: "${imp.name}" (only built-in modules are supported)`
|
|
20186
|
+
);
|
|
20148
20187
|
}
|
|
20149
20188
|
}
|
|
20150
20189
|
}
|
|
@@ -20228,7 +20267,10 @@ var Environment = class extends Session {
|
|
|
20228
20267
|
}
|
|
20229
20268
|
}
|
|
20230
20269
|
async _applyEffectMetamodels(toolPath, metamodels) {
|
|
20231
|
-
for (const mutation of buildEffectMetamodelMutations(
|
|
20270
|
+
for (const mutation of buildEffectMetamodelMutations(
|
|
20271
|
+
toolPath,
|
|
20272
|
+
metamodels
|
|
20273
|
+
)) {
|
|
20232
20274
|
await this._runGraphql(mutation.query, mutation.label);
|
|
20233
20275
|
}
|
|
20234
20276
|
}
|
|
@@ -20277,7 +20319,9 @@ var Environment = class extends Session {
|
|
|
20277
20319
|
}
|
|
20278
20320
|
if (eventType.payloadSchema?.properties) {
|
|
20279
20321
|
const fieldSpecs = {};
|
|
20280
|
-
for (const [propName, propSchema] of Object.entries(
|
|
20322
|
+
for (const [propName, propSchema] of Object.entries(
|
|
20323
|
+
eventType.payloadSchema.properties
|
|
20324
|
+
)) {
|
|
20281
20325
|
const schema = propSchema;
|
|
20282
20326
|
fieldSpecs[propName] = {
|
|
20283
20327
|
type: schema.type ?? "string",
|
|
@@ -20560,7 +20604,9 @@ var Environment = class extends Session {
|
|
|
20560
20604
|
const wave = plans.slice(waveStart, waveStart + concurrency);
|
|
20561
20605
|
await Promise.all(
|
|
20562
20606
|
wave.map(async (plan) => {
|
|
20563
|
-
const { items, durationMs } = await this.executeRecordObjectsChunk(
|
|
20607
|
+
const { items, durationMs } = await this.executeRecordObjectsChunk(
|
|
20608
|
+
plan.slice
|
|
20609
|
+
);
|
|
20564
20610
|
if (items.length !== plan.slice.length) {
|
|
20565
20611
|
throw new Error(
|
|
20566
20612
|
`recordObjects: chunk ${plan.chunkIndex + 1} returned ${items.length} results, expected ${plan.slice.length}`
|
|
@@ -20590,13 +20636,10 @@ var Environment = class extends Session {
|
|
|
20590
20636
|
let lastError;
|
|
20591
20637
|
for (let attempt = 1; attempt <= DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_COUNT; attempt += 1) {
|
|
20592
20638
|
try {
|
|
20593
|
-
const response = await this.controlPlaneRequest(
|
|
20594
|
-
|
|
20595
|
-
{
|
|
20596
|
-
|
|
20597
|
-
body: JSON.stringify({ records: chunk })
|
|
20598
|
-
}
|
|
20599
|
-
);
|
|
20639
|
+
const response = await this.controlPlaneRequest(`/control/environments/${this.environmentId}/records/batch`, {
|
|
20640
|
+
method: "POST",
|
|
20641
|
+
body: JSON.stringify({ records: chunk })
|
|
20642
|
+
});
|
|
20600
20643
|
const items = Array.isArray(response.items) ? response.items : [];
|
|
20601
20644
|
return { items, durationMs: Date.now() - wallStart };
|
|
20602
20645
|
} catch (error2) {
|
|
@@ -20659,7 +20702,9 @@ var Environment = class extends Session {
|
|
|
20659
20702
|
* Fetch a single record import by id.
|
|
20660
20703
|
*/
|
|
20661
20704
|
async getRecordImport(importId) {
|
|
20662
|
-
return this.controlPlaneRequest(
|
|
20705
|
+
return this.controlPlaneRequest(
|
|
20706
|
+
`/control/record-imports/${importId}`
|
|
20707
|
+
);
|
|
20663
20708
|
}
|
|
20664
20709
|
/**
|
|
20665
20710
|
* Cancel a queued/background record import.
|
|
@@ -20726,7 +20771,9 @@ var Granular = class _Granular {
|
|
|
20726
20771
|
constructor(options) {
|
|
20727
20772
|
const auth = options.token ?? options.apiKey;
|
|
20728
20773
|
if (!auth) {
|
|
20729
|
-
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
|
+
);
|
|
20730
20777
|
}
|
|
20731
20778
|
this.apiUrl = resolveApiUrl(options.apiUrl, options.endpointMode);
|
|
20732
20779
|
this.apiKey = resolveAuthTokenForApiUrl(auth, this.apiUrl);
|
|
@@ -20738,10 +20785,10 @@ var Granular = class _Granular {
|
|
|
20738
20785
|
}
|
|
20739
20786
|
/**
|
|
20740
20787
|
* Records/upserts a user and prepares them for sandbox connections
|
|
20741
|
-
*
|
|
20788
|
+
*
|
|
20742
20789
|
* @param options - User options
|
|
20743
20790
|
* @returns The recorded user with both `userId` and `granularId`
|
|
20744
|
-
*
|
|
20791
|
+
*
|
|
20745
20792
|
* @example
|
|
20746
20793
|
* ```typescript
|
|
20747
20794
|
* const user = await granular.recordUser({
|
|
@@ -20752,14 +20799,16 @@ var Granular = class _Granular {
|
|
|
20752
20799
|
* ```
|
|
20753
20800
|
*/
|
|
20754
20801
|
async recordUser(options) {
|
|
20755
|
-
const subject = normalizeSubject(
|
|
20756
|
-
|
|
20757
|
-
|
|
20758
|
-
|
|
20759
|
-
|
|
20760
|
-
|
|
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
|
+
})
|
|
20761
20810
|
})
|
|
20762
|
-
|
|
20811
|
+
);
|
|
20763
20812
|
return normalizeUser({
|
|
20764
20813
|
granularId: subject.granularId,
|
|
20765
20814
|
userId: options.userId,
|
|
@@ -20806,18 +20855,20 @@ var Granular = class _Granular {
|
|
|
20806
20855
|
permissions: options.permissions || []
|
|
20807
20856
|
};
|
|
20808
20857
|
}
|
|
20809
|
-
throw new Error(
|
|
20858
|
+
throw new Error(
|
|
20859
|
+
"connect() requires either userId, granularId, or a user object returned by recordUser()."
|
|
20860
|
+
);
|
|
20810
20861
|
}
|
|
20811
20862
|
/**
|
|
20812
20863
|
* Connect to an ontology environment and establish a real-time session.
|
|
20813
|
-
*
|
|
20864
|
+
*
|
|
20814
20865
|
* Effects are registered at the sandbox level via `granular.registerEffect()`
|
|
20815
20866
|
* or `granular.registerEffects()`. Sessions pick up live availability from
|
|
20816
20867
|
* the sandbox registry automatically.
|
|
20817
|
-
*
|
|
20868
|
+
*
|
|
20818
20869
|
* @param options - Connection options
|
|
20819
20870
|
* @returns An active environment session
|
|
20820
|
-
*
|
|
20871
|
+
*
|
|
20821
20872
|
* @example
|
|
20822
20873
|
* ```typescript
|
|
20823
20874
|
* const environment = await granular.connect({
|
|
@@ -20826,23 +20877,23 @@ var Granular = class _Granular {
|
|
|
20826
20877
|
* userId: 'user_123',
|
|
20827
20878
|
* permissions: ['agent'],
|
|
20828
20879
|
* });
|
|
20829
|
-
*
|
|
20880
|
+
*
|
|
20830
20881
|
* await granular.registerEffect('my-sandbox', {
|
|
20831
20882
|
* name: 'greet',
|
|
20832
20883
|
* description: 'Say hello',
|
|
20833
20884
|
* inputSchema: { type: 'object', properties: {} },
|
|
20834
20885
|
* handler: async () => 'Hello!',
|
|
20835
20886
|
* });
|
|
20836
|
-
*
|
|
20887
|
+
*
|
|
20837
20888
|
* // Submit job
|
|
20838
20889
|
* const job = await environment.submitJob(`
|
|
20839
20890
|
* import { tools } from './sandbox-tools';
|
|
20840
20891
|
* return await tools.greet({});
|
|
20841
20892
|
* `);
|
|
20842
|
-
*
|
|
20893
|
+
*
|
|
20843
20894
|
* console.log(await job.result); // 'Hello!'
|
|
20844
20895
|
* ```
|
|
20845
|
-
|
|
20896
|
+
*/
|
|
20846
20897
|
async connect(options) {
|
|
20847
20898
|
const clientId = options.clientId || `client_${Date.now()}`;
|
|
20848
20899
|
const ontology = options.ontology;
|
|
@@ -20857,8 +20908,15 @@ var Granular = class _Granular {
|
|
|
20857
20908
|
const user = await this.resolveConnectUser(options);
|
|
20858
20909
|
const sandbox = await this.findOrCreateSandbox(ontology);
|
|
20859
20910
|
for (const profileName of user.permissions) {
|
|
20860
|
-
const profileId = await this.ensurePermissionProfile(
|
|
20861
|
-
|
|
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
|
+
);
|
|
20862
20920
|
}
|
|
20863
20921
|
const envData = await this.environments.create(sandbox.sandboxId, {
|
|
20864
20922
|
subjectId: user.granularId,
|
|
@@ -20910,7 +20968,9 @@ var Granular = class _Granular {
|
|
|
20910
20968
|
createdAt: _Granular.coerceIsoDate(row.createdAt ?? row.created_at),
|
|
20911
20969
|
lastSeenAt: _Granular.coerceIsoDate(row.lastSeenAt ?? row.last_seen_at),
|
|
20912
20970
|
summary: row.summary != null ? String(row.summary) : null,
|
|
20913
|
-
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,
|
|
20914
20974
|
subjectId: row.subjectId != null ? String(row.subjectId) : null,
|
|
20915
20975
|
jobCount: typeof row.jobCount === "number" ? row.jobCount : void 0,
|
|
20916
20976
|
toolCallCount: typeof row.toolCallCount === "number" ? row.toolCallCount : void 0
|
|
@@ -21000,7 +21060,13 @@ var Granular = class _Granular {
|
|
|
21000
21060
|
});
|
|
21001
21061
|
await client.connect();
|
|
21002
21062
|
const graphqlEndpoint = `${this.httpUrl}/orchestrator/graphql`;
|
|
21003
|
-
const environment = new Environment(
|
|
21063
|
+
const environment = new Environment(
|
|
21064
|
+
client,
|
|
21065
|
+
envData,
|
|
21066
|
+
clientId,
|
|
21067
|
+
this.apiKey,
|
|
21068
|
+
graphqlEndpoint
|
|
21069
|
+
);
|
|
21004
21070
|
await environment.hello();
|
|
21005
21071
|
return environment;
|
|
21006
21072
|
}
|
|
@@ -21034,14 +21100,18 @@ var Granular = class _Granular {
|
|
|
21034
21100
|
};
|
|
21035
21101
|
}
|
|
21036
21102
|
async publishSandboxEffectCatalog(host) {
|
|
21037
|
-
const effects2 = Array.from(
|
|
21038
|
-
(
|
|
21039
|
-
);
|
|
21040
|
-
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
|
+
});
|
|
21041
21109
|
const acceptedCount = typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
|
|
21042
21110
|
const rejected = Array.isArray(result?.rejected) ? result.rejected : [];
|
|
21043
21111
|
if (acceptedCount === 0 && rejected.length > 0) {
|
|
21044
|
-
const detail = rejected.map(
|
|
21112
|
+
const detail = rejected.map(
|
|
21113
|
+
(entry) => `${entry.name || "unknown"}: ${entry.reason || "rejected"}`
|
|
21114
|
+
).join("; ");
|
|
21045
21115
|
throw new Error(
|
|
21046
21116
|
`Failed to publish live effects for sandbox ${host.sandboxId}: ${detail}`
|
|
21047
21117
|
);
|
|
@@ -21075,13 +21145,15 @@ var Granular = class _Granular {
|
|
|
21075
21145
|
disconnectError
|
|
21076
21146
|
);
|
|
21077
21147
|
}
|
|
21078
|
-
void this.ensureSandboxEffectHost(host.sandboxId).catch(
|
|
21079
|
-
|
|
21080
|
-
|
|
21081
|
-
|
|
21082
|
-
|
|
21083
|
-
|
|
21084
|
-
|
|
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
|
+
);
|
|
21085
21157
|
}
|
|
21086
21158
|
startEffectHostHeartbeat(host) {
|
|
21087
21159
|
if (host.heartbeatTimer) {
|
|
@@ -21102,9 +21174,15 @@ var Granular = class _Granular {
|
|
|
21102
21174
|
host.heartbeatInFlight = false;
|
|
21103
21175
|
});
|
|
21104
21176
|
};
|
|
21105
|
-
sendHeartbeat(
|
|
21177
|
+
sendHeartbeat(
|
|
21178
|
+
"[Granular] Initial effect host heartbeat failed for sandbox",
|
|
21179
|
+
false
|
|
21180
|
+
);
|
|
21106
21181
|
host.heartbeatTimer = setInterval(() => {
|
|
21107
|
-
sendHeartbeat(
|
|
21182
|
+
sendHeartbeat(
|
|
21183
|
+
"[Granular] Effect host heartbeat failed for sandbox",
|
|
21184
|
+
true
|
|
21185
|
+
);
|
|
21108
21186
|
}, 1e4);
|
|
21109
21187
|
}
|
|
21110
21188
|
stopEffectHostHeartbeat(host) {
|
|
@@ -21136,7 +21214,12 @@ var Granular = class _Granular {
|
|
|
21136
21214
|
const effectClientId = crypto.randomUUID();
|
|
21137
21215
|
const clientId = `effect-host:${sandboxId}:${effectClientId}`;
|
|
21138
21216
|
const wsClient = new WSClient({
|
|
21139
|
-
url: buildEffectHostUrl(
|
|
21217
|
+
url: buildEffectHostUrl(
|
|
21218
|
+
this.apiUrl,
|
|
21219
|
+
sandboxId,
|
|
21220
|
+
effectClientId,
|
|
21221
|
+
clientId
|
|
21222
|
+
),
|
|
21140
21223
|
sessionId: `effect-host:${effectClientId}`,
|
|
21141
21224
|
token: this.apiKey,
|
|
21142
21225
|
tokenProvider: this.tokenProvider,
|
|
@@ -21155,7 +21238,10 @@ var Granular = class _Granular {
|
|
|
21155
21238
|
};
|
|
21156
21239
|
wsClient.registerRpcHandler("effect.invoke", async (params) => {
|
|
21157
21240
|
const request = params;
|
|
21158
|
-
return invokeRegisteredEffect(
|
|
21241
|
+
return invokeRegisteredEffect(
|
|
21242
|
+
this.getSandboxEffectMap(sandboxId),
|
|
21243
|
+
request
|
|
21244
|
+
);
|
|
21159
21245
|
});
|
|
21160
21246
|
wsClient.on("open", () => {
|
|
21161
21247
|
void this.synchronizeEffectHost(host).catch((error2) => {
|
|
@@ -21203,7 +21289,7 @@ var Granular = class _Granular {
|
|
|
21203
21289
|
}
|
|
21204
21290
|
/**
|
|
21205
21291
|
* Register multiple effects (tools) for a specific sandbox.
|
|
21206
|
-
*
|
|
21292
|
+
*
|
|
21207
21293
|
* batch version of `registerEffect`.
|
|
21208
21294
|
*/
|
|
21209
21295
|
async registerEffects(sandboxNameOrId, effects2) {
|
|
@@ -21217,7 +21303,7 @@ var Granular = class _Granular {
|
|
|
21217
21303
|
}
|
|
21218
21304
|
/**
|
|
21219
21305
|
* Unregister an effect from a sandbox.
|
|
21220
|
-
*
|
|
21306
|
+
*
|
|
21221
21307
|
* Removes it from the local sandbox registry and updates the
|
|
21222
21308
|
* sandbox-scoped live catalog.
|
|
21223
21309
|
*/
|
|
@@ -21316,27 +21402,31 @@ var Granular = class _Granular {
|
|
|
21316
21402
|
const assignments = await this.request(
|
|
21317
21403
|
`/control/subjects/${subjectId}/assignments`
|
|
21318
21404
|
);
|
|
21319
|
-
const existing = assignments.items.find(
|
|
21320
|
-
(a) => a.sandboxId === sandboxId
|
|
21321
|
-
);
|
|
21405
|
+
const existing = assignments.items.find((a) => a.sandboxId === sandboxId);
|
|
21322
21406
|
if (existing) {
|
|
21323
21407
|
if (existing.permissionProfileId === permissionProfileId) {
|
|
21324
21408
|
return;
|
|
21325
21409
|
}
|
|
21326
|
-
await this.request(
|
|
21327
|
-
|
|
21328
|
-
|
|
21410
|
+
await this.request(
|
|
21411
|
+
`/control/assignments/${existing.assignmentId}`,
|
|
21412
|
+
{
|
|
21413
|
+
method: "DELETE"
|
|
21414
|
+
}
|
|
21415
|
+
);
|
|
21329
21416
|
}
|
|
21330
21417
|
} catch {
|
|
21331
21418
|
}
|
|
21332
|
-
await this.request(
|
|
21333
|
-
|
|
21334
|
-
|
|
21335
|
-
|
|
21336
|
-
|
|
21337
|
-
|
|
21338
|
-
|
|
21339
|
-
|
|
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
|
+
);
|
|
21340
21430
|
}
|
|
21341
21431
|
/**
|
|
21342
21432
|
* Sandbox management API
|
|
@@ -21416,23 +21506,33 @@ var Granular = class _Granular {
|
|
|
21416
21506
|
},
|
|
21417
21507
|
get: async (environmentId) => {
|
|
21418
21508
|
return normalizeEnvironmentData(
|
|
21419
|
-
await this.request(
|
|
21509
|
+
await this.request(
|
|
21510
|
+
`/control/environments/${environmentId}`
|
|
21511
|
+
)
|
|
21420
21512
|
);
|
|
21421
21513
|
},
|
|
21422
21514
|
create: async (sandboxId, data) => {
|
|
21423
21515
|
const environmentName = data.environment || data.envName;
|
|
21424
|
-
return normalizeEnvironmentData(
|
|
21425
|
-
|
|
21426
|
-
|
|
21427
|
-
|
|
21428
|
-
|
|
21429
|
-
|
|
21430
|
-
|
|
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
|
+
);
|
|
21431
21528
|
},
|
|
21432
21529
|
delete: async (environmentId) => {
|
|
21433
|
-
return this.request(
|
|
21434
|
-
|
|
21435
|
-
|
|
21530
|
+
return this.request(
|
|
21531
|
+
`/control/environments/${environmentId}`,
|
|
21532
|
+
{
|
|
21533
|
+
method: "DELETE"
|
|
21534
|
+
}
|
|
21535
|
+
);
|
|
21436
21536
|
}
|
|
21437
21537
|
};
|
|
21438
21538
|
}
|
|
@@ -21452,10 +21552,13 @@ var Granular = class _Granular {
|
|
|
21452
21552
|
}
|
|
21453
21553
|
if (params.since) query.set("since", params.since.toISOString());
|
|
21454
21554
|
if (params.until) query.set("until", params.until.toISOString());
|
|
21455
|
-
if (params.isAcked !== void 0)
|
|
21555
|
+
if (params.isAcked !== void 0)
|
|
21556
|
+
query.set("isAcked", params.isAcked ? "1" : "0");
|
|
21456
21557
|
if (params.limit) query.set("limit", String(params.limit));
|
|
21457
21558
|
if (params.offset) query.set("offset", String(params.offset));
|
|
21458
|
-
const result = await this.request(
|
|
21559
|
+
const result = await this.request(
|
|
21560
|
+
`/control/stream-events?${query.toString()}`
|
|
21561
|
+
);
|
|
21459
21562
|
return (result.items || []).map((row) => ({
|
|
21460
21563
|
eventId: row.event_id,
|
|
21461
21564
|
streamName: row.stream_name,
|
|
@@ -21486,7 +21589,9 @@ var Granular = class _Granular {
|
|
|
21486
21589
|
since: cursor,
|
|
21487
21590
|
limit: 100
|
|
21488
21591
|
});
|
|
21489
|
-
const orderedEvents = [...events].sort(
|
|
21592
|
+
const orderedEvents = [...events].sort(
|
|
21593
|
+
(a, b) => a.createdAt - b.createdAt
|
|
21594
|
+
);
|
|
21490
21595
|
for (const event of orderedEvents) {
|
|
21491
21596
|
if (seenEventIds.has(event.eventId)) {
|
|
21492
21597
|
continue;
|
|
@@ -21499,15 +21604,19 @@ var Granular = class _Granular {
|
|
|
21499
21604
|
params.onEvent(event);
|
|
21500
21605
|
}
|
|
21501
21606
|
} catch (err) {
|
|
21502
|
-
params.onError?.(
|
|
21607
|
+
params.onError?.(
|
|
21608
|
+
err instanceof Error ? err : new Error(String(err))
|
|
21609
|
+
);
|
|
21503
21610
|
}
|
|
21504
21611
|
await new Promise((resolve2) => setTimeout(resolve2, interval));
|
|
21505
21612
|
}
|
|
21506
21613
|
};
|
|
21507
21614
|
poll();
|
|
21508
|
-
return {
|
|
21509
|
-
|
|
21510
|
-
|
|
21615
|
+
return {
|
|
21616
|
+
unsubscribe: () => {
|
|
21617
|
+
running = false;
|
|
21618
|
+
}
|
|
21619
|
+
};
|
|
21511
21620
|
},
|
|
21512
21621
|
ack: async (eventId) => {
|
|
21513
21622
|
await this.request("/control/stream-events/ack", {
|
|
@@ -21525,7 +21634,9 @@ var Granular = class _Granular {
|
|
|
21525
21634
|
const sandbox = await this._resolveSandboxId(params.ontology);
|
|
21526
21635
|
const query = new URLSearchParams({ sandboxId: sandbox });
|
|
21527
21636
|
if (params.environment) query.set("environmentId", params.environment);
|
|
21528
|
-
const result = await this.request(
|
|
21637
|
+
const result = await this.request(
|
|
21638
|
+
`/control/stream-events/stats?${query.toString()}`
|
|
21639
|
+
);
|
|
21529
21640
|
return (result.items || []).map((row) => ({
|
|
21530
21641
|
streamName: row.stream_name,
|
|
21531
21642
|
eventType: row.event_type,
|
|
@@ -21543,10 +21654,14 @@ var Granular = class _Granular {
|
|
|
21543
21654
|
get subjects() {
|
|
21544
21655
|
return {
|
|
21545
21656
|
get: async (subjectId) => {
|
|
21546
|
-
return normalizeSubject(
|
|
21657
|
+
return normalizeSubject(
|
|
21658
|
+
await this.request(`/control/subjects/${subjectId}`)
|
|
21659
|
+
);
|
|
21547
21660
|
},
|
|
21548
21661
|
listAssignments: async (subjectId) => {
|
|
21549
|
-
return this.request(
|
|
21662
|
+
return this.request(
|
|
21663
|
+
`/control/subjects/${subjectId}/assignments`
|
|
21664
|
+
);
|
|
21550
21665
|
}
|
|
21551
21666
|
};
|
|
21552
21667
|
}
|
|
@@ -21556,24 +21671,31 @@ var Granular = class _Granular {
|
|
|
21556
21671
|
get users() {
|
|
21557
21672
|
return {
|
|
21558
21673
|
create: async (data) => {
|
|
21559
|
-
return normalizeSubject(
|
|
21560
|
-
|
|
21561
|
-
|
|
21562
|
-
|
|
21563
|
-
|
|
21564
|
-
|
|
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
|
+
})
|
|
21565
21682
|
})
|
|
21566
|
-
|
|
21683
|
+
);
|
|
21567
21684
|
},
|
|
21568
21685
|
get: async (id) => {
|
|
21569
|
-
return normalizeSubject(
|
|
21686
|
+
return normalizeSubject(
|
|
21687
|
+
await this.request(`/control/subjects/${id}`)
|
|
21688
|
+
);
|
|
21570
21689
|
}
|
|
21571
21690
|
};
|
|
21572
21691
|
}
|
|
21573
21692
|
async _resolveSandboxId(ontologyNameOrId) {
|
|
21574
21693
|
if (ontologyNameOrId.startsWith("sbx_")) return ontologyNameOrId;
|
|
21575
|
-
const result = await this.request(
|
|
21576
|
-
|
|
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}`);
|
|
21577
21699
|
return result.items[0].sandboxId;
|
|
21578
21700
|
}
|
|
21579
21701
|
/**
|
|
@@ -21588,9 +21710,9 @@ var Granular = class _Granular {
|
|
|
21588
21710
|
const response = await fetch(url, {
|
|
21589
21711
|
...options,
|
|
21590
21712
|
headers: {
|
|
21591
|
-
|
|
21713
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
21592
21714
|
"Content-Type": "application/json",
|
|
21593
|
-
|
|
21715
|
+
Connection: "close",
|
|
21594
21716
|
...options.headers
|
|
21595
21717
|
}
|
|
21596
21718
|
});
|
|
@@ -21601,7 +21723,11 @@ var Granular = class _Granular {
|
|
|
21601
21723
|
return response.json();
|
|
21602
21724
|
}
|
|
21603
21725
|
const errorText = await response.text();
|
|
21604
|
-
const retryable = isRetryableLocalWorkerRestart(
|
|
21726
|
+
const retryable = isRetryableLocalWorkerRestart(
|
|
21727
|
+
response.status,
|
|
21728
|
+
errorText,
|
|
21729
|
+
url
|
|
21730
|
+
);
|
|
21605
21731
|
if (retryable && attempt < LOCAL_CONTROL_REQUEST_RETRY_COUNT) {
|
|
21606
21732
|
if (this.debugHttp) {
|
|
21607
21733
|
console.warn(
|