@pouchy_ai/companion-sdk 0.23.0 → 0.25.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,73 @@ a protocol bump is always called out explicitly here.
12
12
 
13
13
  ## [Unreleased]
14
14
 
15
+ ## [0.25.0] - 2026-07-11
16
+
17
+ ### Added
18
+
19
+ - **`sendText(text, { awaitReply: true })` — await the reply as the return
20
+ value.** Resolves with `{ seq, text, envelope }` (a new exported
21
+ `SendTextReply` type) once the turn completes: `text` is the authoritative
22
+ final assistant text (the same value `onMessage` receives), `envelope` the
23
+ full `companion.message` envelope. Request/response hosts — a CLI REPL, an
24
+ HTTP handler, a test — no longer hand-roll the resolve-on-message +
25
+ timeout gate every integration was writing. It rides the same streaming
26
+ request (the terminal `done` frame carries the envelope), so it works
27
+ without `start()`; `onMessage`/`onDelta` subscribers still fire as usual.
28
+ With `stream: false` (or an old server that answers plain JSON) it falls
29
+ back to the event stream — which then does need `start()` — resolving with
30
+ the next `companion.message` or rejecting with a client-side
31
+ `CompanionError` `code: 'reply_timeout'` after `replyTimeoutMs`
32
+ (default 60 000 ms).
33
+
34
+ ### Docs
35
+
36
+ - OpenAPI spec corrections (served at `/api/companion/openapi.json`): the
37
+ memory-write body field is `content` (was wrongly documented as `text`),
38
+ knowledge ingest takes `name`/`kind`/`locale` (was `title`), the
39
+ modalities enum is `text|voice|call|files` (`avatar` never existed),
40
+ `GET …/confirm` (pending confirmations) is now documented, the session
41
+ handshake body documents `handles`/`contextKinds`/`visitor`, and memory
42
+ scopes use the real namespaced strings (`memory.read:app` …).
43
+ - The `/sdk` reference described the call handle as having `.end()` — the
44
+ method is `close()`.
45
+
46
+ ## [0.24.0] - 2026-07-11
47
+
48
+ ### Added
49
+
50
+ - **`CompanionError.code` is now populated on EVERY helper, and the vocabulary
51
+ ships in the package.** `COMPANION_ERROR_CODES` (+ the `CompanionErrorCode`
52
+ type) is exported — the append-only HTTP `code` list the server emits
53
+ (`missing_scope`, `session_not_found`, `turn_pending`, …), drift-tested
54
+ against the server source. Previously only `postJson`-backed calls carried
55
+ `code`; the GET/stream helpers (`recall`, `history`, `setModalities`,
56
+ `ping`, `getAvatar`, `getWallet`, `pendingConfirms`, and `sendText`'s
57
+ streaming fallback) constructed `CompanionError` without it, so a
58
+ `missing_scope` 403 from `getWallet()` had `code === undefined`. All of them
59
+ now thread the server's `code` through — switch on it instead of matching
60
+ error prose.
61
+
62
+ ### Changed (server-side, shipped with the platform)
63
+
64
+ - `GET …/history` now enforces the `chat` scope the API reference always
65
+ documented (a token without `chat` gets a 403 `missing_scope` instead of the
66
+ transcript).
67
+ - The `control.error` stream code `call_mint_failed` (voice-credential mint
68
+ failure) is now documented everywhere the other stream codes are.
69
+
70
+ ### Docs
71
+
72
+ - `/sdk` REST quickstart now shows the REAL session → input flow (the previous
73
+ snippet showed a `POST /api/companion/message` endpoint that never existed)
74
+ and the 0.23.0 `argsJson` tool-handler form.
75
+ - OpenAPI: `history` response shape corrected to `{ ok, count, history }`;
76
+ `confirm` documents instance-token auth + the full
77
+ `{ status, outcome?, retryable? }` response; `end` summary reflects memory
78
+ consolidation.
79
+ - `wallet.read` scope comment no longer claims there is no standalone REST
80
+ endpoint (`GET /api/companion/wallet` shipped in 0.20.0).
81
+
15
82
  ## [0.23.0] - 2026-07-11
