@oberik/sdk 0.5.0 → 0.7.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/dist/cjs/index.js +268 -15
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.d.ts +170 -11
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +266 -15
- package/dist/esm/index.js.map +1 -1
- package/package.json +1 -1
package/dist/esm/index.js
CHANGED
|
@@ -62,6 +62,103 @@ export class AgentCancelledError extends Error {
|
|
|
62
62
|
// Errors
|
|
63
63
|
// ============================================================================
|
|
64
64
|
/** Does this look like a page rather than an answer? */
|
|
65
|
+
// ---- UI component arguments ----
|
|
66
|
+
//
|
|
67
|
+
// A UI component declares `parameters` exactly as a client tool does, and `render` draws
|
|
68
|
+
// into the customer's own product. Two schema violations of the *same* declared schema
|
|
69
|
+
// reached `render` unchecked: `bars` as a JSON string rather than an array, and array
|
|
70
|
+
// items missing a required `percent` while carrying two fields the schema never mentioned.
|
|
71
|
+
//
|
|
72
|
+
// For a client *tool* you can validate in the handler and answer the model. A UI component
|
|
73
|
+
// paints, so the check has to happen before it draws.
|
|
74
|
+
/** JSON Schema `properties`, whichever of the two shapes the app declared. */
|
|
75
|
+
function uiProps(schema) {
|
|
76
|
+
const s = schema;
|
|
77
|
+
return (s?.properties ?? s?.parameters?.properties ?? {});
|
|
78
|
+
}
|
|
79
|
+
function parseIfJson(value, want) {
|
|
80
|
+
if (typeof value !== "string")
|
|
81
|
+
return value;
|
|
82
|
+
const text = value.trim();
|
|
83
|
+
if (!text)
|
|
84
|
+
return value;
|
|
85
|
+
const opens = want === "array" ? "[" : "{";
|
|
86
|
+
if (!text.startsWith(opens))
|
|
87
|
+
return value;
|
|
88
|
+
try {
|
|
89
|
+
return JSON.parse(text);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Passed through untouched. A cosmetic mismatch must not become a thrown render.
|
|
93
|
+
return value;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Make the arguments match the declared types, as far as that is safe.
|
|
98
|
+
*
|
|
99
|
+
* Narrow on purpose — this is the app's own schema and the values go straight back to it.
|
|
100
|
+
* A container declared as `array`/`object` that arrived as its JSON text is parsed, and
|
|
101
|
+
* that parsing recurses into array items, which is where the server's own coercion stops.
|
|
102
|
+
*/
|
|
103
|
+
export function coerceUiArgs(schema, args) {
|
|
104
|
+
const props = uiProps(schema);
|
|
105
|
+
if (!Object.keys(props).length)
|
|
106
|
+
return args;
|
|
107
|
+
const out = { ...args };
|
|
108
|
+
for (const [key, spec] of Object.entries(props)) {
|
|
109
|
+
if (!(key in out))
|
|
110
|
+
continue;
|
|
111
|
+
const want = String(spec?.type ?? "");
|
|
112
|
+
if (want === "array" || want === "object")
|
|
113
|
+
out[key] = parseIfJson(out[key], want);
|
|
114
|
+
// Items of an array of objects: the same JSON-text-instead-of-a-container mistake,
|
|
115
|
+
// one level down, which is the level a chart's data actually lives at.
|
|
116
|
+
if (want === "array" && Array.isArray(out[key]) && spec?.items?.type === "object") {
|
|
117
|
+
out[key] = out[key].map((item) => parseIfJson(item, "object"));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
/** How `args` fails `schema`, as sentences — empty when it does not. */
|
|
123
|
+
export function uiSchemaViolations(schema, args) {
|
|
124
|
+
const props = uiProps(schema);
|
|
125
|
+
if (!Object.keys(props).length)
|
|
126
|
+
return [];
|
|
127
|
+
const required = (schema?.required ?? []);
|
|
128
|
+
const out = [];
|
|
129
|
+
for (const key of required) {
|
|
130
|
+
if (args[key] === undefined || args[key] === null)
|
|
131
|
+
out.push(`missing required "${key}"`);
|
|
132
|
+
}
|
|
133
|
+
for (const [key, spec] of Object.entries(props)) {
|
|
134
|
+
const value = args[key];
|
|
135
|
+
if (value === undefined || value === null)
|
|
136
|
+
continue;
|
|
137
|
+
const want = String(spec?.type ?? "");
|
|
138
|
+
if (want === "array" && !Array.isArray(value)) {
|
|
139
|
+
out.push(`"${key}" should be an array, got ${typeof value}`);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (want === "array" && Array.isArray(value) && spec?.items) {
|
|
143
|
+
const itemRequired = (spec.items.required ?? []);
|
|
144
|
+
value.forEach((item, i) => {
|
|
145
|
+
if (typeof item !== "object" || item === null)
|
|
146
|
+
return;
|
|
147
|
+
for (const f of itemRequired) {
|
|
148
|
+
if (item[f] === undefined)
|
|
149
|
+
out.push(`"${key}[${i}]" is missing required "${f}"`);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
if (want === "number" && typeof value !== "number") {
|
|
154
|
+
out.push(`"${key}" should be a number, got ${typeof value}`);
|
|
155
|
+
}
|
|
156
|
+
if (want === "string" && typeof value !== "string") {
|
|
157
|
+
out.push(`"${key}" should be a string, got ${typeof value}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
65
162
|
function looksLikeHtml(text) {
|
|
66
163
|
return /^\s*(<!doctype html|<html|<head|<body)/i.test(text) || /<\/html>\s*$/i.test(text);
|
|
67
164
|
}
|
|
@@ -82,9 +179,11 @@ function looksLikeHtml(text) {
|
|
|
82
179
|
function describeDetail(status, detail) {
|
|
83
180
|
if (typeof detail === "string") {
|
|
84
181
|
if (looksLikeHtml(detail)) {
|
|
182
|
+
// Report what arrived, not a guess about what produced it. The API answers with
|
|
183
|
+
// JSON, so an HTML body is worth saying plainly — but naming whatever returned it
|
|
184
|
+
// would be speculation about someone else's infrastructure.
|
|
85
185
|
const title = /<title[^>]*>([^<]{1,160})<\/title>/i.exec(detail)?.[1]?.trim();
|
|
86
|
-
|
|
87
|
-
return `HTTP ${status}${title ? `: ${title}` : ""}${where}`;
|
|
186
|
+
return `HTTP ${status}${title ? `: ${title}` : ""} (the response body was HTML, not JSON)`;
|
|
88
187
|
}
|
|
89
188
|
return detail.slice(0, 2000) || `HTTP ${status}`;
|
|
90
189
|
}
|
|
@@ -280,6 +379,37 @@ async function collectDecisions(pending, handler) {
|
|
|
280
379
|
* A handler that throws is treated as the user declining rather than as a failure:
|
|
281
380
|
* the alternative is leaving the turn paused forever on a question nobody can now
|
|
282
381
|
* answer, and "they'd rather talk about it" is both true and recoverable. */
|
|
382
|
+
/** One signal that aborts when any of its inputs does.
|
|
383
|
+
*
|
|
384
|
+
* `AbortSignal.any` exists on Node 20+ and modern browsers; the manual path is for
|
|
385
|
+
* anything older, because a client that cannot compose signals should lose the timeout,
|
|
386
|
+
* not the request. */
|
|
387
|
+
function combineSignals(...signals) {
|
|
388
|
+
const live = signals.filter(Boolean);
|
|
389
|
+
if (live.length <= 1)
|
|
390
|
+
return live[0];
|
|
391
|
+
const anyOf = AbortSignal.any;
|
|
392
|
+
if (typeof anyOf === "function")
|
|
393
|
+
return anyOf(live);
|
|
394
|
+
const ac = new AbortController();
|
|
395
|
+
for (const s of live) {
|
|
396
|
+
if (s.aborted) {
|
|
397
|
+
ac.abort(s.reason);
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
s.addEventListener("abort", () => ac.abort(s.reason), { once: true });
|
|
401
|
+
}
|
|
402
|
+
return ac.signal;
|
|
403
|
+
}
|
|
404
|
+
/** Anything `UploadInput` allows, as a Blob for `FormData`. */
|
|
405
|
+
function asBlob(input) {
|
|
406
|
+
if (typeof Blob !== "undefined" && input instanceof Blob)
|
|
407
|
+
return input;
|
|
408
|
+
const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
409
|
+
// Copied into a fresh buffer: a Node Buffer is a view onto a pooled allocation, and
|
|
410
|
+
// handing that straight to Blob can carry bytes belonging to something else.
|
|
411
|
+
return new Blob([bytes.slice()]);
|
|
412
|
+
}
|
|
283
413
|
async function collectAnswers(pending, handler) {
|
|
284
414
|
const out = [];
|
|
285
415
|
for (const batch of pending) {
|
|
@@ -891,8 +1021,25 @@ export class AgentFramework {
|
|
|
891
1021
|
const component = registry.get(r.name);
|
|
892
1022
|
if (!component)
|
|
893
1023
|
continue;
|
|
1024
|
+
// Coerced and checked against the schema the app declared, because `render` paints
|
|
1025
|
+
// straight into somebody's product and the frame handed to it is the one thing you
|
|
1026
|
+
// would expect to have been checked against the schema you just supplied.
|
|
1027
|
+
//
|
|
1028
|
+
// The server coerces too, but only the top-level properties, and it checks nothing
|
|
1029
|
+
// required — so a declared `bars: array of {label, percent}` arrived as a JSON
|
|
1030
|
+
// *string* in a browser and as `[{label, value, target}]` in Node, and both were
|
|
1031
|
+
// dispatched. The page drew "? 0.0%" four times.
|
|
1032
|
+
const args = coerceUiArgs(component.parameters, r.args ?? {});
|
|
1033
|
+
const problems = uiSchemaViolations(component.parameters, args);
|
|
1034
|
+
if (problems.length) {
|
|
1035
|
+
// Warn, then draw anyway. The agent has already told the user it showed them
|
|
1036
|
+
// something, so silently skipping leaves them looking for a chart that never
|
|
1037
|
+
// appears — worse than a chart with a gap in it. Loud enough to find in a console.
|
|
1038
|
+
console.warn(`[oberik] UI component "${r.name}" was called with arguments that do not match ` +
|
|
1039
|
+
`its declared schema: ${problems.join("; ")}. Rendering anyway.`);
|
|
1040
|
+
}
|
|
894
1041
|
try {
|
|
895
|
-
component.render(
|
|
1042
|
+
component.render(args);
|
|
896
1043
|
}
|
|
897
1044
|
catch (e) {
|
|
898
1045
|
console.warn(`[oberik] UI component "${r.name}" threw while rendering:`, e);
|
|
@@ -930,7 +1077,16 @@ export class AgentFramework {
|
|
|
930
1077
|
headers["Content-Type"] = "application/json";
|
|
931
1078
|
body = JSON.stringify(init.body);
|
|
932
1079
|
}
|
|
933
|
-
|
|
1080
|
+
// A timeout the caller can actually set. Node's built-in fetch gives up waiting
|
|
1081
|
+
// for response headers after 300s with `UND_ERR_HEADERS_TIMEOUT`, and there was
|
|
1082
|
+
// no way to raise it short of hand-building an undici dispatcher — so a blocking
|
|
1083
|
+
// turn that ran a long sandbox job simply died as `TypeError: fetch failed` with
|
|
1084
|
+
// nothing naming this SDK. Composed with the caller's own signal rather than
|
|
1085
|
+
// replacing it, so `opts.signal` still cancels.
|
|
1086
|
+
const ms = this.opts.timeoutMs ?? 600_000;
|
|
1087
|
+
const timer = ms > 0 ? AbortSignal.timeout(ms) : undefined;
|
|
1088
|
+
const signal = combineSignals(init.signal, timer);
|
|
1089
|
+
return this._fetch(url.toString(), { method, headers, body, signal });
|
|
934
1090
|
};
|
|
935
1091
|
let res = await send();
|
|
936
1092
|
// Token expired mid-session: get a fresh one and replay the request once, so the
|
|
@@ -1032,8 +1188,15 @@ export class AgentFramework {
|
|
|
1032
1188
|
},
|
|
1033
1189
|
/** Blocking chat that AUTO-EXECUTES registered client tools: it sends the
|
|
1034
1190
|
* message, and whenever the agent asks for client tools it runs their
|
|
1035
|
-
* handlers, submits the results, and repeats until the agent is done.
|
|
1036
|
-
|
|
1191
|
+
* handlers, submits the results, and repeats until the agent is done.
|
|
1192
|
+
*
|
|
1193
|
+
* Takes ONE argument, with the handlers inside it — unlike `chat.stream(req,
|
|
1194
|
+
* handlers)`, which takes two. Both forms are accepted here because the asymmetry
|
|
1195
|
+
* is a trap: passing handlers the way `stream` takes them was silently ignored, so
|
|
1196
|
+
* `onApproval` never fired and the turn came back with `approvals` set, which reads
|
|
1197
|
+
* exactly like approvals being broken. TypeScript catches it; the docs' own browser
|
|
1198
|
+
* examples are plain JS, where it does not. */
|
|
1199
|
+
run: (opts = {}, handlers) => this.runWithTools(handlers ? { ...opts, ...handlers } : opts),
|
|
1037
1200
|
/** Streaming chat. When client tools are registered/passed, tool calls are
|
|
1038
1201
|
* auto-executed and the stream resumes, so `done` only resolves when the
|
|
1039
1202
|
* agent finishes (never with `requires_action`). */
|
|
@@ -2139,6 +2302,20 @@ export class OberikProject {
|
|
|
2139
2302
|
throw new Error("No fetch available; pass options.fetch");
|
|
2140
2303
|
this._fetch = raw.bind(globalThis);
|
|
2141
2304
|
}
|
|
2305
|
+
/**
|
|
2306
|
+
* Any Project API route, with this project's key — the escape hatch.
|
|
2307
|
+
*
|
|
2308
|
+
* The end-user client has always had `raw()`; this one had a private `request` and
|
|
2309
|
+
* nothing else, so the documented routes with no method here (the webhook secret,
|
|
2310
|
+
* webhook-tool rotation, `webhook-tools/deliveries`) meant hand-rolling `fetch` with
|
|
2311
|
+
* an `X-API-Key` header and re-deriving the base URL. A published SDK should not make
|
|
2312
|
+
* you leave it to use the API it wraps.
|
|
2313
|
+
*
|
|
2314
|
+
* `path` is relative to `/api/projects/<id>` — `raw("GET", "/webhook-secret")`.
|
|
2315
|
+
*/
|
|
2316
|
+
async raw(method, path, body, form) {
|
|
2317
|
+
return this.request(method, path, body, form);
|
|
2318
|
+
}
|
|
2142
2319
|
async request(method, path, body, form) {
|
|
2143
2320
|
const res = await this._fetch(`${this.baseUrl}/api/projects/${this.projectId}${path}`, {
|
|
2144
2321
|
method,
|
|
@@ -2151,7 +2328,9 @@ export class OberikProject {
|
|
|
2151
2328
|
const text = await res.text();
|
|
2152
2329
|
const data = text ? safeJson(text) : null;
|
|
2153
2330
|
if (!res.ok) {
|
|
2154
|
-
|
|
2331
|
+
// `detail` on both planes now, so an integrator writes one accessor rather than
|
|
2332
|
+
// discovering that the control plane spelled it differently.
|
|
2333
|
+
throw new AgentApiError(res.status, (data && (data.detail || data.message)) || `HTTP ${res.status}`);
|
|
2155
2334
|
}
|
|
2156
2335
|
return data;
|
|
2157
2336
|
}
|
|
@@ -2176,11 +2355,38 @@ export class OberikProject {
|
|
|
2176
2355
|
*/
|
|
2177
2356
|
forUser: (input) => async () => (await this.request("POST", "/token", input)).access_token,
|
|
2178
2357
|
};
|
|
2358
|
+
/**
|
|
2359
|
+
* The project itself — its name, description, and the settings that are properties of
|
|
2360
|
+
* the project rather than permissions on a token.
|
|
2361
|
+
*
|
|
2362
|
+
* These had no SDK path at all. `doneWebhookUrl` is documented in the capability table
|
|
2363
|
+
* but is NOT a capability, so the obvious `capabilities.set({ doneWebhookUrl })` stored
|
|
2364
|
+
* nothing; the only symptom was `turn.stopped` never firing. Renaming a project, or
|
|
2365
|
+
* turning citation markers off, meant dropping to raw HTTP through `raw()`.
|
|
2366
|
+
*/
|
|
2367
|
+
project = {
|
|
2368
|
+
get: () => this.request("GET", ""),
|
|
2369
|
+
/** Merges — send only what you want to change. */
|
|
2370
|
+
set: (fields) => this.request("PATCH", "", fields),
|
|
2371
|
+
/** Whether `[2]` stays in the visible answer. Its own route, not a PATCH field.
|
|
2372
|
+
* `stripped` leaves `claims[].start/end` as the only way to place a footnote. */
|
|
2373
|
+
citations: (markers) => this.request("PUT", "/citations", { markers }),
|
|
2374
|
+
};
|
|
2179
2375
|
/** The capability ceiling: the maximum any token minted here may hold. */
|
|
2180
2376
|
capabilities = {
|
|
2181
2377
|
get: () => this.request("GET", "/capabilities"),
|
|
2182
|
-
/**
|
|
2183
|
-
|
|
2378
|
+
/**
|
|
2379
|
+
* Merges — send only what you want to change.
|
|
2380
|
+
*
|
|
2381
|
+
* Read-only fields that `get()` returns are dropped rather than rejected, so
|
|
2382
|
+
* `set(await get())` — the obvious way to flip one flag — works. Anything else
|
|
2383
|
+
* unrecognised still errors: a capability nobody enforces must not be accepted in
|
|
2384
|
+
* silence, which is the failure this whole check exists for.
|
|
2385
|
+
*/
|
|
2386
|
+
set: (caps) => {
|
|
2387
|
+
const { supportedModalities: _readOnly, ...settable } = caps;
|
|
2388
|
+
return this.request("PATCH", "", { capabilities: settable });
|
|
2389
|
+
},
|
|
2184
2390
|
};
|
|
2185
2391
|
/** Documents owned by the project rather than by any one end-user: the corpus you
|
|
2186
2392
|
* curate and your users only read. */
|
|
@@ -2197,11 +2403,20 @@ export class OberikProject {
|
|
|
2197
2403
|
* accepted nor needed — a recipe that worked by luck rather than by expression.
|
|
2198
2404
|
*/
|
|
2199
2405
|
documents = {
|
|
2200
|
-
list
|
|
2406
|
+
/** Same filters as the end-user client's `documents.list`, so the two agree. */
|
|
2407
|
+
list: (query = {}) => {
|
|
2408
|
+
const qs = new URLSearchParams(Object.entries(query).filter(([, v]) => v != null)).toString();
|
|
2409
|
+
return this.request("GET", `/documents${qs ? `?${qs}` : ""}`);
|
|
2410
|
+
},
|
|
2201
2411
|
get: (documentId) => this.request("GET", `/documents/${documentId}`),
|
|
2412
|
+
/** `file` may be a Blob/File, an ArrayBuffer or a Uint8Array — the same shapes the
|
|
2413
|
+
* end-user client takes. It was `Blob | File` only, so the quickstart's own
|
|
2414
|
+
* `await readFile("q3-report.pdf")` (a Buffer) failed deep inside undici as
|
|
2415
|
+
* `parameter 2 is not of type 'Blob'`, with nothing naming Oberik in the stack. */
|
|
2202
2416
|
upload: (file, opts = {}) => {
|
|
2203
2417
|
const form = new FormData();
|
|
2204
|
-
|
|
2418
|
+
const name = opts.filename ?? file.name ?? "upload.bin";
|
|
2419
|
+
form.append("file", asBlob(file), name);
|
|
2205
2420
|
form.append("tags", (opts.tags ?? []).join(","));
|
|
2206
2421
|
return this.request("POST", "/documents", undefined, form);
|
|
2207
2422
|
},
|
|
@@ -2289,9 +2504,19 @@ export class OberikProject {
|
|
|
2289
2504
|
/** Procedures you publish as Agent Plugins, and what your end-users have added. */
|
|
2290
2505
|
skills = {
|
|
2291
2506
|
list: () => this.request("GET", "/skills"),
|
|
2292
|
-
|
|
2507
|
+
/**
|
|
2508
|
+
* `zip` takes the same shapes as every other upload here — a Blob/File, an
|
|
2509
|
+
* ArrayBuffer or a Uint8Array — and `filename` goes in the options object.
|
|
2510
|
+
*
|
|
2511
|
+
* It was `Blob | File` with a positional filename, alone among four upload methods.
|
|
2512
|
+
* `await readFile("skill.zip")` (a Buffer) died as `parameter 2 is not of type 'Blob'`
|
|
2513
|
+
* inside undici with nothing naming Oberik in the stack — the identical failure that
|
|
2514
|
+
* was fixed for `documents.upload` and left here — and passing `{ filename }` in the
|
|
2515
|
+
* second slot, as the siblings take it, silently named the file `[object Object]`.
|
|
2516
|
+
*/
|
|
2517
|
+
upload: (zip, opts = {}) => {
|
|
2293
2518
|
const form = new FormData();
|
|
2294
|
-
form.append("file", zip, filename ?? zip.name ?? "skill.zip");
|
|
2519
|
+
form.append("file", asBlob(zip), opts.filename ?? zip.name ?? "skill.zip");
|
|
2295
2520
|
return this.request("POST", "/skills", undefined, form);
|
|
2296
2521
|
},
|
|
2297
2522
|
delete: (pluginId) => this.request("DELETE", `/skills/${pluginId}`),
|
|
@@ -2311,9 +2536,16 @@ export class OberikProject {
|
|
|
2311
2536
|
tasks = {
|
|
2312
2537
|
list: () => this.request("GET", "/tasks"),
|
|
2313
2538
|
};
|
|
2314
|
-
/**
|
|
2539
|
+
/**
|
|
2540
|
+
* What the agent has written down: remembered facts and wiki pages.
|
|
2541
|
+
*
|
|
2542
|
+
* `kind` defaults to `"wiki"` — the route's default, and the reason this looked like
|
|
2543
|
+
* it "returns pages only": it does, and there was no argument here to ask for the
|
|
2544
|
+
* memories. Same shape and same type as the end-user client's `knowledge.list`, so the
|
|
2545
|
+
* two do not disagree about what a stored item is.
|
|
2546
|
+
*/
|
|
2315
2547
|
wiki = {
|
|
2316
|
-
list: () => this.request("GET", "
|
|
2548
|
+
list: (opts = {}) => this.request("GET", `/wiki?kind=${opts.kind ?? "wiki"}`),
|
|
2317
2549
|
delete: (itemId) => this.request("DELETE", `/wiki/${itemId}`),
|
|
2318
2550
|
};
|
|
2319
2551
|
/** Live sandboxes, and what to do about one. */
|
|
@@ -2327,14 +2559,33 @@ export class OberikProject {
|
|
|
2327
2559
|
};
|
|
2328
2560
|
/** Browser origins allowed to call the data plane with this project's tokens. */
|
|
2329
2561
|
origins = {
|
|
2562
|
+
/** What is allowed right now — the first thing you want when a browser request is
|
|
2563
|
+
* being refused, and previously unanswerable without the dashboard. */
|
|
2564
|
+
get: () => this.request("GET", "/origins"),
|
|
2330
2565
|
set: (origins) => this.request("PUT", "/origins", { origins }),
|
|
2331
2566
|
};
|
|
2332
2567
|
/** Tools the agent calls by URL. The signing secret comes back once, on create. */
|
|
2333
2568
|
webhookTools = {
|
|
2334
2569
|
list: () => this.request("GET", "/webhook-tools"),
|
|
2335
2570
|
create: (tool) => this.request("POST", "/webhook-tools", tool),
|
|
2571
|
+
/** Change a tool WITHOUT rotating its secret.
|
|
2572
|
+
*
|
|
2573
|
+
* There was no update at all, so moving a URL after a tunnel restart meant
|
|
2574
|
+
* delete-and-recreate — which issues a new signing secret, shown once, that your
|
|
2575
|
+
* handler then has to be redeployed with. A URL change is not a credential
|
|
2576
|
+
* rotation. `name` is what the model calls and is not editable. */
|
|
2577
|
+
update: (toolId, patch) => this.request("PATCH", `/webhook-tools/${toolId}`, patch),
|
|
2578
|
+
/** A new signing secret, with the old one still verifying for 24 hours. */
|
|
2579
|
+
rotate: (toolId) => this.request("POST", `/webhook-tools/${toolId}/rotate`, {}),
|
|
2580
|
+
/** Recent calls to your handlers: when, which tool, what came back, how long. */
|
|
2581
|
+
deliveries: () => this.request("GET", "/webhook-tools/deliveries"),
|
|
2336
2582
|
delete: (toolId) => this.request("DELETE", `/webhook-tools/${toolId}`),
|
|
2337
2583
|
};
|
|
2584
|
+
/** The key that signs `turn.stopped` and scheduled-task deliveries to you. */
|
|
2585
|
+
webhookSecret = {
|
|
2586
|
+
get: () => this.request("GET", "/webhook-secret"),
|
|
2587
|
+
rotate: () => this.request("POST", "/webhook-secret/rotate", {}),
|
|
2588
|
+
};
|
|
2338
2589
|
/** Server-side keys. A created one is returned once and never again. */
|
|
2339
2590
|
/**
|
|
2340
2591
|
* Checks on what goes into the model and what comes back.
|