@ingram-cloud/sdk 1.0.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 (64) hide show
  1. package/README.md +74 -0
  2. package/dist/client.js +460 -0
  3. package/dist/events.js +108 -0
  4. package/dist/index.js +19 -0
  5. package/dist/responses.js +12 -0
  6. package/dist/schemas.js +4 -0
  7. package/dist/zod/_page.js +17 -0
  8. package/dist/zod/agents.js +176 -0
  9. package/dist/zod/approvals.js +38 -0
  10. package/dist/zod/budgets.js +62 -0
  11. package/dist/zod/catalog.js +49 -0
  12. package/dist/zod/connections.js +75 -0
  13. package/dist/zod/conversations.js +91 -0
  14. package/dist/zod/customers.js +53 -0
  15. package/dist/zod/deployments.js +81 -0
  16. package/dist/zod/discord.js +32 -0
  17. package/dist/zod/email.js +45 -0
  18. package/dist/zod/files.js +55 -0
  19. package/dist/zod/index.js +35 -0
  20. package/dist/zod/mcp.js +107 -0
  21. package/dist/zod/memories.js +43 -0
  22. package/dist/zod/observability.js +133 -0
  23. package/dist/zod/projects.js +58 -0
  24. package/dist/zod/runs.js +119 -0
  25. package/dist/zod/schedules.js +71 -0
  26. package/dist/zod/slack.js +69 -0
  27. package/dist/zod/smith-revisions.js +42 -0
  28. package/dist/zod/smiths.js +108 -0
  29. package/dist/zod/telegram.js +36 -0
  30. package/dist/zod/tenant.js +219 -0
  31. package/dist/zod/vector-stores.js +251 -0
  32. package/dist/zod/whatsapp.js +47 -0
  33. package/package.json +56 -0
  34. package/ts/client.ts +1187 -0
  35. package/ts/events.ts +119 -0
  36. package/ts/index.ts +20 -0
  37. package/ts/responses.ts +83 -0
  38. package/ts/schemas.ts +4 -0
  39. package/ts/zod/_page.ts +18 -0
  40. package/ts/zod/agents.ts +202 -0
  41. package/ts/zod/approvals.ts +44 -0
  42. package/ts/zod/budgets.ts +75 -0
  43. package/ts/zod/catalog.ts +57 -0
  44. package/ts/zod/connections.ts +87 -0
  45. package/ts/zod/conversations.ts +103 -0
  46. package/ts/zod/customers.ts +62 -0
  47. package/ts/zod/deployments.ts +93 -0
  48. package/ts/zod/discord.ts +39 -0
  49. package/ts/zod/email.ts +52 -0
  50. package/ts/zod/files.ts +62 -0
  51. package/ts/zod/index.ts +35 -0
  52. package/ts/zod/mcp.ts +123 -0
  53. package/ts/zod/memories.ts +53 -0
  54. package/ts/zod/observability.ts +155 -0
  55. package/ts/zod/projects.ts +68 -0
  56. package/ts/zod/runs.ts +135 -0
  57. package/ts/zod/schedules.ts +82 -0
  58. package/ts/zod/slack.ts +79 -0
  59. package/ts/zod/smith-revisions.ts +50 -0
  60. package/ts/zod/smiths.ts +118 -0
  61. package/ts/zod/telegram.ts +43 -0
  62. package/ts/zod/tenant.ts +267 -0
  63. package/ts/zod/vector-stores.ts +296 -0
  64. package/ts/zod/whatsapp.ts +54 -0
