@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.
- package/README.md +74 -0
- package/dist/client.js +460 -0
- package/dist/events.js +108 -0
- package/dist/index.js +19 -0
- package/dist/responses.js +12 -0
- package/dist/schemas.js +4 -0
- package/dist/zod/_page.js +17 -0
- package/dist/zod/agents.js +176 -0
- package/dist/zod/approvals.js +38 -0
- package/dist/zod/budgets.js +62 -0
- package/dist/zod/catalog.js +49 -0
- package/dist/zod/connections.js +75 -0
- package/dist/zod/conversations.js +91 -0
- package/dist/zod/customers.js +53 -0
- package/dist/zod/deployments.js +81 -0
- package/dist/zod/discord.js +32 -0
- package/dist/zod/email.js +45 -0
- package/dist/zod/files.js +55 -0
- package/dist/zod/index.js +35 -0
- package/dist/zod/mcp.js +107 -0
- package/dist/zod/memories.js +43 -0
- package/dist/zod/observability.js +133 -0
- package/dist/zod/projects.js +58 -0
- package/dist/zod/runs.js +119 -0
- package/dist/zod/schedules.js +71 -0
- package/dist/zod/slack.js +69 -0
- package/dist/zod/smith-revisions.js +42 -0
- package/dist/zod/smiths.js +108 -0
- package/dist/zod/telegram.js +36 -0
- package/dist/zod/tenant.js +219 -0
- package/dist/zod/vector-stores.js +251 -0
- package/dist/zod/whatsapp.js +47 -0
- package/package.json +56 -0
- package/ts/client.ts +1187 -0
- package/ts/events.ts +119 -0
- package/ts/index.ts +20 -0
- package/ts/responses.ts +83 -0
- package/ts/schemas.ts +4 -0
- package/ts/zod/_page.ts +18 -0
- package/ts/zod/agents.ts +202 -0
- package/ts/zod/approvals.ts +44 -0
- package/ts/zod/budgets.ts +75 -0
- package/ts/zod/catalog.ts +57 -0
- package/ts/zod/connections.ts +87 -0
- package/ts/zod/conversations.ts +103 -0
- package/ts/zod/customers.ts +62 -0
- package/ts/zod/deployments.ts +93 -0
- package/ts/zod/discord.ts +39 -0
- package/ts/zod/email.ts +52 -0
- package/ts/zod/files.ts +62 -0
- package/ts/zod/index.ts +35 -0
- package/ts/zod/mcp.ts +123 -0
- package/ts/zod/memories.ts +53 -0
- package/ts/zod/observability.ts +155 -0
- package/ts/zod/projects.ts +68 -0
- package/ts/zod/runs.ts +135 -0
- package/ts/zod/schedules.ts +82 -0
- package/ts/zod/slack.ts +79 -0
- package/ts/zod/smith-revisions.ts +50 -0
- package/ts/zod/smiths.ts +118 -0
- package/ts/zod/telegram.ts +43 -0
- package/ts/zod/tenant.ts +267 -0
- package/ts/zod/vector-stores.ts +296 -0
- 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";
|