@granular-software/sdk 0.4.66 → 0.4.68

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",
@@ -19103,6 +19326,10 @@ var Granular = class _Granular {
19103
19326
  });
19104
19327
  }
19105
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
+ }
19106
19333
  return recordOpenAIUsageSpend({
19107
19334
  apiUrl: this.apiUrl,
19108
19335
  token: this.apiKey,
@@ -19275,7 +19502,13 @@ var Granular = class _Granular {
19275
19502
  }
19276
19503
  bindEnvironmentHandle(envData) {
19277
19504
  const graphqlEndpoint = `${this.httpUrl}/orchestrator/graphql`;
19278
- 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
+ );
19279
19512
  }
19280
19513
  async bindWebSocketEnvironmentSession(environment, clientId, session, transportOptions = {}) {
19281
19514
  const client = new WSClient({
@@ -20159,6 +20392,10 @@ var Granular = class _Granular {
20159
20392
  console.log(`[SDK] Requesting: ${url}`);
20160
20393
  }
20161
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
+ }
20162
20399
  const response = await fetch(url, {
20163
20400
  ...options,
20164
20401
  headers: {
@@ -20194,6 +20431,258 @@ var Granular = class _Granular {
20194
20431
  }
20195
20432
  };
20196
20433
 
20434
+ // src/environment-setup.ts
20435
+ var EnvironmentSetupTerminalError = class extends Error {
20436
+ constructor(setup) {
20437
+ super(
20438
+ setup?.errorMessage?.trim() || "Environment setup failed before all records were imported."
20439
+ );
20440
+ this.name = "EnvironmentSetupTerminalError";
20441
+ }
20442
+ };
20443
+ function environmentSetupFailedError(setup) {
20444
+ return new EnvironmentSetupTerminalError(setup);
20445
+ }
20446
+ function environmentSetupAbortError() {
20447
+ const error = new Error("Environment setup polling was aborted.");
20448
+ error.name = "AbortError";
20449
+ return error;
20450
+ }
20451
+ function throwIfAborted(signal) {
20452
+ if (signal.aborted) {
20453
+ throw environmentSetupAbortError();
20454
+ }
20455
+ }
20456
+ function abortableSleep(ms, signal) {
20457
+ return new Promise((resolve, reject) => {
20458
+ if (signal.aborted) {
20459
+ reject(environmentSetupAbortError());
20460
+ return;
20461
+ }
20462
+ const timeout = setTimeout(() => {
20463
+ signal.removeEventListener("abort", abort);
20464
+ resolve();
20465
+ }, ms);
20466
+ const abort = () => {
20467
+ clearTimeout(timeout);
20468
+ signal.removeEventListener("abort", abort);
20469
+ reject(environmentSetupAbortError());
20470
+ };
20471
+ signal.addEventListener("abort", abort, { once: true });
20472
+ });
20473
+ }
20474
+ function setupIsRunning(setup) {
20475
+ return setup?.lifecycleStatus === "running";
20476
+ }
20477
+ function setupHasFailed(setup) {
20478
+ return setup?.lifecycleStatus === "failed";
20479
+ }
20480
+ function hasRecordFailures(setup) {
20481
+ return Boolean(
20482
+ setup && (setup.failedRecords > 0 || setup.canceledRecords > 0)
20483
+ );
20484
+ }
20485
+ function setupHasTerminalLifecycleFailure(setup) {
20486
+ return setupHasFailed(setup);
20487
+ }
20488
+ function importsAreActive(summary) {
20489
+ return Boolean(
20490
+ summary && (summary.awaitingRecords > 0 || summary.activeImports > 0)
20491
+ );
20492
+ }
20493
+ function numericRetryAfter(value) {
20494
+ return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.round(value)) : null;
20495
+ }
20496
+ function environmentSetupTimeoutError(maxDurationMs) {
20497
+ return new Error(`Environment setup timed out after ${maxDurationMs}ms.`);
20498
+ }
20499
+ function environmentSetupProgressFromResponse(environment, response, retryAfterMs) {
20500
+ const readiness = response.readiness;
20501
+ const nextEnvironment = {
20502
+ ...environment,
20503
+ ...response.environment || {}
20504
+ };
20505
+ const explicitReady = typeof readiness?.ready === "boolean" ? readiness.ready : typeof response.ready === "boolean" ? response.ready : void 0;
20506
+ const hasResponseSetup = Object.prototype.hasOwnProperty.call(
20507
+ response,
20508
+ "setup"
20509
+ );
20510
+ const hasReadinessSetup = readiness !== null && typeof readiness === "object" && Object.prototype.hasOwnProperty.call(readiness, "setup");
20511
+ const hasEnvironmentSetup = response.environment !== null && typeof response.environment === "object" && Object.prototype.hasOwnProperty.call(response.environment, "setup");
20512
+ const setup = hasResponseSetup ? response.setup ?? null : hasReadinessSetup ? readiness?.setup ?? null : hasEnvironmentSetup ? response.environment?.setup ?? null : explicitReady === true ? null : nextEnvironment.setup ?? null;
20513
+ nextEnvironment.setup = setup;
20514
+ const summary = response.summary ?? readiness?.summary ?? null;
20515
+ const retry = [
20516
+ retryAfterMs,
20517
+ response.retryAfterMs,
20518
+ readiness?.retryAfterMs
20519
+ ].find((value) => typeof value === "number" && Number.isFinite(value));
20520
+ const recordFailures = hasRecordFailures(setup) || hasRecordFailures(summary);
20521
+ return {
20522
+ environment: nextEnvironment,
20523
+ setup,
20524
+ summary,
20525
+ ready: explicitReady !== false && !setupIsRunning(setup) && !setupHasFailed(setup) && (explicitReady === true || !recordFailures) && !importsAreActive(summary),
20526
+ retryAfterMs: numericRetryAfter(retry)
20527
+ };
20528
+ }
20529
+ function environmentSetupProgressFromError(environment, error) {
20530
+ if (!error || typeof error !== "object") return null;
20531
+ const candidate = error;
20532
+ if (candidate.status !== 503 && candidate.status !== 202 || typeof candidate.body !== "string") {
20533
+ return null;
20534
+ }
20535
+ try {
20536
+ const response = JSON.parse(candidate.body);
20537
+ const explicitReady = response?.readiness?.ready ?? (typeof response?.ready === "boolean" ? response.ready : void 0);
20538
+ if (candidate.status === 202) {
20539
+ const progress = environmentSetupProgressFromResponse(
20540
+ environment,
20541
+ response,
20542
+ numericRetryAfter(candidate.retryAfterMs)
20543
+ );
20544
+ return explicitReady === void 0 ? { ...progress, ready: false } : progress;
20545
+ }
20546
+ return explicitReady === false ? environmentSetupProgressFromResponse(
20547
+ environment,
20548
+ response,
20549
+ numericRetryAfter(candidate.retryAfterMs)
20550
+ ) : null;
20551
+ } catch {
20552
+ return null;
20553
+ }
20554
+ }
20555
+ function defaultTransientError(error) {
20556
+ if (!error) return false;
20557
+ const status = typeof error === "object" ? error.status : void 0;
20558
+ if (typeof status === "number") {
20559
+ return status === 408 || status === 425 || status === 429 || status >= 500;
20560
+ }
20561
+ const message = error instanceof Error ? error.message : String(error);
20562
+ return /failed to fetch|network error|network connection lost|connection (?:reset|closed|refused)|econnreset|socket hang up|socket connection was closed unexpectedly|dns|timed out/i.test(
20563
+ message
20564
+ );
20565
+ }
20566
+ async function pollEnvironmentSetup(input) {
20567
+ const maxAttempts = Math.max(1, input.maxAttempts ?? 3);
20568
+ const maxDurationMs = Math.max(1, input.maxDurationMs ?? 12e4);
20569
+ const sleep2 = input.sleep || ((ms, signal2) => abortableSleep(ms, signal2 || new AbortController().signal));
20570
+ const now = input.now || (() => Date.now());
20571
+ const isTransientError = input.isTransientError || defaultTransientError;
20572
+ const internalController = input.signal ? null : new AbortController();
20573
+ const signal = input.signal || internalController.signal;
20574
+ const startedAt = now();
20575
+ let currentEnvironment = input.environment;
20576
+ let transientFailures = 0;
20577
+ let attempt = 0;
20578
+ const waitForNextAttempt = async (requestedDelayMs) => {
20579
+ throwIfAborted(signal);
20580
+ const remainingMs = maxDurationMs - Math.max(0, now() - startedAt);
20581
+ if (remainingMs <= 0) {
20582
+ throw environmentSetupTimeoutError(maxDurationMs);
20583
+ }
20584
+ await sleep2(Math.min(Math.max(0, requestedDelayMs), remainingMs), signal);
20585
+ throwIfAborted(signal);
20586
+ };
20587
+ const requestWithinDeadline = async (requestAttempt) => {
20588
+ throwIfAborted(signal);
20589
+ const remainingMs = maxDurationMs - Math.max(0, now() - startedAt);
20590
+ if (remainingMs <= 0) {
20591
+ throw environmentSetupTimeoutError(maxDurationMs);
20592
+ }
20593
+ const requestController = new AbortController();
20594
+ let timeoutHandle = null;
20595
+ let abortHandler = null;
20596
+ const requestPromise = Promise.resolve().then(
20597
+ () => input.request(requestAttempt, requestController.signal)
20598
+ );
20599
+ const timeoutPromise = new Promise((_, reject) => {
20600
+ if (!Number.isFinite(remainingMs)) return;
20601
+ timeoutHandle = setTimeout(
20602
+ () => {
20603
+ reject(environmentSetupTimeoutError(maxDurationMs));
20604
+ requestController.abort();
20605
+ },
20606
+ Math.max(1, Math.ceil(remainingMs))
20607
+ );
20608
+ });
20609
+ const abortPromise = new Promise((_, reject) => {
20610
+ abortHandler = () => {
20611
+ requestController.abort();
20612
+ reject(environmentSetupAbortError());
20613
+ };
20614
+ signal.addEventListener("abort", abortHandler, { once: true });
20615
+ });
20616
+ try {
20617
+ return await Promise.race([requestPromise, timeoutPromise, abortPromise]);
20618
+ } finally {
20619
+ if (timeoutHandle !== null) {
20620
+ clearTimeout(timeoutHandle);
20621
+ }
20622
+ if (abortHandler !== null) {
20623
+ signal.removeEventListener("abort", abortHandler);
20624
+ }
20625
+ }
20626
+ };
20627
+ while (true) {
20628
+ throwIfAborted(signal);
20629
+ if (now() - startedAt >= maxDurationMs) {
20630
+ throw environmentSetupTimeoutError(maxDurationMs);
20631
+ }
20632
+ attempt += 1;
20633
+ try {
20634
+ const progress = environmentSetupProgressFromResponse(
20635
+ currentEnvironment,
20636
+ await requestWithinDeadline(attempt)
20637
+ );
20638
+ throwIfAborted(signal);
20639
+ currentEnvironment = progress.environment;
20640
+ input.onProgress?.(progress);
20641
+ if (setupHasTerminalLifecycleFailure(progress.setup) && !progress.ready) {
20642
+ throw environmentSetupFailedError(progress.setup);
20643
+ }
20644
+ if (progress.ready) return progress.environment;
20645
+ transientFailures = 0;
20646
+ await waitForNextAttempt(
20647
+ progress.retryAfterMs ?? input.pollIntervalMs ?? 1e3
20648
+ );
20649
+ } catch (error) {
20650
+ throwIfAborted(signal);
20651
+ if (error instanceof EnvironmentSetupTerminalError) {
20652
+ throw error;
20653
+ }
20654
+ const pending = environmentSetupProgressFromError(
20655
+ currentEnvironment,
20656
+ error
20657
+ );
20658
+ if (pending) {
20659
+ currentEnvironment = pending.environment;
20660
+ input.onProgress?.(pending);
20661
+ if (setupHasTerminalLifecycleFailure(pending.setup) && !pending.ready) {
20662
+ throw environmentSetupFailedError(pending.setup);
20663
+ }
20664
+ if (pending.ready) return pending.environment;
20665
+ transientFailures = 0;
20666
+ await waitForNextAttempt(
20667
+ pending.retryAfterMs ?? input.pollIntervalMs ?? 1e3
20668
+ );
20669
+ continue;
20670
+ }
20671
+ if (!isTransientError(error) || transientFailures + 1 >= maxAttempts) {
20672
+ throw error;
20673
+ }
20674
+ transientFailures += 1;
20675
+ input.onRetry?.({ attempt: transientFailures, error });
20676
+ const retryAfterMs = numericRetryAfter(
20677
+ error && typeof error === "object" ? error.retryAfterMs : null
20678
+ );
20679
+ await waitForNextAttempt(
20680
+ retryAfterMs ?? (input.retryDelayMs ?? 1500) * transientFailures
20681
+ );
20682
+ }
20683
+ }
20684
+ }
20685
+
20197
20686
  // src/record-snapshot-projection.ts
20198
20687
  function stableValue(value) {
20199
20688
  if (Array.isArray(value)) return value.map(stableValue);
@@ -22370,6 +22859,10 @@ function jsonSchemaTypeName(schema) {
22370
22859
  const record = normalizeJsonSchema(schema);
22371
22860
  if (!record) return "unknown";
22372
22861
  const type = record.type;
22862
+ if (record.format === "date-time") return "date-time";
22863
+ if (record.format === "date" || type === "date" || Array.isArray(type) && type.includes("date")) {
22864
+ return "date";
22865
+ }
22373
22866
  if (typeof type === "string") {
22374
22867
  if (type === "array") return "array";
22375
22868
  if (type === "object") return "object";
@@ -22871,6 +23364,8 @@ ${actionIndex}
22871
23364
  - 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()\`.
22872
23365
  - 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.
22873
23366
  - 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.
23367
+ - 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.
23368
+ - 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.
22874
23369
  - 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.
22875
23370
  - 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.
22876
23371
  - 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.
@@ -23512,6 +24007,8 @@ exports.defineStateMachine = defineStateMachine;
23512
24007
  exports.emitFeedDiagnostic = emitFeedDiagnostic;
23513
24008
  exports.emitFeedDiagnosticToDefaultSink = emitFeedDiagnosticToDefaultSink;
23514
24009
  exports.emptyFeedSnapshot = emptyFeedSnapshot;
24010
+ exports.environmentSetupProgressFromError = environmentSetupProgressFromError;
24011
+ exports.environmentSetupProgressFromResponse = environmentSetupProgressFromResponse;
23515
24012
  exports.evaluateContinuation = evaluateContinuation;
23516
24013
  exports.evaluateValidationRule = evaluateValidationRule;
23517
24014
  exports.extractPromptTokens = extractPromptTokens;
@@ -23524,6 +24021,7 @@ exports.hasOpenPrompt = hasOpenPrompt;
23524
24021
  exports.hashHarnessTemplateValue = hashHarnessTemplateValue;
23525
24022
  exports.invokeRegisteredEffect = invokeRegisteredEffect;
23526
24023
  exports.isCanonicalSessionFeedDocument = isCanonicalSessionFeedDocument;
24024
+ exports.isGraphQLMutationDocument = isGraphQLMutationDocument;
23527
24025
  exports.isLocalApiUrl = isLocalApiUrl;
23528
24026
  exports.listHarnessTemplates = listHarnessTemplates;
23529
24027
  exports.mergeFeedItemsBySequence = mergeFeedItemsBySequence;
@@ -23537,6 +24035,7 @@ exports.normalizePromptChoiceOption = normalizePromptChoiceOption;
23537
24035
  exports.normalizePromptText = normalizePromptText;
23538
24036
  exports.normalizePromptType = normalizePromptType;
23539
24037
  exports.orderTransientFeedItems = orderTransientFeedItems;
24038
+ exports.pollEnvironmentSetup = pollEnvironmentSetup;
23540
24039
  exports.projectConversationReferentFocus = projectConversationReferentFocus;
23541
24040
  exports.projectConversationReferentSummary = projectConversationReferentSummary;
23542
24041
  exports.projectHeapSummary = projectHeapSummary;