@granular-software/sdk 0.4.37 → 0.4.39

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/cli/index.js CHANGED
@@ -18991,7 +18991,10 @@ var WSClient = class {
18991
18991
  if (!expiresAt) {
18992
18992
  return;
18993
18993
  }
18994
- const refreshInMs = Math.max(1e3, expiresAt - Date.now() - TOKEN_REFRESH_LEEWAY_MS);
18994
+ const refreshInMs = Math.max(
18995
+ 1e3,
18996
+ expiresAt - Date.now() - TOKEN_REFRESH_LEEWAY_MS
18997
+ );
18995
18998
  const delay = Math.min(refreshInMs, MAX_TIMER_DELAY_MS);
18996
18999
  this.tokenRefreshTimer = setTimeout(() => {
18997
19000
  void this.refreshTokenInBackground();
@@ -19037,7 +19040,10 @@ var WSClient = class {
19037
19040
  return refreshedToken;
19038
19041
  } catch (error2) {
19039
19042
  if (expiresAt > Date.now()) {
19040
- console.warn("[Granular] Token refresh failed, using current token:", error2);
19043
+ console.warn(
19044
+ "[Granular] Token refresh failed, using current token:",
19045
+ error2
19046
+ );
19041
19047
  return this.token;
19042
19048
  }
19043
19049
  throw error2;
@@ -19064,7 +19070,9 @@ var WSClient = class {
19064
19070
  }
19065
19071
  }
19066
19072
  if (!WebSocketClass) {
19067
- throw new Error('No WebSocket implementation found. If using Node.js, please install "ws" and pass the constructor to the SDK options: { WebSocketCtor: WebSocket }.');
19073
+ throw new Error(
19074
+ 'No WebSocket implementation found. If using Node.js, please install "ws" and pass the constructor to the SDK options: { WebSocketCtor: WebSocket }.'
19075
+ );
19068
19076
  }
19069
19077
  return new Promise((resolve2, reject) => {
19070
19078
  try {
@@ -19196,7 +19204,10 @@ var WSClient = class {
19196
19204
  try {
19197
19205
  this.options.onUnexpectedClose(info2);
19198
19206
  } catch (callbackError) {
19199
- console.error("[Granular] onUnexpectedClose callback failed:", callbackError);
19207
+ console.error(
19208
+ "[Granular] onUnexpectedClose callback failed:",
19209
+ callbackError
19210
+ );
19200
19211
  }
19201
19212
  }
19202
19213
  this.reconnectTimer = setTimeout(() => {
@@ -19213,7 +19224,10 @@ var WSClient = class {
19213
19224
  try {
19214
19225
  this.options.onReconnectError(reconnectInfo);
19215
19226
  } catch (callbackError) {
19216
- console.error("[Granular] onReconnectError callback failed:", callbackError);
19227
+ console.error(
19228
+ "[Granular] onReconnectError callback failed:",
19229
+ callbackError
19230
+ );
19217
19231
  }
19218
19232
  }
19219
19233
  });
@@ -19222,7 +19236,10 @@ var WSClient = class {
19222
19236
  }
19223
19237
  handleMessage(message) {
19224
19238
  if (typeof message !== "object" || message === null) return;
19225
- debugWs("[Granular DEBUG] Received message:", JSON.stringify(message).slice(0, 500));
19239
+ debugWs(
19240
+ "[Granular DEBUG] Received message:",
19241
+ JSON.stringify(message).slice(0, 500)
19242
+ );
19226
19243
  if ("type" in message && message.type === "sync") {
19227
19244
  const syncMessage = message;
19228
19245
  let bytes;
@@ -19252,21 +19269,39 @@ var WSClient = class {
19252
19269
  this.syncState = newSyncState;
19253
19270
  const docAny = this.doc;
19254
19271
  if (docAny.catalog) {
19255
- debugWs("[Granular DEBUG] Doc catalog sync applied. Keys in catalog:", Object.keys(docAny.catalog || {}));
19256
- debugWs("[Granular DEBUG] RawToolCatalogs:", Object.keys(docAny.catalog.rawToolCatalogs || {}));
19272
+ debugWs(
19273
+ "[Granular DEBUG] Doc catalog sync applied. Keys in catalog:",
19274
+ Object.keys(docAny.catalog || {})
19275
+ );
19276
+ debugWs(
19277
+ "[Granular DEBUG] RawToolCatalogs:",
19278
+ Object.keys(docAny.catalog.rawToolCatalogs || {})
19279
+ );
19257
19280
  } else {
19258
- debugWs("[Granular DEBUG] Doc synced but no catalog yet. Keys in doc:", Object.keys(docAny));
19281
+ debugWs(
19282
+ "[Granular DEBUG] Doc synced but no catalog yet. Keys in doc:",
19283
+ Object.keys(docAny)
19284
+ );
19259
19285
  }
19260
19286
  this.emit("sync", this.doc);
19261
19287
  } catch (e) {
19262
19288
  try {
19263
- debugWs("[Granular DEBUG] receiveSyncMessage failed, trying applyChanges...");
19289
+ debugWs(
19290
+ "[Granular DEBUG] receiveSyncMessage failed, trying applyChanges..."
19291
+ );
19264
19292
  const [newDoc] = Automerge__namespace.applyChanges(this.doc, [bytes]);
19265
19293
  this.doc = newDoc;
19266
19294
  this.emit("sync", this.doc);
19267
- debugWs("[Granular DEBUG] applyChanges succeeded. Doc:", JSON.stringify(Automerge__namespace.toJS(this.doc)));
19295
+ debugWs(
19296
+ "[Granular DEBUG] applyChanges succeeded. Doc:",
19297
+ JSON.stringify(Automerge__namespace.toJS(this.doc))
19298
+ );
19268
19299
  } catch (applyError) {
19269
- console.warn("[Granular] Failed to apply sync message (both sync & applyChanges)", e, applyError);
19300
+ console.warn(
19301
+ "[Granular] Failed to apply sync message (both sync & applyChanges)",
19302
+ e,
19303
+ applyError
19304
+ );
19270
19305
  }
19271
19306
  }
19272
19307
  return;
@@ -19275,10 +19310,16 @@ var WSClient = class {
19275
19310
  const snapshotMessage = message;
19276
19311
  try {
19277
19312
  const bytes = new Uint8Array(snapshotMessage.data);
19278
- debugWs("[Granular DEBUG] Loading Automerge session snapshot bytes:", bytes.length);
19313
+ debugWs(
19314
+ "[Granular DEBUG] Loading Automerge session snapshot bytes:",
19315
+ bytes.length
19316
+ );
19279
19317
  this.doc = Automerge__namespace.load(bytes);
19280
19318
  this.emit("sync", this.doc);
19281
- debugWs("[Granular DEBUG] Automerge session snapshot loaded. Doc:", JSON.stringify(Automerge__namespace.toJS(this.doc)));
19319
+ debugWs(
19320
+ "[Granular DEBUG] Automerge session snapshot loaded. Doc:",
19321
+ JSON.stringify(Automerge__namespace.toJS(this.doc))
19322
+ );
19282
19323
  } catch (e) {
19283
19324
  console.warn("[Granular] Failed to load snapshot message", e);
19284
19325
  }
@@ -19290,6 +19331,7 @@ var WSClient = class {
19290
19331
  const bytes = new Uint8Array(changeMessage.data);
19291
19332
  const [newDoc] = Automerge__namespace.applyChanges(this.doc, [bytes]);
19292
19333
  this.doc = newDoc;
19334
+ this.emit("change", changeMessage);
19293
19335
  this.emit("sync", this.doc);
19294
19336
  } catch (e) {
19295
19337
  console.warn("[Granular] Failed to apply change message", e);
@@ -19302,12 +19344,16 @@ var WSClient = class {
19302
19344
  if (pending) {
19303
19345
  if (response.type === "rpc_error") {
19304
19346
  pending.reject(
19305
- new Error(`RPC error: ${response.error?.message || "Unknown error"}`)
19347
+ new Error(
19348
+ `RPC error: ${response.error?.message || "Unknown error"}`
19349
+ )
19306
19350
  );
19307
19351
  } else {
19308
19352
  pending.resolve(response.result);
19309
19353
  }
19310
- this.messageQueue = this.messageQueue.filter((q) => q.id !== response.id);
19354
+ this.messageQueue = this.messageQueue.filter(
19355
+ (q) => q.id !== response.id
19356
+ );
19311
19357
  }
19312
19358
  return;
19313
19359
  }
@@ -19590,6 +19636,7 @@ function withPromptTranscriptTimeout(promise) {
19590
19636
  var Session = class {
19591
19637
  client;
19592
19638
  clientId;
19639
+ initialQuota;
19593
19640
  jobsMap = /* @__PURE__ */ new Map();
19594
19641
  pendingAgentMessagesByJobId = /* @__PURE__ */ new Map();
19595
19642
  eventListeners = /* @__PURE__ */ new Map();
@@ -19605,9 +19652,10 @@ var Session = class {
19605
19652
  promptCache = /* @__PURE__ */ new Map();
19606
19653
  /** Prompt ids locally answered before the document sync catches up. */
19607
19654
  hiddenPromptIds = /* @__PURE__ */ new Set();
19608
- constructor(client, clientId) {
19655
+ constructor(client, clientId, options = {}) {
19609
19656
  this.client = client;
19610
19657
  this.clientId = clientId || `client_${Date.now()}`;
19658
+ this.initialQuota = options.initialQuota || null;
19611
19659
  this.setupEventHandlers();
19612
19660
  this.setupToolInvokeHandler();
19613
19661
  }
@@ -19656,6 +19704,16 @@ var Session = class {
19656
19704
  get document() {
19657
19705
  return this.client.doc;
19658
19706
  }
19707
+ get quota() {
19708
+ return this.getQuota();
19709
+ }
19710
+ getQuota() {
19711
+ const quota = this.client.doc.billing?.quota;
19712
+ if (quota && typeof quota === "object") {
19713
+ return quota;
19714
+ }
19715
+ return this.initialQuota;
19716
+ }
19659
19717
  get sessionId() {
19660
19718
  return this.client.currentSessionId;
19661
19719
  }
@@ -21084,9 +21142,10 @@ function normalizeShowRefs(value) {
21084
21142
  const show = {
21085
21143
  entryPaths: normalizeRefs(record.entryPaths),
21086
21144
  listNames: normalizeRefs(record.listNames),
21087
- variableNames: normalizeRefs(record.variableNames)
21145
+ variableNames: normalizeRefs(record.variableNames),
21146
+ fileIds: normalizeRefs(record.fileIds)
21088
21147
  };
21089
- return show.entryPaths || show.listNames || show.variableNames ? show : void 0;
21148
+ return show.entryPaths || show.listNames || show.variableNames || show.fileIds ? show : void 0;
21090
21149
  }
21091
21150
  function stringifyTranscriptValue(value, fallback2 = "") {
21092
21151
  if (typeof value === "string") {
@@ -21294,7 +21353,10 @@ function buildJobCodeEntry(jobId, job2) {
21294
21353
  jobId,
21295
21354
  code,
21296
21355
  jobStatus,
21297
- jobResultPreview: stringifyTranscriptValue(job2.result, "No job result recorded."),
21356
+ jobResultPreview: stringifyTranscriptValue(
21357
+ job2.result,
21358
+ "No job result recorded."
21359
+ ),
21298
21360
  error: error2,
21299
21361
  source: "job_code"
21300
21362
  };
@@ -21303,12 +21365,16 @@ function buildSessionTranscript(input) {
21303
21365
  const liveDoc = input.liveDoc || null;
21304
21366
  const sessionHeap = input.sessionHeap || EMPTY_HEAP;
21305
21367
  const transcript = [];
21306
- const conversationMessages = asArray(asRecord3(liveDoc?.conversation)?.messages).map((message) => normalizeConversationMessage(message)).filter((message) => Boolean(message));
21368
+ const conversationMessages = asArray(
21369
+ asRecord3(liveDoc?.conversation)?.messages
21370
+ ).map((message) => normalizeConversationMessage(message)).filter((message) => Boolean(message));
21307
21371
  const conversationPromptIds = new Set(
21308
21372
  conversationMessages.map((message) => message.promptId).filter((promptId) => Boolean(promptId))
21309
21373
  );
21310
21374
  const assistantConversationJobIds = new Set(
21311
- conversationMessages.filter((message) => message.role === "assistant" && Boolean(message.jobId)).map((message) => message.jobId)
21375
+ conversationMessages.filter(
21376
+ (message) => message.role === "assistant" && Boolean(message.jobId)
21377
+ ).map((message) => message.jobId)
21312
21378
  );
21313
21379
  transcript.push(...conversationMessages);
21314
21380
  const jobsById = asRecord3(asRecord3(liveDoc?.jobs)?.byId) || {};
@@ -21326,7 +21392,10 @@ function buildSessionTranscript(input) {
21326
21392
  ...normalizePromptEntries(jobId, job2.prompts, conversationPromptIds)
21327
21393
  );
21328
21394
  if (!assistantConversationJobIds.has(jobId)) {
21329
- const agentEntries = normalizeAgentMessageEntries(jobId, job2.agentMessages);
21395
+ const agentEntries = normalizeAgentMessageEntries(
21396
+ jobId,
21397
+ job2.agentMessages
21398
+ );
21330
21399
  if (agentEntries.length > 0) {
21331
21400
  transcript.push(...agentEntries);
21332
21401
  } else {
@@ -21511,6 +21580,110 @@ async function invokeRegisteredEffect(effectMap, request) {
21511
21580
  return resolved.handler(request.input, context);
21512
21581
  }
21513
21582
 
21583
+ // src/spend.ts
21584
+ function toGranularHttpBase(apiUrl) {
21585
+ const url = new URL(apiUrl);
21586
+ if (url.protocol === "ws:") {
21587
+ url.protocol = "http:";
21588
+ } else if (url.protocol === "wss:") {
21589
+ url.protocol = "https:";
21590
+ }
21591
+ url.pathname = url.pathname.replace(/\/ws\/connect$/, "").replace(/\/ws$/, "");
21592
+ if (!url.pathname || url.pathname === "/") {
21593
+ url.pathname = "/granular";
21594
+ }
21595
+ url.search = "";
21596
+ url.hash = "";
21597
+ return url.toString().replace(/\/$/, "");
21598
+ }
21599
+ function cleanIdPart(value) {
21600
+ return value.replace(/[^a-zA-Z0-9_-]+/g, "_").replace(/^_+|_+$/g, "");
21601
+ }
21602
+ function buildOpenAISpendEventId(usage, context = {}) {
21603
+ const requestId = usage.requestId?.trim();
21604
+ if (!requestId) return void 0;
21605
+ const scope = context.sessionId || context.environmentId || context.subjectId || context.sandboxId || "global";
21606
+ return ["spend", "openai", scope, requestId].map(cleanIdPart).join("_");
21607
+ }
21608
+ function pricingEffectiveAtSeconds(value) {
21609
+ if (!value) return null;
21610
+ const parsed = Date.parse(value);
21611
+ return Number.isFinite(parsed) ? Math.floor(parsed / 1e3) : null;
21612
+ }
21613
+ function compactContext(context) {
21614
+ return Object.fromEntries(
21615
+ Object.entries(context).filter(
21616
+ ([, value]) => value != null && value !== ""
21617
+ )
21618
+ );
21619
+ }
21620
+ function omitTenantId(context) {
21621
+ const scopedContext = { ...context };
21622
+ delete scopedContext.tenantId;
21623
+ return scopedContext;
21624
+ }
21625
+ async function recordOpenAIUsageSpend(options) {
21626
+ const usageContext = compactContext({
21627
+ ...options.usage.usageContext || {},
21628
+ ...options.context || {}
21629
+ });
21630
+ const context = omitTenantId(usageContext);
21631
+ const spendEventId = options.usage.spendEventId || buildOpenAISpendEventId(options.usage, context);
21632
+ const metadata = {
21633
+ ...options.metadata || {},
21634
+ ...options.usage.rawUsage !== void 0 ? { openaiUsage: options.usage.rawUsage } : {},
21635
+ usageContext: context
21636
+ };
21637
+ const response = await fetch(
21638
+ `${toGranularHttpBase(options.apiUrl)}/control/spend/events`,
21639
+ {
21640
+ method: "POST",
21641
+ cache: "no-store",
21642
+ headers: {
21643
+ Authorization: `Bearer ${options.token}`,
21644
+ "Content-Type": "application/json"
21645
+ },
21646
+ body: JSON.stringify({
21647
+ ...spendEventId ? { spendEventId } : {},
21648
+ sandboxId: context.sandboxId || null,
21649
+ environmentId: context.environmentId || null,
21650
+ sessionId: context.sessionId || null,
21651
+ subjectId: context.subjectId || null,
21652
+ permissionProfileId: context.permissionProfileId || null,
21653
+ source: "openai",
21654
+ lineItemType: "llm_tokens",
21655
+ provider: options.usage.provider,
21656
+ model: options.usage.model,
21657
+ operation: options.usage.operation || "chat.completions",
21658
+ requestId: options.usage.requestId || null,
21659
+ inputTokens: options.usage.inputTokens,
21660
+ outputTokens: options.usage.outputTokens,
21661
+ cachedInputTokens: options.usage.cachedInputTokens,
21662
+ reasoningTokens: options.usage.reasoningTokens,
21663
+ quantity: options.usage.totalTokens,
21664
+ quantityUnit: "tokens",
21665
+ inputPricePerMillionMicros: options.usage.inputPricePerMillionMicros,
21666
+ cachedInputPricePerMillionMicros: options.usage.cachedInputPricePerMillionMicros,
21667
+ outputPricePerMillionMicros: options.usage.outputPricePerMillionMicros,
21668
+ amountMicros: options.usage.amountMicros,
21669
+ currency: options.usage.currency,
21670
+ pricingSource: options.usage.pricingSource,
21671
+ pricingEffectiveAt: pricingEffectiveAtSeconds(
21672
+ options.usage.pricingEffectiveAt
21673
+ ),
21674
+ estimated: false,
21675
+ metadata
21676
+ })
21677
+ }
21678
+ );
21679
+ if (!response.ok) {
21680
+ throw new Error(
21681
+ `Granular spend event failed (${response.status}): ${await response.text()}`
21682
+ );
21683
+ }
21684
+ return response.json();
21685
+ }
21686
+
21514
21687
  // src/manifest-metamodels.ts
21515
21688
  function buildFieldMetamodelMutations(fieldPath, spec) {
21516
21689
  return DEFAULT_METAMODEL_PACKAGES.flatMap(
@@ -21575,6 +21748,25 @@ var LOCAL_CONTROL_REQUEST_RETRY_DELAY_MS = 500;
21575
21748
  var SESSION_DATA_REQUEST_RETRY_COUNT = 4;
21576
21749
  var SESSION_DATA_REQUEST_RETRY_DELAY_MS = 500;
21577
21750
  var EFFECT_HOST_CONNECT_TIMEOUT_MS = 15e3;
21751
+ function filenameFromUploadBody(body) {
21752
+ const maybe = body;
21753
+ return typeof maybe.name === "string" && maybe.name.trim() ? maybe.name.trim() : null;
21754
+ }
21755
+ function contentTypeFromUploadBody(body) {
21756
+ const maybe = body;
21757
+ return typeof maybe.type === "string" && maybe.type.trim() ? maybe.type.trim() : null;
21758
+ }
21759
+ function bodyInitFromSessionFileUpload(body) {
21760
+ if (typeof body === "string") return body;
21761
+ if (body instanceof ArrayBuffer) return body;
21762
+ if (ArrayBuffer.isView(body)) {
21763
+ return body.buffer.slice(
21764
+ body.byteOffset,
21765
+ body.byteOffset + body.byteLength
21766
+ );
21767
+ }
21768
+ return body;
21769
+ }
21578
21770
  var EFFECT_CATALOG_SYNC_TIMEOUT_MS = 3e4;
21579
21771
  var EFFECT_CATALOG_SYNC_RETRY_COUNT = 3;
21580
21772
  var EFFECT_CATALOG_SYNC_RETRY_DELAY_MS = 1e3;
@@ -23012,7 +23204,7 @@ var EnvironmentSession = class extends Session {
23012
23204
  /** The last known graph container status, updated by checkReadiness() or on heartbeat */
23013
23205
  graphContainerStatus = null;
23014
23206
  constructor(client, environment, clientId, options = {}) {
23015
- super(client, clientId);
23207
+ super(client, clientId, { initialQuota: options.initialQuota });
23016
23208
  this.environment = environment;
23017
23209
  this.sessionDataRoutePrefix = options.sessionDataRoutePrefix || "/orchestrator/ws/sessions";
23018
23210
  }
@@ -23059,7 +23251,7 @@ var EnvironmentSession = class extends Session {
23059
23251
  const doc = this.document;
23060
23252
  return normalizeHeapSnapshot(doc?.heap);
23061
23253
  }
23062
- async sessionDataRequest(path7, query, init2 = {}) {
23254
+ buildSessionDataUrl(path7, query) {
23063
23255
  const searchParams = new URLSearchParams();
23064
23256
  for (const [key, value] of Object.entries(query || {})) {
23065
23257
  if (value !== null && typeof value !== "undefined" && value !== "") {
@@ -23067,20 +23259,21 @@ var EnvironmentSession = class extends Session {
23067
23259
  }
23068
23260
  }
23069
23261
  const queryString = searchParams.toString();
23070
- const url = `${this.environment.runtimeBaseUrl}${this.sessionDataRoutePrefix}/${encodeURIComponent(this.sessionId)}${path7}${queryString ? `?${queryString}` : ""}`;
23071
- const body = typeof init2.body === "undefined" ? void 0 : JSON.stringify(init2.body);
23262
+ return `${this.environment.runtimeBaseUrl}${this.sessionDataRoutePrefix}/${encodeURIComponent(this.sessionId)}${path7}${queryString ? `?${queryString}` : ""}`;
23263
+ }
23264
+ async sessionDataFetch(path7, query, init2 = {}) {
23265
+ const url = this.buildSessionDataUrl(path7, query);
23072
23266
  for (let attempt = 1; attempt <= SESSION_DATA_REQUEST_RETRY_COUNT; attempt += 1) {
23073
23267
  try {
23268
+ const headers = new Headers(init2.headers);
23269
+ headers.set("Authorization", `Bearer ${this.environment.authToken}`);
23074
23270
  const response = await fetch(url, {
23075
23271
  method: init2.method || "GET",
23076
- headers: {
23077
- Authorization: `Bearer ${this.environment.authToken}`,
23078
- "Content-Type": "application/json"
23079
- },
23080
- ...typeof body === "undefined" ? {} : { body }
23272
+ headers,
23273
+ ...typeof init2.body === "undefined" ? {} : { body: init2.body }
23081
23274
  });
23082
23275
  if (response.ok) {
23083
- return response.json();
23276
+ return response;
23084
23277
  }
23085
23278
  const errorText = await response.text();
23086
23279
  const error2 = new Error(
@@ -23101,6 +23294,15 @@ var EnvironmentSession = class extends Session {
23101
23294
  }
23102
23295
  throw new Error(`Session data API Error: exhausted retries for ${url}`);
23103
23296
  }
23297
+ async sessionDataRequest(path7, query, init2 = {}) {
23298
+ const body = typeof init2.body === "undefined" ? void 0 : JSON.stringify(init2.body);
23299
+ const response = await this.sessionDataFetch(path7, query, {
23300
+ method: init2.method || "GET",
23301
+ headers: { "Content-Type": "application/json" },
23302
+ ...typeof body === "undefined" ? {} : { body }
23303
+ });
23304
+ return response.json();
23305
+ }
23104
23306
  async collectAllSessionItems(listPage) {
23105
23307
  const items = [];
23106
23308
  let cursor = null;
@@ -23141,6 +23343,53 @@ var EnvironmentSession = class extends Session {
23141
23343
  )
23142
23344
  };
23143
23345
  }
23346
+ get files() {
23347
+ return {
23348
+ list: (options = {}) => this.sessionDataRequest(
23349
+ "/files",
23350
+ options
23351
+ ),
23352
+ get: (fileId) => this.sessionDataRequest(
23353
+ `/files/${encodeURIComponent(fileId)}`
23354
+ ),
23355
+ upload: async (body, options = {}) => {
23356
+ const headers = new Headers({
23357
+ "Content-Type": options.contentType || contentTypeFromUploadBody(body) || "application/octet-stream",
23358
+ "x-granular-filename": options.filename || filenameFromUploadBody(body) || "upload",
23359
+ "x-granular-file-source": options.source || "sdk"
23360
+ });
23361
+ if (options.parentFileIds?.length) {
23362
+ headers.set(
23363
+ "x-granular-parent-file-ids",
23364
+ JSON.stringify(options.parentFileIds)
23365
+ );
23366
+ }
23367
+ if (options.metadata) {
23368
+ headers.set(
23369
+ "x-granular-file-metadata",
23370
+ JSON.stringify(options.metadata)
23371
+ );
23372
+ }
23373
+ const response = await this.sessionDataFetch("/files", void 0, {
23374
+ method: "POST",
23375
+ headers,
23376
+ body: bodyInitFromSessionFileUpload(body)
23377
+ });
23378
+ return response.json();
23379
+ },
23380
+ download: async (fileId) => {
23381
+ const response = await this.sessionDataFetch(
23382
+ `/files/${encodeURIComponent(fileId)}/content`
23383
+ );
23384
+ return response.arrayBuffer();
23385
+ },
23386
+ delete: (fileId) => this.sessionDataRequest(
23387
+ `/files/${encodeURIComponent(fileId)}`,
23388
+ void 0,
23389
+ { method: "DELETE" }
23390
+ )
23391
+ };
23392
+ }
23144
23393
  get heap() {
23145
23394
  return {
23146
23395
  entries: {
@@ -23811,6 +24060,15 @@ var Granular = class _Granular {
23811
24060
  const environment = this.bindEnvironmentHandle(envData);
23812
24061
  return this.bindWebSocketEnvironmentSession(environment, clientId, minted);
23813
24062
  }
24063
+ async recordOpenAIUsageSpend(usage, context, options) {
24064
+ return recordOpenAIUsageSpend({
24065
+ apiUrl: this.apiUrl,
24066
+ token: this.apiKey,
24067
+ usage,
24068
+ context,
24069
+ metadata: options?.metadata
24070
+ });
24071
+ }
23814
24072
  /**
23815
24073
  * Mark a session closed in the control plane. If `environment` is the connected handle for that
23816
24074
  * `sessionId`, disconnects the WebSocket so the runtime tears down cleanly.
@@ -23965,7 +24223,8 @@ var Granular = class _Granular {
23965
24223
  const environmentSession = new EnvironmentSession(
23966
24224
  client,
23967
24225
  environment,
23968
- clientId
24226
+ clientId,
24227
+ { initialQuota: session2.quota || null }
23969
24228
  );
23970
24229
  await environmentSession.hello();
23971
24230
  return environmentSession;