@pouchy_ai/admin-sdk 0.7.0 → 0.8.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 +32 -0
- package/README.md +51 -0
- package/dist/index.d.ts +111 -2
- package/dist/index.js +23 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@pouchy_ai/admin-sdk` are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.8.0 — 2026-07-28
|
|
6
|
+
|
|
7
|
+
- **Durable runs.** `listRuns`, `createRun`, `getRun`, `cancelRun`, `resumeRun`,
|
|
8
|
+
`signalRun` — long-running agent work that is checkpointed per step and
|
|
9
|
+
advanced by the platform across ticks, so it survives restarts and can span
|
|
10
|
+
minutes to hours.
|
|
11
|
+
- The feature shipped server-side without this plane: the routes existed only
|
|
12
|
+
under `/v1/projects/{projectId}/…` (owner session auth), so an admin-key
|
|
13
|
+
integrator had no reachable endpoint at all. `/v1/admin/runs/*` now mirrors
|
|
14
|
+
them, and both planes share one implementation.
|
|
15
|
+
- Note the status codes: every run mutation answers **202**, not 200/201. The
|
|
16
|
+
work happens on a later tick, never inside your request — treating the
|
|
17
|
+
response as a result is the mistake the code is chosen to prevent.
|
|
18
|
+
|
|
19
|
+
## 0.7.1 — 2026-07-27
|
|
20
|
+
|
|
21
|
+
Fix: `timeoutMs: 0` now disables the deadline instead of aborting every request.
|
|
22
|
+
|
|
23
|
+
- **`timeoutMs: 0` means "no deadline".** It previously meant "abort on the next
|
|
24
|
+
tick": `opts.timeoutMs ?? …` correctly treats `0` as a set value, but that `0`
|
|
25
|
+
was then handed to `AbortSignal.timeout(0)`, which fires immediately — so a
|
|
26
|
+
client constructed with `timeoutMs: 0` failed *every* call with the
|
|
27
|
+
self-contradicting `request timed out after 0ms`. The companion JS SDK has
|
|
28
|
+
documented `requestTimeoutMs: 0` as "disables" since 0.35.0 and implements it
|
|
29
|
+
as an explicit `if (!budget)` branch; a host carrying that idiom to this
|
|
30
|
+
package got the opposite behaviour. The signal is now omitted entirely when
|
|
31
|
+
the resolved deadline is `0`.
|
|
32
|
+
- No other value changes. An unset `timeoutMs` still resolves to the
|
|
33
|
+
route-sized default (30s, or `LONG_WORK_TIMEOUT_MS` for the four
|
|
34
|
+
`maxDuration: 300` handlers), and any positive value still bounds every
|
|
35
|
+
request as before.
|
|
36
|
+
|
|
5
37
|
## 0.7.0 — 2026-07-27
|
|
6
38
|
|
|
7
39
|
Adds: a throttled request now tells you how long to wait.
|
package/README.md
CHANGED
|
@@ -80,6 +80,11 @@ Setting `timeoutMs` explicitly always wins and applies to **every** request, lon
|
|
|
80
80
|
or short. `requestDeadlineMs(method, path)` returns the default a given request
|
|
81
81
|
would use.
|
|
82
82
|
|
|
83
|
+
`timeoutMs: 0` **disables** the deadline — the request runs unbounded. This is
|
|
84
|
+
the same contract as the companion JS SDK's `requestTimeoutMs: 0`, so the idiom
|
|
85
|
+
carries between the two packages. (Before 0.7.1 a `0` aborted every request on
|
|
86
|
+
the next tick.)
|
|
87
|
+
|
|
83
88
|
## Errors
|
|
84
89
|
|
|
85
90
|
Every method throws `AdminApiError` on failure — a non-2xx response, a network
|
|
@@ -136,10 +141,56 @@ Reads (`GET`) are not covered by that bucket. `retryAfter` is available from
|
|
|
136
141
|
| Credentials | `listCredentials` · `putCredentials` · `deleteCredentials` |
|
|
137
142
|
| Channels | `listChannels` · `createChannel` · `getChannel` · `updateChannel` · `deleteChannel` |
|
|
138
143
|
| Schedules | `listSchedules` · `createSchedule` · `getSchedule` · `updateSchedule` · `deleteSchedule` |
|
|
144
|
+
| Durable runs | `listRuns` · `createRun` · `getRun` · `cancelRun` · `resumeRun` · `signalRun` |
|
|
139
145
|
| Webhooks | `listWebhooks` · `createWebhook` · `updateWebhook` · `rotateWebhookSecret` · `deleteWebhook` · `testWebhook` · `redeliverWebhook` |
|
|
140
146
|
| Reporting | `getUsage` · `getBilling` · `getTracesSummary` · `getRecentTraces` · `getLogs` · `getProject` · `updateProject` |
|
|
141
147
|
| Escape hatch | `request(method, path, body?)` — any endpoint not yet typed |
|
|
142
148
|
|
|
149
|
+
### Durable runs
|
|
150
|
+
|
|
151
|
+
A schedule *fires* one turn and is done. A **run** is long-lived agent work:
|
|
152
|
+
checkpointed after every step and advanced by the platform across as many ticks
|
|
153
|
+
as it needs, so it survives restarts and can span minutes to hours.
|
|
154
|
+
|
|
155
|
+
Every mutation answers **202**, not 200/201 — the work happens on a later tick,
|
|
156
|
+
never inside your request:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
const agentId = 'agt_123';
|
|
160
|
+
const { run } = await admin.createRun({
|
|
161
|
+
agentId,
|
|
162
|
+
externalUserId: 'user-42',
|
|
163
|
+
goal: 'Reconcile the overnight invoices and flag anything over $500.',
|
|
164
|
+
steps: [
|
|
165
|
+
{ id: 'gather', kind: 'agent_turn' },
|
|
166
|
+
{ id: 'ok', kind: 'human_approval', input: { prompt: 'Approve the flagged refunds?' } },
|
|
167
|
+
{ id: 'apply', kind: 'agent_turn' }
|
|
168
|
+
]
|
|
169
|
+
});
|
|
170
|
+
console.log(run.id, run.status); // → 'queued'
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
When a run reaches a `human_approval` step it **parks**: it holds no lease and
|
|
174
|
+
leaves the platform's due window, so nothing will move it until you answer.
|
|
175
|
+
You learn about it either way — the `agent.run_awaiting` webhook pushes the
|
|
176
|
+
token, and `getRun` always carries it:
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
const runId = 'run_abc';
|
|
180
|
+
const { run } = await admin.getRun(runId);
|
|
181
|
+
if (run.status === 'awaiting_human' && run.awaiting) {
|
|
182
|
+
await admin.resumeRun(runId, { approved: true, token: run.awaiting.token });
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
The `token` is a fencing token, not a secret — authorization is your admin key.
|
|
187
|
+
It exists so a stale or replayed decision cannot land on a question the run has
|
|
188
|
+
already moved past (you get `409 stale_token`).
|
|
189
|
+
|
|
190
|
+
An `await_event` step parks the same way until you call `signalRun` with the
|
|
191
|
+
matching key; a mismatch answers `409 event_mismatch`, which lets a webhook
|
|
192
|
+
retry tell "too late" from "rejected".
|
|
193
|
+
|
|
143
194
|
## OpenAPI
|
|
144
195
|
|
|
145
196
|
The machine-readable contract is served at **`GET https://pouchy.ai/v1/admin/openapi`**
|
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.8.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
|
|
@@ -41,7 +41,13 @@ export interface AdminClientOptions {
|
|
|
41
41
|
* and the GDPR user delete), which default to
|
|
42
42
|
* {@link LONG_WORK_TIMEOUT_MS}. Setting this explicitly always wins and
|
|
43
43
|
* applies to every request, long or short. A request that outlives its
|
|
44
|
-
* deadline rejects with an AdminApiError(status 0).
|
|
44
|
+
* deadline rejects with an AdminApiError(status 0).
|
|
45
|
+
*
|
|
46
|
+
* `0` DISABLES the deadline — the request runs unbounded. This mirrors the
|
|
47
|
+
* companion JS SDK's `requestTimeoutMs`, whose documented contract is the
|
|
48
|
+
* same ("`0` disables"), so the idiom carries between the two packages.
|
|
49
|
+
* Before 0.7.1 a `0` here meant "abort on the next tick", i.e. every call
|
|
50
|
+
* failed with the self-contradicting `request timed out after 0ms`. */
|
|
45
51
|
timeoutMs?: number;
|
|
46
52
|
}
|
|
47
53
|
/** Thrown on any non-2xx response. `status` is the HTTP status; `message` is the
|
|
@@ -160,6 +166,48 @@ export interface DeliveryOutcome {
|
|
|
160
166
|
}
|
|
161
167
|
/** The Admin API client. Every method returns the parsed JSON body; failures
|
|
162
168
|
* throw AdminApiError. */
|
|
169
|
+
/** A durable run as the API returns it. Typed rather than `unknown` because
|
|
170
|
+
* the parked-run flow — read `status`, read `awaiting.token`, answer — is the
|
|
171
|
+
* SDK's most important interaction, and forcing a cast there would put the
|
|
172
|
+
* burden on every integrator at exactly the wrong moment. */
|
|
173
|
+
export interface AdminRunStep {
|
|
174
|
+
id: string;
|
|
175
|
+
kind?: string;
|
|
176
|
+
status: 'pending' | 'running' | 'succeeded' | 'failed' | 'compensated' | 'skipped';
|
|
177
|
+
attempts: number;
|
|
178
|
+
error?: string;
|
|
179
|
+
nextAttemptAt?: number;
|
|
180
|
+
output?: unknown;
|
|
181
|
+
}
|
|
182
|
+
export interface AdminRun {
|
|
183
|
+
id: string;
|
|
184
|
+
projectId: string;
|
|
185
|
+
agentId: string;
|
|
186
|
+
externalUserId: string;
|
|
187
|
+
goal: string;
|
|
188
|
+
status: 'queued' | 'running' | 'awaiting_human' | 'awaiting_event' | 'succeeded' | 'failed' | 'compensating' | 'compensated' | 'compensation_failed' | 'cancelled';
|
|
189
|
+
journal: AdminRunStep[];
|
|
190
|
+
/** Epoch ms the platform next looks at this run. Absent when terminal, or
|
|
191
|
+
* parked on a human decision (only an answer moves it). */
|
|
192
|
+
wakeAt?: number;
|
|
193
|
+
/** Present while parked on a person. `token` fences your answer against a
|
|
194
|
+
* stale or replayed decision — it is not a secret. */
|
|
195
|
+
awaiting?: {
|
|
196
|
+
token: string;
|
|
197
|
+
prompt: string;
|
|
198
|
+
stepId: string;
|
|
199
|
+
since: number;
|
|
200
|
+
};
|
|
201
|
+
/** Present while parked on an external event, with its deadline if it has one. */
|
|
202
|
+
awaitingEvent?: {
|
|
203
|
+
key: string;
|
|
204
|
+
stepId: string;
|
|
205
|
+
since: number;
|
|
206
|
+
timeoutAt?: number;
|
|
207
|
+
};
|
|
208
|
+
createdAt: string;
|
|
209
|
+
updatedAt: string;
|
|
210
|
+
}
|
|
163
211
|
export interface AdminClient {
|
|
164
212
|
listAgents(): Promise<{
|
|
165
213
|
agents: Agent[];
|
|
@@ -515,6 +563,67 @@ export interface AdminClient {
|
|
|
515
563
|
deleteSchedule(scheduleId: string): Promise<{
|
|
516
564
|
ok: boolean;
|
|
517
565
|
}>;
|
|
566
|
+
listRuns(query?: {
|
|
567
|
+
status?: string;
|
|
568
|
+
limit?: number;
|
|
569
|
+
}): Promise<{
|
|
570
|
+
runs: AdminRun[];
|
|
571
|
+
}>;
|
|
572
|
+
/** Start a durable run. Omit `steps` for the common case — a single agent
|
|
573
|
+
* turn carrying `goal`. Step ids must be unique and match
|
|
574
|
+
* `[A-Za-z0-9_-]{1,64}`; `kind` is one of `agent_turn`, `subtask_fanout`,
|
|
575
|
+
* `human_approval`, `await_event`. Answers **202**, not 201: the run is
|
|
576
|
+
* accepted and has not run yet. */
|
|
577
|
+
createRun(input: {
|
|
578
|
+
agentId: string;
|
|
579
|
+
externalUserId: string;
|
|
580
|
+
goal: string;
|
|
581
|
+
env?: 'live' | 'test';
|
|
582
|
+
startAt?: number;
|
|
583
|
+
steps?: {
|
|
584
|
+
id: string;
|
|
585
|
+
kind?: string;
|
|
586
|
+
input?: Record<string, unknown>;
|
|
587
|
+
}[];
|
|
588
|
+
}): Promise<{
|
|
589
|
+
run: AdminRun;
|
|
590
|
+
}>;
|
|
591
|
+
/** Fetch a run with its full step journal — what ran, what retried, what was
|
|
592
|
+
* rolled back. For a failed run this is the only place the reason survives. */
|
|
593
|
+
getRun(runId: string): Promise<{
|
|
594
|
+
run: AdminRun;
|
|
595
|
+
}>;
|
|
596
|
+
/** Request cancellation. The run stops at the next STEP BOUNDARY and then
|
|
597
|
+
* rolls back completed work, so this returns `status: 'cancelling'`. */
|
|
598
|
+
cancelRun(runId: string): Promise<{
|
|
599
|
+
ok: boolean;
|
|
600
|
+
status: string;
|
|
601
|
+
}>;
|
|
602
|
+
/** Answer a run parked on a human decision. `token` comes from the run's
|
|
603
|
+
* `awaiting.token` (also carried by the `agent.run_awaiting` webhook) and
|
|
604
|
+
* fences the decision against a stale or replayed answer. Without this call
|
|
605
|
+
* a parked run never resumes: it holds no lease and has left the platform's
|
|
606
|
+
* due window by design. */
|
|
607
|
+
resumeRun(runId: string, input: {
|
|
608
|
+
approved: boolean;
|
|
609
|
+
token: string;
|
|
610
|
+
note?: string;
|
|
611
|
+
}): Promise<{
|
|
612
|
+
ok: boolean;
|
|
613
|
+
approved: boolean;
|
|
614
|
+
status: string;
|
|
615
|
+
}>;
|
|
616
|
+
/** Deliver an external event to a run parked on one. `event` must equal the
|
|
617
|
+
* key the run is waiting for — a mismatch answers 409 `event_mismatch`, so a
|
|
618
|
+
* webhook retry can tell "too late" from "rejected". */
|
|
619
|
+
signalRun(runId: string, input: {
|
|
620
|
+
event: string;
|
|
621
|
+
payload?: unknown;
|
|
622
|
+
}): Promise<{
|
|
623
|
+
ok: boolean;
|
|
624
|
+
event: string;
|
|
625
|
+
status: string;
|
|
626
|
+
}>;
|
|
518
627
|
listWebhooks(): Promise<{
|
|
519
628
|
webhooks: unknown[];
|
|
520
629
|
}>;
|
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.8.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;
|
|
@@ -135,6 +135,8 @@ export function createAdminClient(opts) {
|
|
|
135
135
|
// An explicit host `timeoutMs` always wins (JS SDK contract); otherwise
|
|
136
136
|
// the deadline is sized to the ROUTE, so the four handlers that declare
|
|
137
137
|
// `maxDuration: 300` are not cut at 30s while they are still working.
|
|
138
|
+
// `??` (not `||`) is deliberate: 0 is a SET value meaning "no deadline",
|
|
139
|
+
// and it must not fall through to the route default.
|
|
138
140
|
const timeoutMs = opts.timeoutMs ?? requestDeadlineMs(method, path);
|
|
139
141
|
// Every failure surfaces as an AdminApiError (the doc contract): a
|
|
140
142
|
// network/DNS error or a timeout would otherwise escape as a raw
|
|
@@ -149,7 +151,12 @@ export function createAdminClient(opts) {
|
|
|
149
151
|
...(body !== undefined ? { 'content-type': 'application/json' } : {})
|
|
150
152
|
},
|
|
151
153
|
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
152
|
-
signal
|
|
154
|
+
// timeoutMs === 0 → no signal at all. `AbortSignal.timeout(0)`
|
|
155
|
+
// does NOT mean "no deadline"; it fires on the next tick, so
|
|
156
|
+
// arming it here aborted every request before the response could
|
|
157
|
+
// land. The companion JS SDK spells the same branch
|
|
158
|
+
// (`if (!budget) return this.doFetch(...)`).
|
|
159
|
+
signal: timeoutMs ? AbortSignal.timeout(timeoutMs) : undefined
|
|
153
160
|
});
|
|
154
161
|
}
|
|
155
162
|
catch (e) {
|
|
@@ -216,6 +223,20 @@ export function createAdminClient(opts) {
|
|
|
216
223
|
getSchedule: (id) => request('GET', `/schedules/${encodeURIComponent(id)}`),
|
|
217
224
|
updateSchedule: (id, patch) => request('PATCH', `/schedules/${encodeURIComponent(id)}`, patch),
|
|
218
225
|
deleteSchedule: (id) => request('DELETE', `/schedules/${encodeURIComponent(id)}`),
|
|
226
|
+
listRuns: (q) => {
|
|
227
|
+
const qs = new URLSearchParams();
|
|
228
|
+
if (q?.status)
|
|
229
|
+
qs.set('status', q.status);
|
|
230
|
+
if (typeof q?.limit === 'number')
|
|
231
|
+
qs.set('limit', String(q.limit));
|
|
232
|
+
const tail = qs.toString();
|
|
233
|
+
return request('GET', `/runs${tail ? `?${tail}` : ''}`);
|
|
234
|
+
},
|
|
235
|
+
createRun: (input) => request('POST', '/runs', input),
|
|
236
|
+
getRun: (id) => request('GET', `/runs/${encodeURIComponent(id)}`),
|
|
237
|
+
cancelRun: (id) => request('DELETE', `/runs/${encodeURIComponent(id)}`),
|
|
238
|
+
resumeRun: (id, input) => request('POST', `/runs/${encodeURIComponent(id)}/resume`, input),
|
|
239
|
+
signalRun: (id, input) => request('POST', `/runs/${encodeURIComponent(id)}/signal`, input),
|
|
219
240
|
listWebhooks: () => request('GET', '/webhooks'),
|
|
220
241
|
createWebhook: (input) => request('POST', '/webhooks', input),
|
|
221
242
|
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.8.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",
|