@402flow/sdk 0.1.0-alpha.3 → 0.1.0-alpha.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,18 @@
1
1
  # @402flow/sdk
2
2
 
3
- Node.js SDK for making paid requests through the 402flow control plane.
3
+ Paid API SDK for AI agents, tool hosts, and governed automation.
4
+
5
+ It gives AI agents, tool hosts, and automation services easy access to paid APIs while organizations keep policy, approvals, receipts, and spend controls outside the agent runtime.
6
+
7
+ Use `fetchPaid(...)` when the exact request is already known.
8
+ Use `preparePaidRequest(...)` when the agent needs merchant-published hints and an authoritative `nextAction` before paying.
9
+
10
+ ## Why This SDK
11
+
12
+ - Inspectable paid request flow. Agents and tool hosts can prepare, revise, and execute paid HTTP requests explicitly instead of hiding everything inside one opaque pay-and-fetch call.
13
+ - Control-plane governance. Policy, approvals, receipts, and audit stay centralized instead of being reimplemented in every host.
14
+ - Agent-ready request shaping. `nextAction` gives models and tools a stable contract for revise, execute, or passthrough.
15
+ - Provider-neutral execution. Use the native SDK path or delegate the paid call to Dexter, pay.sh, or a host-owned executor without losing governance value.
4
16
 
5
17
  ## Install
6
18
 
@@ -8,50 +20,133 @@ Node.js SDK for making paid requests through the 402flow control plane.
8
20
  npm install @402flow/sdk
9
21
  ```
10
22
 
11
- ## Usage
23
+ This is the normal install path. Use `@402flow/sdk` by itself when you want the native 402flow payment flow.
24
+
25
+ Optional official adapters for third-party payers:
26
+
27
+ ```bash
28
+ npm install @402flow/sdk @402flow/sdk-third-party-executors
29
+ ```
30
+
31
+ Install `@402flow/sdk-third-party-executors` only when you want delegated execution through Dexter or pay.sh instead of the native 402flow path.
32
+
33
+ The published package supports Node 20+.
34
+
35
+ ## Core Surface
36
+
37
+ | API | Use it when | What it returns |
38
+ | --- | --- | --- |
39
+ | `fetchPaid(...)` | You already know the request shape | Probe the merchant when no challenge is supplied, then authorize, pay, and return the merchant response |
40
+ | `preparePaidRequest(...)` | You want to inspect before paying | Payment terms, parameter hints, validation issues, and an authoritative `nextAction` |
41
+ | `executePreparedRequest(...)` | You already prepared the request | Executes the exact prepared request without re-probing first |
42
+ | `AgentHarness` | Your model host wants a `preparedId` tool contract | The same flow behind a process-local in-memory three-tool surface |
12
43
 
13
- ### Runtime Token
44
+ ## Quick Start: Host-Controlled Request
45
+
46
+ This first example shows the deterministic application path. Your code already knows which merchant route and request parameters it wants to send, and the SDK handles probing, policy, payment, and receipts around that request.
14
47
 
15
48
  ```ts
16
- import { AgentPayClient } from '@402flow/sdk';
49
+ import {
50
+ AgentPayClient,
51
+ createJsonRequestBody,
52
+ } from '@402flow/sdk';
17
53
 
18
54
  const client = new AgentPayClient({
19
- controlPlaneBaseUrl: 'https://402flow.ai',
20
- auth: {
21
- type: 'runtimeToken',
22
- runtimeToken: process.env.AGENT_PAY_RUNTIME_TOKEN ?? '',
23
- },
55
+ controlPlaneBaseUrl:
56
+ process.env.X402FLOW_CONTROL_PLANE_BASE_URL ?? 'https://api-staging.402flow.ai',
57
+ organization: process.env.X402FLOW_ORGANIZATION ?? 'acme-labs',
58
+ agent: process.env.X402FLOW_AGENT ?? 'research-worker',
59
+ auth: {
60
+ type: 'bootstrapKey',
61
+ bootstrapKey: process.env.X402FLOW_BOOTSTRAP_KEY ?? '',
62
+ },
24
63
  });
