@lucid-agents/pi-xgate 0.1.5 → 0.1.7

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/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # xgate provider for Pi
2
2
 
3
- A Pi **v1.1.0** provider extension for wallet-paid inference. It uses native
3
+ A Pi provider extension for wallet-paid inference, supported on Pi **1.1.0** and
4
+ **0.85.1**. It uses native
4
5
  Anthropic Messages and structured OpenAI Responses through xgate. Pi executes its
5
6
  own tools; xgate never executes local tools. Text, tool calls/results and supported
6
7
  reasoning signatures survive the full agent loop.
@@ -9,10 +10,10 @@ reasoning signatures survive the full agent loop.
9
10
 
10
11
  The release package is `@lucid-agents/pi-xgate`. It includes the client dependency
11
12
  and the reviewed model-capability snapshot; the internal router contracts package
12
- is not required. With Node 24.19+ (below 25) and Pi 1.1.0:
13
+ is not required. With Node 24.19 or later and Pi 1.1.0:
13
14
 
14
15
  ```sh
15
- pi install npm:@lucid-agents/pi-xgate@0.1.5
16
+ pi install npm:@lucid-agents/pi-xgate@0.1.7
16
17
  ```
17
18
 
18
19
  For repository development before a release is available:
@@ -23,7 +24,8 @@ pi install ./packages/pi-xgate
23
24
  ```
24
25
 
25
26
  Pi supplies `@earendil-works/pi-ai` and `@earendil-works/pi-coding-agent` as peers.
26
- This release pins compatibility to 1.1.0; other versions need their own check.
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.
27
29
 
28
30
  ## Connect a wallet broker
29
31
 
@@ -54,8 +56,21 @@ Both files must be regular, nonsymlink files with no group/other permissions.
54
56
  Select a model through `/model`. Only the broker policy's models with compatible
55
57
  live qualifications are offered. OpenAI requires the new
56
58
  `openai-anonymous-tools-v2` router profile; an older server will expose no OpenAI
57
- coding models. Anthropic requires `anthropic-anonymous-usage-v3` and uses only
58
- `anthropic_no_cache` native Messages scopes.
59
+ coding models. Anthropic requires `anthropic-anonymous-usage-v3` native Messages
60
+ scopes qualified as `anthropic_no_cache`, `anthropic_ephemeral_5m` or
61
+ `anthropic_ephemeral_1h`.
62
+
63
+ Anthropic caches a prompt only at explicit `cache_control` breakpoints. On a
64
+ cache-qualified scope the provider marks, like Pi's direct Anthropic provider, the
65
+ last tool, the system prompt and the newest user turn (three of Anthropic's four
66
+ breakpoints), so each call in a conversation can read the prefix the previous call
67
+ wrote. Breakpoints are five-minute `ephemeral` writes; Pi's `cacheRetention: "long"`
68
+ selects one-hour writes only on an `anthropic_ephemeral_1h` scope, and
69
+ `cacheRetention: "none"` sends none. An `anthropic_no_cache` scope gets no
70
+ breakpoints, and cache usage it reports is rejected as an invalid result. The
71
+ breakpoints carry no session ID or other key: the provider cache is keyed by the
72
+ content prefix alone. Cache warming stays off, so no unpaid or extra paid call
73
+ is made to keep a cache alive.
59
74
  An expired/revoked catalog scope cannot authorize payment just because Pi remembers
60
75
  an earlier model list: the provider refreshes before each invocation and the wallet
61
76
  client validates the actual quote again.
@@ -73,6 +88,83 @@ Pi cannot raise the service budget. Authorized ceilings remain consumed after
73
88
  successful settlement even when most of a deposit is returned. A new budget is an
74
89
  explicit wallet-operator action, never automatic rollover or a fresh journal.
75
90
 
91
+ ## Host-injected transport
92
+
93
+ A host that owns payment, such as TaskMarket, builds the provider around its own
94
+ transport instead of the wallet broker. The supported entry point ships built
95
+ JavaScript and type declarations:
96
+
97
+ ```ts
98
+ import {
99
+ createXgateExtension,
100
+ createXgateProvider,
101
+ type XgateTransport,
102
+ } from "@lucid-agents/pi-xgate";
103
+
104
+ const transport: XgateTransport = {
105
+ models: (signal) => hostCatalog(signal), // router catalog rows
106
+ purchase: (invocation, signal) => hostPurchase(invocation, signal),
107
+ };
108
+ // As a Pi extension: the broker configuration and `/xgate setup` are not used.
109
+ export default createXgateExtension({ transport });
110
+ // Or directly, e.g. for an agent's stream function.
111
+ const provider = await createXgateProvider(transport, {
112
+ sessionId: () => sessionId,
113
+ });
114
+ ```
115
+
116
+ `createXgateExtension({ transport })` also accepts a factory, called on load and on
117
+ `/xgate refresh`. `/xgate status`, `budget`, `receipts` and `reconcile` use the
118
+ transport's optional methods of the same names. The default extension
119
+ (`@lucid-agents/pi-xgate/extension`, and Pi's `pi.extensions` entry) keeps the
120
+ standalone `BrokerClient`.
121
+
122
+ The provider keeps catalog qualification, native codecs and invocation identity;
123
+ the transport only moves the frozen request and payment. A transport must send at
124
+ most one paid submission per `Invocation.id`, never retry a paid request, reject a
125
+ repeated ID rather than pay again, and resolve only with a verified result.
126
+ `BrokerClient` satisfies this contract.
127
+
128
+ ### TaskMarket payer
129
+
130
+ `createTaskMarketTransport` adapts TaskMarket's x402 payer (`@taskmarket/x402-payer`
131
+ API v1). That package is private, so this one declares a structural
132
+ `TaskMarketPayer` (`request`, `status`, `reconcile`) and TaskMarket injects its
133
+ instance. The payer remains the only wallet, signer and payment journal; the
134
+ adapter keeps no payment state.
135
+
136
+ ```ts
137
+ const transport = createTaskMarketTransport({
138
+ payer, // TaskMarket's X402Payer
139
+ origin: "https://api.xgate.run",
140
+ allocationId, // the agent session's inference allocation
141
+ nonInteractive: true,
142
+ balances: () => allocations.balances(), // optional, for display
143
+ });
144
+ const provider = await createXgateProvider(transport, {
145
+ sessionId: () => sessionId,
146
+ onPaid: (r) => show(r.payment), // network, scheme, asset, atomic amounts, channel
147
+ onPurchaseFailed: (f) => record(f), // original invocation ID, code, pending
148
+ });
149
+ ```
150
+
151
+ - The Pi invocation ID is the payer `invocationId`. A retried turn reuses it, so
152
+ the payer refuses to send it again (`invocation_duplicate`).
153
+ - The provider refreshes the router catalog (an unpaid `GET /v1/models`) before
154
+ every paid request. The payer's rail echoes the router quote binding and verifies
155
+ settlement.
156
+ - A result is accepted only with a `settled` charge, the Anonymous privacy
157
+ headers and a native JSON body. Every other outcome rejects with
158
+ `XgatePurchaseError`. It is `pending` unless the payer proved no unresolved
159
+ payment; unknown future charge states count as unverified.
160
+ - Pi gets a terminal `error`/`aborted` message naming the original invocation ID,
161
+ worded so Pi's transient-error retry does not fire. Only payer error codes reach
162
+ Pi, never payer messages. Hooks see only `content-type` and `cache-control`.
163
+ - Payment details are rail-neutral (`XgatePayment`): CAIP-2 network, scheme, asset,
164
+ payee, rail kind, authorized and charged atomic amounts, status, reference and,
165
+ for a single matching session deposit, its channel balance. Pi displays them
166
+ and never selects or configures a rail.
167
+
76
168
  ## Behavior and limits
77
169
 
78
170
  Responses are **buffered**, not token-streamed over the network. After a complete
@@ -118,7 +210,11 @@ from the current tool set is rejected rather than silently losing that history.
118
210
 
119
211
  Run `pnpm pi:check` at the repository root. Tests instantiate Pi's real agent core,
120
212
  execute a fixture tool, and verify the next request contains its exact result and
121
- reasoning history. Separate wallet tests cover durable financial boundaries.
213
+ reasoning history. `pnpm packages:check` installs the packed tarballs with Pi 1.1.0
214
+ and 0.85.1 and runs each real Pi CLI through the mock broker and an injected
215
+ transport, type-checks a consumer of the supported exports, and runs the TaskMarket
216
+ payer loop suite (`test/installed-loop.test.ts`: continuation, qualification,
217
+ cancellation, duplicate and lost response) against each installed Pi. Separate wallet tests cover durable financial boundaries.
122
218
  These are fixture tests. The [9 October staging campaign](../../docs/validation/pi-client-staging-20261009.md)
123
219
  separately exercised the installed Pi CLI with live providers and test tokens.
124
220
  That evidence is staging-only; it does not qualify production payment execution.
@@ -0,0 +1,40 @@
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
+ /** The widest Anthropic prompt-cache write lifetime a scope's qualification permits. */
4
+ export type CacheTtl = "none" | "5m" | "1h";
5
+ export type Entry = {
6
+ model: Model<any>;
7
+ catalog: any;
8
+ route: Route;
9
+ efforts: string[];
10
+ /** Anthropic only: requests may carry `cache_control` breakpoints up to this lifetime. */
11
+ cache: CacheTtl;
12
+ /** Anthropic only: the per-million-token price of a one-hour cache write. */
13
+ cacheWrite1h: number;
14
+ };
15
+ export declare function catalogEntries(rows: any[]): Entry[];
16
+ /**
17
+ * Pi 1.1 passes a normalized transcript; Pi 0.85 passes `systemPrompt` and `tools` beside the
18
+ * messages. Structural, so published types do not name a type only one Pi version has.
19
+ */
20
+ export type PiContext = {
21
+ messages: Message[];
22
+ systemPrompt?: string;
23
+ tools?: Tool[];
24
+ };
25
+ type Turn = Exclude<Message, {
26
+ role: "system";
27
+ }>;
28
+ export declare function transcript(context: PiContext): {
29
+ system: string;
30
+ tools: Tool[];
31
+ messages: Turn[];
32
+ };
33
+ export declare function encode(entry: Entry, context: PiContext, options?: SimpleStreamOptions): any;
34
+ export declare function decode(entry: Entry, body: any): {
35
+ content: AssistantMessage["content"];
36
+ usage: Usage;
37
+ stopReason: "stop" | "toolUse";
38
+ responseId: string;
39
+ };
40
+ export {};