16
83
 
17
84
  ### 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
 
@@ -96,6 +96,7 @@ local/device skills).
96
96
 
97
97
  | Method | What it does |
98
98
  |---|---|
99
+ | `sendText(text, { awaitReply: true, replyTimeoutMs? })` | Request/response mode: the promise resolves with the completed turn's reply `{ seq, text, envelope }` (`SendTextReply`) — no `onMessage` wiring or hand-rolled turn gate. Works without `start()` (the reply rides the same streaming request); only the `stream: false` / old-server fallback needs the event stream, and it rejects with `code: 'reply_timeout'` after `replyTimeoutMs` (default 60s). |
99
100
  | `recall({ query?, limit? })` / `remember({ content, … })` | Read / write the token-scoped memory namespace. `query` runs SEMANTIC search over the facts server-side (embedding-based, ranked fallback); omit it for the plain importance/recency ranking. |
100
101
  | `ingestKnowledge({ text, name, … })` / `ingestFile(file)` | Push documents into the user's knowledge base ("My materials"); needs `memory.write:core`. |
101
102
  | `history({ limit? })` | Fetch the session transcript — restore chat on reload. |
@@ -141,8 +142,12 @@ exported: `COMPANION_MODALITIES`, `REPRESENT_SCOPES`, `DEFAULT_MODALITIES`,
141
142
  the SDK's drift test.
142
143
 
143
144
  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
145
+ `INBOUND_TYPES`, `OUTBOUND_TYPES`, and `COMPANION_ERROR_CODES` (the append-only
146
+ HTTP `code` vocabulary behind `CompanionError.code`, e.g. `missing_scope` /
147
+ `session_not_found` / `turn_pending`) — so you can assert the version, validate
148
+ the event vocabulary, or switch on error codes without hard-coding strings.
149
+ Every helper populates `CompanionError.code` when the server names a cause
150
+ (0.24.0 — previously only the POST-backed calls did). Every
146
151
  outbound payload has a named type; `ToolCallPayload` (`{ id, name, args }`) types
147
152
  the `companion.tool_call` event, and `onToolCall`'s callback receives
148
153
  `ToolCallEvent` — the payload plus `argsJson` (pre-parsed args).
package/dist/client.d.ts CHANGED
@@ -181,6 +181,15 @@ export interface EndSessionResult {
181
181
  facts?: number;
182
182
  skipped?: string;
183
183
  }
184
+ /** What `sendText(text, { awaitReply: true })` resolves with: the completed
185
+ * turn's reply. `text` is the authoritative final assistant text (same value
186
+ * onMessage receives); `envelope` is the full `companion.message` envelope
187
+ * for hosts that want the id/ts. */
188
+ export interface SendTextReply {
189
+ seq: number | null;
190
+ text: string;
191
+ envelope: CompanionEnvelope;
192
+ }
184
193
  type Handler = (envelope: CompanionEnvelope) => void;
185
194
  /** Token-streaming subscriber (P1-2). `chunk` is a raw text delta of the reply
186
195
  * being generated; `meta.reset` (with an empty chunk) tells the renderer to
@@ -244,13 +253,38 @@ export declare class CompanionClient {
244
253
  * request — delta subscribers fire as chunks arrive, and onMessage still
245
254
  * fires exactly ONCE with the authoritative final text (the streamed copy
246
255
  * is emitted locally; the event-stream replay of the same envelope is
247
- * deduplicated by id). Pass `stream: false` to force the buffered path. */
256
+ * deduplicated by id). Pass `stream: false` to force the buffered path.
257
+ *
258
+ * AWAIT THE REPLY (request/response hosts — a REPL, an HTTP handler, a
259
+ * test): pass `awaitReply: true` and the promise resolves with the final
260
+ * reply `{ seq, text, envelope }` — no onMessage wiring, no hand-rolled
261
+ * turn gate. It rides the same streaming request (the terminal `done`
262
+ * frame carries the authoritative envelope), so it works without start();
263
+ * onMessage/onDelta subscribers still fire as usual. Only when the reply
264
+ * can't ride the request (`stream: false`, or an old server) does it fall
265
+ * back to the event stream — which then DOES need start() — resolving with
266
+ * the next `companion.message`, or rejecting with code `reply_timeout`
267
+ * after `replyTimeoutMs` (default 60s). */
268
+ sendText(text: string, opts: {
269
+ awaitReply: true;
270
+ images?: string[];
271
+ stream?: boolean;
272
+ replyTimeoutMs?: number;
273
+ }): Promise<SendTextReply>;
248
274
  sendText(text: string, opts?: {
249
275
  images?: string[];
250
276
  stream?: boolean;
277
+ awaitReply?: boolean;
278
+ replyTimeoutMs?: number;
251
279
  }): Promise<{
252
280
  seq: number | null;
253
281
  }>;
