@granular-software/sdk 0.4.65 → 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.js CHANGED
@@ -5303,7 +5303,6 @@ var DEFAULT_RPC_TIMEOUT_MS = 3e4;
5303
5303
  var DOMAIN_PACKAGE_RPC_TIMEOUT_MS = 12e4;
5304
5304
  var EFFECT_CONTROL_RPC_TIMEOUT_MS = 12e4;
5305
5305
  var HARNESS_RUN_RPC_TIMEOUT_MS = 6e5;
5306
- var HEARTBEAT_RPC_TIMEOUT_MS = 15e3;
5307
5306
  var DEFAULT_CONNECT_TIMEOUT_MS = 1e4;
5308
5307
  var DEFAULT_RECONNECT_DELAY_MS = 3e3;
5309
5308
  var DEFAULT_MAX_RECONNECT_ATTEMPTS = 5;
@@ -5349,8 +5348,6 @@ function rpcTimeoutMsForMethod(method) {
5349
5348
  case "domain.fetchPackagePart":
5350
5349
  case "domain.getSummary":
5351
5350
  return DOMAIN_PACKAGE_RPC_TIMEOUT_MS;
5352
- case "client.heartbeat":
5353
- return HEARTBEAT_RPC_TIMEOUT_MS;
5354
5351
  case "effects.publishCatalog":
5355
5352
  case "effects.resetCatalog":
5356
5353
  case "effects.addCatalog":
@@ -5370,6 +5367,7 @@ var WSClient = class {
5370
5367
  sessionId;
5371
5368
  token;
5372
5369
  messageQueue = [];
5370
+ pendingRpcPeriodStartedAt = null;
5373
5371
  rpcHandlers = /* @__PURE__ */ new Map();
5374
5372
  eventHandlers = /* @__PURE__ */ new Map();
5375
5373
  canonicalDocumentQuarantineHandlers = /* @__PURE__ */ new Set();
@@ -5384,6 +5382,7 @@ var WSClient = class {
5384
5382
  cancelConnectAttempt = null;
5385
5383
  documentRepairRequired = false;
5386
5384
  canonicalDocumentActivated = false;
5385
+ lastInboundActivityAt = null;
5387
5386
  options;
5388
5387
  constructor(options) {
5389
5388
  this.options = options;
@@ -5397,6 +5396,29 @@ var WSClient = class {
5397
5396
  get currentSessionId() {
5398
5397
  return this.sessionId;
5399
5398
  }
5399
+ isTransportOpen() {
5400
+ return this.ws?.readyState === READY_STATE_OPEN;
5401
+ }
5402
+ hasPendingRpcRequests() {
5403
+ return this.messageQueue.length > 0;
5404
+ }
5405
+ hasRecentlyPendingRpcPeriod(maxAgeMs, now = Date.now()) {
5406
+ return this.messageQueue.length > 0 && this.pendingRpcPeriodStartedAt !== null && now - this.pendingRpcPeriodStartedAt <= maxAgeMs;
5407
+ }
5408
+ removePendingRpc(id) {
5409
+ this.messageQueue = this.messageQueue.filter(
5410
+ (pending) => pending.id !== id
5411
+ );
5412
+ if (this.messageQueue.length === 0) this.pendingRpcPeriodStartedAt = null;
5413
+ }
5414
+ /**
5415
+ * Whether the current socket has delivered any frame recently. SessionDO
5416
+ * broadcasts a one-way heartbeat every five seconds, so this measures the
5417
+ * transport without adding an RPC that can be delayed by application work.
5418
+ */
5419
+ hasRecentInboundActivity(maxAgeMs, now = Date.now()) {
5420
+ return this.ws?.readyState === READY_STATE_OPEN && this.lastInboundActivityAt !== null && now - this.lastInboundActivityAt <= maxAgeMs;
5421
+ }
5400
5422
  seedDocumentSnapshot(document) {
5401
5423
  if (!document || typeof document !== "object" || Array.isArray(document)) {
5402
5424
  return;
@@ -5548,6 +5570,7 @@ var WSClient = class {
5548
5570
  const socket = this.ws;
5549
5571
  this.connectionEpoch += 1;
5550
5572
  this.ws = null;
5573
+ this.lastInboundActivityAt = null;
5551
5574
  try {
5552
5575
  socket?.close(4001, "Transport failure");
5553
5576
  } catch {
@@ -5630,12 +5653,14 @@ var WSClient = class {
5630
5653
  closeStaleSocket();
5631
5654
  return;
5632
5655
  }
5656
+ this.lastInboundActivityAt = Date.now();
5633
5657
  this.reconnectAttempts = 0;
5634
5658
  this.emit("open", {});
5635
5659
  finish();
5636
5660
  };
5637
5661
  const handleMessage = (data) => {
5638
5662
  if (!isCurrent()) return;
5663
+ this.lastInboundActivityAt = Date.now();
5639
5664
  try {
5640
5665
  const text = typeof data === "string" ? data : data && typeof data === "object" && "toString" in data ? String(data.toString()) : "";
5641
5666
  this.handleMessage(JSON.parse(text));
@@ -5670,9 +5695,7 @@ var WSClient = class {
5670
5695
  this.ws = null;
5671
5696
  closeStaleSocket();
5672
5697
  finish(
5673
- new Error(
5674
- `WebSocket connect timed out after ${connectTimeoutMs}ms`
5675
- )
5698
+ new Error(`WebSocket connect timed out after ${connectTimeoutMs}ms`)
5676
5699
  );
5677
5700
  }, connectTimeoutMs);
5678
5701
  signal?.addEventListener("abort", handleAbort, { once: true });
@@ -5712,6 +5735,7 @@ var WSClient = class {
5712
5735
  pending.reject(error);
5713
5736
  });
5714
5737
  this.messageQueue = [];
5738
+ this.pendingRpcPeriodStartedAt = null;
5715
5739
  }
5716
5740
  emitReconnectErrorMessage(error, terminal = false) {
5717
5741
  const reconnectInfo = {
@@ -6115,9 +6139,7 @@ var WSClient = class {
6115
6139
  } else {
6116
6140
  pending.resolve(response.result);
6117
6141
  }
6118
- this.messageQueue = this.messageQueue.filter(
6119
- (q) => q.id !== response.id
6120
- );
6142
+ this.removePendingRpc(response.id);
6121
6143
  }
6122
6144
  return;
6123
6145
  }
@@ -6155,16 +6177,24 @@ var WSClient = class {
6155
6177
  const timeout = setTimeout(() => {
6156
6178
  const pending = this.messageQueue.find((q) => q.id === id);
6157
6179
  if (pending) {
6158
- this.messageQueue = this.messageQueue.filter((q) => q.id !== id);
6180
+ this.removePendingRpc(id);
6159
6181
  reject(new Error(`RPC timeout: ${method}`));
6160
6182
  }
6161
6183
  }, timeoutMs);
6162
- this.messageQueue.push({ resolve, reject, id, timeout });
6184
+ if (this.messageQueue.length === 0) {
6185
+ this.pendingRpcPeriodStartedAt = Date.now();
6186
+ }
6187
+ this.messageQueue.push({
6188
+ resolve,
6189
+ reject,
6190
+ id,
6191
+ timeout
6192
+ });
6163
6193
  try {
6164
6194
  this.ws.send(JSON.stringify(request));
6165
6195
  } catch (error) {
6166
6196
  clearTimeout(timeout);
6167
- this.messageQueue = this.messageQueue.filter((q) => q.id !== id);
6197
+ this.removePendingRpc(id);
6168
6198
  reject(error instanceof Error ? error : new Error(String(error)));
6169
6199
  }
6170
6200
  });
@@ -6269,6 +6299,7 @@ var WSClient = class {
6269
6299
  this.cancelConnectAttempt?.();
6270
6300
  this.cancelConnectAttempt = null;
6271
6301
  this.connectionEpoch += 1;
6302
+ this.lastInboundActivityAt = null;
6272
6303
  if (this.reconnectTimer) {
6273
6304
  clearTimeout(this.reconnectTimer);
6274
6305
  this.reconnectTimer = null;
@@ -12685,6 +12716,7 @@ function mergeClassSummaryPatch(target, patch) {
12685
12716
  function mergePropertySummaryPatch(target, patch) {
12686
12717
  pushUnique(target.notes, patch.notes || []);
12687
12718
  if (patch.searchable !== void 0) target.searchable = patch.searchable;
12719
+ if (patch.format !== void 0) target.format = patch.format;
12688
12720
  if (patch.searchablePhonetic !== void 0) {
12689
12721
  target.searchablePhonetic = patch.searchablePhonetic;
12690
12722
  }
@@ -15572,6 +15604,91 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15572
15604
  }
15573
15605
  });
15574
15606
 
15607
+ // ../metamodel-temporal/src/index.ts
15608
+ function normalizeTemporalFormat(format) {
15609
+ return format === "date" || format === "date-time" ? format : null;
15610
+ }
15611
+ function buildTemporalFieldMutations(fieldPath, format) {
15612
+ const normalized = normalizeTemporalFormat(format);
15613
+ if (!normalized) return [];
15614
+ return [
15615
+ {
15616
+ label: `set temporal format on ${fieldPath}`,
15617
+ query: `mutation { at(path: ${JSON.stringify(fieldPath)}) { set_temporal_format(format: ${JSON.stringify(normalized)}) { format } } }`
15618
+ }
15619
+ ];
15620
+ }
15621
+ var temporalMetamodelPackage = defineMetamodelPackage({
15622
+ id: "temporal",
15623
+ docs: {
15624
+ fieldRows: [
15625
+ {
15626
+ key: "format",
15627
+ description: "Temporal representation for date fields. Use `date` for a civil calendar date and `date-time` when the instant and time matter."
15628
+ }
15629
+ ]
15630
+ },
15631
+ graphql: {
15632
+ typeDefs: [
15633
+ `
15634
+ type TemporalFormatMetamodel {
15635
+ model: Model!
15636
+ format: String!
15637
+ }
15638
+
15639
+ extend type Model {
15640
+ temporal_format: TemporalFormatMetamodel
15641
+ }
15642
+
15643
+ extend type ModelMutation {
15644
+ set_temporal_format(format: String!): TemporalFormatMetamodel
15645
+ }
15646
+ `
15647
+ ],
15648
+ createResolvers({ run }) {
15649
+ return {
15650
+ TemporalFormatMetamodel: {
15651
+ model: (value) => value.model,
15652
+ format: (value) => value.format
15653
+ },
15654
+ Model: {
15655
+ temporal_format: async (ant) => await run(ant.temporal_format())
15656
+ },
15657
+ ModelMutation: {
15658
+ set_temporal_format: async (ant, { format }) => {
15659
+ if (format !== "date" && format !== "date-time") {
15660
+ throw new Error(`Unsupported temporal format: ${format}`);
15661
+ }
15662
+ return {
15663
+ model: await run(ant.set_temporal_format(format)),
15664
+ format
15665
+ };
15666
+ }
15667
+ }
15668
+ };
15669
+ }
15670
+ },
15671
+ manifest: {
15672
+ buildFieldMutations(fieldPath, spec) {
15673
+ return buildTemporalFieldMutations(fieldPath, spec.format);
15674
+ }
15675
+ },
15676
+ summary: {
15677
+ selections: {
15678
+ propertyFields: ["temporal_format { format }"]
15679
+ },
15680
+ readPropertySummary(rawProperty) {
15681
+ const format = rawProperty.temporal_format?.format;
15682
+ return format === "date" || format === "date-time" ? { format } : {};
15683
+ }
15684
+ },
15685
+ domain: {
15686
+ applyToPropertyIR(propertyIR, _propertySummary) {
15687
+ return propertyIR;
15688
+ }
15689
+ }
15690
+ });
15691
+
15575
15692
  // ../metamodel-validation-rule/src/index.ts
15576
15693
  function describeRule(rule) {
15577
15694
  if (rule.message) return rule.message;
@@ -15841,6 +15958,7 @@ var DEFAULT_METAMODEL_PACKAGES = [
15841
15958
  searchableMetamodelPackage,
15842
15959
  validationRuleMetamodelPackage,
15843
15960
  stateMachineMetamodelPackage,
15961
+ temporalMetamodelPackage,
15844
15962
  effectBehaviorsMetamodelPackage,
15845
15963
  policyMetamodelPackage
15846
15964
  ];
@@ -16163,16 +16281,100 @@ function normalizeEnvironmentData(environment) {
16163
16281
  setup: normalizeEnvironmentSetupSummary(environment.setup)
16164
16282
  };
16165
16283
  }
16284
+ function isGraphQLMutationDocument(query) {
16285
+ if (typeof query !== "string") return false;
16286
+ const tokens = [];
16287
+ for (let index = 0; index < query.length; ) {
16288
+ const character = query[index];
16289
+ if (/[,\s\ufeff]/.test(character)) {
16290
+ index += 1;
16291
+ continue;
16292
+ }
16293
+ if (character === "#") {
16294
+ while (index < query.length && !/[\r\n]/.test(query[index])) index += 1;
16295
+ continue;
16296
+ }
16297
+ if (character === '"') {
16298
+ const block = query.startsWith('"""', index);
16299
+ let closed = false;
16300
+ index += block ? 3 : 1;
16301
+ while (index < query.length) {
16302
+ if (block && query.startsWith('"""', index) && query[index - 1] !== "\\") {
16303
+ index += 3;
16304
+ closed = true;
16305
+ break;
16306
+ }
16307
+ if (!block && query[index] === '"') {
16308
+ index += 1;
16309
+ closed = true;
16310
+ break;
16311
+ }
16312
+ index += query[index] === "\\" && !block ? 2 : 1;
16313
+ }
16314
+ if (!closed) return true;
16315
+ continue;
16316
+ }
16317
+ const name = query.slice(index).match(/^[_A-Za-z][_0-9A-Za-z]*/)?.[0];
16318
+ if (name) {
16319
+ tokens.push(name);
16320
+ index += name.length;
16321
+ continue;
16322
+ }
16323
+ if ("{}()[]".includes(character)) tokens.push(character);
16324
+ index += 1;
16325
+ }
16326
+ const skipSelection = (start) => {
16327
+ let depth = 0;
16328
+ for (let index = start; index < tokens.length; index += 1) {
16329
+ if (tokens[index] === "{") depth += 1;
16330
+ if (tokens[index] === "}" && --depth === 0) return index + 1;
16331
+ }
16332
+ return null;
16333
+ };
16334
+ for (let index = 0; index < tokens.length; ) {
16335
+ const definition = tokens[index++];
16336
+ if (definition === "mutation") return true;
16337
+ if (definition === "{") {
16338
+ const nextDefinition2 = skipSelection(index - 1);
16339
+ if (nextDefinition2 === null) return true;
16340
+ index = nextDefinition2;
16341
+ continue;
16342
+ }
16343
+ if (!["query", "subscription", "fragment"].includes(definition || "")) {
16344
+ return true;
16345
+ }
16346
+ let parentheses = 0;
16347
+ let brackets = 0;
16348
+ while (index < tokens.length) {
16349
+ const token = tokens[index];
16350
+ if (token === "(") parentheses += 1;
16351
+ else if (token === ")") parentheses -= 1;
16352
+ else if (token === "[") brackets += 1;
16353
+ else if (token === "]") brackets -= 1;
16354
+ else if (token === "{" && parentheses === 0 && brackets === 0) break;
16355
+ index += 1;
16356
+ }
16357
+ if (index === tokens.length) return true;
16358
+ const nextDefinition = skipSelection(index);
16359
+ if (nextDefinition === null) return true;
16360
+ index = nextDefinition;
16361
+ }
16362
+ return false;
16363
+ }
16166
16364
  var Environment = class _Environment {
16167
16365
  granular;
16168
16366
  envData;
16169
16367
  _apiKey;
16170
16368
  _apiEndpoint;
16171
- constructor(granular, envData, apiKey, apiEndpoint) {
16369
+ _tokenProvider;
16370
+ _beforeMutationSend;
16371
+ constructor(granular, envData, apiKey, apiEndpoint, tokenProvider, beforeMutationSend) {
16172
16372
  this.granular = granular;
16173
16373
  this.envData = envData;
16174
16374
  this._apiKey = apiKey;
16175
16375
  this._apiEndpoint = apiEndpoint;
16376
+ this._tokenProvider = tokenProvider;
16377
+ this._beforeMutationSend = beforeMutationSend;
16176
16378
  }
16177
16379
  /** The environment ID */
16178
16380
  get environmentId() {
@@ -16238,6 +16440,15 @@ var Environment = class _Environment {
16238
16440
  get authToken() {
16239
16441
  return this._apiKey;
16240
16442
  }
16443
+ resolveAuthToken() {
16444
+ if (!this._tokenProvider) return this._apiKey;
16445
+ return Promise.resolve(this._tokenProvider()).then((provided) => {
16446
+ if (typeof provided === "string" && provided.trim()) {
16447
+ this._apiKey = provided.trim();
16448
+ }
16449
+ return this._apiKey;
16450
+ });
16451
+ }
16241
16452
  /** Base runtime URL derived from the GraphQL endpoint */
16242
16453
  get runtimeBaseUrl() {
16243
16454
  return this.getRuntimeBaseUrl();
@@ -16629,10 +16840,15 @@ var Environment = class _Environment {
16629
16840
  async controlPlaneRequest(path, options = {}) {
16630
16841
  const runtimeBase = this.getRuntimeBaseUrl();
16631
16842
  const url = `${runtimeBase}${path}`;
16843
+ const authToken = await this.resolveAuthToken();
16844
+ const method = (options.method || "GET").toUpperCase();
16845
+ if (method !== "GET" && method !== "HEAD" && method !== "OPTIONS") {
16846
+ this._beforeMutationSend?.();
16847
+ }
16632
16848
  const response = await fetch(url, {
16633
16849
  ...options,
16634
16850
  headers: {
16635
- Authorization: `Bearer ${this._apiKey}`,
16851
+ Authorization: `Bearer ${authToken}`,
16636
16852
  "Content-Type": "application/json",
16637
16853
  ...options.headers
16638
16854
  }
@@ -16697,11 +16913,15 @@ var Environment = class _Environment {
16697
16913
  * ```
16698
16914
  */
16699
16915
  async graphql(query, variables) {
16916
+ const authToken = await this.resolveAuthToken();
16917
+ if (this._beforeMutationSend && isGraphQLMutationDocument(query)) {
16918
+ this._beforeMutationSend?.();
16919
+ }
16700
16920
  const response = await fetch(this._apiEndpoint, {
16701
16921
  method: "POST",
16702
16922
  headers: {
16703
16923
  "Content-Type": "application/json",
16704
- Authorization: `Bearer ${this._apiKey}`
16924
+ Authorization: `Bearer ${authToken}`
16705
16925
  },
16706
16926
  body: JSON.stringify({
16707
16927
  environmentId: this.environmentId,
@@ -17180,8 +17400,10 @@ var Environment = class _Environment {
17180
17400
  eventType.payloadSchema.properties
17181
17401
  )) {
17182
17402
  const schema = propSchema;
17403
+ const format = schema.format === "date" || schema.format === "date-time" ? schema.format : void 0;
17183
17404
  fieldSpecs[propName] = {
17184
17405
  type: schema.type ?? "string",
17406
+ ...format ? { format } : {},
17185
17407
  description: schema.description
17186
17408
  };
17187
17409
  }
@@ -17603,40 +17825,36 @@ var EnvironmentSession = class extends Session {
17603
17825
  environment;
17604
17826
  sessionDataRoutePrefix;
17605
17827
  sessionDataHeaders;
17828
+ beforeMutationSend;
17606
17829
  heartbeatTimer = null;
17607
- heartbeatInFlight = false;
17608
- /** The last known graph container status, updated by checkReadiness() or on heartbeat */
17830
+ /** The last known graph container status, updated by checkReadiness(). */
17609
17831
  graphContainerStatus = null;
17610
17832
  constructor(client, environment, clientId, options = {}) {
17611
17833
  super(client, clientId, { initialQuota: options.initialQuota });
17612
17834
  this.environment = environment;
17613
17835
  this.sessionDataRoutePrefix = options.sessionDataRoutePrefix || "/orchestrator/ws/sessions";
17614
17836
  this.sessionDataHeaders = options.sessionDataHeaders || {};
17837
+ this.beforeMutationSend = options.beforeMutationSend;
17615
17838
  this.setFeedListTransport(
17616
17839
  (feedOptions) => this.sessionDataRequest("/feed", feedOptions)
17617
17840
  );
17618
17841
  }
17619
17842
  /**
17620
17843
  * Keep the browser session transport observable from the client side.
17621
- * A worker/proxy restart can leave a browser WebSocket appearing OPEN even
17622
- * though the server-side Durable Object has already closed its peer. The
17623
- * heartbeat gives the SDK a bounded failure signal so it can revoke that
17624
- * stale socket and use WSClient's normal reconnect path.
17844
+ * SessionDO already broadcasts a heartbeat every five seconds. Treat that
17845
+ * and all other inbound frames as proof of life instead of issuing an RPC:
17846
+ * an application RPC may be delayed by Durable Object work even while the
17847
+ * underlying WebSocket is healthy.
17625
17848
  */
17626
17849
  startHeartbeat() {
17627
17850
  if (this.heartbeatTimer) return;
17628
17851
  this.heartbeatTimer = setInterval(() => {
17629
- if (this.heartbeatInFlight) return;
17630
- this.heartbeatInFlight = true;
17631
- void this.client.call("client.heartbeat", {}).then((result) => {
17632
- if (result?.graphContainerStatus) {
17633
- this.graphContainerStatus = result.graphContainerStatus;
17634
- }
17635
- }).catch((error) => {
17636
- this.client.reportTransportFailure(error);
17637
- }).finally(() => {
17638
- this.heartbeatInFlight = false;
17639
- });
17852
+ if (!this.client.isTransportOpen()) return;
17853
+ if (this.client.hasRecentInboundActivity(3e4)) return;
17854
+ if (this.client.hasRecentlyPendingRpcPeriod(6e4)) return;
17855
+ this.client.reportTransportFailure(
17856
+ new Error("No server activity received for 30 seconds")
17857
+ );
17640
17858
  }, 5e3);
17641
17859
  this.heartbeatTimer.unref?.();
17642
17860
  }
@@ -17645,7 +17863,6 @@ var EnvironmentSession = class extends Session {
17645
17863
  clearInterval(this.heartbeatTimer);
17646
17864
  this.heartbeatTimer = null;
17647
17865
  }
17648
- this.heartbeatInFlight = false;
17649
17866
  }
17650
17867
  async hello() {
17651
17868
  const result = await super.hello();
@@ -17706,7 +17923,13 @@ var EnvironmentSession = class extends Session {
17706
17923
  for (let attempt = 1; attempt <= SESSION_DATA_REQUEST_RETRY_COUNT; attempt += 1) {
17707
17924
  try {
17708
17925
  const headers = new Headers(init2.headers);
17709
- headers.set("Authorization", `Bearer ${this.environment.authToken}`);
17926
+ const resolvedAuthToken = this.environment.resolveAuthToken();
17927
+ const authToken = typeof resolvedAuthToken === "string" ? resolvedAuthToken : await resolvedAuthToken;
17928
+ headers.set("Authorization", `Bearer ${authToken}`);
17929
+ const method = (init2.method || "GET").toUpperCase();
17930
+ if (method !== "GET" && method !== "HEAD" && method !== "OPTIONS") {
17931
+ this.beforeMutationSend?.();
17932
+ }
17710
17933
  const response = await fetch(url, {
17711
17934
  method: init2.method || "GET",
17712
17935
  headers,
@@ -18165,7 +18388,7 @@ var EnvironmentSession = class extends Session {
18165
18388
  method: "POST",
18166
18389
  headers: {
18167
18390
  "Content-Type": "application/json",
18168
- Authorization: `Bearer ${this.environment.authToken}`
18391
+ Authorization: `Bearer ${await this.environment.resolveAuthToken()}`
18169
18392
  },
18170
18393
  body: JSON.stringify({
18171
18394
  reason: "sdk_disconnect_http_fallback",
@@ -18957,6 +19180,9 @@ var Granular = class _Granular {
18957
19180
  if (typeof options.limit === "number") {
18958
19181
  query.set("limit", String(options.limit));
18959
19182
  }
19183
+ if (options.consistency === "refresh") {
19184
+ query.set("consistency", "refresh");
19185
+ }
18960
19186
  const state = await this.request(
18961
19187
  `/sdk/user-environment-state?${query.toString()}`
18962
19188
  );
@@ -19100,6 +19326,10 @@ var Granular = class _Granular {
19100
19326
  });
19101
19327
  }
19102
19328
  async recordOpenAIUsageSpend(usage, context, options) {
19329
+ const providedToken = await this.tokenProvider?.();
19330
+ if (typeof providedToken === "string" && providedToken.trim()) {
19331
+ this.apiKey = providedToken.trim();
19332
+ }
19103
19333
  return recordOpenAIUsageSpend({
19104
19334
  apiUrl: this.apiUrl,
19105
19335
  token: this.apiKey,
@@ -19272,7 +19502,13 @@ var Granular = class _Granular {
19272
19502
  }
19273
19503
  bindEnvironmentHandle(envData) {
19274
19504
  const graphqlEndpoint = `${this.httpUrl}/orchestrator/graphql`;
19275
- return new Environment(this, envData, this.apiKey, graphqlEndpoint);
19505
+ return new Environment(
19506
+ this,
19507
+ envData,
19508
+ this.apiKey,
19509
+ graphqlEndpoint,
19510
+ this.tokenProvider
19511
+ );
19276
19512
  }
19277
19513
  async bindWebSocketEnvironmentSession(environment, clientId, session, transportOptions = {}) {
19278
19514
  const client = new WSClient({
@@ -20156,6 +20392,10 @@ var Granular = class _Granular {
20156
20392
  console.log(`[SDK] Requesting: ${url}`);
20157
20393
  }
20158
20394
  for (let attempt = 1; attempt <= LOCAL_CONTROL_REQUEST_RETRY_COUNT; attempt += 1) {
20395
+ const providedToken = await this.tokenProvider?.();
20396
+ if (typeof providedToken === "string" && providedToken.trim()) {
20397
+ this.apiKey = providedToken.trim();
20398
+ }
20159
20399
  const response = await fetch(url, {
20160
20400
  ...options,
20161
20401
  headers: {
@@ -22367,6 +22607,10 @@ function jsonSchemaTypeName(schema) {
22367
22607
  const record = normalizeJsonSchema(schema);
22368
22608
  if (!record) return "unknown";
22369
22609
  const type = record.type;
22610
+ if (record.format === "date-time") return "date-time";
22611
+ if (record.format === "date" || type === "date" || Array.isArray(type) && type.includes("date")) {
22612
+ return "date";
22613
+ }
22370
22614
  if (typeof type === "string") {
22371
22615
  if (type === "array") return "array";
22372
22616
  if (type === "object") return "object";
@@ -22868,6 +23112,8 @@ ${actionIndex}
22868
23112
  - 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()\`.
22869
23113
  - 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.
22870
23114
  - 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.
23115
+ - 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.
23116
+ - 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.
22871
23117
  - 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.
22872
23118
  - 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.
22873
23119
  - 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.
@@ -23521,6 +23767,7 @@ exports.hasOpenPrompt = hasOpenPrompt;
23521
23767
  exports.hashHarnessTemplateValue = hashHarnessTemplateValue;
23522
23768
  exports.invokeRegisteredEffect = invokeRegisteredEffect;
23523
23769
  exports.isCanonicalSessionFeedDocument = isCanonicalSessionFeedDocument;
23770
+ exports.isGraphQLMutationDocument = isGraphQLMutationDocument;
23524
23771
  exports.isLocalApiUrl = isLocalApiUrl;
23525
23772
  exports.listHarnessTemplates = listHarnessTemplates;
23526
23773
  exports.mergeFeedItemsBySequence = mergeFeedItemsBySequence;