@402flow/sdk 0.1.0-alpha.22 → 0.1.0-alpha.24

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
@@ -132,6 +132,46 @@ If the merchant does not require payment for that exact request, the SDK returns
132
132
 
133
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
134
 
135
+ ### Optional Attribution
136
+
137
+ Most SDK users do not need to send attribution at all.
138
+
139
+ The optional `attribution` field is for callers that already know where this paid endpoint came from and want that provenance to survive into control-plane audit, reporting, and later policy analysis.
140
+
141
+ Common cases:
142
+
143
+ 1. your app found the endpoint through your own registry or catalog
144
+ 2. your app imported the endpoint from a saved workspace config
145
+ 3. your agent is calling a merchant directly and you want to mark that path as `direct`
146
+ 4. your host selected the endpoint from a discovery surface such as Bazaar, Dexter, pay.sh, x402scan, or another catalog
147
+
148
+ This field does not make payment execution work. It improves lifecycle explainability.
149
+
150
+ If you do not already know the endpoint provenance, omit it.
151
+
152
+ ```ts
153
+ const result = await client.fetchPaid(
154
+ 'https://merchant.example.com/reports/daily',
155
+ {
156
+ method: 'POST',
157
+ headers: {
158
+ 'content-type': 'application/json',
159
+ },
160
+ body: createJsonRequestBody({
161
+ date: '2026-03-25',
162
+ }),
163
+ },
164
+ {
165
+ description: 'sync daily paid report',
166
+ attribution: {
167
+ discoverySource: 'direct',
168
+ },
169
+ },
170
+ );
171
+ ```
172
+
173
+ For the first attribution slice, `discoverySource` is the main field callers should set when they have a clear answer. The control plane derives an early endpoint-level `resourceIdentity` from the request method and URL, so callers do not need to compute that themselves.
174
+
135
175
  ### Interpreting Merchant Responses
136
176
 
137
177
  The SDK gives you a stable place for payment metadata, but it does not invent a universal fulfilled-response schema for merchant content.
@@ -228,6 +268,13 @@ const prepared = await client.preparePaidRequest(
228
268
 
229
269
  `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
270
 
271
+ `attribution` is separate from `externalMetadata`.
272
+
273
+ 1. `externalMetadata` helps the SDK understand request shape before execution
274
+ 2. `attribution` helps the control plane explain where the paid endpoint came from after execution
275
+
276
+ Use `externalMetadata` for request hints. Use `attribution` for provenance. Either may be omitted.
277
+
231
278
  ### What `ready` Means
232
279
 
233
280
  `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.
@@ -256,6 +303,13 @@ const prepared = await client.preparePaidRequest(
256
303
  prompt: 'foggy coastline',
257
304
  }),
258
305
  },
306
+
307
+ const result = await client.executePreparedRequest(prepared, {
308
+ description: 'generate paid image',
309
+ attribution: {
310
+ discoverySource: 'manual',
311
+ },
312
+ });
259
313
  );
260
314
 
