@pouchy_ai/companion-sdk 0.15.0 → 0.18.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,7 +12,64 @@ a protocol bump is always called out explicitly here.
12
12
 
13
13
  ## [Unreleased]
14
14
 
15
- ## [0.15.0] - 2026-07-08
15
+ ## [0.18.0] - 2026-07-08
16
+
17
+ ### Added
18
+
19
+ - **`close()`** — one-call teardown for a non-voice (text) session: stops the
20
+ event stream and calls `endSession()` once (idempotent). The text-path mirror
21
+ of what a `connectCall()` handle's `close()` already does for voice.
22
+ - **`EndSessionResult`** type — exported. `endSession()` now RETURNS the server's
23
+ consolidation diagnostic `{ ok, facts?, skipped? }` (or `null`) instead of
24
+ discarding it, so an integrator whose memory write silently does nothing (wrong
25
+ id → `skipped: 'no_session'`, empty transcript → `'no_content'`) gets the
26
+ signal the server already provides. Backwards-compatible: callers ignoring the
27
+ return value are unaffected (`connectCall`'s internal `void endSession()` too).
28
+ - **`CompanionError.code`** — an optional stable, machine-readable tag on the
29
+ thrown error. Local precondition mistakes now throw a `CompanionError`
30
+ (`status: 0`) instead of a bare `Error`, with codes `not_connected` (a method
31
+ called before `connect()`), `missing_option` (no `baseUrl`/`token`), and
32
+ `not_representative` (`pairVisitor` off a non-representative session); HTTP
33
+ failures carry the server's error `code` when present. `catch (e) { if (e
34
+ instanceof CompanionError) … }` — the natural one-error-type pattern — now
35
+ catches the common first-run mistakes too.
36
+
37
+ ### Changed
38
+
39
+ - `endSession(opts?)` return type: `Promise<void>` → `Promise<EndSessionResult | null>`.
40
+
41
+ ## [0.17.0] - 2026-07-08
42
+
43
+ ### Added
44
+
45
+ Surface completion — the last three wire types that lacked a typed
46
+ client accessor now have one. All additive; wire protocol unchanged
47
+ (`PROTOCOL_VERSION 1`).
48
+
49
+ - **`onVoiceInject(fn)`** + `VoiceInjectPayload { text, speak }` — a typed
50
+ subscription for `companion.voice_inject` (a `voiceRelevant` world-state
51
+ line the companion should say aloud during a live call). The event was
52
+ already emitted; this is the public handler + payload type for it.
53
+ - **`client.setModalities(modalities)`** — change a live session's I/O
54
+ modalities mid-session (e.g. toggle `voice`). Intersected with the
55
+ token's granted modalities server-side; returns the effective set.
56
+ Backed by `POST /api/companion/session/{id}/modalities`.
57
+ - **`client.ping()`** — keepalive that bumps the session's last-seen time
58
+ so a long-idle embed stays "live" within the TTL (cross-app A2A friend
59
+ messages keep reaching it). Backed by
60
+ `POST /api/companion/session/{id}/ping`.
61
+
62
+ ### Added
63
+
64
+ - **Conversation history** — `client.history({ limit })` fetches this session's
65
+ recent turns (`{ user, assistant, ts }`, oldest→newest) so a reconnecting
66
+ embed can restore its transcript. Distinct from `recall` (durable memory /
67
+ facts): this is the raw exchange log. Reads the token's OWN session only
68
+ (tenant-safe by construction — the turn log is keyed by the token's uid);
69
+ internal per-turn trace (`meta`) is stripped server-side. `limit` defaults to
70
+ 20, capped at 50. New type `CompanionTurn`. Backed by
71
+ `GET /api/companion/session/{sessionId}/history`. Wire protocol unchanged
72
+ (`PROTOCOL_VERSION 1`) — this is a REST method, not a stream event.
16
73
 
17
74
  ### Added
18
75
 
package/README.md CHANGED
@@ -18,6 +18,10 @@ No bundler? Pull it from a CDN via an import map instead — see
18
18
  [`docs/companion-integration-faq.md`](../../docs/companion-integration-faq.md).
19
19
  Changes per release are tracked in [`CHANGELOG.md`](./CHANGELOG.md).
20
20
 
21
+ **React?** [`@pouchy_ai/react`](../react-sdk/README.md) wraps this SDK in a
22
+ `<CompanionProvider>` + `useCompanion` / `useMessages` / `useTyping` / `useCall`
23
+ hooks so you don't wire the handshake, stream, or teardown yourself.
24
+
21
25
  ## Quickstart
22
26
 
23
27
  Two steps. **1)** Your backend exchanges the project **Secret Key** (create one
@@ -70,6 +74,7 @@ Subscribe with `on(type, fn)` (or `'*'`), or these typed convenience helpers:
70
74
  | `onConfirmRequest(fn)` | `companion.confirm_request` | a sensitive-op approval request. **Platform session tokens** (your end users): show your own confirm card and resolve it with `confirmAction` — this is how confirm-gated custom skills (POST / credentialed) run. First-party user tokens: observe-only — approval stays first-party (where the `stepUp:true` biometric gate lives) |
71
75
  | `onAudio(fn)` | `companion.audio` | TTS clip (non-call modality) |
72
76
  | `onExpression(fn)` | `companion.expression` | avatar viseme / expression / gesture |
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` |
73
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 |
74
79
  | `onUsage(fn)` | `control.usage` | per-token metering echo |
