@pouchy_ai/admin-sdk 0.16.0 → 0.17.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,85 @@
2
2
 
3
3
  All notable changes to `@pouchy_ai/admin-sdk` are documented here.
4
4
 
5
+ ## 0.17.0 — 2026-08-07
6
+
7
+ - **New `extractJson()` — structured JSON output, outside the companion.**
8
+ `POST /v1/admin/utility/json`. Additive; nothing else changed.
9
+
10
+ This exists because using a companion agent for extraction does not work, and
11
+ did not fail in a way that explained itself. A companion turn is a
12
+ conversation engine — persona prompt, memory recall, tools — and when the
13
+ model returns an empty completion it answers with a natural-language fallback
14
+ line rather than shipping an empty bubble. Correct for a chat surface, wrong
15
+ for a parser. An extraction prompt reliably *reaches* that fallback, because
16
+ the reasoning-effort bump off the cheapest tier is gated on conversational and
17
+ functional cues (weather, search, wallet, social) that an extraction input
18
+ never matches. The result was a soft chat sentence where JSON was expected.
19
+
20
+ `extractJson()` shares none of that machinery: no persona, no memory, no
21
+ tools, no session, no fallback. One provider call with `response_format` set,
22
+ and a parsed result.
23
+
24
+ ```ts
25
+ const { data } = await admin.extractJson<{ nickname: string | null }>({
26
+ content: '叫我 Alex',
27
+ schema: {
28
+ type: 'object',
29
+ additionalProperties: false,
30
+ required: ['nickname'],
31
+ properties: { nickname: { type: ['string', 'null'] } }
32
+ }
33
+ });
34
+ // → { nickname: 'Alex' }
35
+ ```
36
+
37
+ - **`strict` defaults to true**, which asks the provider to ENFORCE the schema.
38
+ The schema must then sit inside the provider's structured-output subset: root
39
+ object, `additionalProperties: false`, and every property listed in
40
+ `required` — model an optional field as a union with `null` rather than by
41
+ omitting it from `required`. A schema outside the subset comes back as `400`
42
+ / `code: 'schema_invalid'` carrying the provider's own reason instead of
43
+ failing at generation time. Pass `strict: false` to fall back to plain JSON
44
+ mode plus server-side validation.
45
+
46
+ - **Failures are typed, not prose.** `schema_invalid` (400 — fix the schema;
47
+ retrying verbatim cannot help), `unavailable` (5xx — transient, back off),
48
+ `invalid_json` (502 — a completion arrived but did not parse or did not
49
+ satisfy the schema). `invalid_json` carries `raw`, the text actually
50
+ returned, so the failure is debuggable rather than opaque.
51
+
52
+ - **Note on schema key names.** The companion reply path strips objects
53
+ matching internal memory-pipeline signatures, among them
54
+ `{domain, key, value, label}` and `{content, kind, importance}`. That
55
+ stripping does not apply to this endpoint — but if you were previously
56
+ extracting through a companion agent with a schema shaped like either of
57
+ those, a *correct* answer could be removed before you saw it. Another reason
58
+ to move extraction here.
59
+
60
+ ## 0.16.1 — 2026-08-06
61
+
62
+ Documentation only — no type, signature or runtime change. Two doc comments
63
+ were describing a contract the server does not have.
64
+
65
+ - **`updateSkill` said the response echoes "the knob(s) you changed".** It is
66
+ one knob per call. `PATCH /skills/{slug}` disambiguates by payload, so a
67
+ patch naming two groups — `{ ratePerMin, maxCallsPerDay }` — used to perform
68
+ whichever the server checked first and silently drop the rest under a `200`.
69
+ The plural in this package's own doc invited exactly that call. The server
70
+ now refuses an ambiguous body with a `400` naming the groups it found, and
71
+ the doc states the rule. `{ freeHttp, grantedDomains }` remains ONE knob (a
72
+ single write), not two.
73
+
74
+ The typed conveniences (`setSkillRate`, `setSkillDailyCap`, `grantSkill`)
75
+ were never affected — each sends exactly one knob by construction. Only a
76
+ hand-built `updateSkill` patch could reach it.
77
+
78
+ - **`grantSkill` is a full REPLACE and did not say so.** `grantedDomains` is
79
+ optional in the signature, which reads as "leave it alone"; it is sent as
80
+ `[]` when omitted, clearing the extra allowlist. Pass the domains you want to
81
+ end up with on every call. (The manifest's own `allowed_domains` is a
82
+ separate list and is never touched by this call.)
83
+
5
84
  ## 0.16.0 — 2026-08-06