261
315
  if (prepared.kind === 'ready') {
@@ -270,6 +324,131 @@ if (prepared.kind === 'ready') {
270
324
 
271
325
  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.
272
326
 
327
+ ### Delegated Execution With Custom Executors
328
+
329
+ `executePreparedRequest()` now supports governed delegated execution through a caller-supplied executor interface.
330
+
331
+ This lets the SDK keep authorization, policy, receipts, and final outcome normalization in the 402flow control plane while handing the final paid merchant call to a provider-specific executor owned by the host app or a separate integration package.
332
+
333
+ The core SDK stays provider-neutral. That means third-party payment clients such as Dexter or pay.sh should live in the host app's own dependency graph, not inside `@402flow/sdk` itself.
334
+
335
+ The flow is:
336
+
337
+ 1. prepare the exact request locally with `preparePaidRequest()`
338
+ 2. ask the control plane to authorize delegated execution
339
+ 3. if authorized, let the supplied executor perform the actual paid merchant request
340
+ 4. finalize the normalized executor result back through the control plane
341
+ 5. return the same stable SDK result or `FetchPaidError` contract the caller already uses elsewhere
342
+
343
+ If the control plane denies authorization, the delegated executor is never invoked.
344
+
345
+ ```ts
346
+ import {
347
+ createJsonRequestBody,
348
+ type PreparedRequestExecutorInput,
349
+ type PreparedRequestExecutor,
350
+ } from '@402flow/sdk';
351
+
352
+ async function callDexter(
353
+ prepared: PreparedRequestExecutorInput['prepared'],
354
+ ) {
355
+ // Replace this with your real Dexter SDK call.
356
+ return {
357
+ status: 200,
358
+ body: { ok: true },
359
+ settlementReference: 'settlement-ref-1',
360
+ paymentReference: 'payment-ref-1',
361
+ };
362
+ }
363
+
364
+ function mapDexterResult(
365
+ prepared: PreparedRequestExecutorInput['prepared'],
366
+ result: Awaited<ReturnType<typeof callDexter>>,
367
+ ) {
368
+ return {
369
+ protocol: prepared.protocol,
370
+ executionStatus: 'succeeded' as const,
371
+ settlementEvidenceClass: 'settled' as const,
372
+ merchantOutcome: 'success_response' as const,
373
+ merchantResponse: {
374
+ status: result.status,
375
+ headers: {
376
+ 'content-type': 'application/json',
377
+ },
378
+ body: JSON.stringify(result.body),
379
+ },
380
+ settlementReference: result.settlementReference,
381
+ paymentReference: result.paymentReference,
382
+ };
383
+ }
384
+
385
+ const dexterExecutor: PreparedRequestExecutor = {
386
+ provider: 'dexter',
387
+ async execute({ prepared }) {
388
+ const dexterResult = await callDexter(prepared);
389
+ return mapDexterResult(prepared, dexterResult);
390
+ },
391
+ };
392
+
393
+ const prepared = await client.preparePaidRequest(
394
+ 'https://merchant.example.com/images/generate',
395
+ {
396
+ method: 'POST',
397
+ headers: {
398
+ 'content-type': 'application/json',
399
+ },
400
+ body: createJsonRequestBody({
401
+ prompt: 'foggy coastline',
402
+ }),
403
+ },
404
+ );
405
+
406
+ if (prepared.kind === 'ready') {
407
+ const result = await client.executePreparedRequest(prepared, {
408
+ description: 'generate image through delegated execution',
409
+ executionProvider: 'dexter',
410
+ executor: dexterExecutor,
411
+ });
412
+
413
+ console.log('paid response status:', result.response.status);
414
+ }
415
+ ```
416
+
417
+ This snippet shows the intended split directly: your host-owned code does the provider call and maps it into the delegated execution contract, while the SDK still owns authorize, finalize, and the outward result shape. For a real host-owned Dexter integration that performs the paid call, see `third-party-executors/examples/dexter-delegated-executor.mjs`.
418
+
419
+ Responsibility split:
420
+
421
+ 1. the SDK asks the control plane for delegated authorization
422
+ 2. if authorized, the SDK invokes your executor
423
+ 3. your executor performs the provider-specific paid request and returns `SdkDelegatedExecutionResult`
424
+ 4. the SDK finalizes that result with the control plane
425
+ 5. the SDK returns the same outward `PaidResponse` or `FetchPaidError` contract as the direct path
426
+
427
+ If your host app wants to execute through Dexter, pay.sh, or another provider, that integration should install and own the third-party SDK directly. `@402flow/sdk` only owns the executor contract and the governed authorize/finalize flow.
428
+
429
+ The repo keeps third-party executor proofs in the separate `third-party-executors/` package so the main `@402flow/sdk` install path stays provider-neutral.
430
+
431
+ For a repo-wide verification pass from the SDK root, run:
432
+
433
+ ```bash
434
+ npm run install:all
435
+ npm run check:all
436
+ ```
437
+
438
+ `npm run install:all` installs both the main SDK package and the separate `third-party-executors` package from the SDK root. `npm run check` still validates only the main SDK package. `npm run check:all` runs the main SDK checks first, then runs the separate `third-party-executors` package checks from the top level.
439
+
440
+ For a repo-local host-owned Dexter example, run:
441
+
442
+ ```bash
443
+ cd third-party-executors
444
+ npm install
445
+ export DEXTER_EVM_PRIVATE_KEY="..."
446
+
447
+ npm run example:dexter-delegated-executor -- \
448
+ "https://merchant.example.com/paid-endpoint" \
449
+ '{"topic":"sdk integration rollout","audience":"platform engineers","format":"bullets"}'
450
+ ```
451
+
273
452
  ## Prepared Result Semantics
274
453
 
275
454
  `preparePaidRequest()` separates request checking from paid execution.
@@ -423,8 +602,10 @@ When unset, first-party fixtures default to the self-hosted demo merchant at `ht
423
602
  ## Publish
424
603
 
425
604
  ```bash
426
- npm install
427
- npm run check
605
+ npm run install:all
606
+ npm run check:all
428
607
  npm run pack:check
429
608
  npm publish --access public
430
- ```
609
+ ```
610
+
611
+ `npm publish` now also runs `npm run check:all` through the root `prepublishOnly` hook, so the repo-wide test pass is enforced before publish even if you skip that step manually.