75
80
  | `onError(fn)` | `control.error` | agent / stream errors |
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { AudioClipPayload, CompanionEnvelope, ConfirmRequestPayload, ExpressionPayload, InterfaceUpdatePayload, OutboundType, PendingConfirm, RenderInterfacePayload, SocialMessagePayload, TypingPayload, UsagePayload, WorldStateEvent } from './protocol.js';
1
+ import type { AudioClipPayload, CompanionEnvelope, ConfirmRequestPayload, ExpressionPayload, InterfaceUpdatePayload, OutboundType, PendingConfirm, RenderInterfacePayload, SocialMessagePayload, TypingPayload, UsagePayload, VoiceInjectPayload, WorldStateEvent } from './protocol.js';
2
2
  /** Ergonomic world-state input: a WorldStateEvent with the CloudEvents plumbing
3
3
  * (specversion / id / source) optional — sendWorldState fills them. */
4
4
  export type WorldStateInput<D = unknown> = Omit<WorldStateEvent<D>, 'specversion' | 'id' | 'source'> & Partial<Pick<WorldStateEvent<D>, 'specversion' | 'id' | 'source'>>;
@@ -100,6 +100,14 @@ export interface RecalledMemory {
100
100
  namespace: string;
101
101
  createdAt?: string;
102
102
  }
103
+ /** One exchange from a session's conversation log (see `history`). `user` is the
104
+ * end-user's message, `assistant` the companion's reply; either may be `''` for
105
+ * a paused / assistant-only row. `ts` is epoch ms. */
106
+ export interface CompanionTurn {
107
+ user: string;
108
+ assistant: string;
109
+ ts: number;
110
+ }
103
111
  /** The user's current companion avatar (see getAvatar). */
