@pouchy_ai/companion-sdk 0.23.0 → 0.24.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
@@ -12,6 +12,42 @@ a protocol bump is always called out explicitly here.
12
12
 
13
13
  ## [Unreleased]
14
14
 
15
+ ## [0.24.0] - 2026-07-11
16
+
17
+ ### Added
18
+
19
+ - **`CompanionError.code` is now populated on EVERY helper, and the vocabulary
20
+ ships in the package.** `COMPANION_ERROR_CODES` (+ the `CompanionErrorCode`
21
+ type) is exported — the append-only HTTP `code` list the server emits
22
+ (`missing_scope`, `session_not_found`, `turn_pending`, …), drift-tested
23
+ against the server source. Previously only `postJson`-backed calls carried
24
+ `code`; the GET/stream helpers (`recall`, `history`, `setModalities`,
25
+ `ping`, `getAvatar`, `getWallet`, `pendingConfirms`, and `sendText`'s
26
+ streaming fallback) constructed `CompanionError` without it, so a
27
+ `missing_scope` 403 from `getWallet()` had `code === undefined`. All of them
28
+ now thread the server's `code` through — switch on it instead of matching
29
+ error prose.
30
+
31
+ ### Changed (server-side, shipped with the platform)
32
+
33
+ - `GET …/history` now enforces the `chat` scope the API reference always
34
+ documented (a token without `chat` gets a 403 `missing_scope` instead of the
35
+ transcript).
36
+ - The `control.error` stream code `call_mint_failed` (voice-credential mint
37
+ failure) is now documented everywhere the other stream codes are.
38
+
39
+ ### Docs
40
+
41
+ - `/sdk` REST quickstart now shows the REAL session → input flow (the previous
42
+ snippet showed a `POST /api/companion/message` endpoint that never existed)
43
+ and the 0.23.0 `argsJson` tool-handler form.
44
+ - OpenAPI: `history` response shape corrected to `{ ok, count, history }`;
45
+ `confirm` documents instance-token auth + the full
46
+ `{ status, outcome?, retryable? }` response; `end` summary reflects memory
47
+ consolidation.
48
+ - `wallet.read` scope comment no longer claims there is no standalone REST
49
+ endpoint (`GET /api/companion/wallet` shipped in 0.20.0).
50
+
15
51
  ## [0.23.0] - 2026-07-11
16
52
 
17
53
  ### Added
package/README.md CHANGED
@@ -77,7 +77,7 @@ Subscribe with `on(type, fn)` (or `'*'`), or these typed convenience helpers:
77
77
  | `onVoiceInject(fn)` | `companion.voice_inject` | `fn({text, speak})` — a `voiceRelevant` world-state line to say aloud during a live call; route `text` to your voice session when `speak` |
78
78
  | `onTyping(fn)` | `companion.typing` | activity indicator — `fn({active})` fires `true` when a turn starts working and `false` when it finishes / pauses, spanning the tool-loop / thinking phase before the first text delta. Drive a "typing…" state |
79
79
  | `onUsage(fn)` | `control.usage` | per-token metering echo. _(reserved — not emitted yet)_ |
80
- | `onError(fn)` | `control.error` | agent / stream errors — `agent_error` (server-side turn failed after accept; safe to re-send) or the SDK-synthesized `stream_unauthorized` (stream 401 exhausted reconnects — refresh the token, `start()` again) |
80
+ | `onError(fn)` | `control.error` | agent / stream errors — `agent_error` (server-side turn failed after accept; safe to re-send), `call_mint_failed` (voice-credential mint failed — retry later or fall back to text) or the SDK-synthesized `stream_unauthorized` (stream 401 exhausted reconnects — refresh the token, `start()` again) |
81
81
 
82
82
  ### Instant UI
83
83
 
@@ -141,8 +141,12 @@ exported: `COMPANION_MODALITIES`, `REPRESENT_SCOPES`, `DEFAULT_MODALITIES`,
141
141
  the SDK's drift test.
142
142
 
143
143
  The wire-protocol runtime constants are exported too — `PROTOCOL_VERSION`,