282
+ /** The awaitReply leg of sendText. Prefers the in-request answer (the
283
+ * streaming `done` frame's envelope); falls back to the next
284
+ * `companion.message` off the event stream, bounded by a timeout. The
285
+ * fallback subscription opens BEFORE the POST so a fast event-stream reply
286
+ * can't slip through the gap. */
287
+ private sendTextAwaitingReply;
254
288
  /** The streaming leg of sendText: parse the POST response's SSE frames —
255
289
  * `delta` / `reset` → delta subscribers, `done` → resolve (emitting the
256
290
  * final envelope locally so onMessage fires immediately and the log replay
package/dist/client.js CHANGED
@@ -177,26 +177,58 @@ export class CompanionClient {
177
177
  });
178
178
  return { pairId: json.pairId ?? null };
179
179
  }
180
- /** Send a user text turn, optionally with images (data URLs) for a multimodal
181
- * turn — images need the `files` scope. The reply arrives on the event stream
182
- * as a `companion.message` — subscribe via onMessage()/start() to receive it.
183
- *
184
- * TOKEN STREAMING (P1-2): when any onDelta() subscriber is registered (or
185
- * `opts.stream` is true), the reply streams token-by-token on this very
186
- * request — delta subscribers fire as chunks arrive, and onMessage still
187
- * fires exactly ONCE with the authoritative final text (the streamed copy
188
- * is emitted locally; the event-stream replay of the same envelope is
189
- * deduplicated by id). Pass `stream: false` to force the buffered path. */
190
180
  async sendText(text, opts) {
191
181
  const id = this.requireSession();
192
- const wantStream = opts?.stream ?? this.deltaHandlers.size > 0;
193
182
  const body = { text, ...(opts?.images?.length ? { images: opts.images } : {}) };
183
+ if (opts?.awaitReply)
184
+ return this.sendTextAwaitingReply(id, body, opts);
185
+ const wantStream = opts?.stream ?? this.deltaHandlers.size > 0;
194
186
  if (!wantStream) {
195
187
  const json = await this.postJson(`/api/companion/session/${id}/input`, body);
196
188
  return { seq: json.seq ?? null };
197
189
  }
198
190
  return this.sendTextStreaming(id, body);
199
191
  }
