@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.
@@ -5256,7 +5256,6 @@ var DEFAULT_RPC_TIMEOUT_MS = 3e4;
5256
5256
  var DOMAIN_PACKAGE_RPC_TIMEOUT_MS = 12e4;
5257
5257
  var EFFECT_CONTROL_RPC_TIMEOUT_MS = 12e4;
5258
5258
  var HARNESS_RUN_RPC_TIMEOUT_MS = 6e5;
5259
- var HEARTBEAT_RPC_TIMEOUT_MS = 15e3;
5260
5259
  var DEFAULT_CONNECT_TIMEOUT_MS = 1e4;
5261
5260
  var DEFAULT_RECONNECT_DELAY_MS = 3e3;
5262
5261
  var DEFAULT_MAX_RECONNECT_ATTEMPTS = 5;
@@ -5302,8 +5301,6 @@ function rpcTimeoutMsForMethod(method) {
5302
5301
  case "domain.fetchPackagePart":
5303
5302
  case "domain.getSummary":
5304
5303
  return DOMAIN_PACKAGE_RPC_TIMEOUT_MS;
5305
- case "client.heartbeat":
5306
- return HEARTBEAT_RPC_TIMEOUT_MS;
5307
5304
  case "effects.publishCatalog":
5308
5305
  case "effects.resetCatalog":
5309
5306
  case "effects.addCatalog":
@@ -5323,6 +5320,7 @@ var WSClient = class {
5323
5320
  sessionId;
5324
5321
  token;
5325
5322
  messageQueue = [];
5323
+ pendingRpcPeriodStartedAt = null;
5326
5324
  rpcHandlers = /* @__PURE__ */ new Map();
5327
5325
  eventHandlers = /* @__PURE__ */ new Map();
5328
5326
  canonicalDocumentQuarantineHandlers = /* @__PURE__ */ new Set();
@@ -5337,6 +5335,7 @@ var WSClient = class {
5337
5335
  cancelConnectAttempt = null;
5338
5336
  documentRepairRequired = false;
5339
5337
  canonicalDocumentActivated = false;
5338
+ lastInboundActivityAt = null;
5340
5339
  options;
5341
5340
  constructor(options) {
5342
5341
  this.options = options;
@@ -5350,6 +5349,29 @@ var WSClient = class {
5350
5349
  get currentSessionId() {
5351
5350
  return this.sessionId;
5352
5351
  }
5352
+ isTransportOpen() {
5353
+ return this.ws?.readyState === READY_STATE_OPEN;
5354
+ }
5355
+ hasPendingRpcRequests() {
5356
+ return this.messageQueue.length > 0;
5357
+ }
5358
+ hasRecentlyPendingRpcPeriod(maxAgeMs, now = Date.now()) {
5359
+ return this.messageQueue.length > 0 && this.pendingRpcPeriodStartedAt !== null && now - this.pendingRpcPeriodStartedAt <= maxAgeMs;
5360
+ }
5361
+ removePendingRpc(id) {
5362
+ this.messageQueue = this.messageQueue.filter(
5363
+ (pending) => pending.id !== id
5364
+ );
5365
+ if (this.messageQueue.length === 0) this.pendingRpcPeriodStartedAt = null;
5366
+ }
5367
+ /**
5368
+ * Whether the current socket has delivered any frame recently. SessionDO
5369
+ * broadcasts a one-way heartbeat every five seconds, so this measures the
5370
+ * transport without adding an RPC that can be delayed by application work.
5371
+ */
5372
+ hasRecentInboundActivity(maxAgeMs, now = Date.now()) {
5373
+ return this.ws?.readyState === READY_STATE_OPEN && this.lastInboundActivityAt !== null && now - this.lastInboundActivityAt <= maxAgeMs;
5374
+ }
5353
5375
  seedDocumentSnapshot(document) {
5354
5376
  if (!document || typeof document !== "object" || Array.isArray(document)) {
5355
5377
  return;
@@ -5501,6 +5523,7 @@ var WSClient = class {
5501
5523
  const socket = this.ws;
5502
5524
  this.connectionEpoch += 1;
5503
5525
  this.ws = null;
5526
+ this.lastInboundActivityAt = null;
5504
5527
  try {
5505
5528
  socket?.close(4001, "Transport failure");
5506
5529
  } catch {
@@ -5583,12 +5606,14 @@ var WSClient = class {
5583
5606
  closeStaleSocket();
5584
5607
  return;
5585
5608
  }
5609
+ this.lastInboundActivityAt = Date.now();
5586
5610
  this.reconnectAttempts = 0;
5587
5611
  this.emit("open", {});
5588
5612
  finish();
5589
5613
  };
5590
5614
  const handleMessage = (data) => {
5591
5615
  if (!isCurrent()) return;
5616
+ this.lastInboundActivityAt = Date.now();
5592
5617
  try {
5593
5618
  const text = typeof data === "string" ? data : data && typeof data === "object" && "toString" in data ? String(data.toString()) : "";
5594
5619
  this.handleMessage(JSON.parse(text));
@@ -5623,9 +5648,7 @@ var WSClient = class {
5623
5648
  this.ws = null;
5624
5649
  closeStaleSocket();
5625
5650
  finish(
5626
- new Error(
5627
- `WebSocket connect timed out after ${connectTimeoutMs}ms`
5628
- )
5651
+ new Error(`WebSocket connect timed out after ${connectTimeoutMs}ms`)
5629
5652
  );
5630
5653
  }, connectTimeoutMs);
5631
5654
  signal?.addEventListener("abort", handleAbort, { once: true });
@@ -5665,6 +5688,7 @@ var WSClient = class {
5665
5688
  pending.reject(error);
5666
5689
  });
5667
5690
  this.messageQueue = [];
5691
+ this.pendingRpcPeriodStartedAt = null;
5668
5692
  }
5669
5693
  emitReconnectErrorMessage(error, terminal = false) {
5670
5694
  const reconnectInfo = {
@@ -6068,9 +6092,7 @@ var WSClient = class {
6068
6092
  } else {
6069
6093
  pending.resolve(response.result);
6070
6094
  }
6071
- this.messageQueue = this.messageQueue.filter(
6072
- (q) => q.id !== response.id
6073
- );
6095
+ this.removePendingRpc(response.id);
6074
6096
  }
6075
6097
  return;
6076
6098
  }
@@ -6108,16 +6130,24 @@ var WSClient = class {
6108
6130
  const timeout = setTimeout(() => {
6109
6131
  const pending = this.messageQueue.find((q) => q.id === id);
6110
6132
  if (pending) {
6111
- this.messageQueue = this.messageQueue.filter((q) => q.id !== id);
6133
+ this.removePendingRpc(id);
6112
6134
  reject(new Error(`RPC timeout: ${method}`));
6113
6135
  }
6114
6136
  }, timeoutMs);
6115
- this.messageQueue.push({ resolve, reject, id, timeout });
6137
+ if (this.messageQueue.length === 0) {
6138
+ this.pendingRpcPeriodStartedAt = Date.now();
6139
+ }
6140
+ this.messageQueue.push({
6141
+ resolve,
6142
+ reject,
6143
+ id,
6144
+ timeout
6145
+ });
6116
6146
  try {
6117
6147
  this.ws.send(JSON.stringify(request));
6118
6148
  } catch (error) {
6119
6149
  clearTimeout(timeout);
6120
- this.messageQueue = this.messageQueue.filter((q) => q.id !== id);
6150
+ this.removePendingRpc(id);
6121
6151
  reject(error instanceof Error ? error : new Error(String(error)));
6122
6152
  }
6123
6153
  });
@@ -6222,6 +6252,7 @@ var WSClient = class {
6222
6252
  this.cancelConnectAttempt?.();
6223
6253
  this.cancelConnectAttempt = null;
6224
6254
  this.connectionEpoch += 1;
6255
+ this.lastInboundActivityAt = null;
6225
6256
  if (this.reconnectTimer) {
6226
6257
  clearTimeout(this.reconnectTimer);
6227
6258
  this.reconnectTimer = null;
@@ -12638,6 +12669,7 @@ function mergeClassSummaryPatch(target, patch) {
12638
12669
  function mergePropertySummaryPatch(target, patch) {
12639
12670
  pushUnique(target.notes, patch.notes || []);
12640
12671
  if (patch.searchable !== void 0) target.searchable = patch.searchable;
12672
+ if (patch.format !== void 0) target.format = patch.format;
12641
12673
  if (patch.searchablePhonetic !== void 0) {
12642
12674
  target.searchablePhonetic = patch.searchablePhonetic;
12643
12675
  }
@@ -15474,6 +15506,91 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15474
15506
  }
15475
15507
  });
15476
15508
 
15509
+ // ../metamodel-temporal/src/index.ts
15510
+ function normalizeTemporalFormat(format) {
15511
+ return format === "date" || format === "date-time" ? format : null;
15512
+ }
15513
+ function buildTemporalFieldMutations(fieldPath, format) {
15514
+ const normalized = normalizeTemporalFormat(format);
15515
+ if (!normalized) return [];
15516
+ return [
15517
+ {
15518
+ label: `set temporal format on ${fieldPath}`,
15519
+ query: `mutation { at(path: ${JSON.stringify(fieldPath)}) { set_temporal_format(format: ${JSON.stringify(normalized)}) { format } } }`
15520
+ }
15521
+ ];
15522
+ }
15523
+ var temporalMetamodelPackage = defineMetamodelPackage({
15524
+ id: "temporal",
15525
+ docs: {
15526
+ fieldRows: [
15527
+ {
15528
+ key: "format",
15529
+ description: "Temporal representation for date fields. Use `date` for a civil calendar date and `date-time` when the instant and time matter."
15530
+ }
15531
+ ]
15532
+ },
15533
+ graphql: {
15534
+ typeDefs: [
15535
+ `
15536
+ type TemporalFormatMetamodel {
15537
+ model: Model!
15538
+ format: String!
15539
+ }
15540
+
15541
+ extend type Model {
15542
+ temporal_format: TemporalFormatMetamodel
15543
+ }
15544
+
15545
+ extend type ModelMutation {
15546
+ set_temporal_format(format: String!): TemporalFormatMetamodel
15547
+ }
15548
+ `
15549
+ ],
15550
+ createResolvers({ run }) {
15551
+ return {
15552
+ TemporalFormatMetamodel: {
15553
+ model: (value) => value.model,
15554
+ format: (value) => value.format
15555
+ },
15556
+ Model: {
15557
+ temporal_format: async (ant) => await run(ant.temporal_format())
15558
+ },
15559
+ ModelMutation: {
15560
+ set_temporal_format: async (ant, { format }) => {
15561
+ if (format !== "date" && format !== "date-time") {
15562
+ throw new Error(`Unsupported temporal format: ${format}`);
15563
+ }
15564
+ return {
15565
+ model: await run(ant.set_temporal_format(format)),
15566
+ format
15567
+ };
15568
+ }
15569
+ }
15570
+ };
15571
+ }
15572
+ },
15573
+ manifest: {
15574
+ buildFieldMutations(fieldPath, spec) {
15575
+ return buildTemporalFieldMutations(fieldPath, spec.format);
15576
+ }
15577
+ },
15578
+ summary: {
15579
+ selections: {
15580
+ propertyFields: ["temporal_format { format }"]
15581
+ },
15582
+ readPropertySummary(rawProperty) {
15583
+ const format = rawProperty.temporal_format?.format;
15584
+ return format === "date" || format === "date-time" ? { format } : {};
15585
+ }
15586
+ },
15587
+ domain: {
15588
+ applyToPropertyIR(propertyIR, _propertySummary) {
15589
+ return propertyIR;
15590
+ }
15591
+ }
15592
+ });
15593
+
15477
15594
  // ../metamodel-validation-rule/src/index.ts
15478
15595
  function describeRule(rule) {
15479
15596
  if (rule.message) return rule.message;
@@ -15645,6 +15762,7 @@ var DEFAULT_METAMODEL_PACKAGES = [
15645
15762
  searchableMetamodelPackage,
15646
15763
  validationRuleMetamodelPackage,
15647
15764
  stateMachineMetamodelPackage,
15765
+ temporalMetamodelPackage,
15648
15766
  effectBehaviorsMetamodelPackage,
15649
15767
  policyMetamodelPackage
15650
15768
  ];
@@ -15967,16 +16085,100 @@ function normalizeEnvironmentData(environment) {
15967
16085
  setup: normalizeEnvironmentSetupSummary(environment.setup)
15968
16086
  };
15969
16087
  }
16088
+ function isGraphQLMutationDocument(query) {
16089
+ if (typeof query !== "string") return false;
16090
+ const tokens = [];
16091
+ for (let index = 0; index < query.length; ) {
16092
+ const character = query[index];
16093
+ if (/[,\s\ufeff]/.test(character)) {
16094
+ index += 1;
16095
+ continue;
16096
+ }
16097
+ if (character === "#") {
16098
+ while (index < query.length && !/[\r\n]/.test(query[index])) index += 1;
16099
+ continue;
16100
+ }
16101
+ if (character === '"') {
16102
+ const block = query.startsWith('"""', index);
16103
+ let closed = false;
16104
+ index += block ? 3 : 1;
16105
+ while (index < query.length) {
16106
+ if (block && query.startsWith('"""', index) && query[index - 1] !== "\\") {
16107
+ index += 3;
16108
+ closed = true;
16109
+ break;
16110
+ }
16111
+ if (!block && query[index] === '"') {
16112
+ index += 1;
16113
+ closed = true;
16114
+ break;
16115
+ }
16116
+ index += query[index] === "\\" && !block ? 2 : 1;
16117
+ }
16118
+ if (!closed) return true;
16119
+ continue;
16120
+ }
16121
+ const name = query.slice(index).match(/^[_A-Za-z][_0-9A-Za-z]*/)?.[0];
16122
+ if (name) {
16123
+ tokens.push(name);
16124
+ index += name.length;
16125
+ continue;
16126
+ }
16127
+ if ("{}()[]".includes(character)) tokens.push(character);
16128
+ index += 1;
16129
+ }
16130
+ const skipSelection = (start) => {
16131
+ let depth = 0;
16132
+ for (let index = start; index < tokens.length; index += 1) {
16133
+ if (tokens[index] === "{") depth += 1;
16134
+ if (tokens[index] === "}" && --depth === 0) return index + 1;
16135
+ }
16136
+ return null;
16137
+ };
16138
+ for (let index = 0; index < tokens.length; ) {
16139
+ const definition = tokens[index++];
16140
+ if (definition === "mutation") return true;
16141
+ if (definition === "{") {
16142
+ const nextDefinition2 = skipSelection(index - 1);
16143
+ if (nextDefinition2 === null) return true;
16144
+ index = nextDefinition2;
16145
+ continue;
16146
+ }
16147
+ if (!["query", "subscription", "fragment"].includes(definition || "")) {
16148
+ return true;
16149
+ }
16150
+ let parentheses = 0;
16151
+ let brackets = 0;
16152
+ while (index < tokens.length) {
16153
+ const token = tokens[index];
16154
+ if (token === "(") parentheses += 1;
16155
+ else if (token === ")") parentheses -= 1;
16156
+ else if (token === "[") brackets += 1;
16157
+ else if (token === "]") brackets -= 1;
16158
+ else if (token === "{" && parentheses === 0 && brackets === 0) break;
16159
+ index += 1;
16160
+ }
16161
+ if (index === tokens.length) return true;
16162
+ const nextDefinition = skipSelection(index);
16163
+ if (nextDefinition === null) return true;
16164
+ index = nextDefinition;
16165
+ }
16166
+ return false;
16167
+ }
15970
16168
  var Environment = class _Environment {
15971
16169
  granular;
15972
16170
  envData;
15973
16171
  _apiKey;
15974
16172
  _apiEndpoint;
15975
- constructor(granular, envData, apiKey, apiEndpoint) {
16173
+ _tokenProvider;
16174
+ _beforeMutationSend;
16175
+ constructor(granular, envData, apiKey, apiEndpoint, tokenProvider, beforeMutationSend) {
15976
16176
  this.granular = granular;
15977
16177
  this.envData = envData;
15978
16178
  this._apiKey = apiKey;
15979
16179
  this._apiEndpoint = apiEndpoint;
16180
+ this._tokenProvider = tokenProvider;
16181
+ this._beforeMutationSend = beforeMutationSend;
15980
16182
  }
15981
16183
  /** The environment ID */
15982
16184
  get environmentId() {
@@ -16042,6 +16244,15 @@ var Environment = class _Environment {
16042
16244
  get authToken() {
16043
16245
  return this._apiKey;
16044
16246
  }
16247
+ resolveAuthToken() {
16248
+ if (!this._tokenProvider) return this._apiKey;
16249
+ return Promise.resolve(this._tokenProvider()).then((provided) => {
16250
+ if (typeof provided === "string" && provided.trim()) {
16251
+ this._apiKey = provided.trim();
16252
+ }
16253
+ return this._apiKey;
16254
+ });
16255
+ }
16045
16256
  /** Base runtime URL derived from the GraphQL endpoint */
16046
16257
  get runtimeBaseUrl() {
16047
16258
  return this.getRuntimeBaseUrl();
@@ -16433,10 +16644,15 @@ var Environment = class _Environment {
16433
16644
  async controlPlaneRequest(path2, options = {}) {
16434
16645
  const runtimeBase = this.getRuntimeBaseUrl();
16435
16646
  const url = `${runtimeBase}${path2}`;
16647
+ const authToken = await this.resolveAuthToken();
16648
+ const method = (options.method || "GET").toUpperCase();
16649
+ if (method !== "GET" && method !== "HEAD" && method !== "OPTIONS") {
16650
+ this._beforeMutationSend?.();
16651
+ }
16436
16652
  const response = await fetch(url, {
16437
16653
  ...options,
16438
16654
  headers: {
16439
- Authorization: `Bearer ${this._apiKey}`,
16655
+ Authorization: `Bearer ${authToken}`,
16440
16656
  "Content-Type": "application/json",
16441
16657
  ...options.headers
16442
16658
  }
@@ -16501,11 +16717,15 @@ var Environment = class _Environment {
16501
16717
  * ```
16502
16718
  */
16503
16719
  async graphql(query, variables) {
16720
+ const authToken = await this.resolveAuthToken();
16721
+ if (this._beforeMutationSend && isGraphQLMutationDocument(query)) {
16722
+ this._beforeMutationSend?.();
16723
+ }
16504
16724
  const response = await fetch(this._apiEndpoint, {
16505
16725
  method: "POST",
16506
16726
  headers: {
16507
16727
  "Content-Type": "application/json",
16508
- Authorization: `Bearer ${this._apiKey}`
16728
+ Authorization: `Bearer ${authToken}`
16509
16729
  },
16510
16730
  body: JSON.stringify({
16511
16731
  environmentId: this.environmentId,
@@ -16984,8 +17204,10 @@ var Environment = class _Environment {
16984
17204
  eventType.payloadSchema.properties
16985
17205
  )) {
16986
17206
  const schema = propSchema;
17207
+ const format = schema.format === "date" || schema.format === "date-time" ? schema.format : void 0;
16987
17208
  fieldSpecs[propName] = {
16988
17209
  type: schema.type ?? "string",
17210
+ ...format ? { format } : {},
16989
17211
  description: schema.description
16990
17212
  };
16991
17213
  }
@@ -17407,40 +17629,36 @@ var EnvironmentSession = class extends Session {
17407
17629
  environment;
17408
17630
  sessionDataRoutePrefix;
17409
17631
  sessionDataHeaders;
17632
+ beforeMutationSend;
17410
17633
  heartbeatTimer = null;
17411
- heartbeatInFlight = false;
17412
- /** The last known graph container status, updated by checkReadiness() or on heartbeat */
17634
+ /** The last known graph container status, updated by checkReadiness(). */
17413
17635
  graphContainerStatus = null;
17414
17636
  constructor(client, environment, clientId, options = {}) {
17415
17637
  super(client, clientId, { initialQuota: options.initialQuota });
17416
17638
  this.environment = environment;
17417
17639
  this.sessionDataRoutePrefix = options.sessionDataRoutePrefix || "/orchestrator/ws/sessions";
17418
17640
  this.sessionDataHeaders = options.sessionDataHeaders || {};
17641
+ this.beforeMutationSend = options.beforeMutationSend;
17419
17642
  this.setFeedListTransport(
17420
17643
  (feedOptions) => this.sessionDataRequest("/feed", feedOptions)
17421
17644
  );
17422
17645
  }
17423
17646
  /**
17424
17647
  * Keep the browser session transport observable from the client side.
17425
- * A worker/proxy restart can leave a browser WebSocket appearing OPEN even
17426
- * though the server-side Durable Object has already closed its peer. The
17427
- * heartbeat gives the SDK a bounded failure signal so it can revoke that
17428
- * stale socket and use WSClient's normal reconnect path.
17648
+ * SessionDO already broadcasts a heartbeat every five seconds. Treat that
17649
+ * and all other inbound frames as proof of life instead of issuing an RPC:
17650
+ * an application RPC may be delayed by Durable Object work even while the
17651
+ * underlying WebSocket is healthy.
17429
17652
  */
17430
17653
  startHeartbeat() {
17431
17654
  if (this.heartbeatTimer) return;
17432
17655
  this.heartbeatTimer = setInterval(() => {
17433
- if (this.heartbeatInFlight) return;
17434
- this.heartbeatInFlight = true;
17435
- void this.client.call("client.heartbeat", {}).then((result) => {
17436
- if (result?.graphContainerStatus) {
17437
- this.graphContainerStatus = result.graphContainerStatus;
17438
- }
17439
- }).catch((error) => {
17440
- this.client.reportTransportFailure(error);
17441
- }).finally(() => {
17442
- this.heartbeatInFlight = false;
17443
- });
17656
+ if (!this.client.isTransportOpen()) return;
17657
+ if (this.client.hasRecentInboundActivity(3e4)) return;
17658
+ if (this.client.hasRecentlyPendingRpcPeriod(6e4)) return;
17659
+ this.client.reportTransportFailure(
17660
+ new Error("No server activity received for 30 seconds")
17661
+ );
17444
17662
  }, 5e3);
17445
17663
  this.heartbeatTimer.unref?.();
17446
17664
  }
@@ -17449,7 +17667,6 @@ var EnvironmentSession = class extends Session {
17449
17667
  clearInterval(this.heartbeatTimer);
17450
17668
  this.heartbeatTimer = null;
17451
17669
  }
17452
- this.heartbeatInFlight = false;
17453
17670
  }
17454
17671
  async hello() {
17455
17672
  const result = await super.hello();
@@ -17510,7 +17727,13 @@ var EnvironmentSession = class extends Session {
17510
17727
  for (let attempt = 1; attempt <= SESSION_DATA_REQUEST_RETRY_COUNT; attempt += 1) {
17511
17728
  try {
17512
17729
  const headers = new Headers(init2.headers);
17513
- headers.set("Authorization", `Bearer ${this.environment.authToken}`);
17730
+ const resolvedAuthToken = this.environment.resolveAuthToken();
17731
+ const authToken = typeof resolvedAuthToken === "string" ? resolvedAuthToken : await resolvedAuthToken;
17732
+ headers.set("Authorization", `Bearer ${authToken}`);
17733
+ const method = (init2.method || "GET").toUpperCase();
17734
+ if (method !== "GET" && method !== "HEAD" && method !== "OPTIONS") {
17735
+ this.beforeMutationSend?.();
17736
+ }
17514
17737
  const response = await fetch(url, {
17515
17738
  method: init2.method || "GET",
17516
17739
  headers,
@@ -17969,7 +18192,7 @@ var EnvironmentSession = class extends Session {
17969
18192
  method: "POST",
17970
18193
  headers: {
17971
18194
  "Content-Type": "application/json",
17972
- Authorization: `Bearer ${this.environment.authToken}`
18195
+ Authorization: `Bearer ${await this.environment.resolveAuthToken()}`
17973
18196
  },
17974
18197
  body: JSON.stringify({
17975
18198
  reason: "sdk_disconnect_http_fallback",
@@ -18907,6 +19130,10 @@ var Granular = class _Granular {
18907
19130
  });
18908
19131
  }
18909
19132
  async recordOpenAIUsageSpend(usage, context, options) {
19133
+ const providedToken = await this.tokenProvider?.();
19134
+ if (typeof providedToken === "string" && providedToken.trim()) {
19135
+ this.apiKey = providedToken.trim();
19136
+ }
18910
19137
  return recordOpenAIUsageSpend({
18911
19138
  apiUrl: this.apiUrl,
18912
19139
  token: this.apiKey,
@@ -19079,7 +19306,13 @@ var Granular = class _Granular {
19079
19306
  }
19080
19307
  bindEnvironmentHandle(envData) {
19081
19308
  const graphqlEndpoint = `${this.httpUrl}/orchestrator/graphql`;
19082
- return new Environment(this, envData, this.apiKey, graphqlEndpoint);
19309
+ return new Environment(
19310
+ this,
19311
+ envData,
19312
+ this.apiKey,
19313
+ graphqlEndpoint,
19314
+ this.tokenProvider
19315
+ );
19083
19316
  }
19084
19317
  async bindWebSocketEnvironmentSession(environment, clientId, session, transportOptions = {}) {
19085
19318
  const client = new WSClient({
@@ -19963,6 +20196,10 @@ var Granular = class _Granular {
19963
20196
  console.log(`[SDK] Requesting: ${url}`);
19964
20197
  }
19965
20198
  for (let attempt = 1; attempt <= LOCAL_CONTROL_REQUEST_RETRY_COUNT; attempt += 1) {
20199
+ const providedToken = await this.tokenProvider?.();
20200
+ if (typeof providedToken === "string" && providedToken.trim()) {
20201
+ this.apiKey = providedToken.trim();
20202
+ }
19966
20203
  const response = await fetch(url, {
19967
20204
  ...options,
19968
20205
  headers: {
@@ -21894,6 +22131,10 @@ function jsonSchemaTypeName(schema) {
21894
22131
  const record = normalizeJsonSchema(schema);
21895
22132
  if (!record) return "unknown";
21896
22133
  const type = record.type;
22134
+ if (record.format === "date-time") return "date-time";
22135
+ if (record.format === "date" || type === "date" || Array.isArray(type) && type.includes("date")) {
22136
+ return "date";
22137
+ }
21897
22138
  if (typeof type === "string") {
21898
22139
  if (type === "array") return "array";
21899
22140
  if (type === "object") return "object";
@@ -22395,6 +22636,8 @@ ${actionIndex}
22395
22636
  - 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()\`.
22396
22637
  - 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.
22397
22638
  - 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.
22639
+ - 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.
22640
+ - 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.
22398
22641
  - 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.
22399
22642
  - 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.
22400
22643
  - 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.