64
+
65
+ const result = await client.fetchPaid(
66
+ 'https://demo-merchant-staging.402flow.ai/demo-merchant/research-brief/solana-devnet',
67
+ {
68
+ method: 'POST',
69
+ headers: {
70
+ 'content-type': 'application/json',
71
+ },
72
+ body: createJsonRequestBody({
73
+ topic: 'sdk integration rollout',
74
+ audience: 'platform engineers',
75
+ format: 'bullets',
76
+ }),
77
+ },
78
+ {
79
+ description: 'generate a staged research brief',
80
+ idempotencyKey: 'sdk-readme-solana-devnet-brief',
81
+ },
82
+ );
83
+
84
+ console.log(await result.response.json());
85
+ console.log(result.receiptId);
25
86
  ```
26
87
 
27
- ### Bootstrap Key
88
+ This is why the request body is filled in directly in code here. `fetchPaid(...)` is the simplest integration path when your application already knows the parameters.
89
+
90
+ Important probe semantics: when you do not supply a merchant challenge, both `fetchPaid(...)` and `preparePaidRequest(...)` send the original request to the merchant first to discover whether payment is required. That initial merchant probe happens before any control-plane authorization or payment attempt. For non-idempotent `POST` routes, use this only against endpoints that are explicitly safe to probe or after you already have the merchant challenge from another step.
91
+
92
+ Use `fetchPaid(...)` when the request is already shaped and you want the shortest path.
93
+ Use `preparePaidRequest(...)` when the caller needs to inspect what the merchant published, construct the right request, and execute only when `nextAction === 'execute'`.
94
+
95
+ ## Quick Start: Agent-Driven Request Construction
96
+
97
+ If you want the agent to decide which parameters to send, do not hardcode those decisions into the SDK call site. Instead, expose the SDK through `AgentHarness` or your own tool wrapper and let the agent react to `nextAction`, `validationIssues`, and `hints`.
98
+
99
+ The typical loop is:
100
+
101
+ 1. the agent proposes a request
102
+ 2. the SDK returns `nextAction`, `validationIssues`, and merchant-published `hints`
103
+ 3. the agent revises the request until `nextAction === 'execute'`
104
+ 4. the host executes the prepared request and reads the stored result before summarizing the outcome
105
+
106
+ That is the path to use when the model is supposed to fill request parameters properly instead of relying on host code that already knows the answer.
107
+
108
+ ## AgentHarness
109
+
110
+ `AgentHarness` is the optional model-host wrapper for the same inspect-then-execute loop.
111
+
112
+ It stores process-local in-memory prepared state behind a `preparedId`, exposes a canonical three-tool contract, and keeps the rule that matters most stable across hosts:
113
+
114
+ `nextAction` is authoritative.
28
115
 
29
116
  ```ts
30
- import { AgentPayClient } from '@402flow/sdk';
117
+ import {
118
+ AgentHarness,
119
+ defaultHarnessInstructions,
120
+ defaultHarnessToolSpecs,
121
+ } from '@402flow/sdk';
31
122
 
32
- const client = new AgentPayClient({
33
- controlPlaneBaseUrl: 'https://402flow.ai',
34
- auth: {
35
- type: 'bootstrapKey',
36
- bootstrapKey: process.env.AGENT_PAY_BOOTSTRAP_KEY ?? '',
37
- },
38
- });
39
- ```
123
+ const harness = new AgentHarness({ client });
40
124
 
41
- When you configure `bootstrapKey` auth, the SDK exchanges that credential for a runtime token and reuses the runtime token for subsequent control-plane calls until it needs refresh.
125
+ console.log(defaultHarnessInstructions);
126
+ console.log(defaultHarnessToolSpecs.map((spec) => spec.name));
127
+ // [ 'prepare_paid_request', 'execute_prepared_request', 'get_execution_result' ]
128
+ ```
42
129
 
43
- ## Runtime Requirements
130
+ Use this path when you want the model to construct a correct request instead of guessing its way into a paid call.
44
131
 
45
- Use this SDK from modern Node.js environments with built-in `fetch` support.
132
+ `AgentHarness` is a convenience wrapper for single-process hosts. It is not a durable cross-process orchestration store.
46
133
 
47
- That matters because the SDK uses the Fetch API directly, not only when it calls `fetchPaid()`, but also when it normalizes headers and constructs response objects while mapping control-plane decisions back into SDK results. In practice, the SDK expects `fetch`, `Headers`, and `Response` to be available in the runtime.
134
+ ## Governed Third-Party Execution
48
135
 
49
- The current target is modern Node.js with built-in Fetch API support.
136
+ 402flow can execute paid x402 requests natively, or you can delegate final payment execution to Dexter, pay.sh, or another executor. Once a payable challenge is already known, 402flow authorizes the paid attempt before execution and finalizes the normalized result afterward, keeping policy, approvals, receipts, and audit centralized.
50
137
 
51
- ## Repository Layout
138
+ Official adapters live in `@402flow/sdk-third-party-executors`, and the repo-local source for those adapters lives under `third-party-executors/`. Import the provider-specific subpath you actually use:
52
139
 
53
- This repository is a single-package npm repo. The public consumer surface is the package `@402flow/sdk`.
140
+ ```ts
141
+ import { createDexterExecutor } from '@402flow/sdk-third-party-executors/dexter';
142
+ // or:
143
+ import { createPayShExecutor } from '@402flow/sdk-third-party-executors/pay-sh';
144
+ ```
54
145
 
55
- ## Publish
146
+ ## Further Reading
56
147
 
57
- The package is published from the repository root.
148
+ - [Detailed SDK guide](docs/sdk-guide.md)
149
+ - [Evaluation harness](docs/evaluation-harness.md)
150
+ - [Harness scenarios](docs/harness-scenarios.md)
151
+ - [Dexter and pay.sh executors](third-party-executors/README.md)
152
+ - [Publishing and release checks](docs/releasing.md)
@@ -0,0 +1,164 @@
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 stored in-memory 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 createExecutionPromise;
159
+ private handleExecutionRejection;
160
+ private getRecordForExecution;
161
+ private getKnownRecord;
162
+ private refreshRecordState;
163
+ private supersedeActiveRecords;
164
+ }