144
- `INBOUND_TYPES`, and `OUTBOUND_TYPES` — so you can assert the version or
145
- enumerate/validate the event vocabulary without hard-coding strings. Every
144
+ `INBOUND_TYPES`, `OUTBOUND_TYPES`, and `COMPANION_ERROR_CODES` (the append-only
145
+ HTTP `code` vocabulary behind `CompanionError.code`, e.g. `missing_scope` /
146
+ `session_not_found` / `turn_pending`) — so you can assert the version, validate
147
+ the event vocabulary, or switch on error codes without hard-coding strings.
148
+ Every helper populates `CompanionError.code` when the server names a cause
149
+ (0.24.0 — previously only the POST-backed calls did). Every
146
150
  outbound payload has a named type; `ToolCallPayload` (`{ id, name, args }`) types
147
151
  the `companion.tool_call` event, and `onToolCall`'s callback receives
148
152
  `ToolCallEvent` — the payload plus `argsJson` (pre-parsed args).
package/dist/client.js CHANGED
@@ -213,7 +213,7 @@ export class CompanionClient {
213
213
  // Old server / error before the stream opened — behave like postJson.
214
214
  const json = (await res.json().catch(() => null));
215
215
  if (!res.ok || !json || json.ok === false) {
216
- throw new CompanionError(json?.error || `request failed (${res.status})`, res.status);
216
+ throw new CompanionError(json?.error || `request failed (${res.status})`, res.status, json?.code);
217
217
  }
218
218
  return { seq: json.seq ?? null };
219
219
  }
@@ -250,7 +250,7 @@ export class CompanionClient {
250
250
  /* stream already finished */
251
251
  }
252
252
  if (d.ok === false) {
253
- throw new CompanionError(d.error || 'turn failed', d.status ?? 500);
253
+ throw new CompanionError(d.error || 'turn failed', d.status ?? 500, d.code);
254
254
  }
255
255
  // Emit the final envelope NOW so onMessage fires with zero poll
256
256
  // latency; the event-stream replay of the same id is deduped.
@@ -555,7 +555,7 @@ export class CompanionClient {
555
555
  });
556
556
  const json = (await res.json().catch(() => null));
557
557
  if (!res.ok || !json || json.ok === false) {
558
- throw new CompanionError(json?.error || `recall failed (${res.status})`, res.status);
558
+ throw new CompanionError(json?.error || `recall failed (${res.status})`, res.status, json?.code);
559
559
  }
560
560
  return json.memories ?? [];
561
561
  }
@@ -570,7 +570,7 @@ export class CompanionClient {
570
570
  const res = await this.fetchAuthed(this.url(`/api/companion/session/${encodeURIComponent(id)}/history${qs}`), { headers: { Authorization: `Bearer ${this.opts.token}` } });
571
571
  const json = (await res.json().catch(() => null));
572
572
  if (!res.ok || !json || json.ok === false) {
573
- throw new CompanionError(json?.error || `history failed (${res.status})`, res.status);
573
+ throw new CompanionError(json?.error || `history failed (${res.status})`, res.status, json?.code);
574
574
  }
575
575
  return json.history ?? [];
576
576
  }
@@ -590,7 +590,7 @@ export class CompanionClient {
590
590
  });
591
591
  const json = (await res.json().catch(() => null));
592
592
  if (!res.ok || !json || json.ok === false) {
593
- throw new CompanionError(json?.error || `setModalities failed (${res.status})`, res.status);
593
+ throw new CompanionError(json?.error || `setModalities failed (${res.status})`, res.status, json?.code);
594
594
  }
595
595
  return { modalities: json.modalities ?? modalities };
596
596
  }
@@ -602,7 +602,7 @@ export class CompanionClient {
602
602
  const res = await this.fetchAuthed(this.url(`/api/companion/session/${encodeURIComponent(id)}/ping`), { method: 'POST', headers: { Authorization: `Bearer ${this.opts.token}` } });
603
603
  if (!res.ok) {
604
604
  const json = (await res.json().catch(() => null));
605
- throw new CompanionError(json?.error || `ping failed (${res.status})`, res.status);
605
+ throw new CompanionError(json?.error || `ping failed (${res.status})`, res.status, json?.code);
606
606
  }
607
607
  }