104
112
  export interface CompanionAvatar {
105
113
  /** Companion display name (the user's chosen name), or null. */
@@ -121,9 +129,25 @@ export type BrandIconSize = 256 | 512 | 1024;
121
129
  * deployment origin. Standalone (no client/token needed) so a surface can show
122
130
  * the Pouchy mark before/without connecting. */
123
131
  export declare function pouchyBrandIconUrl(baseUrl: string, size?: BrandIconSize): string;
132
+ /** The single error type the SDK throws — for HTTP failures (`status` is the
133
+ * response code) AND for local precondition mistakes (`status: 0`, e.g. calling
134
+ * a method before `connect()`). `code` is a stable machine-readable tag you can
135
+ * switch on, shared with the `control.error` stream event vocabulary:
136
+ * `'not_connected'`, `'missing_option'`, `'not_representative'`, or the server's
137
+ * own error code for an HTTP failure (when it sends one). */
124
138
  export declare class CompanionError extends Error {
125
139
  status: number;
126
- constructor(message: string, status: number);
140
+ code?: string;
141
+ constructor(message: string, status: number, code?: string);
142
+ }
143
+ /** The server's memory-consolidation diagnostic from `endSession()` / `close()`.
144
+ * `ok` is whether consolidation ran; `facts` is how many memories it wrote;
145
+ * `skipped` (when present) says why nothing was written
146
+ * (`'no_session'` | `'no_content'` | `'throttled'`). */
147
+ export interface EndSessionResult {
148
+ ok: boolean;
149
+ facts?: number;
150
+ skipped?: string;
127
151
  }
128
152
  type Handler = (envelope: CompanionEnvelope) => void;
129
153
  /** Token-streaming subscriber (P1-2). `chunk` is a raw text delta of the reply
@@ -234,13 +258,27 @@ export declare class CompanionClient {
234
258
  * Called automatically when a connectCall() handle is closed; call it directly
235
259
  * when a non-voice session ends. Best-effort + idempotent (safe to call twice;
236
260
  * uses keepalive so it survives a page unload). Pass the voice transcript when
237
- * you have it (connectCall does this for you). */
261
+ * you have it (connectCall does this for you).
262
+ *
263
+ * Returns the server's consolidation diagnostic — `{ ok, facts?, skipped? }`
264
+ * — or `null` if there was no session or the (swallowed) keepalive request
265
+ * failed. `skipped` tells you WHY nothing was written: `'no_session'` (you
266
+ * passed the instance id, not the session id), `'no_content'` (empty turn log
267
+ * / a voice transcript that never arrived), `'throttled'` (a duplicate end
268
+ * beacon — harmless). `facts` is how many memories were consolidated. */
238
269
  endSession(opts?: {
239
270
  transcript?: {
240
271
  role: string;
241
272
  text: string;
242
273
  }[];
243
- }): Promise<void>;
274
+ }): Promise<EndSessionResult | null>;
275
+ /** Tear the session down in one call: stop the event stream and consolidate
276
+ * memory (endSession) exactly once. The single teardown entry point for a
277
+ * NON-voice (text) session — mirror of what a connectCall() handle's close()
278
+ * does for voice. Idempotent. Returns endSession's diagnostic (or null).
279
+ * NOTE: if you drive `ping()` on your own timer for a background tab, clear
280
+ * that timer yourself — the client doesn't own it. */
281
+ close(): Promise<EndSessionResult | null>;
244
282
  /** Report the result of a companion.tool_call this surface performed. When all
245
283
  * of the turn's calls are reported, the companion resumes and the continuation
246
284
  * (a companion.message or more tool calls) arrives on the event stream. */
