@pouchy_ai/companion-sdk 0.14.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 +45 -1
- package/README.md +2 -0
- package/dist/client.d.ts +39 -1
- package/dist/client.js +69 -0
- package/dist/index.d.ts +2 -2
- package/dist/protocol.d.ts +20 -1
- package/dist/protocol.js +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -12,7 +12,51 @@ a protocol bump is always called out explicitly here.
|
|
|
12
12
|
|
|
13
13
|
## [Unreleased]
|
|
14
14
|
|
|
15
|
-
## [0.
|
|
15
|
+
## [0.17.0] - 2026-07-08
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
Surface completion — the last three wire types that lacked a typed
|
|
20
|
+
client accessor now have one. All additive; wire protocol unchanged
|
|
21
|
+
(`PROTOCOL_VERSION 1`).
|
|
22
|
+
|
|
23
|
+
- **`onVoiceInject(fn)`** + `VoiceInjectPayload { text, speak }` — a typed
|
|
24
|
+
subscription for `companion.voice_inject` (a `voiceRelevant` world-state
|
|
25
|
+
line the companion should say aloud during a live call). The event was
|
|
26
|
+
already emitted; this is the public handler + payload type for it.
|
|
27
|
+
- **`client.setModalities(modalities)`** — change a live session's I/O
|
|
28
|
+
modalities mid-session (e.g. toggle `voice`). Intersected with the
|
|
29
|
+
token's granted modalities server-side; returns the effective set.
|
|
30
|
+
Backed by `POST /api/companion/session/{id}/modalities`.
|
|
31
|
+
- **`client.ping()`** — keepalive that bumps the session's last-seen time
|
|
32
|
+
so a long-idle embed stays "live" within the TTL (cross-app A2A friend
|
|
33
|
+
messages keep reaching it). Backed by
|
|
34
|
+
`POST /api/companion/session/{id}/ping`.
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- **Conversation history** — `client.history({ limit })` fetches this session's
|
|
39
|
+
recent turns (`{ user, assistant, ts }`, oldest→newest) so a reconnecting
|
|
40
|
+
embed can restore its transcript. Distinct from `recall` (durable memory /
|
|
41
|
+
facts): this is the raw exchange log. Reads the token's OWN session only
|
|
42
|
+
(tenant-safe by construction — the turn log is keyed by the token's uid);
|
|
43
|
+
internal per-turn trace (`meta`) is stripped server-side. `limit` defaults to
|
|
44
|
+
20, capped at 50. New type `CompanionTurn`. Backed by
|
|
45
|
+
`GET /api/companion/session/{sessionId}/history`. Wire protocol unchanged
|
|
46
|
+
(`PROTOCOL_VERSION 1`) — this is a REST method, not a stream event.
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
|
|
50
|
+
- **Typing / activity indicator** — a new `companion.typing` event and a
|
|
51
|
+
`client.onTyping(({ active }) => …)` convenience. The server emits
|
|
52
|
+
`active:true` when a turn starts working and `active:false` when it emits
|
|
53
|
+
its reply or pauses for an app tool result, so it spans the whole turn —
|
|
54
|
+
including the tool-loop / thinking phase BEFORE the first text delta, which
|
|
55
|
+
`onDelta` can't observe. Drive a "typing…" affordance uniformly across
|
|
56
|
+
streaming and non-streaming replies. Additive and non-breaking (receivers
|
|
57
|
+
ignore unknown types); the terminal `companion.message` stays authoritative.
|
|
58
|
+
Wire protocol stays `PROTOCOL_VERSION 1` (an added outbound type is
|
|
59
|
+
backwards-compatible). New payload type: `TypingPayload { active: boolean }`.
|
|
16
60
|
|
|
17
61
|
### Added
|
|
18
62
|
|
package/README.md
CHANGED
|
@@ -70,6 +70,8 @@ Subscribe with `on(type, fn)` (or `'*'`), or these typed convenience helpers:
|
|
|
70
70
|
| `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
71
|
| `onAudio(fn)` | `companion.audio` | TTS clip (non-call modality) |
|
|
72
72
|
| `onExpression(fn)` | `companion.expression` | avatar viseme / expression / gesture |
|
|
73
|
+
| `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` |
|
|
74
|
+
| `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 |
|
|
73
75
|
| `onUsage(fn)` | `control.usage` | per-token metering echo |
|
|
74
76
|
| `onError(fn)` | `control.error` | agent / stream errors |
|
|
75
77
|
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AudioClipPayload, CompanionEnvelope, ConfirmRequestPayload, ExpressionPayload, InterfaceUpdatePayload, OutboundType, PendingConfirm, RenderInterfacePayload, SocialMessagePayload, 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. */
|
|
@@ -273,6 +281,25 @@ export declare class CompanionClient {
|
|
|
273
281
|
recall(opts?: {
|
|
274
282
|
limit?: number;
|
|
275
283
|
}): Promise<RecalledMemory[]>;
|
|
284
|
+
/** Fetch this session's recent conversation turns (oldest→newest) so a
|
|
285
|
+
* reconnecting embed can restore its transcript. Distinct from `recall`
|
|
286
|
+
* (durable memory / facts) — this is the raw exchange log. Returns the
|
|
287
|
+
* token's OWN session turns only; requires a live session (call `connect`
|
|
288
|
+
* first). `limit` defaults to 20, capped at 50. */
|
|
289
|
+
history(opts?: {
|
|
290
|
+
limit?: number;
|
|
291
|
+
}): Promise<CompanionTurn[]>;
|
|
292
|
+
/** Change this session's I/O modalities mid-session (e.g. enable/disable
|
|
293
|
+
* `voice`). The request is intersected with the token's granted modalities
|
|
294
|
+
* server-side — a session can't widen past its key — and the EFFECTIVE set is
|
|
295
|
+
* returned. Requires a live session. */
|
|
296
|
+
setModalities(modalities: string[]): Promise<{
|
|
297
|
+
modalities: string[];
|
|
298
|
+
}>;
|
|
299
|
+
/** Keepalive: bump this session's last-seen time so a long-idle embed stays
|
|
300
|
+
* "live" within the session TTL (cross-app A2A friend messages keep reaching
|
|
301
|
+
* it). Cheap; call it on a timer for a background tab. Requires a live session. */
|
|
302
|
+
ping(): Promise<void>;
|
|
276
303
|
/** Remember a fact (into this app's namespace unless `namespace` is given and
|
|
277
304
|
* the token has the core scope). Intimate-tier writes are rejected server-side. */
|
|
278
305
|
remember(fact: {
|
|
@@ -401,6 +428,17 @@ export declare class CompanionClient {
|
|
|
401
428
|
* friends (companion.social_message), delivered cross-app to any embed whose
|
|
402
429
|
* token holds `social.message`. Surface them in your own UI / notify the user. */
|
|
403
430
|
onSocialMessage(handler: (payload: SocialMessagePayload, envelope: CompanionEnvelope) => void): () => void;
|
|
431
|
+
/** Convenience: subscribe to the companion's activity indicator
|
|
432
|
+
* (companion.typing). Fires `active:true` when a turn starts working and
|
|
433
|
+
* `active:false` when it finishes or pauses for a tool result — spanning the
|
|
434
|
+
* tool-loop / thinking phase before the first text delta. Drive a "typing…"
|
|
435
|
+
* affordance from it: `client.onTyping(({ active }) => setTyping(active))`. */
|
|
436
|
+
onTyping(handler: (payload: TypingPayload, envelope: CompanionEnvelope) => void): () => void;
|
|
437
|
+
/** Convenience: subscribe to voice-inject cues (companion.voice_inject) — a
|
|
438
|
+
* `voiceRelevant` world-state line the companion should say aloud during a
|
|
439
|
+
* live call. Route `payload.text` to your active voice session when
|
|
440
|
+
* `payload.speak`. A text-only host can ignore it. */
|
|
441
|
+
onVoiceInject(handler: (payload: VoiceInjectPayload, envelope: CompanionEnvelope) => void): () => void;
|
|
404
442
|
/** Open the event stream (auto-reconnecting with the resume cursor). Uses the
|
|
405
443
|
* WebSocket plane when opted in + available, else SSE. */
|
|
406
444
|
start(): void;
|
package/dist/client.js
CHANGED
|
@@ -465,6 +465,52 @@ export class CompanionClient {
|
|
|
465
465
|
}
|
|
466
466
|
return json.memories ?? [];
|
|
467
467
|
}
|
|
468
|
+
/** Fetch this session's recent conversation turns (oldest→newest) so a
|
|
469
|
+
* reconnecting embed can restore its transcript. Distinct from `recall`
|
|
470
|
+
* (durable memory / facts) — this is the raw exchange log. Returns the
|
|
471
|
+
* token's OWN session turns only; requires a live session (call `connect`
|
|
472
|
+
* first). `limit` defaults to 20, capped at 50. */
|
|
473
|
+
async history(opts) {
|
|
474
|
+
const id = this.requireSession();
|
|
475
|
+
const qs = opts?.limit ? `?limit=${encodeURIComponent(opts.limit)}` : '';
|
|
476
|
+
const res = await this.doFetch(this.url(`/api/companion/session/${encodeURIComponent(id)}/history${qs}`), { headers: { Authorization: `Bearer ${this.opts.token}` } });
|
|
477
|
+
const json = (await res.json().catch(() => null));
|
|
478
|
+
if (!res.ok || !json || json.ok === false) {
|
|
479
|
+
throw new CompanionError(json?.error || `history failed (${res.status})`, res.status);
|
|
480
|
+
}
|
|
481
|
+
return json.history ?? [];
|
|
482
|
+
}
|
|
483
|
+
/** Change this session's I/O modalities mid-session (e.g. enable/disable
|
|
484
|
+
* `voice`). The request is intersected with the token's granted modalities
|
|
485
|
+
* server-side — a session can't widen past its key — and the EFFECTIVE set is
|
|
486
|
+
* returned. Requires a live session. */
|
|
487
|
+
async setModalities(modalities) {
|
|
488
|
+
const id = this.requireSession();
|
|
489
|
+
const res = await this.doFetch(this.url(`/api/companion/session/${encodeURIComponent(id)}/modalities`), {
|
|
490
|
+
method: 'POST',
|
|
491
|
+
headers: {
|
|
492
|
+
Authorization: `Bearer ${this.opts.token}`,
|
|
493
|
+
'Content-Type': 'application/json'
|
|
494
|
+
},
|
|
495
|
+
body: JSON.stringify({ modalities })
|
|
496
|
+
});
|
|
497
|
+
const json = (await res.json().catch(() => null));
|
|
498
|
+
if (!res.ok || !json || json.ok === false) {
|
|
499
|
+
throw new CompanionError(json?.error || `setModalities failed (${res.status})`, res.status);
|
|
500
|
+
}
|
|
501
|
+
return { modalities: json.modalities ?? modalities };
|
|
502
|
+
}
|
|
503
|
+
/** Keepalive: bump this session's last-seen time so a long-idle embed stays
|
|
504
|
+
* "live" within the session TTL (cross-app A2A friend messages keep reaching
|
|
505
|
+
* it). Cheap; call it on a timer for a background tab. Requires a live session. */
|
|
506
|
+
async ping() {
|
|
507
|
+
const id = this.requireSession();
|
|
508
|
+
const res = await this.doFetch(this.url(`/api/companion/session/${encodeURIComponent(id)}/ping`), { method: 'POST', headers: { Authorization: `Bearer ${this.opts.token}` } });
|
|
509
|
+
if (!res.ok) {
|
|
510
|
+
const json = (await res.json().catch(() => null));
|
|
511
|
+
throw new CompanionError(json?.error || `ping failed (${res.status})`, res.status);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
468
514
|
/** Remember a fact (into this app's namespace unless `namespace` is given and
|
|
469
515
|
* the token has the core scope). Intimate-tier writes are rejected server-side. */
|
|
470
516
|
async remember(fact) {
|
|
@@ -643,6 +689,29 @@ export class CompanionClient {
|
|
|
643
689
|
handler(p, env);
|
|
644
690
|
});
|
|
645
691
|
}
|
|
692
|
+
/** Convenience: subscribe to the companion's activity indicator
|
|
693
|
+
* (companion.typing). Fires `active:true` when a turn starts working and
|
|
694
|
+
* `active:false` when it finishes or pauses for a tool result — spanning the
|
|
695
|
+
* tool-loop / thinking phase before the first text delta. Drive a "typing…"
|
|
696
|
+
* affordance from it: `client.onTyping(({ active }) => setTyping(active))`. */
|
|
697
|
+
onTyping(handler) {
|
|
698
|
+
return this.on('companion.typing', (env) => {
|
|
699
|
+
const p = env.payload;
|
|
700
|
+
if (p && typeof p === 'object' && typeof p.active === 'boolean')
|
|
701
|
+
handler(p, env);
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
/** Convenience: subscribe to voice-inject cues (companion.voice_inject) — a
|
|
705
|
+
* `voiceRelevant` world-state line the companion should say aloud during a
|
|
706
|
+
* live call. Route `payload.text` to your active voice session when
|
|
707
|
+
* `payload.speak`. A text-only host can ignore it. */
|
|
708
|
+
onVoiceInject(handler) {
|
|
709
|
+
return this.on('companion.voice_inject', (env) => {
|
|
710
|
+
const p = env.payload;
|
|
711
|
+
if (p && typeof p === 'object' && typeof p.text === 'string')
|
|
712
|
+
handler(p, env);
|
|
713
|
+
});
|
|
714
|
+
}
|
|
646
715
|
/** Open the event stream (auto-reconnecting with the resume cursor). Uses the
|
|
647
716
|
* WebSocket plane when opted in + available, else SSE. */
|
|
648
717
|
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 } 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, 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;
|
package/dist/protocol.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export declare const PROTOCOL_VERSION: 1;
|
|
|
3
3
|
export declare const INBOUND_TYPES: readonly ["hello", "input.text", "context.event", "context.snapshot", "tool.result", "control.start_call", "control.end_call", "control.set_modalities", "control.ping"];
|
|
4
4
|
export type InboundType = (typeof INBOUND_TYPES)[number];
|
|
5
5
|
/** Pouchy → app (control/data plane). */
|
|
6
|
-
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", "control.call_ready", "control.error", "control.usage"];
|
|
6
|
+
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"];
|
|
7
7
|
export type OutboundType = (typeof OUTBOUND_TYPES)[number];
|
|
8
8
|
export type MessageType = InboundType | OutboundType;
|
|
9
9
|
/** The single envelope every control/data-plane message shares. */
|
|
@@ -120,6 +120,25 @@ 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
|
+
}
|
|
132
|
+
/** Payload of a `companion.typing` event — an activity indicator bracketing the
|
|
133
|
+
* model work of a turn: `active:true` when the companion starts working,
|
|
134
|
+
* `active:false` when it emits its reply or pauses for an app tool result. It
|
|
135
|
+
* spans the whole turn — the tool-loop / thinking phase BEFORE the first text
|
|
136
|
+
* delta, which `onDelta` can't observe — so a host can show a "typing…" state
|
|
137
|
+
* uniformly across streaming and non-streaming replies. Advisory: a lost event
|
|
138
|
+
* never affects the turn; the terminal `companion.message` is authoritative. */
|
|
139
|
+
export interface TypingPayload {
|
|
140
|
+
active: boolean;
|
|
141
|
+
}
|
|
123
142
|
/** A live world-state event the app streams in. `retained:true` = latest-value
|
|
124
143
|
* state (coalesced); `retained:false` = a transient moment carrying `salience`
|
|
125
144
|
* and optional `voiceRelevant`. */
|
package/dist/protocol.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pouchy_ai/companion-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.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",
|