@granular-software/sdk 0.4.66 → 0.4.67

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/index.mjs CHANGED
@@ -5281,7 +5281,6 @@ var DEFAULT_RPC_TIMEOUT_MS = 3e4;
5281
5281
  var DOMAIN_PACKAGE_RPC_TIMEOUT_MS = 12e4;
5282
5282
  var EFFECT_CONTROL_RPC_TIMEOUT_MS = 12e4;
5283
5283
  var HARNESS_RUN_RPC_TIMEOUT_MS = 6e5;
5284
- var HEARTBEAT_RPC_TIMEOUT_MS = 15e3;
5285
5284
  var DEFAULT_CONNECT_TIMEOUT_MS = 1e4;
5286
5285
  var DEFAULT_RECONNECT_DELAY_MS = 3e3;
5287
5286
  var DEFAULT_MAX_RECONNECT_ATTEMPTS = 5;
@@ -5327,8 +5326,6 @@ function rpcTimeoutMsForMethod(method) {
5327
5326
  case "domain.fetchPackagePart":
5328
5327
  case "domain.getSummary":
5329
5328
  return DOMAIN_PACKAGE_RPC_TIMEOUT_MS;
5330
- case "client.heartbeat":
5331
- return HEARTBEAT_RPC_TIMEOUT_MS;
5332
5329
  case "effects.publishCatalog":
5333
5330
  case "effects.resetCatalog":
5334
5331
  case "effects.addCatalog":
@@ -5348,6 +5345,7 @@ var WSClient = class {
5348
5345
  sessionId;
5349
5346
  token;
5350
5347
  messageQueue = [];
5348
+ pendingRpcPeriodStartedAt = null;
5351
5349
  rpcHandlers = /* @__PURE__ */ new Map();
5352
5350
  eventHandlers = /* @__PURE__ */ new Map();
5353
5351
  canonicalDocumentQuarantineHandlers = /* @__PURE__ */ new Set();
@@ -5362,6 +5360,7 @@ var WSClient = class {
5362
5360
  cancelConnectAttempt = null;
5363
5361
  documentRepairRequired = false;
5364
5362
  canonicalDocumentActivated = false;
5363
+ lastInboundActivityAt = null;
5365
5364
  options;
5366
5365
  constructor(options) {
5367
5366
  this.options = options;
@@ -5375,6 +5374,29 @@ var WSClient = class {
5375
5374
  get currentSessionId() {
5376
5375
  return this.sessionId;
5377
5376
  }
5377
+ isTransportOpen() {
5378
+ return this.ws?.readyState === READY_STATE_OPEN;
5379
+ }
5380
+ hasPendingRpcRequests() {
5381
+ return this.messageQueue.length > 0;
5382
+ }
5383
+ hasRecentlyPendingRpcPeriod(maxAgeMs, now = Date.now()) {
5384
+ return this.messageQueue.length > 0 && this.pendingRpcPeriodStartedAt !== null && now - this.pendingRpcPeriodStartedAt <= maxAgeMs;
5385
+ }
5386
+ removePendingRpc(id) {
5387
+ this.messageQueue = this.messageQueue.filter(
5388
+ (pending) => pending.id !== id
5389
+ );
5390
+ if (this.messageQueue.length === 0) this.pendingRpcPeriodStartedAt = null;
5391
+ }
5392
+ /**
5393
+ * Whether the current socket has delivered any frame recently. SessionDO
5394
+ * broadcasts a one-way heartbeat every five seconds, so this measures the
5395
+ * transport without adding an RPC that can be delayed by application work.
5396
+ */
5397
+ hasRecentInboundActivity(maxAgeMs, now = Date.now()) {
5398
+ return this.ws?.readyState === READY_STATE_OPEN && this.lastInboundActivityAt !== null && now - this.lastInboundActivityAt <= maxAgeMs;
5399
+ }
5378
5400
  seedDocumentSnapshot(document) {
5379
5401
  if (!document || typeof document !== "object" || Array.isArray(document)) {
5380
5402
  return;
@@ -5526,6 +5548,7 @@ var WSClient = class {
5526
5548
  const socket = this.ws;
5527
5549
  this.connectionEpoch += 1;
5528
5550
  this.ws = null;
5551
+ this.lastInboundActivityAt = null;
5529
5552
  try {
5530
5553
  socket?.close(4001, "Transport failure");
5531
5554
  } catch {
@@ -5608,12 +5631,14 @@ var WSClient = class {
5608
5631
  closeStaleSocket();
5609
5632
  return;
5610
5633
  }
5634
+ this.lastInboundActivityAt = Date.now();
5611
5635
  this.reconnectAttempts = 0;
5612
5636
  this.emit("open", {});
5613
5637
  finish();
5614
5638
  };
5615
5639
  const handleMessage = (data) => {
5616
5640
  if (!isCurrent()) return;
5641
+ this.lastInboundActivityAt = Date.now();
5617
5642
  try {
5618
5643
  const text = typeof data === "string" ? data : data && typeof data === "object" && "toString" in data ? String(data.toString()) : "";
5619
5644
  this.handleMessage(JSON.parse(text));
@@ -5648,9 +5673,7 @@ var WSClient = class {
5648
5673
  this.ws = null;
5649
5674
  closeStaleSocket();
5650
5675
  finish(
5651
- new Error(
5652
- `WebSocket connect timed out after ${connectTimeoutMs}ms`
5653
- )
5676
+ new Error(`WebSocket connect timed out after ${connectTimeoutMs}ms`)
5654
5677
  );
5655
5678
  }, connectTimeoutMs);
5656
5679
  signal?.addEventListener("abort", handleAbort, { once: true });
@@ -5690,6 +5713,7 @@ var WSClient = class {
5690
5713
  pending.reject(error);
5691
5714
  });
5692
5715
  this.messageQueue = [];
5716
+ this.pendingRpcPeriodStartedAt = null;
5693
5717
  }
5694
5718
  emitReconnectErrorMessage(error, terminal = false) {
5695
5719
  const reconnectInfo = {
@@ -6093,9 +6117,7 @@ var WSClient = class {
6093
6117
  } else {
6094
6118
  pending.resolve(response.result);
6095
6119
  }
6096
- this.messageQueue = this.messageQueue.filter(
6097
- (q) => q.id !== response.id
6098
- );
6120
+ this.removePendingRpc(response.id);
6099
6121
  }
6100
6122
  return;
6101
6123
  }
@@ -6133,16 +6155,24 @@ var WSClient = class {
6133
6155
  const timeout = setTimeout(() => {
6134
6156
  const pending = this.messageQueue.find((q) => q.id === id);
6135
6157
  if (pending) {
6136
- this.messageQueue = this.messageQueue.filter((q) => q.id !== id);
6158
+ this.removePendingRpc(id);
6137
6159
  reject(new Error(`RPC timeout: ${method}`));
6138
6160
  }
6139
6161
  }, timeoutMs);
6140
- this.messageQueue.push({ resolve, reject, id, timeout });
6162
+ if (this.messageQueue.length === 0) {
6163
+ this.pendingRpcPeriodStartedAt = Date.now();
6164
+ }
6165
+ this.messageQueue.push({
6166
+ resolve,
6167
+ reject,
6168
+ id,
6169
+ timeout
6170
+ });
6141
6171
  try {
6142
6172
  this.ws.send(JSON.stringify(request));
6143
6173
  } catch (error) {
6144
6174
  clearTimeout(timeout);
6145
- this.messageQueue = this.messageQueue.filter((q) => q.id !== id);
6175
+ this.removePendingRpc(id);
6146
6176
  reject(error instanceof Error ? error : new Error(String(error)));
6147
6177
  }
6148
6178
  });
@@ -6247,6 +6277,7 @@ var WSClient = class {
6247
6277
  this.cancelConnectAttempt?.();
6248
6278
  this.cancelConnectAttempt = null;
6249
6279
  this.connectionEpoch += 1;
6280
+ this.lastInboundActivityAt = null;
6250
6281
  if (this.reconnectTimer) {
6251
6282
  clearTimeout(this.reconnectTimer);
6252
6283
  this.reconnectTimer = null;
@@ -12663,6 +12694,7 @@ function mergeClassSummaryPatch(target, patch) {
12663
12694
  function mergePropertySummaryPatch(target, patch) {
12664
12695
  pushUnique(target.notes, patch.notes || []);
12665
12696
  if (patch.searchable !== void 0) target.searchable = patch.searchable;
12697
+ if (patch.format !== void 0) target.format = patch.format;
12666
12698
  if (patch.searchablePhonetic !== void 0) {
12667
12699
  target.searchablePhonetic = patch.searchablePhonetic;
12668
12700
  }
@@ -15550,6 +15582,91 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15550
15582
  }
15551
15583
  });
15552
15584
 
15585
+ // ../metamodel-temporal/src/index.ts
15586
+ function normalizeTemporalFormat(format) {
15587
+ return format === "date" || format === "date-time" ? format : null;
15588
+ }
15589
+ function buildTemporalFieldMutations(fieldPath, format) {
15590
+ const normalized = normalizeTemporalFormat(format);
15591
+ if (!normalized) return [];
15592
+ return [
15593
+ {
15594
+ label: `set temporal format on ${fieldPath}`,
15595
+ query: `mutation { at(path: ${JSON.stringify(fieldPath)}) { set_temporal_format(format: ${JSON.stringify(normalized)}) { format } } }`
15596
+ }
15597
+ ];
15598
+ }
15599
+ var temporalMetamodelPackage = defineMetamodelPackage({
15600
+ id: "temporal",
15601
+ docs: {
15602
+ fieldRows: [
15603
+ {
15604
+ key: "format",
15605
+ description: "Temporal representation for date fields. Use `date` for a civil calendar date and `date-time` when the instant and time matter."
15606
+ }
15607
+ ]
15608
+ },
15609
+ graphql: {
15610
+ typeDefs: [
15611
+ `
15612
+ type TemporalFormatMetamodel {
15613
+ model: Model!
15614
+ format: String!
15615
+ }
15616
+
15617
+ extend type Model {
15618
+ temporal_format: TemporalFormatMetamodel
15619
+ }
15620
+
15621
+ extend type ModelMutation {
15622
+ set_temporal_format(format: String!): TemporalFormatMetamodel
15623
+ }
15624
+ `
15625
+ ],
15626
+ createResolvers({ run }) {
15627
+ return {
15628
+ TemporalFormatMetamodel: {
15629
+ model: (value) => value.model,
15630
+ format: (value) => value.format
15631
+ },
15632
+ Model: {
15633
+ temporal_format: async (ant) => await run(ant.temporal_format())
15634
+ },
15635
+ ModelMutation: {
15636
+ set_temporal_format: async (ant, { format }) => {
15637
+ if (format !== "date" && format !== "date-time") {
15638
+ throw new Error(`Unsupported temporal format: ${format}`);
15639
+ }
15640
+ return {
15641
+ model: await run(ant.set_temporal_format(format)),
15642
+ format
15643
+ };
15644
+ }
15645
+ }
15646
+ };
15647
+ }
15648
+ },
15649
+ manifest: {
15650
+ buildFieldMutations(fieldPath, spec) {
15651
+ return buildTemporalFieldMutations(fieldPath, spec.format);
15652
+ }
15653
+ },
15654
+ summary: {
15655
+ selections: {
15656
+ propertyFields: ["temporal_format { format }"]
15657
+ },
15658
+ readPropertySummary(rawProperty) {
15659
+ const format = rawProperty.temporal_format?.format;
15660
+ return format === "date" || format === "date-time" ? { format } : {};
15661
+ }
15662
+ },
15663
+ domain: {
15664
+ applyToPropertyIR(propertyIR, _propertySummary) {
15665
+ return propertyIR;
15666
+ }
15667
+ }
15668
+ });
15669
+
15553
15670
  // ../metamodel-validation-rule/src/index.ts
15554
15671
  function describeRule(rule) {
15555
15672
  if (rule.message) return rule.message;
@@ -15819,6 +15936,7 @@ var DEFAULT_METAMODEL_PACKAGES = [
15819
15936
  searchableMetamodelPackage,
15820
15937
  validationRuleMetamodelPackage,
15821
15938
  stateMachineMetamodelPackage,
15939
+ temporalMetamodelPackage,
15822
15940
  effectBehaviorsMetamodelPackage,
15823
15941
  policyMetamodelPackage
15824
15942
  ];
@@ -16141,16 +16259,100 @@ function normalizeEnvironmentData(environment) {
16141
16259
  setup: normalizeEnvironmentSetupSummary(environment.setup)
16142
16260
  };
16143
16261
  }
16262
+ function isGraphQLMutationDocument(query) {
16263
+ if (typeof query !== "string") return false;
16264
+ const tokens = [];
16265
+ for (let index = 0; index < query.length; ) {
16266
+ const character = query[index];
16267
+ if (/[,\s\ufeff]/.test(character)) {
16268
+ index += 1;
16269
+ continue;
16270
+ }
16271
+ if (character === "#") {
16272
+ while (index < query.length && !/[\r\n]/.test(query[index])) index += 1;
16273
+ continue;
16274
+ }
16275
+ if (character === '"') {
16276
+ const block = query.startsWith('"""', index);
16277
+ let closed = false;
16278
+ index += block ? 3 : 1;
16279
+ while (index < query.length) {
16280
+ if (block && query.startsWith('"""', index) && query[index - 1] !== "\\") {
16281
+ index += 3;
16282
+ closed = true;
16283
+ break;
16284
+ }
16285
+ if (!block && query[index] === '"') {
16286
+ index += 1;
16287
+ closed = true;
16288
+ break;
16289
+ }
16290
+ index += query[index] === "\\" && !block ? 2 : 1;
16291
+ }
16292
+ if (!closed) return true;
16293
+ continue;
16294
+ }
16295
+ const name = query.slice(index).match(/^[_A-Za-z][_0-9A-Za-z]*/)?.[0];
16296
+ if (name) {
16297
+ tokens.push(name);
16298
+ index += name.length;
16299
+ continue;
16300
+ }
16301
+ if ("{}()[]".includes(character)) tokens.push(character);
16302
+ index += 1;
16303
+ }
16304
+ const skipSelection = (start) => {
16305
+ let depth = 0;
16306
+ for (let index = start; index < tokens.length; index += 1) {
16307
+ if (tokens[index] === "{") depth += 1;
16308
+ if (tokens[index] === "}" && --depth === 0) return index + 1;
16309
+ }
16310
+ return null;
16311
+ };
16312
+ for (let index = 0; index < tokens.length; ) {
16313
+ const definition = tokens[index++];
16314
+ if (definition === "mutation") return true;
16315
+ if (definition === "{") {
16316
+ const nextDefinition2 = skipSelection(index - 1);
16317
+ if (nextDefinition2 === null) return true;
16318
+ index = nextDefinition2;
16319
+ continue;
16320
+ }
16321
+ if (!["query", "subscription", "fragment"].includes(definition || "")) {
16322
+ return true;
16323
+ }
16324
+ let parentheses = 0;
16325
+ let brackets = 0;
16326
+ while (index < tokens.length) {
16327
+ const token = tokens[index];
16328
+ if (token === "(") parentheses += 1;
16329
+ else if (token === ")") parentheses -= 1;
16330
+ else if (token === "[") brackets += 1;
16331
+ else if (token === "]") brackets -= 1;
16332
+ else if (token === "{" && parentheses === 0 && brackets === 0) break;
16333
+ index += 1;
16334
+ }
16335
+ if (index === tokens.length) return true;
16336
+ const nextDefinition = skipSelection(index);
16337
+ if (nextDefinition === null) return true;
16338
+ index = nextDefinition;
16339
+ }
16340
+ return false;
16341
+ }
16144
16342
  var Environment = class _Environment {
16145
16343
  granular;
16146
16344
  envData;
16147
16345
  _apiKey;
16148
16346
  _apiEndpoint;
16149
- constructor(granular, envData, apiKey, apiEndpoint) {
16347
+ _tokenProvider;
16348
+ _beforeMutationSend;
16349
+ constructor(granular, envData, apiKey, apiEndpoint, tokenProvider, beforeMutationSend) {
16150
16350
  this.granular = granular;
16151
16351
  this.envData = envData;
16152
16352
  this._apiKey = apiKey;
16153
16353
  this._apiEndpoint = apiEndpoint;
16354
+ this._tokenProvider = tokenProvider;
16355
+ this._beforeMutationSend = beforeMutationSend;
16154
16356
  }
16155
16357
  /** The environment ID */
16156
16358
  get environmentId() {
@@ -16216,6 +16418,15 @@ var Environment = class _Environment {
16216
16418
  get authToken() {
16217
16419
  return this._apiKey;
16218
16420
  }
16421
+ resolveAuthToken() {
16422
+ if (!this._tokenProvider) return this._apiKey;
16423
+ return Promise.resolve(this._tokenProvider()).then((provided) => {
16424
+ if (typeof provided === "string" && provided.trim()) {
16425
+ this._apiKey = provided.trim();
16426
+ }
16427
+ return this._apiKey;
16428
+ });
16429
+ }
16219
16430
  /** Base runtime URL derived from the GraphQL endpoint */
16220
16431
  get runtimeBaseUrl() {
16221
16432
  return this.getRuntimeBaseUrl();
@@ -16607,10 +16818,15 @@ var Environment = class _Environment {
16607
16818
  async controlPlaneRequest(path, options = {}) {
16608
16819
  const runtimeBase = this.getRuntimeBaseUrl();
16609
16820
  const url = `${runtimeBase}${path}`;
16821
+ const authToken = await this.resolveAuthToken();
16822
+ const method = (options.method || "GET").toUpperCase();
16823
+ if (method !== "GET" && method !== "HEAD" && method !== "OPTIONS") {
16824
+ this._beforeMutationSend?.();
16825
+ }
16610
16826
  const response = await fetch(url, {
16611
16827
  ...options,
16612
16828
  headers: {
16613
- Authorization: `Bearer ${this._apiKey}`,
16829
+ Authorization: `Bearer ${authToken}`,
16614
16830
  "Content-Type": "application/json",
16615
16831
  ...options.headers
16616
16832
  }
@@ -16675,11 +16891,15 @@ var Environment = class _Environment {
16675
16891
  * ```
16676
16892
  */
16677
16893
  async graphql(query, variables) {
16894
+ const authToken = await this.resolveAuthToken();
16895
+ if (this._beforeMutationSend && isGraphQLMutationDocument(query)) {
16896
+ this._beforeMutationSend?.();
16897
+ }
16678
16898
  const response = await fetch(this._apiEndpoint, {
16679
16899
  method: "POST",
16680
16900
  headers: {
16681
16901
  "Content-Type": "application/json",
16682
- Authorization: `Bearer ${this._apiKey}`
16902
+ Authorization: `Bearer ${authToken}`
16683
16903
  },
16684
16904
  body: JSON.stringify({
16685
16905
  environmentId: this.environmentId,
@@ -17158,8 +17378,10 @@ var Environment = class _Environment {
17158
17378
  eventType.payloadSchema.properties
17159
17379
  )) {
17160
17380
  const schema = propSchema;
17381
+ const format = schema.format === "date" || schema.format === "date-time" ? schema.format : void 0;
17161
17382
  fieldSpecs[propName] = {
17162
17383
  type: schema.type ?? "string",
17384
+ ...format ? { format } : {},
17163
17385
  description: schema.description
17164
17386
  };
17165
17387
  }
@@ -17581,40 +17803,36 @@ var EnvironmentSession = class extends Session {
17581
17803
  environment;
17582
17804
  sessionDataRoutePrefix;
17583
17805
  sessionDataHeaders;
17806
+ beforeMutationSend;
17584
17807
  heartbeatTimer = null;
17585
- heartbeatInFlight = false;
17586
- /** The last known graph container status, updated by checkReadiness() or on heartbeat */
17808
+ /** The last known graph container status, updated by checkReadiness(). */
17587
17809
  graphContainerStatus = null;
17588
17810
  constructor(client, environment, clientId, options = {}) {
17589
17811
  super(client, clientId, { initialQuota: options.initialQuota });
17590
17812
  this.environment = environment;
17591
17813
  this.sessionDataRoutePrefix = options.sessionDataRoutePrefix || "/orchestrator/ws/sessions";
17592
17814
  this.sessionDataHeaders = options.sessionDataHeaders || {};
17815
+ this.beforeMutationSend = options.beforeMutationSend;
17593
17816
  this.setFeedListTransport(
17594
17817
  (feedOptions) => this.sessionDataRequest("/feed", feedOptions)
17595
17818
  );
17596
17819
  }
17597
17820
  /**
17598
17821
  * Keep the browser session transport observable from the client side.
17599
- * A worker/proxy restart can leave a browser WebSocket appearing OPEN even
17600
- * though the server-side Durable Object has already closed its peer. The
17601
- * heartbeat gives the SDK a bounded failure signal so it can revoke that
17602
- * stale socket and use WSClient's normal reconnect path.
17822
+ * SessionDO already broadcasts a heartbeat every five seconds. Treat that
17823
+ * and all other inbound frames as proof of life instead of issuing an RPC:
17824
+ * an application RPC may be delayed by Durable Object work even while the
17825
+ * underlying WebSocket is healthy.
17603
17826
  */
17604
17827
  startHeartbeat() {
17605
17828
  if (this.heartbeatTimer) return;
17606
17829
  this.heartbeatTimer = setInterval(() => {
17607
- if (this.heartbeatInFlight) return;
17608
- this.heartbeatInFlight = true;
17609
- void this.client.call("client.heartbeat", {}).then((result) => {
17610
- if (result?.graphContainerStatus) {
17611
- this.graphContainerStatus = result.graphContainerStatus;
17612
- }
17613
- }).catch((error) => {
17614
- this.client.reportTransportFailure(error);
17615
- }).finally(() => {
17616
- this.heartbeatInFlight = false;
17617
- });
17830
+ if (!this.client.isTransportOpen()) return;
17831
+ if (this.client.hasRecentInboundActivity(3e4)) return;
17832
+ if (this.client.hasRecentlyPendingRpcPeriod(6e4)) return;
17833
+ this.client.reportTransportFailure(
17834
+ new Error("No server activity received for 30 seconds")
17835
+ );
17618
17836
  }, 5e3);
17619
17837
  this.heartbeatTimer.unref?.();
17620
17838
  }
@@ -17623,7 +17841,6 @@ var EnvironmentSession = class extends Session {
17623
17841
  clearInterval(this.heartbeatTimer);
17624
17842
  this.heartbeatTimer = null;
17625
17843
  }
17626
- this.heartbeatInFlight = false;
17627
17844
  }
17628
17845
  async hello() {
17629
17846
  const result = await super.hello();
@@ -17684,7 +17901,13 @@ var EnvironmentSession = class extends Session {
17684
17901
  for (let attempt = 1; attempt <= SESSION_DATA_REQUEST_RETRY_COUNT; attempt += 1) {
17685
17902
  try {
17686
17903
  const headers = new Headers(init2.headers);
17687
- headers.set("Authorization", `Bearer ${this.environment.authToken}`);
17904
+ const resolvedAuthToken = this.environment.resolveAuthToken();
17905
+ const authToken = typeof resolvedAuthToken === "string" ? resolvedAuthToken : await resolvedAuthToken;
17906
+ headers.set("Authorization", `Bearer ${authToken}`);
17907
+ const method = (init2.method || "GET").toUpperCase();
17908
+ if (method !== "GET" && method !== "HEAD" && method !== "OPTIONS") {
17909
+ this.beforeMutationSend?.();
17910
+ }
17688
17911
  const response = await fetch(url, {
17689
17912
  method: init2.method || "GET",
17690
17913
  headers,
@@ -18143,7 +18366,7 @@ var EnvironmentSession = class extends Session {
18143
18366
  method: "POST",
18144
18367
  headers: {
18145
18368
  "Content-Type": "application/json",
18146
- Authorization: `Bearer ${this.environment.authToken}`
18369
+ Authorization: `Bearer ${await this.environment.resolveAuthToken()}`
18147
18370
  },
18148
18371
  body: JSON.stringify({
18149
18372
  reason: "sdk_disconnect_http_fallback",
@@ -19081,6 +19304,10 @@ var Granular = class _Granular {
19081
19304
  });
19082
19305
  }
19083
19306
  async recordOpenAIUsageSpend(usage, context, options) {
19307
+ const providedToken = await this.tokenProvider?.();
19308
+ if (typeof providedToken === "string" && providedToken.trim()) {
19309
+ this.apiKey = providedToken.trim();
19310
+ }
19084
19311
  return recordOpenAIUsageSpend({
19085
19312
  apiUrl: this.apiUrl,
19086
19313
  token: this.apiKey,
@@ -19253,7 +19480,13 @@ var Granular = class _Granular {
19253
19480
  }
19254
19481
  bindEnvironmentHandle(envData) {
19255
19482
  const graphqlEndpoint = `${this.httpUrl}/orchestrator/graphql`;
19256
- return new Environment(this, envData, this.apiKey, graphqlEndpoint);
19483
+ return new Environment(
19484
+ this,
19485
+ envData,
19486
+ this.apiKey,
19487
+ graphqlEndpoint,
19488
+ this.tokenProvider
19489
+ );
19257
19490
  }
19258
19491
  async bindWebSocketEnvironmentSession(environment, clientId, session, transportOptions = {}) {
19259
19492
  const client = new WSClient({
@@ -20137,6 +20370,10 @@ var Granular = class _Granular {
20137
20370
  console.log(`[SDK] Requesting: ${url}`);
20138
20371
  }
20139
20372
  for (let attempt = 1; attempt <= LOCAL_CONTROL_REQUEST_RETRY_COUNT; attempt += 1) {
20373
+ const providedToken = await this.tokenProvider?.();
20374
+ if (typeof providedToken === "string" && providedToken.trim()) {
20375
+ this.apiKey = providedToken.trim();
20376
+ }
20140
20377
  const response = await fetch(url, {
20141
20378
  ...options,
20142
20379
  headers: {
@@ -22348,6 +22585,10 @@ function jsonSchemaTypeName(schema) {
22348
22585
  const record = normalizeJsonSchema(schema);
22349
22586
  if (!record) return "unknown";
22350
22587
  const type = record.type;
22588
+ if (record.format === "date-time") return "date-time";
22589
+ if (record.format === "date" || type === "date" || Array.isArray(type) && type.includes("date")) {
22590
+ return "date";
22591
+ }
22351
22592
  if (typeof type === "string") {
22352
22593
  if (type === "array") return "array";
22353
22594
  if (type === "object") return "object";
@@ -22849,6 +23090,8 @@ ${actionIndex}
22849
23090
  - A suggestion is a recommendation button: \`await stateHandle.suggest(message)\`. A prepared action is the editable form the user can review/run: \`const action = stateHandle.reach(); const prepared = await action.open()\`.
22850
23091
  - Use suggestions when the user asks "what can I do next?", asks for options, or gives an unclear intent. Prefer 2 to 5 concrete suggestions and keep text short.
22851
23092
  - Opening a prepared action does not execute the underlying mutation. Use \`.open()\` only when the user asks to prepare, review, show, or edit an action before running it, when a new record must be prepared, or when a reviewable form must collect missing editable inputs. Prefill only grounded values and leave unknown fields empty.
23093
+ - For creation inputs, use the declared action schema and its field descriptions as the mapping authority. Copy every compatible value explicitly present in the current request, conversation, selected/saved object, file, fresh read, or declared ontology default, including values for fields with arbitrary names. Omit only values for which that context provides no evidence.
23094
+ - Never fill an input because of its field name, a regular expression or other string pattern, its primitive type, a schema minimum, an example value, or the first enum option. If the evidence is ambiguous, leave the field unresolved or ask the user.
22852
23095
  - When the user explicitly commands an existing-record mutation such as approve, reject, block, route, send, or update, and one visible domain action uniquely matches, call that domain action directly. Do not substitute a state-handle \`.open()\` artifact for execution. If runtime policy requires confirmation, invoke the action once and let the runtime pause and resume that same invocation.
22853
23096
  - When a grounded related/context record can satisfy the prepared action through declared relationships, use those relationships to fill required relationship inputs before opening or updating the action. If a required relationship remains empty, continue through declared relationship chains from the grounded object when the next hop can fill that slot. Do not only save the context record in memory while leaving derivable relationship slots blank.
22854
23097
  - A derived intermediate relationship is not enough when another required relationship is still reachable from it. For example, if a team gives a cost center and the action also requires a budget, traverse the cost center's declared budget relationship before opening the action.
@@ -23449,6 +23692,6 @@ function calculateOpenAITokenSpend(model, rawUsage) {
23449
23692
  };
23450
23693
  }
23451
23694
 
23452
- export { Environment, EnvironmentSession, GRANULAR_FEED_DIAGNOSTIC_EVENT, Granular, OPENAI_MODEL_PRICING_USD_PER_MILLION, OntologyHandle, Session, SessionFeedController, WSClient, buildContinuationInstruction, buildContinuationInstructionFromTemplate, buildGranularAgentCheckpointBlock, buildGranularAgentDomainBlock, buildGranularAgentFileBlock, buildGranularAgentHeapBlock, buildGranularAgentLoopBlock, buildGranularAgentManualActionBlock, buildGranularAgentManualActionMemorySummary, buildGranularAgentReferentBlock, buildGranularAgentRuntimeImportsBlock, buildGranularAgentSessionBlock, buildGranularAgentSystemPrompt, buildGranularAgentSystemPromptFromTemplate, buildGranularAgentToolBlock, buildGranularAgentWorkflowBlock, buildOpenAISpendEventId, buildSessionTranscript, buildSessionTranscriptFromFeedItems, calculateOpenAITokenSpend, canonicalizeCommitValue, changedRecordSnapshots, consumeGranularReasoningOnlyChunk, consumeGranularReasoningTraceChunk, createFeedPublisher, createHarnessVerifierSnapshot, defineEffect, defineProjection, defineStateMachine, emitFeedDiagnostic, emitFeedDiagnosticToDefaultSink, emptyFeedSnapshot, evaluateContinuation, evaluateValidationRule, extractPromptTokens, getCurrentClosureId, getDefaultHarnessTemplateId, getExclusivePromptTarget, getOpenAIModelPricing, hasCanonicalSessionFeedActivation, hasOpenPrompt, hashHarnessTemplateValue, invokeRegisteredEffect, isCanonicalSessionFeedDocument, isLocalApiUrl, listHarnessTemplates, mergeFeedItemsBySequence, normalizeEffectBehaviors, normalizeFeedDiagnostic, normalizeFeedDiagnosticKind, normalizeFeedPage, normalizeOpenAIUsage, normalizePrompt, normalizePromptChoiceOption, normalizePromptText, normalizePromptType, orderTransientFeedItems, projectConversationReferentFocus, projectConversationReferentSummary, projectHeapSummary, projectLoopSummary, projectRecordSnapshotMutation, projectSessionFileSummary, projectWorkflowFocus, projectWorkflowSummary, readSessionFeedSnapshot, recordOpenAIUsageSpend, renderContinuationInstructionFromTemplate, renderGranularAgentSystemPromptFromTemplate, resolveApiUrl, resolveAuthTokenForApiUrl, resolveHarnessTemplate, resolveJobPresentation, resolvePromptAnswer, reviewGeneratedJobCode, scorePromptChoiceMatch, stripGranularReasoningTrace, toGranularHttpBase, validateHarnessTemplateManifest, validateProjectionResult, validationRuleFailureMessage };
23695
+ export { Environment, EnvironmentSession, GRANULAR_FEED_DIAGNOSTIC_EVENT, Granular, OPENAI_MODEL_PRICING_USD_PER_MILLION, OntologyHandle, Session, SessionFeedController, WSClient, buildContinuationInstruction, buildContinuationInstructionFromTemplate, buildGranularAgentCheckpointBlock, buildGranularAgentDomainBlock, buildGranularAgentFileBlock, buildGranularAgentHeapBlock, buildGranularAgentLoopBlock, buildGranularAgentManualActionBlock, buildGranularAgentManualActionMemorySummary, buildGranularAgentReferentBlock, buildGranularAgentRuntimeImportsBlock, buildGranularAgentSessionBlock, buildGranularAgentSystemPrompt, buildGranularAgentSystemPromptFromTemplate, buildGranularAgentToolBlock, buildGranularAgentWorkflowBlock, buildOpenAISpendEventId, buildSessionTranscript, buildSessionTranscriptFromFeedItems, calculateOpenAITokenSpend, canonicalizeCommitValue, changedRecordSnapshots, consumeGranularReasoningOnlyChunk, consumeGranularReasoningTraceChunk, createFeedPublisher, createHarnessVerifierSnapshot, defineEffect, defineProjection, defineStateMachine, emitFeedDiagnostic, emitFeedDiagnosticToDefaultSink, emptyFeedSnapshot, evaluateContinuation, evaluateValidationRule, extractPromptTokens, getCurrentClosureId, getDefaultHarnessTemplateId, getExclusivePromptTarget, getOpenAIModelPricing, hasCanonicalSessionFeedActivation, hasOpenPrompt, hashHarnessTemplateValue, invokeRegisteredEffect, isCanonicalSessionFeedDocument, isGraphQLMutationDocument, isLocalApiUrl, listHarnessTemplates, mergeFeedItemsBySequence, normalizeEffectBehaviors, normalizeFeedDiagnostic, normalizeFeedDiagnosticKind, normalizeFeedPage, normalizeOpenAIUsage, normalizePrompt, normalizePromptChoiceOption, normalizePromptText, normalizePromptType, orderTransientFeedItems, projectConversationReferentFocus, projectConversationReferentSummary, projectHeapSummary, projectLoopSummary, projectRecordSnapshotMutation, projectSessionFileSummary, projectWorkflowFocus, projectWorkflowSummary, readSessionFeedSnapshot, recordOpenAIUsageSpend, renderContinuationInstructionFromTemplate, renderGranularAgentSystemPromptFromTemplate, resolveApiUrl, resolveAuthTokenForApiUrl, resolveHarnessTemplate, resolveJobPresentation, resolvePromptAnswer, reviewGeneratedJobCode, scorePromptChoiceMatch, stripGranularReasoningTrace, toGranularHttpBase, validateHarnessTemplateManifest, validateProjectionResult, validationRuleFailureMessage };
23453
23696
  //# sourceMappingURL=index.mjs.map
23454
23697
  //# sourceMappingURL=index.mjs.map