@pouchy_ai/companion-sdk 0.25.0 → 0.26.1

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,39 @@ a protocol bump is always called out explicitly here.
12
12
 
13
13
  ## [Unreleased]
14
14
 
15
+ ## [0.26.1] - 2026-07-12
16
+
17
+ ### Changed
18
+
19
+ - **Docs: `/api/stt` now requires auth.** The audio-transcription relay the
20
+ `ingestKnowledge` guidance points at is no longer an open endpoint — send
21
+ your `pchy_…` access token as the bearer (any valid token works; the call
22
+ is metered like other companion ops). No SDK code paths changed; this
23
+ release updates the inline JSDoc guidance only.
24
+ - Server-side hardening (no client change needed): concurrent `input` posts
25
+ on one session now lose cleanly with the existing 409 `turn_pending` code
26
+ instead of double-running a turn — retry after the in-flight reply lands.
27
+
28
+ ## [0.26.0] - 2026-07-11
29
+
30
+ ### Fixed
31
+
32
+ - **Voice-call connect failures now honor the typed-error contract.** The
33
+ call helpers (`connectCall` / `startCall` / `openCompanionCall`) used to
34
+ reject with plain `Error`s for the connect-step failures, so they carried
35
+ no `.code` and couldn't be `switch`ed like every other SDK rejection.
36
+ They now reject with `CompanionError` and three new client-synthesized
37
+ codes: `call_unsupported` (non-browser environment — no WebRTC/mic),
38
+ `call_connect_failed` (mic-permission timeout, SDP exchange failed or
39
+ timed out; `status` carries the HTTP status when the exchange answered
40
+ non-2xx), and `call_dependency_missing` (the optional
41
+ `@elevenlabs/client` peer dependency isn't installed). Like
42
+ `reply_timeout`/`stream_unauthorized` these are SDK-synthesized and
43
+ deliberately NOT part of the server-mirrored `COMPANION_ERROR_CODES`
44
+ HTTP vocabulary. Browser-native `getUserMedia` rejections (e.g.
45
+ `NotAllowedError` on permission denial) still propagate untouched.
46
+ `CompanionError` itself is unchanged and keeps its import path.
47
+
15
48
  ## [0.25.0] - 2026-07-11
16
49
 
17
50
  ### Added
package/README.md CHANGED
@@ -147,7 +147,13 @@ HTTP `code` vocabulary behind `CompanionError.code`, e.g. `missing_scope` /
147
147
  `session_not_found` / `turn_pending`) — so you can assert the version, validate
148
148
  the event vocabulary, or switch on error codes without hard-coding strings.
149
149
  Every helper populates `CompanionError.code` when the server names a cause
150
- (0.24.0 — previously only the POST-backed calls did). Every
150
+ (0.24.0 — previously only the POST-backed calls did). The SDK also synthesizes
151
+ a few client-side codes outside that HTTP vocabulary: `reply_timeout`
152
+ (awaitReply fallback gave up), `stream_unauthorized` (event-stream 401
153
+ exhausted reconnects), and — 0.26.0 — the voice connect-step codes
154
+ `call_unsupported` (no WebRTC/mic in this environment), `call_connect_failed`
155
+ (mic timeout / SDP exchange failed) and `call_dependency_missing`
156
+ (`@elevenlabs/client` not installed). Every
151
157
  outbound payload has a named type; `ToolCallPayload` (`{ id, name, args }`) types
152
158
  the `companion.tool_call` event, and `onToolCall`'s callback receives
153
159
  `ToolCallEvent` — the payload plus `argsJson` (pre-parsed args).
package/dist/call.js CHANGED
@@ -10,6 +10,7 @@
10
10
  //
11
11
  // Browser-only (needs WebRTC + getUserMedia). Returns a handle to hang up and to
12
12
  // inject live context mid-call (the bridge the server uses for voiceRelevant).
13
+ import { CompanionError } from './errors.js';
13
14
  /** Universal "host control" verbs the companion can call on ANY surface (web /
14
15
  * game / app / hardware), regardless of provider. They're generic on purpose —
15
16
  * the SPECIFIC intent rides in the string params, which YOUR surface interprets
@@ -147,7 +148,7 @@ const OPENAI_REALTIME_CALLS = 'https://api.openai.com/v1/realtime/calls';
147
148
  /** Open a live call from pre-minted credentials. */
