@pouchy_ai/admin-sdk 0.9.0 → 0.11.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/CHANGELOG.md +41 -0
- package/README.md +62 -0
- package/dist/index.d.ts +114 -6
- package/dist/index.js +60 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,47 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@pouchy_ai/admin-sdk` are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.11.0 — 2026-08-01
|
|
6
|
+
|
|
7
|
+
- **Data capabilities, headless.** Six new methods over the `/v1/admin/capabilities`
|
|
8
|
+
mirror: `listCapabilities` (heads + masked signing status — the `pcsk_`
|
|
9
|
+
plaintext is issued once on the owner plane and is never readable through any
|
|
10
|
+
API), `publishCapability` (immutable next version; idempotent on content),
|
|
11
|
+
`setCapabilityDisabled` (per-capability live revoke — live both directions,
|
|
12
|
+
pinned reconciliations unaffected), `listCapabilityVersions` (append-only
|
|
13
|
+
history; rollback = republishing an old declaration as the next version),
|
|
14
|
+
`testReadCapability`, and `testActionCapability` (the four-phase Action
|
|
15
|
+
protocol verifier; the server refuses without `confirmDuplicates: true`
|
|
16
|
+
because phases B/C intentionally re-deliver the same `actionId`).
|
|
17
|
+
- `POST /capabilities/{name}/test-action` joins `LONG_WORK_REQUESTS`: the
|
|
18
|
+
verifier makes real round-trips to YOUR endpoint, and aborting mid-test
|
|
19
|
+
cannot undo phase A's side effect — so the default deadline is the long one.
|
|
20
|
+
|
|
21
|
+
## 0.10.0 — 2026-07-29
|
|
22
|
+
|
|
23
|
+
- **`AdminApiError.code` — the Admin API's machine-readable failure tag.** The
|
|
24
|
+
server has been sending `code` on ten failures across nineteen emit sites;
|
|
25
|
+
this package read `error` off the body and dropped `code` on the floor, so the
|
|
26
|
+
field was unreachable from the first-party client.
|
|
27
|
+
- **Why it matters more than it sounds: every one of those ten codes is a 409.**
|
|
28
|
+
On this plane the status line never disambiguates, and two of the durable-run
|
|
29
|
+
routes answer 409 for two different reasons each, with opposite recoveries —
|
|
30
|
+
`resumeRun` (`stale_token`: re-read and answer again / `run_not_parked`: stop,
|
|
31
|
+
someone already decided) and `signalRun` (`event_mismatch`: your event is early
|
|
32
|
+
or wrong, retry later / `run_not_waiting`: the wait ended, stop). This
|
|
33
|
+
package's own doc comments told callers to make exactly those distinctions,
|
|
34
|
+
which was not possible without string-matching English prose.
|
|
35
|
+
- **`ADMIN_ERROR_CODES` + `AdminErrorCode` are exported** so a `switch` is typed.
|
|
36
|
+
The type carries a `(string & {})` arm (same doctrine as the companion SDK's
|
|
37
|
+
`CompanionErrorCodeValue`), so a code newer than your installed build still
|
|
38
|
+
arrives as a readable string instead of being filtered out. The list is
|
|
39
|
+
drift-gated against the server's own vocabulary in both directions.
|
|
40
|
+
- Additive only — no method signature, request shape, status code or error
|
|
41
|
+
message changed. `code` is `undefined` on every failure the server did not tag
|
|
42
|
+
(400s, 404s, the 429, transport errors), and a non-string or empty `code` on
|
|
43
|
+
the wire leaves it `undefined` rather than stamping a falsy value a `switch`
|
|
44
|
+
would fall through on.
|
|
45
|
+
|
|
5
46
|
## 0.9.0 — 2026-07-28
|
|
6
47
|
|
|
7
48
|
- **Channel CRUD is typed (CR-77).** `createChannel` takes
|
package/README.md
CHANGED
|
@@ -100,6 +100,62 @@ try {
|
|
|
100
100
|
}
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
+
### Failure codes (409)
|
|
104
|
+
|
|
105
|
+
**Every machine-readable failure on this API is a 409**, so `status` separates
|
|
106
|
+
none of them. `AdminApiError.code` is the discriminator — switch on it rather
|
|
107
|
+
than string-matching `message`, which is prose and may be reworded:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { AdminApiError } from '@pouchy_ai/admin-sdk';
|
|
111
|
+
|
|
112
|
+
// `runId` and `token` come from the run you are answering — the
|
|
113
|
+
// `agent.run_awaiting` webhook carries both, or read them off `getRun`.
|
|
114
|
+
declare const runId: string;
|
|
115
|
+
declare const token: string;
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
await admin.resumeRun(runId, { approved: true, token });
|
|
119
|
+
} catch (e) {
|
|
120
|
+
if (!(e instanceof AdminApiError)) throw e;
|
|
121
|
+
switch (e.code) {
|
|
122
|
+
case 'stale_token': {
|
|
123
|
+
// The run moved on to a different question. Re-read and answer the
|
|
124
|
+
// current one — this is the retryable branch.
|
|
125
|
+
const { run } = await admin.getRun(runId);
|
|
126
|
+
if (run.awaiting) await admin.resumeRun(runId, { approved: true, token: run.awaiting.token });
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
case 'run_not_parked':
|
|
130
|
+
// Someone else decided, or the run was cancelled. Do NOT retry.
|
|
131
|
+
break;
|
|
132
|
+
default:
|
|
133
|
+
throw e;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The vocabulary is exported as `ADMIN_ERROR_CODES` (and the `AdminErrorCode`
|
|
139
|
+
type), and is append-only:
|
|
140
|
+
|
|
141
|
+
| code | route | what to do |
|
|
142
|
+
|---|---|---|
|
|
143
|
+
| `schedule_limit_reached` | `createSchedule` | delete or disable a schedule, retry |
|
|
144
|
+
| `channel_limit_reached` | `createChannel` | delete an unused connector, retry |
|
|
145
|
+
| `webhook_limit_reached` | `createWebhook` | delete an unused endpoint, retry |
|
|
146
|
+
| `run_limit_reached` | `createRun` | wait for a run to finish, or `cancelRun` one |
|
|
147
|
+
| `reembed_required` | knowledge config | clear + re-ingest to switch embedding model |
|
|
148
|
+
| `run_terminal` | `cancelRun` | the run already finished — nothing to cancel |
|
|
149
|
+
| `run_not_parked` | `resumeRun` | already decided; stop retrying |
|
|
150
|
+
| `stale_token` | `resumeRun` | `getRun`, then answer the current `awaiting.token` |
|
|
151
|
+
| `run_not_waiting` | `signalRun` | the wait ended; stop retrying |
|
|
152
|
+
| `event_mismatch` | `signalRun` | the run wants a different key; your event is early or wrong |
|
|
153
|
+
|
|
154
|
+
`code` is `undefined` on every failure the server did not tag (400s, 404s, the
|
|
155
|
+
429, transport errors), so always keep a `default` branch. The type has a
|
|
156
|
+
`(string & {})` arm on purpose: a code newer than your installed build still
|
|
157
|
+
arrives as a readable string. Available from **0.10.0**.
|
|
158
|
+
|
|
103
159
|
### Throttling (429)
|
|
104
160
|
|
|
105
161
|
Writes are rate-limited per IP — 120 non-`GET` requests per minute across the
|
|
@@ -147,6 +203,7 @@ Channel types are checked at compile time: `createChannel` takes a
|
|
|
147
203
|
`CreatableChannelType` (the platform's 31-transport union minus the adapterless
|
|
148
204
|
`internal-a2a`), and `secret` is a named `ChannelSecretInput`. Per-transport
|
|
149
205
|
`secret.extra` fields are listed in <https://pouchy.ai/docs/channel-setup>.
|
|
206
|
+
| Data capabilities | `listCapabilities` (heads + MASKED signing status) · `publishCapability` (immutable next version, idempotent on content) · `setCapabilityDisabled` (per-capability live revoke) · `listCapabilityVersions` (history; rollback = republish an old declaration) · `testReadCapability` · `testActionCapability({ confirmDuplicates: true, … })` (REAL deliveries incl. intentional duplicates — 428 without consent) |
|
|
150
207
|
| Webhooks | `listWebhooks` · `createWebhook` · `updateWebhook` · `rotateWebhookSecret` · `deleteWebhook` · `testWebhook` · `redeliverWebhook` |
|
|
151
208
|
| Reporting | `getUsage` · `getBilling` · `getTracesSummary` · `getRecentTraces` · `getLogs` · `getProject` · `updateProject` |
|
|
152
209
|
| Escape hatch | `request(method, path, body?)` — any endpoint not yet typed |
|
|
@@ -175,6 +232,11 @@ const { run } = await admin.createRun({
|
|
|
175
232
|
console.log(run.id, run.status); // → 'queued'
|
|
176
233
|
```
|
|
177
234
|
|
|
235
|
+
A `subtask_fanout` step runs at most **4** subtasks (`input.subtasks`).
|
|
236
|
+
Declaring more is a **400** naming the cap rather than a silent trim — split
|
|
237
|
+
the remainder into a second fan-out step. Entries without a non-empty `goal`
|
|
238
|
+
are ignored and do not count against the cap.
|
|
239
|
+
|
|
178
240
|
When a run reaches a `human_approval` step it **parks**: it holds no lease and
|
|
179
241
|
leaves the platform's due window, so nothing will move it until you answer.
|
|
180
242
|
You learn about it either way — the `agent.run_awaiting` webhook pushes the
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const ADMIN_SDK_VERSION = "0.
|
|
1
|
+
export declare const ADMIN_SDK_VERSION = "0.11.0";
|
|
2
2
|
export declare const DEFAULT_BASE_URL = "https://pouchy.ai/v1/admin";
|
|
3
3
|
/** Deadline for the routes whose server handler declares `maxDuration: 300` —
|
|
4
4
|
* the server's own ceiling plus headroom, so a client abort can only ever mean
|
|
@@ -50,9 +50,33 @@ export interface AdminClientOptions {
|
|
|
50
50
|
* failed with the self-contradicting `request timed out after 0ms`. */
|
|
51
51
|
timeoutMs?: number;
|
|
52
52
|
}
|
|
53
|
+
/** Machine-readable failure tags the Admin API puts on an error body, mirroring
|
|
54
|
+
* the server's own list (`src/lib/server/platform/api-error-codes.ts`) and
|
|
55
|
+
* drift-tested against it. APPEND-ONLY: these are documented as switchable, so
|
|
56
|
+
* renaming or removing one breaks integrators.
|
|
57
|
+
*
|
|
58
|
+
* Every member is a **409**. That is the whole reason this vocabulary is
|
|
59
|
+
* published: on this plane the status line never disambiguates, so two of the
|
|
60
|
+
* durable-run routes answer 409 for two entirely different reasons each, with
|
|
61
|
+
* different recoveries. `code` is the only thing that tells them apart. */
|
|
62
|
+
export declare const ADMIN_ERROR_CODES: readonly ["schedule_limit_reached", "channel_limit_reached", "webhook_limit_reached", "run_limit_reached", "reembed_required", "run_terminal", "run_not_parked", "stale_token", "run_not_waiting", "event_mismatch"];
|
|
63
|
+
/** `AdminApiError.code` values. The `(string & {})` arm keeps the type open for
|
|
64
|
+
* codes newer than this SDK build while preserving autocomplete — same
|
|
65
|
+
* doctrine as the companion SDK's `CompanionErrorCodeValue`. */
|
|
66
|
+
export type AdminErrorCode = (typeof ADMIN_ERROR_CODES)[number] | (string & {});
|
|
53
67
|
/** Thrown on any non-2xx response. `status` is the HTTP status; `message` is the
|
|
54
68
|
* server's `error` string when present; `retryAfter` is the throttle's own
|
|
55
|
-
* backoff in SECONDS on a 429 (undefined on every other failure)
|
|
69
|
+
* backoff in SECONDS on a 429 (undefined on every other failure); `code` is the
|
|
70
|
+
* server's machine tag when it named one (undefined otherwise).
|
|
71
|
+
*
|
|
72
|
+
* Why `code` exists: the Admin API's ten machine-readable failures are ALL on
|
|
73
|
+
* 409, so `status` cannot separate them and this package used to discard the
|
|
74
|
+
* one field that can. Two routes are the point — `resumeRun` answers 409 for
|
|
75
|
+
* `run_not_parked` (someone already decided; re-read, do not retry) or
|
|
76
|
+
* `stale_token` (your token aged out; re-read for the current one and answer
|
|
77
|
+
* again), and `signalRun` answers 409 for `run_not_waiting` or
|
|
78
|
+
* `event_mismatch`. This package's own docs told callers to distinguish those,
|
|
79
|
+
* which was not possible without switching on prose.
|
|
56
80
|
*
|
|
57
81
|
* Why `retryAfter` exists: the entire write surface of this SDK sits behind one
|
|
58
82
|
* per-IP throttle — `hooks.server.ts` runs a `v1-admin-write` bucket (120
|
|
@@ -69,7 +93,8 @@ export interface AdminClientOptions {
|
|
|
69
93
|
export declare class AdminApiError extends Error {
|
|
70
94
|
status: number;
|
|
71
95
|
readonly retryAfter?: number;
|
|
72
|
-
|
|
96
|
+
readonly code?: AdminErrorCode;
|
|
97
|
+
constructor(message: string, status: number, retryAfter?: number, code?: string);
|
|
73
98
|
}
|
|
74
99
|
export type Env = 'live' | 'test';
|
|
75
100
|
export type AgentStatus = 'draft' | 'published';
|
|
@@ -598,7 +623,12 @@ export interface AdminClient {
|
|
|
598
623
|
* turn carrying `goal`. Step ids must be unique and match
|
|
599
624
|
* `[A-Za-z0-9_-]{1,64}`; `kind` is one of `agent_turn`, `subtask_fanout`,
|
|
600
625
|
* `human_approval`, `await_event`. Answers **202**, not 201: the run is
|
|
601
|
-
* accepted and has not run yet.
|
|
626
|
+
* accepted and has not run yet.
|
|
627
|
+
*
|
|
628
|
+
* A `subtask_fanout` step runs at most **4** subtasks (`input.subtasks`).
|
|
629
|
+
* Declaring more is a **400** naming the cap, not a silent trim — split the
|
|
630
|
+
* remainder into a second fan-out step. Entries without a non-empty `goal`
|
|
631
|
+
* are ignored and do not count against the cap. */
|
|
602
632
|
createRun(input: {
|
|
603
633
|
agentId: string;
|
|
604
634
|
externalUserId: string;
|
|
@@ -628,7 +658,13 @@ export interface AdminClient {
|
|
|
628
658
|
* `awaiting.token` (also carried by the `agent.run_awaiting` webhook) and
|
|
629
659
|
* fences the decision against a stale or replayed answer. Without this call
|
|
630
660
|
* a parked run never resumes: it holds no lease and has left the platform's
|
|
631
|
-
* due window by design.
|
|
661
|
+
* due window by design.
|
|
662
|
+
*
|
|
663
|
+
* Both failures here are **409**; switch on `AdminApiError.code` to tell
|
|
664
|
+
* them apart. `stale_token` — the run has moved on to a different question:
|
|
665
|
+
* re-read it with `getRun` and answer the current `awaiting.token`.
|
|
666
|
+
* `run_not_parked` — it is no longer awaiting anyone (someone else decided,
|
|
667
|
+
* or it was cancelled): do NOT retry. */
|
|
632
668
|
resumeRun(runId: string, input: {
|
|
633
669
|
approved: boolean;
|
|
634
670
|
token: string;
|
|
@@ -640,7 +676,13 @@ export interface AdminClient {
|
|
|
640
676
|
}>;
|
|
641
677
|
/** Deliver an external event to a run parked on one. `event` must equal the
|
|
642
678
|
* key the run is waiting for — a mismatch answers 409 `event_mismatch`, so a
|
|
643
|
-
* webhook retry can tell "too late" from "rejected".
|
|
679
|
+
* webhook retry can tell "too late" from "rejected".
|
|
680
|
+
*
|
|
681
|
+
* Read that verdict off `AdminApiError.code` (0.10.0; before that the field
|
|
682
|
+
* was dropped and this advice was unfollowable). `event_mismatch` — the run
|
|
683
|
+
* wants a DIFFERENT key, so your event is early or wrong: safe to retry
|
|
684
|
+
* later. `run_not_waiting` — the run is not parked on an event at all, so
|
|
685
|
+
* the wait already ended: stop retrying. */
|
|
644
686
|
signalRun(runId: string, input: {
|
|
645
687
|
event: string;
|
|
646
688
|
payload?: unknown;
|
|
@@ -649,6 +691,72 @@ export interface AdminClient {
|
|
|
649
691
|
event: string;
|
|
650
692
|
status: string;
|
|
651
693
|
}>;
|
|
694
|
+
/** Capability heads + MASKED signing status (key IDs and rotation state
|
|
695
|
+
* only — the `pcsk_` plaintext is issued ONCE on the owner plane and is
|
|
696
|
+
* never readable through any API). */
|
|
697
|
+
listCapabilities(): Promise<{
|
|
698
|
+
capabilities: Array<{
|
|
699
|
+
capabilityId: string;
|
|
700
|
+
name: string;
|
|
701
|
+
kind: string;
|
|
702
|
+
latestVersion: number;
|
|
703
|
+
disabled?: boolean;
|
|
704
|
+
}>;
|
|
705
|
+
signing: {
|
|
706
|
+
activeKeyId: string;
|
|
707
|
+
previousKeyId: string | null;
|
|
708
|
+
} | null;
|
|
709
|
+
}>;
|
|
710
|
+
/** Publish the next IMMUTABLE revision. Idempotent on content: an unchanged
|
|
711
|
+
* manifest returns the existing version with `created: false`; different
|
|
712
|
+
* bytes mint version+1 and never rewrite an old one. Publishing is NOT
|
|
713
|
+
* revocation — the per-agent data flags and `setCapabilityDisabled` are
|
|
714
|
+
* the live levers. */
|
|
715
|
+
publishCapability(declaration: Record<string, unknown>): Promise<{
|
|
716
|
+
revision: {
|
|
717
|
+
capabilityId: string;
|
|
718
|
+
version: number;
|
|
719
|
+
revisionHash: string;
|
|
720
|
+
};
|
|
721
|
+
created: boolean;
|
|
722
|
+
}>;
|
|
723
|
+
/** Per-capability live revoke: flips the head's `disabled` bit. Live in
|
|
724
|
+
* BOTH directions on the next resolution — existing sessions lose it from
|
|
725
|
+
* their next turn, new plans/ingress see honest absence; a reconciliation
|
|
726
|
+
* pinned to a revision keeps reconciling. */
|
|
727
|
+
setCapabilityDisabled(name: string, disabled: boolean): Promise<{
|
|
728
|
+
name: string;
|
|
729
|
+
disabled: boolean;
|
|
730
|
+
}>;
|
|
731
|
+
/** Full version history, newest first, WITH declaration bodies. There is no
|
|
732
|
+
* rollback verb: restoring vN = `publishCapability(versions[i].declaration)`,
|
|
733
|
+
* which mints it as the NEXT version (history stays append-only). */
|
|
734
|
+
listCapabilityVersions(name: string): Promise<{
|
|
735
|
+
versions: Array<{
|
|
736
|
+
version: number;
|
|
737
|
+
revisionHash: string;
|
|
738
|
+
publishedAt: string;
|
|
739
|
+
declaration: Record<string, unknown>;
|
|
740
|
+
}>;
|
|
741
|
+
}>;
|
|
742
|
+
/** View integration test: real pinned revision + production signer; returns
|
|
743
|
+
* the curated rows exactly as an agent would see them, the menu/model
|
|
744
|
+
* bytes, and DECODED (never signed, never replayable) claims. */
|
|
745
|
+
testReadCapability(name: string, input?: {
|
|
746
|
+
externalUserId?: string;
|
|
747
|
+
filters?: Record<string, unknown>;
|
|
748
|
+
}): Promise<Record<string, unknown>>;
|
|
749
|
+
/** Action protocol verifier — four phases (connectivity, duplicate
|
|
750
|
+
* same-intent, mismatch, reconcile) against your REAL endpoint, including
|
|
751
|
+
* intentional duplicate deliveries of one actionId; the server refuses
|
|
752
|
+
* (428) without `confirmDuplicates: true`. Phase outcomes use production
|
|
753
|
+
* vocabulary (committed/rejected/unknown) SEPARATELY from protocol
|
|
754
|
+
* verdicts — "Protocol: PASS, outcome: unknown" is a valid result. */
|
|
755
|
+
testActionCapability(name: string, input: {
|
|
756
|
+
confirmDuplicates: true;
|
|
757
|
+
args?: Record<string, unknown>;
|
|
758
|
+
externalUserId?: string;
|
|
759
|
+
}): Promise<Record<string, unknown>>;
|
|
652
760
|
listWebhooks(): Promise<{
|
|
653
761
|
webhooks: unknown[];
|
|
654
762
|
}>;
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// import { createAdminClient } from '@pouchy_ai/admin-sdk';
|
|
9
9
|
// const admin = createAdminClient({ adminKey: process.env.POUCHY_ADMIN_KEY! });
|
|
10
10
|
// const { agents } = await admin.listAgents();
|
|
11
|
-
export const ADMIN_SDK_VERSION = '0.
|
|
11
|
+
export const ADMIN_SDK_VERSION = '0.11.0';
|
|
12
12
|
export const DEFAULT_BASE_URL = 'https://pouchy.ai/v1/admin';
|
|
13
13
|
/** Default per-request timeout (ms). A hung upstream otherwise never rejects. */
|
|
14
14
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
@@ -44,7 +44,12 @@ export const LONG_WORK_REQUESTS = [
|
|
|
44
44
|
// GDPR erasure: recursive delete of the instance's whole users/** subtree
|
|
45
45
|
// plus the top-level social graph. A client abort here is the worst of the
|
|
46
46
|
// four — the operator is left not knowing whether the wipe completed.
|
|
47
|
-
{ method: 'DELETE', path: /^\/users\/[^/]+$/ }
|
|
47
|
+
{ method: 'DELETE', path: /^\/users\/[^/]+$/ },
|
|
48
|
+
// The Action protocol verifier makes REAL deliveries to the developer's
|
|
49
|
+
// endpoint (four phases incl. reconcile) — network round-trips we don't
|
|
50
|
+
// control the latency of. An abort mid-test also cannot undo phase A's
|
|
51
|
+
// side effect, so waiting is strictly better than cutting.
|
|
52
|
+
{ method: 'POST', path: /^\/capabilities\/[^/]+\/test-action$/ }
|
|
48
53
|
];
|
|
49
54
|
/** The deadline a given request runs under when the host set no `timeoutMs`.
|
|
50
55
|
* Exported so the contract is assertable without timing anything. */
|
|
@@ -55,9 +60,40 @@ export function requestDeadlineMs(method, path) {
|
|
|
55
60
|
? LONG_WORK_TIMEOUT_MS
|
|
56
61
|
: DEFAULT_TIMEOUT_MS;
|
|
57
62
|
}
|
|
63
|
+
/** Machine-readable failure tags the Admin API puts on an error body, mirroring
|
|
64
|
+
* the server's own list (`src/lib/server/platform/api-error-codes.ts`) and
|
|
65
|
+
* drift-tested against it. APPEND-ONLY: these are documented as switchable, so
|
|
66
|
+
* renaming or removing one breaks integrators.
|
|
67
|
+
*
|
|
68
|
+
* Every member is a **409**. That is the whole reason this vocabulary is
|
|
69
|
+
* published: on this plane the status line never disambiguates, so two of the
|
|
70
|
+
* durable-run routes answer 409 for two entirely different reasons each, with
|
|
71
|
+
* different recoveries. `code` is the only thing that tells them apart. */
|
|
72
|
+
export const ADMIN_ERROR_CODES = [
|
|
73
|
+
'schedule_limit_reached',
|
|
74
|
+
'channel_limit_reached',
|
|
75
|
+
'webhook_limit_reached',
|
|
76
|
+
'run_limit_reached',
|
|
77
|
+
'reembed_required',
|
|
78
|
+
'run_terminal',
|
|
79
|
+
'run_not_parked',
|
|
80
|
+
'stale_token',
|
|
81
|
+
'run_not_waiting',
|
|
82
|
+
'event_mismatch'
|
|
83
|
+
];
|
|
58
84
|
/** Thrown on any non-2xx response. `status` is the HTTP status; `message` is the
|
|
59
85
|
* server's `error` string when present; `retryAfter` is the throttle's own
|
|
60
|
-
* backoff in SECONDS on a 429 (undefined on every other failure)
|
|
86
|
+
* backoff in SECONDS on a 429 (undefined on every other failure); `code` is the
|
|
87
|
+
* server's machine tag when it named one (undefined otherwise).
|
|
88
|
+
*
|
|
89
|
+
* Why `code` exists: the Admin API's ten machine-readable failures are ALL on
|
|
90
|
+
* 409, so `status` cannot separate them and this package used to discard the
|
|
91
|
+
* one field that can. Two routes are the point — `resumeRun` answers 409 for
|
|
92
|
+
* `run_not_parked` (someone already decided; re-read, do not retry) or
|
|
93
|
+
* `stale_token` (your token aged out; re-read for the current one and answer
|
|
94
|
+
* again), and `signalRun` answers 409 for `run_not_waiting` or
|
|
95
|
+
* `event_mismatch`. This package's own docs told callers to distinguish those,
|
|
96
|
+
* which was not possible without switching on prose.
|
|
61
97
|
*
|
|
62
98
|
* Why `retryAfter` exists: the entire write surface of this SDK sits behind one
|
|
63
99
|
* per-IP throttle — `hooks.server.ts` runs a `v1-admin-write` bucket (120
|
|
@@ -74,14 +110,27 @@ export function requestDeadlineMs(method, path) {
|
|
|
74
110
|
export class AdminApiError extends Error {
|
|
75
111
|
status;
|
|
76
112
|
retryAfter;
|
|
77
|
-
|
|
113
|
+
code;
|
|
114
|
+
constructor(message, status, retryAfter, code) {
|
|
78
115
|
super(message);
|
|
79
116
|
this.name = 'AdminApiError';
|
|
80
117
|
this.status = status;
|
|
81
118
|
if (retryAfter !== undefined)
|
|
82
119
|
this.retryAfter = retryAfter;
|
|
120
|
+
// Only a non-empty string. A body carrying `code: null` or `code: 42`
|
|
121
|
+
// must leave the property ABSENT rather than stamping a falsy value that
|
|
122
|
+
// a `switch (e.code)` would fall through on in a way the caller can't see.
|
|
123
|
+
if (typeof code === 'string' && code)
|
|
124
|
+
this.code = code;
|
|
83
125
|
}
|
|
84
126
|
}
|
|
127
|
+
/** The `code` a non-2xx body named, or undefined. Kept beside `retryAfterFrom`
|
|
128
|
+
* and equally defensive: the body is parsed with a `.catch(() => ({}))`, so
|
|
129
|
+
* every field here is untrusted shape, not a typed response. */
|
|
130
|
+
function codeFrom(body) {
|
|
131
|
+
const v = body?.code;
|
|
132
|
+
return typeof v === 'string' && v ? v : undefined;
|
|
133
|
+
}
|
|
85
134
|
/** Seconds to wait before retrying a throttled response, or undefined when the
|
|
86
135
|
* server named neither a body field nor a header.
|
|
87
136
|
*
|
|
@@ -165,7 +214,7 @@ export function createAdminClient(opts) {
|
|
|
165
214
|
}
|
|
166
215
|
const data = (await res.json().catch(() => ({})));
|
|
167
216
|
if (!res.ok)
|
|
168
|
-
throw new AdminApiError(data?.error ?? `HTTP ${res.status}`, res.status, retryAfterFrom(res, data));
|
|
217
|
+
throw new AdminApiError(data?.error ?? `HTTP ${res.status}`, res.status, retryAfterFrom(res, data), codeFrom(data));
|
|
169
218
|
return data;
|
|
170
219
|
}
|
|
171
220
|
return {
|
|
@@ -237,6 +286,12 @@ export function createAdminClient(opts) {
|
|
|
237
286
|
cancelRun: (id) => request('DELETE', `/runs/${encodeURIComponent(id)}`),
|
|
238
287
|
resumeRun: (id, input) => request('POST', `/runs/${encodeURIComponent(id)}/resume`, input),
|
|
239
288
|
signalRun: (id, input) => request('POST', `/runs/${encodeURIComponent(id)}/signal`, input),
|
|
289
|
+
listCapabilities: () => request('GET', '/capabilities'),
|
|
290
|
+
publishCapability: (declaration) => request('POST', '/capabilities', declaration),
|
|
291
|
+
setCapabilityDisabled: (name, disabled) => request('PATCH', `/capabilities/${encodeURIComponent(name)}`, { disabled }),
|
|
292
|
+
listCapabilityVersions: (name) => request('GET', `/capabilities/${encodeURIComponent(name)}/versions`),
|
|
293
|
+
testReadCapability: (name, input = {}) => request('POST', `/capabilities/${encodeURIComponent(name)}/test-read`, input),
|
|
294
|
+
testActionCapability: (name, input) => request('POST', `/capabilities/${encodeURIComponent(name)}/test-action`, input),
|
|
240
295
|
listWebhooks: () => request('GET', '/webhooks'),
|
|
241
296
|
createWebhook: (input) => request('POST', '/webhooks', input),
|
|
242
297
|
deleteWebhook: (id) => request('DELETE', `/webhooks/${encodeURIComponent(id)}`),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pouchy_ai/admin-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Typed TypeScript client for the Pouchy Admin API \u2014 manage agents, keys, end users, knowledge, skills, channels, schedules, webhooks and credentials headlessly, with a project Admin key.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|