@rubicon-caliga/agent-sdk 0.1.2 → 0.1.3
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 +21 -20
- package/dist/agent-client.d.ts +20 -5
- package/dist/agent-client.d.ts.map +1 -1
- package/dist/agent-client.js +82 -4
- package/dist/agent-client.js.map +1 -1
- package/dist/agent-client.test.js +81 -0
- package/dist/agent-client.test.js.map +1 -1
- package/dist/circle-agent-wallet.d.ts +6 -6
- package/dist/circle-agent-wallet.d.ts.map +1 -1
- package/dist/circle-agent-wallet.js +8 -8
- package/dist/circle-agent-wallet.js.map +1 -1
- package/dist/circle-cli-gateway-payment.d.ts +8 -4
- package/dist/circle-cli-gateway-payment.d.ts.map +1 -1
- package/dist/circle-cli-gateway-payment.js +43 -5
- package/dist/circle-cli-gateway-payment.js.map +1 -1
- package/dist/payment-engine.d.ts +17 -5
- package/dist/payment-engine.d.ts.map +1 -1
- package/dist/payment-engine.js +30 -2
- package/dist/payment-engine.js.map +1 -1
- package/package.json +2 -2
- package/src/agent-client.test.ts +87 -0
- package/src/agent-client.ts +93 -4
- package/src/circle-agent-wallet.ts +10 -12
- package/src/circle-cli-gateway-payment.ts +50 -5
- package/src/payment-engine.ts +39 -5
|
@@ -6,10 +6,10 @@ import { ExactEvmScheme } from "@x402/evm/exact/client";
|
|
|
6
6
|
import { serializeTypedData, toEip712Payload } from "./circle-agent-wallet.js";
|
|
7
7
|
const execFileAsync = promisify(execFile);
|
|
8
8
|
/**
|
|
9
|
-
* Circle CLI / Agent Wallet payment engine. It
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* Circle CLI / Agent Wallet payment engine. It delegates EIP-712 signing to
|
|
10
|
+
* `circle wallet sign typed-data`, so agents never need raw private keys or
|
|
11
|
+
* hand-built Circle / Arc payloads. Current gateways may call the legacy
|
|
12
|
+
* one-word method as a chunk-compatibility path.
|
|
13
13
|
*/
|
|
14
14
|
export class CircleCliGatewayPaymentEngine {
|
|
15
15
|
x402 = new x402Client();
|
|
@@ -29,13 +29,29 @@ export class CircleCliGatewayPaymentEngine {
|
|
|
29
29
|
}
|
|
30
30
|
async createWordPayment(session) {
|
|
31
31
|
if (!session.paymentRequired) {
|
|
32
|
-
throw new Error("Session did not include
|
|
32
|
+
throw new Error("Session did not include a legacy x402 payment requirement");
|
|
33
33
|
}
|
|
34
34
|
await this.signer.ensureAddress();
|
|
35
35
|
return {
|
|
36
36
|
paymentPayload: await this.x402.createPaymentPayload(session.paymentRequired),
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
|
+
async createChunkPayment(session, input) {
|
|
40
|
+
if (!session.paymentRequired) {
|
|
41
|
+
throw new Error("Session did not include an x402 payment requirement");
|
|
42
|
+
}
|
|
43
|
+
await this.signer.ensureAddress();
|
|
44
|
+
const amountAtomic = BigInt(session.wordPaymentAtomic) * BigInt(input.maxWords);
|
|
45
|
+
return {
|
|
46
|
+
paymentPayload: await this.x402.createPaymentPayload(withChunkRequirement(session.paymentRequired, {
|
|
47
|
+
amountAtomic: `${amountAtomic}`,
|
|
48
|
+
maxWords: input.maxWords,
|
|
49
|
+
nextSequence: input.nextSequence,
|
|
50
|
+
sessionId: session.sessionId,
|
|
51
|
+
})),
|
|
52
|
+
maxWords: input.maxWords,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
39
55
|
}
|
|
40
56
|
export class CircleCliGatewaySigner {
|
|
41
57
|
options;
|
|
@@ -198,6 +214,28 @@ function findString(value, keys) {
|
|
|
198
214
|
}
|
|
199
215
|
return undefined;
|
|
200
216
|
}
|
|
217
|
+
function withChunkRequirement(paymentRequired, chunk) {
|
|
218
|
+
const source = paymentRequired;
|
|
219
|
+
if (!Array.isArray(source.accepts)) {
|
|
220
|
+
return paymentRequired;
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
...paymentRequired,
|
|
224
|
+
accepts: source.accepts.map((accept) => ({
|
|
225
|
+
...accept,
|
|
226
|
+
amount: chunk.amountAtomic,
|
|
227
|
+
extra: {
|
|
228
|
+
...(accept.extra ?? {}),
|
|
229
|
+
amountAtomic: chunk.amountAtomic,
|
|
230
|
+
maxWords: chunk.maxWords,
|
|
231
|
+
sequence: chunk.nextSequence,
|
|
232
|
+
authorizationMode: "chunk",
|
|
233
|
+
nonce: `${chunk.sessionId}:${chunk.nextSequence}:${chunk.maxWords}`,
|
|
234
|
+
idempotencyKey: `${chunk.sessionId}:${chunk.nextSequence}:${chunk.maxWords}`,
|
|
235
|
+
},
|
|
236
|
+
})),
|
|
237
|
+
};
|
|
238
|
+
}
|
|
201
239
|
function parseJson(value) {
|
|
202
240
|
try {
|
|
203
241
|
return JSON.parse(value);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"circle-cli-gateway-payment.js","sourceRoot":"","sources":["../src/circle-cli-gateway-payment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAE/E,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAyC1C;;;;;GAKG;AACH,MAAM,OAAO,6BAA6B;IACvB,IAAI,GAAG,IAAI,UAAU,EAAE,CAAC;IACxB,MAAM,CAAyB;IAEhD,YAAY,UAAgD,EAAE;QAC5D,IAAI,CAAC,MAAM,GAAG,IAAI,sBAAsB,CAAC;YACvC,kBAAkB,EAAE,OAAO,CAAC,kBAAkB,IAAI,OAAO,CAAC,aAAa;YACvE,kBAAkB,EAAE,OAAO,CAAC,kBAAkB,IAAI,OAAO,CAAC,UAAU;YACpE,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,aAAa;YACrC,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,QAAQ;YACpC,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,YAAY;SACvC,CAAC,CAAC;QACH,mBAAmB,CAAC,IAAI,CAAC,IAAI,EAAE;YAC7B,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,cAAc,EAAE,IAAI,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC;SAChD,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,iBAAiB,CAAC,OAA6B;QACnD,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"circle-cli-gateway-payment.js","sourceRoot":"","sources":["../src/circle-cli-gateway-payment.ts"],"names":[],"mappings":"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"}
|
package/dist/payment-engine.d.ts
CHANGED
|
@@ -1,18 +1,30 @@
|
|
|
1
|
-
import type { StartSessionResponse, StreamPaymentRequest } from "@rubicon-caliga/core";
|
|
1
|
+
import type { StartSessionResponse, StreamAuthorizationRequest, StreamPaymentRequest } from "@rubicon-caliga/core";
|
|
2
2
|
/**
|
|
3
|
-
* Produces
|
|
4
|
-
*
|
|
3
|
+
* Produces Circle / Arc authorization payloads for Rubicon reads. Preferred
|
|
4
|
+
* engines authorize a whole session once; fallback engines authorize chunks.
|
|
5
|
+
* Legacy one-word payloads remain supported for older gateways and tests.
|
|
5
6
|
*/
|
|
6
7
|
export interface AgentPaymentEngine {
|
|
8
|
+
createSessionAuthorization?(session: StartSessionResponse): Promise<StreamAuthorizationRequest>;
|
|
9
|
+
createChunkPayment?(session: StartSessionResponse, input: {
|
|
10
|
+
nextSequence: number;
|
|
11
|
+
maxWords: number;
|
|
12
|
+
}): Promise<StreamPaymentRequest>;
|
|
13
|
+
/** @deprecated Compatibility path for one-word x402 gateways. */
|
|
7
14
|
createWordPayment(session: StartSessionResponse): Promise<StreamPaymentRequest>;
|
|
8
15
|
}
|
|
9
16
|
/**
|
|
10
|
-
* Development engine. Declares the
|
|
11
|
-
* for use against a dev-mode gateway. NOT for production.
|
|
17
|
+
* Development engine. Declares the authorized amount without settling real
|
|
18
|
+
* funds, for use against a dev-mode gateway. NOT for production.
|
|
12
19
|
*/
|
|
13
20
|
export declare class StaticPaymentEngine implements AgentPaymentEngine {
|
|
14
21
|
private readonly network;
|
|
15
22
|
constructor(network?: string);
|
|
23
|
+
createSessionAuthorization(session: StartSessionResponse): Promise<StreamAuthorizationRequest>;
|
|
24
|
+
createChunkPayment(session: StartSessionResponse, input: {
|
|
25
|
+
nextSequence: number;
|
|
26
|
+
maxWords: number;
|
|
27
|
+
}): Promise<StreamPaymentRequest>;
|
|
16
28
|
createWordPayment(session: StartSessionResponse): Promise<StreamPaymentRequest>;
|
|
17
29
|
}
|
|
18
30
|
//# sourceMappingURL=payment-engine.d.ts.map
|
|
@@ -1 +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;
|
|
1
|
+
{"version":3,"file":"payment-engine.d.ts","sourceRoot":"","sources":["../src/payment-engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAEnH;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,0BAA0B,CAAC,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAAC;IAChG,kBAAkB,CAAC,CAAC,OAAO,EAAE,oBAAoB,EAAE,KAAK,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACrI,iEAAiE;IACjE,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,0BAA0B,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,0BAA0B,CAAC;IAa9F,kBAAkB,CAAC,OAAO,EAAE,oBAAoB,EAAE,KAAK,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAiBnI,iBAAiB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC;CAWtF"}
|
package/dist/payment-engine.js
CHANGED
|
@@ -1,12 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Development engine. Declares the
|
|
3
|
-
* for use against a dev-mode gateway. NOT for production.
|
|
2
|
+
* Development engine. Declares the authorized amount without settling real
|
|
3
|
+
* funds, for use against a dev-mode gateway. NOT for production.
|
|
4
4
|
*/
|
|
5
5
|
export class StaticPaymentEngine {
|
|
6
6
|
network;
|
|
7
7
|
constructor(network = "eip155:5042002") {
|
|
8
8
|
this.network = network;
|
|
9
9
|
}
|
|
10
|
+
async createSessionAuthorization(session) {
|
|
11
|
+
return {
|
|
12
|
+
authorizationPayload: {
|
|
13
|
+
scheme: "development-static",
|
|
14
|
+
network: this.network,
|
|
15
|
+
sessionId: session.sessionId,
|
|
16
|
+
amountAtomic: session.authorizationRequired?.maxAuthorizedAtomic ?? session.maxArticlePriceAtomic,
|
|
17
|
+
meteringUnit: "word",
|
|
18
|
+
authorizationMode: "session",
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
async createChunkPayment(session, input) {
|
|
23
|
+
const amountAtomic = BigInt(session.wordPaymentAtomic) * BigInt(input.maxWords);
|
|
24
|
+
return {
|
|
25
|
+
paymentPayload: {
|
|
26
|
+
scheme: "development-static",
|
|
27
|
+
network: this.network,
|
|
28
|
+
sessionId: session.sessionId,
|
|
29
|
+
amountAtomic: `${amountAtomic}`,
|
|
30
|
+
meteringUnit: "word",
|
|
31
|
+
authorizationMode: "chunk",
|
|
32
|
+
maxWords: input.maxWords,
|
|
33
|
+
nextSequence: input.nextSequence,
|
|
34
|
+
},
|
|
35
|
+
maxWords: input.maxWords,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
10
38
|
async createWordPayment(session) {
|
|
11
39
|
return {
|
|
12
40
|
paymentPayload: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"payment-engine.js","sourceRoot":"","sources":["../src/payment-engine.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"payment-engine.js","sourceRoot":"","sources":["../src/payment-engine.ts"],"names":[],"mappings":"AAcA;;;GAGG;AACH,MAAM,OAAO,mBAAmB;IACD;IAA7B,YAA6B,UAAU,gBAAgB;QAA1B,YAAO,GAAP,OAAO,CAAmB;IAAG,CAAC;IAE3D,KAAK,CAAC,0BAA0B,CAAC,OAA6B;QAC5D,OAAO;YACL,oBAAoB,EAAE;gBACpB,MAAM,EAAE,oBAAoB;gBAC5B,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,YAAY,EAAE,OAAO,CAAC,qBAAqB,EAAE,mBAAmB,IAAI,OAAO,CAAC,qBAAqB;gBACjG,YAAY,EAAE,MAAM;gBACpB,iBAAiB,EAAE,SAAS;aAC7B;SACF,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,kBAAkB,CAAC,OAA6B,EAAE,KAAiD;QACvG,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAChF,OAAO;YACL,cAAc,EAAE;gBACd,MAAM,EAAE,oBAAoB;gBAC5B,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,YAAY,EAAE,GAAG,YAAY,EAAE;gBAC/B,YAAY,EAAE,MAAM;gBACpB,iBAAiB,EAAE,OAAO;gBAC1B,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,YAAY,EAAE,KAAK,CAAC,YAAY;aACjC;YACD,QAAQ,EAAE,KAAK,CAAC,QAAQ;SACzB,CAAC;IACJ,CAAC;IAED,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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rubicon-caliga/agent-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Client SDK for autonomous agents consuming per-word article streams via Rubicon x402.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@x402/evm": "^2.15.0",
|
|
32
32
|
"eventsource": "^4.0.0",
|
|
33
33
|
"viem": "^2.52.2",
|
|
34
|
-
"@rubicon-caliga/core": "0.1.
|
|
34
|
+
"@rubicon-caliga/core": "0.1.1"
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
37
|
"build": "tsc -p tsconfig.json",
|
package/src/agent-client.test.ts
CHANGED
|
@@ -9,6 +9,18 @@ const paymentEngine: AgentPaymentEngine = {
|
|
|
9
9
|
},
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
const chunkPaymentEngine: AgentPaymentEngine = {
|
|
13
|
+
async createWordPayment() {
|
|
14
|
+
throw new Error("word fallback should not be used");
|
|
15
|
+
},
|
|
16
|
+
async createChunkPayment(_session, input) {
|
|
17
|
+
return {
|
|
18
|
+
paymentPayload: { amountAtomic: `${input.maxWords}` },
|
|
19
|
+
maxWords: input.maxWords,
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
12
24
|
test("run receipt preserves Gateway settlement receipt fields", async () => {
|
|
13
25
|
const fetcher = (async (input: Parameters<typeof fetch>[0]) => {
|
|
14
26
|
const url = String(input);
|
|
@@ -80,6 +92,67 @@ test("run receipt preserves Gateway settlement receipt fields", async () => {
|
|
|
80
92
|
assert.equal(receipt.network, "eip155:5042002");
|
|
81
93
|
});
|
|
82
94
|
|
|
95
|
+
test("run can consume chunk stream while preserving per-word receipt fields", async () => {
|
|
96
|
+
const fetcher = (async (input: Parameters<typeof fetch>[0], init?: Parameters<typeof fetch>[1]) => {
|
|
97
|
+
const url = String(input);
|
|
98
|
+
if (url.endsWith("/v1/sessions")) {
|
|
99
|
+
return jsonResponse({
|
|
100
|
+
sessionId: "session_1",
|
|
101
|
+
state: "active",
|
|
102
|
+
article: article(),
|
|
103
|
+
navigation: navigation(),
|
|
104
|
+
pricePerWordAtomic: "1",
|
|
105
|
+
maxArticlePriceAtomic: "10",
|
|
106
|
+
conversationId: "conversation_1",
|
|
107
|
+
wordPaymentAtomic: "1",
|
|
108
|
+
gatewayFeeBps: 0,
|
|
109
|
+
paymentRequired: { accepts: [{ amount: "1" }] },
|
|
110
|
+
expiresAt: "2026-06-18T12:00:00.000Z",
|
|
111
|
+
wordsPaid: 0,
|
|
112
|
+
wordsDelivered: 0,
|
|
113
|
+
paidAtomic: "0",
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
if (url.endsWith("/v1/sessions/session_1/stream")) {
|
|
117
|
+
const body = JSON.parse(String(init?.body)) as { maxWords: number };
|
|
118
|
+
assert.equal(body.maxWords, 3);
|
|
119
|
+
return jsonResponse({
|
|
120
|
+
accepted: true,
|
|
121
|
+
words: [
|
|
122
|
+
{ sequence: 0, word: "Rubicon", priceAtomic: "1", payment: payment(0, "Rubicon") },
|
|
123
|
+
{ sequence: 1, word: "streams", priceAtomic: "1", payment: payment(1, "streams") },
|
|
124
|
+
{ sequence: 2, word: "chunks", priceAtomic: "1", payment: payment(2, "chunks") },
|
|
125
|
+
],
|
|
126
|
+
text: "Rubicon streams chunks",
|
|
127
|
+
wordsPaid: 3,
|
|
128
|
+
wordsDelivered: 3,
|
|
129
|
+
paidAtomic: "3",
|
|
130
|
+
completed: true,
|
|
131
|
+
settlementIds: ["settlement_chunk"],
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
throw new Error(`Unexpected fetch: ${url}`);
|
|
135
|
+
}) as typeof fetch;
|
|
136
|
+
|
|
137
|
+
const client = new RubiconClient({
|
|
138
|
+
baseUrl: "http://rubicon.test",
|
|
139
|
+
paymentEngine: chunkPaymentEngine,
|
|
140
|
+
fetch: fetcher,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const receipt = await client.run({
|
|
144
|
+
articleId: "article_1",
|
|
145
|
+
maxSpendAtomic: "10",
|
|
146
|
+
chunkWords: 3,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
assert.equal(receipt.text, "Rubicon streams chunks");
|
|
150
|
+
assert.equal(receipt.wordsRead, 3);
|
|
151
|
+
assert.equal(receipt.amountPaidAtomic, "3");
|
|
152
|
+
assert.equal(receipt.payments.length, 3);
|
|
153
|
+
assert.deepEqual(receipt.settlementIds, ["settlement_chunk"]);
|
|
154
|
+
});
|
|
155
|
+
|
|
83
156
|
function jsonResponse(body: unknown): Response {
|
|
84
157
|
return new Response(JSON.stringify(body), {
|
|
85
158
|
status: 200,
|
|
@@ -116,3 +189,17 @@ function navigation() {
|
|
|
116
189
|
stopConditions: [],
|
|
117
190
|
};
|
|
118
191
|
}
|
|
192
|
+
|
|
193
|
+
function payment(sequence: number, word: string) {
|
|
194
|
+
return {
|
|
195
|
+
paymentId: `payment_${sequence}`,
|
|
196
|
+
sessionId: "session_1",
|
|
197
|
+
articleId: "article_1",
|
|
198
|
+
sequence,
|
|
199
|
+
meteringUnit: "word",
|
|
200
|
+
amountAtomic: "1",
|
|
201
|
+
currency: "USDC",
|
|
202
|
+
settledAt: "2026-06-18T12:00:00.000Z",
|
|
203
|
+
word,
|
|
204
|
+
};
|
|
205
|
+
}
|
package/src/agent-client.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
StartConversationResponse,
|
|
8
8
|
StartSessionRequest,
|
|
9
9
|
StartSessionResponse,
|
|
10
|
+
StreamChunkResponse,
|
|
10
11
|
StreamPaymentRequest,
|
|
11
12
|
StreamPaymentResponse,
|
|
12
13
|
WordPaymentReceipt,
|
|
@@ -63,6 +64,14 @@ export type RubiconReadEvent =
|
|
|
63
64
|
payment?: WordPaymentReceipt;
|
|
64
65
|
text: string;
|
|
65
66
|
}
|
|
67
|
+
| {
|
|
68
|
+
type: "article.chunk";
|
|
69
|
+
words: Array<{ sequence: number; word: string; priceAtomic: `${bigint}`; payment?: WordPaymentReceipt }>;
|
|
70
|
+
text: string;
|
|
71
|
+
wordsRead: number;
|
|
72
|
+
amountPaidAtomic: `${bigint}`;
|
|
73
|
+
completed: boolean;
|
|
74
|
+
}
|
|
66
75
|
| { type: "article.usage"; wordsPaid: number; wordsDelivered: number; paidAtomic: `${bigint}` }
|
|
67
76
|
| { type: "article.completed"; receipt: ReadReceipt }
|
|
68
77
|
| { type: "article.error"; message: string };
|
|
@@ -76,6 +85,8 @@ export interface ReadOptions {
|
|
|
76
85
|
maxSpendAtomic?: `${bigint}`;
|
|
77
86
|
budget?: Budget;
|
|
78
87
|
maxWords?: number;
|
|
88
|
+
/** Number of words to authorize and deliver per gateway round trip when supported. */
|
|
89
|
+
chunkWords?: number;
|
|
79
90
|
/** Return true to stop reading once enough information has been collected. */
|
|
80
91
|
stopWhen?: (state: {
|
|
81
92
|
text: string;
|
|
@@ -92,8 +103,8 @@ export interface RunOptions extends ReadOptions {
|
|
|
92
103
|
|
|
93
104
|
/**
|
|
94
105
|
* High-level buyer-agent client for Rubicon. `read()` runs the entire
|
|
95
|
-
*
|
|
96
|
-
*
|
|
106
|
+
* authorize -> word -> usage loop until a stop condition is met, so application
|
|
107
|
+
* developers never drive a payment flow for every word themselves.
|
|
97
108
|
*/
|
|
98
109
|
export class RubiconClient {
|
|
99
110
|
private readonly fetcher: typeof fetch;
|
|
@@ -170,6 +181,18 @@ export class RubiconClient {
|
|
|
170
181
|
return response.json() as Promise<StreamPaymentResponse>;
|
|
171
182
|
}
|
|
172
183
|
|
|
184
|
+
async streamChunk(sessionId: string, payment: StreamPaymentRequest): Promise<StreamChunkResponse> {
|
|
185
|
+
const response = await this.fetcher(`${this.baseUrl}/v1/sessions/${sessionId}/stream`, {
|
|
186
|
+
method: "POST",
|
|
187
|
+
headers: this.headers({ "content-type": "application/json" }),
|
|
188
|
+
body: JSON.stringify(payment),
|
|
189
|
+
});
|
|
190
|
+
if (!response.ok) {
|
|
191
|
+
throw new Error(`Chunk stream rejected: ${response.status} ${await response.text()}`);
|
|
192
|
+
}
|
|
193
|
+
return response.json() as Promise<StreamChunkResponse>;
|
|
194
|
+
}
|
|
195
|
+
|
|
173
196
|
async abort(sessionId: string, reason = "agent_cancelled"): Promise<void> {
|
|
174
197
|
await this.fetcher(`${this.baseUrl}/v1/sessions/${sessionId}/abort`, {
|
|
175
198
|
method: "POST",
|
|
@@ -222,8 +245,8 @@ export class RubiconClient {
|
|
|
222
245
|
}
|
|
223
246
|
|
|
224
247
|
/**
|
|
225
|
-
* Read an article
|
|
226
|
-
* running usage, and a final completion event carrying the receipt.
|
|
248
|
+
* Read an article with word-level metering. Yields seller messages, paid
|
|
249
|
+
* words, running usage, and a final completion event carrying the receipt.
|
|
227
250
|
*/
|
|
228
251
|
async *read(options: ReadOptions): AsyncGenerator<RubiconReadEvent, ReadReceipt> {
|
|
229
252
|
const budget: Budget =
|
|
@@ -274,6 +297,8 @@ export class RubiconClient {
|
|
|
274
297
|
const transactionHashes: string[] = [];
|
|
275
298
|
const settlementIds: string[] = [];
|
|
276
299
|
const payments: WordPaymentReceipt[] = [];
|
|
300
|
+
const chunkWords = normalizeChunkWords(options.chunkWords);
|
|
301
|
+
const useChunks = chunkWords > 1 && typeof this.paymentEngine.createChunkPayment === "function";
|
|
277
302
|
let stopReason: ReadReceipt["stopReason"] = "article_completed";
|
|
278
303
|
let completed = false;
|
|
279
304
|
|
|
@@ -308,6 +333,64 @@ export class RubiconClient {
|
|
|
308
333
|
break;
|
|
309
334
|
}
|
|
310
335
|
|
|
336
|
+
if (useChunks) {
|
|
337
|
+
const remainingWords = options.maxWords === undefined ? chunkWords : Math.max(0, options.maxWords - wordsRead);
|
|
338
|
+
const affordableWords = Number((budgetAtomic - amountPaid) / wordPaymentAtomic);
|
|
339
|
+
const maxWords = Math.max(1, Math.min(chunkWords, remainingWords || chunkWords, affordableWords));
|
|
340
|
+
const payment = await this.paymentEngine.createChunkPayment!(session, {
|
|
341
|
+
nextSequence: wordsRead,
|
|
342
|
+
maxWords,
|
|
343
|
+
});
|
|
344
|
+
const idempotencyKey = `${session.sessionId}:${wordsRead}:${maxWords}`;
|
|
345
|
+
let result: StreamChunkResponse;
|
|
346
|
+
try {
|
|
347
|
+
result = await this.streamChunk(session.sessionId, { ...payment, idempotencyKey, maxWords });
|
|
348
|
+
} catch (error) {
|
|
349
|
+
yield { type: "article.error", message: error instanceof Error ? error.message : String(error) };
|
|
350
|
+
stopReason = "aborted";
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
if (result.words.length === 0 && result.completed) {
|
|
354
|
+
completed = true;
|
|
355
|
+
stopReason = "article_completed";
|
|
356
|
+
const receipt = makeReceipt();
|
|
357
|
+
yield { type: "article.completed", receipt };
|
|
358
|
+
return receipt;
|
|
359
|
+
}
|
|
360
|
+
for (const entry of result.words) {
|
|
361
|
+
if (entry.payment) {
|
|
362
|
+
payments.push(entry.payment);
|
|
363
|
+
}
|
|
364
|
+
text = text ? `${text} ${entry.word}` : entry.word;
|
|
365
|
+
}
|
|
366
|
+
wordsRead = result.wordsDelivered;
|
|
367
|
+
amountPaid = BigInt(result.paidAtomic);
|
|
368
|
+
transactionHashes.push(...(result.transactionHashes ?? (result.transactionHash ? [result.transactionHash] : [])));
|
|
369
|
+
settlementIds.push(...(result.settlementIds ?? (result.settlementId ? [result.settlementId] : [])));
|
|
370
|
+
yield {
|
|
371
|
+
type: "article.chunk",
|
|
372
|
+
words: result.words,
|
|
373
|
+
text,
|
|
374
|
+
wordsRead,
|
|
375
|
+
amountPaidAtomic: `${amountPaid}`,
|
|
376
|
+
completed: result.completed,
|
|
377
|
+
};
|
|
378
|
+
yield {
|
|
379
|
+
type: "article.usage",
|
|
380
|
+
wordsPaid: result.wordsPaid,
|
|
381
|
+
wordsDelivered: result.wordsDelivered,
|
|
382
|
+
paidAtomic: result.paidAtomic,
|
|
383
|
+
};
|
|
384
|
+
if (result.completed) {
|
|
385
|
+
completed = true;
|
|
386
|
+
stopReason = "article_completed";
|
|
387
|
+
const receipt = makeReceipt();
|
|
388
|
+
yield { type: "article.completed", receipt };
|
|
389
|
+
return receipt;
|
|
390
|
+
}
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
|
|
311
394
|
const payment = await this.paymentEngine.createWordPayment(session);
|
|
312
395
|
// Idempotency key ties this payment to the specific next word; safe retries.
|
|
313
396
|
const idempotencyKey = `${session.sessionId}:${wordsRead}`;
|
|
@@ -391,3 +474,9 @@ export class RubiconClient {
|
|
|
391
474
|
|
|
392
475
|
/** Backwards-compatible alias. */
|
|
393
476
|
export const AgentClient = RubiconClient;
|
|
477
|
+
|
|
478
|
+
function normalizeChunkWords(chunkWords: number | undefined): number {
|
|
479
|
+
if (chunkWords === undefined) return 1;
|
|
480
|
+
if (!Number.isInteger(chunkWords) || chunkWords < 1) return 1;
|
|
481
|
+
return Math.min(chunkWords, 256);
|
|
482
|
+
}
|
|
@@ -2,10 +2,8 @@ import type { StartSessionResponse, StreamPaymentRequest } from "@rubicon-caliga
|
|
|
2
2
|
import { x402Client } from "@x402/core/client";
|
|
3
3
|
import { registerBatchScheme } from "@circle-fin/x402-batching/client";
|
|
4
4
|
import { ExactEvmScheme } from "@x402/evm/exact/client";
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
type CircleDeveloperControlledWalletsClient,
|
|
8
|
-
} from "@circle-fin/developer-controlled-wallets";
|
|
5
|
+
import * as CircleWallets from "@circle-fin/developer-controlled-wallets";
|
|
6
|
+
import type { CircleDeveloperControlledWalletsClient } from "@circle-fin/developer-controlled-wallets";
|
|
9
7
|
import type { AgentPaymentEngine } from "./payment-engine.js";
|
|
10
8
|
|
|
11
9
|
export interface CircleAgentWalletEngineOptions {
|
|
@@ -13,7 +11,7 @@ export interface CircleAgentWalletEngineOptions {
|
|
|
13
11
|
apiKey: string;
|
|
14
12
|
/** Entity secret registered for the Circle developer account. */
|
|
15
13
|
entitySecret: string;
|
|
16
|
-
/** The Agent Wallet that holds USDC and signs
|
|
14
|
+
/** The Agent Wallet that holds USDC and signs Circle / Arc authorizations. */
|
|
17
15
|
walletId: string;
|
|
18
16
|
/**
|
|
19
17
|
* The wallet's on-chain address. Optional — when omitted it is resolved once
|
|
@@ -80,7 +78,7 @@ class CircleAgentWalletSigner {
|
|
|
80
78
|
const res = await this.client.signTypedData({
|
|
81
79
|
walletId: this.walletId,
|
|
82
80
|
data: serializeTypedData(toEip712Payload(typed)),
|
|
83
|
-
memo: "Rubicon
|
|
81
|
+
memo: "Rubicon reading authorization",
|
|
84
82
|
});
|
|
85
83
|
const signature = res.data?.signature;
|
|
86
84
|
if (!signature) {
|
|
@@ -91,10 +89,10 @@ class CircleAgentWalletSigner {
|
|
|
91
89
|
}
|
|
92
90
|
|
|
93
91
|
/**
|
|
94
|
-
* Circle Agent Wallet engine. Signs
|
|
95
|
-
* custodial Circle Agent Wallet
|
|
96
|
-
*
|
|
97
|
-
*
|
|
92
|
+
* Circle Agent Wallet engine. Signs gateway authorization terms with a
|
|
93
|
+
* custodial Circle Agent Wallet so the buyer agent never handles a local
|
|
94
|
+
* signing key. Current gateways may still call the legacy one-word method as a
|
|
95
|
+
* chunk-compatibility path.
|
|
98
96
|
*/
|
|
99
97
|
export class CircleAgentWalletEngine implements AgentPaymentEngine {
|
|
100
98
|
private readonly x402 = new x402Client();
|
|
@@ -103,7 +101,7 @@ export class CircleAgentWalletEngine implements AgentPaymentEngine {
|
|
|
103
101
|
constructor(options: CircleAgentWalletEngineOptions) {
|
|
104
102
|
const client =
|
|
105
103
|
options.client ??
|
|
106
|
-
initiateDeveloperControlledWalletsClient({
|
|
104
|
+
CircleWallets.initiateDeveloperControlledWalletsClient({
|
|
107
105
|
apiKey: options.apiKey,
|
|
108
106
|
entitySecret: options.entitySecret,
|
|
109
107
|
...(options.baseUrl ? { baseUrl: options.baseUrl } : {}),
|
|
@@ -119,7 +117,7 @@ export class CircleAgentWalletEngine implements AgentPaymentEngine {
|
|
|
119
117
|
|
|
120
118
|
async createWordPayment(session: StartSessionResponse): Promise<StreamPaymentRequest> {
|
|
121
119
|
if (!session.paymentRequired) {
|
|
122
|
-
throw new Error("Session did not include
|
|
120
|
+
throw new Error("Session did not include a legacy x402 payment requirement");
|
|
123
121
|
}
|
|
124
122
|
// Resolve the wallet address up front so the synchronous `address` read
|
|
125
123
|
// inside createPaymentPayload sees a real value.
|
|
@@ -49,10 +49,10 @@ interface TypedDataRequest {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
|
-
* Circle CLI / Agent Wallet payment engine. It
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
52
|
+
* Circle CLI / Agent Wallet payment engine. It delegates EIP-712 signing to
|
|
53
|
+
* `circle wallet sign typed-data`, so agents never need raw private keys or
|
|
54
|
+
* hand-built Circle / Arc payloads. Current gateways may call the legacy
|
|
55
|
+
* one-word method as a chunk-compatibility path.
|
|
56
56
|
*/
|
|
57
57
|
export class CircleCliGatewayPaymentEngine implements AgentPaymentEngine {
|
|
58
58
|
private readonly x402 = new x402Client();
|
|
@@ -74,13 +74,32 @@ export class CircleCliGatewayPaymentEngine implements AgentPaymentEngine {
|
|
|
74
74
|
|
|
75
75
|
async createWordPayment(session: StartSessionResponse): Promise<StreamPaymentRequest> {
|
|
76
76
|
if (!session.paymentRequired) {
|
|
77
|
-
throw new Error("Session did not include
|
|
77
|
+
throw new Error("Session did not include a legacy x402 payment requirement");
|
|
78
78
|
}
|
|
79
79
|
await this.signer.ensureAddress();
|
|
80
80
|
return {
|
|
81
81
|
paymentPayload: await this.x402.createPaymentPayload(session.paymentRequired as never),
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
|
+
|
|
85
|
+
async createChunkPayment(session: StartSessionResponse, input: { nextSequence: number; maxWords: number }): Promise<StreamPaymentRequest> {
|
|
86
|
+
if (!session.paymentRequired) {
|
|
87
|
+
throw new Error("Session did not include an x402 payment requirement");
|
|
88
|
+
}
|
|
89
|
+
await this.signer.ensureAddress();
|
|
90
|
+
const amountAtomic = BigInt(session.wordPaymentAtomic) * BigInt(input.maxWords);
|
|
91
|
+
return {
|
|
92
|
+
paymentPayload: await this.x402.createPaymentPayload(
|
|
93
|
+
withChunkRequirement(session.paymentRequired, {
|
|
94
|
+
amountAtomic: `${amountAtomic}`,
|
|
95
|
+
maxWords: input.maxWords,
|
|
96
|
+
nextSequence: input.nextSequence,
|
|
97
|
+
sessionId: session.sessionId,
|
|
98
|
+
}) as never,
|
|
99
|
+
),
|
|
100
|
+
maxWords: input.maxWords,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
84
103
|
}
|
|
85
104
|
|
|
86
105
|
export class CircleCliGatewaySigner {
|
|
@@ -263,6 +282,32 @@ function findString(value: unknown, keys: string[]): string | undefined {
|
|
|
263
282
|
return undefined;
|
|
264
283
|
}
|
|
265
284
|
|
|
285
|
+
function withChunkRequirement(
|
|
286
|
+
paymentRequired: unknown,
|
|
287
|
+
chunk: { amountAtomic: `${bigint}`; maxWords: number; nextSequence: number; sessionId: string },
|
|
288
|
+
): unknown {
|
|
289
|
+
const source = paymentRequired as { accepts?: Array<Record<string, unknown>> };
|
|
290
|
+
if (!Array.isArray(source.accepts)) {
|
|
291
|
+
return paymentRequired;
|
|
292
|
+
}
|
|
293
|
+
return {
|
|
294
|
+
...(paymentRequired as Record<string, unknown>),
|
|
295
|
+
accepts: source.accepts.map((accept) => ({
|
|
296
|
+
...accept,
|
|
297
|
+
amount: chunk.amountAtomic,
|
|
298
|
+
extra: {
|
|
299
|
+
...((accept.extra as Record<string, unknown> | undefined) ?? {}),
|
|
300
|
+
amountAtomic: chunk.amountAtomic,
|
|
301
|
+
maxWords: chunk.maxWords,
|
|
302
|
+
sequence: chunk.nextSequence,
|
|
303
|
+
authorizationMode: "chunk",
|
|
304
|
+
nonce: `${chunk.sessionId}:${chunk.nextSequence}:${chunk.maxWords}`,
|
|
305
|
+
idempotencyKey: `${chunk.sessionId}:${chunk.nextSequence}:${chunk.maxWords}`,
|
|
306
|
+
},
|
|
307
|
+
})),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
266
311
|
function parseJson(value: string): unknown {
|
|
267
312
|
try {
|
|
268
313
|
return JSON.parse(value);
|