@@ -273,6 +311,25 @@ export declare class CompanionClient {
273
311
  recall(opts?: {
274
312
  limit?: number;
275
313
  }): Promise<RecalledMemory[]>;
314
+ /** Fetch this session's recent conversation turns (oldest→newest) so a
315
+ * reconnecting embed can restore its transcript. Distinct from `recall`
316
+ * (durable memory / facts) — this is the raw exchange log. Returns the
317
+ * token's OWN session turns only; requires a live session (call `connect`
318
+ * first). `limit` defaults to 20, capped at 50. */
319
+ history(opts?: {
320
+ limit?: number;
321
+ }): Promise<CompanionTurn[]>;
322
+ /** Change this session's I/O modalities mid-session (e.g. enable/disable
323
+ * `voice`). The request is intersected with the token's granted modalities
324
+ * server-side — a session can't widen past its key — and the EFFECTIVE set is
325
+ * returned. Requires a live session. */
326
+ setModalities(modalities: string[]): Promise<{
327
+ modalities: string[];
328
+ }>;
329
+ /** Keepalive: bump this session's last-seen time so a long-idle embed stays
330
+ * "live" within the session TTL (cross-app A2A friend messages keep reaching
331
+ * it). Cheap; call it on a timer for a background tab. Requires a live session. */
332
+ ping(): Promise<void>;
276
333
  /** Remember a fact (into this app's namespace unless `namespace` is given and
277
334
  * the token has the core scope). Intimate-tier writes are rejected server-side. */
278
335
  remember(fact: {
@@ -407,6 +464,11 @@ export declare class CompanionClient {
407
464
  * tool-loop / thinking phase before the first text delta. Drive a "typing…"
408
465
  * affordance from it: `client.onTyping(({ active }) => setTyping(active))`. */
409
466
  onTyping(handler: (payload: TypingPayload, envelope: CompanionEnvelope) => void): () => void;
467
+ /** Convenience: subscribe to voice-inject cues (companion.voice_inject) — a
468
+ * `voiceRelevant` world-state line the companion should say aloud during a
469
+ * live call. Route `payload.text` to your active voice session when
470
+ * `payload.speak`. A text-only host can ignore it. */
471
+ onVoiceInject(handler: (payload: VoiceInjectPayload, envelope: CompanionEnvelope) => void): () => void;
410
472
  /** Open the event stream (auto-reconnecting with the resume cursor). Uses the
411
473
  * WebSocket plane when opted in + available, else SSE. */
412
474
  start(): void;
package/dist/client.js CHANGED
@@ -19,12 +19,21 @@ import { openCompanionCall, HOST_CONTROL_TOOLS, HOST_CONTROL_TOOL_NAMES, AVATAR_
19
19
  export function pouchyBrandIconUrl(baseUrl, size = 512) {
20
20
  return baseUrl.replace(/\/+$/, '') + `/brand-assets/icon/pouchy-icon-${size}.png`;
21
21
  }
22
+ /** The single error type the SDK throws — for HTTP failures (`status` is the
23
+ * response code) AND for local precondition mistakes (`status: 0`, e.g. calling
24
+ * a method before `connect()`). `code` is a stable machine-readable tag you can
25
+ * switch on, shared with the `control.error` stream event vocabulary:
26
+ * `'not_connected'`, `'missing_option'`, `'not_representative'`, or the server's
27
+ * own error code for an HTTP failure (when it sends one). */
22
28
  export class CompanionError extends Error {
23
29
  status;
24
- constructor(message, status) {
30
+ code;
31
+ constructor(message, status, code) {
25
32
  super(message);
26
33
  this.name = 'CompanionError';
27
34
  this.status = status;
35
+ if (code)
36
+ this.code = code;
28
37
  }
29
38
  }
30
39
  const SEEN_CAP = 512;
@@ -44,9 +53,9 @@ export class CompanionClient {
44
53
  pendingVoiceTools = new Map();
45
54
  constructor(opts) {
46
55
  if (!opts.baseUrl)
47
- throw new Error('CompanionClient: baseUrl is required');
56
+ throw new CompanionError('CompanionClient: baseUrl is required', 0, 'missing_option');
48
57
  if (!opts.token)
49
- throw new Error('CompanionClient: token is required');
58
+ throw new CompanionError('CompanionClient: token is required', 0, 'missing_option');
50
59
  this.opts = opts;
51
60
  this.doFetch = opts.fetch ?? globalThis.fetch.bind(globalThis);
52
61
  }
@@ -62,7 +71,7 @@ export class CompanionClient {
62
71
  }
63
72
  requireSession() {
64
73
  if (!this._session)
65
- throw new Error('CompanionClient: call connect() first');
74
+ throw new CompanionError('CompanionClient: call connect() first', 0, 'not_connected');
66
75
  return this._session;
67
76
  }
68
77
  async postJson(path, body) {
@@ -73,7 +82,7 @@ export class CompanionClient {
73
82
  });
74
83
  const json = (await res.json().catch(() => null));
75
84
  if (!res.ok || !json || json.ok === false) {
76
- throw new CompanionError(json?.error || `request failed (${res.status})`, res.status);
85
+ throw new CompanionError(json?.error || `request failed (${res.status})`, res.status, json?.code);
77
86
  }
78
87
  return json;
79
88
  }
@@ -106,10 +115,10 @@ export class CompanionClient {
106
115
  * — as proof + consent. The visitor must therefore also be a Pouchy user. */
107
116
  async pairVisitor(visitorToken) {
108
117
  if (!this.opts.visitor) {
109
- throw new Error('pairVisitor() requires a representative session (createCompanion({ visitor }))');
118
+ throw new CompanionError('pairVisitor() requires a representative session (createCompanion({ visitor }))', 0, 'not_representative');
110
119
  }
111
120
  if (!visitorToken)
112
- throw new Error('pairVisitor: visitorToken is required');
121
+ throw new CompanionError('pairVisitor: visitorToken is required', 0, 'missing_option');
113
122
  const json = await this.postJson('/api/companion/pair', {
114
123
  visitorToken,
115
124
  visitorId: this.opts.visitor.id
@@ -320,28 +329,47 @@ export class CompanionClient {
320
329
  * Called automatically when a connectCall() handle is closed; call it directly
321
330
  * when a non-voice session ends. Best-effort + idempotent (safe to call twice;
322
331
  * uses keepalive so it survives a page unload). Pass the voice transcript when
323
- * you have it (connectCall does this for you). */
332
+ * you have it (connectCall does this for you).
333
+ *
334
+ * Returns the server's consolidation diagnostic — `{ ok, facts?, skipped? }`
335
+ * — or `null` if there was no session or the (swallowed) keepalive request
336
+ * failed. `skipped` tells you WHY nothing was written: `'no_session'` (you
337
+ * passed the instance id, not the session id), `'no_content'` (empty turn log
338
+ * / a voice transcript that never arrived), `'throttled'` (a duplicate end
339
+ * beacon — harmless). `facts` is how many memories were consolidated. */
324
340
  async endSession(opts) {
325
341
  const id = this._session;
326
342
  if (!id)
327
- return;
343
+ return null;
328
344
  // Cap the payload (fetch keepalive bodies are size-limited): last 60 lines.
329
345
  const transcript = (opts?.transcript ?? [])
330
346
  .filter((t) => t && typeof t.text === 'string' && t.text.trim())
331
347
  .slice(-60)
332
348
  .map((t) => ({ role: t.role, text: t.text.slice(0, 500) }));
333
349
  try {
334
- await this.doFetch(this.url(`/api/companion/session/${id}/end`), {
350
+ const res = await this.doFetch(this.url(`/api/companion/session/${id}/end`), {
335
351
  method: 'POST',
336
352
  headers: this.jsonHeaders(),
337
353
  body: JSON.stringify({ transcript }),
338
354
  keepalive: true
339
355
  });
356
+ return (await res.json().catch(() => null));
340
357
  }
341
358
  catch {
342
359
  /* best-effort — memory consolidation is not on the user's critical path */
360
+ return null;
343
361
  }
344
362
  }
363
+ /** Tear the session down in one call: stop the event stream and consolidate
364
+ * memory (endSession) exactly once. The single teardown entry point for a
365
+ * NON-voice (text) session — mirror of what a connectCall() handle's close()
366
+ * does for voice. Idempotent. Returns endSession's diagnostic (or null).
367
+ * NOTE: if you drive `ping()` on your own timer for a background tab, clear
368
+ * that timer yourself — the client doesn't own it. */
369
+ async close() {
370
+ this.stop();
371
+ return this.endSession();
372
+ }
345
373
  /** Report the result of a companion.tool_call this surface performed. When all
346
374
  * of the turn's calls are reported, the companion resumes and the continuation
347
375
  * (a companion.message or more tool calls) arrives on the event stream. */
@@ -465,6 +493,52 @@ export class CompanionClient {
465
493
  }
466
494
  return json.memories ?? [];
467
495
  }
496
+ /** Fetch this session's recent conversation turns (oldest→newest) so a
497
+ * reconnecting embed can restore its transcript. Distinct from `recall`
498
+ * (durable memory / facts) — this is the raw exchange log. Returns the
499
+ * token's OWN session turns only; requires a live session (call `connect`
500
+ * first). `limit` defaults to 20, capped at 50. */
501
+ async history(opts) {
502
+ const id = this.requireSession();
503
+ const qs = opts?.limit ? `?limit=${encodeURIComponent(opts.limit)}` : '';
504
+ const res = await this.doFetch(this.url(`/api/companion/session/${encodeURIComponent(id)}/history${qs}`), { headers: { Authorization: `Bearer ${this.opts.token}` } });
505
+ const json = (await res.json().catch(() => null));
506
+ if (!res.ok || !json || json.ok === false) {
507
+ throw new CompanionError(json?.error || `history failed (${res.status})`, res.status);
508
+ }
509
+ return json.history ?? [];
510
+ }
511
+ /** Change this session's I/O modalities mid-session (e.g. enable/disable
512
+ * `voice`). The request is intersected with the token's granted modalities
513
+ * server-side — a session can't widen past its key — and the EFFECTIVE set is
514
+ * returned. Requires a live session. */
515
+ async setModalities(modalities) {
516
+ const id = this.requireSession();
517
+ const res = await this.doFetch(this.url(`/api/companion/session/${encodeURIComponent(id)}/modalities`), {
518
+ method: 'POST',
519
+ headers: {
520
+ Authorization: `Bearer ${this.opts.token}`,
521
+ 'Content-Type': 'application/json'
522
+ },
523
+ body: JSON.stringify({ modalities })
524
+ });
525
+ const json = (await res.json().catch(() => null));
526
+ if (!res.ok || !json || json.ok === false) {
527
+ throw new CompanionError(json?.error || `setModalities failed (${res.status})`, res.status);
528
+ }
529
+ return { modalities: json.modalities ?? modalities };
530
+ }
531
+ /** Keepalive: bump this session's last-seen time so a long-idle embed stays
532
+ * "live" within the session TTL (cross-app A2A friend messages keep reaching
533
+ * it). Cheap; call it on a timer for a background tab. Requires a live session. */
534
+ async ping() {
535
+ const id = this.requireSession();
536
+ const res = await this.doFetch(this.url(`/api/companion/session/${encodeURIComponent(id)}/ping`), { method: 'POST', headers: { Authorization: `Bearer ${this.opts.token}` } });
537
+ if (!res.ok) {
538
+ const json = (await res.json().catch(() => null));
539
+ throw new CompanionError(json?.error || `ping failed (${res.status})`, res.status);
540
+ }
541
+ }
468
542
  /** Remember a fact (into this app's namespace unless `namespace` is given and
469
543
  * the token has the core scope). Intimate-tier writes are rejected server-side. */
470
544
  async remember(fact) {
@@ -655,6 +729,17 @@ export class CompanionClient {
655
729
  handler(p, env);
656
730
  });
657
731
  }
732
+ /** Convenience: subscribe to voice-inject cues (companion.voice_inject) — a
733
+ * `voiceRelevant` world-state line the companion should say aloud during a
734
+ * live call. Route `payload.text` to your active voice session when
735
+ * `payload.speak`. A text-only host can ignore it. */
736
+ onVoiceInject(handler) {
737
+ return this.on('companion.voice_inject', (env) => {
738
+ const p = env.payload;
739
+ if (p && typeof p === 'object' && typeof p.text === 'string')
740
+ handler(p, env);
741
+ });
742
+ }
658
743
  /** Open the event stream (auto-reconnecting with the resume cursor). Uses the
659
744
  * WebSocket plane when opted in + available, else SSE. */
660
745
  start() {
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { CompanionClient, type CompanionClientOptions } from './client.js';
2
2
  export { CompanionClient, CompanionError, pouchyBrandIconUrl } from './client.js';
3
- export type { CompanionClientOptions, HelloAck, RecalledMemory, CallCredentials, CompanionToolDecl, WorldStateInput, CompanionAvatar, BrandIconSize } from './client.js';
3
+ export type { CompanionClientOptions, HelloAck, RecalledMemory, CompanionTurn, CallCredentials, CompanionToolDecl, WorldStateInput, CompanionAvatar, BrandIconSize, EndSessionResult } 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
- export type { CompanionEnvelope, OutboundType, InboundType, WorldStateEvent, RenderInterfacePayload, InterfaceUpdatePayload, SocialMessagePayload, ConfirmRequestPayload, PendingConfirm, AudioClipPayload, ExpressionPayload, TypingPayload, UsagePayload } from './protocol.js';
6
+ export type { CompanionEnvelope, OutboundType, InboundType, WorldStateEvent, RenderInterfacePayload, InterfaceUpdatePayload, SocialMessagePayload, ConfirmRequestPayload, PendingConfirm, AudioClipPayload, ExpressionPayload, TypingPayload, VoiceInjectPayload, UsagePayload } from './protocol.js';
7
7
  /** Create a companion client. */
8
8
  export declare function createCompanion(opts: CompanionClientOptions): CompanionClient;
@@ -120,6 +120,15 @@ export interface SocialMessagePayload {
120
120
  /** ISO timestamp of delivery. */
121
121
  createdAt: string;
122
122
  }
123
+ /** Payload of a `companion.voice_inject` event — a `voiceRelevant` world-state
124
+ * line the companion should say aloud DURING a live call (the app pushed the
125
+ * moment via `sendWorldState`; the server decided it's worth voicing). `speak`
126
+ * is the host's cue to route `text` to the active voice session. A text-only
127
+ * host can ignore it — the moment still shaped the next turn's context. */
128
+ export interface VoiceInjectPayload {
129
+ text: string;
130
+ speak: boolean;
131
+ }
123
132
  /** Payload of a `companion.typing` event — an activity indicator bracketing the
124
133
  * model work of a turn: `active:true` when the companion starts working,
125
134
  * `active:false` when it emits its reply or pauses for an app tool result. It
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pouchy_ai/companion-sdk",
3
- "version": "0.15.0",
3
+ "version": "0.18.0",
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",