608
608
  /** Remember a fact (into this app's namespace unless `namespace` is given and
@@ -649,7 +649,7 @@ export class CompanionClient {
649
649
  });
650
650
  const json = (await res.json().catch(() => null));
651
651
  if (!res.ok || !json || json.ok === false) {
652
- throw new CompanionError(json?.error || `avatar fetch failed (${res.status})`, res.status);
652
+ throw new CompanionError(json?.error || `avatar fetch failed (${res.status})`, res.status, json?.code);
653
653
  }
654
654
  return {
655
655
  name: json.name ?? null,
@@ -670,7 +670,7 @@ export class CompanionClient {
670
670
  });
671
671
  const json = (await res.json().catch(() => null));
672
672
  if (!res.ok || !json || json.ok === false) {
673
- throw new CompanionError(json?.error || `wallet fetch failed (${res.status})`, res.status);
673
+ throw new CompanionError(json?.error || `wallet fetch failed (${res.status})`, res.status, json?.code);
674
674
  }
675
675
  return {
676
676
  balances: json.balances ?? [],
@@ -784,7 +784,7 @@ export class CompanionClient {
784
784
  const res = await this.fetchAuthed(this.url(`/api/companion/session/${encodeURIComponent(sessionId)}/confirm`), { headers: { Authorization: `Bearer ${this.opts.token}` } });
785
785
  const json = (await res.json().catch(() => null));
786
786
  if (!res.ok || !json || json.ok === false) {
787
- throw new CompanionError(json?.error || `pending confirms failed (${res.status})`, res.status);
787
+ throw new CompanionError(json?.error || `pending confirms failed (${res.status})`, res.status, json?.code);
788
788
  }
789
789
  return json.pending ?? [];
790
790
  }
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ export type { CompanionCall, CompanionCallOptions, VoiceToolBridge } from './cal
6
6
  export { COMPANION_SCOPES, COMPANION_MODALITIES, SENSITIVE_SCOPES, REPRESENT_SCOPES, DEFAULT_SCOPES, DEFAULT_MODALITIES, isSensitiveScope, isRepresentScope, hasScope } from './scopes.js';
7
7
  export type { CompanionScope, CompanionModality } from './scopes.js';
8
8
  export type { CompanionEnvelope, OutboundType, InboundType, WorldStateEvent, RenderInterfacePayload, InterfaceUpdatePayload, SocialMessagePayload, ConfirmRequestPayload, PendingConfirm, ToolCallPayload, AudioClipPayload, ExpressionPayload, TypingPayload, VoiceInjectPayload, UsagePayload } from './protocol.js';
9
- export { PROTOCOL_VERSION, INBOUND_TYPES, OUTBOUND_TYPES } from './protocol.js';
9
+ export { PROTOCOL_VERSION, INBOUND_TYPES, OUTBOUND_TYPES, COMPANION_ERROR_CODES } from './protocol.js';
10
+ export type { CompanionErrorCode } from './protocol.js';
10
11
  /** Create a companion client. */
