@pouchy_ai/admin-sdk 0.34.0 → 0.38.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 CHANGED
@@ -2,6 +2,80 @@
2
2
 
3
3
  All notable changes to `@pouchy_ai/admin-sdk` are documented here.
4
4
 
5
+ ## 0.38.0 — 2026-09-25
6
+
7
+ - **`ADMIN_ERROR_CODES` gains `subscription_exists`** (409). `POST
8
+ /v1/projects/{id}/billing/checkout` now refuses when the account already pays
9
+ through a live Stripe subscription. Before, a second checkout opened a SECOND
10
+ subscription: both billed, and each one's lifecycle events rewrote the
11
+ account's plan. Upgrade through `POST /v1/projects/{id}/billing/upgrade`
12
+ instead, which moves the existing subscription to the new price and charges
13
+ the prorated difference. Additive: the `AdminErrorCode` type was
14
+ already open to codes newer than the build.
15
+
16
+ ## 0.37.0 — 2026-09-24
17
+
18
+ - **`getUsage()` now types the `credits` block** the server has returned since
19
+ admin API 1.14.0: `{ project, account, limit, scope: 'account', enforcement:
20
+ { armed, plan, verdict, effect } }` (new exported types `UsageCredits` and
21
+ `UsageCreditEnforcement`). Additive: `usage` is unchanged, and `credits` is
22
+ optional because a server older than 1.14.0 does not send it.
23
+
24
+ `usage.credits / usage.creditLimit` is NOT a headroom ratio — `usage.credits`
25
+ is this project's slice and `creditLimit` is the ACCOUNT's pooled allowance,
26
+ so on a multi-project account it understates consumption. Until now the typed
27
+ client offered only that ratio (reading `res.credits` did not compile), and
28
+ this README's quickstart printed it. Read `credits.account / credits.limit`
29
+ instead; `account` is `null` when the pool could not be read, which means
30
+ unknown, never zero.
31
+
32
+ ## 0.36.0 — 2026-09-12
33
+
34
+ - **`deleteChannel` reports what happened to the PROVIDER registration**, and
35
+ takes `{ deprovision: false }` to suppress it. Admin API 1.13.0. Additive:
36
+ the option is optional and the response field is new.
37
+
38
+ Create has registered telegram/discord connectors with their provider since
39
+ 1.6.0 (`setWebhook` / the slash command). Delete never unregistered — and the
40
+ asymmetry was worse than an omission, because the delete also destroys the
41
+ stored bot token, so afterwards nothing on the server side could ever do it.
42
+ The bot kept pushing every message it could see to a dead inbound URL, which
43
+ on a fail-closed rate-limited prefix also keeps spending the project's
44
+ limiter quota.
45
+
46
+ Surfaced by an integrator asking whether `DELETE` called `deleteWebhook` —
47
+ the shape of question that is worth answering by reading the code.
48
+
49
+ - `ChannelProvisionCode` gains `webhook_deleted`, `command_deleted` and
50
+ `command_absent`. The union is closed and a published member is never removed,
51
+ so handle an unknown code as informational. `command_absent` is a **skip**,
52
+ not an ok: the Discord command was already gone, and "we removed it" versus
53
+ "there was nothing to remove" are different facts about the provider.
54
+
55
+ ## 0.35.0 — 2026-09-10
56
+
57
+ ### Added
58
+
59
+ - **Four story-package codes join `ADMIN_ERROR_CODES`**, and two of them were
60
+ already on the wire without being in it. `has_pins` and `pins_unknown` are
61
+ the two 409s of `DELETE …/story-packages/{packageId}` (world API 1.35.0):
62
+ an environment revision has pinned this package, versus the package predates
63
+ the pin stamp so whether one points at it is UNKNOWN and is not guessed. They
64
+ shipped through the same VARIABLE indirection that hid
65
+ `story_contract_v2_write_disabled` — the route forwarded `result.reason` as
66
+ `code`, so the vocabulary census could not see them; the route spells them
67
+ out now.
68
+
69
+ `story_package_limit` and `story_package_documents_limit` are the two ceiling
70
+ refusals, newly carrying a code at all. An integrator reported asserting on
71
+ the refusal SENTENCES because they were the only observation point for the
72
+ quota — which made a copy edit a breaking change for them and a number change
73
+ harmless, the exact inversion of what either should cost. The codes retire
74
+ the prose parsing.
75
+
76
+ Distinguishing the two delete refusals matters because their outs differ:
77
+ `has_pins` sends you to an environment, `pins_unknown` sends you to disable.
78
+
5
79
  ## 0.34.0 — 2026-09-08
