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 +142 -0
- package/LICENSE +42 -0
- package/README.md +208 -0
- package/Runtime/CompanionClient.cs +626 -0
- package/Runtime/CompanionError.cs +48 -0
- package/Runtime/Models.cs +417 -0
- package/Runtime/Pouchy.Companion.asmdef +14 -0
- package/Runtime/PouchyCompanionBehaviour.cs +141 -0
- package/Runtime/Protocol.cs +133 -0
- package/package.json +35 -0
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.
|