@ingram-cloud/sdk 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -35
- package/dist/client.js +240 -23
- package/dist/index.js +4 -0
- package/dist/scopes.js +52 -0
- package/dist/zod/_actor.js +33 -0
- package/dist/zod/_page.js +18 -4
- package/dist/zod/agents.js +7 -3
- package/dist/zod/approvals.js +9 -0
- package/dist/zod/billing.js +136 -0
- package/dist/zod/budgets.js +2 -3
- package/dist/zod/connections.js +2 -3
- package/dist/zod/conversations.js +4 -11
- package/dist/zod/deployments.js +32 -0
- package/dist/zod/files.js +2 -9
- package/dist/zod/index.js +3 -0
- package/dist/zod/mcp.js +8 -10
- package/dist/zod/observability.js +38 -19
- package/dist/zod/projects.js +4 -4
- package/dist/zod/runs.js +19 -4
- package/dist/zod/schedules.js +2 -7
- package/dist/zod/skills.js +73 -0
- package/dist/zod/smith-revisions.js +2 -3
- package/dist/zod/smiths.js +6 -0
- package/dist/zod/tenant.js +24 -4
- package/dist/zod/vector-stores.js +6 -19
- package/package.json +25 -18
- package/ts/client.ts +456 -60
- package/ts/index.ts +4 -0
- package/ts/responses.ts +40 -2
- package/ts/scopes.ts +57 -0
- package/ts/zod/_actor.ts +36 -0
- package/ts/zod/_page.ts +19 -4
- package/ts/zod/agents.ts +7 -3
- package/ts/zod/approvals.ts +9 -0
- package/ts/zod/billing.ts +168 -0
- package/ts/zod/budgets.ts +2 -3
- package/ts/zod/connections.ts +2 -3
- package/ts/zod/conversations.ts +7 -11
- package/ts/zod/deployments.ts +36 -0
- package/ts/zod/files.ts +2 -9
- package/ts/zod/index.ts +3 -0
- package/ts/zod/mcp.ts +8 -11
- package/ts/zod/observability.ts +74 -24
- package/ts/zod/projects.ts +4 -4
- package/ts/zod/runs.ts +21 -4
- package/ts/zod/schedules.ts +2 -7
- package/ts/zod/skills.ts +85 -0
- package/ts/zod/smith-revisions.ts +2 -3
- package/ts/zod/smiths.ts +6 -0
- package/ts/zod/tenant.ts +33 -5
- package/ts/zod/vector-stores.ts +9 -19
package/README.md
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
# `@ingram-cloud/sdk`
|
|
2
2
|
|
|
3
|
-
The Ingram Cloud `/v1` API
|
|
4
|
-
schemas
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
types are inferred from them.
|
|
3
|
+
The Ingram Cloud `/v1` API contract in TypeScript: Zod request/response
|
|
4
|
+
schemas, SSE/webhook event types, JSON response types, and a typed
|
|
5
|
+
management-plane client built on them. The schemas are hand-authored; the API
|
|
6
|
+
imports them to validate requests and to emit its OpenAPI document, and the
|
|
7
|
+
`IC*` response types are inferred from them.
|
|
9
8
|
|
|
10
9
|
```ts
|
|
11
10
|
import { schemas } from "@ingram-cloud/sdk";
|
|
@@ -33,42 +32,42 @@ const smith = await ic.smiths.create({ external_id: "user-42" });
|
|
|
33
32
|
|
|
34
33
|
## Exports
|
|
35
34
|
|
|
36
|
-
-
|
|
35
|
+
- `.`: the `schemas` Zod map plus the SSE/webhook event types (`EVENT_TYPES`,
|
|
37
36
|
`webhookEvent`, `streamFrame`, …).
|
|
38
|
-
- `./schemas
|
|
39
|
-
- `./zod
|
|
40
|
-
- `./responses
|
|
41
|
-
Zod-free; `import type` these
|
|
42
|
-
- `./client
|
|
43
|
-
inputs are `z.input`-inferred from the
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
a
|
|
47
|
-
|
|
48
|
-
|
|
37
|
+
- `./schemas`: the Zod `schemas` map only.
|
|
38
|
+
- `./zod`: the same schemas as individual named exports, one module per resource.
|
|
39
|
+
- `./responses`: the `IC*` TypeScript types for the JSON response bodies.
|
|
40
|
+
Zod-free; `import type` these.
|
|
41
|
+
- `./client`: `IngramCloud`, the typed management-plane REST client. Method
|
|
42
|
+
inputs are `z.input`-inferred from the schemas the API validates with. Zod-free
|
|
43
|
+
at runtime (type-only imports; transport is the global `fetch`). Auth is a
|
|
44
|
+
static bearer or a per-request minting function. Smith-scoped calls made with
|
|
45
|
+
a tenant token pass `{ smith }`, sent as the `IC-Smith-Id` header. Non-2xx
|
|
46
|
+
throws `ICError { status, code, requestId }`. A 429 or 503 that names a
|
|
47
|
+
`Retry-After` is retried, up to four attempts and a minute's wait; a 402 is
|
|
48
|
+
never retried.
|
|
49
49
|
|
|
50
|
-
The OpenAPI document is served by the API
|
|
51
|
-
|
|
50
|
+
The OpenAPI document is served by the API at `/openapi.json`, emitted from these
|
|
51
|
+
schemas.
|
|
52
52
|
|
|
53
|
-
The client
|
|
54
|
-
|
|
55
|
-
`@
|
|
56
|
-
|
|
57
|
-
`Response` unconsumed).
|
|
53
|
+
The client covers the management plane only. Chat goes through the
|
|
54
|
+
OpenAI-compatible surface: use `@ingram-cloud/ai-sdk` and the standard
|
|
55
|
+
`@ai-sdk/*` types. The native run stream is exposed raw: `smiths.runs.stream`
|
|
56
|
+
returns the SSE `Response` unconsumed.
|
|
58
57
|
|
|
59
58
|
> Ships compiled ESM (`dist/`) alongside the TypeScript source (`ts/`). Node
|
|
60
|
-
> and bundlers load `dist
|
|
61
|
-
>
|
|
59
|
+
> and bundlers load `dist/`. Types resolve to the source, and Bun (the `bun`
|
|
60
|
+
> export condition) runs the source directly.
|
|
62
61
|
|
|
63
62
|
## Coverage
|
|
64
63
|
|
|
65
64
|
Every resource's request bodies and non-streaming JSON responses are typed as
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
65
|
+
Zod (one module per resource under `./zod`), with the `IC*` types inferred from
|
|
66
|
+
them. Not in the typed surface, because no single response schema expresses
|
|
67
|
+
them: the streaming/union endpoints (`/runs` stream, `/chat/completions`,
|
|
68
|
+
`/responses`, each a stream or JSON from one handler), deployment webhook acks,
|
|
69
|
+
and the OAuth redirect. The `{v:1}` webhook/feed envelope and the SSE
|
|
70
|
+
run-stream frames are the hand-authored `./events` half.
|
|
72
71
|
|
|
73
|
-
The OpenAI-compatible stream chunks
|
|
74
|
-
|
|
72
|
+
The OpenAI-compatible stream chunks are standard; use the `@ai-sdk/*` types for
|
|
73
|
+
them.
|
package/dist/client.js
CHANGED
|
@@ -19,6 +19,61 @@ export class ICError extends Error {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
const enc = encodeURIComponent;
|
|
22
|
+
/** Attempts per request, including the first. Small on purpose: the server
|
|
23
|
+
* tells us when to come back, so this is a bound on pathological cases, not
|
|
24
|
+
* a backoff strategy. */
|
|
25
|
+
const MAX_ATTEMPTS = 4;
|
|
26
|
+
/** Longest we will sit out one `Retry-After`. A server (or an intermediary that
|
|
27
|
+
* never heard of this API) can name an hour; a client library must not silently
|
|
28
|
+
* block a caller for one. Past this we stop retrying and surface the refusal, so
|
|
29
|
+
* the caller decides. */
|
|
30
|
+
const MAX_WAIT_MS = 60_000;
|
|
31
|
+
/**
|
|
32
|
+
* How long this response says to wait, or null if it does not say — which is
|
|
33
|
+
* itself the answer: a 402 carries no `Retry-After` because the wallet will not
|
|
34
|
+
* refill because we asked twice.
|
|
35
|
+
*
|
|
36
|
+
* RFC 9110 allows both forms, and intermediaries do send the date one, so parse
|
|
37
|
+
* both. Anything unparseable is "no usable instruction", never a zero-delay
|
|
38
|
+
* hammer at an upstream that is already struggling.
|
|
39
|
+
*/
|
|
40
|
+
function retryAfterMs(res, now) {
|
|
41
|
+
const raw = res.headers.get("retry-after")?.trim();
|
|
42
|
+
if (!raw)
|
|
43
|
+
return null;
|
|
44
|
+
const seconds = Number(raw);
|
|
45
|
+
const ms = Number.isFinite(seconds) ? seconds * 1000 : Date.parse(raw) - now;
|
|
46
|
+
if (!Number.isFinite(ms))
|
|
47
|
+
return null;
|
|
48
|
+
return Math.max(0, ms);
|
|
49
|
+
}
|
|
50
|
+
/** Retry only what retrying can fix, and only when told how long to wait. */
|
|
51
|
+
function retryDelay(res, now) {
|
|
52
|
+
if (res.status !== 429 && res.status !== 503)
|
|
53
|
+
return null;
|
|
54
|
+
const ms = retryAfterMs(res, now);
|
|
55
|
+
return ms === null || ms > MAX_WAIT_MS ? null : ms;
|
|
56
|
+
}
|
|
57
|
+
/** The `Retry-After` wait, abortable: a caller cancelling mid-wait should not
|
|
58
|
+
* sit out the rest of a Retry-After that can be tens of seconds — reject as
|
|
59
|
+
* soon as `signal` fires, the same way an aborted `transport()` call would. */
|
|
60
|
+
function sleep(ms, signal) {
|
|
61
|
+
if (!signal)
|
|
62
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
63
|
+
if (signal.aborted)
|
|
64
|
+
return Promise.reject(signal.reason ?? new Error("aborted"));
|
|
65
|
+
return new Promise((resolve, reject) => {
|
|
66
|
+
const timer = setTimeout(() => {
|
|
67
|
+
signal.removeEventListener("abort", onAbort);
|
|
68
|
+
resolve();
|
|
69
|
+
}, ms);
|
|
70
|
+
const onAbort = () => {
|
|
71
|
+
clearTimeout(timer);
|
|
72
|
+
reject(signal.reason ?? new Error("aborted"));
|
|
73
|
+
};
|
|
74
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
75
|
+
});
|
|
76
|
+
}
|
|
22
77
|
function qs(query) {
|
|
23
78
|
if (!query)
|
|
24
79
|
return "";
|
|
@@ -31,6 +86,52 @@ function qs(query) {
|
|
|
31
86
|
const s = p.toString();
|
|
32
87
|
return s ? `?${s}` : "";
|
|
33
88
|
}
|
|
89
|
+
/** Build the multipart body `/v1/skills` accepts.
|
|
90
|
+
*
|
|
91
|
+
* The path rides the part's *filename*, slashes and all — that is how the
|
|
92
|
+
* bundle's directory structure survives a multipart body. `FormData` in Node,
|
|
93
|
+
* Bun and browsers all pass it through verbatim. */
|
|
94
|
+
function bundleForm(bundle) {
|
|
95
|
+
const form = new FormData();
|
|
96
|
+
if (bundle instanceof Blob) {
|
|
97
|
+
form.append("file", bundle, "bundle.zip");
|
|
98
|
+
return form;
|
|
99
|
+
}
|
|
100
|
+
for (const file of bundle) {
|
|
101
|
+
// A `Uint8Array`'s buffer type is generic (and may be a `SharedArrayBuffer`),
|
|
102
|
+
// which `Blob`'s constructor does not accept — copy into a fresh one, whose
|
|
103
|
+
// buffer is always a plain `ArrayBuffer`.
|
|
104
|
+
const part = typeof file.content === "string" || file.content instanceof Blob
|
|
105
|
+
? file.content
|
|
106
|
+
: new Uint8Array(file.content);
|
|
107
|
+
const blob = part instanceof Blob
|
|
108
|
+
? part
|
|
109
|
+
: new Blob([part], { type: mediaTypeFor(file.path) });
|
|
110
|
+
form.append("files[]", blob, file.path);
|
|
111
|
+
}
|
|
112
|
+
return form;
|
|
113
|
+
}
|
|
114
|
+
/** A `Blob` built from a string has no type of its own; the server falls back to
|
|
115
|
+
* `application/octet-stream` when a part carries none, which would make every
|
|
116
|
+
* text file non-indexable. Name the common ones from the extension. */
|
|
117
|
+
function mediaTypeFor(path) {
|
|
118
|
+
const ext = path.slice(path.lastIndexOf(".") + 1).toLowerCase();
|
|
119
|
+
const known = {
|
|
120
|
+
md: "text/markdown",
|
|
121
|
+
markdown: "text/markdown",
|
|
122
|
+
txt: "text/plain",
|
|
123
|
+
json: "application/json",
|
|
124
|
+
yaml: "text/yaml",
|
|
125
|
+
yml: "text/yaml",
|
|
126
|
+
csv: "text/csv",
|
|
127
|
+
py: "text/x-python",
|
|
128
|
+
sh: "text/x-shellscript",
|
|
129
|
+
js: "text/javascript",
|
|
130
|
+
ts: "text/typescript",
|
|
131
|
+
html: "text/html",
|
|
132
|
+
};
|
|
133
|
+
return known[ext] ?? "application/octet-stream";
|
|
134
|
+
}
|
|
34
135
|
export class IngramCloud {
|
|
35
136
|
token;
|
|
36
137
|
base;
|
|
@@ -59,14 +160,23 @@ export class IngramCloud {
|
|
|
59
160
|
...(opts.smith ? { "ic-smith-id": opts.smith } : {}),
|
|
60
161
|
...opts.headers,
|
|
61
162
|
};
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
163
|
+
let res;
|
|
164
|
+
for (let attempt = 1;; attempt++) {
|
|
165
|
+
res = await this.transport(`${this.base}/v1${path}${qs(opts.query)}`, {
|
|
166
|
+
...this.requestInit,
|
|
167
|
+
method,
|
|
168
|
+
headers,
|
|
169
|
+
body: opts.rawBody ??
|
|
170
|
+
(opts.body !== undefined ? JSON.stringify(opts.body) : undefined),
|
|
171
|
+
signal: opts.signal,
|
|
172
|
+
});
|
|
173
|
+
if (res.ok || attempt >= MAX_ATTEMPTS)
|
|
174
|
+
break;
|
|
175
|
+
const wait = retryDelay(res, Date.now());
|
|
176
|
+
if (wait === null)
|
|
177
|
+
break;
|
|
178
|
+
await sleep(wait, opts.signal);
|
|
179
|
+
}
|
|
70
180
|
if (!res.ok) {
|
|
71
181
|
const body = await res.text().catch(() => "");
|
|
72
182
|
let code = `http_${res.status}`;
|
|
@@ -121,14 +231,14 @@ export class IngramCloud {
|
|
|
121
231
|
}),
|
|
122
232
|
},
|
|
123
233
|
revisions: {
|
|
124
|
-
list: (pid, opts) => this.
|
|
234
|
+
list: (pid, query, opts) => this.page(`/smiths/${enc(pid)}/revisions`, query, opts),
|
|
125
235
|
restore: (pid, version, body = {}, opts) => this.json("POST", `/smiths/${enc(pid)}/revisions/${version}/restore`, {
|
|
126
236
|
...opts,
|
|
127
237
|
body,
|
|
128
238
|
}),
|
|
129
239
|
},
|
|
130
240
|
connections: {
|
|
131
|
-
list: (pid, opts) => this.
|
|
241
|
+
list: (pid, query, opts) => this.page(`/smiths/${enc(pid)}/connections`, query, opts),
|
|
132
242
|
get: (pid, cid, opts) => this.json("GET", `/smiths/${enc(pid)}/connections/${enc(cid)}`, opts),
|
|
133
243
|
create: (pid, body, opts) => this.json("POST", `/smiths/${enc(pid)}/connections`, {
|
|
134
244
|
...opts,
|
|
@@ -151,7 +261,7 @@ export class IngramCloud {
|
|
|
151
261
|
delete: (pid, provider, opts) => this.empty("DELETE", `/smiths/${enc(pid)}/model_keys/${enc(provider)}`, opts),
|
|
152
262
|
},
|
|
153
263
|
schedules: {
|
|
154
|
-
list: (pid, opts) => this.
|
|
264
|
+
list: (pid, query, opts) => this.page(`/smiths/${enc(pid)}/schedules`, query, opts),
|
|
155
265
|
create: (pid, body, opts) => this.json("POST", `/smiths/${enc(pid)}/schedules`, {
|
|
156
266
|
...opts,
|
|
157
267
|
body,
|
|
@@ -213,6 +323,7 @@ export class IngramCloud {
|
|
|
213
323
|
// ── Runs (tenant-wide feed) ─────────────────────────────────────────────
|
|
214
324
|
runs = {
|
|
215
325
|
list: (query, opts) => this.page("/runs", query, opts),
|
|
326
|
+
trace: (rid, opts) => this.json("GET", `/runs/${enc(rid)}/trace`, opts),
|
|
216
327
|
};
|
|
217
328
|
// ── Agents ──────────────────────────────────────────────────────────────
|
|
218
329
|
agents = {
|
|
@@ -222,7 +333,7 @@ export class IngramCloud {
|
|
|
222
333
|
update: (aid, body, opts) => this.json("PATCH", `/agents/${enc(aid)}`, { ...opts, body }),
|
|
223
334
|
delete: (aid, opts) => this.empty("DELETE", `/agents/${enc(aid)}`, opts),
|
|
224
335
|
versions: {
|
|
225
|
-
list: (aid, opts) => this.
|
|
336
|
+
list: (aid, query, opts) => this.page(`/agents/${enc(aid)}/versions`, query, opts),
|
|
226
337
|
/** Snapshot the draft as the next immutable version. */
|
|
227
338
|
publish: (aid, body = {}, opts) => this.json("POST", `/agents/${enc(aid)}/versions`, {
|
|
228
339
|
...opts,
|
|
@@ -246,7 +357,9 @@ export class IngramCloud {
|
|
|
246
357
|
* `{ name, csp?, permissions?, tool? }` sidecar. Replaces by name. */
|
|
247
358
|
put: (aid, html, meta, opts) => {
|
|
248
359
|
const form = new FormData();
|
|
249
|
-
form.append("file", html instanceof Blob
|
|
360
|
+
form.append("file", html instanceof Blob
|
|
361
|
+
? html
|
|
362
|
+
: new Blob([html], { type: "text/html" }), `${meta.name}.html`);
|
|
250
363
|
form.append("metadata", JSON.stringify(meta));
|
|
251
364
|
return this.json("POST", `/agents/${enc(aid)}/ui`, {
|
|
252
365
|
...opts,
|
|
@@ -260,12 +373,16 @@ export class IngramCloud {
|
|
|
260
373
|
...opts,
|
|
261
374
|
headers: { accept: "text/html", ...opts?.headers },
|
|
262
375
|
}).then((r) => r.text()),
|
|
263
|
-
delete: (aid, name, opts) => this.
|
|
376
|
+
delete: (aid, name, opts) => this.empty("DELETE", `/agents/${enc(aid)}/ui/${enc(name)}`, opts),
|
|
264
377
|
},
|
|
265
378
|
};
|
|
266
379
|
// ── Conversations (smith-scoped: pass `{ smith }` with a tenant token) ──
|
|
267
380
|
conversations = {
|
|
268
|
-
list
|
|
381
|
+
/** OpenAI `list` envelope; page forward with `after: page.last_id`. */
|
|
382
|
+
list: (query, opts) => this.json("GET", "/conversations", {
|
|
383
|
+
...opts,
|
|
384
|
+
query,
|
|
385
|
+
}),
|
|
269
386
|
create: (body = {}, opts) => this.json("POST", "/conversations", { ...opts, body }),
|
|
270
387
|
get: (cnvId, opts) => this.json("GET", `/conversations/${enc(cnvId)}`, opts),
|
|
271
388
|
/** OpenAI-style modify — a POST, not a PATCH. */
|
|
@@ -288,6 +405,13 @@ export class IngramCloud {
|
|
|
288
405
|
events = {
|
|
289
406
|
list: (query, opts) => this.page("/events", query, opts),
|
|
290
407
|
};
|
|
408
|
+
/** What arrived, before anything interpreted it — including arrivals that
|
|
409
|
+
* matched no smith (`smith_id: ""`). The `iev_` ids `deployment.inbound`
|
|
410
|
+
* carries resolve here. */
|
|
411
|
+
inboundEvents = {
|
|
412
|
+
list: (query, opts) => this.page("/inbound_events", query, opts),
|
|
413
|
+
get: (ievId, opts) => this.json("GET", `/inbound_events/${enc(ievId)}`, opts),
|
|
414
|
+
};
|
|
291
415
|
// ── Customers / budgets ─────────────────────────────────────────────────
|
|
292
416
|
customers = {
|
|
293
417
|
list: (query, opts) => this.page("/customers", query, opts),
|
|
@@ -297,7 +421,7 @@ export class IngramCloud {
|
|
|
297
421
|
delete: (cid, opts) => this.empty("DELETE", `/customers/${enc(cid)}`, opts),
|
|
298
422
|
};
|
|
299
423
|
budgets = {
|
|
300
|
-
list: (opts) => this.
|
|
424
|
+
list: (query, opts) => this.page("/budgets", query, opts),
|
|
301
425
|
create: (body, opts) => this.json("POST", "/budgets", { ...opts, body }),
|
|
302
426
|
get: (bid, opts) => this.json("GET", `/budgets/${enc(bid)}`, opts),
|
|
303
427
|
update: (bid, body, opts) => this.json("PATCH", `/budgets/${enc(bid)}`, { ...opts, body }),
|
|
@@ -320,10 +444,25 @@ export class IngramCloud {
|
|
|
320
444
|
list: (opts) => this.data("GET", "/catalog", opts),
|
|
321
445
|
get: (slug, opts) => this.json("GET", `/catalog/${enc(slug)}`, opts),
|
|
322
446
|
};
|
|
447
|
+
// ── Embeddings ──────────────────────────────────────────────────────────
|
|
448
|
+
/** Embed one string or a batch on the OpenAI-compatible wire. Pure tenant
|
|
449
|
+
* compute — no smith runs. Omit `model` for the project default. Reach for
|
|
450
|
+
* the `openai` SDK instead if you already hold one; this is the same route. */
|
|
451
|
+
embeddings = {
|
|
452
|
+
create: (body, opts) => this.json("POST", "/embeddings", { ...opts, body }),
|
|
453
|
+
};
|
|
323
454
|
// ── Observability ───────────────────────────────────────────────────────
|
|
324
455
|
traces = {
|
|
325
456
|
list: (query, opts) => this.page("/traces", query, opts),
|
|
326
457
|
get: (traceId, opts) => this.json("GET", `/traces/${enc(traceId)}`, opts),
|
|
458
|
+
/** Push spans from your own runtime or an OTel exporter. The tenant comes
|
|
459
|
+
* from the token; a smith-scoped token may only attribute to its own smith.
|
|
460
|
+
* Unknown `kind`s land as `runtime_event` rather than erroring. Returns the
|
|
461
|
+
* number written. */
|
|
462
|
+
ingest: (spans, opts) => this.json("POST", "/traces:ingest", {
|
|
463
|
+
...opts,
|
|
464
|
+
body: { spans },
|
|
465
|
+
}),
|
|
327
466
|
};
|
|
328
467
|
usage = {
|
|
329
468
|
/** Token/cost/run totals grouped by app, smith, model, or customer. */
|
|
@@ -352,10 +491,16 @@ export class IngramCloud {
|
|
|
352
491
|
// ── Vector stores (the OpenAI Vector Stores API) ─────────────────────────
|
|
353
492
|
vectorStores = {
|
|
354
493
|
create: (body, opts) => this.json("POST", "/vector_stores", { ...opts, body }),
|
|
355
|
-
list: (query, opts) => this.json("GET", "/vector_stores", {
|
|
494
|
+
list: (query, opts) => this.json("GET", "/vector_stores", {
|
|
495
|
+
...opts,
|
|
496
|
+
query,
|
|
497
|
+
}),
|
|
356
498
|
get: (vsId, opts) => this.json("GET", `/vector_stores/${enc(vsId)}`, opts),
|
|
357
499
|
/** Modify (OpenAI uses `POST`, not `PATCH`). */
|
|
358
|
-
update: (vsId, body, opts) => this.json("POST", `/vector_stores/${enc(vsId)}`, {
|
|
500
|
+
update: (vsId, body, opts) => this.json("POST", `/vector_stores/${enc(vsId)}`, {
|
|
501
|
+
...opts,
|
|
502
|
+
body,
|
|
503
|
+
}),
|
|
359
504
|
delete: (vsId, opts) => this.json("DELETE", `/vector_stores/${enc(vsId)}`, opts),
|
|
360
505
|
search: (vsId, body, opts) => this.json("POST", `/vector_stores/${enc(vsId)}/search`, {
|
|
361
506
|
...opts,
|
|
@@ -378,6 +523,34 @@ export class IngramCloud {
|
|
|
378
523
|
files: (vsId, batchId, query, opts) => this.json("GET", `/vector_stores/${enc(vsId)}/file_batches/${enc(batchId)}/files`, { ...opts, query }),
|
|
379
524
|
},
|
|
380
525
|
};
|
|
526
|
+
// ── Agent Skills — a folder anchored by SKILL.md, versioned, attached to
|
|
527
|
+
// agents. Upload takes either the bundle's files as path/content pairs, or
|
|
528
|
+
// the whole bundle as a zip Blob — the same two shapes /v1/skills accepts.
|
|
529
|
+
// Both are runtime-agnostic: nothing here touches a filesystem. ───────────
|
|
530
|
+
skills = {
|
|
531
|
+
list: (opts) => this.json("GET", "/skills", opts),
|
|
532
|
+
get: (id, opts) => this.json("GET", `/skills/${enc(id)}`, opts),
|
|
533
|
+
create: (bundle, opts) => this.json("POST", "/skills", {
|
|
534
|
+
...opts,
|
|
535
|
+
rawBody: bundleForm(bundle),
|
|
536
|
+
}),
|
|
537
|
+
/** Move `default_version` to an existing version. */
|
|
538
|
+
update: (id, body, opts) => this.json("POST", `/skills/${enc(id)}`, {
|
|
539
|
+
...opts,
|
|
540
|
+
body,
|
|
541
|
+
}),
|
|
542
|
+
delete: (id, opts) => this.request("DELETE", `/skills/${enc(id)}`, opts).then(() => undefined),
|
|
543
|
+
versions: {
|
|
544
|
+
list: (id, opts) => this.json("GET", `/skills/${enc(id)}/versions`, opts),
|
|
545
|
+
get: (id, version, opts) => this.json("GET", `/skills/${enc(id)}/versions/${version}`, opts),
|
|
546
|
+
create: (id, bundle, opts) => this.json("POST", `/skills/${enc(id)}/versions`, { ...opts, rawBody: bundleForm(bundle) }),
|
|
547
|
+
delete: (id, version, opts) => this.request("DELETE", `/skills/${enc(id)}/versions/${version}`, opts).then(() => undefined),
|
|
548
|
+
/** One file's bytes, or — with no `path` — the whole version as a zip.
|
|
549
|
+
* The raw `Response` (matching `files.content`), so a caller streams it
|
|
550
|
+
* through rather than buffering the whole zip into memory. */
|
|
551
|
+
content: (id, version, path, opts) => this.request("GET", `/skills/${enc(id)}/versions/${version}/content${path ? `?path=${encodeURIComponent(path)}` : ""}`, opts),
|
|
552
|
+
},
|
|
553
|
+
};
|
|
381
554
|
// ── Tenant config ───────────────────────────────────────────────────────
|
|
382
555
|
tenant = {
|
|
383
556
|
usage: (opts) => this.json("GET", "/tenant/usage", opts),
|
|
@@ -426,10 +599,13 @@ export class IngramCloud {
|
|
|
426
599
|
delete: (provider, opts) => this.empty("DELETE", `/tenant/model_keys/${enc(provider)}`, opts),
|
|
427
600
|
},
|
|
428
601
|
mcp: {
|
|
429
|
-
list: (opts) => this.
|
|
602
|
+
list: (query, opts) => this.page("/tenant/mcp", query, opts),
|
|
430
603
|
get: (name, opts) => this.json("GET", `/tenant/mcp/${enc(name)}`, opts),
|
|
431
604
|
/** Register or replace a server (full replace; probes `tools/list`). */
|
|
432
|
-
put: (name, body, opts) => this.json("PUT", `/tenant/mcp/${enc(name)}`, {
|
|
605
|
+
put: (name, body, opts) => this.json("PUT", `/tenant/mcp/${enc(name)}`, {
|
|
606
|
+
...opts,
|
|
607
|
+
body,
|
|
608
|
+
}),
|
|
433
609
|
refresh: (name, opts) => this.json("POST", `/tenant/mcp/${enc(name)}/refresh`, opts),
|
|
434
610
|
delete: (name, opts) => this.empty("DELETE", `/tenant/mcp/${enc(name)}`, opts),
|
|
435
611
|
},
|
|
@@ -473,11 +649,52 @@ export class IngramCloud {
|
|
|
473
649
|
decline: (requestId, opts) => this.json("POST", `/oauth/authorize-requests/${enc(requestId)}/decline`, opts),
|
|
474
650
|
},
|
|
475
651
|
};
|
|
476
|
-
// ── Organization (org token: projects
|
|
477
|
-
// Note: the `/v1/organization/billing/*` surface is not yet wrapped here.
|
|
652
|
+
// ── Organization (org token: projects, tokens, billing) ─────────────────
|
|
478
653
|
organization = {
|
|
654
|
+
/** Platform credits — the org wallet that funds every project's runs.
|
|
655
|
+
* Amounts are integer minor units of the wallet's `currency`. */
|
|
656
|
+
billing: {
|
|
657
|
+
balance: (opts) => this.json("GET", "/organization/billing/balance", opts),
|
|
658
|
+
/** Money in (top-ups, grants, codes) and out (usage debits), newest first. */
|
|
659
|
+
ledger: (query, opts) => this.page("/organization/billing/ledger", query, opts),
|
|
660
|
+
/** Per-project draw for a calendar month (`period`, `YYYY-MM`). */
|
|
661
|
+
usage: (query, opts) => this.json("GET", "/organization/billing/usage", {
|
|
662
|
+
...opts,
|
|
663
|
+
query,
|
|
664
|
+
}),
|
|
665
|
+
/** Daily per-project draw over a rolling window of `days`. */
|
|
666
|
+
usageSeries: (query, opts) => this.json("GET", "/organization/billing/usage/series", {
|
|
667
|
+
...opts,
|
|
668
|
+
query,
|
|
669
|
+
}),
|
|
670
|
+
/** Open a Stripe Checkout Session to top up; send the user to its `url`. */
|
|
671
|
+
checkout: (body, opts) => this.json("POST", "/organization/billing/checkout", { ...opts, body }),
|
|
672
|
+
/** Add a card with no charge, unlocking the one-time welcome credit. */
|
|
673
|
+
setup: (body, opts) => this.json("POST", "/organization/billing/setup", {
|
|
674
|
+
...opts,
|
|
675
|
+
body,
|
|
676
|
+
}),
|
|
677
|
+
redeem: (body, opts) => this.json("POST", "/organization/billing/redeem", { ...opts, body }),
|
|
678
|
+
/** Credit a returning Checkout Session. Safe to call twice — the ledger
|
|
679
|
+
* keys on the session id, so it can't double-credit. */
|
|
680
|
+
confirm: (body, opts) => this.json("POST", "/organization/billing/confirm", { ...opts, body }),
|
|
681
|
+
autoreload: {
|
|
682
|
+
get: (opts) => this.json("GET", "/organization/billing/autoreload", opts),
|
|
683
|
+
put: (body, opts) => this.json("PUT", "/organization/billing/autoreload", {
|
|
684
|
+
...opts,
|
|
685
|
+
body,
|
|
686
|
+
}),
|
|
687
|
+
},
|
|
688
|
+
/** Charge the saved card now. `amount_cents` defaults to the auto-reload amount. */
|
|
689
|
+
reload: (body, opts) => this.json("POST", "/organization/billing/reload", { ...opts, body: body ?? {} }),
|
|
690
|
+
/** A Stripe billing-portal URL for managing cards and invoices. */
|
|
691
|
+
portal: (query, opts) => this.json("GET", "/organization/billing/portal", {
|
|
692
|
+
...opts,
|
|
693
|
+
query,
|
|
694
|
+
}),
|
|
695
|
+
},
|
|
479
696
|
projects: {
|
|
480
|
-
list: (opts) => this.
|
|
697
|
+
list: (query, opts) => this.page("/organization/projects", query, opts),
|
|
481
698
|
create: (body, opts) => this.json("POST", "/organization/projects", {
|
|
482
699
|
...opts,
|
|
483
700
|
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
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
+
}
|