@lucid-agents/pi-xgate 0.0.0-stage → 0.1.6

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 xgate contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,212 @@
1
- # Temporary Holding Version
1
+ # xgate provider for Pi
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A Pi provider extension for wallet-paid inference, supported on Pi **1.1.0** and
4
+ **0.85.1**. It uses native
5
+ Anthropic Messages and structured OpenAI Responses through xgate. Pi executes its
6
+ own tools; xgate never executes local tools. Text, tool calls/results and supported
7
+ reasoning signatures survive the full agent loop.
8
+
9
+ ## Install
10
+
11
+ The release package is `@lucid-agents/pi-xgate`. It includes the client dependency
12
+ and the reviewed model-capability snapshot; the internal router contracts package
13
+ is not required. With Node 24.19+ (below 25) and Pi 1.1.0:
14
+
15
+ ```sh
16
+ pi install npm:@lucid-agents/pi-xgate@0.1.6
17
+ ```
18
+
19
+ For repository development before a release is available:
20
+
21
+ ```sh
22
+ pnpm --filter @lucid-agents/xgate-client build
23
+ pi install ./packages/pi-xgate
24
+ ```
25
+
26
+ Pi supplies `@earendil-works/pi-ai` and `@earendil-works/pi-coding-agent` as peers.
27
+ This release supports exactly 1.1.0 and 0.85.1 (the version TaskMarket's agent
28
+ runtime pins); other versions need their own check.
29
+
30
+ ## Connect a wallet broker
31
+
32
+ Choose Tempo MPP, Base x402 batch settlement, or Solana x402 exact in the
33
+ [wallet service policy](../xgate-client/README.md#x402-configuration). Pi's provider
34
+ and tool loop are the same for each; payment selection and keys stay in the broker.
35
+ The new x402 client adapters have fixture coverage and still require separately
36
+ budgeted signed live qualification before production use.
37
+
38
+ Run the [wallet service](../xgate-client/README.md) outside the filesystem/process
39
+ access available to Pi tools. The extension reads a **broker token**, not a wallet
40
+ key. A broker running under the same user as unrestricted Pi shell tools does not
41
+ provide key isolation.
42
+
43
+ Use `/xgate setup` in Pi to set the broker URL and an absolute path to its token
44
+ file. Setup writes `~/.pi/agent/xgate.json` only if it does not already exist. To
45
+ change an existing setup, edit that owner-only file and use `/xgate refresh`.
46
+ For headless runs, set `XGATE_CONFIG` to an absolute owner-only JSON file:
47
+
48
+ ```json
49
+ {
50
+ "url": "http://127.0.0.1:4021",
51
+ "tokenFile": "/absolute/private/broker-token"
52
+ }
53
+ ```
54
+
55
+ Both files must be regular, nonsymlink files with no group/other permissions.
56
+ Select a model through `/model`. Only the broker policy's models with compatible
57
+ live qualifications are offered. OpenAI requires the new
58
+ `openai-anonymous-tools-v2` router profile; an older server will expose no OpenAI
59
+ coding models. Anthropic requires `anthropic-anonymous-usage-v3` and uses only
60
+ `anthropic_no_cache` native Messages scopes.
61
+ An expired/revoked catalog scope cannot authorize payment just because Pi remembers
62
+ an earlier model list: the provider refreshes before each invocation and the wallet
63
+ client validates the actual quote again.
64
+
65
+ Commands:
66
+
67
+ - `/xgate status`: pending purchases and budget totals.
68
+ - `/xgate budget`: show the wallet service's fixed authorization caps.
69
+ - `/xgate receipts`: recent content-free payment records.
70
+ - `/xgate reconcile <invocation ID>`: inspect/reconcile the original purchase without
71
+ resubmitting inference or payment.
72
+ - `/xgate refresh`: reload private configuration and available models.
73
+
74
+ Pi cannot raise the service budget. Authorized ceilings remain consumed after
75
+ successful settlement even when most of a deposit is returned. A new budget is an
76
+ explicit wallet-operator action, never automatic rollover or a fresh journal.
77
+
78
+ ## Host-injected transport
79
+
80
+ A host that owns payment, such as TaskMarket, builds the provider around its own
81
+ transport instead of the wallet broker. The supported entry point ships built
82
+ JavaScript and type declarations:
83
+
84
+ ```ts
85
+ import {
86
+ createXgateExtension,
87
+ createXgateProvider,
88
+ type XgateTransport,
89
+ } from "@lucid-agents/pi-xgate";
90
+
91
+ const transport: XgateTransport = {
92
+ models: (signal) => hostCatalog(signal), // router catalog rows
93
+ purchase: (invocation, signal) => hostPurchase(invocation, signal),
94
+ };
95
+ // As a Pi extension: the broker configuration and `/xgate setup` are not used.
96
+ export default createXgateExtension({ transport });
97
+ // Or directly, e.g. for an agent's stream function.
98
+ const provider = await createXgateProvider(transport, {
99
+ sessionId: () => sessionId,
100
+ });
101
+ ```
102
+
103
+ `createXgateExtension({ transport })` also accepts a factory, called on load and on
104
+ `/xgate refresh`. `/xgate status`, `budget`, `receipts` and `reconcile` use the
105
+ transport's optional methods of the same names. The default extension
106
+ (`@lucid-agents/pi-xgate/extension`, and Pi's `pi.extensions` entry) keeps the
107
+ standalone `BrokerClient`.
108
+
109
+ The provider keeps catalog qualification, native codecs and invocation identity;
110
+ the transport only moves the frozen request and payment. A transport must send at
111
+ most one paid submission per `Invocation.id`, never retry a paid request, reject a
112
+ repeated ID rather than pay again, and resolve only with a verified result.
113
+ `BrokerClient` satisfies this contract.
114
+
115
+ ### TaskMarket payer
116
+
117
+ `createTaskMarketTransport` adapts TaskMarket's x402 payer (`@taskmarket/x402-payer`
118
+ API v1). That package is private, so this one declares a structural
119
+ `TaskMarketPayer` (`request`, `status`, `reconcile`) and TaskMarket injects its
120
+ instance. The payer remains the only wallet, signer and payment journal; the
121
+ adapter keeps no payment state.
122
+
123
+ ```ts
124
+ const transport = createTaskMarketTransport({
125
+ payer, // TaskMarket's X402Payer
126
+ origin: "https://api.xgate.run",
127
+ allocationId, // the agent session's inference allocation
128
+ nonInteractive: true,
129
+ balances: () => allocations.balances(), // optional, for display
130
+ });
131
+ const provider = await createXgateProvider(transport, {
132
+ sessionId: () => sessionId,
133
+ onPaid: (r) => show(r.payment), // network, scheme, asset, atomic amounts, channel
134
+ onPurchaseFailed: (f) => record(f), // original invocation ID, code, pending
135
+ });
136
+ ```
137
+
138
+ - The Pi invocation ID is the payer `invocationId`. A retried turn reuses it, so
139
+ the payer refuses to send it again (`invocation_duplicate`).
140
+ - The provider refreshes the router catalog (an unpaid `GET /v1/models`) before
141
+ every paid request. The payer's rail echoes the router quote binding and verifies
142
+ settlement.
143
+ - A result is accepted only with a `settled` charge, the Anonymous privacy
144
+ headers and a native JSON body. Every other outcome rejects with
145
+ `XgatePurchaseError`. It is `pending` unless the payer proved no unresolved
146
+ payment; unknown future charge states count as unverified.
147
+ - Pi gets a terminal `error`/`aborted` message naming the original invocation ID,
148
+ worded so Pi's transient-error retry does not fire. Only payer error codes reach
149
+ Pi, never payer messages. Hooks see only `content-type` and `cache-control`.
150
+ - Payment details are rail-neutral (`XgatePayment`): CAIP-2 network, scheme, asset,
151
+ payee, rail kind, authorized and charged atomic amounts, status, reference and,
152
+ for a single matching session deposit, its channel balance. Pi displays them
153
+ and never selects or configures a rail.
154
+
155
+ ## Behavior and limits
156
+
157
+ Responses are **buffered**, not token-streamed over the network. After a complete
158
+ response and independently verified payment, the adapter emits Pi's normal text,
159
+ thinking and tool-call events. It never exposes partial tool calls before payment
160
+ verification. It emits one terminal event and honors cancellation; cancellation
161
+ can still leave a charged or unresolved purchase requiring status inspection.
162
+
163
+ There is no automatic paid POST retry. Stable invocation IDs bind retries to the
164
+ same ledger record even when a payload hook changes its output. Unresolved
165
+ purchases block new purchases. A lost result is not recoverable from xgate and
166
+ cannot be regenerated by replaying the same invocation. Keep Pi agent/provider
167
+ retry disabled for an initial deployment; the durable wallet guard is independent
168
+ of that setting. Cache warming is disabled for this provider.
169
+
170
+ The initial scope is text-only, with a maximum advertised output of 8,192 tokens
171
+ and a default request cap of 4,096. A lower Pi `models.json` `modelOverrides`
172
+ `maxTokens` value lowers that default; it cannot raise the live catalog limit. Pi thinking controls expose the reviewed levels (GPT-5 Mini currently permits only
173
+ `minimal`) and clamp the selected level through Pi's native model handling.
174
+ It rejects images, unsupported sampling/forced
175
+ tool settings, excessive schemas and incompatible thinking levels before paying.
176
+ OpenAI allows eight custom tools / 8 KiB of declarations; Anthropic allows 32 /
177
+ 64 KiB. The router additionally validates tool/result identities and message bounds.
178
+ Opaque OpenAI reasoning reserves the reviewed full input ceiling because ciphertext
179
+ length cannot prove its decoded token count. Consequently its authorization hold
180
+ can be much larger than its eventual charge; the wallet cap still applies.
181
+
182
+ Pi's token-cost figures are supplier-rate estimates, excluding router markup and
183
+ success floors. `/xgate` status and receipts report actual atomic payment amounts.
184
+ The extension sends no Pi session ID, caller metadata, cache affinity key or wallet
185
+ credential to the inference provider. Payload hooks run before quoting and cannot
186
+ change the model, output cap or tool declarations. Response hooks receive sanitized
187
+ headers without payment/status capabilities.
188
+
189
+ Pi retains its own transcripts by default. That is separate from xgate's
190
+ application content-retention policy. Use Pi's `--no-session` option if local
191
+ transcript persistence is unwanted; financial reconciliation records are separate.
192
+ Changing models can discard incompatible opaque reasoning signatures, but preserves
193
+ text/tool history and consistent tool IDs. Removing a historical Anthropic tool
194
+ from the current tool set is rejected rather than silently losing that history.
195
+
196
+ ## Tests and rollout
197
+
198
+ Run `pnpm pi:check` at the repository root. Tests instantiate Pi's real agent core,
199
+ execute a fixture tool, and verify the next request contains its exact result and
200
+ reasoning history. `pnpm packages:check` installs the packed tarballs with Pi 1.1.0
201
+ and 0.85.1 and runs each real Pi CLI through the mock broker and an injected
202
+ transport, type-checks a consumer of the supported exports, and runs the TaskMarket
203
+ payer loop suite (`test/installed-loop.test.ts`: continuation, qualification,
204
+ cancellation, duplicate and lost response) against each installed Pi. Separate wallet tests cover durable financial boundaries.
205
+ These are fixture tests. The [9 October staging campaign](../../docs/validation/pi-client-staging-20261009.md)
206
+ separately exercised the installed Pi CLI with live providers and test tokens.
207
+ That evidence is staging-only; it does not qualify production payment execution.
208
+ Installing the extension does not publish router qualifications or deploy any service.
209
+
210
+ ## License
211
+
212
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,34 @@
1
+ import type { AssistantMessage, Message, Model, SimpleStreamOptions, Tool, Usage } from "@earendil-works/pi-ai";
2
+ import { type Route } from "@lucid-agents/xgate-client/types";
3
+ export type Entry = {
4
+ model: Model<any>;
5
+ catalog: any;
6
+ route: Route;
7
+ efforts: string[];
8
+ };
9
+ export declare function catalogEntries(rows: any[]): Entry[];
10
+ /**
11
+ * Pi 1.1 passes a normalized transcript; Pi 0.85 passes `systemPrompt` and `tools` beside the
12
+ * messages. Structural, so published types do not name a type only one Pi version has.
13
+ */
14
+ export type PiContext = {
15
+ messages: Message[];
16
+ systemPrompt?: string;
17
+ tools?: Tool[];
18
+ };
19
+ type Turn = Exclude<Message, {
20
+ role: "system";
21
+ }>;
22
+ export declare function transcript(context: PiContext): {
23
+ system: string;
24
+ tools: Tool[];
25
+ messages: Turn[];
26
+ };
27
+ export declare function encode(entry: Entry, context: PiContext, options?: SimpleStreamOptions): any;
28
+ export declare function decode(entry: Entry, body: any): {
29
+ content: AssistantMessage["content"];
30
+ usage: Usage;
31
+ stopReason: "stop" | "toolUse";
32
+ responseId: string;
33
+ };
34
+ export {};