@rubicon-caliga/agent-sdk 0.1.0

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 Rubicon
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 ADDED
@@ -0,0 +1,76 @@
1
+ # @rubicon-caliga/agent-sdk
2
+
3
+ Buyer-agent SDK for Rubicon. The agent opens a budgeted reading session and pays
4
+ for **every individual word** it receives via x402. A high-level `read()` loop
5
+ runs the whole seller conversation → session → one-word payment → word → usage
6
+ cycle until a stop condition is met, so you never send a payment per word by
7
+ hand. The buyer can stop the moment it has enough information and pays for
8
+ exactly the words it received.
9
+
10
+ ## Quick Start
11
+
12
+ ```ts
13
+ import Rubicon, { CircleGatewayPaymentEngine, StaticPaymentEngine } from "@rubicon-caliga/agent-sdk";
14
+
15
+ const privateKey = process.env.CIRCLE_PRIVATE_KEY as `0x${string}` | undefined;
16
+
17
+ const rubicon = new Rubicon({
18
+ paymentEngine: privateKey
19
+ ? new CircleGatewayPaymentEngine({ chain: "arcTestnet", privateKey, rpcUrl: process.env.CIRCLE_RPC_URL })
20
+ : new StaticPaymentEngine(),
21
+ });
22
+
23
+ const receipt = await rubicon.run({
24
+ articleId: "live-article-id-from-repository",
25
+ goal: "Find the resale-fee clause",
26
+ maxSpendAtomic: "20000",
27
+ stopWhen: ({ text, wordsRead, amountPaid }) => wordsRead > 50 || /resale fee/i.test(text),
28
+ onWord: (word) => {
29
+ process.stdout.write(`${word} `);
30
+ },
31
+ });
32
+
33
+ console.log("\nreceipt:", receipt);
34
+ ```
35
+
36
+ `baseUrl` defaults to `http://localhost:8787`. In development, omitting
37
+ `paymentEngine` uses `StaticPaymentEngine`, which works against a dev-mode
38
+ gateway. For real settlement, pass `CircleGatewayPaymentEngine`.
39
+
40
+ ## `run(options)`
41
+
42
+ Runs the whole seller conversation → session → one-word payment → word → usage
43
+ cycle and returns a final receipt. Use `onWord` or `onEvent` when you want live
44
+ progress.
45
+
46
+ ## `read(options)`
47
+
48
+ Yields `session.started`, `seller.message`, `article.word`, `article.usage`,
49
+ `article.completed` (with a final receipt), and `article.error`. It handles:
50
+
51
+ - seller-agent conversation and starting-section selection
52
+ - session creation and one-word payment creation/submission
53
+ - word receipt and running usage
54
+ - retry idempotency (per-word idempotency keys)
55
+ - budget enforcement (`maxSpendAtomic` / `budget`)
56
+ - early stopping (`stopWhen`, `maxWords`)
57
+ - stream abortion and a final receipt
58
+
59
+ ## Lower-level methods
60
+
61
+ `getRepository`, `getNavigation`, `startConversation`, `sendConversationMessage`,
62
+ `startSession`, `payForWord`, `abort`, and `streamEvents` (raw SSE) are available
63
+ for custom flows.
64
+
65
+ ## Payment engines
66
+
67
+ - `StaticPaymentEngine` — no-money development engine for a dev-mode gateway.
68
+ - `CircleGatewayPaymentEngine` — signs the gateway's one-word x402 terms; Circle
69
+ may batch settlement internally, but each payload corresponds to one word.
70
+
71
+ ## Local Run
72
+
73
+ ```bash
74
+ pnpm dev:gateway
75
+ pnpm dev:agent
76
+ ```
@@ -0,0 +1,124 @@
1
+ import type { ArticleSummary, Budget, GatewayEvent, SendConversationMessageResponse, StartConversationResponse, StartSessionRequest, StartSessionResponse, StreamPaymentRequest, StreamPaymentResponse, WordPaymentReceipt } from "@rubicon-caliga/core";
2
+ import { type AgentPaymentEngine } from "./payment-engine.js";
3
+ export interface RubiconClientOptions {
4
+ baseUrl?: string;
5
+ paymentEngine?: AgentPaymentEngine;
6
+ /** Optional auth header value for the public agent API, e.g. "Bearer <token>". */
7
+ authorization?: string;
8
+ fetch?: typeof fetch;
9
+ }
10
+ export interface RepositoryResponse {
11
+ repository: "articles";
12
+ articles: ArticleSummary[];
13
+ }
14
+ export interface NavigationResponse {
15
+ article: ArticleSummary;
16
+ navigation: StartSessionResponse["navigation"];
17
+ }
18
+ export interface ReadReceipt {
19
+ sessionId: string;
20
+ articleId: string;
21
+ conversationId: string;
22
+ wordsRead: number;
23
+ amountPaidAtomic: `${bigint}`;
24
+ payments: WordPaymentReceipt[];
25
+ transactionHashes: string[];
26
+ text: string;
27
+ completed: boolean;
28
+ stopReason: "article_completed" | "stop_condition" | "budget_reached" | "max_words" | "aborted";
29
+ }
30
+ export type RubiconReadEvent = {
31
+ type: "session.started";
32
+ session: StartSessionResponse;
33
+ } | {
34
+ type: "seller.message";
35
+ content: string;
36
+ recommendedSectionId?: string;
37
+ } | {
38
+ type: "article.word";
39
+ sequence: number;
40
+ word: string;
41
+ priceAtomic: `${bigint}`;
42
+ wordsRead: number;
43
+ amountPaidAtomic: `${bigint}`;
44
+ transactionHash?: string;
45
+ transactionHashes?: string[];
46
+ payment?: WordPaymentReceipt;
47
+ text: string;
48
+ } | {
49
+ type: "article.usage";
50
+ wordsPaid: number;
51
+ wordsDelivered: number;
52
+ paidAtomic: `${bigint}`;
53
+ } | {
54
+ type: "article.completed";
55
+ receipt: ReadReceipt;
56
+ } | {
57
+ type: "article.error";
58
+ message: string;
59
+ };
60
+ export interface ReadOptions {
61
+ articleId: string;
62
+ goal?: string;
63
+ sectionId?: string;
64
+ conversationId?: string;
65
+ /** Hard spend ceiling in atomic USDC. Equivalent to budget.maxAmountAtomic. */
66
+ maxSpendAtomic?: `${bigint}`;
67
+ budget?: Budget;
68
+ maxWords?: number;
69
+ /** Return true to stop reading once enough information has been collected. */
70
+ stopWhen?: (state: {
71
+ text: string;
72
+ wordsRead: number;
73
+ amountPaid: bigint;
74
+ }) => boolean | Promise<boolean>;
75
+ metadata?: Record<string, unknown>;
76
+ }
77
+ export interface RunOptions extends ReadOptions {
78
+ onEvent?: (event: RubiconReadEvent) => void | Promise<void>;
79
+ onWord?: (word: string, state: {
80
+ text: string;
81
+ wordsRead: number;
82
+ amountPaidAtomic: `${bigint}`;
83
+ }) => void | Promise<void>;
84
+ }
85
+ /**
86
+ * High-level buyer-agent client for Rubicon. `read()` runs the entire
87
+ * pay -> word -> usage loop one word at a time until a stop condition is met,
88
+ * so application developers never send a payment for every word themselves.
89
+ */
90
+ export declare class RubiconClient {
91
+ private readonly options;
92
+ private readonly fetcher;
93
+ private readonly baseUrl;
94
+ private readonly paymentEngine;
95
+ constructor(options: RubiconClientOptions);
96
+ getRepository(): Promise<RepositoryResponse>;
97
+ getNavigation(articleId: string, goal?: string): Promise<NavigationResponse>;
98
+ startConversation(input: {
99
+ articleId: string;
100
+ goal?: string;
101
+ message?: string;
102
+ }): Promise<StartConversationResponse>;
103
+ sendConversationMessage(conversationId: string, message: string): Promise<SendConversationMessageResponse>;
104
+ startSession(request: StartSessionRequest): Promise<StartSessionResponse>;
105
+ payForWord(sessionId: string, payment: StreamPaymentRequest): Promise<StreamPaymentResponse>;
106
+ abort(sessionId: string, reason?: string): Promise<void>;
107
+ /** Subscribe to raw word-level server-sent events for observation/logging. */
108
+ streamEvents(sessionId: string, onEvent: (event: GatewayEvent) => void): () => void;
109
+ /**
110
+ * Simplest path for agents: run the whole paid read and return the receipt.
111
+ * Use callbacks only when the caller wants live progress.
112
+ */
113
+ run(options: RunOptions): Promise<ReadReceipt>;
114
+ /**
115
+ * Read an article one paid word at a time. Yields seller messages, paid words,
116
+ * running usage, and a final completion event carrying the receipt.
117
+ */
118
+ read(options: ReadOptions): AsyncGenerator<RubiconReadEvent, ReadReceipt>;
119
+ private headers;
120
+ private readJson;
121
+ }
122
+ /** Backwards-compatible alias. */
123
+ export declare const AgentClient: typeof RubiconClient;
124
+ //# sourceMappingURL=agent-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-client.d.ts","sourceRoot":"","sources":["../src/agent-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,EACd,MAAM,EACN,YAAY,EACZ,+BAA+B,EAC/B,yBAAyB,EACzB,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAuB,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEnF,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC,kFAAkF;IAClF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,cAAc,CAAC;IACxB,UAAU,EAAE,oBAAoB,CAAC,YAAY,CAAC,CAAC;CAChD;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,GAAG,MAAM,EAAE,CAAC;IAC9B,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,mBAAmB,GAAG,gBAAgB,GAAG,gBAAgB,GAAG,WAAW,GAAG,SAAS,CAAC;CACjG;AAED,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,OAAO,EAAE,oBAAoB,CAAA;CAAE,GAC1D;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,oBAAoB,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1E;IACE,IAAI,EAAE,cAAc,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,GAAG,MAAM,EAAE,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,GAAG,MAAM,EAAE,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;CACd,GACD;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,GAAG,MAAM,EAAE,CAAA;CAAE,GAC7F;IAAE,IAAI,EAAE,mBAAmB,CAAC;IAAC,OAAO,EAAE,WAAW,CAAA;CAAE,GACnD;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/C,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+EAA+E;IAC/E,cAAc,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;KACpB,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,UAAW,SAAQ,WAAW;IAC7C,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,GAAG,MAAM,EAAE,CAAA;KAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5H;AAED;;;;GAIG;AACH,qBAAa,aAAa;IAKZ,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;gBAEtB,OAAO,EAAE,oBAAoB;IAMpD,aAAa,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAI5C,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAQ5E,iBAAiB,CAAC,KAAK,EAAE;QAC7B,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAUhC,uBAAuB,CAC3B,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,+BAA+B,CAAC;IAarC,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAUzE,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAY5F,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,SAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAQzE,8EAA8E;IAC9E,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,MAAM,IAAI;IAiBnF;;;OAGG;IACG,GAAG,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC;IAqBpD;;;OAGG;IACI,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,cAAc,CAAC,gBAAgB,EAAE,WAAW,CAAC;IA8IhF,OAAO,CAAC,OAAO;YAQD,QAAQ;CAMvB;AAED,kCAAkC;AAClC,eAAO,MAAM,WAAW,sBAAgB,CAAC"}
@@ -0,0 +1,258 @@
1
+ import { EventSource } from "eventsource";
2
+ import { StaticPaymentEngine } from "./payment-engine.js";
3
+ /**
4
+ * High-level buyer-agent client for Rubicon. `read()` runs the entire
5
+ * pay -> word -> usage loop one word at a time until a stop condition is met,
6
+ * so application developers never send a payment for every word themselves.
7
+ */
8
+ export class RubiconClient {
9
+ options;
10
+ fetcher;
11
+ baseUrl;
12
+ paymentEngine;
13
+ constructor(options) {
14
+ this.options = options;
15
+ this.fetcher = options.fetch ?? fetch;
16
+ this.baseUrl = options.baseUrl ?? "http://localhost:8787";
17
+ this.paymentEngine = options.paymentEngine ?? new StaticPaymentEngine();
18
+ }
19
+ async getRepository() {
20
+ return this.readJson(await this.fetcher(`${this.baseUrl}/v1/repository`, { headers: this.headers() }));
21
+ }
22
+ async getNavigation(articleId, goal) {
23
+ const url = new URL(`${this.baseUrl}/v1/articles/${articleId}/navigation`);
24
+ if (goal) {
25
+ url.searchParams.set("goal", goal);
26
+ }
27
+ return this.readJson(await this.fetcher(url.toString(), { headers: this.headers() }));
28
+ }
29
+ async startConversation(input) {
30
+ return this.readJson(await this.fetcher(`${this.baseUrl}/v1/seller-agent/conversations`, {
31
+ method: "POST",
32
+ headers: this.headers({ "content-type": "application/json" }),
33
+ body: JSON.stringify(input),
34
+ }));
35
+ }
36
+ async sendConversationMessage(conversationId, message) {
37
+ return this.readJson(await this.fetcher(`${this.baseUrl}/v1/seller-agent/conversations/${conversationId}/messages`, {
38
+ method: "POST",
39
+ headers: this.headers({ "content-type": "application/json" }),
40
+ body: JSON.stringify({ message }),
41
+ }));
42
+ }
43
+ async startSession(request) {
44
+ return this.readJson(await this.fetcher(`${this.baseUrl}/v1/sessions`, {
45
+ method: "POST",
46
+ headers: this.headers({ "content-type": "application/json" }),
47
+ body: JSON.stringify(request),
48
+ }));
49
+ }
50
+ async payForWord(sessionId, payment) {
51
+ const response = await this.fetcher(`${this.baseUrl}/v1/sessions/${sessionId}/payments`, {
52
+ method: "POST",
53
+ headers: this.headers({ "content-type": "application/json" }),
54
+ body: JSON.stringify(payment),
55
+ });
56
+ if (!response.ok) {
57
+ throw new Error(`Word payment rejected: ${response.status} ${await response.text()}`);
58
+ }
59
+ return response.json();
60
+ }
61
+ async abort(sessionId, reason = "agent_cancelled") {
62
+ await this.fetcher(`${this.baseUrl}/v1/sessions/${sessionId}/abort`, {
63
+ method: "POST",
64
+ headers: this.headers({ "content-type": "application/json" }),
65
+ body: JSON.stringify({ reason }),
66
+ });
67
+ }
68
+ /** Subscribe to raw word-level server-sent events for observation/logging. */
69
+ streamEvents(sessionId, onEvent) {
70
+ const headers = this.headers();
71
+ const source = new EventSource(`${this.baseUrl}/v1/sessions/${sessionId}/events`, {
72
+ fetch: (input, init) => this.fetcher(input, {
73
+ ...init,
74
+ headers: {
75
+ ...Object.fromEntries(new Headers(init?.headers).entries()),
76
+ ...headers,
77
+ },
78
+ }),
79
+ });
80
+ source.onmessage = (message) => onEvent(JSON.parse(message.data));
81
+ source.onerror = () => source.close();
82
+ return () => source.close();
83
+ }
84
+ /**
85
+ * Simplest path for agents: run the whole paid read and return the receipt.
86
+ * Use callbacks only when the caller wants live progress.
87
+ */
88
+ async run(options) {
89
+ let receipt;
90
+ for await (const event of this.read(options)) {
91
+ await options.onEvent?.(event);
92
+ if (event.type === "article.word") {
93
+ await options.onWord?.(event.word, {
94
+ text: event.text,
95
+ wordsRead: event.wordsRead,
96
+ amountPaidAtomic: event.amountPaidAtomic,
97
+ });
98
+ }
99
+ if (event.type === "article.completed") {
100
+ receipt = event.receipt;
101
+ }
102
+ }
103
+ if (!receipt) {
104
+ throw new Error("Rubicon read finished without a receipt");
105
+ }
106
+ return receipt;
107
+ }
108
+ /**
109
+ * Read an article one paid word at a time. Yields seller messages, paid words,
110
+ * running usage, and a final completion event carrying the receipt.
111
+ */
112
+ async *read(options) {
113
+ const budget = options.budget ??
114
+ (options.maxSpendAtomic
115
+ ? { currency: "USDC", maxAmountAtomic: options.maxSpendAtomic }
116
+ : (() => {
117
+ throw new Error("read() requires maxSpendAtomic or budget");
118
+ })());
119
+ const budgetAtomic = BigInt(budget.maxAmountAtomic);
120
+ // Let the seller agent recommend a starting section if the buyer has a goal
121
+ // and did not pick one explicitly.
122
+ let conversationId = options.conversationId;
123
+ let sectionId = options.sectionId;
124
+ if (options.goal && !conversationId) {
125
+ const conversation = await this.startConversation({
126
+ articleId: options.articleId,
127
+ goal: options.goal,
128
+ message: options.goal,
129
+ });
130
+ conversationId = conversation.conversationId;
131
+ const seller = conversation.messages.find((message) => message.role === "seller");
132
+ if (seller) {
133
+ yield {
134
+ type: "seller.message",
135
+ content: seller.content,
136
+ recommendedSectionId: seller.recommendedSectionId,
137
+ };
138
+ sectionId = sectionId ?? seller.recommendedSectionId;
139
+ }
140
+ }
141
+ const session = await this.startSession({
142
+ articleId: options.articleId,
143
+ goal: options.goal,
144
+ conversationId,
145
+ sectionId,
146
+ budget,
147
+ metadata: options.metadata,
148
+ });
149
+ yield { type: "session.started", session };
150
+ const wordPaymentAtomic = BigInt(session.wordPaymentAtomic);
151
+ let text = "";
152
+ let wordsRead = 0;
153
+ let amountPaid = 0n;
154
+ const transactionHashes = [];
155
+ const payments = [];
156
+ let stopReason = "article_completed";
157
+ let completed = false;
158
+ const makeReceipt = () => ({
159
+ sessionId: session.sessionId,
160
+ articleId: session.article.articleId,
161
+ conversationId: session.conversationId,
162
+ wordsRead,
163
+ amountPaidAtomic: `${amountPaid}`,
164
+ payments: [...payments],
165
+ transactionHashes: [...transactionHashes],
166
+ text,
167
+ completed,
168
+ stopReason,
169
+ });
170
+ while (true) {
171
+ if (options.maxWords !== undefined && wordsRead >= options.maxWords) {
172
+ stopReason = "max_words";
173
+ break;
174
+ }
175
+ if (amountPaid + wordPaymentAtomic > budgetAtomic) {
176
+ stopReason = "budget_reached";
177
+ break;
178
+ }
179
+ if (await options.stopWhen?.({ text, wordsRead, amountPaid })) {
180
+ stopReason = "stop_condition";
181
+ break;
182
+ }
183
+ const payment = await this.paymentEngine.createWordPayment(session);
184
+ // Idempotency key ties this payment to the specific next word; safe retries.
185
+ const idempotencyKey = `${session.sessionId}:${wordsRead}`;
186
+ let result;
187
+ try {
188
+ result = await this.payForWord(session.sessionId, { ...payment, idempotencyKey });
189
+ }
190
+ catch (error) {
191
+ yield { type: "article.error", message: error instanceof Error ? error.message : String(error) };
192
+ stopReason = "aborted";
193
+ break;
194
+ }
195
+ if (result.completed && result.word === "") {
196
+ // Article exhausted with no further word to deliver.
197
+ completed = true;
198
+ stopReason = "article_completed";
199
+ const receipt = makeReceipt();
200
+ yield { type: "article.completed", receipt };
201
+ return receipt;
202
+ }
203
+ wordsRead = result.wordsDelivered;
204
+ amountPaid = BigInt(result.paidAtomic);
205
+ if (result.payment) {
206
+ payments.push(result.payment);
207
+ }
208
+ transactionHashes.push(...(result.transactionHashes ?? (result.transactionHash ? [result.transactionHash] : [])));
209
+ text = text ? `${text} ${result.word}` : result.word;
210
+ yield {
211
+ type: "article.word",
212
+ sequence: result.sequence,
213
+ word: result.word,
214
+ priceAtomic: result.priceAtomic,
215
+ wordsRead,
216
+ amountPaidAtomic: `${amountPaid}`,
217
+ transactionHash: result.transactionHash,
218
+ transactionHashes: result.transactionHashes,
219
+ payment: result.payment,
220
+ text,
221
+ };
222
+ yield {
223
+ type: "article.usage",
224
+ wordsPaid: result.wordsPaid,
225
+ wordsDelivered: result.wordsDelivered,
226
+ paidAtomic: result.paidAtomic,
227
+ };
228
+ if (result.completed) {
229
+ completed = true;
230
+ stopReason = "article_completed";
231
+ const receipt = makeReceipt();
232
+ yield { type: "article.completed", receipt };
233
+ return receipt;
234
+ }
235
+ }
236
+ // Stopped early by the buyer: abort the session so no further words are owed.
237
+ await this.abort(session.sessionId, stopReason).catch(() => { });
238
+ const receipt = makeReceipt();
239
+ yield { type: "article.completed", receipt };
240
+ return receipt;
241
+ }
242
+ headers(extra = {}) {
243
+ const headers = { ...extra };
244
+ if (this.options.authorization) {
245
+ headers.authorization = this.options.authorization;
246
+ }
247
+ return headers;
248
+ }
249
+ async readJson(response) {
250
+ if (!response.ok) {
251
+ throw new Error(`Gateway request failed: ${response.status} ${await response.text()}`);
252
+ }
253
+ return response.json();
254
+ }
255
+ }
256
+ /** Backwards-compatible alias. */
257
+ export const AgentClient = RubiconClient;
258
+ //# sourceMappingURL=agent-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-client.js","sourceRoot":"","sources":["../src/agent-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export * from "./agent-client.js";
2
+ export * from "./payment-engine.js";
3
+ export { RubiconClient as Rubicon, RubiconClient as default } from "./agent-client.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,aAAa,IAAI,OAAO,EAAE,aAAa,IAAI,OAAO,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from "./agent-client.js";
2
+ export * from "./payment-engine.js";
3
+ export { RubiconClient as Rubicon, RubiconClient as default } from "./agent-client.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,aAAa,IAAI,OAAO,EAAE,aAAa,IAAI,OAAO,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,32 @@
1
+ import type { StartSessionResponse, StreamPaymentRequest } from "@rubicon-caliga/core";
2
+ import { type GatewayClientConfig } from "@circle-fin/x402-batching/client";
3
+ /**
4
+ * Produces the payment payload for exactly one word. Called once per word by the
5
+ * SDK's read loop — application developers never assemble payments themselves.
6
+ */
7
+ export interface AgentPaymentEngine {
8
+ createWordPayment(session: StartSessionResponse): Promise<StreamPaymentRequest>;
9
+ }
10
+ /**
11
+ * Development engine. Declares the one-word amount without settling real funds,
12
+ * for use against a dev-mode gateway. NOT for production.
13
+ */
14
+ export declare class StaticPaymentEngine implements AgentPaymentEngine {
15
+ private readonly network;
16
+ constructor(network?: string);
17
+ createWordPayment(session: StartSessionResponse): Promise<StreamPaymentRequest>;
18
+ }
19
+ export type CircleGatewayPaymentEngineOptions = GatewayClientConfig;
20
+ /**
21
+ * Circle/x402 engine. Signs the gateway's one-word `paymentRequired` terms.
22
+ * Circle may batch settlement internally, but each signed payload corresponds to
23
+ * exactly one word.
24
+ */
25
+ export declare class CircleGatewayPaymentEngine implements AgentPaymentEngine {
26
+ private readonly options;
27
+ private readonly client;
28
+ private readonly account;
29
+ constructor(options: CircleGatewayPaymentEngineOptions);
30
+ createWordPayment(session: StartSessionResponse): Promise<StreamPaymentRequest>;
31
+ }
32
+ //# sourceMappingURL=payment-engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-engine.d.ts","sourceRoot":"","sources":["../src/payment-engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAEvF,OAAO,EAAuB,KAAK,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAIjG;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,iBAAiB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;CACjF;AAED;;;GAGG;AACH,qBAAa,mBAAoB,YAAW,kBAAkB;IAChD,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,SAAmB;IAEjD,iBAAiB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC;CAWtF;AAED,MAAM,MAAM,iCAAiC,GAAG,mBAAmB,CAAC;AAEpE;;;;GAIG;AACH,qBAAa,0BAA2B,YAAW,kBAAkB;IAIvD,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoB;IAC3C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyC;gBAEpC,OAAO,EAAE,iCAAiC;IAajE,iBAAiB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC;CAQtF"}
@@ -0,0 +1,57 @@
1
+ import { x402Client } from "@x402/core/client";
2
+ import { registerBatchScheme } from "@circle-fin/x402-batching/client";
3
+ import { ExactEvmScheme } from "@x402/evm/exact/client";
4
+ import { privateKeyToAccount } from "viem/accounts";
5
+ /**
6
+ * Development engine. Declares the one-word amount without settling real funds,
7
+ * for use against a dev-mode gateway. NOT for production.
8
+ */
9
+ export class StaticPaymentEngine {
10
+ network;
11
+ constructor(network = "eip155:5042002") {
12
+ this.network = network;
13
+ }
14
+ async createWordPayment(session) {
15
+ return {
16
+ paymentPayload: {
17
+ scheme: "development-static",
18
+ network: this.network,
19
+ sessionId: session.sessionId,
20
+ amountAtomic: session.wordPaymentAtomic,
21
+ meteringUnit: "word",
22
+ },
23
+ };
24
+ }
25
+ }
26
+ /**
27
+ * Circle/x402 engine. Signs the gateway's one-word `paymentRequired` terms.
28
+ * Circle may batch settlement internally, but each signed payload corresponds to
29
+ * exactly one word.
30
+ */
31
+ export class CircleGatewayPaymentEngine {
32
+ options;
33
+ client = new x402Client();
34
+ account;
35
+ constructor(options) {
36
+ this.options = options;
37
+ this.account = privateKeyToAccount(this.options.privateKey);
38
+ // Recommended buyer integration (Circle x402 buyer how-to): register the
39
+ // gasless batched scheme with an `exact` fallback. `registerBatchScheme`
40
+ // wires a CompositeEvmScheme that uses Gateway batching when the seller
41
+ // supports it and falls back to a standard EIP-3009 `exact` payment
42
+ // otherwise — no per-request routing logic needed.
43
+ registerBatchScheme(this.client, {
44
+ signer: this.account,
45
+ fallbackScheme: new ExactEvmScheme(this.account),
46
+ });
47
+ }
48
+ async createWordPayment(session) {
49
+ if (!session.paymentRequired) {
50
+ throw new Error("Session did not include an x402 one-word payment requirement");
51
+ }
52
+ return {
53
+ paymentPayload: await this.client.createPaymentPayload(session.paymentRequired),
54
+ };
55
+ }
56
+ }
57
+ //# sourceMappingURL=payment-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-engine.js","sourceRoot":"","sources":["../src/payment-engine.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAA4B,MAAM,kCAAkC,CAAC;AACjG,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAUpD;;;GAGG;AACH,MAAM,OAAO,mBAAmB;IACD;IAA7B,YAA6B,UAAU,gBAAgB;QAA1B,YAAO,GAAP,OAAO,CAAmB;IAAG,CAAC;IAE3D,KAAK,CAAC,iBAAiB,CAAC,OAA6B;QACnD,OAAO;YACL,cAAc,EAAE;gBACd,MAAM,EAAE,oBAAoB;gBAC5B,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,YAAY,EAAE,OAAO,CAAC,iBAAiB;gBACvC,YAAY,EAAE,MAAM;aACrB;SACF,CAAC;IACJ,CAAC;CACF;AAID;;;;GAIG;AACH,MAAM,OAAO,0BAA0B;IAIR;IAHZ,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;IAC1B,OAAO,CAAyC;IAEjE,YAA6B,OAA0C;QAA1C,YAAO,GAAP,OAAO,CAAmC;QACrE,IAAI,CAAC,OAAO,GAAG,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC5D,yEAAyE;QACzE,yEAAyE;QACzE,wEAAwE;QACxE,oEAAoE;QACpE,mDAAmD;QACnD,mBAAmB,CAAC,IAAI,CAAC,MAAM,EAAE;YAC/B,MAAM,EAAE,IAAI,CAAC,OAAO;YACpB,cAAc,EAAE,IAAI,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC;SACjD,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,iBAAiB,CAAC,OAA6B;QACnD,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CAAC,8DAA8D,CAAC,CAAC;QAClF,CAAC;QACD,OAAO;YACL,cAAc,EAAE,MAAM,IAAI,CAAC,MAAM,CAAC,oBAAoB,CAAC,OAAO,CAAC,eAAwB,CAAC;SACzF,CAAC;IACJ,CAAC;CACF"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@rubicon-caliga/agent-sdk",
3
+ "version": "0.1.0",
4
+ "description": "Client SDK for autonomous agents consuming per-word article streams via Rubicon x402.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "src"
18
+ ],
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/michaelzoub/rubicon.git",
22
+ "directory": "packages/agent-sdk"
23
+ },
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "scripts": {
28
+ "build": "tsc -p tsconfig.json",
29
+ "lint": "tsc -p tsconfig.json --noEmit",
30
+ "typecheck": "tsc -p tsconfig.json --noEmit",
31
+ "test": "node --test dist/**/*.test.js",
32
+ "prepublishOnly": "pnpm run build"
33
+ },
34
+ "dependencies": {
35
+ "@rubicon-caliga/core": "^0.1.0",
36
+ "@circle-fin/x402-batching": "^3.1.2",
37
+ "@x402/core": "^2.15.0",
38
+ "@x402/evm": "^2.15.0",
39
+ "eventsource": "^4.0.0",
40
+ "viem": "^2.52.2"
41
+ }
42
+ }
@@ -0,0 +1,383 @@
1
+ import { EventSource } from "eventsource";
2
+ import type {
3
+ ArticleSummary,
4
+ Budget,
5
+ GatewayEvent,
6
+ SendConversationMessageResponse,
7
+ StartConversationResponse,
8
+ StartSessionRequest,
9
+ StartSessionResponse,
10
+ StreamPaymentRequest,
11
+ StreamPaymentResponse,
12
+ WordPaymentReceipt,
13
+ } from "@rubicon-caliga/core";
14
+ import { StaticPaymentEngine, type AgentPaymentEngine } from "./payment-engine.js";
15
+
16
+ export interface RubiconClientOptions {
17
+ baseUrl?: string;
18
+ paymentEngine?: AgentPaymentEngine;
19
+ /** Optional auth header value for the public agent API, e.g. "Bearer <token>". */
20
+ authorization?: string;
21
+ fetch?: typeof fetch;
22
+ }
23
+
24
+ export interface RepositoryResponse {
25
+ repository: "articles";
26
+ articles: ArticleSummary[];
27
+ }
28
+
29
+ export interface NavigationResponse {
30
+ article: ArticleSummary;
31
+ navigation: StartSessionResponse["navigation"];
32
+ }
33
+
34
+ export interface ReadReceipt {
35
+ sessionId: string;
36
+ articleId: string;
37
+ conversationId: string;
38
+ wordsRead: number;
39
+ amountPaidAtomic: `${bigint}`;
40
+ payments: WordPaymentReceipt[];
41
+ transactionHashes: string[];
42
+ text: string;
43
+ completed: boolean;
44
+ stopReason: "article_completed" | "stop_condition" | "budget_reached" | "max_words" | "aborted";
45
+ }
46
+
47
+ export type RubiconReadEvent =
48
+ | { type: "session.started"; session: StartSessionResponse }
49
+ | { type: "seller.message"; content: string; recommendedSectionId?: string }
50
+ | {
51
+ type: "article.word";
52
+ sequence: number;
53
+ word: string;
54
+ priceAtomic: `${bigint}`;
55
+ wordsRead: number;
56
+ amountPaidAtomic: `${bigint}`;
57
+ transactionHash?: string;
58
+ transactionHashes?: string[];
59
+ payment?: WordPaymentReceipt;
60
+ text: string;
61
+ }
62
+ | { type: "article.usage"; wordsPaid: number; wordsDelivered: number; paidAtomic: `${bigint}` }
63
+ | { type: "article.completed"; receipt: ReadReceipt }
64
+ | { type: "article.error"; message: string };
65
+
66
+ export interface ReadOptions {
67
+ articleId: string;
68
+ goal?: string;
69
+ sectionId?: string;
70
+ conversationId?: string;
71
+ /** Hard spend ceiling in atomic USDC. Equivalent to budget.maxAmountAtomic. */
72
+ maxSpendAtomic?: `${bigint}`;
73
+ budget?: Budget;
74
+ maxWords?: number;
75
+ /** Return true to stop reading once enough information has been collected. */
76
+ stopWhen?: (state: {
77
+ text: string;
78
+ wordsRead: number;
79
+ amountPaid: bigint;
80
+ }) => boolean | Promise<boolean>;
81
+ metadata?: Record<string, unknown>;
82
+ }
83
+
84
+ export interface RunOptions extends ReadOptions {
85
+ onEvent?: (event: RubiconReadEvent) => void | Promise<void>;
86
+ onWord?: (word: string, state: { text: string; wordsRead: number; amountPaidAtomic: `${bigint}` }) => void | Promise<void>;
87
+ }
88
+
89
+ /**
90
+ * High-level buyer-agent client for Rubicon. `read()` runs the entire
91
+ * pay -> word -> usage loop one word at a time until a stop condition is met,
92
+ * so application developers never send a payment for every word themselves.
93
+ */
94
+ export class RubiconClient {
95
+ private readonly fetcher: typeof fetch;
96
+ private readonly baseUrl: string;
97
+ private readonly paymentEngine: AgentPaymentEngine;
98
+
99
+ constructor(private readonly options: RubiconClientOptions) {
100
+ this.fetcher = options.fetch ?? fetch;
101
+ this.baseUrl = options.baseUrl ?? "http://localhost:8787";
102
+ this.paymentEngine = options.paymentEngine ?? new StaticPaymentEngine();
103
+ }
104
+
105
+ async getRepository(): Promise<RepositoryResponse> {
106
+ return this.readJson(await this.fetcher(`${this.baseUrl}/v1/repository`, { headers: this.headers() }));
107
+ }
108
+
109
+ async getNavigation(articleId: string, goal?: string): Promise<NavigationResponse> {
110
+ const url = new URL(`${this.baseUrl}/v1/articles/${articleId}/navigation`);
111
+ if (goal) {
112
+ url.searchParams.set("goal", goal);
113
+ }
114
+ return this.readJson(await this.fetcher(url.toString(), { headers: this.headers() }));
115
+ }
116
+
117
+ async startConversation(input: {
118
+ articleId: string;
119
+ goal?: string;
120
+ message?: string;
121
+ }): Promise<StartConversationResponse> {
122
+ return this.readJson(
123
+ await this.fetcher(`${this.baseUrl}/v1/seller-agent/conversations`, {
124
+ method: "POST",
125
+ headers: this.headers({ "content-type": "application/json" }),
126
+ body: JSON.stringify(input),
127
+ }),
128
+ );
129
+ }
130
+
131
+ async sendConversationMessage(
132
+ conversationId: string,
133
+ message: string,
134
+ ): Promise<SendConversationMessageResponse> {
135
+ return this.readJson(
136
+ await this.fetcher(
137
+ `${this.baseUrl}/v1/seller-agent/conversations/${conversationId}/messages`,
138
+ {
139
+ method: "POST",
140
+ headers: this.headers({ "content-type": "application/json" }),
141
+ body: JSON.stringify({ message }),
142
+ },
143
+ ),
144
+ );
145
+ }
146
+
147
+ async startSession(request: StartSessionRequest): Promise<StartSessionResponse> {
148
+ return this.readJson(
149
+ await this.fetcher(`${this.baseUrl}/v1/sessions`, {
150
+ method: "POST",
151
+ headers: this.headers({ "content-type": "application/json" }),
152
+ body: JSON.stringify(request),
153
+ }),
154
+ );
155
+ }
156
+
157
+ async payForWord(sessionId: string, payment: StreamPaymentRequest): Promise<StreamPaymentResponse> {
158
+ const response = await this.fetcher(`${this.baseUrl}/v1/sessions/${sessionId}/payments`, {
159
+ method: "POST",
160
+ headers: this.headers({ "content-type": "application/json" }),
161
+ body: JSON.stringify(payment),
162
+ });
163
+ if (!response.ok) {
164
+ throw new Error(`Word payment rejected: ${response.status} ${await response.text()}`);
165
+ }
166
+ return response.json() as Promise<StreamPaymentResponse>;
167
+ }
168
+
169
+ async abort(sessionId: string, reason = "agent_cancelled"): Promise<void> {
170
+ await this.fetcher(`${this.baseUrl}/v1/sessions/${sessionId}/abort`, {
171
+ method: "POST",
172
+ headers: this.headers({ "content-type": "application/json" }),
173
+ body: JSON.stringify({ reason }),
174
+ });
175
+ }
176
+
177
+ /** Subscribe to raw word-level server-sent events for observation/logging. */
178
+ streamEvents(sessionId: string, onEvent: (event: GatewayEvent) => void): () => void {
179
+ const headers = this.headers();
180
+ const source = new EventSource(`${this.baseUrl}/v1/sessions/${sessionId}/events`, {
181
+ fetch: (input, init) =>
182
+ this.fetcher(input, {
183
+ ...init,
184
+ headers: {
185
+ ...Object.fromEntries(new Headers(init?.headers).entries()),
186
+ ...headers,
187
+ },
188
+ }),
189
+ });
190
+ source.onmessage = (message) => onEvent(JSON.parse(message.data) as GatewayEvent);
191
+ source.onerror = () => source.close();
192
+ return () => source.close();
193
+ }
194
+
195
+ /**
196
+ * Simplest path for agents: run the whole paid read and return the receipt.
197
+ * Use callbacks only when the caller wants live progress.
198
+ */
199
+ async run(options: RunOptions): Promise<ReadReceipt> {
200
+ let receipt: ReadReceipt | undefined;
201
+ for await (const event of this.read(options)) {
202
+ await options.onEvent?.(event);
203
+ if (event.type === "article.word") {
204
+ await options.onWord?.(event.word, {
205
+ text: event.text,
206
+ wordsRead: event.wordsRead,
207
+ amountPaidAtomic: event.amountPaidAtomic,
208
+ });
209
+ }
210
+ if (event.type === "article.completed") {
211
+ receipt = event.receipt;
212
+ }
213
+ }
214
+ if (!receipt) {
215
+ throw new Error("Rubicon read finished without a receipt");
216
+ }
217
+ return receipt;
218
+ }
219
+
220
+ /**
221
+ * Read an article one paid word at a time. Yields seller messages, paid words,
222
+ * running usage, and a final completion event carrying the receipt.
223
+ */
224
+ async *read(options: ReadOptions): AsyncGenerator<RubiconReadEvent, ReadReceipt> {
225
+ const budget: Budget =
226
+ options.budget ??
227
+ (options.maxSpendAtomic
228
+ ? { currency: "USDC", maxAmountAtomic: options.maxSpendAtomic }
229
+ : (() => {
230
+ throw new Error("read() requires maxSpendAtomic or budget");
231
+ })());
232
+ const budgetAtomic = BigInt(budget.maxAmountAtomic);
233
+
234
+ // Let the seller agent recommend a starting section if the buyer has a goal
235
+ // and did not pick one explicitly.
236
+ let conversationId = options.conversationId;
237
+ let sectionId = options.sectionId;
238
+ if (options.goal && !conversationId) {
239
+ const conversation = await this.startConversation({
240
+ articleId: options.articleId,
241
+ goal: options.goal,
242
+ message: options.goal,
243
+ });
244
+ conversationId = conversation.conversationId;
245
+ const seller = conversation.messages.find((message) => message.role === "seller");
246
+ if (seller) {
247
+ yield {
248
+ type: "seller.message",
249
+ content: seller.content,
250
+ recommendedSectionId: seller.recommendedSectionId,
251
+ };
252
+ sectionId = sectionId ?? seller.recommendedSectionId;
253
+ }
254
+ }
255
+
256
+ const session = await this.startSession({
257
+ articleId: options.articleId,
258
+ goal: options.goal,
259
+ conversationId,
260
+ sectionId,
261
+ budget,
262
+ metadata: options.metadata,
263
+ });
264
+ yield { type: "session.started", session };
265
+
266
+ const wordPaymentAtomic = BigInt(session.wordPaymentAtomic);
267
+ let text = "";
268
+ let wordsRead = 0;
269
+ let amountPaid = 0n;
270
+ const transactionHashes: string[] = [];
271
+ const payments: WordPaymentReceipt[] = [];
272
+ let stopReason: ReadReceipt["stopReason"] = "article_completed";
273
+ let completed = false;
274
+
275
+ const makeReceipt = (): ReadReceipt => ({
276
+ sessionId: session.sessionId,
277
+ articleId: session.article.articleId,
278
+ conversationId: session.conversationId,
279
+ wordsRead,
280
+ amountPaidAtomic: `${amountPaid}`,
281
+ payments: [...payments],
282
+ transactionHashes: [...transactionHashes],
283
+ text,
284
+ completed,
285
+ stopReason,
286
+ });
287
+
288
+ while (true) {
289
+ if (options.maxWords !== undefined && wordsRead >= options.maxWords) {
290
+ stopReason = "max_words";
291
+ break;
292
+ }
293
+ if (amountPaid + wordPaymentAtomic > budgetAtomic) {
294
+ stopReason = "budget_reached";
295
+ break;
296
+ }
297
+ if (await options.stopWhen?.({ text, wordsRead, amountPaid })) {
298
+ stopReason = "stop_condition";
299
+ break;
300
+ }
301
+
302
+ const payment = await this.paymentEngine.createWordPayment(session);
303
+ // Idempotency key ties this payment to the specific next word; safe retries.
304
+ const idempotencyKey = `${session.sessionId}:${wordsRead}`;
305
+ let result: StreamPaymentResponse;
306
+ try {
307
+ result = await this.payForWord(session.sessionId, { ...payment, idempotencyKey });
308
+ } catch (error) {
309
+ yield { type: "article.error", message: error instanceof Error ? error.message : String(error) };
310
+ stopReason = "aborted";
311
+ break;
312
+ }
313
+
314
+ if (result.completed && result.word === "") {
315
+ // Article exhausted with no further word to deliver.
316
+ completed = true;
317
+ stopReason = "article_completed";
318
+ const receipt = makeReceipt();
319
+ yield { type: "article.completed", receipt };
320
+ return receipt;
321
+ }
322
+
323
+ wordsRead = result.wordsDelivered;
324
+ amountPaid = BigInt(result.paidAtomic);
325
+ if (result.payment) {
326
+ payments.push(result.payment);
327
+ }
328
+ transactionHashes.push(...(result.transactionHashes ?? (result.transactionHash ? [result.transactionHash] : [])));
329
+ text = text ? `${text} ${result.word}` : result.word;
330
+
331
+ yield {
332
+ type: "article.word",
333
+ sequence: result.sequence,
334
+ word: result.word,
335
+ priceAtomic: result.priceAtomic,
336
+ wordsRead,
337
+ amountPaidAtomic: `${amountPaid}`,
338
+ transactionHash: result.transactionHash,
339
+ transactionHashes: result.transactionHashes,
340
+ payment: result.payment,
341
+ text,
342
+ };
343
+ yield {
344
+ type: "article.usage",
345
+ wordsPaid: result.wordsPaid,
346
+ wordsDelivered: result.wordsDelivered,
347
+ paidAtomic: result.paidAtomic,
348
+ };
349
+
350
+ if (result.completed) {
351
+ completed = true;
352
+ stopReason = "article_completed";
353
+ const receipt = makeReceipt();
354
+ yield { type: "article.completed", receipt };
355
+ return receipt;
356
+ }
357
+ }
358
+
359
+ // Stopped early by the buyer: abort the session so no further words are owed.
360
+ await this.abort(session.sessionId, stopReason).catch(() => {});
361
+ const receipt = makeReceipt();
362
+ yield { type: "article.completed", receipt };
363
+ return receipt;
364
+ }
365
+
366
+ private headers(extra: Record<string, string> = {}): Record<string, string> {
367
+ const headers: Record<string, string> = { ...extra };
368
+ if (this.options.authorization) {
369
+ headers.authorization = this.options.authorization;
370
+ }
371
+ return headers;
372
+ }
373
+
374
+ private async readJson<T>(response: Response): Promise<T> {
375
+ if (!response.ok) {
376
+ throw new Error(`Gateway request failed: ${response.status} ${await response.text()}`);
377
+ }
378
+ return response.json() as Promise<T>;
379
+ }
380
+ }
381
+
382
+ /** Backwards-compatible alias. */
383
+ export const AgentClient = RubiconClient;
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./agent-client.js";
2
+ export * from "./payment-engine.js";
3
+ export { RubiconClient as Rubicon, RubiconClient as default } from "./agent-client.js";
@@ -0,0 +1,67 @@
1
+ import type { StartSessionResponse, StreamPaymentRequest } from "@rubicon-caliga/core";
2
+ import { x402Client } from "@x402/core/client";
3
+ import { registerBatchScheme, type GatewayClientConfig } from "@circle-fin/x402-batching/client";
4
+ import { ExactEvmScheme } from "@x402/evm/exact/client";
5
+ import { privateKeyToAccount } from "viem/accounts";
6
+
7
+ /**
8
+ * Produces the payment payload for exactly one word. Called once per word by the
9
+ * SDK's read loop — application developers never assemble payments themselves.
10
+ */
11
+ export interface AgentPaymentEngine {
12
+ createWordPayment(session: StartSessionResponse): Promise<StreamPaymentRequest>;
13
+ }
14
+
15
+ /**
16
+ * Development engine. Declares the one-word amount without settling real funds,
17
+ * for use against a dev-mode gateway. NOT for production.
18
+ */
19
+ export class StaticPaymentEngine implements AgentPaymentEngine {
20
+ constructor(private readonly network = "eip155:5042002") {}
21
+
22
+ async createWordPayment(session: StartSessionResponse): Promise<StreamPaymentRequest> {
23
+ return {
24
+ paymentPayload: {
25
+ scheme: "development-static",
26
+ network: this.network,
27
+ sessionId: session.sessionId,
28
+ amountAtomic: session.wordPaymentAtomic,
29
+ meteringUnit: "word",
30
+ },
31
+ };
32
+ }
33
+ }
34
+
35
+ export type CircleGatewayPaymentEngineOptions = GatewayClientConfig;
36
+
37
+ /**
38
+ * Circle/x402 engine. Signs the gateway's one-word `paymentRequired` terms.
39
+ * Circle may batch settlement internally, but each signed payload corresponds to
40
+ * exactly one word.
41
+ */
42
+ export class CircleGatewayPaymentEngine implements AgentPaymentEngine {
43
+ private readonly client = new x402Client();
44
+ private readonly account: ReturnType<typeof privateKeyToAccount>;
45
+
46
+ constructor(private readonly options: CircleGatewayPaymentEngineOptions) {
47
+ this.account = privateKeyToAccount(this.options.privateKey);
48
+ // Recommended buyer integration (Circle x402 buyer how-to): register the
49
+ // gasless batched scheme with an `exact` fallback. `registerBatchScheme`
50
+ // wires a CompositeEvmScheme that uses Gateway batching when the seller
51
+ // supports it and falls back to a standard EIP-3009 `exact` payment
52
+ // otherwise — no per-request routing logic needed.
53
+ registerBatchScheme(this.client, {
54
+ signer: this.account,
55
+ fallbackScheme: new ExactEvmScheme(this.account),
56
+ });
57
+ }
58
+
59
+ async createWordPayment(session: StartSessionResponse): Promise<StreamPaymentRequest> {
60
+ if (!session.paymentRequired) {
61
+ throw new Error("Session did not include an x402 one-word payment requirement");
62
+ }
63
+ return {
64
+ paymentPayload: await this.client.createPaymentPayload(session.paymentRequired as never),
65
+ };
66
+ }
67
+ }