@ingram-cloud/sdk 1.2.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 +185 -14
  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 +17 -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 +20 -14
  15. package/dist/zod/observability.js +39 -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 +7 -5
  21. package/dist/zod/smiths.js +14 -0
  22. package/dist/zod/tenant.js +41 -4
  23. package/dist/zod/vector-stores.js +36 -23
  24. package/package.json +6 -8
  25. package/ts/client.ts +431 -50
  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 +17 -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 +20 -15
  41. package/ts/zod/observability.ts +75 -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 +7 -5
  47. package/ts/zod/smiths.ts +14 -0
  48. package/ts/zod/tenant.ts +52 -5
  49. package/ts/zod/vector-stores.ts +41 -23
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,
@@ -143,8 +189,15 @@ export class IngramCloud {
143
189
  body,
144
190
  }),
145
191
  },
192
+ /** End-user BYOK (#170): a smith's own provider keys, resolved ahead of the
193
+ * tenant's so the end-user's provider account is billed. Never read back. */
194
+ modelKeys: {
195
+ list: (pid, opts) => this.data("GET", `/smiths/${enc(pid)}/model_keys`, opts),
196
+ put: (pid, provider, body, opts) => this.json("PUT", `/smiths/${enc(pid)}/model_keys/${enc(provider)}`, { ...opts, body }),
197
+ delete: (pid, provider, opts) => this.empty("DELETE", `/smiths/${enc(pid)}/model_keys/${enc(provider)}`, opts),
198
+ },
146
199
  schedules: {
147
- list: (pid, opts) => this.data("GET", `/smiths/${enc(pid)}/schedules`, opts),
200
+ list: (pid, query, opts) => this.page(`/smiths/${enc(pid)}/schedules`, query, opts),
148
201
  create: (pid, body, opts) => this.json("POST", `/smiths/${enc(pid)}/schedules`, {
149
202
  ...opts,
150
203
  body,
@@ -206,6 +259,7 @@ export class IngramCloud {
206
259
  // ── Runs (tenant-wide feed) ─────────────────────────────────────────────
207
260
  runs = {
208
261
  list: (query, opts) => this.page("/runs", query, opts),
262
+ trace: (rid, opts) => this.json("GET", `/runs/${enc(rid)}/trace`, opts),
209
263
  };
210
264
  // ── Agents ──────────────────────────────────────────────────────────────
211
265
  agents = {
@@ -215,7 +269,7 @@ export class IngramCloud {
215
269
  update: (aid, body, opts) => this.json("PATCH", `/agents/${enc(aid)}`, { ...opts, body }),
216
270
  delete: (aid, opts) => this.empty("DELETE", `/agents/${enc(aid)}`, opts),
217
271
  versions: {
218
- list: (aid, opts) => this.data("GET", `/agents/${enc(aid)}/versions`, opts),
272
+ list: (aid, query, opts) => this.page(`/agents/${enc(aid)}/versions`, query, opts),
219
273
  /** Snapshot the draft as the next immutable version. */
220
274
  publish: (aid, body = {}, opts) => this.json("POST", `/agents/${enc(aid)}/versions`, {
221
275
  ...opts,
@@ -239,19 +293,32 @@ export class IngramCloud {
239
293
  * `{ name, csp?, permissions?, tool? }` sidecar. Replaces by name. */
240
294
  put: (aid, html, meta, opts) => {
241
295
  const form = new FormData();
242
- 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`);
243
299
  form.append("metadata", JSON.stringify(meta));
244
300
  return this.json("POST", `/agents/${enc(aid)}/ui`, {
245
301
  ...opts,
246
302
  rawBody: form,
247
303
  });
248
304
  },
249
- delete: (aid, name, opts) => this.json("DELETE", `/agents/${enc(aid)}/ui/${enc(name)}`, opts),
305
+ /** The template's HTML bundle the same bytes the agent's MCP endpoint
306
+ * serves, so a host can render a panel without vendoring a copy.
307
+ * Tenant-authed: call it server-side and proxy to your chat UI. */
308
+ content: (aid, name, opts) => this.request("GET", `/agents/${enc(aid)}/ui/${enc(name)}/content`, {
309
+ ...opts,
310
+ headers: { accept: "text/html", ...opts?.headers },
311
+ }).then((r) => r.text()),
312
+ delete: (aid, name, opts) => this.empty("DELETE", `/agents/${enc(aid)}/ui/${enc(name)}`, opts),
250
313
  },
251
314
  };
252
315
  // ── Conversations (smith-scoped: pass `{ smith }` with a tenant token) ──
253
316
  conversations = {
254
- 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
+ }),
255
322
  create: (body = {}, opts) => this.json("POST", "/conversations", { ...opts, body }),
256
323
  get: (cnvId, opts) => this.json("GET", `/conversations/${enc(cnvId)}`, opts),
257
324
  /** OpenAI-style modify — a POST, not a PATCH. */
@@ -274,6 +341,13 @@ export class IngramCloud {
274
341
  events = {
275
342
  list: (query, opts) => this.page("/events", query, opts),
276
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
+ };
277
351
  // ── Customers / budgets ─────────────────────────────────────────────────
278
352
  customers = {
279
353
  list: (query, opts) => this.page("/customers", query, opts),
@@ -283,7 +357,7 @@ export class IngramCloud {
283
357
  delete: (cid, opts) => this.empty("DELETE", `/customers/${enc(cid)}`, opts),
284
358
  };
285
359
  budgets = {
286
- list: (opts) => this.data("GET", "/budgets", opts),
360
+ list: (query, opts) => this.page("/budgets", query, opts),
287
361
  create: (body, opts) => this.json("POST", "/budgets", { ...opts, body }),
288
362
  get: (bid, opts) => this.json("GET", `/budgets/${enc(bid)}`, opts),
289
363
  update: (bid, body, opts) => this.json("PATCH", `/budgets/${enc(bid)}`, { ...opts, body }),
@@ -306,10 +380,25 @@ export class IngramCloud {
306
380
  list: (opts) => this.data("GET", "/catalog", opts),
307
381
  get: (slug, opts) => this.json("GET", `/catalog/${enc(slug)}`, opts),
308
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
+ };
309
390
  // ── Observability ───────────────────────────────────────────────────────
310
391
  traces = {
311
392
  list: (query, opts) => this.page("/traces", query, opts),
312
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
+ }),
313
402
  };
314
403
  usage = {
315
404
  /** Token/cost/run totals grouped by app, smith, model, or customer. */
@@ -338,10 +427,16 @@ export class IngramCloud {
338
427
  // ── Vector stores (the OpenAI Vector Stores API) ─────────────────────────
339
428
  vectorStores = {
340
429
  create: (body, opts) => this.json("POST", "/vector_stores", { ...opts, body }),
341
- list: (query, opts) => this.json("GET", "/vector_stores", { ...opts, query }),
430
+ list: (query, opts) => this.json("GET", "/vector_stores", {
431
+ ...opts,
432
+ query,
433
+ }),
342
434
  get: (vsId, opts) => this.json("GET", `/vector_stores/${enc(vsId)}`, opts),
343
435
  /** Modify (OpenAI uses `POST`, not `PATCH`). */
344
- 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
+ }),
345
440
  delete: (vsId, opts) => this.json("DELETE", `/vector_stores/${enc(vsId)}`, opts),
346
441
  search: (vsId, body, opts) => this.json("POST", `/vector_stores/${enc(vsId)}/search`, {
347
442
  ...opts,
@@ -364,6 +459,34 @@ export class IngramCloud {
364
459
  files: (vsId, batchId, query, opts) => this.json("GET", `/vector_stores/${enc(vsId)}/file_batches/${enc(batchId)}/files`, { ...opts, query }),
365
460
  },
366
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
+ };
367
490
  // ── Tenant config ───────────────────────────────────────────────────────
368
491
  tenant = {
369
492
  usage: (opts) => this.json("GET", "/tenant/usage", opts),
@@ -384,6 +507,9 @@ export class IngramCloud {
384
507
  body,
385
508
  }),
386
509
  delete: (wid, opts) => this.empty("DELETE", `/tenant/webhooks/${enc(wid)}`, opts),
510
+ /** Rotate the signing secret, returning the new one exactly once. Pass
511
+ * `grace_seconds` to keep the old secret accepted during an overlap window. */
512
+ rotateSecret: (wid, body, opts) => this.json("POST", `/tenant/webhooks/${enc(wid)}/rotate_secret`, { ...opts, body: body ?? {} }),
387
513
  test: (wid, opts) => this.json("POST", `/tenant/webhooks/${enc(wid)}/test`, opts),
388
514
  /** The persisted delivery attempts for a webhook (durable retry audit trail). */
389
515
  deliveries: (wid, query, opts) => this.page(`/tenant/webhooks/${enc(wid)}/deliveries`, query, opts),
@@ -409,10 +535,13 @@ export class IngramCloud {
409
535
  delete: (provider, opts) => this.empty("DELETE", `/tenant/model_keys/${enc(provider)}`, opts),
410
536
  },
411
537
  mcp: {
412
- list: (opts) => this.data("GET", "/tenant/mcp", opts),
538
+ list: (query, opts) => this.page("/tenant/mcp", query, opts),
413
539
  get: (name, opts) => this.json("GET", `/tenant/mcp/${enc(name)}`, opts),
414
540
  /** Register or replace a server (full replace; probes `tools/list`). */
415
- 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
+ }),
416
545
  refresh: (name, opts) => this.json("POST", `/tenant/mcp/${enc(name)}/refresh`, opts),
417
546
  delete: (name, opts) => this.empty("DELETE", `/tenant/mcp/${enc(name)}`, opts),
418
547
  },
@@ -456,10 +585,52 @@ export class IngramCloud {
456
585
  decline: (requestId, opts) => this.json("POST", `/oauth/authorize-requests/${enc(requestId)}/decline`, opts),
457
586
  },
458
587
  };
459
- // ── Organization (org token: projects + billing) ────────────────────────
588
+ // ── Organization (org token: projects, tokens, billing) ─────────────────
460
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
+ },
461
632
  projects: {
462
- list: (opts) => this.data("GET", "/organization/projects", opts),
633
+ list: (query, opts) => this.page("/organization/projects", query, opts),
463
634
  create: (body, opts) => this.json("POST", "/organization/projects", {
464
635
  ...opts,
465
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({
@@ -67,6 +68,10 @@ export const AgentDraft = z
67
68
  model: z.string().nullable(),
68
69
  enabled_hosted_tools: z.array(z.string()),
69
70
  vector_store_ids: z.array(z.string()),
71
+ /** Registered MCP servers this agent's smiths load, by name. Null = all. */
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),
70
75
  auto_memory: z.boolean().nullable(),
71
76
  memory_consolidation: z.boolean().nullable(),
72
77
  variables: z.array(AgentVariable),
@@ -87,6 +92,8 @@ export const AgentOut = z
87
92
  rollout_version: z.number().int().nullable(),
88
93
  rollout_percent: z.number().int(),
89
94
  smith_count: z.number().int().optional(),
95
+ /** Newest run across every smith of this agent. Null until one runs. */
96
+ last_activity_at: z.string().nullable().optional(),
90
97
  created_at: z.string().nullable(),
91
98
  updated_at: z.string().nullable(),
92
99
  })
@@ -101,6 +108,8 @@ export const AgentVersionOut = z
101
108
  model: z.string().nullish(),
102
109
  enabled_hosted_tools: z.array(z.string()).optional(),
103
110
  vector_store_ids: z.array(z.string()).optional(),
111
+ mcp_servers: z.array(z.string()).nullish(),
112
+ skills: z.array(SkillRef).optional(),
104
113
  auto_memory: z.boolean().nullish(),
105
114
  memory_consolidation: z.boolean().nullish(),
106
115
  variables: z.array(AgentVariable).optional(),
@@ -111,9 +120,7 @@ export const AgentVersionOut = z
111
120
  created_at: z.string().nullable(),
112
121
  })
113
122
  .meta({ id: "AgentVersionOut" });
114
- export const AgentVersionListOut = z
115
- .object({ data: z.array(AgentVersionOut) })
116
- .meta({ id: "AgentVersionListOut" });
123
+ export const AgentVersionListOut = pageOut(AgentVersionOut, "AgentVersionListOut");
117
124
  // ── Request bodies ──────────────────────────────────────────────────────────
118
125
  export const AgentIn = z
119
126
  .object({
@@ -123,6 +130,9 @@ export const AgentIn = z
123
130
  model: z.string().nullish(),
124
131
  enabled_hosted_tools: z.array(z.string()).nullish(),
125
132
  vector_store_ids: z.array(z.string()).nullish(),
133
+ /** Scope runs to these registered MCP servers (by name). Null/omitted = all. */
134
+ mcp_servers: z.array(z.string()).nullish(),
135
+ skills: z.array(SkillRef).nullish(),
126
136
  auto_memory: z.boolean().nullish(),
127
137
  memory_consolidation: z.boolean().nullish(),
128
138
  variables: z.array(AgentVariable).nullish(),
@@ -135,6 +145,10 @@ export const AgentPatch = z
135
145
  model: z.string().nullish(),
136
146
  enabled_hosted_tools: z.array(z.string()).nullish(),
137
147
  vector_store_ids: z.array(z.string()).nullish(),
148
+ /** Scope runs to these registered MCP servers (by name). An explicit null
149
+ * clears the restriction (= all); omitted leaves it unchanged. */
150
+ mcp_servers: z.array(z.string()).nullish(),
151
+ skills: z.array(SkillRef).nullish(),
138
152
  auto_memory: z.boolean().nullish(),
139
153
  memory_consolidation: z.boolean().nullish(),
140
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" });