@pouchy_ai/companion-sdk 0.17.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 +26 -0
- package/README.md +4 -0
- package/dist/client.d.ts +33 -3
- package/dist/client.js +38 -10
- package/dist/index.d.ts +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,32 @@ a protocol bump is always called out explicitly here.
|
|
|
12
12
|
|
|
13
13
|
## [Unreleased]
|
|
14
14
|
|
|
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
|
+
|
|
15
41
|
## [0.17.0] - 2026-07-08
|
|
16
42
|
|
|
17
43
|
### Added
|
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
|
package/dist/client.d.ts
CHANGED
|
@@ -129,9 +129,25 @@ export type BrandIconSize = 256 | 512 | 1024;
|
|
|
129
129
|
* deployment origin. Standalone (no client/token needed) so a surface can show
|
|
130
130
|
* the Pouchy mark before/without connecting. */
|
|
131
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). */
|
|
132
138
|
export declare class CompanionError extends Error {
|
|
133
139
|
status: number;
|
|
134
|
-
|
|
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;
|
|
135
151
|
}
|
|
136
152
|
type Handler = (envelope: CompanionEnvelope) => void;
|
|
137
153
|
/** Token-streaming subscriber (P1-2). `chunk` is a raw text delta of the reply
|
|
@@ -242,13 +258,27 @@ export declare class CompanionClient {
|
|
|
242
258
|
* Called automatically when a connectCall() handle is closed; call it directly
|
|
243
259
|
* when a non-voice session ends. Best-effort + idempotent (safe to call twice;
|
|
244
260
|
* uses keepalive so it survives a page unload). Pass the voice transcript when
|
|
245
|
-
* 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. */
|
|
246
269
|
endSession(opts?: {
|
|
247
270
|
transcript?: {
|
|
248
271
|
role: string;
|
|
249
272
|
text: string;
|
|
250
273
|
}[];
|
|
251
|
-
}): Promise<
|
|
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>;
|
|
252
282
|
/** Report the result of a companion.tool_call this surface performed. When all
|
|
253
283
|
* of the turn's calls are reported, the companion resumes and the continuation
|
|
254
284
|
* (a companion.message or more tool calls) arrives on the event stream. */
|
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
|
-
|
|
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
|
|
56
|
+
throw new CompanionError('CompanionClient: baseUrl is required', 0, 'missing_option');
|
|
48
57
|
if (!opts.token)
|
|
49
|
-
throw new
|
|
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
|
|
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
|
|
118
|
+
throw new CompanionError('pairVisitor() requires a representative session (createCompanion({ visitor }))', 0, 'not_representative');
|
|
110
119
|
}
|
|
111
120
|
if (!visitorToken)
|
|
112
|
-
throw new
|
|
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. */
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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, 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
6
|
export type { CompanionEnvelope, OutboundType, InboundType, WorldStateEvent, RenderInterfacePayload, InterfaceUpdatePayload, SocialMessagePayload, ConfirmRequestPayload, PendingConfirm, AudioClipPayload, ExpressionPayload, TypingPayload, VoiceInjectPayload, UsagePayload } from './protocol.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pouchy_ai/companion-sdk",
|
|
3
|
-
"version": "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",
|