@ingram-cloud/sdk 1.4.0 → 1.6.0

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.
Files changed (51) hide show
  1. package/README.md +34 -35
  2. package/dist/client.js +240 -23
  3. package/dist/index.js +4 -0
  4. package/dist/scopes.js +52 -0
  5. package/dist/zod/_actor.js +33 -0
  6. package/dist/zod/_page.js +18 -4
  7. package/dist/zod/agents.js +7 -3
  8. package/dist/zod/approvals.js +9 -0
  9. package/dist/zod/billing.js +136 -0
  10. package/dist/zod/budgets.js +2 -3
  11. package/dist/zod/connections.js +2 -3
  12. package/dist/zod/conversations.js +4 -11
  13. package/dist/zod/deployments.js +32 -0
  14. package/dist/zod/files.js +2 -9
  15. package/dist/zod/index.js +3 -0
  16. package/dist/zod/mcp.js +8 -10
  17. package/dist/zod/observability.js +38 -19
  18. package/dist/zod/projects.js +4 -4
  19. package/dist/zod/runs.js +19 -4
  20. package/dist/zod/schedules.js +2 -7
  21. package/dist/zod/skills.js +73 -0
  22. package/dist/zod/smith-revisions.js +2 -3
  23. package/dist/zod/smiths.js +6 -0
  24. package/dist/zod/tenant.js +24 -4
  25. package/dist/zod/vector-stores.js +6 -19
  26. package/package.json +25 -18
  27. package/ts/client.ts +456 -60
  28. package/ts/index.ts +4 -0
  29. package/ts/responses.ts +40 -2
  30. package/ts/scopes.ts +57 -0
  31. package/ts/zod/_actor.ts +36 -0
  32. package/ts/zod/_page.ts +19 -4
  33. package/ts/zod/agents.ts +7 -3
  34. package/ts/zod/approvals.ts +9 -0
  35. package/ts/zod/billing.ts +168 -0
  36. package/ts/zod/budgets.ts +2 -3
  37. package/ts/zod/connections.ts +2 -3
  38. package/ts/zod/conversations.ts +7 -11
  39. package/ts/zod/deployments.ts +36 -0
  40. package/ts/zod/files.ts +2 -9
  41. package/ts/zod/index.ts +3 -0
  42. package/ts/zod/mcp.ts +8 -11
  43. package/ts/zod/observability.ts +74 -24
  44. package/ts/zod/projects.ts +4 -4
  45. package/ts/zod/runs.ts +21 -4
  46. package/ts/zod/schedules.ts +2 -7
  47. package/ts/zod/skills.ts +85 -0
  48. package/ts/zod/smith-revisions.ts +2 -3
  49. package/ts/zod/smiths.ts +6 -0
  50. package/ts/zod/tenant.ts +33 -5
  51. package/ts/zod/vector-stores.ts +9 -19
