@ingram-cloud/sdk 1.4.0 → 1.5.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 (49) hide show
  1. package/dist/client.js +168 -15
  2. package/dist/index.js +4 -0
  3. package/dist/scopes.js +52 -0
  4. package/dist/zod/_actor.js +33 -0
  5. package/dist/zod/_page.js +18 -4
  6. package/dist/zod/agents.js +7 -3
  7. package/dist/zod/billing.js +136 -0
  8. package/dist/zod/budgets.js +2 -3
  9. package/dist/zod/connections.js +2 -3
  10. package/dist/zod/conversations.js +4 -11
  11. package/dist/zod/deployments.js +32 -0
  12. package/dist/zod/files.js +2 -9
  13. package/dist/zod/index.js +3 -0
  14. package/dist/zod/mcp.js +8 -10
  15. package/dist/zod/observability.js +38 -19
  16. package/dist/zod/projects.js +4 -4
  17. package/dist/zod/runs.js +16 -4
  18. package/dist/zod/schedules.js +2 -7
  19. package/dist/zod/skills.js +73 -0
  20. package/dist/zod/smith-revisions.js +2 -3
  21. package/dist/zod/smiths.js +6 -0
  22. package/dist/zod/tenant.js +24 -4
  23. package/dist/zod/vector-stores.js +6 -19
  24. package/package.json +6 -8
  25. package/ts/client.ts +385 -51
  26. package/ts/index.ts +4 -0
  27. package/ts/responses.ts +40 -2
  28. package/ts/scopes.ts +57 -0
  29. package/ts/zod/.impeccable/hook.cache.json +1 -0
  30. package/ts/zod/_actor.ts +36 -0
  31. package/ts/zod/_page.ts +19 -4
  32. package/ts/zod/agents.ts +7 -3
  33. package/ts/zod/billing.ts +168 -0
  34. package/ts/zod/budgets.ts +2 -3
  35. package/ts/zod/connections.ts +2 -3
  36. package/ts/zod/conversations.ts +7 -11
  37. package/ts/zod/deployments.ts +36 -0
  38. package/ts/zod/files.ts +2 -9
  39. package/ts/zod/index.ts +3 -0
  40. package/ts/zod/mcp.ts +8 -11
  41. package/ts/zod/observability.ts +74 -24
  42. package/ts/zod/projects.ts +4 -4
  43. package/ts/zod/runs.ts +18 -4
  44. package/ts/zod/schedules.ts +2 -7
  45. package/ts/zod/skills.ts +85 -0
  46. package/ts/zod/smith-revisions.ts +2 -3
  47. package/ts/zod/smiths.ts +6 -0
  48. package/ts/zod/tenant.ts +33 -5
  49. package/ts/zod/vector-stores.ts +9 -19
package/dist/client.js CHANGED
@@ -31,6 +31,52 @@ function qs(query) {
31
31
  const s = p.toString();
32
32
  return s ? `?${s}` : "";
33
33
  }
34
+ /** Build the multipart body `/v1/skills` accepts.
35
+ *
36
+ * The path rides the part's *filename*, slashes and all — that is how the
37
+ * bundle's directory structure survives a multipart body. `FormData` in Node,
38
+ * Bun and browsers all pass it through verbatim. */
39
+ function bundleForm(bundle) {
40
+ const form = new FormData();
41
+ if (bundle instanceof Blob) {
42
+ form.append("file", bundle, "bundle.zip");
43
+ return form;
44
+ }
45
+ for (const file of bundle) {
46
+ // A `Uint8Array`'s buffer type is generic (and may be a `SharedArrayBuffer`),
47
+ // which `Blob`'s constructor does not accept — copy into a fresh one, whose
48
+ // buffer is always a plain `ArrayBuffer`.
49
+ const part = typeof file.content === "string" || file.content instanceof Blob
50
+ ? file.content
51
+ : new Uint8Array(file.content);
52
+ const blob = part instanceof Blob
53
+ ? part
54
+ : new Blob([part], { type: mediaTypeFor(file.path) });
55
+ form.append("files[]", blob, file.path);
56
+ }
57
+ return form;
58
+ }
59
+ /** A `Blob` built from a string has no type of its own; the server falls back to
60
+ * `application/octet-stream` when a part carries none, which would make every
61
+ * text file non-indexable. Name the common ones from the extension. */
62
+ function mediaTypeFor(path) {
63
+ const ext = path.slice(path.lastIndexOf(".") + 1).toLowerCase();
64
+ const known = {
65
+ md: "text/markdown",
66
+ markdown: "text/markdown",
67
+ txt: "text/plain",
68
+ json: "application/json",
69
+ yaml: "text/yaml",
70
+ yml: "text/yaml",
71
+ csv: "text/csv",
72
+ py: "text/x-python",
73
+ sh: "text/x-shellscript",
74
+ js: "text/javascript",
75
+ ts: "text/typescript",
76
+ html: "text/html",
77
+ };
78
+ return known[ext] ?? "application/octet-stream";
79
+ }
34
80
  export class IngramCloud {
35
81
  token;
36
82
  base;
@@ -121,14 +167,14 @@ export class IngramCloud {
121
167
  }),