package/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # `@ingram-cloud/sdk`
2
+
3
+ The Ingram Cloud `/v1` API wire contract in TypeScript — **Zod request/response
4
+ schemas + SSE/webhook event types + JSON response types** — plus a typed
5
+ **management-plane client** built on it. The schemas are hand-authored and are
6
+ the **source of truth for the wire**: the API imports the same schemas to
7
+ validate requests and to emit its OpenAPI document, and the `IC*` response
8
+ types are inferred from them.
9
+
10
+ ```ts
11
+ import { schemas } from "@ingram-cloud/sdk";
12
+
13
+ // Runtime-validate a request/response body against the contract.
14
+ const agent = schemas.AgentIn.parse(input);
15
+ ```
16
+
17
+ ```ts
18
+ import type { ICSmith, ICRun, ICAgent } from "@ingram-cloud/sdk/responses";
19
+
20
+ // Type a /v1 JSON response — no zod is pulled in.
21
+ function render(smith: ICSmith) {
22
+ /* … */
23
+ }
24
+ ```
25
+
26
+ ```ts
27
+ import { IngramCloud } from "@ingram-cloud/sdk/client";
28
+
29
+ // Typed CRUD over the management plane (smiths, agents, tenant config, …).
30
+ const ic = new IngramCloud({ token: process.env.INGRAM_CLOUD_TOKEN! });
31
+ const smith = await ic.smiths.create({ external_id: "user-42" });
32
+ ```
33
+
34
+ ## Exports
35
+
36
+ - `.` — the `schemas` Zod map plus the SSE/webhook event types (`EVENT_TYPES`,
37
+ `webhookEvent`, `streamFrame`, …).
38
+ - `./schemas` — just the Zod `schemas` map.
39
+ - `./zod` — the same schemas as individual named exports, one module per resource.
40
+ - `./responses` — the `IC*` TypeScript types for the JSON response bodies.
41
+ Zod-free; `import type` these to stay dependency-light.
42
+ - `./client` — `IngramCloud`, the typed management-plane REST client. Method
43
+ inputs are `z.input`-inferred from the same schemas the API validates with,
44
+ so the client can't drift from the contract. Zod-free at runtime (type-only
45
+ imports; transport is the global `fetch`). Auth is a pluggable token seam:
46
+ a static bearer or a per-request minting function; smith-scoped calls made
47
+ with a tenant token pass `{ smith }` (the `IC-Smith-Id` header). Non-2xx
48
+ throws `ICError { status, code, requestId }`.
49
+
50
+ The OpenAPI document is served by the API itself (`/openapi.json`), emitted from
51
+ these schemas — it is no longer shipped as a file in this package.
52
+
53
+ The client is the **management plane** only. The **data plane** stays on
54
+ industry standards: chat rides the OpenAI-compatible surface — use
55
+ `@ingram-cloud/ai-sdk` and the standard `@ai-sdk/*` types for that. The
56
+ native run stream is exposed raw (`smiths.runs.stream` returns the SSE
57
+ `Response` unconsumed).
58
+
59
+ > Ships compiled ESM (`dist/`) alongside the TypeScript source (`ts/`). Node
60
+ > and bundlers load `dist/` — no transpile config needed. Types resolve straight
61
+ > to the source, and Bun (the `bun` export condition) runs the source directly.
62
+
63
+ ## Coverage
64
+
65
+ Every resource's request bodies and non-streaming JSON responses are typed as
66
+ precise Zod (one module per resource under `./zod`), and the `IC*` types are
67
+ inferred from them. The **streaming/union** endpoints (`/runs` stream,
68
+ `/chat/completions`, `/responses` — a stream *or* JSON from one handler), deployment
69
+ **webhook acks**, and the OAuth **redirect** are not expressible as a single
70
+ response schema, so they're not in the typed surface; the `{v:1}` webhook/feed
71
+ envelope and the SSE run-stream frames are the hand-authored `./events` half.
72
+
73
+ The OpenAI-compatible stream chunks themselves are standard — use the `@ai-sdk/*`
74
+ types rather than redefining them here.
package/dist/client.js ADDED
@@ -0,0 +1,460 @@
1
+ export const DEFAULT_BASE_URL = "https://api.cloud.ingram.tech";
2
+ /** The `/v1` API version this client pins (`IC-Api-Version`). */
3
+ export const DEFAULT_API_VERSION = "2026-05-01";
4
+ /** A non-2xx `/v1` response: HTTP status + the error envelope's `code`.
5
+ * `message` carries the full context (method, path, status); `detail` is the
6
+ * envelope's bare `error.message`, suitable for user-facing copy. */
7
+ export class ICError extends Error {
8
+ status;
9
+ code;
10
+ requestId;
11
+ detail;
12
+ constructor(status, code, message, requestId, detail) {
13
+ super(message);
14
+ this.status = status;
15
+ this.code = code;
16
+ this.requestId = requestId;
17
+ this.detail = detail;
18
+ this.name = "ICError";
19
+ }
20
+ }
21
+ const enc = encodeURIComponent;
22
+ function qs(query) {
23
+ if (!query)
24
+ return "";
25
+ const p = new URLSearchParams();
26
+ for (const [k, v] of Object.entries(query)) {
27
+ if (v === undefined || v === null || v === "")
28
+ continue;
29
+ p.set(k, String(v));
30
+ }
31
+ const s = p.toString();
32
+ return s ? `?${s}` : "";
33
+ }
34
+ export class IngramCloud {
35
+ token;
36
+ base;
37
+ apiVersion;
38
+ transport;
39
+ requestInit;
40
+ constructor(opts) {
41
+ this.token = opts.token;
42
+ this.base = (opts.baseURL ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
43
+ this.apiVersion = opts.apiVersion ?? DEFAULT_API_VERSION;
44
+ this.transport = opts.fetch ?? ((url, init) => fetch(url, init));
45
+ this.requestInit = opts.requestInit ?? {};
46
+ }
47
+ /** Low-level escape hatch: an authenticated `/v1` call (path without the
48
+ * `/v1` prefix). Throws {@link ICError} on a non-2xx. */
49
+ async request(method, path, opts = {}) {
50
+ const token = opts.token ??
51
+ (typeof this.token === "function" ? await this.token() : this.token);
52
+ const headers = {
53
+ accept: "application/json",
54
+ "ic-api-version": this.apiVersion,
55
+ authorization: `Bearer ${token}`,
56
+ ...(opts.body !== undefined && opts.rawBody === undefined
57
+ ? { "content-type": "application/json" }
58
+ : {}),
59
+ ...(opts.smith ? { "ic-smith-id": opts.smith } : {}),
60
+ ...opts.headers,
61
+ };
62
+ const res = await this.transport(`${this.base}/v1${path}${qs(opts.query)}`, {
63
+ ...this.requestInit,
64
+ method,
65
+ headers,
66
+ body: opts.rawBody ??
67
+ (opts.body !== undefined ? JSON.stringify(opts.body) : undefined),
68
+ signal: opts.signal,
69
+ });
70
+ if (!res.ok) {
71
+ const body = await res.text().catch(() => "");
72
+ let code = `http_${res.status}`;
73
+ let detail;
74
+ try {
75
+ const parsed = JSON.parse(body);
76
+ code = parsed.error?.code ?? code;
77
+ detail = parsed.error?.message;
78
+ }
79
+ catch { }
80
+ const requestId = res.headers.get("x-request-id") ?? undefined;
81
+ throw new ICError(res.status, code, `IC ${method} ${path} → ${res.status} ${code}: ${detail ?? body.slice(0, 300)}${requestId ? ` [${requestId}]` : ""}`, requestId, detail);
82
+ }
83
+ return res;
84
+ }
85
+ /** {@link request}, parsed as JSON. */
86
+ async json(method, path, opts = {}) {
87
+ const res = await this.request(method, path, opts);
88
+ return res.json();
89
+ }
90
+ async page(path, query, opts) {
91
+ const r = await this.json("GET", path, { ...opts, query });
92
+ return {
93
+ data: r.data ?? [],
94
+ next_cursor: r.next_cursor ?? null,
95
+ has_more: r.has_more ?? false,
96
+ };
97
+ }
98
+ async data(method, path, opts = {}) {
99
+ const r = await this.json(method, path, opts);
100
+ return r.data ?? [];
101
+ }
102
+ async empty(method, path, opts = {}) {
103
+ await this.request(method, path, opts);
104
+ }
105
+ // ── Smiths ──────────────────────────────────────────────────────────────
106
+ smiths = {
107
+ list: (query, opts) => this.page("/smiths", query, opts),
108
+ create: (body, opts) => this.json("POST", "/smiths", { ...opts, body }),
109
+ get: (pid, opts) => this.json("GET", `/smiths/${enc(pid)}`, opts),
110
+ update: (pid, body, opts) => this.json("PATCH", `/smiths/${enc(pid)}`, { ...opts, body }),
111
+ delete: (pid, opts) => this.empty("DELETE", `/smiths/${enc(pid)}`, opts),
112
+ memory: {
113
+ get: (pid, opts) => this.json("GET", `/smiths/${enc(pid)}/memory`, opts),
114
+ set: (pid, body, opts) => this.json("PUT", `/smiths/${enc(pid)}/memory`, {
115
+ ...opts,
116
+ body,
117
+ }),
118
+ recall: (pid, body, opts) => this.data("POST", `/smiths/${enc(pid)}/memory/recall`, {
119
+ ...opts,
120
+ body,
121
+ }),
122
+ },
123
+ revisions: {
124
+ list: (pid, opts) => this.data("GET", `/smiths/${enc(pid)}/revisions`, opts),
125
+ restore: (pid, version, body = {}, opts) => this.json("POST", `/smiths/${enc(pid)}/revisions/${version}/restore`, {
126
+ ...opts,
127
+ body,
128
+ }),
129
+ },
130
+ connections: {
131
+ list: (pid, opts) => this.data("GET", `/smiths/${enc(pid)}/connections`, opts),
132
+ get: (pid, cid, opts) => this.json("GET", `/smiths/${enc(pid)}/connections/${enc(cid)}`, opts),
133
+ create: (pid, body, opts) => this.json("POST", `/smiths/${enc(pid)}/connections`, {
134
+ ...opts,
135
+ body,
136
+ }),
137
+ update: (pid, cid, body, opts) => this.json("PATCH", `/smiths/${enc(pid)}/connections/${enc(cid)}`, { ...opts, body }),
138
+ delete: (pid, cid, opts) => this.empty("DELETE", `/smiths/${enc(pid)}/connections/${enc(cid)}`, opts),
139
+ refresh: (pid, cid, opts) => this.json("POST", `/smiths/${enc(pid)}/connections/${enc(cid)}/refresh`, opts),
140
+ /** Mint a hosted-consent authorize URL the end user is sent to. */
141
+ authorize: (pid, body, opts) => this.json("POST", `/smiths/${enc(pid)}/connections/authorize`, {
142
+ ...opts,
143
+ body,
144
+ }),
145
+ },
146
+ schedules: {
147
+ list: (pid, opts) => this.data("GET", `/smiths/${enc(pid)}/schedules`, opts),
148
+ create: (pid, body, opts) => this.json("POST", `/smiths/${enc(pid)}/schedules`, {
149
+ ...opts,
150
+ body,
151
+ }),
152
+ update: (pid, sid, body, opts) => this.json("PATCH", `/smiths/${enc(pid)}/schedules/${enc(sid)}`, { ...opts, body }),
153
+ delete: (pid, sid, opts) => this.empty("DELETE", `/smiths/${enc(pid)}/schedules/${enc(sid)}`, opts),
154
+ runNow: (pid, sid, opts) => this.json("POST", `/smiths/${enc(pid)}/schedules/${enc(sid)}/run_now`, opts),
155
+ },
156
+ runs: {
157
+ list: (pid, query, opts) => this.page(`/smiths/${enc(pid)}/runs`, query, opts),
158
+ /** Non-streaming run: returns the completed (or paused) run record. */
159
+ create: (pid, body, opts) => this.json("POST", `/smiths/${enc(pid)}/runs`, {
160
+ ...opts,
161
+ body: { ...body, stream: false },
162
+ }),
163
+ /** Streaming run: returns the raw SSE `Response`, body unconsumed. */
164
+ stream: (pid, body, opts) => this.request("POST", `/smiths/${enc(pid)}/runs`, {
165
+ ...opts,
166
+ body: { ...body, stream: true },
167
+ }),
168
+ get: (pid, rid, opts) => this.json("GET", `/smiths/${enc(pid)}/runs/${enc(rid)}`, opts),
169
+ /** Resume a paused run (approval decision, tool result, cancel). */
170
+ submit: (pid, rid, body, opts) => this.json("POST", `/smiths/${enc(pid)}/runs/${enc(rid)}/submit`, { ...opts, body }),
171
+ /** Re-run a recorded run's input as a fresh run. */
172
+ replay: (pid, rid, opts) => this.json("POST", `/smiths/${enc(pid)}/runs/${enc(rid)}/replay`, { ...opts, body: {} }),
173
+ /** The recorded run events (the SSE replay endpoint, parsed). */
174
+ events: async (pid, rid, opts) => {
175
+ const res = await this.request("GET", `/smiths/${enc(pid)}/runs/${enc(rid)}/events`, opts);
176
+ const text = await res.text();
177
+ const out = [];
178
+ for (const block of text.split(/\r?\n\r?\n/)) {
179
+ let seq = 0;
180
+ let type = "";
181
+ let data = "";
182
+ for (const line of block.split(/\r?\n/)) {
183
+ if (line.startsWith("id:"))
184
+ seq = Number(line.slice(3).trim());
185
+ else if (line.startsWith("event:"))
186
+ type = line.slice(6).trim();
187
+ else if (line.startsWith("data:"))
188
+ data += line.slice(5).trim();
189
+ }
190
+ if (!data)
191
+ continue;
192
+ try {
193
+ out.push({
194
+ seq,
195
+ type,
196
+ data: JSON.parse(data),
197
+ created_at: null,
198
+ });
199
+ }
200
+ catch { }
201
+ }
202
+ return out;
203
+ },
204
+ },
205
+ };
206
+ // ── Runs (tenant-wide feed) ─────────────────────────────────────────────
207
+ runs = {
208
+ list: (query, opts) => this.page("/runs", query, opts),
209
+ };
210
+ // ── Agents ──────────────────────────────────────────────────────────────
211
+ agents = {
212
+ list: (query, opts) => this.page("/agents", query, opts),
213
+ create: (body, opts) => this.json("POST", "/agents", { ...opts, body }),
214
+ get: (aid, opts) => this.json("GET", `/agents/${enc(aid)}`, opts),
215
+ update: (aid, body, opts) => this.json("PATCH", `/agents/${enc(aid)}`, { ...opts, body }),
216
+ delete: (aid, opts) => this.empty("DELETE", `/agents/${enc(aid)}`, opts),
217
+ versions: {
218
+ list: (aid, opts) => this.data("GET", `/agents/${enc(aid)}/versions`, opts),
219
+ /** Snapshot the draft as the next immutable version. */
220
+ publish: (aid, body = {}, opts) => this.json("POST", `/agents/${enc(aid)}/versions`, {
221
+ ...opts,
222
+ body,
223
+ }),
224
+ },
225
+ /** Point smiths at a version (`percent < 100` stages a sticky rollout). */
226
+ rollout: (aid, body, opts) => this.json("POST", `/agents/${enc(aid)}/rollout`, {
227
+ ...opts,
228
+ body,
229
+ }),
230
+ /** Seed a new agent from an existing smith's effective config. */
231
+ import: (body, opts) => this.json("POST", "/agents/import", { ...opts, body }),
232
+ /** Adopt existing smiths onto this agent. */
233
+ attach: (aid, body, opts) => this.json("POST", `/agents/${enc(aid)}/attach`, { ...opts, body }),
234
+ /** MCP Apps UI templates (SEP-1865) attached to the agent's draft. */
235
+ ui: {
236
+ list: (aid, opts) => this.data("GET", `/agents/${enc(aid)}/ui`, opts),
237
+ get: (aid, name, opts) => this.json("GET", `/agents/${enc(aid)}/ui/${enc(name)}`, opts),
238
+ /** Upload/replace a template — `html` is the bundle, `meta` its
239
+ * `{ name, csp?, permissions?, tool? }` sidecar. Replaces by name. */
240
+ put: (aid, html, meta, opts) => {
241
+ const form = new FormData();
242
+ form.append("file", html instanceof Blob ? html : new Blob([html], { type: "text/html" }), `${meta.name}.html`);
243
+ form.append("metadata", JSON.stringify(meta));
244
+ return this.json("POST", `/agents/${enc(aid)}/ui`, {
245
+ ...opts,
246
+ rawBody: form,
247
+ });
248
+ },
249
+ delete: (aid, name, opts) => this.json("DELETE", `/agents/${enc(aid)}/ui/${enc(name)}`, opts),
250
+ },
251
+ };
252
+ // ── Conversations (smith-scoped: pass `{ smith }` with a tenant token) ──
253
+ conversations = {
254
+ list: (query, opts) => this.page("/conversations", query, opts),
255
+ create: (body = {}, opts) => this.json("POST", "/conversations", { ...opts, body }),
256
+ get: (cnvId, opts) => this.json("GET", `/conversations/${enc(cnvId)}`, opts),
257
+ /** OpenAI-style modify — a POST, not a PATCH. */
258
+ update: (cnvId, body, opts) => this.json("POST", `/conversations/${enc(cnvId)}`, {
259
+ ...opts,
260
+ body,
261
+ }),
262
+ delete: (cnvId, opts) => this.empty("DELETE", `/conversations/${enc(cnvId)}`, opts),
263
+ /** The faithful transcript (message / function_call / mcp_call items). */
264
+ items: (cnvId, query, opts) => this.data("GET", `/conversations/${enc(cnvId)}/items`, {
265
+ ...opts,
266
+ query,
267
+ }),
268
+ };
269
+ // ── Approvals / events ──────────────────────────────────────────────────
270
+ approvals = {
271
+ list: (query, opts) => this.page("/approvals", query, opts),
272
+ get: (aprId, opts) => this.json("GET", `/approvals/${enc(aprId)}`, opts),
273
+ };
274
+ events = {
275
+ list: (query, opts) => this.page("/events", query, opts),
276
+ };
277
+ // ── Customers / budgets ─────────────────────────────────────────────────
278
+ customers = {
279
+ list: (query, opts) => this.page("/customers", query, opts),
280
+ create: (body, opts) => this.json("POST", "/customers", { ...opts, body }),
281
+ get: (cid, opts) => this.json("GET", `/customers/${enc(cid)}`, opts),
282
+ update: (cid, body, opts) => this.json("PATCH", `/customers/${enc(cid)}`, { ...opts, body }),
283
+ delete: (cid, opts) => this.empty("DELETE", `/customers/${enc(cid)}`, opts),
284
+ };
285
+ budgets = {
286
+ list: (opts) => this.data("GET", "/budgets", opts),
287
+ create: (body, opts) => this.json("POST", "/budgets", { ...opts, body }),
288
+ get: (bid, opts) => this.json("GET", `/budgets/${enc(bid)}`, opts),
289
+ update: (bid, body, opts) => this.json("PATCH", `/budgets/${enc(bid)}`, { ...opts, body }),
290
+ delete: (bid, opts) => this.empty("DELETE", `/budgets/${enc(bid)}`, opts),
291
+ status: (bid, opts) => this.json("GET", `/budgets/${enc(bid)}/status`, opts),
292
+ };
293
+ // ── Deployments (smith/agent bound to a messaging surface) ──────────────
294
+ deployments = {
295
+ list: (query, opts) => this.page("/deployments", query, opts),
296
+ create: (body, opts) => this.json("POST", "/deployments", { ...opts, body }),
297
+ get: (depId, opts) => this.json("GET", `/deployments/${enc(depId)}`, opts),
298
+ update: (depId, body, opts) => this.json("PATCH", `/deployments/${enc(depId)}`, {
299
+ ...opts,
300
+ body,
301
+ }),
302
+ delete: (depId, opts) => this.empty("DELETE", `/deployments/${enc(depId)}`, opts),
303
+ };
304
+ // ── Catalog (Ingram-curated MCP integration presets) ────────────────────
305
+ catalog = {
306
+ list: (opts) => this.data("GET", "/catalog", opts),
307
+ get: (slug, opts) => this.json("GET", `/catalog/${enc(slug)}`, opts),
308
+ };
309
+ // ── Observability ───────────────────────────────────────────────────────
310
+ traces = {
311
+ list: (query, opts) => this.page("/traces", query, opts),
312
+ get: (traceId, opts) => this.json("GET", `/traces/${enc(traceId)}`, opts),
313
+ };
314
+ usage = {
315
+ /** Token/cost/run totals grouped by app, smith, model, or customer. */
316
+ breakdown: (query, opts) => this.json("GET", "/usage", { ...opts, query }),
317
+ };
318
+ // ── Files (the OpenAI Files API) ─────────────────────────────────────────
319
+ files = {
320
+ /** Multipart upload. `file` is a `File`/`Blob`; `purpose` defaults to
321
+ * `assistants` (the vector-store source purpose). */
322
+ upload: (file, opts) => {
323
+ const form = new FormData();
324
+ form.set("file", file, opts?.filename ?? (file instanceof File ? file.name : "file"));
325
+ form.set("purpose", opts?.purpose ?? "assistants");
326
+ return this.json("POST", "/files", { ...opts, rawBody: form });
327
+ },
328
+ /** Uploads only (OpenAI `list` envelope); inline files stay unlisted. */
329
+ list: (query, opts) => this.json("GET", "/files", { ...opts, query }),
330
+ get: (id, opts) => this.json("GET", `/files/${enc(id)}`, opts),
331
+ /** The raw bytes `Response` (follows the presigned-URL redirect). */
332
+ content: (id, opts) => this.request("GET", `/files/${enc(id)}/content`, opts),
333
+ delete: (id, opts) => this.json("DELETE", `/files/${enc(id)}`, opts),
334
+ };
335
+ // ── Vector stores (the OpenAI Vector Stores API) ─────────────────────────
336
+ vectorStores = {
337
+ create: (body, opts) => this.json("POST", "/vector_stores", { ...opts, body }),
338
+ list: (query, opts) => this.json("GET", "/vector_stores", { ...opts, query }),
339
+ get: (vsId, opts) => this.json("GET", `/vector_stores/${enc(vsId)}`, opts),
340
+ /** Modify (OpenAI uses `POST`, not `PATCH`). */
341
+ update: (vsId, body, opts) => this.json("POST", `/vector_stores/${enc(vsId)}`, { ...opts, body }),
342
+ delete: (vsId, opts) => this.json("DELETE", `/vector_stores/${enc(vsId)}`, opts),
343
+ search: (vsId, body, opts) => this.json("POST", `/vector_stores/${enc(vsId)}/search`, {
344
+ ...opts,
345
+ body,
346
+ }),
347
+ files: {
348
+ create: (vsId, body, opts) => this.json("POST", `/vector_stores/${enc(vsId)}/files`, {
349
+ ...opts,
350
+ body,
351
+ }),
352
+ list: (vsId, query, opts) => this.json("GET", `/vector_stores/${enc(vsId)}/files`, { ...opts, query }),
353
+ get: (vsId, fileId, opts) => this.json("GET", `/vector_stores/${enc(vsId)}/files/${enc(fileId)}`, opts),
354
+ update: (vsId, fileId, body, opts) => this.json("POST", `/vector_stores/${enc(vsId)}/files/${enc(fileId)}`, { ...opts, body }),
355
+ delete: (vsId, fileId, opts) => this.json("DELETE", `/vector_stores/${enc(vsId)}/files/${enc(fileId)}`, opts),
356
+ },
357
+ fileBatches: {
358
+ create: (vsId, body, opts) => this.json("POST", `/vector_stores/${enc(vsId)}/file_batches`, { ...opts, body }),
359
+ get: (vsId, batchId, opts) => this.json("GET", `/vector_stores/${enc(vsId)}/file_batches/${enc(batchId)}`, opts),
360
+ cancel: (vsId, batchId, opts) => this.json("POST", `/vector_stores/${enc(vsId)}/file_batches/${enc(batchId)}/cancel`, opts),
361
+ files: (vsId, batchId, query, opts) => this.json("GET", `/vector_stores/${enc(vsId)}/file_batches/${enc(batchId)}/files`, { ...opts, query }),
362
+ },
363
+ };
364
+ // ── Tenant config ───────────────────────────────────────────────────────
365
+ tenant = {
366
+ usage: (opts) => this.json("GET", "/tenant/usage", opts),
367
+ models: (opts) => this.json("GET", "/tenant/models", opts),
368
+ hostedTools: (opts) => this.data("GET", "/tenant/hosted_tools", opts),
369
+ tokens: {
370
+ list: (query, opts) => this.page("/tenant/tokens", query, opts),
371
+ /** Mint a tenant-admin or smith token (secret shown once). */
372
+ create: (body, opts) => this.json("POST", "/tenant/tokens", { ...opts, body }),
373
+ revoke: (tid, opts) => this.empty("DELETE", `/tenant/tokens/${enc(tid)}`, opts),
374
+ },
375
+ webhooks: {
376
+ list: (query, opts) => this.page("/tenant/webhooks", query, opts),
377
+ /** Returns the signing `secret` exactly once. */
378
+ create: (body, opts) => this.json("POST", "/tenant/webhooks", { ...opts, body }),
379
+ update: (wid, body, opts) => this.json("PATCH", `/tenant/webhooks/${enc(wid)}`, {
380
+ ...opts,
381
+ body,
382
+ }),
383
+ delete: (wid, opts) => this.empty("DELETE", `/tenant/webhooks/${enc(wid)}`, opts),
384
+ test: (wid, opts) => this.json("POST", `/tenant/webhooks/${enc(wid)}/test`, opts),
385
+ },
386
+ providers: {
387
+ list: (opts) => this.data("GET", "/tenant/providers", opts),
388
+ get: (provider, opts) => this.json("GET", `/tenant/providers/${enc(provider)}`, opts),
389
+ put: (provider, body, opts) => this.json("PUT", `/tenant/providers/${enc(provider)}`, {
390
+ ...opts,
391
+ body,
392
+ }),
393
+ delete: (provider, opts) => this.empty("DELETE", `/tenant/providers/${enc(provider)}`, opts),
394
+ },
395
+ modelKeys: {
396
+ list: (opts) => this.data("GET", "/tenant/model_keys", opts),
397
+ /** Store a BYOK model-provider key (never read back). */
398
+ put: (provider, body, opts) => this.json("PUT", `/tenant/model_keys/${enc(provider)}`, {
399
+ ...opts,
400
+ body,
401
+ }),
402
+ delete: (provider, opts) => this.empty("DELETE", `/tenant/model_keys/${enc(provider)}`, opts),
403
+ },
404
+ mcp: {
405
+ list: (opts) => this.data("GET", "/tenant/mcp", opts),
406
+ get: (name, opts) => this.json("GET", `/tenant/mcp/${enc(name)}`, opts),
407
+ /** Register or replace a server (full replace; probes `tools/list`). */
408
+ put: (name, body, opts) => this.json("PUT", `/tenant/mcp/${enc(name)}`, { ...opts, body }),
409
+ refresh: (name, opts) => this.json("POST", `/tenant/mcp/${enc(name)}/refresh`, opts),
410
+ delete: (name, opts) => this.empty("DELETE", `/tenant/mcp/${enc(name)}`, opts),
411
+ },
412
+ telegram: {
413
+ get: (opts) => this.json("GET", "/tenant/telegram", opts),
414
+ put: (body, opts) => this.json("PUT", "/tenant/telegram", { ...opts, body }),
415
+ delete: (opts) => this.empty("DELETE", "/tenant/telegram", opts),
416
+ },
417
+ slack: {
418
+ get: (opts) => this.json("GET", "/tenant/slack", opts),
419
+ put: (body, opts) => this.json("PUT", "/tenant/slack", { ...opts, body }),
420
+ delete: (opts) => this.empty("DELETE", "/tenant/slack", opts),
421
+ },
422
+ discord: {
423
+ get: (opts) => this.json("GET", "/tenant/discord", opts),
424
+ put: (body, opts) => this.json("PUT", "/tenant/discord", { ...opts, body }),
425
+ delete: (opts) => this.empty("DELETE", "/tenant/discord", opts),
426
+ },
427
+ whatsapp: {
428
+ get: (opts) => this.json("GET", "/tenant/whatsapp", opts),
429
+ put: (body, opts) => this.json("PUT", "/tenant/whatsapp", {
430
+ ...opts,
431
+ body,
432
+ }),
433
+ delete: (opts) => this.empty("DELETE", "/tenant/whatsapp", opts),
434
+ },
435
+ email: {
436
+ get: (opts) => this.json("GET", "/tenant/email", opts),
437
+ put: (body, opts) => this.json("PUT", "/tenant/email", { ...opts, body }),
438
+ delete: (opts) => this.empty("DELETE", "/tenant/email", opts),
439
+ },
440
+ };
441
+ // ── Organization (org token: projects + billing) ────────────────────────
442
+ organization = {
443
+ projects: {
444
+ list: (opts) => this.data("GET", "/organization/projects", opts),
445
+ create: (body, opts) => this.json("POST", "/organization/projects", {
446
+ ...opts,
447
+ body,
448
+ }),
449
+ get: (pid, opts) => this.json("GET", `/organization/projects/${enc(pid)}`, opts),
450
+ delete: (pid, opts) => this.empty("DELETE", `/organization/projects/${enc(pid)}`, opts),
451
+ tokens: {
452
+ create: (pid, body, opts) => this.json("POST", `/organization/projects/${enc(pid)}/tokens`, {
453
+ ...opts,
454
+ body,
455
+ }),
456
+ delete: (pid, tid, opts) => this.empty("DELETE", `/organization/projects/${enc(pid)}/tokens/${enc(tid)}`, opts),
457
+ },
458
+ },
459
+ };
460
+ }
package/dist/events.js ADDED
@@ -0,0 +1,108 @@
1
+ /**
2
+ * The Ingram Cloud event types — HAND-AUTHORED (not generated).
3
+ *
4
+ * Two event surfaces ride the native `{v:1}` envelope and are deliberately NOT in
5
+ * `openapi.json`: OpenAPI can't describe an SSE frame sequence (even OpenAI's own
6
+ * spec doesn't type its stream chunks — SDKs hand-define them). So these live
7
+ * here, hand-authored, and must be kept in step with
8
+ * `web/src/content/docs/events.md` (the canonical catalog).
9
+ *
10
+ * 1. {@link webhookEvent} — the append-only feed / webhook delivery envelope
11
+ * (`GET /v1/events`, signed webhook POSTs).
12
+ * 2. {@link streamFrame} — the live SSE run-stream frames (`POST .../runs` with
13
+ * `stream:true`), i.e. `runs.py::_stream`.
14
+ *
15
+ * `data` is `.passthrough()` on purpose: the catalog documents the *notable*
16
+ * fields per type, not an exhaustive closed shape — same philosophy as the
17
+ * `extra="allow"` response models. Consumers narrow on `type` / `event`.
18
+ */
19
+ import { z } from "zod";
20
+ // ─── Feed / webhook event types (docs/events.md "Event type catalog") ────────
21
+ // Only these reach the `/v1/events` feed and signed webhooks. Pure run-stream frames
22
+ // — `run.started`, `message.delta`, `message.completed`, `run.cancelled` — ride the
23
+ // live SSE stream and the per-run timeline only; they are NOT feed events (see
24
+ // {@link STREAM_EVENTS}). `tool.executing` / `tool.completed` are both: a live frame
25
+ // AND a feed event.
26
+ export const EVENT_TYPES = [
27
+ "run.paused",
28
+ "run.completed",
29
+ "run.failed",
30
+ "tool.executing",
31
+ "tool.completed",
32
+ "approval.required",
33
+ "approval.resolved",
34
+ "connection.required",
35
+ "unbound_message",
36
+ "budget.threshold",
37
+ "credit.exhausted",
38
+ "deployment.bound",
39
+ "deployment.inbound",
40
+ "slack.app_provisioned",
41
+ "slack.install",
42
+ "slack.uninstalled",
43
+ "email.send_failed",
44
+ "webhook.test",
45
+ ];
46
+ export const eventType = z.enum(EVENT_TYPES);
47
+ /**
48
+ * The envelope carried identically by the `/v1/events` feed and signed webhook
49
+ * POSTs. `type` is a plain string (not the enum) so a forward-added type still
50
+ * parses; compare against {@link EVENT_TYPES} / {@link eventType} when narrowing.
51
+ */
52
+ export const webhookEvent = z
53
+ .object({
54
+ v: z.literal(1),
55
+ id: z.string(),
56
+ type: z.string(),
57
+ created_at: z.string(),
58
+ tenant_id: z.string(),
59
+ smith_id: z.string().nullable().optional(),
60
+ data: z.record(z.string(), z.unknown()).default({}),
61
+ })
62
+ .passthrough();
63
+ // ─── Native SSE run-stream frames (runs.py::_stream `{v:1, run_id, ...}`) ─────
64
+ // The SSE `event:` line becomes `event`; the frame's data fields sit alongside
65
+ // `v` / `run_id`. `tool.executing` / `tool.completed` ride the live stream as they
66
+ // happen AND are mirrored to the run timeline + feed (see EVENT_TYPES) so a run's
67
+ // tool activity is auditable after the fact. The standard surface expresses the same
68
+ // live status on the OpenAI Responses API (`/v1/responses`) as `mcp_call` items
69
+ // (`response.mcp_call.in_progress` / `.completed`); those are standard OpenAI shapes,
70
+ // so we don't re-type them here — consume them with the OpenAI/AI SDK. These native
71
+ // frames are the place to retire as that lands everywhere (see CLAUDE.md).
72
+ export const STREAM_EVENTS = [
73
+ "run.started",
74
+ "message.delta",
75
+ "tool.executing",
76
+ "tool.completed",
77
+ "run.paused",
78
+ "approval.required",
79
+ "run.completed",
80
+ "run.failed",
81
+ "run.cancelled",
82
+ "run.duplicate",
83
+ "message.completed",
84
+ ];
85
+ export const streamEventName = z.enum(STREAM_EVENTS);
86
+ export const streamFrame = z
87
+ .object({
88
+ event: z.string(),
89
+ v: z.number().optional(),
90
+ run_id: z.string().optional(),
91
+ })
92
+ .passthrough();
93
+ // ─── A few well-known `data` shapes (convenience; still passthrough) ─────────
94
+ export const messageDeltaData = z.object({ delta: z.string() }).passthrough();
95
+ export const runCompletedData = z
96
+ .object({
97
+ stop_reason: z.string(),
98
+ usage: z.record(z.string(), z.unknown()).optional(),
99
+ })
100
+ .passthrough();
101
+ export const approvalRequiredData = z
102
+ .object({
103
+ approval_id: z.string(),
104
+ tool: z.string().nullable().optional(),
105
+ args: z.record(z.string(), z.unknown()).optional(),
106
+ })
107
+ .passthrough();
108
+ export const toolActivityData = z.object({ tool: z.string().nullable() }).passthrough();
package/dist/index.js ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The Ingram Cloud API wire contract, in TypeScript.
3
+ *
4
+ * `schemas` is the named map of hand-authored Zod schemas for the request and
5
+ * response bodies (defined in `./zod`, one module per resource). It's the source
6
+ * of truth for the wire shapes — the API imports the same schemas to validate
7
+ * requests and to emit its OpenAPI document — and you can use it to validate a
8
+ * body against the contract. This package is not an HTTP client.
9
+ *
10
+ * `./responses` is the matching `IC*` TypeScript types (inferred from the same
11
+ * schemas), for typing the JSON you read back without pulling in Zod.
12
+ *
13
+ * `./events` is the hand-authored `{v:1}` webhook/feed envelope and the SSE
14
+ * run-stream frames, which OpenAPI can't express.
15
+ *
16
+ * See `../README.md`.
17
+ */
18
+ export { schemas } from "./schemas.js";
19
+ export * from "./events.js";