11
12
  export declare function createCompanion(opts: CompanionClientOptions): CompanionClient;
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ export { openCompanionCall, HOST_CONTROL_TOOLS, HOST_CONTROL_TOOL_NAMES, AVATAR_
15
15
  export { COMPANION_SCOPES, COMPANION_MODALITIES, SENSITIVE_SCOPES, REPRESENT_SCOPES, DEFAULT_SCOPES, DEFAULT_MODALITIES, isSensitiveScope, isRepresentScope, hasScope } from './scopes.js';
16
16
  // Runtime protocol constants — so a host can assert PROTOCOL_VERSION or
17
17
  // enumerate/validate the event vocabulary without hard-coding the strings.
18
- export { PROTOCOL_VERSION, INBOUND_TYPES, OUTBOUND_TYPES } from './protocol.js';
18
+ export { PROTOCOL_VERSION, INBOUND_TYPES, OUTBOUND_TYPES, COMPANION_ERROR_CODES } from './protocol.js';
19
19
  /** Create a companion client. */
20
20
  export function createCompanion(opts) {
21
21
  return new CompanionClient(opts);
@@ -12,6 +12,12 @@ export type InboundType = (typeof INBOUND_TYPES)[number];
12
12
  export declare const OUTBOUND_TYPES: readonly ["hello.ack", "companion.message", "companion.audio", "companion.tool_call", "companion.ui_action", "companion.ui_update", "companion.expression", "companion.voice_inject", "companion.confirm_request", "companion.social_message", "companion.typing", "control.call_ready", "control.error", "control.usage"];
13
13
  export type OutboundType = (typeof OUTBOUND_TYPES)[number];
14
14
  export type MessageType = InboundType | OutboundType;
15
+ /** HTTP error `code` vocabulary — what `CompanionError.code` can hold. A
16
+ * self-contained mirror of the server's api-error.ts list (drift-tested);
17
+ * APPEND-ONLY: renaming or removing a code is a breaking SDK change.
18
+ * Consumers can switch on these instead of string-matching error prose. */
19
+ export declare const COMPANION_ERROR_CODES: readonly ["missing_token", "invalid_token", "missing_scope", "invalid_request", "session_not_found", "turn_pending", "no_pending_tools", "unknown_call", "payload_too_large", "forbidden", "unavailable"];
20
+ export type CompanionErrorCode = (typeof COMPANION_ERROR_CODES)[number];
15
21
  /** The single envelope every control/data-plane message shares. */
16
22
  export interface CompanionEnvelope<T = unknown> {
17
23
  v: typeof PROTOCOL_VERSION;
package/dist/protocol.js CHANGED
@@ -40,3 +40,20 @@ export const OUTBOUND_TYPES = [
40
40
  'control.error',
41
41
  'control.usage' // reserved — not emitted yet
42
42
  ];
43
+ /** HTTP error `code` vocabulary — what `CompanionError.code` can hold. A
44
+ * self-contained mirror of the server's api-error.ts list (drift-tested);
45
+ * APPEND-ONLY: renaming or removing a code is a breaking SDK change.
46
+ * Consumers can switch on these instead of string-matching error prose. */
47
+ export const COMPANION_ERROR_CODES = [
48
+ 'missing_token', // 401 — no Authorization: Bearer header
49
+ 'invalid_token', // 401 — token unknown, revoked, or expired
50
+ 'missing_scope', // 403 — token lacks a required grant
51
+ 'invalid_request', // 400 — malformed JSON / missing or invalid fields
52
+ 'session_not_found', // 404 — sessionId expired or never started
53
+ 'turn_pending', // 409 — a turn is paused awaiting tool results
54
+ 'no_pending_tools', // 409 — tool result posted but nothing is pending
55
+ 'unknown_call', // 404 — tool result for a callId the turn didn't issue
56
+ 'payload_too_large', // 413 — body/image over the documented cap
57
+ 'forbidden', // 403 — authorization denial other than a missing scope
58
+ 'unavailable' // 503 — backing store/provider not configured or down
59
+ ];
package/dist/scopes.js CHANGED
@@ -21,7 +21,7 @@ export const COMPANION_SCOPES = [
21
21
  'memory.read:core', // read the shared global brain (SENSITIVE)
22
22
  'memory.write:core', // write into the shared global brain (SENSITIVE)
23
23
  'skills.execute', // run the user's installed skills (SENSITIVE)
24
- 'wallet.read', // READ-ONLY wallet: balance + own deposit address (receive-only; not sensitive). Surfaced via the companion's wallet tools (ask it) no standalone REST endpoint.
24
+ 'wallet.read', // READ-ONLY wallet: balance + own deposit address (receive-only; not sensitive). GET /api/companion/wallet (SDK: getWallet(), 0.20.0+) or ask the companion's wallet tools.
25
25
  'wallet.spend', // spend from the Care Wallet (SENSITIVE)
26
26
  'social.message', // message friends via A2A (SENSITIVE)
27
27
  // ── Representative ("on-behalf-of") plane ─────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pouchy_ai/companion-sdk",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "description": "Embed the Pouchy companion \u2014 chat, voice, tools, memory, live world-state, instant UI, and agent-to-agent messaging \u2014 in any app, game, or site.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",