148
149
  export async function openCompanionCall(creds, opts = {}, bridge) {
149
150
  if (typeof navigator === 'undefined' || !navigator.mediaDevices) {
150
- throw new Error('connectCall is browser-only (needs WebRTC + microphone access)');
151
+ throw new CompanionError('connectCall is browser-only (needs WebRTC + microphone access)', 0, 'call_unsupported');
151
152
  }
152
153
  return creds.provider === 'openai-realtime'
153
154
  ? openOpenAICall(creds, opts, bridge)
@@ -176,7 +177,7 @@ async function getMicWithTimeout() {
176
177
  .catch(() => { });
177
178
  const timeout = new Promise((_, reject) => setTimeout(() => {
178
179
  timedOut = true;
179
- reject(new Error('microphone request timed out'));
180
+ reject(new CompanionError('microphone request timed out', 0, 'call_connect_failed'));
180
181
  }, GUM_TIMEOUT_MS));
181
182
  return Promise.race([micPromise, timeout]);
182
183
  }
@@ -343,14 +344,14 @@ async function openOpenAICall(creds, opts, bridge) {
343
344
  catch {
344
345
  clearTimeout(sdpTimer);
345
346
  cleanupPartial();
346
- throw new Error(sdpAbort.signal.aborted
347
+ throw new CompanionError(sdpAbort.signal.aborted
347
348
  ? 'OpenAI Realtime SDP exchange timed out'
348
- : 'OpenAI Realtime SDP exchange failed');
349
+ : 'OpenAI Realtime SDP exchange failed', 0, 'call_connect_failed');
349
350
  }
350
351
  clearTimeout(sdpTimer);
351
352
  if (!res.ok) {
352
353
  cleanupPartial();
353
- throw new Error(`OpenAI Realtime SDP exchange failed (${res.status})`);
354
+ throw new CompanionError(`OpenAI Realtime SDP exchange failed (${res.status})`, res.status, 'call_connect_failed');
354
355
  }
355
356
  await pc.setRemoteDescription({ type: 'answer', sdp: await res.text() });
356
357
  let closed = false;
@@ -458,7 +459,7 @@ async function openConvaiCall(creds, opts, bridge) {
458
459
  mod = await import('@elevenlabs/client');
459
460
  }
460
461
  catch {
461
- throw new Error("elevenlabs-convai call needs the optional peer dependency '@elevenlabs/client' — install it (npm i @elevenlabs/client)");
462
+ throw new CompanionError("elevenlabs-convai call needs the optional peer dependency '@elevenlabs/client' — install it (npm i @elevenlabs/client)", 0, 'call_dependency_missing');
462
463
  }
463
464
  // App tools → EL client tools. NOTE: ElevenLabs only CALLS a client tool the
464
465
  // agent already knows (declared on the agent config); a shared Convai agent
package/dist/client.d.ts CHANGED
@@ -166,12 +166,10 @@ export declare function pouchyBrandIconUrl(baseUrl: string, size?: BrandIconSize
166
166
  * a method before `connect()`). `code` is a stable machine-readable tag you can
167
167
  * switch on, shared with the `control.error` stream event vocabulary:
168
168
  * `'not_connected'`, `'missing_option'`, `'not_representative'`, or the server's
169
- * own error code for an HTTP failure (when it sends one). */
170
- export declare class CompanionError extends Error {
171
- status: number;
172
- code?: string;
173
- constructor(message: string, status: number, code?: string);
174
- }
169
+ * own error code for an HTTP failure (when it sends one). Defined in errors.ts
170
+ * (so call.ts shares it cycle-free); re-exported here so the public import
171
+ * path is unchanged. */
172
+ export { CompanionError } from './errors.js';
175
173
  /** The server's memory-consolidation diagnostic from `endSession()` / `close()`.
176
174
  * `ok` is whether consolidation ran; `facts` is how many memories it wrote;
177
175
  * `skipped` (when present) says why nothing was written
@@ -434,7 +432,8 @@ export declare class CompanionClient {
434
432
  * materials" list with source attribution, exactly like a first-party upload.
435
433
  * So the materials entry point can live in YOUR app, not just Pouchy's.
436
434
  *
437
- * Extract the text yourself (your own parser, or Pouchy's /api/stt for audio)
435
+ * Extract the text yourself (your own parser, or Pouchy's /api/stt for audio
436
+ * send your pchy_ token as the bearer; the relay is auth-gated and metered)
438
437
  * and pass it here. Writes the user's SHARED knowledge, so the token must
439
438
  * hold `memory.write:core` (the user's consent) — otherwise 403. Embeddings
440
439
  * are computed on the user's next first-party open (eventually-consistent
@@ -581,4 +580,3 @@ export declare class CompanionClient {
581
580
  private consume;
582
581
  private sleep;
583
582
  }
584
- export {};
package/dist/client.js CHANGED
@@ -14,6 +14,7 @@
14
14
  // kept in lockstep by protocol.drift.test.ts.
15
15
  import { PROTOCOL_VERSION } from './protocol.js';
16
16
  import { parseSse } from './sse.js';
17
+ import { CompanionError } from './errors.js';
17
18
  import { openCompanionCall, HOST_CONTROL_TOOLS, HOST_CONTROL_TOOL_NAMES, AVATAR_VISUAL_TOOLS, AVATAR_VISUAL_TOOL_NAMES } from './call.js';
18
19
  /** Model tool arguments are model output — tolerate junk without throwing. */
19
20
  function parseArgsJson(args) {
@@ -37,18 +38,10 @@ export function pouchyBrandIconUrl(baseUrl, size = 512) {
37
38
  * a method before `connect()`). `code` is a stable machine-readable tag you can
38
39
  * switch on, shared with the `control.error` stream event vocabulary:
39
40
  * `'not_connected'`, `'missing_option'`, `'not_representative'`, or the server's
40
- * own error code for an HTTP failure (when it sends one). */
41
- export class CompanionError extends Error {
42
- status;
43
- code;
44
- constructor(message, status, code) {
45
- super(message);
46
- this.name = 'CompanionError';
47
- this.status = status;
48
- if (code)
49
- this.code = code;
50
- }
51
- }
41
+ * own error code for an HTTP failure (when it sends one). Defined in errors.ts
42
+ * (so call.ts shares it cycle-free); re-exported here so the public import
43
+ * path is unchanged. */
44
+ export { CompanionError } from './errors.js';
52
45
  const SEEN_CAP = 512;
53
46
  export class CompanionClient {
54
47
  opts;
@@ -649,7 +642,8 @@ export class CompanionClient {
649
642
  * materials" list with source attribution, exactly like a first-party upload.
650
643
  * So the materials entry point can live in YOUR app, not just Pouchy's.
651
644
  *
652
- * Extract the text yourself (your own parser, or Pouchy's /api/stt for audio)
645
+ * Extract the text yourself (your own parser, or Pouchy's /api/stt for audio
646
+ * send your pchy_ token as the bearer; the relay is auth-gated and metered)
653
647
  * and pass it here. Writes the user's SHARED knowledge, so the token must
654
648
  * hold `memory.write:core` (the user's consent) — otherwise 403. Embeddings
655
649
  * are computed on the user's next first-party open (eventually-consistent
@@ -0,0 +1,9 @@
1
+ /** Thrown by every helper on an HTTP or client-side failure. `status` is the
2
+ * HTTP status (0 for client-synthesized failures — timeouts, unsupported
3
+ * environments, missing optional deps); `code` is machine-switchable when
4
+ * the server (or the SDK itself) names a cause. */
5
+ export declare class CompanionError extends Error {
6
+ status: number;
7
+ code?: string;
8
+ constructor(message: string, status: number, code?: string);
9
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,20 @@
1
+ // CompanionError — the one error type every SDK helper rejects with (README
2
+ // contract: consumers `switch` on `.code` instead of string-matching prose).
3
+ // Lives in its own module so call.ts can use it without a runtime cycle
4
+ // through client.ts (client.ts re-exports it, keeping the public import path
5
+ // `@pouchy_ai/companion-sdk` → `CompanionError` unchanged).
6
+ /** Thrown by every helper on an HTTP or client-side failure. `status` is the
7
+ * HTTP status (0 for client-synthesized failures — timeouts, unsupported
8
+ * environments, missing optional deps); `code` is machine-switchable when
9
+ * the server (or the SDK itself) names a cause. */
10
+ export class CompanionError extends Error {
11
+ status;
12
+ code;
13
+ constructor(message, status, code) {
14
+ super(message);
15
+ this.name = 'CompanionError';
16
+ this.status = status;
17
+ if (code)
18
+ this.code = code;
19
+ }
20
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pouchy_ai/companion-sdk",
3
- "version": "0.25.0",
3
+ "version": "0.26.1",
4
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",