@402flow/sdk 0.1.0-alpha.23 → 0.1.0-alpha.25

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
@@ -324,6 +324,152 @@ if (prepared.kind === 'ready') {
324
324
 
325
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.
326
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
+ That package is a private repo-local set of reference adapters, not part of the published `@402flow/sdk` package itself.
432
+
433
+ Current repo-local adapters:
434
+
435
+ | Adapter | Current scope | Key dependencies |
436
+ | --- | --- | --- |
437
+ | Dexter | Host-owned delegated execution against Dexter's paid request client | `@dexterai/x402` |
438
+ | pay.sh | Host-owned x402 Solana exact delegated execution | `@x402/core`, `@x402/svm`, `@solana/kit` |
439
+
440
+ The pay.sh adapter intentionally does not depend on `@solana/pay` today. The current proof targets pay.sh's x402 Solana exact flow, so challenge parsing, signed payment payload creation, and paid response parsing come from `@x402/core` plus `@x402/svm`, while `@solana/kit` only supplies the signer. `@solana/pay` becomes relevant when the repo adds a separate Solana Pay or MPP-specific adapter path.
441
+
442
+ For a repo-wide verification pass from the SDK root, run:
443
+
444
+ ```bash
445
+ npm run install:all
446
+ npm run check:all
447
+ ```
448
+
449
+ `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.
450
+
451
+ For a repo-local host-owned Dexter example, run:
452
+
453
+ ```bash
454
+ cd third-party-executors
455
+ npm install
456
+ export DEXTER_EVM_PRIVATE_KEY="..."
457
+
458
+ npm run example:dexter-delegated-executor -- \
459
+ "https://merchant.example.com/paid-endpoint" \
460
+ '{"topic":"sdk integration rollout","audience":"platform engineers","format":"bullets"}'
461
+ ```
462
+
463
+ For a repo-local host-owned pay.sh x402 example, run:
464
+
465
+ ```bash
466
+ cd third-party-executors
467
+ npm install
468
+ npm run example:pay-sh-delegated-executor -- --help
469
+ ```
470
+
471
+ The pay.sh example expects the standard SDK auth environment plus a local Solana signer path in `PAY_SH_SOLANA_KEYPAIR_PATH`. Run the `--help` form first to see the full required environment and argument contract.
472
+
327
473
  ## Prepared Result Semantics
328
474
 
329
475
  `preparePaidRequest()` separates request checking from paid execution.
@@ -477,8 +623,10 @@ When unset, first-party fixtures default to the self-hosted demo merchant at `ht
477
623
  ## Publish
478
624
 
479
625
  ```bash
480
- npm install
481
- npm run check
626
+ npm run install:all
627
+ npm run check:all
482
628
  npm run pack:check
483
629
  npm publish --access public
484
- ```
630
+ ```
631
+
632
+ `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.