@pouchy_ai/companion-sdk 0.29.0 → 0.29.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 +33 -1
- package/README.md +8 -6
- package/dist/client.d.ts +3 -0
- package/dist/client.js +21 -9
- package/package.json +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,37 @@ a protocol bump is always called out explicitly here.
|
|
|
12
12
|
|
|
13
13
|
## [Unreleased]
|
|
14
14
|
|
|
15
|
+
## [0.29.1] - 2026-07-12
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **`CompanionError.retryAfter` no longer reports a spurious `0`.** A missing
|
|
20
|
+
`Retry-After` header was parsed as `Number(null) === 0`, so EVERY error
|
|
21
|
+
thrown from a POST without the header (a `400 invalid_request`, a
|
|
22
|
+
`404 session_not_found`, …) carried `retryAfter: 0` — telling backoff code
|
|
23
|
+
"retry immediately" on non-retryable errors. It is now `undefined` unless
|
|
24
|
+
the body's `retryAfterSec` or a real header names a value, as documented.
|
|
25
|
+
- **`sendText({ awaitReply: true, stream: false })` now cancels its POST.**
|
|
26
|
+
The buffered leg never passed the operation's `AbortSignal` to the network
|
|
27
|
+
call, so the promise rejected on abort/deadline but the billed turn kept
|
|
28
|
+
running server-side. It now aborts with the same linked controller the
|
|
29
|
+
streaming leg uses.
|
|
30
|
+
- **Abort-listener leak on a reused `AbortSignal` in `awaitReply`.** The
|
|
31
|
+
deadline's reject listener was never removed, so a host reusing one
|
|
32
|
+
long-lived signal across many `sendText({ awaitReply, signal })` calls
|
|
33
|
+
accumulated one orphan listener per call (memory growth + Node's
|
|
34
|
+
MaxListenersExceeded warning). Removed in the same `finally` as the rest.
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- **`signal` on `sendToolResult` and `startCall`** — the two request-performing
|
|
39
|
+
methods 0.29.0's "AbortSignal on every network method" pass missed
|
|
40
|
+
(backwards-compatible addition, per this package's patch policy).
|
|
41
|
+
- **`require()` of the package now works on Node ≥ 20.17** (require-ESM): the
|
|
42
|
+
`exports` map gained a `default` condition — previously `require()` failed
|
|
43
|
+
with `ERR_PACKAGE_PATH_NOT_EXPORTED` before the feature could apply, despite
|
|
44
|
+
the README documenting it.
|
|
45
|
+
|
|
15
46
|
## [0.29.0] - 2026-07-12
|
|
16
47
|
|
|
17
48
|
### Added
|
|
@@ -616,7 +647,8 @@ self-hosted/CDN build; this entry captures the surface that ships publicly.
|
|
|
616
647
|
- **Versioned wire protocol.** `PROTOCOL_VERSION = 1`, kept in lockstep with the
|
|
617
648
|
server by `protocol.drift.test.ts` (fails CI on divergence).
|
|
618
649
|
|
|
619
|
-
[Unreleased]: https://github.com/oviswang/Pouchy/compare/companion-sdk-v0.29.
|
|
650
|
+
[Unreleased]: https://github.com/oviswang/Pouchy/compare/companion-sdk-v0.29.1...HEAD
|
|
651
|
+
[0.29.1]: https://github.com/oviswang/Pouchy/compare/companion-sdk-v0.29.0...companion-sdk-v0.29.1
|
|
620
652
|
[0.29.0]: https://github.com/oviswang/Pouchy/compare/companion-sdk-v0.28.1...companion-sdk-v0.29.0
|
|
621
653
|
[0.28.1]: https://github.com/oviswang/Pouchy/compare/companion-sdk-v0.28.0...companion-sdk-v0.28.1
|
|
622
654
|
[0.28.0]: https://github.com/oviswang/Pouchy/compare/companion-sdk-v0.27.0...companion-sdk-v0.28.0
|
package/README.md
CHANGED
|
@@ -122,12 +122,14 @@ are offered automatically once you declare `tools`/`handles`, and
|
|
|
122
122
|
embeds receive expression cues — declare them or they no-op silently. Full
|
|
123
123
|
contract: `docs/companion-host-control.md`.
|
|
124
124
|
|
|
125
|
-
**Cancellation (0.29.0):** every request-performing method
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
125
|
+
**Cancellation (0.29.0, completed in 0.29.1):** every request-performing method
|
|
126
|
+
accepts an optional `AbortSignal` — including `sendToolResult(callId, result,
|
|
127
|
+
{ signal })` and `startCall({ signal })` since 0.29.1. Methods with an options
|
|
128
|
+
object take a `signal?` field (`sendText(text, { signal })`,
|
|
129
|
+
`recall({ signal })`, …); the rest take a trailing `opts`
|
|
130
|
+
(`getAvatar({ signal })`, `ping({ signal })`, …). Aborting rejects with
|
|
131
|
+
`CompanionError` `code: 'aborted'`, and for `sendText({ awaitReply })` it also
|
|
132
|
+
tears down the in-flight event stream.
|
|
131
133
|
|
|
132
134
|
```ts
|
|
133
135
|
const ac = new AbortController();
|
package/dist/client.d.ts
CHANGED
|
@@ -346,6 +346,7 @@ export declare class CompanionClient {
|
|
|
346
346
|
startCall(opts?: {
|
|
347
347
|
voice?: string;
|
|
348
348
|
locale?: string;
|
|
349
|
+
signal?: AbortSignal;
|
|
349
350
|
}): Promise<CallCredentials>;
|
|
350
351
|
/** Open a live voice call end-to-end: mints credentials (startCall) and opens a
|
|
351
352
|
* WebRTC session directly to the provider — no first-party app code needed.
|
|
@@ -388,6 +389,8 @@ export declare class CompanionClient {
|
|
|
388
389
|
sendToolResult(callId: string, result: {
|
|
389
390
|
ok?: boolean;
|
|
390
391
|
result?: unknown;
|
|
392
|
+
}, opts?: {
|
|
393
|
+
signal?: AbortSignal;
|
|
391
394
|
}): Promise<{
|
|
392
395
|
allDone: boolean;
|
|
393
396
|
}>;
|
package/dist/client.js
CHANGED
|
@@ -191,7 +191,13 @@ export class CompanionClient {
|
|
|
191
191
|
const fromBody = json?.retryAfterSec;
|
|
192
192
|
if (typeof fromBody === 'number' && Number.isFinite(fromBody) && fromBody >= 0)
|
|
193
193
|
return fromBody;
|
|
194
|
-
|
|
194
|
+
// Missing/blank header must stay undefined — Number(null) is 0, which
|
|
195
|
+
// used to stamp retryAfter: 0 on EVERY error without the header (a 400,
|
|
196
|
+
// a 404 …), telling backoff code "retry immediately".
|
|
197
|
+
const raw = res.headers.get('Retry-After');
|
|
198
|
+
if (raw === null || raw.trim() === '')
|
|
199
|
+
return undefined;
|
|
200
|
+
const header = Number(raw);
|
|
195
201
|
return Number.isFinite(header) && header >= 0 ? header : undefined;
|
|
196
202
|
}
|
|
197
203
|
async postJson(path, body, signal) {
|
|
@@ -305,6 +311,9 @@ export class CompanionClient {
|
|
|
305
311
|
opts.signal.addEventListener('abort', onCallerAbort, { once: true });
|
|
306
312
|
}
|
|
307
313
|
let timer;
|
|
314
|
+
// Held so the finally can REMOVE it — a host reusing one long-lived signal
|
|
315
|
+
// across many awaitReply calls accumulated one orphan listener per call.
|
|
316
|
+
let onCallerAbortReject;
|
|
308
317
|
const deadline = new Promise((_, reject) => {
|
|
309
318
|
timer = setTimeout(() => {
|
|
310
319
|
ac.abort();
|
|
@@ -313,7 +322,8 @@ export class CompanionClient {
|
|
|
313
322
|
// The caller's signal also rejects the operation (the stream teardown
|
|
314
323
|
// alone would otherwise leave the fallback wait hanging to the timer).
|
|
315
324
|
if (opts?.signal) {
|
|
316
|
-
|
|
325
|
+
onCallerAbortReject = () => reject(new CompanionError('request aborted', 0, 'aborted'));
|
|
326
|
+
opts.signal.addEventListener('abort', onCallerAbortReject, { once: true });
|
|
317
327
|
}
|
|
318
328
|
});
|
|
319
329
|
try {
|
|
@@ -322,7 +332,10 @@ export class CompanionClient {
|
|
|
322
332
|
let pausedOnTools = false;
|
|
323
333
|
if (opts?.stream === false) {
|
|
324
334
|
const json = await Promise.race([
|
|
325
|
-
|
|
335
|
+
// ac.signal, like the streaming leg: the deadline and the caller's
|
|
336
|
+
// signal both abort it — without this the promise rejected but the
|
|
337
|
+
// billed POST kept running beyond cancellation.
|
|
338
|
+
this.postJson(`/api/companion/session/${sessionId}/input`, body, ac.signal),
|
|
326
339
|
deadline
|
|
327
340
|
]);
|
|
328
341
|
seq = json.seq ?? null;
|
|
@@ -354,6 +367,8 @@ export class CompanionClient {
|
|
|
354
367
|
clearTimeout(timer);
|
|
355
368
|
if (onCallerAbort)
|
|
356
369
|
opts?.signal?.removeEventListener('abort', onCallerAbort);
|
|
370
|
+
if (onCallerAbortReject)
|
|
371
|
+
opts?.signal?.removeEventListener('abort', onCallerAbortReject);
|
|
357
372
|
}
|
|
358
373
|
}
|
|
359
374
|
/** The streaming leg of sendText: parse the POST response's SSE frames —
|
|
@@ -512,10 +527,7 @@ export class CompanionClient {
|
|
|
512
527
|
*/
|
|
513
528
|
async startCall(opts) {
|
|
514
529
|
const id = this.requireSession();
|
|
515
|
-
return this.postJson(`/api/companion/session/${id}/call`, {
|
|
516
|
-
voice: opts?.voice,
|
|
517
|
-
locale: opts?.locale
|
|
518
|
-
});
|
|
530
|
+
return this.postJson(`/api/companion/session/${id}/call`, { voice: opts?.voice, locale: opts?.locale }, opts?.signal);
|
|
519
531
|
}
|
|
520
532
|
/** Open a live voice call end-to-end: mints credentials (startCall) and opens a
|
|
521
533
|
* WebRTC session directly to the provider — no first-party app code needed.
|
|
@@ -651,7 +663,7 @@ export class CompanionClient {
|
|
|
651
663
|
/** Report the result of a companion.tool_call this surface performed. When all
|
|
652
664
|
* of the turn's calls are reported, the companion resumes and the continuation
|
|
653
665
|
* (a companion.message or more tool calls) arrives on the event stream. */
|
|
654
|
-
async sendToolResult(callId, result) {
|
|
666
|
+
async sendToolResult(callId, result, opts) {
|
|
655
667
|
// Voice tool-call: resolve it locally so the result flows back to the voice
|
|
656
668
|
// provider, not to a server text turn. (Same call shape as the text path.)
|
|
657
669
|
const pending = this.pendingVoiceTools.get(callId);
|
|
@@ -660,7 +672,7 @@ export class CompanionClient {
|
|
|
660
672
|
return { allDone: true };
|
|
661
673
|
}
|
|
662
674
|
const id = this.requireSession();
|
|
663
|
-
const json = await this.postJson(`/api/companion/session/${id}/tool-result`, { callId, ok: result.ok !== false, result: result.result });
|
|
675
|
+
const json = await this.postJson(`/api/companion/session/${id}/tool-result`, { callId, ok: result.ok !== false, result: result.result }, opts?.signal);
|
|
664
676
|
return { allDone: json.allDone ?? false };
|
|
665
677
|
}
|
|
666
678
|
/** A tool the live voice call asked the app to run. Fires the SAME
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pouchy_ai/companion-sdk",
|
|
3
|
-
"version": "0.29.
|
|
4
|
-
"description": "Embed the Pouchy companion
|
|
3
|
+
"version": "0.29.1",
|
|
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",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"exports": {
|
|
17
17
|
".": {
|
|
18
18
|
"types": "./dist/index.d.ts",
|
|
19
|
-
"import": "./dist/index.js"
|
|
19
|
+
"import": "./dist/index.js",
|
|
20
|
+
"default": "./dist/index.js"
|
|
20
21
|
}
|
|
21
22
|
},
|
|
22
23
|
"main": "./dist/index.js",
|