192
+ /** The awaitReply leg of sendText. Prefers the in-request answer (the
193
+ * streaming `done` frame's envelope); falls back to the next
194
+ * `companion.message` off the event stream, bounded by a timeout. The
195
+ * fallback subscription opens BEFORE the POST so a fast event-stream reply
196
+ * can't slip through the gap. */
197
+ async sendTextAwaitingReply(sessionId, body, opts) {
198
+ const timeoutMs = opts?.replyTimeoutMs ?? 60_000;
199
+ let resolveFallback;
200
+ const fallback = new Promise((res) => (resolveFallback = res));
201
+ const off = this.on('companion.message', (env) => resolveFallback(env));
202
+ let timer;
203
+ try {
204
+ let seq = null;
205
+ let envelope;
206
+ if (opts?.stream === false) {
207
+ const json = await this.postJson(`/api/companion/session/${sessionId}/input`, body);
208
+ seq = json.seq ?? null;
209
+ }
210
+ else {
211
+ const r = await this.sendTextStreaming(sessionId, body);
212
+ seq = r.seq;
213
+ envelope = r.envelope;
214
+ }
215
+ if (!envelope) {
216
+ envelope = await Promise.race([
217
+ fallback,
218
+ new Promise((_, reject) => {
219
+ timer = setTimeout(() => reject(new CompanionError(`timed out after ${timeoutMs}ms waiting for the reply — buffered mode needs start() polling the event stream`, 0, 'reply_timeout')), timeoutMs);
220
+ })
221
+ ]);
222
+ }
223
+ const replyText = envelope.payload?.text;
224
+ return { seq, text: typeof replyText === 'string' ? replyText : '', envelope };
225
+ }
226
+ finally {
227
+ off();
228
+ if (timer !== undefined)
229
+ clearTimeout(timer);
230
+ }
231
+ }
200
232
  /** The streaming leg of sendText: parse the POST response's SSE frames —
201
233
  * `delta` / `reset` → delta subscribers, `done` → resolve (emitting the
202
234
  * final envelope locally so onMessage fires immediately and the log replay
@@ -213,7 +245,7 @@ export class CompanionClient {
213
245
  // Old server / error before the stream opened — behave like postJson.
214
246
  const json = (await res.json().catch(() => null));
215
247
  if (!res.ok || !json || json.ok === false) {
216
- throw new CompanionError(json?.error || `request failed (${res.status})`, res.status);
248
+ throw new CompanionError(json?.error || `request failed (${res.status})`, res.status, json?.code);
217
249
  }
218
250
  return { seq: json.seq ?? null };
219
251
  }
@@ -250,13 +282,13 @@ export class CompanionClient {
250
282
  /* stream already finished */
251
283
  }
252
284
  if (d.ok === false) {
253
- throw new CompanionError(d.error || 'turn failed', d.status ?? 500);
285
+ throw new CompanionError(d.error || 'turn failed', d.status ?? 500, d.code);
254
286
  }
255
287
  // Emit the final envelope NOW so onMessage fires with zero poll
256
288
  // latency; the event-stream replay of the same id is deduped.
257
289
  if (d.envelope && typeof d.envelope === 'object')
258
290
  this.emit(d.envelope);
259
- return { seq: d.seq ?? null };
291
+ return { seq: d.seq ?? null, envelope: d.envelope ?? undefined };
260
292
  }
261
293
  }
262
294
  if (done)
@@ -555,7 +587,7 @@ export class CompanionClient {
555
587
  });
556
588
  const json = (await res.json().catch(() => null));
557
589
  if (!res.ok || !json || json.ok === false) {
558
- throw new CompanionError(json?.error || `recall failed (${res.status})`, res.status);
590
+ throw new CompanionError(json?.error || `recall failed (${res.status})`, res.status, json?.code);
559
591
  }
560
592
  return json.memories ?? [];
561
593
  }
@@ -570,7 +602,7 @@ export class CompanionClient {
570
602
  const res = await this.fetchAuthed(this.url(`/api/companion/session/${encodeURIComponent(id)}/history${qs}`), { headers: { Authorization: `Bearer ${this.opts.token}` } });
571
603
  const json = (await res.json().catch(() => null));
572
604
  if (!res.ok || !json || json.ok === false) {
573
- throw new CompanionError(json?.error || `history failed (${res.status})`, res.status);
605
+ throw new CompanionError(json?.error || `history failed (${res.status})`, res.status, json?.code);
574
606
  }
575
607
  return json.history ?? [];
576
608
  }
@@ -590,7 +622,7 @@ export class CompanionClient {
590
622
  });
591
623
  const json = (await res.json().catch(() => null));
592
624
  if (!res.ok || !json || json.ok === false) {
593
- throw new CompanionError(json?.error || `setModalities failed (${res.status})`, res.status);
625
+ throw new CompanionError(json?.error || `setModalities failed (${res.status})`, res.status, json?.code);
594
626
  }