6
80
 
7
81
  ### Fixed
package/README.md CHANGED
@@ -38,9 +38,15 @@ const { key } = await admin.createKey({ label: 'prod-backend', env: 'live' });
38
38
  console.log(key); // the plaintext token — shown ONCE
39
39
 
40
40
  // Read this month's usage — MAU, tokens, and the credit meter (all typed)
41
- const { usage } = await admin.getUsage();
41
+ const { usage, credits } = await admin.getUsage();
42
42
  console.log(usage.mau, '/', usage.mauLimit, 'MAU');
43
- console.log(usage.credits, '/', usage.creditLimit, 'credits');
43
+ // Credits pool across the ACCOUNT's projects: `usage.credits` is only this
44
+ // project's slice, so divide the ACCOUNT figure by the limit, never the slice.
45
+ // `account` is null when the pool could not be read — unknown, not zero.
46
+ if (credits) {
47
+ console.log(credits.project, 'credits here;', credits.account ?? '?', '/', credits.limit, 'account-wide');
48
+ console.log('next turn:', credits.enforcement.verdict ?? 'unknown', '—', credits.enforcement.effect);
49
+ }
44
50
  // Voice is the priciest metered unit — watch its slice, not just the total
45
51
  console.log(usage.creditsVoice, 'of those credits were', usage.voiceCalls, 'voice calls');
46
52
 
@@ -417,6 +423,7 @@ type), and is append-only:
417
423
  | `event_mismatch` | `signalRun` | the run wants a different key; your event is early or wrong |
418
424
  | `one_shot_spent` | `updateSchedule` | the one-shot already fired — create a new schedule instead of re-enabling |
419
425
  | `ghost_row` | `updateSchedule` | a legacy fieldless row that can never run — delete it, create a new schedule |
426
+ | `subscription_exists` | `POST /v1/projects/{id}/billing/checkout` (console) | the account already pays through a live subscription — upgrade it with `POST …/billing/upgrade` |
420
427
 