122
168
  },
123
169
  revisions: {
124
- list: (pid, opts) => this.data("GET", `/smiths/${enc(pid)}/revisions`, opts),
170
+ list: (pid, query, opts) => this.page(`/smiths/${enc(pid)}/revisions`, query, opts),
125
171
  restore: (pid, version, body = {}, opts) => this.json("POST", `/smiths/${enc(pid)}/revisions/${version}/restore`, {
126
172
  ...opts,
127
173
  body,
128
174
  }),
129
175
  },
130
176
  connections: {
131
- list: (pid, opts) => this.data("GET", `/smiths/${enc(pid)}/connections`, opts),
177
+ list: (pid, query, opts) => this.page(`/smiths/${enc(pid)}/connections`, query, opts),
132
178
  get: (pid, cid, opts) => this.json("GET", `/smiths/${enc(pid)}/connections/${enc(cid)}`, opts),
133
179
  create: (pid, body, opts) => this.json("POST", `/smiths/${enc(pid)}/connections`, {
134
180
  ...opts,
@@ -151,7 +197,7 @@ export class IngramCloud {
151
197
  delete: (pid, provider, opts) => this.empty("DELETE", `/smiths/${enc(pid)}/model_keys/${enc(provider)}`, opts),
152
198
  },
153
199
  schedules: {
154
- list: (pid, opts) => this.data("GET", `/smiths/${enc(pid)}/schedules`, opts),
200
+ list: (pid, query, opts) => this.page(`/smiths/${enc(pid)}/schedules`, query, opts),
155
201
  create: (pid, body, opts) => this.json("POST", `/smiths/${enc(pid)}/schedules`, {
156
202
  ...opts,
157
203
  body,
@@ -213,6 +259,7 @@ export class IngramCloud {
213
259
  // ── Runs (tenant-wide feed) ─────────────────────────────────────────────
214
260
  runs = {
215
261
  list: (query, opts) => this.page("/runs", query, opts),
262
+ trace: (rid, opts) => this.json("GET", `/runs/${enc(rid)}/trace`, opts),
216
263
  };
217
264
  // ── Agents ──────────────────────────────────────────────────────────────
218
265
  agents = {
@@ -222,7 +269,7 @@ export class IngramCloud {
222
269
  update: (aid, body, opts) => this.json("PATCH", `/agents/${enc(aid)}`, { ...opts, body }),
223
270
  delete: (aid, opts) => this.empty("DELETE", `/agents/${enc(aid)}`, opts),
224
271
  versions: {
225
- list: (aid, opts) => this.data("GET", `/agents/${enc(aid)}/versions`, opts),
272
+ list: (aid, query, opts) => this.page(`/agents/${enc(aid)}/versions`, query, opts),
226
273
  /** Snapshot the draft as the next immutable version. */
227
274
  publish: (aid, body = {}, opts) => this.json("POST", `/agents/${enc(aid)}/versions`, {
228
275
  ...opts,
@@ -246,7 +293,9 @@ export class IngramCloud {
246
293
  * `{ name, csp?, permissions?, tool? }` sidecar. Replaces by name. */
247
294
  put: (aid, html, meta, opts) => {
248
295
  const form = new FormData();
249
- form.append("file", html instanceof Blob ? html : new Blob([html], { type: "text/html" }), `${meta.name}.html`);
296
+ form.append("file", html instanceof Blob
297
+ ? html
298
+ : new Blob([html], { type: "text/html" }), `${meta.name}.html`);
250
299
  form.append("metadata", JSON.stringify(meta));
251
300
  return this.json("POST", `/agents/${enc(aid)}/ui`, {
252
301
  ...opts,
@@ -260,12 +309,16 @@ export class IngramCloud {
260
309
  ...opts,
261
310
  headers: { accept: "text/html", ...opts?.headers },
262
311
  }).then((r) => r.text()),
263
- delete: (aid, name, opts) => this.json("DELETE", `/agents/${enc(aid)}/ui/${enc(name)}`, opts),
312
+ delete: (aid, name, opts) => this.empty("DELETE", `/agents/${enc(aid)}/ui/${enc(name)}`, opts),
264
313
  },
265
314
  };
266
315
  // ── Conversations (smith-scoped: pass `{ smith }` with a tenant token) ──
267
316
  conversations = {
268
- list: (query, opts) => this.page("/conversations", query, opts),
317
+ /** OpenAI `list` envelope; page forward with `after: page.last_id`. */
318
+ list: (query, opts) => this.json("GET", "/conversations", {
319
+ ...opts,
320
+ query,
321
+ }),
269
322
  create: (body = {}, opts) => this.json("POST", "/conversations", { ...opts, body }),
270
323
  get: (cnvId, opts) => this.json("GET", `/conversations/${enc(cnvId)}`, opts),
271
324
  /** OpenAI-style modify — a POST, not a PATCH. */
@@ -288,6 +341,13 @@ export class IngramCloud {
288
341
  events = {
289
342
  list: (query, opts) => this.page("/events", query, opts),
290
343
  };
344
+ /** What arrived, before anything interpreted it — including arrivals that
345
+ * matched no smith (`smith_id: ""`). The `iev_` ids `deployment.inbound`
346
+ * carries resolve here. */
347
+ inboundEvents = {
348
+ list: (query, opts) => this.page("/inbound_events", query, opts),
349
+ get: (ievId, opts) => this.json("GET", `/inbound_events/${enc(ievId)}`, opts),
350
+ };
291
351
  // ── Customers / budgets ─────────────────────────────────────────────────
292
352
  customers = {
293
353
  list: (query, opts) => this.page("/customers", query, opts),
@@ -297,7 +357,7 @@ export class IngramCloud {
297
357
  delete: (cid, opts) => this.empty("DELETE", `/customers/${enc(cid)}`, opts),
298
358
  };
299
359
  budgets = {
300
- list: (opts) => this.data("GET", "/budgets", opts),
360
+ list: (query, opts) => this.page("/budgets", query, opts),
301
361
  create: (body, opts) => this.json("POST", "/budgets", { ...opts, body }),
302
362
  get: (bid, opts) => this.json("GET", `/budgets/${enc(bid)}`, opts),
303
363
  update: (bid, body, opts) => this.json("PATCH", `/budgets/${enc(bid)}`, { ...opts, body }),
@@ -320,10 +380,25 @@ export class IngramCloud {
320
380
  list: (opts) => this.data("GET", "/catalog", opts),
321
381
  get: (slug, opts) => this.json("GET", `/catalog/${enc(slug)}`, opts),
322
382
  };
383
+ // ── Embeddings ──────────────────────────────────────────────────────────
384
+ /** Embed one string or a batch on the OpenAI-compatible wire. Pure tenant
385
+ * compute — no smith runs. Omit `model` for the project default. Reach for
386
+ * the `openai` SDK instead if you already hold one; this is the same route. */
387
+ embeddings = {
388
+ create: (body, opts) => this.json("POST", "/embeddings", { ...opts, body }),
389
+ };
323
390
  // ── Observability ───────────────────────────────────────────────────────
324
391
  traces = {
325
392
  list: (query, opts) => this.page("/traces", query, opts),
326
393
  get: (traceId, opts) => this.json("GET", `/traces/${enc(traceId)}`, opts),
394
+ /** Push spans from your own runtime or an OTel exporter. The tenant comes
395
+ * from the token; a smith-scoped token may only attribute to its own smith.
396
+ * Unknown `kind`s land as `runtime_event` rather than erroring. Returns the
397
+ * number written. */
398
+ ingest: (spans, opts) => this.json("POST", "/traces:ingest", {
399
+ ...opts,
400
+ body: { spans },
401
+ }),
327
402
  };
328
403
  usage = {
329
404
  /** Token/cost/run totals grouped by app, smith, model, or customer. */
@@ -352,10 +427,16 @@ export class IngramCloud {
352
427
  // ── Vector stores (the OpenAI Vector Stores API) ─────────────────────────
353
428
  vectorStores = {
354
429
  create: (body, opts) => this.json("POST", "/vector_stores", { ...opts, body }),
355
- list: (query, opts) => this.json("GET", "/vector_stores", { ...opts, query }),
430
+ list: (query, opts) => this.json("GET", "/vector_stores", {
431
+ ...opts,
432
+ query,
433
+ }),
356
434
  get: (vsId, opts) => this.json("GET", `/vector_stores/${enc(vsId)}`, opts),
357
435
  /** Modify (OpenAI uses `POST`, not `PATCH`). */
358
- update: (vsId, body, opts) => this.json("POST", `/vector_stores/${enc(vsId)}`, { ...opts, body }),
436
+ update: (vsId, body, opts) => this.json("POST", `/vector_stores/${enc(vsId)}`, {
437
+ ...opts,
438
+ body,
439
+ }),
359
440
  delete: (vsId, opts) => this.json("DELETE", `/vector_stores/${enc(vsId)}`, opts),
360
441
  search: (vsId, body, opts) => this.json("POST", `/vector_stores/${enc(vsId)}/search`, {
361
442
  ...opts,
@@ -378,6 +459,34 @@ export class IngramCloud {
378
459
  files: (vsId, batchId, query, opts) => this.json("GET", `/vector_stores/${enc(vsId)}/file_batches/${enc(batchId)}/files`, { ...opts, query }),
379
460
  },
380
461
  };
462
+ // ── Agent Skills — a folder anchored by SKILL.md, versioned, attached to
463
+ // agents. Upload takes either the bundle's files as path/content pairs, or
464
+ // the whole bundle as a zip Blob — the same two shapes /v1/skills accepts.
465
+ // Both are runtime-agnostic: nothing here touches a filesystem. ───────────
466
+ skills = {
467
+ list: (opts) => this.json("GET", "/skills", opts),
468
+ get: (id, opts) => this.json("GET", `/skills/${enc(id)}`, opts),
469
+ create: (bundle, opts) => this.json("POST", "/skills", {
470
+ ...opts,
471
+ rawBody: bundleForm(bundle),
472
+ }),
473
+ /** Move `default_version` to an existing version. */
474
+ update: (id, body, opts) => this.json("POST", `/skills/${enc(id)}`, {
475
+ ...opts,
476
+ body,
477
+ }),
478
+ delete: (id, opts) => this.request("DELETE", `/skills/${enc(id)}`, opts).then(() => undefined),
479
+ versions: {
480
+ list: (id, opts) => this.json("GET", `/skills/${enc(id)}/versions`, opts),
481
+ get: (id, version, opts) => this.json("GET", `/skills/${enc(id)}/versions/${version}`, opts),
482
+ create: (id, bundle, opts) => this.json("POST", `/skills/${enc(id)}/versions`, { ...opts, rawBody: bundleForm(bundle) }),
483
+ delete: (id, version, opts) => this.request("DELETE", `/skills/${enc(id)}/versions/${version}`, opts).then(() => undefined),
484
+ /** One file's bytes, or — with no `path` — the whole version as a zip.
485
+ * The raw `Response` (matching `files.content`), so a caller streams it
486
+ * through rather than buffering the whole zip into memory. */
487
+ content: (id, version, path, opts) => this.request("GET", `/skills/${enc(id)}/versions/${version}/content${path ? `?path=${encodeURIComponent(path)}` : ""}`, opts),
488
+ },
489
+ };
381
490
  // ── Tenant config ───────────────────────────────────────────────────────
382
491
  tenant = {
383
492
  usage: (opts) => this.json("GET", "/tenant/usage", opts),
@@ -426,10 +535,13 @@ export class IngramCloud {
426
535
  delete: (provider, opts) => this.empty("DELETE", `/tenant/model_keys/${enc(provider)}`, opts),
427
536
  },
428
537
  mcp: {
429
- list: (opts) => this.data("GET", "/tenant/mcp", opts),
538
+ list: (query, opts) => this.page("/tenant/mcp", query, opts),
430
539
  get: (name, opts) => this.json("GET", `/tenant/mcp/${enc(name)}`, opts),
431
540
  /** Register or replace a server (full replace; probes `tools/list`). */
432
- put: (name, body, opts) => this.json("PUT", `/tenant/mcp/${enc(name)}`, { ...opts, body }),
541
+ put: (name, body, opts) => this.json("PUT", `/tenant/mcp/${enc(name)}`, {
542
+ ...opts,
543
+ body,
544
+ }),
433
545
  refresh: (name, opts) => this.json("POST", `/tenant/mcp/${enc(name)}/refresh`, opts),
434
546
  delete: (name, opts) => this.empty("DELETE", `/tenant/mcp/${enc(name)}`, opts),
435
547
  },
@@ -473,11 +585,52 @@ export class IngramCloud {
473
585
  decline: (requestId, opts) => this.json("POST", `/oauth/authorize-requests/${enc(requestId)}/decline`, opts),
474
586
  },
475
587
  };
476
- // ── Organization (org token: projects + tokens) ─────────────────────────
477
- // Note: the `/v1/organization/billing/*` surface is not yet wrapped here.
588
+ // ── Organization (org token: projects, tokens, billing) ─────────────────
478
589
  organization = {
590
+ /** Platform credits — the org wallet that funds every project's runs.
591
+ * Amounts are integer minor units of the wallet's `currency`. */
592
+ billing: {
593
+ balance: (opts) => this.json("GET", "/organization/billing/balance", opts),
594
+ /** Money in (top-ups, grants, codes) and out (usage debits), newest first. */
595
+ ledger: (query, opts) => this.page("/organization/billing/ledger", query, opts),
596
+ /** Per-project draw for a calendar month (`period`, `YYYY-MM`). */
597
+ usage: (query, opts) => this.json("GET", "/organization/billing/usage", {
598
+ ...opts,
599
+ query,
600
+ }),
601
+ /** Daily per-project draw over a rolling window of `days`. */
602
+ usageSeries: (query, opts) => this.json("GET", "/organization/billing/usage/series", {
603
+ ...opts,
604
+ query,
605
+ }),
606
+ /** Open a Stripe Checkout Session to top up; send the user to its `url`. */
607
+ checkout: (body, opts) => this.json("POST", "/organization/billing/checkout", { ...opts, body }),
608
+ /** Add a card with no charge, unlocking the one-time welcome credit. */
609
+ setup: (body, opts) => this.json("POST", "/organization/billing/setup", {
610
+ ...opts,
611
+ body,
612
+ }),
613
+ redeem: (body, opts) => this.json("POST", "/organization/billing/redeem", { ...opts, body }),
614
+ /** Credit a returning Checkout Session. Safe to call twice — the ledger
615
+ * keys on the session id, so it can't double-credit. */
616
+ confirm: (body, opts) => this.json("POST", "/organization/billing/confirm", { ...opts, body }),
617
+ autoreload: {
618
+ get: (opts) => this.json("GET", "/organization/billing/autoreload", opts),
619
+ put: (body, opts) => this.json("PUT", "/organization/billing/autoreload", {
620
+ ...opts,
621
+ body,
622
+ }),
623
+ },
624
+ /** Charge the saved card now. `amount_cents` defaults to the auto-reload amount. */
625
+ reload: (body, opts) => this.json("POST", "/organization/billing/reload", { ...opts, body: body ?? {} }),
626
+ /** A Stripe billing-portal URL for managing cards and invoices. */
627
+ portal: (query, opts) => this.json("GET", "/organization/billing/portal", {
628
+ ...opts,
629
+ query,
630
+ }),
631
+ },
479
632
  projects: {
480
- list: (opts) => this.data("GET", "/organization/projects", opts),
633
+ list: (query, opts) => this.page("/organization/projects", query, opts),
481
634
  create: (body, opts) => this.json("POST", "/organization/projects", {
482
635
  ...opts,
483
636
  body,
package/dist/index.js CHANGED
@@ -13,7 +13,11 @@
13
13
  * `./events` is the hand-authored `{v:1}` webhook/feed envelope and the SSE
14
14
  * run-stream frames, which OpenAPI can't express.
15
15
  *
16
+ * `./scopes` is the closed permission vocabulary a smith token may carry — you
17
+ * must name the scopes you want when minting one.
18
+ *
16
19
  * See `../README.md`.
17
20
  */
18
21
  export { schemas } from "./schemas.js";
19
22
  export * from "./events.js";
23
+ export * from "./scopes.js";
package/dist/scopes.js ADDED
@@ -0,0 +1,52 @@
1
+ /**
2
+ * The closed permission vocabulary a smith token may carry, in mint order.
3
+ *
4
+ * It lives here rather than in the API because it is wire contract: a caller
5
+ * minting a token has to name the scopes it wants (`permissions` is required —
6
+ * there is no "grant everything" default), and the console's token form needs
7
+ * the same list to offer full access. One definition, so a new scope reaches
8
+ * every minting surface at once instead of drifting into a stale copy.
9
+ *
10
+ * Not in here: the admin markers (`tenant:*`, `operator:*`) and the account key
11
+ * (`organization:*`). Those are postures, not permissions — they are never a
12
+ * legal `permissions` entry, and the API refuses them as unknown scopes.
13
+ */
14
+ export const V1_SCOPES = [
15
+ "runs:read",
16
+ "runs:write",
17
+ "conversations:read",
18
+ "conversations:write",
19
+ "memories:read",
20
+ "memories:write",
21
+ "connections:read",
22
+ "connections:write",
23
+ "deployments:read",
24
+ "deployments:write",
25
+ "schedules:read",
26
+ "schedules:write",
27
+ "approvals:read",
28
+ "approvals:write",
29
+ "traces:read",
30
+ "traces:write",
31
+ "usage:read",
32
+ "usage:write",
33
+ "customers:read",
34
+ "customers:write",
35
+ "files:read",
36
+ "files:write",
37
+ "vector_stores:read",
38
+ "vector_stores:write",
39
+ // Smith-level provider keys (#170, end-user BYOK): an end-user sets their own
40
+ // key; a tenant token manages any of its smiths' keys.
41
+ "model_keys:read",
42
+ "model_keys:write",
43
+ // Agent Skills (#175): a tenant's skill bundles and their immutable versions.
44
+ "skills:read",
45
+ "skills:write",
46
+ // Embeddings: the stateless text→vector compute endpoint (POST /v1/embeddings).
47
+ // Write-only — it produces a result, it reads no stored state.
48
+ "embeddings:write",
49
+ ];
50
+ /** The read half of the vocabulary — the scope set for a token that must not
51
+ * change anything. Derived, so it cannot fall behind {@link V1_SCOPES}. */
52
+ export const V1_READ_SCOPES = V1_SCOPES.filter((s) => s.endsWith(":read"));
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The acting principal — who a run or an event is attributable to.
3
+ *
4
+ * Resolved from the authenticated caller at the point of action and stamped on
5
+ * the record it produced; never inferred afterwards. Every event a run produces
6
+ * inherits the run's actor, so a whole turn is attributable to one identity.
7
+ */
8
+ import { z } from "zod";
9
+ export const Actor = z
10
+ .object({
11
+ /** `smith` — a smith acted (a smith-bound token, or the smith itself on an
12
+ * autonomous turn); `tenant` — a tenant-admin token acted on a smith's
13
+ * behalf; `operator` — Ingram staff acted through the operator console. */
14
+ kind: z.enum(["smith", "tenant", "operator"]),
15
+ /** The smith id, tenant id, or operator email, per `kind`. */
16
+ id: z.string(),
17
+ /** `jti` of the token that authorized the action. Empty when no token
18
+ * acted — a scheduled or channel-driven turn the platform ran itself, or a
19
+ * console session, which signs a short-lived per-request token that is never
20
+ * registered. Read it with `email`: both empty means the platform acted. */
21
+ token_id: z.string(),
22
+ /** The human behind the action, when one is named — the signed-in console user
23
+ * or the Ingram operator. Empty for a machine caller (an API token, a smith
24
+ * acting for itself) and for autonomous work.
25
+ *
26
+ * This is what makes a config change attributable to a *person* rather than to
27
+ * the tenant they share: console mutations all carry `kind: "tenant"`, so
28
+ * without this every colleague's action looked identical. Defaulted rather than
29
+ * optional so records written before it existed read as "no human named"
30
+ * instead of failing to parse. */
31
+ email: z.string().default(""),
32
+ })
33
+ .meta({ id: "Actor" });
package/dist/zod/_page.js CHANGED
@@ -1,8 +1,10 @@
1
1
  /**
2
- * The shared cursor-paginated list envelope. One definition so every paginated
3
- * `/v1` list reads back the same shape: `data` + the opaque `next_cursor` (null
4
- * on the last page) + `has_more`. The cursor is an opaque, short-lived token
5
- * pass it straight back as `?cursor=`, never parse it.
2
+ * The two `/v1` list envelopes, one definition each. Native resources page by
3
+ * keyset: `data` + the opaque `next_cursor` (null on the last page) + `has_more`
4
+ * the cursor is an opaque, short-lived token; pass it straight back as
5
+ * `?cursor=`, never parse it. OpenAI-mirrored resources use the OpenAI `list`
6
+ * envelope instead: `object:"list"` + `first_id`/`last_id` + `has_more`, paged
7
+ * by passing `last_id` back as `?after=`.
6
8
  */
7
9
  import { z } from "zod";
8
10
  /** Wrap an item schema as a cursor-paginated list out, named `id` in the spec. */
@@ -15,3 +17,15 @@ export function pageOut(item, id) {
15
17
  })
16
18
  .meta({ id });
17
19
  }
20
+ /** Wrap an item schema in the OpenAI `list` envelope, named `id` in the spec. */
21
+ export function oaiListOut(item, id) {
22
+ return z
23
+ .object({
24
+ object: z.literal("list"),
25
+ data: z.array(item),
26
+ first_id: z.string().nullable(),
27
+ last_id: z.string().nullable(),
28
+ has_more: z.boolean(),
29
+ })
30
+ .meta({ id });
31
+ }
@@ -13,6 +13,7 @@
13
13
  */
14
14
  import { z } from "zod";
15
15
  import { pageOut } from "./_page.js";
16
+ import { SkillRef } from "./skills.js";
16
17
  /** A per-smith variable an agent declares; bound at run time. */
17
18
  export const AgentVariable = z
18
19
  .object({
@@ -69,6 +70,8 @@ export const AgentDraft = z
69
70
  vector_store_ids: z.array(z.string()),
70
71
  /** Registered MCP servers this agent's smiths load, by name. Null = all. */
71
72
  mcp_servers: z.array(z.string()).nullable(),
73
+ /** Skills this agent's smiths carry. Frozen into the snapshot at publish. */
74
+ skills: z.array(SkillRef),
72
75
  auto_memory: z.boolean().nullable(),
73
76
  memory_consolidation: z.boolean().nullable(),
74
77
  variables: z.array(AgentVariable),
@@ -106,6 +109,7 @@ export const AgentVersionOut = z
106
109
  enabled_hosted_tools: z.array(z.string()).optional(),
107
110
  vector_store_ids: z.array(z.string()).optional(),
108
111
  mcp_servers: z.array(z.string()).nullish(),
112
+ skills: z.array(SkillRef).optional(),
109
113
  auto_memory: z.boolean().nullish(),
110
114
  memory_consolidation: z.boolean().nullish(),
111
115
  variables: z.array(AgentVariable).optional(),
@@ -116,9 +120,7 @@ export const AgentVersionOut = z
116
120
  created_at: z.string().nullable(),
117
121
  })
118
122
  .meta({ id: "AgentVersionOut" });
119
- export const AgentVersionListOut = z
120
- .object({ data: z.array(AgentVersionOut) })
121
- .meta({ id: "AgentVersionListOut" });
123
+ export const AgentVersionListOut = pageOut(AgentVersionOut, "AgentVersionListOut");
122
124
  // ── Request bodies ──────────────────────────────────────────────────────────
123
125
  export const AgentIn = z
124
126
  .object({
@@ -130,6 +132,7 @@ export const AgentIn = z
130
132
  vector_store_ids: z.array(z.string()).nullish(),
131
133
  /** Scope runs to these registered MCP servers (by name). Null/omitted = all. */
132
134
  mcp_servers: z.array(z.string()).nullish(),
135
+ skills: z.array(SkillRef).nullish(),
133
136
  auto_memory: z.boolean().nullish(),
134
137
  memory_consolidation: z.boolean().nullish(),
135
138
  variables: z.array(AgentVariable).nullish(),
@@ -145,6 +148,7 @@ export const AgentPatch = z
145
148
  /** Scope runs to these registered MCP servers (by name). An explicit null
146
149
  * clears the restriction (= all); omitted leaves it unchanged. */
147
150
  mcp_servers: z.array(z.string()).nullish(),
151
+ skills: z.array(SkillRef).nullish(),
148
152
  auto_memory: z.boolean().nullish(),
149
153
  memory_consolidation: z.boolean().nullish(),
150
154
  variables: z.array(AgentVariable).nullish(),
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Hand-authored Zod schemas for platform credits — the org wallet the tenant
3
+ * funds to pay *Ingram* (`/v1/organization/billing/*`). One wallet pools across
4
+ * all of the org's projects; every endpoint needs an organization-scoped token.
5
+ *
6
+ * Source of truth is the handler (`api/src/routes/billing.ts`), which imports
7
+ * these for request validation and response typing. Amounts are always integer
8
+ * **minor units** (cents) of `currency` (ISO-4217, lower-case) — never floats.
9
+ *
10
+ * The per-endpoint query schemas stay in the handler: they carry OpenAPI
11
+ * `param` metadata, and the client types query args as plain TS.
12
+ *
13
+ * `.meta({ id })` names the component so the emitted OpenAPI references it as
14
+ * `#/components/schemas/<id>` rather than inlining it.
15
+ */
16
+ import { z } from "zod";
17
+ // ── Balance + ledger ────────────────────────────────────────────────────────
18
+ export const BalanceOut = z
19
+ .object({
20
+ /** ISO-4217, lower-case. Clients format `balance_cents` in this currency. */
21
+ currency: z.string(),
22
+ balance_cents: z.number().int(),
23
+ /** Whether the org has a Stripe Customer yet (materialized on first payment). */
24
+ stripe_customer: z.boolean(),
25
+ })
26
+ .meta({ id: "BalanceOut" });
27
+ /** One credit-ledger row. `amount_cents` is positive for money in (top-ups,
28
+ * grants, redeemed codes) and negative for usage debits. */
29
+ export const LedgerEntryOut = z
30
+ .object({
31
+ id: z.string(),
32
+ amount_cents: z.number().int(),
33
+ currency: z.string(),
34
+ kind: z.string(),
35
+ description: z.string(),
36
+ created_at: z.string().nullable(),
37
+ })
38
+ .meta({ id: "LedgerEntryOut" });
39
+ /** A page of ledger rows (keyset pagination). */
40
+ export const LedgerListOut = z
41
+ .object({
42
+ data: z.array(LedgerEntryOut),
43
+ next_cursor: z.string().nullable(),
44
+ has_more: z.boolean(),
45
+ })
46
+ .meta({ id: "LedgerListOut" });
47
+ // ── Per-project draw from the wallet ────────────────────────────────────────
48
+ /** One project's draw for a calendar month — which project is spending the
49
+ * shared funds, and against what cap. */
50
+ export const OrgUsageProject = z
51
+ .object({
52
+ project_id: z.string(),
53
+ name: z.string(),
54
+ /** Credits drawn this period (sum of the project's debit rows). */
55
+ drawn_cents: z.number().int(),
56
+ /** Tokens the project's runs consumed this period (priced daily rollup). */
57
+ tokens: z.number().int(),
58
+ /** The project's tenant-scope budget limit (billing currency, major units),
59
+ * or null when it draws freely from the org wallet. */
60
+ budget_limit: z.number().nullable(),
61
+ budget_action: z.string().nullable(),
62
+ })
63
+ .meta({ id: "OrgUsageProject" });
64
+ export const OrgUsageOut = z
65
+ .object({
66
+ period: z.string(),
67
+ currency: z.string(),
68
+ total_drawn_cents: z.number().int(),
69
+ total_tokens: z.number().int(),
70
+ projects: z.array(OrgUsageProject),
71
+ })
72
+ .meta({ id: "OrgUsageOut" });
73
+ /** One day/project draw. Points are sparse — a day with no draw is absent, and
74
+ * the client fills the gaps with zero. */
75
+ export const OrgUsageSeriesPoint = z
76
+ .object({
77
+ day: z.string(),
78
+ project_id: z.string(),
79
+ drawn_cents: z.number().int(),
80
+ })
81
+ .meta({ id: "OrgUsageSeriesPoint" });
82
+ export const OrgUsageSeriesOut = z
83
+ .object({
84
+ currency: z.string(),
85
+ from: z.string(),
86
+ to: z.string(),
87
+ /** Projects with any draw in the window, ranked by total draw. */
88
+ projects: z.array(z.object({ project_id: z.string(), name: z.string() })),
89
+ points: z.array(OrgUsageSeriesPoint),
90
+ })
91
+ .meta({ id: "OrgUsageSeriesOut" });
92
+ // ── Money movement ──────────────────────────────────────────────────────────
93
+ export const CheckoutIn = z
94
+ .object({
95
+ amount_cents: z.number().int(),
96
+ /** The console's own origin URL to return to; must carry the Stripe session
97
+ * template so the page can reflect the result. */
98
+ return_url: z.string(),
99
+ })
100
+ .meta({ id: "CheckoutIn" });
101
+ export const CheckoutOut = z.object({ url: z.string() }).meta({ id: "CheckoutOut" });
102
+ export const ConfirmIn = z.object({ session_id: z.string() }).meta({ id: "ConfirmIn" });
103
+ export const ConfirmOut = z
104
+ .object({ credited: z.boolean(), payment_status: z.string() })
105
+ .meta({ id: "ConfirmOut" });
106
+ /** Add a card with no charge (Stripe setup-mode Checkout); on success it unlocks
107
+ * the one-time welcome credit. Same return-url contract as checkout. */
108
+ export const SetupIn = z.object({ return_url: z.string() }).meta({ id: "SetupIn" });
109
+ export const SetupOut = z.object({ url: z.string() }).meta({ id: "SetupOut" });
110
+ /** Redeem a one-time credit code, matched case-insensitively. */
111
+ export const RedeemIn = z.object({ code: z.string() }).meta({ id: "RedeemIn" });
112
+ export const RedeemOut = z
113
+ .object({
114
+ amount_cents: z.number().int(),
115
+ currency: z.string(),
116
+ })
117
+ .meta({ id: "RedeemOut" });
118
+ export const AutoreloadOut = z
119
+ .object({
120
+ enabled: z.boolean(),
121
+ /** When the balance drops below `threshold_cents`, the saved card is charged
122
+ * for `amount_cents`. Both are integer minor units of the billing currency. */
123
+ threshold_cents: z.number().int(),
124
+ amount_cents: z.number().int(),
125
+ })
126
+ .meta({ id: "AutoreloadOut" });
127
+ /** Set the same shape you read back. */
128
+ export const AutoreloadIn = AutoreloadOut;
129
+ /** `amount_cents` is optional — it defaults to the configured auto-reload amount. */
130
+ export const ReloadIn = z
131
+ .object({ amount_cents: z.number().int().optional() })
132
+ .meta({ id: "ReloadIn" });
133
+ export const ReloadOut = z
134
+ .object({ credited: z.boolean(), payment_status: z.string() })
135
+ .meta({ id: "ReloadOut" });
136
+ export const PortalOut = z.object({ url: z.string() }).meta({ id: "PortalOut" });
@@ -12,6 +12,7 @@
12
12
  * `#/components/schemas/<id>` rather than inlining it.
13
13
  */
14
14
  import { z } from "zod";
15
+ import { pageOut } from "./_page.js";
15
16
  /** What a budget caps; `agent`/`smith`/`customer` budgets carry the id in
16
17
  * `scope_id` (an agent design, a single smith, or one of your customers). */
17
18
  export const BudgetScope = z.enum(["tenant", "agent", "smith", "customer"]);
@@ -40,9 +41,7 @@ export const BudgetStatusOut = BudgetOut.extend({
40
41
  pct: z.number(),
41
42
  over: z.boolean(),
42
43
  }).meta({ id: "BudgetStatusOut" });
43
- export const BudgetListOut = z
44
- .object({ data: z.array(BudgetOut) })
45
- .meta({ id: "BudgetListOut" });
44
+ export const BudgetListOut = pageOut(BudgetOut, "BudgetListOut");
46
45
  // ── Request bodies ──────────────────────────────────────────────────────────
47
46
  export const BudgetIn = z
48
47
  .object({
@@ -18,6 +18,7 @@
18
18
  * `#/components/schemas/<id>` rather than inlining it.
19
19
  */
20
20
  import { z } from "zod";
21
+ import { pageOut } from "./_page.js";
21
22
  /** OAuth token material the tenant pushes in; IC stores it encrypted and never
22
23
  * echoes it back. Only `kind: "oauth_tokens"` is accepted. */
23
24
  export const Credential = z
@@ -41,9 +42,7 @@ export const ConnectionOut = z
41
42
  created_at: z.string().nullable(),
42
43
  })
43
44
  .meta({ id: "ConnectionOut" });
44
- export const ConnectionListOut = z
45
- .object({ data: z.array(ConnectionOut) })
46
- .meta({ id: "ConnectionListOut" });
45
+ export const ConnectionListOut = pageOut(ConnectionOut, "ConnectionListOut");
47
46
  // ── Request bodies ──────────────────────────────────────────────────────────
48
47
  export const ConnectionIn = z
49
48
  .object({