@402flow/sdk 0.1.0-alpha.2 → 0.1.0-alpha.21
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 +368 -23
- package/dist/agent-harness.d.ts +163 -0
- package/dist/agent-harness.js +474 -0
- package/dist/agent-harness.js.map +1 -0
- package/dist/challenge-detection.d.ts +17 -5
- package/dist/challenge-detection.js +65 -102
- package/dist/challenge-detection.js.map +1 -1
- package/dist/contracts.d.ts +4604 -326
- package/dist/contracts.js +232 -18
- package/dist/contracts.js.map +1 -1
- package/dist/harness-instructions.d.ts +7 -0
- package/dist/harness-instructions.js +28 -0
- package/dist/harness-instructions.js.map +1 -0
- package/dist/index.d.ts +105 -8
- package/dist/index.js +997 -32
- package/dist/index.js.map +1 -1
- package/dist/version.d.ts +4 -0
- package/dist/version.js +9 -0
- package/dist/version.js.map +1 -0
- package/package.json +11 -8
- package/dist/challenge-types.d.ts +0 -57
- package/dist/challenge-types.js +0 -111
- package/dist/challenge-types.js.map +0 -1
- package/dist/x402-v1.d.ts +0 -4
- package/dist/x402-v1.js +0 -53
- package/dist/x402-v1.js.map +0 -1
- package/dist/x402-v2.d.ts +0 -4
- package/dist/x402-v2.js +0 -52
- package/dist/x402-v2.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @402flow/sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Paid HTTP SDK for AI agents with an inspectable prepare/execute flow.
|
|
4
|
+
|
|
5
|
+
It uses the 402flow control plane as the governance and execution backend, but the main developer surface is a small client for preparing and executing paid HTTP requests.
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
@@ -8,50 +10,393 @@ Node.js SDK for making paid requests through the 402flow control plane.
|
|
|
8
10
|
npm install @402flow/sdk
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
The published package supports Node 20+.
|
|
14
|
+
|
|
15
|
+
## Overview
|
|
16
|
+
|
|
17
|
+
`@402flow/sdk` is built for AI agents and other callers that need to inspect, prepare, and execute paid HTTP requests without embedding payment-protocol or governance logic in the host.
|
|
18
|
+
|
|
19
|
+
The SDK has one core client, `AgentPayClient`, and three main calls:
|
|
20
|
+
|
|
21
|
+
| API | Use when | What it does |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| `fetchPaid(...)` | You already know the request shape | Probes if needed, resolves payment through the control plane, and returns passthrough or success |
|
|
24
|
+
| `preparePaidRequest(...)` | You want to inspect before paying | Returns normalized payment terms, request hints, validation issues, and `nextAction` |
|
|
25
|
+
| `executePreparedRequest(...)` | You already prepared the request | Executes the exact prepared request without re-probing first |
|
|
26
|
+
|
|
27
|
+
Use `fetchPaid(...)` for the simplest direct path.
|
|
28
|
+
Use `preparePaidRequest(...)` plus `executePreparedRequest(...)` when the caller needs an explicit inspect, revise, then execute loop.
|
|
29
|
+
|
|
30
|
+
The package also includes `AgentHarness`, an optional preparedId-based wrapper for tool hosts. That host-facing wrapper adds a human-readable `costSummary` on prepared results and ships canonical `defaultHarnessInstructions` plus `defaultHarnessToolSpecs` exports so adapters can reuse the same orchestration contract.
|
|
12
31
|
|
|
13
|
-
|
|
32
|
+
## Runtime Notes
|
|
33
|
+
|
|
34
|
+
Two constraints matter early in real integrations:
|
|
35
|
+
|
|
36
|
+
1. paid prepare and execute flows require replayable request bodies
|
|
37
|
+
2. the current replayable body types are `string` and `URLSearchParams`
|
|
38
|
+
|
|
39
|
+
That means JSON payloads should be sent as strings, and form-style payloads should be sent as `URLSearchParams`.
|
|
40
|
+
|
|
41
|
+
The SDK exports small helpers for that:
|
|
14
42
|
|
|
15
43
|
```ts
|
|
16
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
createFormUrlEncodedBody,
|
|
46
|
+
createJsonRequestBody,
|
|
47
|
+
} from '@402flow/sdk';
|
|
48
|
+
|
|
49
|
+
const jsonBody = createJsonRequestBody({
|
|
50
|
+
prompt: 'foggy coastline',
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const formBody = createFormUrlEncodedBody({
|
|
54
|
+
prompt: 'foggy coastline',
|
|
55
|
+
style: 'noir',
|
|
56
|
+
tags: ['coast', 'mist'],
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`FormData`, `Blob`, streams, and framework-specific body wrappers are not currently accepted in paid flows because the SDK has to replay the exact request body through preparation and execution. Convert those upstream into a stable string or `URLSearchParams` first.
|
|
61
|
+
|
|
62
|
+
## Create A Client
|
|
63
|
+
|
|
64
|
+
Create one `AgentPayClient` per agent identity. The client binds the 402flow control-plane location and the organization plus agent selectors up front, and each request only carries request-specific context.
|
|
65
|
+
|
|
66
|
+
### Bootstrap key
|
|
67
|
+
|
|
68
|
+
For most SDK integrations, bootstrap-key auth is the recommended mode. The SDK exchanges it for a short-lived runtime token, caches that token, and refreshes it automatically before expiry.
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { AgentPayClient, createJsonRequestBody } from '@402flow/sdk';
|
|
17
72
|
|
|
18
73
|
const client = new AgentPayClient({
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
74
|
+
controlPlaneBaseUrl: 'https://402flow.ai',
|
|
75
|
+
organization: 'acme-labs',
|
|
76
|
+
agent: 'reporting-worker',
|
|
77
|
+
auth: {
|
|
78
|
+
type: 'bootstrapKey',
|
|
79
|
+
bootstrapKey: process.env.X402FLOW_BOOTSTRAP_KEY ?? '',
|
|
80
|
+
},
|
|
24
81
|
});
|
|
25
82
|
```
|
|
26
83
|
|
|
27
|
-
###
|
|
84
|
+
### Runtime token
|
|
28
85
|
|
|
29
86
|
```ts
|
|
30
87
|
import { AgentPayClient } from '@402flow/sdk';
|
|
31
88
|
|
|
32
89
|
const client = new AgentPayClient({
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
90
|
+
controlPlaneBaseUrl: 'https://402flow.ai',
|
|
91
|
+
organization: 'acme-labs',
|
|
92
|
+
agent: 'reporting-worker',
|
|
93
|
+
auth: {
|
|
94
|
+
type: 'runtimeToken',
|
|
95
|
+
runtimeToken: process.env.X402FLOW_RUNTIME_TOKEN ?? '',
|
|
96
|
+
},
|
|
38
97
|
});
|
|
39
98
|
```
|
|
40
99
|
|
|
41
|
-
|
|
100
|
+
## Fast Path: `fetchPaid()`
|
|
101
|
+
|
|
102
|
+
Call `fetchPaid()` when you already know the merchant URL, method, headers, and body.
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
try {
|
|
106
|
+
const result = await client.fetchPaid(
|
|
107
|
+
'https://merchant.example.com/reports/daily',
|
|
108
|
+
{
|
|
109
|
+
method: 'POST',
|
|
110
|
+
headers: {
|
|
111
|
+
'content-type': 'application/json',
|
|
112
|
+
},
|
|
113
|
+
body: createJsonRequestBody({
|
|
114
|
+
date: '2026-03-25',
|
|
115
|
+
}),
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
description: 'sync daily paid report',
|
|
119
|
+
idempotencyKey: 'daily-report-2026-03-25',
|
|
120
|
+
},
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const merchantBody = await result.response.json();
|
|
124
|
+
console.log('merchant response body:', merchantBody);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error('paid request failed', error);
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
If the merchant does not require payment for that exact request, the SDK returns a passthrough response. If the merchant returns a payable challenge, the SDK resolves payment through the control plane and returns a durable paid outcome.
|
|
132
|
+
|
|
133
|
+
`result.response` is always the merchant HTTP response. SDK-owned payment metadata such as `paidRequestId`, `paymentAttemptId`, `receiptId`, and `receipt` stays on the SDK result instead of being injected into the merchant JSON body.
|
|
134
|
+
|
|
135
|
+
### Interpreting Merchant Responses
|
|
136
|
+
|
|
137
|
+
The SDK gives you a stable place for payment metadata, but it does not invent a universal fulfilled-response schema for merchant content.
|
|
138
|
+
|
|
139
|
+
In practice:
|
|
140
|
+
|
|
141
|
+
1. the SDK result carries durable payment metadata such as `receiptId` and `receipt`
|
|
142
|
+
2. `result.response` carries the merchant fulfillment payload
|
|
143
|
+
3. the merchant contract decides where the useful paid content lives inside that payload
|
|
144
|
+
|
|
145
|
+
If you need request-shape guidance before execution, use `preparePaidRequest()` and inspect:
|
|
146
|
+
|
|
147
|
+
1. `prepared.hints` for authoritative request fields, examples, notes, and query/body guidance when the challenge publishes them
|
|
148
|
+
2. `prepared.challengeDetails` for raw merchant challenge data such as resource metadata, accepted payment candidates, and extensions like Bazaar discovery metadata
|
|
149
|
+
3. optional caller-supplied `externalMetadata` as advisory context only
|
|
150
|
+
|
|
151
|
+
If you do not have enough contract information to interpret a merchant response safely, return the raw merchant body and explain what is still missing instead of inventing a payload shape.
|
|
152
|
+
|
|
153
|
+
## Preparation Flow
|
|
154
|
+
|
|
155
|
+
Use `preparePaidRequest()` when the caller needs a first-class pre-execution result before paying.
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
import { createJsonRequestBody } from '@402flow/sdk';
|
|
159
|
+
|
|
160
|
+
const prepared = await client.preparePaidRequest(
|
|
161
|
+
'https://merchant.example.com/images/generate',
|
|
162
|
+
{
|
|
163
|
+
method: 'POST',
|
|
164
|
+
headers: {
|
|
165
|
+
'content-type': 'application/json',
|
|
166
|
+
},
|
|
167
|
+
body: createJsonRequestBody({
|
|
168
|
+
prompt: 'foggy coastline',
|
|
169
|
+
}),
|
|
170
|
+
},
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
if (prepared.kind === 'passthrough') {
|
|
174
|
+
console.log('merchant did not require payment', prepared.probe?.responseStatus);
|
|
175
|
+
} else {
|
|
176
|
+
console.log('protocol:', prepared.protocol);
|
|
177
|
+
console.log('payment requirement:', prepared.paymentRequirement);
|
|
178
|
+
console.log('request hints:', prepared.hints);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
console.log('next action:', prepared.nextAction);
|
|
182
|
+
console.log('validation issues:', prepared.validationIssues);
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
This flow is useful when:
|
|
186
|
+
|
|
187
|
+
1. an agent needs request-shape hints before attempting execution
|
|
188
|
+
2. the caller wants normalized payment terms before paying
|
|
189
|
+
3. the caller wants to merge optional `externalMetadata` it already has from another system
|
|
190
|
+
|
|
191
|
+
The common loop is:
|
|
192
|
+
|
|
193
|
+
1. prepare the request
|
|
194
|
+
2. inspect `kind`, `paymentRequirement`, `hints`, `validationIssues`, and `nextAction`
|
|
195
|
+
3. revise if needed
|
|
196
|
+
4. execute only once the request is understood
|
|
197
|
+
|
|
198
|
+
If your system already has endpoint metadata, you can pass it in as optional context:
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
import { createJsonRequestBody } from '@402flow/sdk';
|
|
202
|
+
|
|
203
|
+
const prepared = await client.preparePaidRequest(
|
|
204
|
+
'https://merchant.example.com/images/generate',
|
|
205
|
+
{
|
|
206
|
+
method: 'POST',
|
|
207
|
+
headers: {
|
|
208
|
+
'content-type': 'application/json',
|
|
209
|
+
},
|
|
210
|
+
body: createJsonRequestBody({
|
|
211
|
+
prompt: 'foggy coastline',
|
|
212
|
+
}),
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
externalMetadata: {
|
|
216
|
+
requestBodyType: 'json',
|
|
217
|
+
requestBodyFields: [
|
|
218
|
+
{
|
|
219
|
+
name: 'prompt',
|
|
220
|
+
type: 'string',
|
|
221
|
+
required: true,
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
`externalMetadata` is optional caller context. It improves preparation when the caller already has structured endpoint knowledge, but it is not required for normal SDK use.
|
|
230
|
+
|
|
231
|
+
### What `ready` Means
|
|
232
|
+
|
|
233
|
+
`ready` means this exact request can proceed through governed paid execution as-is; it does not mean the SDK has inferred the best task parameters for you.
|
|
234
|
+
|
|
235
|
+
That distinction matters:
|
|
236
|
+
|
|
237
|
+
1. `ready` is about protocol and payment executability
|
|
238
|
+
2. `validationIssues` and `hints` are about request-shape guidance
|
|
239
|
+
3. choosing semantically correct task parameters still belongs to the caller or agent
|
|
240
|
+
|
|
241
|
+
### Execute A Prepared Request
|
|
242
|
+
|
|
243
|
+
If preparation returns `kind === 'ready'`, execute that exact prepared request with `executePreparedRequest(prepared, ...)`.
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
import { createJsonRequestBody } from '@402flow/sdk';
|
|
247
|
+
|
|
248
|
+
const prepared = await client.preparePaidRequest(
|
|
249
|
+
'https://merchant.example.com/images/generate',
|
|
250
|
+
{
|
|
251
|
+
method: 'POST',
|
|
252
|
+
headers: {
|
|
253
|
+
'content-type': 'application/json',
|
|
254
|
+
},
|
|
255
|
+
body: createJsonRequestBody({
|
|
256
|
+
prompt: 'foggy coastline',
|
|
257
|
+
}),
|
|
258
|
+
},
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
if (prepared.kind === 'ready') {
|
|
262
|
+
const result = await client.executePreparedRequest(prepared, {
|
|
263
|
+
description: 'generate image',
|
|
264
|
+
idempotencyKey: 'image-generate-foggy-coastline',
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
console.log('paid response status:', result.response.status);
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
If preparation does not return `kind === 'ready'`, that is not necessarily an error. It means this exact request did not currently resolve to a payable executable path. The caller can accept that result, run a normal non-paid path, or revise and prepare again.
|
|
42
272
|
|
|
43
|
-
##
|
|
273
|
+
## Prepared Result Semantics
|
|
44
274
|
|
|
45
|
-
|
|
275
|
+
`preparePaidRequest()` separates request checking from paid execution.
|
|
46
276
|
|
|
47
|
-
|
|
277
|
+
The preparation result distinguishes four important things:
|
|
48
278
|
|
|
49
|
-
|
|
279
|
+
1. `paymentRequirement`: normalized payment terms derived from the merchant challenge when available
|
|
280
|
+
2. `hints`: request-shape hints such as body fields, query params, path params, descriptions, examples, and notes
|
|
281
|
+
3. `validationIssues`: structured remediation diagnostics derived from the current request and defensible preparation inputs
|
|
282
|
+
4. `nextAction`: the authoritative machine contract for what to do next, such as `execute`, `revise_request`, or `treat_as_passthrough`
|
|
283
|
+
|
|
284
|
+
Each prepared hint carries `attribution` so callers can distinguish live merchant-authoritative data from advisory caller-supplied metadata.
|
|
285
|
+
|
|
286
|
+
In this model, payment terms come from the merchant challenge, optional request-shape enrichment comes from `externalMetadata`, and live confirmation is represented by the prepared `probe` result.
|
|
287
|
+
|
|
288
|
+
## Result And Error Semantics
|
|
289
|
+
|
|
290
|
+
`fetchPaid()` and `executePreparedRequest()` either:
|
|
291
|
+
|
|
292
|
+
1. return a passthrough response when the request did not require payment
|
|
293
|
+
2. return `success` with a receipt when the paid request completed successfully
|
|
294
|
+
3. throw `FetchPaidError` for all non-success paid outcomes
|
|
295
|
+
|
|
296
|
+
`FetchPaidError` kinds are:
|
|
297
|
+
|
|
298
|
+
1. `denied`
|
|
299
|
+
2. `preflight_failed`
|
|
300
|
+
3. `execution_pending`
|
|
301
|
+
4. `execution_failed`
|
|
302
|
+
5. `paid_fulfillment_failed`
|
|
303
|
+
6. `execution_inconclusive`
|
|
304
|
+
7. `request_failed`
|
|
305
|
+
|
|
306
|
+
Receipt notes:
|
|
307
|
+
|
|
308
|
+
1. `receipt.status = 'confirmed'` means the control plane has chain-backed settlement attribution for the paid attempt
|
|
309
|
+
2. `receipt.status = 'provisional'` means the paid outcome was supportable by merchant-provided evidence, but final settlement attribution is still pending reconciliation
|
|
310
|
+
3. callers should treat provisional receipts as payment-attempt evidence, not as proof of final settlement
|
|
311
|
+
4. if you safely retry the same logical paid request with the same `idempotencyKey`, the SDK returns the same durable paid outcome instead of creating a second paid attempt
|
|
312
|
+
|
|
313
|
+
## Receipt Lookup
|
|
314
|
+
|
|
315
|
+
```ts
|
|
316
|
+
const receipt = await client.lookupReceipt('receipt-id');
|
|
317
|
+
|
|
318
|
+
console.log(receipt.receipt.status);
|
|
319
|
+
```
|
|
50
320
|
|
|
51
|
-
##
|
|
321
|
+
## Canonical Host Metadata
|
|
52
322
|
|
|
53
|
-
|
|
323
|
+
If you are building a tool host, do not copy orchestration rules into ad hoc prompts. Import the canonical host-agnostic metadata from the SDK and adapt it to your model provider.
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
import {
|
|
327
|
+
defaultHarnessInstructions,
|
|
328
|
+
defaultHarnessToolSpecs,
|
|
329
|
+
} from '@402flow/sdk';
|
|
330
|
+
|
|
331
|
+
console.log(defaultHarnessInstructions);
|
|
332
|
+
console.log(defaultHarnessToolSpecs);
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
`defaultHarnessToolSpecs` defines the canonical three-tool contract:
|
|
336
|
+
|
|
337
|
+
1. `prepare_paid_request`
|
|
338
|
+
2. `execute_prepared_request`
|
|
339
|
+
3. `get_execution_result`
|
|
340
|
+
|
|
341
|
+
Those descriptions encode the orchestration rules, including:
|
|
342
|
+
|
|
343
|
+
1. `nextAction` is authoritative
|
|
344
|
+
2. execute only after `nextAction === 'execute'`
|
|
345
|
+
3. read the stored execution result before summarizing the outcome
|
|
346
|
+
|
|
347
|
+
Keep provider-specific tool objects in your host adapter, not in the SDK core package.
|
|
348
|
+
|
|
349
|
+
## Tiny OpenAI Tools Host
|
|
350
|
+
|
|
351
|
+
If you want a minimal real host integration instead of the larger evaluation harness, use the tiny OpenAI Responses example in `examples/openai-tools-quickstart.mjs`.
|
|
352
|
+
|
|
353
|
+
For repo-local example runs, create `.env` from `.env.example` in the SDK root. The SDK examples load that file directly, so scenario runs stay self-contained in this repo whether they point at a local control plane or a hosted environment.
|
|
354
|
+
|
|
355
|
+
It keeps the host story narrow:
|
|
356
|
+
|
|
357
|
+
1. create an `AgentPayClient`
|
|
358
|
+
2. wrap it with optional `AgentHarness` so tool calls can pass `preparedId`
|
|
359
|
+
3. build provider-specific tool objects from `defaultHarnessToolSpecs`
|
|
360
|
+
4. expose `prepare_paid_request`, `execute_prepared_request`, and `get_execution_result`
|
|
361
|
+
|
|
362
|
+
Run it with one prompt. Either populate `.env` from `.env.example`, or export the same values in your shell for a one-off run:
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
cp .env.example .env
|
|
366
|
+
export OPENAI_API_KEY="..."
|
|
367
|
+
export X402FLOW_CONTROL_PLANE_BASE_URL="https://402flow.ai"
|
|
368
|
+
export X402FLOW_ORGANIZATION="acme-labs"
|
|
369
|
+
export X402FLOW_AGENT="reporting-worker"
|
|
370
|
+
export X402FLOW_BOOTSTRAP_KEY="..."
|
|
371
|
+
|
|
372
|
+
npm run example:openai-tools-quickstart -- \
|
|
373
|
+
"Prepare and execute a paid POST request to https://nickeljoke.vercel.app/api/joke with JSON body {\"topic\":\"sdk integration\",\"tone\":\"dry\",\"audience\":\"platform engineers\"}"
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Use this when you want the shortest real host integration path. Use the full evaluation harness only when you need scenarios, transcripts, or repeated eval runs.
|
|
377
|
+
|
|
378
|
+
## Optional `AgentHarness`
|
|
379
|
+
|
|
380
|
+
`AgentHarness` is an optional preparedId-based wrapper for tool hosts that do not want to manage in-flight prepared request objects themselves. It is a convenience layer on top of `AgentPayClient`, not a required abstraction.
|
|
381
|
+
|
|
382
|
+
Key behavior:
|
|
383
|
+
|
|
384
|
+
1. preparations are stored in memory behind a `preparedId`
|
|
385
|
+
2. a newer active preparation for the same method plus origin plus pathname supersedes the older one
|
|
386
|
+
3. duplicate execute calls for the same consumed `preparedId` are rejected locally as already consumed
|
|
387
|
+
4. hosts should call `getExecutionResult(preparedId)` after execution to read the durable stored outcome
|
|
388
|
+
5. host-facing prepare results include `costSummary`, for example `Costs 0.001000 USDC on Base Sepolia (exact).`
|
|
389
|
+
|
|
390
|
+
For harness usage, presets, transcripts, and scenario packs, see:
|
|
391
|
+
|
|
392
|
+
1. [docs/evaluation-harness.md](docs/evaluation-harness.md)
|
|
393
|
+
2. [docs/harness-scenarios.md](docs/harness-scenarios.md)
|
|
54
394
|
|
|
55
395
|
## Publish
|
|
56
396
|
|
|
57
|
-
|
|
397
|
+
```bash
|
|
398
|
+
npm install
|
|
399
|
+
npm run check
|
|
400
|
+
npm run pack:check
|
|
401
|
+
npm publish --access public
|
|
402
|
+
```
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentHarness provides a small in-memory orchestration layer on top of
|
|
3
|
+
* AgentPayClient for hosts that prefer preparedId-based handoffs over holding
|
|
4
|
+
* full prepared request objects.
|
|
5
|
+
*
|
|
6
|
+
* The intended host-facing flow is:
|
|
7
|
+
*
|
|
8
|
+
* 1. prepare a candidate request and receive a preparedId plus the preparation summary
|
|
9
|
+
* 2. execute later by preparedId once nextAction is execute
|
|
10
|
+
* 3. read back the stored execution result by preparedId
|
|
11
|
+
*
|
|
12
|
+
* This is useful for tool-driven hosts such as OpenAI tools, MCP servers,
|
|
13
|
+
* Claude tools, LangGraph nodes, or custom orchestrators where passing a small
|
|
14
|
+
* opaque id between turns is easier than preserving the full prepared object.
|
|
15
|
+
*
|
|
16
|
+
* This file implements only a convenience layer. The core SDK contract remains
|
|
17
|
+
* AgentPayClient with preparePaidRequest() and executePreparedRequest().
|
|
18
|
+
*
|
|
19
|
+
* Important behavior:
|
|
20
|
+
* - State is kept only in memory inside this process.
|
|
21
|
+
* - Prepared requests expire after a TTL.
|
|
22
|
+
* - A newer active preparation for the same method + origin + pathname supersedes
|
|
23
|
+
* the older one.
|
|
24
|
+
* - Execution is rejected locally unless the stored preparation is still active,
|
|
25
|
+
* kind === 'ready', and nextAction === 'execute'.
|
|
26
|
+
*/
|
|
27
|
+
import type { PaidRequestChallenge, SdkExternalMetadata, SdkMerchantResponse, SdkPreparedChallengeDetails, SdkPreparedNextAction, SdkPreparedPaidRequest, SdkPreparedPaymentRequirement, SdkPreparedRequestHints, SdkPreparedValidationIssue } from './contracts.js';
|
|
28
|
+
import type { AgentPayClient, ExecutePreparedRequest, FetchPaidFailureResponse, PaidResponse } from './index.js';
|
|
29
|
+
export type AgentHarnessPreparedState = 'active' | 'consumed' | 'expired' | 'superseded';
|
|
30
|
+
/** Local rejection reasons produced by the harness before the SDK is called. */
|
|
31
|
+
export type AgentHarnessRejectionCode = 'missing_prepared_id' | 'unknown_prepared_id' | 'expired_prepared_id' | 'prepared_request_superseded' | 'prepared_request_consumed' | 'prepared_request_not_ready' | 'prepared_request_not_executable';
|
|
32
|
+
/** Typed error used internally to convert local state failures into stable results. */
|
|
33
|
+
export declare class AgentHarnessError extends Error {
|
|
34
|
+
readonly code: AgentHarnessRejectionCode;
|
|
35
|
+
readonly preparedId: string | undefined;
|
|
36
|
+
constructor(code: AgentHarnessRejectionCode, message: string, preparedId?: string);
|
|
37
|
+
}
|
|
38
|
+
/** Host-facing input for the harness prepare step. */
|
|
39
|
+
export type AgentHarnessPrepareInput = {
|
|
40
|
+
url: string;
|
|
41
|
+
method?: string;
|
|
42
|
+
headers?: Record<string, string>;
|
|
43
|
+
body?: string;
|
|
44
|
+
externalMetadata?: SdkExternalMetadata;
|
|
45
|
+
};
|
|
46
|
+
/** Host-facing input for the harness execute step. */
|
|
47
|
+
export type AgentHarnessExecuteInput = {
|
|
48
|
+
preparedId: string;
|
|
49
|
+
executionContext?: ExecutePreparedRequest;
|
|
50
|
+
};
|
|
51
|
+
/** Exact immutable execution payload stored behind a preparedId. */
|
|
52
|
+
export type AgentHarnessExecutionBinding = {
|
|
53
|
+
method: string;
|
|
54
|
+
url: string;
|
|
55
|
+
headers: Record<string, string>;
|
|
56
|
+
body?: string;
|
|
57
|
+
bodyHash?: string;
|
|
58
|
+
challenge?: {
|
|
59
|
+
protocol: PaidRequestChallenge['protocol'];
|
|
60
|
+
headers: Record<string, string>;
|
|
61
|
+
body?: unknown;
|
|
62
|
+
};
|
|
63
|
+
merchantOrigin: string;
|
|
64
|
+
};
|
|
65
|
+
/** Summary returned to the host after preparation succeeds. */
|
|
66
|
+
export type AgentHarnessPreparedSummary = {
|
|
67
|
+
preparedId: string;
|
|
68
|
+
state: 'active';
|
|
69
|
+
kind: SdkPreparedPaidRequest['kind'];
|
|
70
|
+
protocol: SdkPreparedPaidRequest['protocol'];
|
|
71
|
+
costSummary?: string;
|
|
72
|
+
challengeDetails?: SdkPreparedChallengeDetails;
|
|
73
|
+
paymentRequirement?: SdkPreparedPaymentRequirement;
|
|
74
|
+
hints: SdkPreparedRequestHints;
|
|
75
|
+
probe?: SdkPreparedPaidRequest['probe'];
|
|
76
|
+
validationIssues: SdkPreparedValidationIssue[];
|
|
77
|
+
nextAction: SdkPreparedNextAction;
|
|
78
|
+
expiresAt: string;
|
|
79
|
+
};
|
|
80
|
+
/** Stored summary for an SDK-backed paid execution outcome. */
|
|
81
|
+
export type AgentHarnessExecutedResult = {
|
|
82
|
+
preparedId: string;
|
|
83
|
+
harnessDisposition: 'executed';
|
|
84
|
+
sdkOutcomeKind: PaidResponse['kind'] | FetchPaidFailureResponse['kind'];
|
|
85
|
+
status: number;
|
|
86
|
+
merchantResponse: SdkMerchantResponse;
|
|
87
|
+
receiptId?: string;
|
|
88
|
+
paidRequestId?: string;
|
|
89
|
+
paymentAttemptId?: string;
|
|
90
|
+
reason?: string;
|
|
91
|
+
policyReviewEventId?: string;
|
|
92
|
+
};
|
|
93
|
+
/** Stored summary for a harness-local rejection outcome. */
|
|
94
|
+
export type AgentHarnessRejectedResult = {
|
|
95
|
+
preparedId: string;
|
|
96
|
+
harnessDisposition: 'rejected';
|
|
97
|
+
rejectionCode: AgentHarnessRejectionCode;
|
|
98
|
+
message: string;
|
|
99
|
+
};
|
|
100
|
+
export type AgentHarnessExecutionResult = AgentHarnessExecutedResult | AgentHarnessRejectedResult;
|
|
101
|
+
/** Lookup shape returned when a host asks for the durable result of a preparedId. */
|
|
102
|
+
export type AgentHarnessExecutionLookup = {
|
|
103
|
+
preparedId: string;
|
|
104
|
+
state: AgentHarnessPreparedState;
|
|
105
|
+
supersededByPreparedId?: string;
|
|
106
|
+
executionResult?: AgentHarnessExecutionResult;
|
|
107
|
+
};
|
|
108
|
+
/** Full internal record shape exposed for debugging and test inspection. */
|
|
109
|
+
export type AgentHarnessPreparedRecord = {
|
|
110
|
+
preparedId: string;
|
|
111
|
+
state: AgentHarnessPreparedState;
|
|
112
|
+
createdAt: string;
|
|
113
|
+
expiresAt: string;
|
|
114
|
+
supersededByPreparedId?: string;
|
|
115
|
+
prepared: SdkPreparedPaidRequest;
|
|
116
|
+
executionBinding: AgentHarnessExecutionBinding;
|
|
117
|
+
executionResult?: AgentHarnessExecutionResult;
|
|
118
|
+
};
|
|
119
|
+
/** Minimal client surface AgentHarness needs from the core SDK. */
|
|
120
|
+
export type AgentHarnessClient = Pick<AgentPayClient, 'preparePaidRequest' | 'executePreparedRequest'>;
|
|
121
|
+
/** Configuration for the in-memory wrapper, including TTL and id generation hooks. */
|
|
122
|
+
export type AgentHarnessOptions = {
|
|
123
|
+
client: AgentHarnessClient;
|
|
124
|
+
preparedTtlMs?: number;
|
|
125
|
+
now?: () => Date;
|
|
126
|
+
createPreparedId?: () => string;
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* Optional in-memory preparedId wrapper over AgentPayClient.
|
|
130
|
+
*/
|
|
131
|
+
export declare class AgentHarness {
|
|
132
|
+
private readonly client;
|
|
133
|
+
private readonly preparedTtlMs;
|
|
134
|
+
private readonly now;
|
|
135
|
+
private readonly createPreparedId;
|
|
136
|
+
private readonly preparedRecords;
|
|
137
|
+
constructor(options: AgentHarnessOptions);
|
|
138
|
+
/**
|
|
139
|
+
* Prepare a candidate request through the core SDK and store the immutable
|
|
140
|
+
* prepared result behind a generated preparedId for later execution.
|
|
141
|
+
*/
|
|
142
|
+
preparePaidRequest(input: AgentHarnessPrepareInput): Promise<AgentHarnessPreparedSummary>;
|
|
143
|
+
/**
|
|
144
|
+
* Execute a previously stored ready preparation. Local state failures are
|
|
145
|
+
* converted into deterministic rejected results rather than thrown to the host.
|
|
146
|
+
*/
|
|
147
|
+
executePreparedRequest(input: AgentHarnessExecuteInput): Promise<AgentHarnessExecutionResult>;
|
|
148
|
+
/**
|
|
149
|
+
* Return the durable stored outcome for a preparedId without re-running any
|
|
150
|
+
* merchant or control-plane call.
|
|
151
|
+
*/
|
|
152
|
+
getExecutionResult(preparedId: string): AgentHarnessExecutionLookup;
|
|
153
|
+
/**
|
|
154
|
+
* Return the full stored record for debugging, tests, or host inspection.
|
|
155
|
+
*/
|
|
156
|
+
getPreparedRecord(preparedId: string): AgentHarnessPreparedRecord;
|
|
157
|
+
private runExecution;
|
|
158
|
+
private handleExecutionRejection;
|
|
159
|
+
private getRecordForExecution;
|
|
160
|
+
private getKnownRecord;
|
|
161
|
+
private refreshRecordState;
|
|
162
|
+
private supersedeActiveRecords;
|
|
163
|
+
}
|