6
85
 
7
86
  - **Catches the package up to a server change that already shipped.**
package/README.md CHANGED
@@ -58,6 +58,44 @@ console.log(`armed — ${armed.reprovisioned} running instance(s) updated`);
58
58
  // The agent can now drive the API from the skill's prose via http_request.
59
59
  ```
60
60
 
61
+ ### Structured JSON — use `extractJson`, not a companion agent
62
+
63
+ ```ts
64
+ const { data } = await admin.extractJson<{ nickname: string | null }>({
65
+ content: '叫我 Alex',
66
+ schema: {
67
+ type: 'object',
68
+ additionalProperties: false,
69
+ required: ['nickname'], // strict mode: EVERY property, always
70
+ properties: { nickname: { type: ['string', 'null'] } } // optional → union with null
71
+ }
72
+ });
73
+ console.log(data.nickname); // 'Alex'
74
+ ```
75
+
76
+ A companion turn is a **conversation** engine — persona prompt, memory recall,
77
+ tools — and when the model returns an empty completion it answers with a
78
+ natural-language fallback line rather than an empty bubble. That is right for a
79
+ chat surface and wrong for a parser, and an extraction prompt reliably reaches
80
+ it, because the reasoning-effort bump off the cheapest tier is gated on
81
+ conversational/functional cues an extraction input never matches. So a companion
82
+ agent asked for JSON returns chat filler, fairly consistently.
83
+
84
+ `extractJson` shares none of that: no persona, no memory, no tools, no session,
85
+ no fallback — one provider call with `response_format` set. `strict` defaults to
86
+ true, which makes the provider **enforce** the schema; the schema must then sit
87
+ inside the provider's structured-output subset (root object,
88
+ `additionalProperties: false`, every property listed in `required`). Pass
89
+ `strict: false` for schemas outside it — you still get JSON mode plus
90
+ server-side validation.
91
+
92
+ Failures are typed rather than prose, so the recoveries are distinguishable:
93
+ `schema_invalid` (400 — fix the schema; retrying verbatim cannot help),
94
+ `unavailable` (5xx — transient, back off), and `invalid_json` (502 — a
95
+ completion arrived but did not parse or satisfy the schema; the error carries
96
+ `raw`, the text actually returned). Tokens roll into the project's month usage
97
+ like any other model call.
98
+
61
99
  Every skill knob (`setSkillRate`, `setSkillDailyCap`, `grantSkill`) returns
62
100
  `SkillKnobResult<T>` — the knob you set plus `reprovisioned` (instances the new
63
101
  def reached) and `truncated`. A knob only binds a running agent once the def
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const ADMIN_SDK_VERSION = "0.16.0";
1
+ export declare const ADMIN_SDK_VERSION = "0.17.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
@@ -652,6 +652,49 @@ export interface AdminClient {
652
652
  mode: 'semantic' | 'lexical';
653
653
  hits: unknown[];
654
654
  }>;
655
+ /** Structured JSON extraction, OUTSIDE the companion.
656
+ *
657
+ * Use this — not a companion agent — whenever you want machine-readable
658
+ * output. A companion turn is a conversation engine: it carries a persona
659
+ * prompt, recalls memory, and when the model returns nothing it answers with
660
+ * a natural-language fallback line rather than dead air. That is right for a
661
+ * chat bubble and wrong for a parser, and an extraction prompt reliably
662
+ * triggers it, because the reasoning-effort bump off the cheapest tier is
663
+ * gated on conversational/functional cues that an extraction input does not
664
+ * match. This endpoint shares none of that: no persona, no memory, no tools,
665
+ * no session, no fallback — one provider call with `response_format` set.
666
+ *
667
+ * `strict` (default true) asks the provider to ENFORCE the schema. It must
668
+ * then sit inside the provider's structured-output subset: a root object,
669
+ * `additionalProperties: false`, and EVERY property listed in `required`
670
+ * (model an optional field as a union with `null`, not by omitting it from
671
+ * `required`). A schema outside the subset is rejected with `400` /
672
+ * `code: 'schema_invalid'` naming the provider's reason — pass
673
+ * `strict: false` to fall back to plain JSON mode plus server-side
674
+ * validation.
675
+ *
676
+ * Failures are typed rather than prose, which is the point:
677
+ * `schema_invalid` (400 — fix the schema, retrying verbatim cannot help),
678
+ * `unavailable` (5xx — transient, back off), `invalid_json` (502 — a
679
+ * completion arrived but did not parse or did not satisfy the schema; the
680
+ * error carries `raw`, the text actually returned).
681
+ *
682
+ * Tokens roll into the project's month usage like any other model call. */
683
+ extractJson<T = unknown>(input: {
684
+ schema: Record<string, unknown>;
685
+ content: string;
686
+ system?: string;
687
+ strict?: boolean;
688
+ model?: string;
689
+ schemaName?: string;
690
+ }): Promise<{
691
+ data: T;
692
+ raw: string;
693
+ usage?: {
694
+ promptTokens: number;
695
+ completionTokens: number;
696
+ };
697
+ }>;
655
698
  deleteKnowledge(docId: string): Promise<{
656
699
  deleted: boolean;
657
700
  }>;
@@ -671,11 +714,15 @@ export interface AdminClient {
671
714
  version: number;
672
715
  };
673
716
  }>;
674
- /** Generic PATCH of a skill's knobs. The response echoes the knob(s) you
675
- * changed plus the re-push outcome every knob now carries
676
- * (`reprovisioned`, `truncated`) prefer the typed conveniences
677
- * (setSkillRate / setSkillDailyCap / grantSkill) for a precise return
678
- * type. */
717
+ /** Generic PATCH of a skill's knobs. **Exactly ONE knob per call** — the
718
+ * endpoint disambiguates by payload, so a patch naming two (say
719
+ * `{ ratePerMin, maxCallsPerDay }`) is refused with 400 rather than
720
+ * half-applied. `{ freeHttp, grantedDomains }` is one knob, not two: the
721
+ * grant is a single write. The response echoes the knob you changed plus
722
+ * the re-push outcome every knob carries (`reprovisioned`, `truncated`).
723
+ * Prefer the typed conveniences (setSkillRate / setSkillDailyCap /
724
+ * grantSkill) — they are one knob each by construction and give a precise
725
+ * return type. */
679
726
  updateSkill(slug: string, patch: Record<string, unknown>): Promise<Record<string, unknown>>;
680
727
  /** Set a skill's per-minute call budget (1..120; null restores the default).
681
728
  * Re-pushes the def to running instances — the limiter reads the budget off
@@ -708,7 +755,12 @@ export interface AdminClient {
708
755
  * `allowed_domains` ∪ `grantedDomains`. This is how a docs-only skill
709
756
  * (no `tools:` block) becomes runnable — install it, attach it to an agent
710
757
  * (`updateAgent(id, { skills: [...] })`), then grant it here. The new def is
711
- * re-pushed to running instances; `reprovisioned` is how many were updated. */
758
+ * re-pushed to running instances; `reprovisioned` is how many were updated.
759
+ *
760
+ * This is a full REPLACE of the grant, not a merge: omitting
761
+ * `grantedDomains` sends `[]` and clears the extra allowlist (the manifest's
762
+ * own `allowed_domains` is unaffected). Pass the domains you want to end up
763
+ * with on every call. */
712
764
  grantSkill(slug: string, grant: {
713
765
  freeHttp: boolean;
714
766
  grantedDomains?: string[];
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.16.0';
11
+ export const ADMIN_SDK_VERSION = '0.17.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;
@@ -247,6 +247,7 @@ export function createAdminClient(opts) {
247
247
  ingestKnowledgeFile: (input) => request('POST', '/knowledge/file', input),
248
248
  ingestKnowledgeUrl: (input) => request('POST', '/knowledge/url', input),
249
249
  searchKnowledge: (query) => request('POST', '/knowledge/search', { query }),
250
+ extractJson: (input) => request('POST', '/utility/json', input),
250
251
  deleteKnowledge: (id) => request('DELETE', `/knowledge/${encodeURIComponent(id)}`),
251
252
  listSkills: () => request('GET', '/skills'),
252
253
  installSkill: (input) => request('POST', '/skills', input),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pouchy_ai/admin-sdk",
3
- "version": "0.16.0",
3
+ "version": "0.17.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",