595
627
  return { modalities: json.modalities ?? modalities };
596
628
  }
@@ -602,7 +634,7 @@ export class CompanionClient {
602
634
  const res = await this.fetchAuthed(this.url(`/api/companion/session/${encodeURIComponent(id)}/ping`), { method: 'POST', headers: { Authorization: `Bearer ${this.opts.token}` } });
603
635
  if (!res.ok) {
604
636
  const json = (await res.json().catch(() => null));
605
- throw new CompanionError(json?.error || `ping failed (${res.status})`, res.status);
637
+ throw new CompanionError(json?.error || `ping failed (${res.status})`, res.status, json?.code);
606
638
  }
607
639
  }
608
640
  /** Remember a fact (into this app's namespace unless `namespace` is given and
@@ -649,7 +681,7 @@ export class CompanionClient {
649
681
  });
650
682
  const json = (await res.json().catch(() => null));
651
683
  if (!res.ok || !json || json.ok === false) {
652
- throw new CompanionError(json?.error || `avatar fetch failed (${res.status})`, res.status);
684
+ throw new CompanionError(json?.error || `avatar fetch failed (${res.status})`, res.status, json?.code);
653
685
  }
654
686
  return {
655
687
  name: json.name ?? null,
@@ -670,7 +702,7 @@ export class CompanionClient {
670
702
  });
671
703
  const json = (await res.json().catch(() => null));
672
704
  if (!res.ok || !json || json.ok === false) {
673
- throw new CompanionError(json?.error || `wallet fetch failed (${res.status})`, res.status);
705
+ throw new CompanionError(json?.error || `wallet fetch failed (${res.status})`, res.status, json?.code);
674
706
  }
675
707
  return {
676
708
  balances: json.balances ?? [],
@@ -784,7 +816,7 @@ export class CompanionClient {
784
816
  const res = await this.fetchAuthed(this.url(`/api/companion/session/${encodeURIComponent(sessionId)}/confirm`), { headers: { Authorization: `Bearer ${this.opts.token}` } });
785
817
  const json = (await res.json().catch(() => null));
786
818
  if (!res.ok || !json || json.ok === false) {
787
- throw new CompanionError(json?.error || `pending confirms failed (${res.status})`, res.status);
819
+ throw new CompanionError(json?.error || `pending confirms failed (${res.status})`, res.status, json?.code);
788
820
  }
789
821
  return json.pending ?? [];
790
822
  }
package/dist/index.d.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import { CompanionClient, type CompanionClientOptions } from './client.js';
2
2
  export { CompanionClient, CompanionError, pouchyBrandIconUrl } from './client.js';
3
- export type { CompanionClientOptions, HelloAck, RecalledMemory, CompanionTurn, CallCredentials, CompanionToolDecl, ToolCallEvent, WorldStateInput, CompanionAvatar, CompanionWallet, WalletBalance, BrandIconSize, EndSessionResult } from './client.js';
3
+ export type { CompanionClientOptions, HelloAck, RecalledMemory, CompanionTurn, CallCredentials, CompanionToolDecl, ToolCallEvent, WorldStateInput, CompanionAvatar, CompanionWallet, WalletBalance, BrandIconSize, EndSessionResult, SendTextReply } from './client.js';
4
4
  export { openCompanionCall, HOST_CONTROL_TOOLS, HOST_CONTROL_TOOL_NAMES, AVATAR_VISUAL_TOOLS, AVATAR_VISUAL_TOOL_NAMES } from './call.js';
5
5
  export type { CompanionCall, CompanionCallOptions, VoiceToolBridge } from './call.js';
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,7 +1,7 @@
1
1
  {
2
2
  "name": "@pouchy_ai/companion-sdk",
3
- "version": "0.23.0",
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.",
3
+ "version": "0.25.0",
4
+ "description": "Embed the Pouchy companion chat, voice, tools, memory, live world-state, instant UI, and agent-to-agent messaging in any app, game, or site.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",
7
7
  "homepage": "https://pouchy.ai",