ai.pouchy.companion 0.1.0-preview.5

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 ADDED
@@ -0,0 +1,142 @@
1
+ # Changelog
2
+
3
+ All notable changes to the Pouchy Companion Unity/C# SDK are documented here.
4
+ This project adheres to [Semantic Versioning](https://semver.org).
5
+
6
+ > Parity note: the JS SDK 0.35.0 per-request header deadline
7
+ > (`requestTimeoutMs`) needs no port here — every request already runs under
8
+ > `CompanionClientOptions.Timeout` (CompanionClient.cs). Recorded so the
9
+ > cross-SDK parity ledger stays auditable.
10
+
11
+ > Parity ledger — JS surfaces not yet ported (recorded audit round 26, so the
12
+ > gap is a decision, not drift): `handles`/`contextKinds` hello options (the
13
+ > game track's core play-along declaration — the natural NEXT port here),
14
+ > representative mode (`visitor` + `pairVisitor`), `GetAvatar` (a Unity host
15
+ > rendering the companion is a primary consumer), and binary `IngestFile`
16
+ > (`IngestKnowledgeAsync` text is covered). All are single REST calls — port
17
+ > on demand.
18
+ >
19
+ > Added audit round 32: JS `setToken` (swap the bearer token on a live client
20
+ > after ~1h session-token expiry) is also unported — `_token` is readonly
21
+ > here, so a Unity host currently reconnects with a fresh client instead (the
22
+ > session survives: a fresh token for the same user resumes the same session).
23
+ > Python gained `set_token()` this round; port here on demand.
24
+
25
+ ## 0.1.0-preview.5 — 2026-07-26
26
+
27
+ - **`SummaryKey` / `SummaryVars` on `ConfirmRequestPayload` and `PendingConfirm`**
28
+ (JS SDK 0.43.0 parity). The confirmation sentence now also travels as data — an
29
+ i18n key plus its placeholder values — so a Unity host that knows the player's
30
+ language can render the approval in it. `Summary` is unchanged and still always
31
+ present; both new fields are null for a tool with no keyed summary and on
32
+ confirmations recorded before this release, so fall back to `Summary`.
33
+
34
+ ## 0.1.0-preview.4 — 2026-07-17
35
+
36
+ ### Added
37
+
38
+ - **Paused-turn recovery fields (read-only), mirroring JS SDK 0.37.0:**
39
+ `HelloAckPayload.PendingToolCalls` (new `PendingToolCall` model —
40
+ `Id`, `Name`, `Args`, `TurnId?`, `PausedAt?`) lists the outstanding tool
41
+ calls of a turn paused before the handshake (non-empty only when the
42
+ session was resumed mid-pause), and `ToolCallPayload.Replayed` marks the
43
+ re-delivery of such a pause-orphaned call. A restarted host can complete
44
+ the paused turn by posting each result via `SendToolResultAsync` (server
45
+ apply is idempotent per call id) instead of abandoning it via
46
+ `EndSessionAsync`.
47
+
48
+ > Parity note (preview.4): the JS SDK's `replayPendingToolCalls` AUTO-replay
49
+ > is deliberately not ported — same decision as the Python SDK 0.1.6: this
50
+ > client has no tool-call handler registry to replay into (integrators
51
+ > consume the event stream themselves), so recovery stays the caller's
52
+ > explicit `PendingToolCalls` → perform → `SendToolResultAsync` walk.
53
+ > Recorded so the cross-SDK parity ledger stays auditable.
54
+
55
+ ## 0.1.0-preview.3 — 2026-07-17
56
+
57
+ ### Added
58
+
59
+ - `SendTextResult.Duplicate` — the server has flagged replayed turnIds with
60
+ `duplicate: true` all along, but the client dropped the field on both send
61
+ paths, so a caller could not tell a replay from a fresh billed turn. Now
62
+ populated on the plain POST and the streaming `done` frame (null for a fresh
63
+ turn). The `SendTextAsync` doc also no longer claims a plain duplicate
64
+ "returns the recorded reply" — the recorded TEXT only rides back on the
65
+ streaming path.
66
+ - `CompanionError(message, status, code, retryAfter, innerException)` overload,
67
+ used by the transport wrapping below to preserve the original exception.
68
+
69
+ ### Fixed
70
+
71
+ - **Transport failures now throw `CompanionError`** (the README's "every failed
72
+ call throws CompanionError" was false for them): `HttpRequestException` and
73
+ the per-request deadline's `TaskCanceledException` escaped raw from
74
+ `RequestJsonAsync`, `SendTextStreamingAsync`, and `StreamEventsAsync`. They
75
+ are wrapped with `Status=0` and `Code="request_timeout"` (deadline elapsed) /
76
+ `"aborted"` (other transport failures, incl. a dropped body stream) — the JS
77
+ SDK's synthesized-code vocabulary — with the cause as `InnerException`.
78
+ Caller-token cancellation on the event stream is deliberately NOT wrapped
79
+ (it stays an `OperationCanceledException`, the behaviour's teardown signal).
80
+ - **No more spurious `OnError` on teardown**: `PouchyCompanionBehaviour.OnDestroy`
81
+ cancels the CTS and disposes the response, which surfaced the resulting
82
+ `ObjectDisposedException`/`IOException` as an `OnError` on every normal
83
+ exit. `RunStreamAsync` now swallows any exception once cancellation was
84
+ requested.
85
+ - `RequireSession` tags its error `Code="not_connected"` (the JS SDK's code for
86
+ the same misuse) instead of null, so switch-on-`Code` handling covers it.
87
+ - `RetryAfter` also parses the RFC 9110 HTTP-date form of the `Retry-After`
88
+ header (delta from now, clamped at 0); only numeric seconds parsed before.
89
+ - `CompanionClientOptions.Timeout` doc no longer overpromises: it reliably
90
+ bounds time-to-response-headers on the streaming input POST, not whole-body
91
+ reads.
92
+ - README: escaped the `|` in the `SendWorldStateAsync(event \| events)` table
93
+ row (it split the GFM table cell); documented the transport-error codes.
94
+ - UPM manifest: added the `license` field and shipped the SDK LICENSE file
95
+ (the companion-sdk license text, UPM-adapted).
96
+
97
+ > Parity note (preview.3): `request_timeout` / `aborted` / `not_connected`
98
+ > mirror the JS SDK's SDK_SYNTHESIZED_ERROR_CODES and Python 0.1.5 — one
99
+ > cross-SDK error vocabulary. As in Python, `aborted` here covers any
100
+ > non-timeout transport failure (there is no AbortSignal equivalent).
101
+
102
+ ## 0.1.0-preview.2 — 2026-07-15
103
+
104
+ ### Fixed
105
+
106
+ - `IngestKnowledgeAsync` sent `{ "source": ... }`, but the server reads `name`
107
+ (title), `kind`, and `locale` and ignored `source` — so every ingested
108
+ document was titled "an uploaded file". The signature is now
109
+ `IngestKnowledgeAsync(text, name, kind, locale)` matching the server contract
110
+ and the JS/Python SDKs.
111
+
112
+ ## 0.1.0-preview.1 — 2026-07-14
113
+
114
+ Initial preview release — the C# counterpart of the Python and JS companion
115
+ SDKs, targeting Unity 2021.3+ (netstandard2.1) and plain .NET.
116
+
117
+ ### Added
118
+
119
+ - `CompanionClient` (pure .NET, no UnityEngine dependency): `ConnectAsync`,
120
+ `SendTextAsync` (plain + streaming await-reply with `onDelta`),
121
+ `StreamEventsAsync` (SSE with typed payloads + auto-advancing cursor),
122
+ `PendingConfirmsAsync`, `ConfirmActionAsync`, `SendToolResultAsync`,
123
+ `SendWorldStateAsync` (single + batch), `HistoryAsync`, `RecallAsync`,
124
+ `RememberAsync`, `IngestKnowledgeAsync`, `GetWalletAsync`,
125
+ `SetModalitiesAsync`, `PingAsync`, `EndSessionAsync`.
126
+ - `PouchyCompanionBehaviour` — optional Unity MonoBehaviour that owns a client,
127
+ streams events on a background Task, and marshals `OnMessage` / `OnTyping` /
128
+ `OnConfirmRequest` / `OnToolCall` / `OnError` onto the main thread.
129
+ - Typed models (`Runtime/Models.cs`) for every payload in the wire protocol,
130
+ with `[JsonExtensionData]` forward-compat and an `OutboundPayloadTypes` map.
131
+ - `CompanionError` with `Status` / `Code` / `RetryAfter`.
132
+ - `Runtime/Protocol.cs` — the message-type and error-code vocabulary, generated
133
+ from the TypeScript source of truth.
134
+ - UPM manifest (`ai.pouchy.companion`), Newtonsoft.Json dependency, asmdef.
135
+
136
+ ### Notes
137
+
138
+ - **Wire protocol is drift-guarded** against `src/lib/companion-sdk/protocol.ts`
139
+ (Protocol.cs byte-equality + Models.cs field-name pins in CI).
140
+ - **Compile verification pending** — this preview was authored without a local
141
+ .NET toolchain; the first Unity/CI compile pass will follow. Report issues.
142
+ - Voice/WebRTC calls are intentionally out of scope (REST/SSE only).
package/LICENSE ADDED
@@ -0,0 +1,42 @@
1
+ Pouchy Companion SDK License
2
+ Copyright (c) 2026 Pouchy.ai. All Rights Reserved.
3
+
4
+ This license governs use of the "ai.pouchy.companion" software package (the
5
+ "SDK") as distributed for the Unity Package Manager. The SDK is proprietary
6
+ software owned by Pouchy.ai. By installing, copying, or using the SDK you agree
7
+ to these terms.
8
+
9
+ 1. GRANT. Subject to these terms, Pouchy.ai grants you a worldwide,
10
+ non-exclusive, non-transferable, royalty-free, revocable license to install
11
+ and use the SDK, as distributed, solely to build and operate applications
12
+ that integrate with Pouchy.ai's official services and APIs.
13
+
14
+ 2. RESTRICTIONS. Except to the extent a restriction below is prohibited by
15
+ applicable law, you may not:
16
+ (a) modify, adapt, or create derivative works of the SDK's source for
17
+ redistribution;
18
+ (b) redistribute, sublicense, sell, rent, or lease the SDK as a standalone
19
+ product, or republish it under a different name;
20
+ (c) reverse engineer, decompile, or disassemble the SDK except to the extent
21
+ necessary for interoperability and expressly permitted by law;
22
+ (d) use the SDK to build or operate a service that competes with Pouchy.ai's
23
+ services, or to access Pouchy.ai's services in violation of their terms;
24
+ (e) remove or alter any copyright, trademark, or other proprietary notices.
25
+
26
+ 3. RESERVATION OF RIGHTS. All rights not expressly granted are reserved by
27
+ Pouchy.ai. No rights are granted in Pouchy.ai's source repositories,
28
+ trademarks, or services beyond what is needed to use the SDK as described.
29
+
30
+ 4. TERMINATION. This license terminates automatically if you breach it. On
31
+ termination you must stop using and delete all copies of the SDK.
32
+
33
+ 5. NO WARRANTY. THE SDK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
34
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
35
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
36
+
37
+ 6. LIMITATION OF LIABILITY. IN NO EVENT SHALL POUCHY.AI BE LIABLE FOR ANY CLAIM,
38
+ DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR
39
+ OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SDK OR THE USE OR
40
+ OTHER DEALINGS IN THE SDK.
41
+
42
+ For any other use, or for written permission, contact legal@pouchy.ai.
package/README.md ADDED
@@ -0,0 +1,208 @@
1
+ # Pouchy Companion — Unity / C# SDK
2
+
3
+ A thin, typed C# client for the [Pouchy](https://pouchy.ai) companion REST/SSE
4
+ plane — the same wire contract the JS (`@pouchy_ai/companion-sdk`) and Python
5
+ (`pouchy-companion`) SDKs speak, aimed at **Unity games and .NET integrators**.
6
+
7
+ > **⚠️ 0.1.0-preview** — the **wire protocol** is drift-tested against the
8
+ > TypeScript source of truth (`Runtime/Protocol.cs` is generated; `Models.cs`
9
+ > field names are pinned in CI), so the vocabulary cannot silently diverge.
10
+ > The **C# itself is pending its first Unity/CI compile pass** — it was written
11
+ > without a local .NET toolchain. Please report compile issues to
12
+ > <support@pouchy.ai>.
13
+
14
+ - `System.Net.Http.HttpClient` + async/await + **Newtonsoft.Json** — the pure
15
+ `CompanionClient` has **no UnityEngine dependency**, so it unit-tests outside
16
+ the editor. `PouchyCompanionBehaviour` is the optional Unity glue.
17
+ - Typed models for every envelope/payload; unknown/newer server fields are
18
+ preserved (`[JsonExtensionData]`) instead of failing to parse.
19
+ - Voice/WebRTC and browser-only surfaces are **deliberately out of scope**
20
+ (REST/SSE only) — they need a native WebRTC plugin. Use the JS SDK for embeds.
21
+
22
+ ## Install
23
+
24
+ The package is published on npm and installs through a **UPM scoped registry**
25
+ (Unity's Package Manager speaks the npm protocol natively). Add both blocks to
26
+ your project's `Packages/manifest.json`:
27
+
28
+ ```json
29
+ {
30
+ "scopedRegistries": [
31
+ {
32
+ "name": "Pouchy",
33
+ "url": "https://registry.npmjs.org",
34
+ "scopes": ["ai.pouchy"]
35
+ }
36
+ ],
37
+ "dependencies": {
38
+ "ai.pouchy.companion": "0.1.0-preview.5"
39
+ }
40
+ }
41
+ ```
42
+
43
+ Unity resolves it on the next editor focus; after that the package shows up in
44
+ **Window → Package Manager → My Registries** with the normal version/upgrade UI.
45
+
46
+ This package depends on **Newtonsoft.Json** via
47
+ `com.unity.nuget.newtonsoft-json` (declared in `package.json`). If your project
48
+ doesn't already resolve it, install *Newtonsoft Json* from the Package Manager
49
+ (or add `"com.unity.nuget.newtonsoft-json": "3.2.1"` to your manifest).
50
+
51
+ **Player settings:** the client uses `System.Net.Http`, so set **Project
52
+ Settings → Player → Api Compatibility Level = .NET Standard 2.1** (Unity's
53
+ default). On IL2CPP builds keep managed stripping at Low, or add a `link.xml`
54
+ preserving `Pouchy.Companion` and `Newtonsoft.Json`, so the reflection-based
55
+ JSON binding survives the AOT strip. (These are the two most likely
56
+ first-compile/runtime snags — see the preview note above.)
57
+
58
+ ## Quickstart
59
+
60
+ Mint a session token from **your backend** — one per end user. Never ship a
61
+ secret key (`pchy_sk_…`) in a client build; the token is the client credential.
62
+
63
+ ```bash
64
+ curl -X POST https://pouchy.ai/v1/sessions \
65
+ -H "Authorization: Bearer pchy_sk_…" \
66
+ -H "Content-Type: application/json" \
67
+ -d '{ "agent": "<agentId>", "external_user_id": "player_42" }'
68
+ ```
69
+
70
+ ### Plain .NET (`CompanionClient`)
71
+
72
+ ```csharp
73
+ using Pouchy.Companion;
74
+
75
+ using var client = new CompanionClient("https://pouchy.ai", sessionToken);
76
+ var ack = await client.ConnectAsync();
77
+ Console.WriteLine("scopes: " + string.Join(",", ack.GrantedScopes ?? new string[0]));
78
+
79
+ // Request/response in one call — streams server-side, returns the reply.
80
+ var reply = await client.SendTextAsync("what about the boss on floor 3?", awaitReply: true);
81
+ Console.WriteLine(reply.Text);
82
+
83
+ // Streaming deltas:
84
+ await client.SendTextAsync("tell me a story", awaitReply: true,
85
+ onDelta: (chunk, reset) => Console.Write(chunk));
86
+ ```
87
+
88
+ The event stream (proactive messages, confirms, tool calls) resumes from the
89
+ session cursor and gives each envelope a typed `TypedPayload`:
90
+
91
+ ```csharp
92
+ var cts = new CancellationTokenSource();
93
+ await client.StreamEventsAsync(async env =>
94
+ {
95
+ switch (env.TypedPayload)
96
+ {
97
+ case MessagePayload m:
98
+ Debug.Log("companion: " + m.Text);
99
+ break;
100
+ case ConfirmRequestPayload c:
101
+ // Platform session tokens may resolve confirms directly — your end
102
+ // user IS this instance's human. Show your own UI, then:
103
+ var res = await client.ConfirmActionAsync(c.ConfirmId, approve: true);
104
+ Debug.Log("outcome: " + res.Outcome);
105
+ break;
106
+ case ToolCallPayload t:
107
+ await client.SendToolResultAsync(t.Id, ok: true, result: new { answer = 42 });
108
+ break;
109
+ }
110
+ }, cts.Token);
111
+ ```
112
+
113
+ ### Unity (`PouchyCompanionBehaviour`)
114
+
115
+ Add the component, set **Base Url** + **Token** in the Inspector, wire the
116
+ UnityEvents, and call `Connect()`. Every event is raised on the **main thread**,
117
+ so handlers can touch Unity APIs safely.
118
+
119
+ ```csharp
120
+ var companion = gameObject.AddComponent<PouchyCompanionBehaviour>();
121
+ companion.Token = sessionToken;
122
+ companion.OnMessage.AddListener(text => chatUI.Append(text));
123
+ companion.OnTyping.AddListener(active => typingDot.SetActive(active));
124
+ companion.Connect();
125
+ // ...later, from a send button:
126
+ companion.SendText("hi!");
127
+ ```
128
+
129
+ ### World state (play-along context)
130
+
131
+ ```csharp
132
+ await client.SendWorldStateAsync(new WorldStateEvent {
133
+ Type = "game.event.boss_spawned",
134
+ Data = Newtonsoft.Json.Linq.JToken.FromObject(new { name = "Ashen Knight", floor = 3 }),
135
+ Salience = 0.9,
136
+ });
137
+ ```
138
+
139
+ ## Methods
140
+
141
+ | Method | REST | Notes |
142
+ | --- | --- | --- |
143
+ | `ConnectAsync()` | `POST /api/companion/session` | handshake → `HelloAckPayload`; sets `SessionId`, `Cursor` |
144
+ | `SendTextAsync(text, awaitReply, onDelta, images, turnId)` | `POST …/session/{id}/input` | plain or streaming; always returns the `TurnId` (retry-idempotent — a replayed turn sets `Duplicate`) |
145
+ | `StreamEventsAsync(onEvent, ct)` | `GET …/session/{id}/stream` | SSE; typed `TypedPayload`, cursor auto-advances |
146
+ | `PendingConfirmsAsync()` | `GET …/session/{id}/confirm` | `List<PendingConfirm>` |
147
+ | `ConfirmActionAsync(confirmId, approve)` | `POST …/session/{id}/confirm` | `ConfirmResolution` |
148
+ | `SendToolResultAsync(callId, ok, result)` | `POST …/session/{id}/tool-result` | returns `allDone` |
149
+ | `SendWorldStateAsync(event \| events)` | `POST …/session/{id}/context` | fills specversion/id/source; `WorldStateAccepted` |
150
+ | `HistoryAsync(limit)` | `GET …/session/{id}/history` | `List<HistoryTurn>` |
151
+ | `RecallAsync(query, limit)` | `GET /api/companion/memory` | `List<RecalledMemory>` |
152
+ | `RememberAsync(fact)` | `POST /api/companion/memory` | needs `memory.write:app` / `:core` |
153
+ | `IngestKnowledgeAsync(text, name, kind, locale)` | `POST /api/companion/knowledge` | — |
154
+ | `GetWalletAsync()` | `GET /api/companion/wallet` | read-only (`wallet.read`) |
155
+ | `SetModalitiesAsync(modalities)` | `POST …/session/{id}/modalities` | — |
156
+ | `PingAsync()` | `POST …/session/{id}/ping` | keep-alive |
157
+ | `EndSessionAsync()` | `POST …/session/{id}/end` | clears `SessionId` |
158
+
159
+ ## Tokens & scopes
160
+
161
+ A **platform session token** (minted via `POST /v1/sessions`) represents one of
162
+ your end users and may resolve confirms directly. The token's granted scopes
163
+ (`ack.GrantedScopes`) gate memory writes, wallet reads, social messages, etc.
164
+ The scope model, confirm flow, world-state salience, and limits are identical
165
+ across SDKs — see the [Python SDK README](../python-sdk/README.md) for the same
166
+ token/scopes note, and the full reference at [pouchy.ai/sdk](https://pouchy.ai/sdk)
167
+ and `docs/companion-api-reference.md`.
168
+
169
+ ## Errors
170
+
171
+ Every failed call throws `CompanionError` with `.Status`, `.Code`
172
+ (machine-switchable — `rate_limited`, `turn_pending`, `confirm_resolved`, … see
173
+ `Protocol.ErrorCodes`) and `.RetryAfter` seconds when the server said when to
174
+ come back. Transport failures are wrapped too — `Status` is `0` and `Code` is
175
+ `request_timeout` (the per-request deadline elapsed) or `aborted` (any other
176
+ network failure), with the original exception preserved as `InnerException`.
177
+
178
+ ```csharp
179
+ try { await client.SendTextAsync("hi"); }
180
+ catch (CompanionError e) when (e.Code == "rate_limited")
181
+ {
182
+ await Task.Delay(TimeSpan.FromSeconds(e.RetryAfter ?? 1));
183
+ }
184
+ ```
185
+
186
+ ## Voice / WebRTC
187
+
188
+ Not included. This SDK is REST/SSE only; live voice calls need a native WebRTC
189
+ plugin the game supplies. `control.call_ready` / `CallReadyPayload` are part of
190
+ the typed vocabulary, but `startCall` (which returns the WebRTC credentials) is
191
+ not implemented here.
192
+
193
+ ## Not yet ported from the JS SDK
194
+
195
+ A few JS-SDK surfaces are deliberately unported (recorded decisions, not
196
+ drift — the full ledger with rationale lives at the top of `CHANGELOG.md`):
197
+ `handles`/`contextKinds` hello options, representative mode
198
+ (`visitor`/`pairVisitor`), `GetAvatar`, binary `IngestFile`
199
+ (`IngestKnowledgeAsync` text is covered), and `setToken` live-token swap (a
200
+ Unity host reconnects with a fresh client after ~1h token expiry — the
201
+ session survives the reconnect). Ask (or port) on demand.
202
+
203
+ ## Protocol parity
204
+
205
+ `Runtime/Protocol.cs` is **generated from the TypeScript source of truth**
206
+ (`src/lib/companion-sdk/protocol.ts`) and drift-tested in CI; the payload models
207
+ in `Runtime/Models.cs` have their field names pinned to the same interfaces, so
208
+ the three SDKs cannot silently diverge on the wire.