@granular-software/sdk 0.4.30 → 0.4.31
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 +25 -20
- package/dist/agent-evals.d.mts +7 -6
- package/dist/agent-evals.d.ts +7 -6
- package/dist/agent-evals.js +681 -277
- package/dist/agent-evals.js.map +1 -1
- package/dist/agent-evals.mjs +681 -277
- package/dist/agent-evals.mjs.map +1 -1
- package/dist/cli/index.js +526 -271
- package/dist/{client-BVvOMfln.d.mts → client-CTH1hfwe.d.mts} +254 -123
- package/dist/{client-BVvOMfln.d.ts → client-CTH1hfwe.d.ts} +254 -123
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +439 -187
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +438 -188
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4669,27 +4669,27 @@ var Session = class {
|
|
|
4669
4669
|
}
|
|
4670
4670
|
async publishTools(tools, revision = "1.0.0") {
|
|
4671
4671
|
throw new Error(
|
|
4672
|
-
"Environment-scoped effect publication was removed. Declare effects in the manifest and register live handlers with granular.
|
|
4672
|
+
"Environment-scoped effect publication was removed. Declare effects in the manifest and register live handlers with granular.ontology(environment.sandboxId).effects.registerMany(effects)."
|
|
4673
4673
|
);
|
|
4674
4674
|
}
|
|
4675
4675
|
async publishEffect(effect) {
|
|
4676
4676
|
throw new Error(
|
|
4677
|
-
"Environment-scoped effect publication was removed. Use granular.
|
|
4677
|
+
"Environment-scoped effect publication was removed. Use granular.ontology(environment.sandboxId).effects.register(effect)."
|
|
4678
4678
|
);
|
|
4679
4679
|
}
|
|
4680
4680
|
async publishEffects(effects) {
|
|
4681
4681
|
throw new Error(
|
|
4682
|
-
"Environment-scoped effect publication was removed. Use granular.
|
|
4682
|
+
"Environment-scoped effect publication was removed. Use granular.ontology(environment.sandboxId).effects.registerMany(effects)."
|
|
4683
4683
|
);
|
|
4684
4684
|
}
|
|
4685
4685
|
async unpublishEffect(name) {
|
|
4686
4686
|
throw new Error(
|
|
4687
|
-
"Environment-scoped effect publication was removed. Use granular.
|
|
4687
|
+
"Environment-scoped effect publication was removed. Use granular.ontology(environment.sandboxId).effects.unregister(effectName)."
|
|
4688
4688
|
);
|
|
4689
4689
|
}
|
|
4690
4690
|
async unpublishAllEffects() {
|
|
4691
4691
|
throw new Error(
|
|
4692
|
-
"Environment-scoped effect publication was removed. Use granular.
|
|
4692
|
+
"Environment-scoped effect publication was removed. Use granular.ontology(environment.sandboxId).effects.clear()."
|
|
4693
4693
|
);
|
|
4694
4694
|
}
|
|
4695
4695
|
/**
|
|
@@ -11713,6 +11713,22 @@ function normalizeHeapSnapshot(raw) {
|
|
|
11713
11713
|
updatedAt: typeof heap.updatedAt === "number" ? heap.updatedAt : Date.now()
|
|
11714
11714
|
};
|
|
11715
11715
|
}
|
|
11716
|
+
function deriveRuntimeBaseUrl(apiEndpoint) {
|
|
11717
|
+
try {
|
|
11718
|
+
const endpoint = new URL(apiEndpoint);
|
|
11719
|
+
const graphqlSuffix = "/orchestrator/graphql";
|
|
11720
|
+
if (endpoint.pathname.endsWith(graphqlSuffix)) {
|
|
11721
|
+
endpoint.pathname = endpoint.pathname.slice(0, -graphqlSuffix.length);
|
|
11722
|
+
} else if (endpoint.pathname.endsWith("/graphql")) {
|
|
11723
|
+
endpoint.pathname = endpoint.pathname.slice(0, -"/graphql".length);
|
|
11724
|
+
}
|
|
11725
|
+
endpoint.search = "";
|
|
11726
|
+
endpoint.hash = "";
|
|
11727
|
+
return endpoint.toString().replace(/\/$/, "");
|
|
11728
|
+
} catch {
|
|
11729
|
+
return apiEndpoint.replace(/\/orchestrator\/graphql$/, "").replace(/\/$/, "");
|
|
11730
|
+
}
|
|
11731
|
+
}
|
|
11716
11732
|
function normalizeSubject(subject) {
|
|
11717
11733
|
const granularId = subject.granularId || subject.subjectId;
|
|
11718
11734
|
const userId = subject.userId || subject.identityId || granularId;
|
|
@@ -11752,12 +11768,13 @@ function normalizeEnvironmentData(environment) {
|
|
|
11752
11768
|
tracking: environment.tracking || buildPolicy
|
|
11753
11769
|
};
|
|
11754
11770
|
}
|
|
11755
|
-
var Environment = class
|
|
11771
|
+
var Environment = class {
|
|
11772
|
+
granular;
|
|
11756
11773
|
envData;
|
|
11757
11774
|
_apiKey;
|
|
11758
11775
|
_apiEndpoint;
|
|
11759
|
-
constructor(
|
|
11760
|
-
|
|
11776
|
+
constructor(granular, envData, apiKey, apiEndpoint) {
|
|
11777
|
+
this.granular = granular;
|
|
11761
11778
|
this.envData = envData;
|
|
11762
11779
|
this._apiKey = apiKey;
|
|
11763
11780
|
this._apiEndpoint = apiEndpoint;
|
|
@@ -11798,35 +11815,126 @@ var Environment = class extends Session {
|
|
|
11798
11815
|
get permissionProfileId() {
|
|
11799
11816
|
return this.envData.permissionProfileId;
|
|
11800
11817
|
}
|
|
11818
|
+
/** The current build policy backing this environment */
|
|
11819
|
+
get buildPolicy() {
|
|
11820
|
+
return this.envData.buildPolicy;
|
|
11821
|
+
}
|
|
11822
|
+
/** The current update state relative to the followed tag */
|
|
11823
|
+
get updateState() {
|
|
11824
|
+
return this.envData.updateState;
|
|
11825
|
+
}
|
|
11826
|
+
/** Convenience flag for whether this environment trails the current tag target */
|
|
11827
|
+
get isOutdated() {
|
|
11828
|
+
return this.envData.updateState === "update_available";
|
|
11829
|
+
}
|
|
11830
|
+
/** The followed tag name when this environment is tag-tracked */
|
|
11831
|
+
get tag() {
|
|
11832
|
+
return this.envData.tag?.name || this.envData.buildPolicy.tagName || null;
|
|
11833
|
+
}
|
|
11801
11834
|
/** The GraphQL API endpoint URL */
|
|
11802
11835
|
get apiEndpoint() {
|
|
11803
11836
|
return this._apiEndpoint;
|
|
11804
11837
|
}
|
|
11838
|
+
/** Internal auth token used for control-plane and runtime fallback requests */
|
|
11839
|
+
get authToken() {
|
|
11840
|
+
return this._apiKey;
|
|
11841
|
+
}
|
|
11842
|
+
/** Base runtime URL derived from the GraphQL endpoint */
|
|
11843
|
+
get runtimeBaseUrl() {
|
|
11844
|
+
return this.getRuntimeBaseUrl();
|
|
11845
|
+
}
|
|
11846
|
+
get sessions() {
|
|
11847
|
+
return {
|
|
11848
|
+
list: async (options) => this.listSessions(options?.status || "active"),
|
|
11849
|
+
create: async (options) => this.createSession(options),
|
|
11850
|
+
connect: async (sessionId, options) => this.connectSession(sessionId, options),
|
|
11851
|
+
reopen: async (sessionId, options) => this.reopenSession(sessionId, options),
|
|
11852
|
+
close: async (sessionId, session) => this.closeSession(sessionId, session)
|
|
11853
|
+
};
|
|
11854
|
+
}
|
|
11855
|
+
get data() {
|
|
11856
|
+
return {
|
|
11857
|
+
record: async (record) => this.recordObject(record),
|
|
11858
|
+
recordMany: async (records, options) => this.recordObjects(records, options),
|
|
11859
|
+
import: async (records, options) => this.enqueueRecordImport(records, options),
|
|
11860
|
+
listImports: async (status) => this.listRecordImports(status),
|
|
11861
|
+
getImport: async (importId) => this.getRecordImport(importId),
|
|
11862
|
+
getImportSummary: async () => this.getRecordImportSummary(),
|
|
11863
|
+
cancelImport: async (importId) => this.cancelRecordImport(importId),
|
|
11864
|
+
getAwaitingCount: async () => this.getAwaitingRecordCount()
|
|
11865
|
+
};
|
|
11866
|
+
}
|
|
11867
|
+
get feedback() {
|
|
11868
|
+
return {
|
|
11869
|
+
list: async () => this.listFeedback()
|
|
11870
|
+
};
|
|
11871
|
+
}
|
|
11805
11872
|
/**
|
|
11806
|
-
*
|
|
11807
|
-
*
|
|
11808
|
-
*
|
|
11809
|
-
*
|
|
11873
|
+
* Sessionless environments do not own a live transport, so disconnecting the
|
|
11874
|
+
* environment handle itself is a no-op. This keeps the public surface
|
|
11875
|
+
* symmetric with `EnvironmentSession.disconnect()` and lets callers always
|
|
11876
|
+
* clean up safely without tracking whether they currently hold an environment
|
|
11877
|
+
* or a session.
|
|
11810
11878
|
*/
|
|
11811
|
-
|
|
11812
|
-
const doc = this.document;
|
|
11813
|
-
return normalizeHeapSnapshot(doc?.heap);
|
|
11879
|
+
async disconnect() {
|
|
11814
11880
|
}
|
|
11815
|
-
|
|
11816
|
-
|
|
11817
|
-
const
|
|
11818
|
-
|
|
11819
|
-
|
|
11820
|
-
|
|
11821
|
-
|
|
11822
|
-
|
|
11823
|
-
|
|
11824
|
-
endpoint.search = "";
|
|
11825
|
-
endpoint.hash = "";
|
|
11826
|
-
return endpoint.toString().replace(/\/$/, "");
|
|
11827
|
-
} catch {
|
|
11828
|
-
return this._apiEndpoint.replace(/\/orchestrator\/graphql$/, "").replace(/\/$/, "");
|
|
11881
|
+
async listSessions(status = "active") {
|
|
11882
|
+
if (status === "all") {
|
|
11883
|
+
const [active, closed] = await Promise.all([
|
|
11884
|
+
this.granular.listOpenSessions({ environmentId: this.environmentId }),
|
|
11885
|
+
this.granular.listClosedSessions({ environmentId: this.environmentId })
|
|
11886
|
+
]);
|
|
11887
|
+
return [...active, ...closed].sort(
|
|
11888
|
+
(left, right) => Date.parse(right.lastSeenAt) - Date.parse(left.lastSeenAt)
|
|
11889
|
+
);
|
|
11829
11890
|
}
|
|
11891
|
+
return status === "closed" ? this.granular.listClosedSessions({ environmentId: this.environmentId }) : this.granular.listOpenSessions({ environmentId: this.environmentId });
|
|
11892
|
+
}
|
|
11893
|
+
async createSession(options) {
|
|
11894
|
+
return this.granular.createSession({
|
|
11895
|
+
environmentId: this.environmentId,
|
|
11896
|
+
clientId: options?.clientId,
|
|
11897
|
+
initialHeap: options?.initialHeap
|
|
11898
|
+
});
|
|
11899
|
+
}
|
|
11900
|
+
async connectSession(sessionId, options) {
|
|
11901
|
+
const session = await this.granular["connectSession"]({
|
|
11902
|
+
sessionId,
|
|
11903
|
+
clientId: options?.clientId
|
|
11904
|
+
});
|
|
11905
|
+
if (session.environmentId !== this.environmentId) {
|
|
11906
|
+
await session.disconnect().catch(() => {
|
|
11907
|
+
session.disconnectTransport();
|
|
11908
|
+
});
|
|
11909
|
+
throw new Error(
|
|
11910
|
+
`Session ${sessionId} belongs to environment ${session.environmentId}, not ${this.environmentId}.`
|
|
11911
|
+
);
|
|
11912
|
+
}
|
|
11913
|
+
return session;
|
|
11914
|
+
}
|
|
11915
|
+
async reopenSession(sessionId, options) {
|
|
11916
|
+
const session = await this.granular.reopenSession(sessionId, {
|
|
11917
|
+
clientId: options?.clientId
|
|
11918
|
+
});
|
|
11919
|
+
if (session.environmentId !== this.environmentId) {
|
|
11920
|
+
await session.disconnect().catch(() => {
|
|
11921
|
+
session.disconnectTransport();
|
|
11922
|
+
});
|
|
11923
|
+
throw new Error(
|
|
11924
|
+
`Session ${sessionId} belongs to environment ${session.environmentId}, not ${this.environmentId}.`
|
|
11925
|
+
);
|
|
11926
|
+
}
|
|
11927
|
+
return session;
|
|
11928
|
+
}
|
|
11929
|
+
async closeSession(sessionId, session) {
|
|
11930
|
+
await this.granular.closeSession(sessionId, session);
|
|
11931
|
+
}
|
|
11932
|
+
async listFeedback() {
|
|
11933
|
+
const response = await this.controlPlaneRequest(`/control/environments/${this.environmentId}/feedback`);
|
|
11934
|
+
return Array.isArray(response.items) ? response.items : [];
|
|
11935
|
+
}
|
|
11936
|
+
getRuntimeBaseUrl() {
|
|
11937
|
+
return deriveRuntimeBaseUrl(this._apiEndpoint);
|
|
11830
11938
|
}
|
|
11831
11939
|
async controlPlaneRequest(path, options = {}) {
|
|
11832
11940
|
const runtimeBase = this.getRuntimeBaseUrl();
|
|
@@ -11847,95 +11955,6 @@ var Environment = class extends Session {
|
|
|
11847
11955
|
}
|
|
11848
11956
|
return response.json();
|
|
11849
11957
|
}
|
|
11850
|
-
/**
|
|
11851
|
-
* Close the session and disconnect from the sandbox.
|
|
11852
|
-
*
|
|
11853
|
-
* Sends `client.goodbye` over WebSocket first, then issues an HTTP fallback
|
|
11854
|
-
* to the runtime goodbye endpoint if no definitive WS-side runtime notify
|
|
11855
|
-
* acknowledgement was observed.
|
|
11856
|
-
*/
|
|
11857
|
-
async disconnect() {
|
|
11858
|
-
let wsNotifiedRuntime = false;
|
|
11859
|
-
try {
|
|
11860
|
-
const goodbye = await this.rpc(
|
|
11861
|
-
"client.goodbye",
|
|
11862
|
-
{
|
|
11863
|
-
timestamp: Date.now()
|
|
11864
|
-
}
|
|
11865
|
-
);
|
|
11866
|
-
wsNotifiedRuntime = Boolean(goodbye?.ok && goodbye?.via);
|
|
11867
|
-
} catch {
|
|
11868
|
-
wsNotifiedRuntime = false;
|
|
11869
|
-
}
|
|
11870
|
-
if (!wsNotifiedRuntime) {
|
|
11871
|
-
try {
|
|
11872
|
-
const runtimeBase = this.getRuntimeBaseUrl();
|
|
11873
|
-
await fetch(
|
|
11874
|
-
`${runtimeBase}/orchestrator/runtime/environments/${this.environmentId}/session-goodbye`,
|
|
11875
|
-
{
|
|
11876
|
-
method: "POST",
|
|
11877
|
-
headers: {
|
|
11878
|
-
"Content-Type": "application/json",
|
|
11879
|
-
Authorization: `Bearer ${this._apiKey}`,
|
|
11880
|
-
Connection: "close"
|
|
11881
|
-
},
|
|
11882
|
-
body: JSON.stringify({
|
|
11883
|
-
reason: "sdk_disconnect_http_fallback",
|
|
11884
|
-
sessionId: this.client.currentSessionId
|
|
11885
|
-
})
|
|
11886
|
-
}
|
|
11887
|
-
);
|
|
11888
|
-
} catch {
|
|
11889
|
-
}
|
|
11890
|
-
}
|
|
11891
|
-
this.client.disconnect();
|
|
11892
|
-
}
|
|
11893
|
-
/**
|
|
11894
|
-
* Close only the socket transport without sending `client.goodbye`.
|
|
11895
|
-
*
|
|
11896
|
-
* Use this when the caller intends to immediately reattach to the same
|
|
11897
|
-
* session after an unexpected disconnect.
|
|
11898
|
-
*/
|
|
11899
|
-
disconnectTransport() {
|
|
11900
|
-
this.client.disconnect();
|
|
11901
|
-
}
|
|
11902
|
-
// ==================== GRAPH CONTAINER READINESS ====================
|
|
11903
|
-
/** The last known graph container status, updated by checkReadiness() or on heartbeat */
|
|
11904
|
-
graphContainerStatus = null;
|
|
11905
|
-
/**
|
|
11906
|
-
* Check if the graph container is ready and warm.
|
|
11907
|
-
*
|
|
11908
|
-
* Sends a lightweight heartbeat RPC to the Session DO which internally
|
|
11909
|
-
* pings the FalkorDB container. The response includes `graphContainerStatus`,
|
|
11910
|
-
* which is stored locally and emitted as a `readiness` event.
|
|
11911
|
-
*
|
|
11912
|
-
* Use this method to proactively warm the graph container before any
|
|
11913
|
-
* GraphQL query that requires it, or to poll the container's state in
|
|
11914
|
-
* the background.
|
|
11915
|
-
*
|
|
11916
|
-
* @returns The current graph container status object
|
|
11917
|
-
*
|
|
11918
|
-
* @example
|
|
11919
|
-
* ```typescript
|
|
11920
|
-
* const status = await env.checkReadiness();
|
|
11921
|
-
* console.log(status.status); // 'hot' | 'warming' | 'unknown'
|
|
11922
|
-
*
|
|
11923
|
-
* // Or listen for live updates
|
|
11924
|
-
* env.on('readiness', (status) => {
|
|
11925
|
-
* console.log('Graph is now:', status.status);
|
|
11926
|
-
* });
|
|
11927
|
-
* ```
|
|
11928
|
-
*/
|
|
11929
|
-
async checkReadiness() {
|
|
11930
|
-
const result = await this.client.call("client.heartbeat", {});
|
|
11931
|
-
const containerStatus = result?.graphContainerStatus ?? {
|
|
11932
|
-
lastKeepAliveAt: Date.now(),
|
|
11933
|
-
status: "unknown"
|
|
11934
|
-
};
|
|
11935
|
-
this.graphContainerStatus = containerStatus;
|
|
11936
|
-
this.emit("readiness", containerStatus);
|
|
11937
|
-
return containerStatus;
|
|
11938
|
-
}
|
|
11939
11958
|
// ==================== ID ↔ GRAPH PATH MAPPING ====================
|
|
11940
11959
|
/**
|
|
11941
11960
|
* Convert a class name + real-world ID into a unique graph path.
|
|
@@ -12796,36 +12815,186 @@ var Environment = class extends Session {
|
|
|
12796
12815
|
}
|
|
12797
12816
|
);
|
|
12798
12817
|
}
|
|
12799
|
-
|
|
12818
|
+
};
|
|
12819
|
+
var EnvironmentSession = class extends Session {
|
|
12820
|
+
environment;
|
|
12821
|
+
/** The last known graph container status, updated by checkReadiness() or on heartbeat */
|
|
12822
|
+
graphContainerStatus = null;
|
|
12823
|
+
constructor(client, environment, clientId) {
|
|
12824
|
+
super(client, clientId);
|
|
12825
|
+
this.environment = environment;
|
|
12826
|
+
}
|
|
12827
|
+
get environmentId() {
|
|
12828
|
+
return this.environment.environmentId;
|
|
12829
|
+
}
|
|
12830
|
+
get sandboxId() {
|
|
12831
|
+
return this.environment.sandboxId;
|
|
12832
|
+
}
|
|
12833
|
+
get ontologyId() {
|
|
12834
|
+
return this.environment.ontologyId;
|
|
12835
|
+
}
|
|
12836
|
+
get subjectId() {
|
|
12837
|
+
return this.environment.subjectId;
|
|
12838
|
+
}
|
|
12839
|
+
get envName() {
|
|
12840
|
+
return this.environment.envName;
|
|
12841
|
+
}
|
|
12842
|
+
get versionId() {
|
|
12843
|
+
return this.environment.versionId;
|
|
12844
|
+
}
|
|
12845
|
+
get granularId() {
|
|
12846
|
+
return this.environment.granularId;
|
|
12847
|
+
}
|
|
12848
|
+
get permissionProfileId() {
|
|
12849
|
+
return this.environment.permissionProfileId;
|
|
12850
|
+
}
|
|
12851
|
+
get apiEndpoint() {
|
|
12852
|
+
return this.environment.apiEndpoint;
|
|
12853
|
+
}
|
|
12854
|
+
get data() {
|
|
12855
|
+
return this.environment.data;
|
|
12856
|
+
}
|
|
12857
|
+
get feedback() {
|
|
12858
|
+
return this.environment.feedback;
|
|
12859
|
+
}
|
|
12800
12860
|
/**
|
|
12801
|
-
*
|
|
12861
|
+
* Return a plain JS snapshot of the synced session heap.
|
|
12802
12862
|
*/
|
|
12803
|
-
|
|
12804
|
-
|
|
12863
|
+
getHeap() {
|
|
12864
|
+
const doc = this.document;
|
|
12865
|
+
return normalizeHeapSnapshot(doc?.heap);
|
|
12866
|
+
}
|
|
12867
|
+
async graphql(query, variables) {
|
|
12868
|
+
return this.environment.graphql(query, variables);
|
|
12869
|
+
}
|
|
12870
|
+
async defineRelationship(options) {
|
|
12871
|
+
return this.environment.defineRelationship(options);
|
|
12872
|
+
}
|
|
12873
|
+
async getRelationships(modelPath) {
|
|
12874
|
+
return this.environment.getRelationships(modelPath);
|
|
12875
|
+
}
|
|
12876
|
+
async attach(modelPath, submodelPath, targetPath) {
|
|
12877
|
+
return this.environment.attach(modelPath, submodelPath, targetPath);
|
|
12878
|
+
}
|
|
12879
|
+
async detach(modelPath, submodelPath, targetPath) {
|
|
12880
|
+
return this.environment.detach(modelPath, submodelPath, targetPath);
|
|
12881
|
+
}
|
|
12882
|
+
async listRelated(modelPath, submodelPath) {
|
|
12883
|
+
return this.environment.listRelated(modelPath, submodelPath);
|
|
12884
|
+
}
|
|
12885
|
+
async applyManifest(manifest) {
|
|
12886
|
+
return this.environment.applyManifest(manifest);
|
|
12887
|
+
}
|
|
12888
|
+
async recordObject(options) {
|
|
12889
|
+
return this.environment.recordObject(options);
|
|
12890
|
+
}
|
|
12891
|
+
async recordObjects(records, options) {
|
|
12892
|
+
return this.environment.recordObjects(records, options);
|
|
12893
|
+
}
|
|
12894
|
+
async enqueueRecordImport(records, options = {}) {
|
|
12895
|
+
return this.environment.enqueueRecordImport(records, options);
|
|
12896
|
+
}
|
|
12897
|
+
async listRecordImports(status) {
|
|
12898
|
+
return this.environment.listRecordImports(status);
|
|
12899
|
+
}
|
|
12900
|
+
async getRecordImportSummary() {
|
|
12901
|
+
return this.environment.getRecordImportSummary();
|
|
12902
|
+
}
|
|
12903
|
+
async getAwaitingRecordCount() {
|
|
12904
|
+
return this.environment.getAwaitingRecordCount();
|
|
12905
|
+
}
|
|
12906
|
+
async getRecordImport(importId) {
|
|
12907
|
+
return this.environment.getRecordImport(importId);
|
|
12908
|
+
}
|
|
12909
|
+
async cancelRecordImport(importId) {
|
|
12910
|
+
return this.environment.cancelRecordImport(importId);
|
|
12911
|
+
}
|
|
12912
|
+
async listFeedback() {
|
|
12913
|
+
return this.environment.listFeedback();
|
|
12805
12914
|
}
|
|
12806
12915
|
/**
|
|
12807
|
-
*
|
|
12916
|
+
* Close the session and disconnect from the sandbox.
|
|
12917
|
+
*
|
|
12918
|
+
* Sends `client.goodbye` over WebSocket first, then issues an HTTP fallback
|
|
12919
|
+
* to the runtime goodbye endpoint if no definitive WS-side runtime notify
|
|
12920
|
+
* acknowledgement was observed.
|
|
12808
12921
|
*/
|
|
12809
|
-
async
|
|
12810
|
-
|
|
12922
|
+
async disconnect() {
|
|
12923
|
+
let wsNotifiedRuntime = false;
|
|
12924
|
+
try {
|
|
12925
|
+
const goodbye = await this.rpc(
|
|
12926
|
+
"client.goodbye",
|
|
12927
|
+
{
|
|
12928
|
+
timestamp: Date.now()
|
|
12929
|
+
}
|
|
12930
|
+
);
|
|
12931
|
+
wsNotifiedRuntime = Boolean(goodbye?.ok && goodbye?.via);
|
|
12932
|
+
} catch {
|
|
12933
|
+
wsNotifiedRuntime = false;
|
|
12934
|
+
}
|
|
12935
|
+
if (!wsNotifiedRuntime) {
|
|
12936
|
+
try {
|
|
12937
|
+
await fetch(
|
|
12938
|
+
`${this.environment.runtimeBaseUrl}/orchestrator/runtime/environments/${this.environmentId}/session-goodbye`,
|
|
12939
|
+
{
|
|
12940
|
+
method: "POST",
|
|
12941
|
+
headers: {
|
|
12942
|
+
"Content-Type": "application/json",
|
|
12943
|
+
Authorization: `Bearer ${this.environment.authToken}`,
|
|
12944
|
+
Connection: "close"
|
|
12945
|
+
},
|
|
12946
|
+
body: JSON.stringify({
|
|
12947
|
+
reason: "sdk_disconnect_http_fallback",
|
|
12948
|
+
sessionId: this.client.currentSessionId
|
|
12949
|
+
})
|
|
12950
|
+
}
|
|
12951
|
+
);
|
|
12952
|
+
} catch {
|
|
12953
|
+
}
|
|
12954
|
+
}
|
|
12955
|
+
this.client.disconnect();
|
|
12811
12956
|
}
|
|
12812
12957
|
/**
|
|
12813
|
-
*
|
|
12958
|
+
* Close only the socket transport without sending `client.goodbye`.
|
|
12814
12959
|
*/
|
|
12815
|
-
|
|
12816
|
-
|
|
12960
|
+
disconnectTransport() {
|
|
12961
|
+
this.client.disconnect();
|
|
12817
12962
|
}
|
|
12818
12963
|
/**
|
|
12819
|
-
*
|
|
12964
|
+
* Backwards-compatible alias for `disconnect()`.
|
|
12820
12965
|
*/
|
|
12821
|
-
async
|
|
12822
|
-
|
|
12966
|
+
async close() {
|
|
12967
|
+
await this.disconnect();
|
|
12823
12968
|
}
|
|
12824
12969
|
/**
|
|
12825
|
-
*
|
|
12970
|
+
* Check if the graph container is ready and warm.
|
|
12826
12971
|
*/
|
|
12827
|
-
async
|
|
12828
|
-
|
|
12972
|
+
async checkReadiness() {
|
|
12973
|
+
const result = await this.client.call("client.heartbeat", {});
|
|
12974
|
+
const containerStatus = result?.graphContainerStatus ?? {
|
|
12975
|
+
lastKeepAliveAt: Date.now(),
|
|
12976
|
+
status: "unknown"
|
|
12977
|
+
};
|
|
12978
|
+
this.graphContainerStatus = containerStatus;
|
|
12979
|
+
this.emit("readiness", containerStatus);
|
|
12980
|
+
return containerStatus;
|
|
12981
|
+
}
|
|
12982
|
+
};
|
|
12983
|
+
var OntologyHandle = class {
|
|
12984
|
+
granular;
|
|
12985
|
+
ontologyNameOrId;
|
|
12986
|
+
constructor(granular, ontologyNameOrId) {
|
|
12987
|
+
this.granular = granular;
|
|
12988
|
+
this.ontologyNameOrId = ontologyNameOrId;
|
|
12989
|
+
}
|
|
12990
|
+
get effects() {
|
|
12991
|
+
return {
|
|
12992
|
+
register: async (effect) => this.granular.registerEffect(this.ontologyNameOrId, effect),
|
|
12993
|
+
registerMany: async (effects) => this.granular.registerEffects(this.ontologyNameOrId, effects),
|
|
12994
|
+
unregister: async (name) => this.granular.unregisterEffect(this.ontologyNameOrId, name),
|
|
12995
|
+
clear: async () => this.granular.unregisterAllEffects(this.ontologyNameOrId),
|
|
12996
|
+
disconnect: async () => this.granular.disconnectEffects(this.ontologyNameOrId)
|
|
12997
|
+
};
|
|
12829
12998
|
}
|
|
12830
12999
|
};
|
|
12831
13000
|
var Granular = class _Granular {
|
|
@@ -12862,6 +13031,12 @@ var Granular = class _Granular {
|
|
|
12862
13031
|
this.onReconnectError = options.onReconnectError;
|
|
12863
13032
|
this.httpUrl = this.apiUrl.replace(/^wss:\/\//, "https://").replace(/^ws:\/\//, "http://").replace(/\/ws$/, "");
|
|
12864
13033
|
}
|
|
13034
|
+
/**
|
|
13035
|
+
* Return an ontology-scoped handle for effects and other ontology-level APIs.
|
|
13036
|
+
*/
|
|
13037
|
+
ontology(ontologyNameOrId) {
|
|
13038
|
+
return new OntologyHandle(this, ontologyNameOrId);
|
|
13039
|
+
}
|
|
12865
13040
|
/**
|
|
12866
13041
|
* Records/upserts a user and prepares them for sandbox connections
|
|
12867
13042
|
*
|
|
@@ -12898,7 +13073,23 @@ var Granular = class _Granular {
|
|
|
12898
13073
|
permissions: options.permissions || []
|
|
12899
13074
|
});
|
|
12900
13075
|
}
|
|
13076
|
+
/**
|
|
13077
|
+
* Alias for `recordUser()` with user-facing naming that matches upsert semantics.
|
|
13078
|
+
*/
|
|
13079
|
+
async upsertUser(options) {
|
|
13080
|
+
return this.recordUser(options);
|
|
13081
|
+
}
|
|
12901
13082
|
async resolveConnectUser(options) {
|
|
13083
|
+
const providedIdentityCount = [
|
|
13084
|
+
Boolean(options.user),
|
|
13085
|
+
Boolean(options.userId),
|
|
13086
|
+
Boolean(options.granularId)
|
|
13087
|
+
].filter(Boolean).length;
|
|
13088
|
+
if (providedIdentityCount !== 1) {
|
|
13089
|
+
throw new Error(
|
|
13090
|
+
"openEnvironment() requires exactly one of userId, granularId, or a user object returned by recordUser()."
|
|
13091
|
+
);
|
|
13092
|
+
}
|
|
12902
13093
|
if (options.user) {
|
|
12903
13094
|
const user = normalizeUser(options.user);
|
|
12904
13095
|
return {
|
|
@@ -12935,56 +13126,85 @@ var Granular = class _Granular {
|
|
|
12935
13126
|
};
|
|
12936
13127
|
}
|
|
12937
13128
|
throw new Error(
|
|
12938
|
-
"
|
|
13129
|
+
"openEnvironment() requires either userId, granularId, or a user object returned by recordUser()."
|
|
12939
13130
|
);
|
|
12940
13131
|
}
|
|
12941
13132
|
/**
|
|
12942
|
-
*
|
|
12943
|
-
*
|
|
12944
|
-
* Effects are registered at the sandbox level via `granular.registerEffect()`
|
|
12945
|
-
* or `granular.registerEffects()`. Sessions pick up live availability from
|
|
12946
|
-
* the sandbox registry automatically.
|
|
12947
|
-
*
|
|
12948
|
-
* @param options - Connection options
|
|
12949
|
-
* @returns An active environment session
|
|
13133
|
+
* Open or resolve an ontology environment for one user without opening a session.
|
|
12950
13134
|
*
|
|
12951
13135
|
* @example
|
|
12952
13136
|
* ```typescript
|
|
12953
|
-
* const environment = await granular.
|
|
13137
|
+
* const environment = await granular.openEnvironment({
|
|
12954
13138
|
* ontology: 'my-ontology',
|
|
12955
|
-
*
|
|
13139
|
+
* tag: 'dev',
|
|
12956
13140
|
* userId: 'user_123',
|
|
12957
13141
|
* permissions: ['agent'],
|
|
12958
13142
|
* });
|
|
12959
13143
|
*
|
|
12960
|
-
* await
|
|
12961
|
-
*
|
|
12962
|
-
*
|
|
12963
|
-
*
|
|
12964
|
-
* handler: async () => 'Hello!',
|
|
13144
|
+
* await environment.data.record({
|
|
13145
|
+
* className: 'customer',
|
|
13146
|
+
* id: 'acme',
|
|
13147
|
+
* fields: { name: 'Acme' },
|
|
12965
13148
|
* });
|
|
12966
13149
|
*
|
|
12967
|
-
*
|
|
12968
|
-
* const job = await
|
|
12969
|
-
*
|
|
12970
|
-
* return await tools.greet({});
|
|
12971
|
-
* `);
|
|
12972
|
-
*
|
|
12973
|
-
* console.log(await job.result); // 'Hello!'
|
|
13150
|
+
* const session = await environment.sessions.create();
|
|
13151
|
+
* const job = await session.submitJob(`return "hello";`);
|
|
13152
|
+
* console.log(await job.result);
|
|
12974
13153
|
* ```
|
|
12975
13154
|
*/
|
|
13155
|
+
async openEnvironment(options) {
|
|
13156
|
+
const envData = await this.resolveOpenEnvironmentData(
|
|
13157
|
+
options,
|
|
13158
|
+
"openEnvironment"
|
|
13159
|
+
);
|
|
13160
|
+
return this.bindEnvironmentHandle(envData);
|
|
13161
|
+
}
|
|
13162
|
+
/**
|
|
13163
|
+
* Deprecated compatibility alias for `openEnvironment()`.
|
|
13164
|
+
*
|
|
13165
|
+
* `connect()` no longer opens a runtime session automatically.
|
|
13166
|
+
*/
|
|
12976
13167
|
async connect(options) {
|
|
12977
|
-
|
|
13168
|
+
return this.openEnvironment({
|
|
13169
|
+
...options,
|
|
13170
|
+
tag: this.resolveRequestedTag(options, "connect"),
|
|
13171
|
+
permissions: options.permissions || options.user?.permissions || []
|
|
13172
|
+
});
|
|
13173
|
+
}
|
|
13174
|
+
resolveRequestedTag(options, methodName) {
|
|
13175
|
+
const tag = options.tag?.trim() || options.tagName?.trim() || options.environment?.trim();
|
|
13176
|
+
if (!tag) {
|
|
13177
|
+
throw new Error(`${methodName}() requires \`tag\`.`);
|
|
13178
|
+
}
|
|
13179
|
+
return tag;
|
|
13180
|
+
}
|
|
13181
|
+
buildManagedEnvironmentName(tag, versionId) {
|
|
13182
|
+
return `__sdk__${tag}__${versionId}`;
|
|
13183
|
+
}
|
|
13184
|
+
matchesTagTrackedEnvironment(environment, tagName, tagId) {
|
|
13185
|
+
const environmentTagName = environment.tag?.name || environment.buildPolicy.tagName || null;
|
|
13186
|
+
return environment.tagId === tagId || environmentTagName === tagName || environment.environment === tagName || environment.envName === tagName || environment.environment === this.buildManagedEnvironmentName(tagName, environment.versionId) || environment.envName === this.buildManagedEnvironmentName(tagName, environment.versionId);
|
|
13187
|
+
}
|
|
13188
|
+
sortEnvironmentsByRecency(environments) {
|
|
13189
|
+
return [...environments].sort(
|
|
13190
|
+
(left, right) => right.updatedAt - left.updatedAt
|
|
13191
|
+
);
|
|
13192
|
+
}
|
|
13193
|
+
async resolveOpenEnvironmentData(options, methodName) {
|
|
12978
13194
|
const ontology = options.ontology;
|
|
12979
13195
|
if (!ontology) {
|
|
12980
|
-
throw new Error(
|
|
13196
|
+
throw new Error(`${methodName}() requires \`ontology\`.`);
|
|
12981
13197
|
}
|
|
12982
|
-
const
|
|
12983
|
-
if (!
|
|
12984
|
-
throw new Error(
|
|
13198
|
+
const tagName = options.tag?.trim();
|
|
13199
|
+
if (!tagName) {
|
|
13200
|
+
throw new Error(`${methodName}() requires \`tag\`.`);
|
|
12985
13201
|
}
|
|
12986
|
-
const tagName = options.tagName?.trim() || void 0;
|
|
12987
13202
|
const user = await this.resolveConnectUser(options);
|
|
13203
|
+
if (!Array.isArray(user.permissions) || user.permissions.length === 0) {
|
|
13204
|
+
throw new Error(
|
|
13205
|
+
`${methodName}() requires at least one permission so the SDK can ensure assignments for new users.`
|
|
13206
|
+
);
|
|
13207
|
+
}
|
|
12988
13208
|
const sandbox = await this.findOrCreateSandbox(ontology);
|
|
12989
13209
|
for (const profileName of user.permissions) {
|
|
12990
13210
|
const profileId = await this.ensurePermissionProfile(
|
|
@@ -12997,22 +13217,49 @@ var Granular = class _Granular {
|
|
|
12997
13217
|
profileId
|
|
12998
13218
|
);
|
|
12999
13219
|
}
|
|
13000
|
-
const
|
|
13220
|
+
const tags = await this.request(
|
|
13221
|
+
`/control/sandboxes/${sandbox.sandboxId}/tags`
|
|
13222
|
+
);
|
|
13223
|
+
const tag = (tags.items || []).find(
|
|
13224
|
+
(candidate) => Boolean(candidate?.name === tagName)
|
|
13225
|
+
);
|
|
13226
|
+
if (!tag) {
|
|
13227
|
+
throw new Error(
|
|
13228
|
+
`Tag "${tagName}" was not found for ontology ${sandbox.sandboxId}.`
|
|
13229
|
+
);
|
|
13230
|
+
}
|
|
13231
|
+
const targetVersionId = tag.targetVersionId || tag.targetBuildId;
|
|
13232
|
+
if (!targetVersionId) {
|
|
13233
|
+
throw new Error(
|
|
13234
|
+
`Tag "${tagName}" does not currently point to a build/version.`
|
|
13235
|
+
);
|
|
13236
|
+
}
|
|
13237
|
+
const allEnvironments = await this.environments.list(sandbox.sandboxId);
|
|
13238
|
+
const userEnvironments = allEnvironments.filter(
|
|
13239
|
+
(environment) => environment.subjectId === user.granularId
|
|
13240
|
+
);
|
|
13241
|
+
const currentMatches = this.sortEnvironmentsByRecency(
|
|
13242
|
+
userEnvironments.filter(
|
|
13243
|
+
(environment) => this.matchesTagTrackedEnvironment(environment, tagName, tag.tagId) && environment.versionId === targetVersionId
|
|
13244
|
+
)
|
|
13245
|
+
);
|
|
13246
|
+
if (currentMatches.length > 0) {
|
|
13247
|
+
return currentMatches[0];
|
|
13248
|
+
}
|
|
13249
|
+
const outdatedMatches = this.sortEnvironmentsByRecency(
|
|
13250
|
+
userEnvironments.filter(
|
|
13251
|
+
(environment) => this.matchesTagTrackedEnvironment(environment, tagName, tag.tagId)
|
|
13252
|
+
)
|
|
13253
|
+
);
|
|
13254
|
+
if (outdatedMatches.length > 0 && options.createFreshIfOutdated !== true) {
|
|
13255
|
+
return outdatedMatches[0];
|
|
13256
|
+
}
|
|
13257
|
+
return this.environments.create(sandbox.sandboxId, {
|
|
13001
13258
|
subjectId: user.granularId,
|
|
13002
|
-
environment:
|
|
13003
|
-
|
|
13259
|
+
environment: this.buildManagedEnvironmentName(tagName, targetVersionId),
|
|
13260
|
+
tagId: tag.tagId,
|
|
13004
13261
|
permissionProfileId: null
|
|
13005
13262
|
});
|
|
13006
|
-
await this.activateEnvironment(envData.environmentId);
|
|
13007
|
-
const session = await this.request("/ws/sessions", {
|
|
13008
|
-
method: "POST",
|
|
13009
|
-
body: JSON.stringify({
|
|
13010
|
-
environmentId: envData.environmentId,
|
|
13011
|
-
clientId,
|
|
13012
|
-
initialHeap: options.initialHeap
|
|
13013
|
-
})
|
|
13014
|
-
});
|
|
13015
|
-
return this.bindWebSocketEnvironment(envData, clientId, session);
|
|
13016
13263
|
}
|
|
13017
13264
|
/**
|
|
13018
13265
|
* List active (open) sessions for an environment — each session is one agent conversation thread.
|
|
@@ -13075,6 +13322,7 @@ var Granular = class _Granular {
|
|
|
13075
13322
|
const clientId = options.clientId || `client_${Date.now()}`;
|
|
13076
13323
|
await this.activateEnvironment(options.environmentId);
|
|
13077
13324
|
const envData = await this.environments.get(options.environmentId);
|
|
13325
|
+
const environment = this.bindEnvironmentHandle(envData);
|
|
13078
13326
|
const session = await this.request("/ws/sessions", {
|
|
13079
13327
|
method: "POST",
|
|
13080
13328
|
body: JSON.stringify({
|
|
@@ -13083,7 +13331,7 @@ var Granular = class _Granular {
|
|
|
13083
13331
|
initialHeap: options.initialHeap
|
|
13084
13332
|
})
|
|
13085
13333
|
});
|
|
13086
|
-
return this.
|
|
13334
|
+
return this.bindWebSocketEnvironmentSession(environment, clientId, session);
|
|
13087
13335
|
}
|
|
13088
13336
|
/**
|
|
13089
13337
|
* Connect to an existing open session (same conversation thread) using a freshly minted WebSocket token.
|
|
@@ -13095,7 +13343,8 @@ var Granular = class _Granular {
|
|
|
13095
13343
|
body: JSON.stringify({})
|
|
13096
13344
|
});
|
|
13097
13345
|
const envData = await this.environments.get(minted.environmentId);
|
|
13098
|
-
|
|
13346
|
+
const environment = this.bindEnvironmentHandle(envData);
|
|
13347
|
+
return this.bindWebSocketEnvironmentSession(environment, clientId, minted);
|
|
13099
13348
|
}
|
|
13100
13349
|
/**
|
|
13101
13350
|
* Mark a session closed in the control plane. If `environment` is the connected handle for that
|
|
@@ -13127,7 +13376,11 @@ var Granular = class _Granular {
|
|
|
13127
13376
|
});
|
|
13128
13377
|
return this.connectSession({ sessionId, clientId: options?.clientId });
|
|
13129
13378
|
}
|
|
13130
|
-
|
|
13379
|
+
bindEnvironmentHandle(envData) {
|
|
13380
|
+
const graphqlEndpoint = `${this.httpUrl}/orchestrator/graphql`;
|
|
13381
|
+
return new Environment(this, envData, this.apiKey, graphqlEndpoint);
|
|
13382
|
+
}
|
|
13383
|
+
async bindWebSocketEnvironmentSession(environment, clientId, session) {
|
|
13131
13384
|
const client = new WSClient({
|
|
13132
13385
|
url: session.wsUrl,
|
|
13133
13386
|
sessionId: session.sessionId,
|
|
@@ -13138,16 +13391,13 @@ var Granular = class _Granular {
|
|
|
13138
13391
|
onReconnectError: this.onReconnectError
|
|
13139
13392
|
});
|
|
13140
13393
|
await client.connect();
|
|
13141
|
-
const
|
|
13142
|
-
const environment = new Environment(
|
|
13394
|
+
const environmentSession = new EnvironmentSession(
|
|
13143
13395
|
client,
|
|
13144
|
-
|
|
13145
|
-
clientId
|
|
13146
|
-
this.apiKey,
|
|
13147
|
-
graphqlEndpoint
|
|
13396
|
+
environment,
|
|
13397
|
+
clientId
|
|
13148
13398
|
);
|
|
13149
|
-
await
|
|
13150
|
-
return
|
|
13399
|
+
await environmentSession.hello();
|
|
13400
|
+
return environmentSession;
|
|
13151
13401
|
}
|
|
13152
13402
|
async activateEnvironment(environmentId) {
|
|
13153
13403
|
await this.request(`/orchestrator/runtime/environments/${environmentId}/activate`, {
|
|
@@ -15539,7 +15789,9 @@ function buildSessionTranscript(input) {
|
|
|
15539
15789
|
}
|
|
15540
15790
|
|
|
15541
15791
|
exports.Environment = Environment;
|
|
15792
|
+
exports.EnvironmentSession = EnvironmentSession;
|
|
15542
15793
|
exports.Granular = Granular;
|
|
15794
|
+
exports.OntologyHandle = OntologyHandle;
|
|
15543
15795
|
exports.Session = Session;
|
|
15544
15796
|
exports.WSClient = WSClient;
|
|
15545
15797
|
exports.buildContinuationInstruction = buildContinuationInstruction;
|