package/ts/client.ts CHANGED
@@ -30,6 +30,8 @@ import type {
30
30
  ICAgentVersion,
31
31
  ICApproval,
32
32
  ICAuthorizeRequest,
33
+ ICAutoreload,
34
+ ICBalance,
33
35
  ICBudget,
34
36
  ICBudgetStatus,
35
37
  ICCatalogEntry,
@@ -41,13 +43,18 @@ import type {
41
43
  ICDeploymentCreated,
42
44
  ICDiscordApp,
43
45
  ICEmailConfig,
46
+ ICEmbeddingList,
44
47
  ICEvent,
45
48
  ICFile,
46
49
  ICFileList,
50
+ ICInboundEvent,
51
+ ICLedgerEntry,
47
52
  ICMcpServer,
48
53
  ICMintedToken,
49
54
  ICModelCatalog,
50
55
  ICModelKey,
56
+ ICOrgUsage,
57
+ ICOrgUsageSeries,
51
58
  ICProject,
52
59
  ICProvider,
53
60
  ICRecallHit,
@@ -57,6 +64,7 @@ import type {
57
64
  ICSlackApp,
58
65
  ICSmith,
59
66
  ICSmithRevision,
67
+ ICSpanIn,
60
68
  ICTelegramBot,
61
69
  ICToken,
62
70
  ICTrace,
@@ -83,6 +91,20 @@ import type {
83
91
  RolloutIn,
84
92
  UiResourceIn,
85
93
  } from "./zod/agents.js";
94
+ import type {
95
+ AutoreloadIn,
96
+ CheckoutIn,
97
+ CheckoutOut,
98
+ ConfirmIn,
99
+ ConfirmOut,
100
+ PortalOut,
101
+ RedeemIn,
102
+ RedeemOut,
103
+ ReloadIn,
104
+ ReloadOut,
105
+ SetupIn,
106
+ SetupOut,
107
+ } from "./zod/billing.js";
86
108
  import type { BudgetIn, BudgetPatch } from "./zod/budgets.js";
87
109
  import type {
88
110
  AuthorizeIn,
@@ -95,10 +117,11 @@ import type { CustomerCreate, CustomerPatch } from "./zod/customers.js";
95
117
  import type { DeploymentIn, DeploymentPatch } from "./zod/deployments.js";
96
118
  import type { DiscordAppIn } from "./zod/discord.js";
97
119
  import type { EmailConfigIn } from "./zod/email.js";
98
- import type { McpServerIn, McpServerWriteOut } from "./zod/mcp.js";
120
+ import type { McpServerIn } from "./zod/mcp.js";
99
121
  import type { RecallBody, WorkingMemorySet } from "./zod/memories.js";
100
122
  import type { ProjectIn, ProjectTokenIn, ProjectTokenOut } from "./zod/projects.js";
101
123
  import type { RunIn, Submit } from "./zod/runs.js";
124
+ import type { Skill, SkillUpdateIn, SkillVersion } from "./zod/skills.js";
102
125
  import type {
103
126
  VectorStoreFileBatchIn,
104
127
  VectorStoreFileIn,
@@ -143,6 +166,16 @@ export interface ICPage<T> {
143
166
  has_more: boolean;
144
167
  }
145
168
 
169
+ /** An OpenAI-dialect list page (`object:"list"` — files, vector stores,
170
+ * conversations). Page forward by passing `last_id` back as `after`. */
171
+ export interface ICOaiList<T> {
172
+ object: "list";
173
+ data: T[];
174
+ first_id: string | null;
175
+ last_id: string | null;
176
+ has_more: boolean;
177
+ }
178
+
146
179
  /** A non-2xx `/v1` response: HTTP status + the error envelope's `code`.
147
180
  * `message` carries the full context (method, path, status); `detail` is the
148
181
  * envelope's bare `error.message`, suitable for user-facing copy. */
@@ -205,6 +238,61 @@ export interface PageOpts {
205
238
 
206
239
  const enc = encodeURIComponent;
207
240
 
241
+ /** Attempts per request, including the first. Small on purpose: the server
242
+ * tells us when to come back, so this is a bound on pathological cases, not
243
+ * a backoff strategy. */
244
+ const MAX_ATTEMPTS = 4;
245
+
246
+ /** Longest we will sit out one `Retry-After`. A server (or an intermediary that
247
+ * never heard of this API) can name an hour; a client library must not silently
248
+ * block a caller for one. Past this we stop retrying and surface the refusal, so
249
+ * the caller decides. */
250
+ const MAX_WAIT_MS = 60_000;
251
+
252
+ /**
253
+ * How long this response says to wait, or null if it does not say — which is
254
+ * itself the answer: a 402 carries no `Retry-After` because the wallet will not
255
+ * refill because we asked twice.
256
+ *
257
+ * RFC 9110 allows both forms, and intermediaries do send the date one, so parse
258
+ * both. Anything unparseable is "no usable instruction", never a zero-delay
259
+ * hammer at an upstream that is already struggling.
260
+ */
261
+ function retryAfterMs(res: Response, now: number): number | null {
262
+ const raw = res.headers.get("retry-after")?.trim();
263
+ if (!raw) return null;
264
+ const seconds = Number(raw);
265
+ const ms = Number.isFinite(seconds) ? seconds * 1000 : Date.parse(raw) - now;
266
+ if (!Number.isFinite(ms)) return null;
267
+ return Math.max(0, ms);
268
+ }
269
+
270
+ /** Retry only what retrying can fix, and only when told how long to wait. */
271
+ function retryDelay(res: Response, now: number): number | null {
272
+ if (res.status !== 429 && res.status !== 503) return null;
273
+ const ms = retryAfterMs(res, now);
274
+ return ms === null || ms > MAX_WAIT_MS ? null : ms;
275
+ }
276
+
277
+ /** The `Retry-After` wait, abortable: a caller cancelling mid-wait should not
278
+ * sit out the rest of a Retry-After that can be tens of seconds — reject as
279
+ * soon as `signal` fires, the same way an aborted `transport()` call would. */
280
+ function sleep(ms: number, signal: AbortSignal | undefined): Promise<void> {
281
+ if (!signal) return new Promise((resolve) => setTimeout(resolve, ms));
282
+ if (signal.aborted) return Promise.reject(signal.reason ?? new Error("aborted"));
283
+ return new Promise((resolve, reject) => {
284
+ const timer = setTimeout(() => {
285
+ signal.removeEventListener("abort", onAbort);
286
+ resolve();
287
+ }, ms);
288
+ const onAbort = () => {
289
+ clearTimeout(timer);
290
+ reject(signal.reason ?? new Error("aborted"));
291
+ };
292
+ signal.addEventListener("abort", onAbort, { once: true });
293
+ });
294
+ }
295
+
208
296
  function qs(query: Query | undefined): string {
209
297
  if (!query) return "";
210
298
  const p = new URLSearchParams();
@@ -216,6 +304,66 @@ function qs(query: Query | undefined): string {
216
304
  return s ? `?${s}` : "";
217
305
  }
218
306
 
307
+ /** One file of a bundle, as the caller holds it. The path is relative to — and
308
+ * includes — the skill's root directory, e.g. `invoice-review/SKILL.md`. */
309
+ export interface SkillFileInput {
310
+ path: string;
311
+ content: string | Blob | Uint8Array;
312
+ }
313
+
314
+ /** A bundle to upload: its files, or the whole thing zipped. */
315
+ export type SkillBundle = SkillFileInput[] | Blob;
316
+
317
+ /** Build the multipart body `/v1/skills` accepts.
318
+ *
319
+ * The path rides the part's *filename*, slashes and all — that is how the
320
+ * bundle's directory structure survives a multipart body. `FormData` in Node,
321
+ * Bun and browsers all pass it through verbatim. */
322
+ function bundleForm(bundle: SkillBundle): FormData {
323
+ const form = new FormData();
324
+ if (bundle instanceof Blob) {
325
+ form.append("file", bundle, "bundle.zip");
326
+ return form;
327
+ }
328
+ for (const file of bundle) {
329
+ // A `Uint8Array`'s buffer type is generic (and may be a `SharedArrayBuffer`),
330
+ // which `Blob`'s constructor does not accept — copy into a fresh one, whose
331
+ // buffer is always a plain `ArrayBuffer`.
332
+ const part =
333
+ typeof file.content === "string" || file.content instanceof Blob
334
+ ? file.content
335
+ : new Uint8Array(file.content);
336
+ const blob =
337
+ part instanceof Blob
338
+ ? part
339
+ : new Blob([part], { type: mediaTypeFor(file.path) });
340
+ form.append("files[]", blob, file.path);
341
+ }
342
+ return form;
343
+ }
344
+
345
+ /** A `Blob` built from a string has no type of its own; the server falls back to
346
+ * `application/octet-stream` when a part carries none, which would make every
347
+ * text file non-indexable. Name the common ones from the extension. */
348
+ function mediaTypeFor(path: string): string {
349
+ const ext = path.slice(path.lastIndexOf(".") + 1).toLowerCase();
350
+ const known: Record<string, string> = {
351
+ md: "text/markdown",
352
+ markdown: "text/markdown",
353
+ txt: "text/plain",
354
+ json: "application/json",
355
+ yaml: "text/yaml",
356
+ yml: "text/yaml",
357
+ csv: "text/csv",
358
+ py: "text/x-python",
359
+ sh: "text/x-shellscript",
360
+ js: "text/javascript",
361
+ ts: "text/typescript",
362
+ html: "text/html",
363
+ };
364
+ return known[ext] ?? "application/octet-stream";
365
+ }
366
+
219
367
  export class IngramCloud {
220
368
  private readonly token: IngramCloudOptions["token"];
221
369
  private readonly base: string;
@@ -254,15 +402,22 @@ export class IngramCloud {
254
402
  ...(opts.smith ? { "ic-smith-id": opts.smith } : {}),
255
403
  ...opts.headers,
256
404
  };
257
- const res = await this.transport(`${this.base}/v1${path}${qs(opts.query)}`, {
258
- ...this.requestInit,
259
- method,
260
- headers,
261
- body:
262
- opts.rawBody ??
263
- (opts.body !== undefined ? JSON.stringify(opts.body) : undefined),
264
- signal: opts.signal,
265
- });
405
+ let res!: Response;
406
+ for (let attempt = 1; ; attempt++) {
407
+ res = await this.transport(`${this.base}/v1${path}${qs(opts.query)}`, {
408
+ ...this.requestInit,
409
+ method,
410
+ headers,
411
+ body:
412
+ opts.rawBody ??
413
+ (opts.body !== undefined ? JSON.stringify(opts.body) : undefined),
414
+ signal: opts.signal,
415
+ });
416
+ if (res.ok || attempt >= MAX_ATTEMPTS) break;
417
+ const wait = retryDelay(res, Date.now());
418
+ if (wait === null) break;
419
+ await sleep(wait, opts.signal);
420
+ }
266
421
  if (!res.ok) {
267
422
  const body = await res.text().catch(() => "");
268
423
  let code = `http_${res.status}`;
@@ -374,10 +529,10 @@ export class IngramCloud {
374
529
  },
375
530
 
376
531
  revisions: {
377
- list: (pid: string, opts?: RequestOptions) =>
378
- this.data<ICSmithRevision>(
379
- "GET",
532
+ list: (pid: string, query?: PageOpts, opts?: RequestOptions) =>
533
+ this.page<ICSmithRevision>(
380
534
  `/smiths/${enc(pid)}/revisions`,
535
+ query,
381
536
  opts,
382
537
  ),
383
538
  restore: (
@@ -397,8 +552,12 @@ export class IngramCloud {
397
552
  },
398
553
 
399
554
  connections: {
400
- list: (pid: string, opts?: RequestOptions) =>
401
- this.data<ICConnection>("GET", `/smiths/${enc(pid)}/connections`, opts),
555
+ list: (
556
+ pid: string,
557
+ query?: PageOpts & { provider?: string },
558
+ opts?: RequestOptions,
559
+ ) =>
560
+ this.page<ICConnection>(`/smiths/${enc(pid)}/connections`, query, opts),
402
561
  get: (pid: string, cid: string, opts?: RequestOptions) =>
403
562
  this.json<ICConnection>(
404
563
  "GET",
@@ -478,8 +637,8 @@ export class IngramCloud {
478
637
  },
479
638
 
480
639
  schedules: {
481
- list: (pid: string, opts?: RequestOptions) =>
482
- this.data<ICSchedule>("GET", `/smiths/${enc(pid)}/schedules`, opts),
640
+ list: (pid: string, query?: PageOpts, opts?: RequestOptions) =>
641
+ this.page<ICSchedule>(`/smiths/${enc(pid)}/schedules`, query, opts),
483
642
  create: (
484
643
  pid: string,
485
644
  body: z.input<typeof ScheduleIn>,
@@ -605,6 +764,8 @@ export class IngramCloud {
605
764
  },
606
765
  opts?: RequestOptions,
607
766
  ) => this.page<ICRun>("/runs", query, opts),
767
+ trace: (rid: string, opts?: RequestOptions) =>
768
+ this.json<ICTraceDetail>("GET", `/runs/${enc(rid)}/trace`, opts),
608
769
  };
609
770
 
610
771
  // ── Agents ──────────────────────────────────────────────────────────────
@@ -625,8 +786,8 @@ export class IngramCloud {
625
786
  this.empty("DELETE", `/agents/${enc(aid)}`, opts),
626
787
 
627
788
  versions: {
628
- list: (aid: string, opts?: RequestOptions) =>
629
- this.data<ICAgentVersion>("GET", `/agents/${enc(aid)}/versions`, opts),
789
+ list: (aid: string, query?: PageOpts, opts?: RequestOptions) =>
790
+ this.page<ICAgentVersion>(`/agents/${enc(aid)}/versions`, query, opts),
630
791
  /** Snapshot the draft as the next immutable version. */
631
792
  publish: (
632
793
  aid: string,
@@ -664,7 +825,11 @@ export class IngramCloud {
664
825
  list: (aid: string, opts?: RequestOptions) =>
665
826
  this.data<ICUiResource>("GET", `/agents/${enc(aid)}/ui`, opts),
666
827
  get: (aid: string, name: string, opts?: RequestOptions) =>
667
- this.json<ICUiResource>("GET", `/agents/${enc(aid)}/ui/${enc(name)}`, opts),
828
+ this.json<ICUiResource>(
829
+ "GET",
830
+ `/agents/${enc(aid)}/ui/${enc(name)}`,
831
+ opts,
832
+ ),
668
833
  /** Upload/replace a template — `html` is the bundle, `meta` its
669
834
  * `{ name, csp?, permissions?, tool? }` sidecar. Replaces by name. */
670
835
  put: (
@@ -676,7 +841,9 @@ export class IngramCloud {
676
841
  const form = new FormData();
677
842
  form.append(
678
843
  "file",
679
- html instanceof Blob ? html : new Blob([html], { type: "text/html" }),
844
+ html instanceof Blob
845
+ ? html
846
+ : new Blob([html], { type: "text/html" }),
680
847
  `${meta.name}.html`,
681
848
  );
682
849
  form.append("metadata", JSON.stringify(meta));
@@ -694,19 +861,22 @@ export class IngramCloud {
694
861
  headers: { accept: "text/html", ...opts?.headers },
695
862
  }).then((r) => r.text()),
696
863
  delete: (aid: string, name: string, opts?: RequestOptions) =>
697
- this.json<{ name: string; deleted: boolean }>(
698
- "DELETE",
699
- `/agents/${enc(aid)}/ui/${enc(name)}`,
700
- opts,
701
- ),
864
+ this.empty("DELETE", `/agents/${enc(aid)}/ui/${enc(name)}`, opts),
702
865
  },
703
866
  };
704
867
 
705
868
  // ── Conversations (smith-scoped: pass `{ smith }` with a tenant token) ──
706
869
 
707
870
  readonly conversations = {
708
- list: (query?: PageOpts, opts?: RequestOptions) =>
709
- this.page<ICConversation>("/conversations", query, opts),
871
+ /** OpenAI `list` envelope; page forward with `after: page.last_id`. */
872
+ list: (
873
+ query?: { limit?: number; order?: "asc" | "desc"; after?: string },
874
+ opts?: RequestOptions,
875
+ ) =>
876
+ this.json<ICOaiList<ICConversation>>("GET", "/conversations", {
877
+ ...opts,
878
+ query,
879
+ }),
710
880
  create: (
711
881
  body: z.input<typeof ConversationCreate> = {},
712
882
  opts?: RequestOptions,
@@ -753,6 +923,18 @@ export class IngramCloud {
753
923
  ) => this.page<ICEvent>("/events", query, opts),
754
924
  };
755
925
 
926
+ /** What arrived, before anything interpreted it — including arrivals that
927
+ * matched no smith (`smith_id: ""`). The `iev_` ids `deployment.inbound`
928
+ * carries resolve here. */
929
+ readonly inboundEvents = {
930
+ list: (
931
+ query?: PageOpts & { source?: string; smith_id?: string; since?: string },
932
+ opts?: RequestOptions,
933
+ ) => this.page<ICInboundEvent>("/inbound_events", query, opts),
934
+ get: (ievId: string, opts?: RequestOptions) =>
935
+ this.json<ICInboundEvent>("GET", `/inbound_events/${enc(ievId)}`, opts),
936
+ };
937
+
756
938
  // ── Customers / budgets ─────────────────────────────────────────────────
757
939
 
758
940
  readonly customers = {
@@ -773,7 +955,8 @@ export class IngramCloud {
773
955
  };
774
956
 
775
957
  readonly budgets = {
776
- list: (opts?: RequestOptions) => this.data<ICBudget>("GET", "/budgets", opts),
958
+ list: (query?: PageOpts, opts?: RequestOptions) =>
959
+ this.page<ICBudget>("/budgets", query, opts),
777
960
  create: (body: z.input<typeof BudgetIn>, opts?: RequestOptions) =>
778
961
  this.json<ICBudget>("POST", "/budgets", { ...opts, body }),
779
962
  get: (bid: string, opts?: RequestOptions) =>
@@ -822,6 +1005,18 @@ export class IngramCloud {
822
1005
  this.json<ICCatalogEntry>("GET", `/catalog/${enc(slug)}`, opts),
823
1006
  };
824
1007
 
1008
+ // ── Embeddings ──────────────────────────────────────────────────────────
1009
+
1010
+ /** Embed one string or a batch on the OpenAI-compatible wire. Pure tenant
1011
+ * compute — no smith runs. Omit `model` for the project default. Reach for
1012
+ * the `openai` SDK instead if you already hold one; this is the same route. */
1013
+ readonly embeddings = {
1014
+ create: (
1015
+ body: { input: string | string[]; model?: string },
1016
+ opts?: RequestOptions,
1017
+ ) => this.json<ICEmbeddingList>("POST", "/embeddings", { ...opts, body }),
1018
+ };
1019
+
825
1020
  // ── Observability ───────────────────────────────────────────────────────
826
1021
 
827
1022
  readonly traces = {
@@ -836,6 +1031,15 @@ export class IngramCloud {
836
1031
  ) => this.page<ICTrace>("/traces", query, opts),
837
1032
  get: (traceId: string, opts?: RequestOptions) =>
838
1033
  this.json<ICTraceDetail>("GET", `/traces/${enc(traceId)}`, opts),
1034
+ /** Push spans from your own runtime or an OTel exporter. The tenant comes
1035
+ * from the token; a smith-scoped token may only attribute to its own smith.
1036
+ * Unknown `kind`s land as `runtime_event` rather than erroring. Returns the
1037
+ * number written. */
1038
+ ingest: (spans: ICSpanIn[], opts?: RequestOptions) =>
1039
+ this.json<{ accepted: number }>("POST", "/traces:ingest", {
1040
+ ...opts,
1041
+ body: { spans },
1042
+ }),
839
1043
  };
840
1044
 
841
1045
  readonly usage = {
@@ -885,7 +1089,12 @@ export class IngramCloud {
885
1089
  },
886
1090
  /** Uploads only (OpenAI `list` envelope); inline files stay unlisted. */
887
1091
  list: (
888
- query?: { purpose?: string; after?: string; limit?: number; order?: "asc" | "desc" },
1092
+ query?: {
1093
+ purpose?: string;
1094
+ after?: string;
1095
+ limit?: number;
1096
+ order?: "asc" | "desc";
1097
+ },
889
1098
  opts?: RequestOptions,
890
1099
  ) => this.json<ICFileList>("GET", "/files", { ...opts, query }),
891
1100
  get: (id: string, opts?: RequestOptions) =>
@@ -894,7 +1103,11 @@ export class IngramCloud {
894
1103
  content: (id: string, opts?: RequestOptions) =>
895
1104
  this.request("GET", `/files/${enc(id)}/content`, opts),
896
1105
  delete: (id: string, opts?: RequestOptions) =>
897
- this.json<{ id: string; deleted: true }>("DELETE", `/files/${enc(id)}`, opts),
1106
+ this.json<{ id: string; deleted: true }>(
1107
+ "DELETE",
1108
+ `/files/${enc(id)}`,
1109
+ opts,
1110
+ ),
898
1111
  };
899
1112
 
900
1113
  // ── Vector stores (the OpenAI Vector Stores API) ─────────────────────────
@@ -903,14 +1116,18 @@ export class IngramCloud {
903
1116
  create: (body: z.input<typeof VectorStoreIn>, opts?: RequestOptions) =>
904
1117
  this.json<ICVectorStore>("POST", "/vector_stores", { ...opts, body }),
905
1118
  list: (
906
- query?: { limit?: number; order?: "asc" | "desc"; after?: string; before?: string },
1119
+ query?: {
1120
+ limit?: number;
1121
+ order?: "asc" | "desc";
1122
+ after?: string;
1123
+ before?: string;
1124
+ },
907
1125
  opts?: RequestOptions,
908
1126
  ) =>
909
- this.json<{ data: ICVectorStore[]; first_id: string | null; last_id: string | null; has_more: boolean }>(
910
- "GET",
911
- "/vector_stores",
912
- { ...opts, query },
913
- ),
1127
+ this.json<ICOaiList<ICVectorStore>>("GET", "/vector_stores", {
1128
+ ...opts,
1129
+ query,
1130
+ }),
914
1131
  get: (vsId: string, opts?: RequestOptions) =>
915
1132
  this.json<ICVectorStore>("GET", `/vector_stores/${enc(vsId)}`, opts),
916
1133
  /** Modify (OpenAI uses `POST`, not `PATCH`). */
@@ -918,7 +1135,11 @@ export class IngramCloud {
918
1135
  vsId: string,
919
1136
  body: z.input<typeof VectorStorePatch>,
920
1137
  opts?: RequestOptions,
921
- ) => this.json<ICVectorStore>("POST", `/vector_stores/${enc(vsId)}`, { ...opts, body }),
1138
+ ) =>
1139
+ this.json<ICVectorStore>("POST", `/vector_stores/${enc(vsId)}`, {
1140
+ ...opts,
1141
+ body,
1142
+ }),
922
1143
  delete: (vsId: string, opts?: RequestOptions) =>
923
1144
  this.json<{ id: string; deleted: true }>(
924
1145
  "DELETE",
@@ -930,10 +1151,14 @@ export class IngramCloud {
930
1151
  body: z.input<typeof VectorStoreSearchIn>,
931
1152
  opts?: RequestOptions,
932
1153
  ) =>
933
- this.json<ICVectorStoreSearchPage>("POST", `/vector_stores/${enc(vsId)}/search`, {
934
- ...opts,
935
- body,
936
- }),
1154
+ this.json<ICVectorStoreSearchPage>(
1155
+ "POST",
1156
+ `/vector_stores/${enc(vsId)}/search`,
1157
+ {
1158
+ ...opts,
1159
+ body,
1160
+ },
1161
+ ),
937
1162
 
938
1163
  files: {
939
1164
  create: (
@@ -941,10 +1166,14 @@ export class IngramCloud {
941
1166
  body: z.input<typeof VectorStoreFileIn>,
942
1167
  opts?: RequestOptions,
943
1168
  ) =>
944
- this.json<ICVectorStoreFile>("POST", `/vector_stores/${enc(vsId)}/files`, {
945
- ...opts,
946
- body,
947
- }),
1169
+ this.json<ICVectorStoreFile>(
1170
+ "POST",
1171
+ `/vector_stores/${enc(vsId)}/files`,
1172
+ {
1173
+ ...opts,
1174
+ body,
1175
+ },
1176
+ ),
948
1177
  list: (
949
1178
  vsId: string,
950
1179
  query?: {
@@ -956,7 +1185,7 @@ export class IngramCloud {
956
1185
  },
957
1186
  opts?: RequestOptions,
958
1187
  ) =>
959
- this.json<{ data: ICVectorStoreFile[]; first_id: string | null; last_id: string | null; has_more: boolean }>(
1188
+ this.json<ICOaiList<ICVectorStoreFile>>(
960
1189
  "GET",
961
1190
  `/vector_stores/${enc(vsId)}/files`,
962
1191
  { ...opts, query },
@@ -1012,7 +1241,12 @@ export class IngramCloud {
1012
1241
  files: (
1013
1242
  vsId: string,
1014
1243
  batchId: string,
1015
- query?: { limit?: number; order?: "asc" | "desc"; after?: string; filter?: string },
1244
+ query?: {
1245
+ limit?: number;
1246
+ order?: "asc" | "desc";
1247
+ after?: string;
1248
+ filter?: string;
1249
+ },
1016
1250
  opts?: RequestOptions,
1017
1251
  ) =>
1018
1252
  this.json<{ data: ICVectorStoreFile[]; has_more: boolean }>(
@@ -1023,6 +1257,81 @@ export class IngramCloud {
1023
1257
  },
1024
1258
  };
1025
1259
 
1260
+ // ── Agent Skills — a folder anchored by SKILL.md, versioned, attached to
1261
+ // agents. Upload takes either the bundle's files as path/content pairs, or
1262
+ // the whole bundle as a zip Blob — the same two shapes /v1/skills accepts.
1263
+ // Both are runtime-agnostic: nothing here touches a filesystem. ───────────
1264
+
1265
+ readonly skills = {
1266
+ list: (opts?: RequestOptions) =>
1267
+ this.json<{ object: "list"; data: z.infer<typeof Skill>[] }>(
1268
+ "GET",
1269
+ "/skills",
1270
+ opts,
1271
+ ),
1272
+ get: (id: string, opts?: RequestOptions) =>
1273
+ this.json<z.infer<typeof Skill>>("GET", `/skills/${enc(id)}`, opts),
1274
+ create: (bundle: SkillBundle, opts?: RequestOptions) =>
1275
+ this.json<z.infer<typeof Skill>>("POST", "/skills", {
1276
+ ...opts,
1277
+ rawBody: bundleForm(bundle),
1278
+ }),
1279
+ /** Move `default_version` to an existing version. */
1280
+ update: (
1281
+ id: string,
1282
+ body: z.input<typeof SkillUpdateIn>,
1283
+ opts?: RequestOptions,
1284
+ ) =>
1285
+ this.json<z.infer<typeof Skill>>("POST", `/skills/${enc(id)}`, {
1286
+ ...opts,
1287
+ body,
1288
+ }),
1289
+ delete: (id: string, opts?: RequestOptions) =>
1290
+ this.request("DELETE", `/skills/${enc(id)}`, opts).then(() => undefined),
1291
+ versions: {
1292
+ list: (id: string, opts?: RequestOptions) =>
1293
+ this.json<{ object: "list"; data: z.infer<typeof SkillVersion>[] }>(
1294
+ "GET",
1295
+ `/skills/${enc(id)}/versions`,
1296
+ opts,
1297
+ ),
1298
+ get: (id: string, version: number, opts?: RequestOptions) =>
1299
+ this.json<z.infer<typeof SkillVersion>>(
1300
+ "GET",
1301
+ `/skills/${enc(id)}/versions/${version}`,
1302
+ opts,
1303
+ ),
1304
+ create: (id: string, bundle: SkillBundle, opts?: RequestOptions) =>
1305
+ this.json<z.infer<typeof SkillVersion>>(
1306
+ "POST",
1307
+ `/skills/${enc(id)}/versions`,
1308
+ { ...opts, rawBody: bundleForm(bundle) },
1309
+ ),
1310
+ delete: (id: string, version: number, opts?: RequestOptions) =>
1311
+ this.request(
1312
+ "DELETE",
1313
+ `/skills/${enc(id)}/versions/${version}`,
1314
+ opts,
1315
+ ).then(() => undefined),
1316
+ /** One file's bytes, or — with no `path` — the whole version as a zip.
1317
+ * The raw `Response` (matching `files.content`), so a caller streams it
1318
+ * through rather than buffering the whole zip into memory. */
1319
+ content: (
1320
+ id: string,
1321
+ version: number,
1322
+ path?: string,
1323
+ opts?: RequestOptions,
1324
+ ) =>
1325
+ this.request(
1326
+ "GET",
1327
+ `/skills/${enc(id)}/versions/${version}/content${
1328
+ path ? `?path=${encodeURIComponent(path)}` : ""
1329
+ }`,
1330
+ opts,
1331
+ ),
1332
+ },
1333
+ };
1334
+
1026
1335
  // ── Tenant config ───────────────────────────────────────────────────────
1027
1336
 
1028
1337
  readonly tenant = {
@@ -1150,8 +1459,8 @@ export class IngramCloud {
1150
1459
  },
1151
1460
 
1152
1461
  mcp: {
1153
- list: (opts?: RequestOptions) =>
1154
- this.data<ICMcpServer>("GET", "/tenant/mcp", opts),
1462
+ list: (query?: PageOpts, opts?: RequestOptions) =>
1463
+ this.page<ICMcpServer>("/tenant/mcp", query, opts),
1155
1464
  get: (name: string, opts?: RequestOptions) =>
1156
1465
  this.json<ICMcpServer>("GET", `/tenant/mcp/${enc(name)}`, opts),
1157
1466
  /** Register or replace a server (full replace; probes `tools/list`). */
@@ -1160,13 +1469,12 @@ export class IngramCloud {
1160
1469
  body: z.input<typeof McpServerIn>,
1161
1470
  opts?: RequestOptions,
1162
1471
  ) =>
1163
- this.json<z.infer<typeof McpServerWriteOut>>(
1164
- "PUT",
1165
- `/tenant/mcp/${enc(name)}`,
1166
- { ...opts, body },
1167
- ),
1472
+ this.json<ICMcpServer>("PUT", `/tenant/mcp/${enc(name)}`, {
1473
+ ...opts,
1474
+ body,
1475
+ }),
1168
1476
  refresh: (name: string, opts?: RequestOptions) =>
1169
- this.json<z.infer<typeof McpServerWriteOut>>(
1477
+ this.json<ICMcpServer>(
1170
1478
  "POST",
1171
1479
  `/tenant/mcp/${enc(name)}/refresh`,
1172
1480
  opts,
@@ -1256,13 +1564,101 @@ export class IngramCloud {
1256
1564
  },
1257
1565
  };
1258
1566
 
1259
- // ── Organization (org token: projects + tokens) ─────────────────────────
1260
- // Note: the `/v1/organization/billing/*` surface is not yet wrapped here.
1567
+ // ── Organization (org token: projects, tokens, billing) ─────────────────
1261
1568
 
1262
1569
  readonly organization = {
1570
+ /** Platform credits — the org wallet that funds every project's runs.
1571
+ * Amounts are integer minor units of the wallet's `currency`. */
1572
+ billing: {
1573
+ balance: (opts?: RequestOptions) =>
1574
+ this.json<ICBalance>("GET", "/organization/billing/balance", opts),
1575
+ /** Money in (top-ups, grants, codes) and out (usage debits), newest first. */
1576
+ ledger: (
1577
+ query?: PageOpts & { direction?: "credit" | "debit" },
1578
+ opts?: RequestOptions,
1579
+ ) => this.page<ICLedgerEntry>("/organization/billing/ledger", query, opts),
1580
+ /** Per-project draw for a calendar month (`period`, `YYYY-MM`). */
1581
+ usage: (query?: { period?: string }, opts?: RequestOptions) =>
1582
+ this.json<ICOrgUsage>("GET", "/organization/billing/usage", {
1583
+ ...opts,
1584
+ query,
1585
+ }),
1586
+ /** Daily per-project draw over a rolling window of `days`. */
1587
+ usageSeries: (query?: { days?: number }, opts?: RequestOptions) =>
1588
+ this.json<ICOrgUsageSeries>(
1589
+ "GET",
1590
+ "/organization/billing/usage/series",
1591
+ {
1592
+ ...opts,
1593
+ query,
1594
+ },
1595
+ ),
1596
+ /** Open a Stripe Checkout Session to top up; send the user to its `url`. */
1597
+ checkout: (body: z.input<typeof CheckoutIn>, opts?: RequestOptions) =>
1598
+ this.json<z.infer<typeof CheckoutOut>>(
1599
+ "POST",
1600
+ "/organization/billing/checkout",
1601
+ { ...opts, body },
1602
+ ),
1603
+ /** Add a card with no charge, unlocking the one-time welcome credit. */
1604
+ setup: (body: z.input<typeof SetupIn>, opts?: RequestOptions) =>
1605
+ this.json<z.infer<typeof SetupOut>>(
1606
+ "POST",
1607
+ "/organization/billing/setup",
1608
+ {
1609
+ ...opts,
1610
+ body,
1611
+ },
1612
+ ),
1613
+ redeem: (body: z.input<typeof RedeemIn>, opts?: RequestOptions) =>
1614
+ this.json<z.infer<typeof RedeemOut>>(
1615
+ "POST",
1616
+ "/organization/billing/redeem",
1617
+ { ...opts, body },
1618
+ ),
1619
+ /** Credit a returning Checkout Session. Safe to call twice — the ledger
1620
+ * keys on the session id, so it can't double-credit. */
1621
+ confirm: (body: z.input<typeof ConfirmIn>, opts?: RequestOptions) =>
1622
+ this.json<z.infer<typeof ConfirmOut>>(
1623
+ "POST",
1624
+ "/organization/billing/confirm",
1625
+ { ...opts, body },
1626
+ ),
1627
+ autoreload: {
1628
+ get: (opts?: RequestOptions) =>
1629
+ this.json<ICAutoreload>(
1630
+ "GET",
1631
+ "/organization/billing/autoreload",
1632
+ opts,
1633
+ ),
1634
+ put: (body: z.input<typeof AutoreloadIn>, opts?: RequestOptions) =>
1635
+ this.json<ICAutoreload>("PUT", "/organization/billing/autoreload", {
1636
+ ...opts,
1637
+ body,
1638
+ }),
1639
+ },
1640
+ /** Charge the saved card now. `amount_cents` defaults to the auto-reload amount. */
1641
+ reload: (body?: z.input<typeof ReloadIn>, opts?: RequestOptions) =>
1642
+ this.json<z.infer<typeof ReloadOut>>(
1643
+ "POST",
1644
+ "/organization/billing/reload",
1645
+ { ...opts, body: body ?? {} },
1646
+ ),
1647
+ /** A Stripe billing-portal URL for managing cards and invoices. */
1648
+ portal: (query: { return_url: string }, opts?: RequestOptions) =>
1649
+ this.json<z.infer<typeof PortalOut>>(
1650
+ "GET",
1651
+ "/organization/billing/portal",
1652
+ {
1653
+ ...opts,
1654
+ query,
1655
+ },
1656
+ ),
1657
+ },
1658
+
1263
1659
  projects: {
1264
- list: (opts?: RequestOptions) =>
1265
- this.data<ICProject>("GET", "/organization/projects", opts),
1660
+ list: (query?: PageOpts & { name?: string }, opts?: RequestOptions) =>
1661
+ this.page<ICProject>("/organization/projects", query, opts),
1266
1662
  create: (body: z.input<typeof ProjectIn>, opts?: RequestOptions) =>
1267
1663
  this.json<ICProject>("POST", "/organization/projects", {
1268
1664
  ...opts,