421
428
  `code` is `undefined` on every failure the server did not tag (400s, 404s, the
422
429
  429, transport errors), so always keep a `default` branch. The type has a
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const ADMIN_SDK_VERSION = "0.34.0";
1
+ export declare const ADMIN_SDK_VERSION = "0.38.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
@@ -60,7 +60,7 @@ export interface AdminClientOptions {
60
60
  * published: on this plane the status line never disambiguates, so two of the
61
61
  * durable-run routes answer 409 for two entirely different reasons each, with
62
62
  * different recoveries. `code` is the only thing that tells them apart. */
63
- export declare const ADMIN_ERROR_CODES: readonly ["story_contract_v2_write_disabled", "story_contract_v3_write_disabled", "story_contract_v4_write_disabled", "episode_history_incomplete", "episode_ended", "idempotency_key_reused", "stale_deliberation", "deliberation_disabled", "deliberation_uncalibrated", "deliberation_not_enabled", "deliberation_candidates_exceeded", "schedule_limit_reached", "one_shot_spent", "ghost_row", "channel_limit_reached", "webhook_limit_reached", "run_limit_reached", "reembed_required", "run_terminal", "run_not_parked", "stale_token", "run_not_waiting", "event_mismatch"];
63
+ export declare const ADMIN_ERROR_CODES: readonly ["story_contract_v2_write_disabled", "story_contract_v3_write_disabled", "story_contract_v4_write_disabled", "has_pins", "pins_unknown", "story_package_limit", "story_package_documents_limit", "episode_history_incomplete", "episode_ended", "idempotency_key_reused", "stale_deliberation", "deliberation_disabled", "deliberation_uncalibrated", "deliberation_not_enabled", "deliberation_candidates_exceeded", "schedule_limit_reached", "one_shot_spent", "ghost_row", "channel_limit_reached", "webhook_limit_reached", "run_limit_reached", "reembed_required", "run_terminal", "run_not_parked", "stale_token", "run_not_waiting", "event_mismatch", "subscription_exists"];
64
64
  /** `AdminApiError.code` values. The `(string & {})` arm keeps the type open for
65
65
  * codes newer than this SDK build while preserving autocomplete — same
66
66
  * doctrine as the companion SDK's `CompanionErrorCodeValue`. */
@@ -312,6 +312,36 @@ export interface MonthUsage {
312
312
  creditLimit?: number;
313
313
  [k: string]: unknown;
314
314
  }
315
+ /** What the credit gate would do on the NEXT turn of this account. */
316
+ export interface UsageCreditEnforcement {
317
+ /** Whether this deployment has the credit gate switched on at all. While
318
+ * false nothing is refused or downgraded at the ceiling. */
319
+ armed: boolean;
320
+ plan: string;
321
+ /** Computed on the ACCOUNT total, exactly as the gate does. `null` when the
322
+ * pool could not be read — unknown, not `allow`. */
323
+ verdict: 'allow' | 'downgrade' | 'deny' | null;
324
+ /** One line saying what `verdict` does (a paid-plan `downgrade` keeps turns
325
+ * running on `standard`; a free-plan ceiling refuses NEW sessions). */
326
+ effect: string;
327
+ }
328
+ /** The credit meter with both scopes named (admin API 1.14.0+). Read this, not
329
+ * `usage.credits / usage.creditLimit`: that divides THIS project's figure by
330
+ * the ACCOUNT's allowance and understates consumption on any multi-project
331
+ * account. */
332
+ export interface UsageCredits {
333
+ /** THIS project's consumption — the same number `usage.credits` carries. */
334
+ project: number;
335
+ /** The whole ACCOUNT's consumption: the numerator the gate compares to
336
+ * `limit`. `null` means it could not be read — never treat it as zero. */
337
+ account: number | null;
338
+ /** The ACCOUNT allowance (same value as `usage.creditLimit`). */
339
+ limit: number;
340
+ /** Which pool `limit` governs. Credits pool across the owner's projects, so
341
+ * a second project does NOT isolate a workload — a second account does. */
342
+ scope: 'account';
343
+ enforcement: UsageCreditEnforcement;
344
+ }
315
345
  /** One month of the usage-history series. Unlike `MonthUsage` (the current
316
346
  * month's full meter incl. day buckets and the cap), history rows are the
317
347
  * compact per-month rollup the server keeps for every past month. */
@@ -401,13 +431,15 @@ export interface ChannelSecretInput {
401
431
  extra?: Record<string, string>;
402
432
  }
403
433
  /** Outcome class of the provider registration the server performs on create,
404
- * and on a rotate / secret replacement. Mirrors the server's `ProvisionStatus`
405
- * (`channels/provision.ts`); `admin-sdk-provision.drift.test.ts` binds them. */
434
+ * on a rotate / secret replacement, and — from admin API 1.13.0 — the
435
+ * UNregistration it performs on delete. Mirrors the server's
436
+ * `ProvisionStatus` (`channels/provision.ts`);
437
+ * `admin-sdk-provision.drift.test.ts` binds them. */
406
438
  export type ChannelProvisionStatus = 'ok' | 'skipped' | 'failed';
407
439
  /** Machine-readable reason for a `ChannelProvisionResult`. The server treats
408
440
  * this vocabulary as part of the response contract (the dashboard keys its
409
441
  * copy off it), so branch on `code`, never on `detail`. */
410
- export type ChannelProvisionCode = 'webhook_set' | 'command_registered' | 'unsupported_type' | 'no_token' | 'no_inbound_url' | 'not_requested' | 'invalid_command_name' | 'app_lookup_failed' | 'upstream_rejected' | 'upstream_unreachable';
442
+ export type ChannelProvisionCode = 'webhook_set' | 'command_registered' | 'unsupported_type' | 'no_token' | 'no_inbound_url' | 'not_requested' | 'invalid_command_name' | 'app_lookup_failed' | 'upstream_rejected' | 'upstream_unreachable' | 'webhook_deleted' | 'command_deleted' | 'command_absent';
411
443
  /** What the provider said when the server registered this connector for you.
412
444
  *
413
445
  * Registration is an enrichment on top of a durable write: the connector
@@ -1083,8 +1115,24 @@ export interface AdminClient {
1083
1115
  * transport, when not suppressed with `autoProvision: false`. */
1084
1116
  provision?: ChannelProvisionResult;
1085
1117
  }>;
1086
- deleteChannel(channelId: string): Promise<{
1118
+ /** Delete a connector — and, for telegram/discord, UNREGISTER it with the
1119
+ * provider first (admin API 1.13.0).
1120
+ *
1121
+ * The ordering is the point: the delete also destroys the stored bot token,
1122
+ * so an unregistration skipped at that moment can never be done afterwards
1123
+ * from the server side — the bot would keep pushing every message it can see
1124
+ * to a dead inbound URL, and only whoever holds the token could stop it.
1125
+ *
1126
+ * Same contract as create-side registration: the connector is removed
1127
+ * whether or not the provider answers, and a `failed` outcome carries
1128
+ * `manualCommand`. Pass `deprovision: false` to keep the provider
1129
+ * registration (for a caller who manages the bot webhook themselves). */
1130
+ deleteChannel(channelId: string, options?: {
1131
+ deprovision?: false;
1132
+ }): Promise<{
1087
1133
  ok: boolean;
1134
+ /** Absent for a transport with no provider registration. */
1135
+ deprovision?: ChannelProvisionResult;
1088
1136
  }>;
1089
1137
  listSchedules(): Promise<{
1090
1138
  schedules: unknown[];
@@ -1344,8 +1392,12 @@ export interface AdminClient {
1344
1392
  testWebhook(webhookId: string): Promise<DeliveryOutcome>;
1345
1393
  /** Manually re-send a recorded delivery's original body, re-signed. */
1346
1394
  redeliverWebhook(deliveryId: string): Promise<DeliveryOutcome>;
1395
+ /** Current-month usage. `credits` (admin API 1.14.0+; absent from an older
1396
+ * server) is the block to read for headroom — `account / limit` is the
1397
+ * ratio the gate enforces. */
1347
1398
  getUsage(): Promise<{
1348
1399
  usage: MonthUsage;
1400
+ credits?: UsageCredits;
1349
1401
  }>;
1350
1402
  /** Month-over-month usage series, oldest first; quiet months zero-fill
1351
1403
  * rather than drop. `scope: 'account'` means the pooled account series —
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.34.0';
11
+ export const ADMIN_SDK_VERSION = '0.38.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;
@@ -73,6 +73,10 @@ export const ADMIN_ERROR_CODES = [
73
73
  'story_contract_v2_write_disabled',
74
74
  'story_contract_v3_write_disabled',
75
75
  'story_contract_v4_write_disabled',
76
+ 'has_pins',
77
+ 'pins_unknown',
78
+ 'story_package_limit',
79
+ 'story_package_documents_limit',
76
80
  'episode_history_incomplete',
77
81
  'episode_ended',
78
82
  'idempotency_key_reused',
@@ -92,7 +96,11 @@ export const ADMIN_ERROR_CODES = [
92
96
  'run_not_parked',
93
97
  'stale_token',
94
98
  'run_not_waiting',
95
- 'event_mismatch'
99
+ 'event_mismatch',
100
+ // 0.38.0 — checkout on an account that already pays through a live
101
+ // subscription. Recovery: POST /v1/projects/{id}/billing/upgrade, which moves
102
+ // that subscription to the new price instead of opening a second one.
103
+ 'subscription_exists'
96
104
  ];
97
105
  /** Thrown on any non-2xx response AND on a transport failure that never reached
98
106
  * the server (network / DNS / timeout), which carries `status: 0`. `status` is
@@ -330,7 +338,7 @@ export function createAdminClient(opts) {
330
338
  createChannel: (input) => request('POST', '/channels', input),
331
339
  getChannel: (id) => request('GET', `/channels/${encodeURIComponent(id)}`),
332
340
  updateChannel: (id, patch) => request('PATCH', `/channels/${encodeURIComponent(id)}`, patch),
333
- deleteChannel: (id) => request('DELETE', `/channels/${encodeURIComponent(id)}`),
341
+ deleteChannel: (id, options) => request('DELETE', `/channels/${encodeURIComponent(id)}${options?.deprovision === false ? '?deprovision=false' : ''}`),
334
342
  listSchedules: () => request('GET', '/schedules'),
335
343
  createSchedule: (input) => request('POST', '/schedules', input),
336
344
  getSchedule: (id) => request('GET', `/schedules/${encodeURIComponent(id)}`),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pouchy_ai/admin-sdk",
3
- "version": "0.34.0",
3
+ "version": "0.38.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",