@itpay/cli 2.0.3 → 2.0.7

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.
Files changed (77) hide show
  1. package/README.md +96 -142
  2. package/dist/src/client/backend.js +26 -8
  3. package/dist/src/client/http.js +29 -23
  4. package/dist/src/commands/buy.js +84 -132
  5. package/dist/src/commands/cart.js +274 -169
  6. package/dist/src/commands/catalog.js +64 -38
  7. package/dist/src/commands/checkout.js +128 -79
  8. package/dist/src/commands/docs.js +97 -51
  9. package/dist/src/commands/guidance.js +112 -16
  10. package/dist/src/commands/install.js +50 -87
  11. package/dist/src/commands/next.js +45 -0
  12. package/dist/src/commands/order.js +44 -69
  13. package/dist/src/commands/orders.js +43 -15
  14. package/dist/src/commands/pay.js +51 -22
  15. package/dist/src/commands/readyz.js +8 -4
  16. package/dist/src/commands/refund.js +132 -11
  17. package/dist/src/commands/services.js +799 -147
  18. package/dist/src/commands/skill.js +55 -0
  19. package/dist/src/main.js +820 -193
  20. package/dist/src/render/output.js +2 -3
  21. package/dist/src/state/agent_type.js +19 -0
  22. package/dist/src/state/cart_session.js +13 -17
  23. package/dist/src/state/client_context.js +4 -2
  24. package/dist/src/state/config.js +5 -15
  25. package/dist/src/state/device_authority.js +175 -57
  26. package/docs/agent/buyer/cart-checkout.json +27 -83
  27. package/docs/agent/buyer/catalog-list.json +2 -1
  28. package/docs/agent/buyer/identity-and-sessions.json +64 -0
  29. package/docs/agent/buyer/install-and-setup.json +35 -65
  30. package/docs/agent/buyer/orders-refunds.json +31 -53
  31. package/docs/agent/buyer/payment-flow.json +28 -57
  32. package/docs/agent/buyer/quickstart.json +46 -161
  33. package/docs/agent/buyer/render-hosts.json +43 -57
  34. package/docs/cli-reference/agent-types.md +51 -0
  35. package/docs/cli-reference/commands/buy.md +167 -0
  36. package/docs/cli-reference/commands/cart/add.md +86 -0
  37. package/docs/cli-reference/commands/cart/clear.md +53 -0
  38. package/docs/cli-reference/commands/cart/index.md +30 -0
  39. package/docs/cli-reference/commands/cart/next.md +71 -0
  40. package/docs/cli-reference/commands/cart/remove.md +53 -0
  41. package/docs/cli-reference/commands/cart/show.md +65 -0
  42. package/docs/cli-reference/commands/catalog/index.md +26 -0
  43. package/docs/cli-reference/commands/catalog/list.md +45 -0
  44. package/docs/cli-reference/commands/checkout.md +74 -0
  45. package/docs/cli-reference/commands/device.md +13 -0
  46. package/docs/cli-reference/commands/docs/index.md +28 -0
  47. package/docs/cli-reference/commands/docs/list.md +51 -0
  48. package/docs/cli-reference/commands/docs/search.md +69 -0
  49. package/docs/cli-reference/commands/docs/show.md +68 -0
  50. package/docs/cli-reference/commands/install.md +114 -0
  51. package/docs/cli-reference/commands/next.md +87 -0
  52. package/docs/cli-reference/commands/order.md +92 -0
  53. package/docs/cli-reference/commands/orders.md +83 -0
  54. package/docs/cli-reference/commands/pay.md +103 -0
  55. package/docs/cli-reference/commands/readyz.md +38 -0
  56. package/docs/cli-reference/commands/refund/cancel.md +62 -0
  57. package/docs/cli-reference/commands/refund/create.md +85 -0
  58. package/docs/cli-reference/commands/refund/get.md +60 -0
  59. package/docs/cli-reference/commands/refund/index.md +33 -0
  60. package/docs/cli-reference/commands/refund/list.md +68 -0
  61. package/docs/cli-reference/commands/refund/watch.md +73 -0
  62. package/docs/cli-reference/commands/services/action.md +48 -0
  63. package/docs/cli-reference/commands/services/checkout.md +82 -0
  64. package/docs/cli-reference/commands/services/events.md +73 -0
  65. package/docs/cli-reference/commands/services/get.md +66 -0
  66. package/docs/cli-reference/commands/services/index.md +45 -0
  67. package/docs/cli-reference/commands/services/invoke.md +67 -0
  68. package/docs/cli-reference/commands/services/list.md +61 -0
  69. package/docs/cli-reference/commands/services/next.md +181 -0
  70. package/docs/cli-reference/commands/services/quote.md +63 -0
  71. package/docs/cli-reference/commands/services/read-result.md +98 -0
  72. package/docs/cli-reference/commands/services/start.md +55 -0
  73. package/docs/cli-reference/commands/skill.md +17 -0
  74. package/docs/cli-reference/conventions.md +97 -0
  75. package/docs/cli-reference/index.md +65 -0
  76. package/package.json +1 -1
  77. package/skills/itpay-buyer/SKILL.md +71 -110
package/README.md CHANGED
@@ -1,152 +1,106 @@
1
1
  # ItPay CLI
2
2
 
3
- The V3 terminal and agent-facing CLI for ItPay commerce.
3
+ The official V3 CLI for Agent-driven ItPay service discovery, checkout, delivery, order recovery, and refunds.
4
4
 
5
5
  ```bash
6
6
  npm install -g @itpay/cli
7
- itpay readyz
8
- itpay docs show quickstart
7
+ itpay readyz --json
8
+ itpay skill show itpay-buyer --json
9
+ itpay install --json
10
+ itpay --agent-type codex-desktop readyz --json
11
+ # follow next.command: typed skill show, then catalog list
9
12
  ```
10
13
 
11
- The CLI defaults to the production app at `https://app.itpay.ai`. Set
12
- `ITPAY_BACKEND_URL` only when intentionally using another backend.
13
-
14
- ## Commands
15
-
16
- - `itpay readyz` — `GET /v1/readyz`
17
- - `itpay next [--json]` — show the next recommended agent action from remembered server handles
18
- - `itpay cart add --item <id> --variant <id> --offer <id> [--quantity 1] [--input <json>] [--host <host>] [--json]` — create/update the canonical server cart; service-backed lines return `service_execution_id`
19
- - `itpay cart next [--json]` — show the next recommended action for the remembered canonical server cart
20
- - `itpay cart add --local ...` — explicit local draft compatibility mode only, not valid for service-backed flows
21
- - `itpay cart remove --line <cart_item_id>` — soft-remove an active line from the canonical server cart; quote-locked or checkout-bound lines are rejected
22
- - `itpay cart remove --local --variant <id> --offer <id>` — drop a local draft line
23
- - `itpay cart show` — print the canonical server cart, or local draft fallback when no server cart handle exists
24
- - `itpay cart clear` — abandon the canonical server cart and clear local handles
25
- - `itpay cart clear --local` — clear local handles/draft only
26
- - `itpay buy --cart <cart_id> --host <host> [--target <target>] [--qr-format ...] [--qr-file <path>]` create checkout from a canonical server cart and render the branded checkout QR for the host
27
- - `itpay buy --host <host> [--target <target>] [--item ... --variant ... --offer ...] [--quantity 1]` — compatibility one-shot cart + checkout path
28
- - `itpay services start <service_id>` — start a generic Service Execution run
29
- - `itpay services invoke <service_execution_id> --capability <capability_id> --input key=value` — invoke an agent-visible capability
30
- - `itpay services action <service_execution_id> --action <action_type> [--result-item <id>]` — record a human/agent service action
31
- - `itpay services checkout <service_execution_id> --capability <capability_id> [--email <email>] [--host <host>] [--json]` — create quote lock from Service Execution state, require email only for capabilities that deliver a claim link, persist the handoff, and render the branded ItPay checkout
32
- - `itpay services checkout <service_execution_id> --resume --json` — reissue a lost or expired handoff for the same unpaid checkout without asking for contact information again
33
- - `itpay services next <service_execution_id> [--json]` — show the next recommended action from the Service Execution read model
34
- - `itpay services get <service_execution_id>` / `itpay services events <service_execution_id>` — read the redacted Service Execution timeline
35
- - `itpay checkout --id <checkout_id> --token <display_token>` — read canonical checkout presentation
36
- - `itpay pay --checkout <id> --method alipay|wechatpay` — CLI escape hatch for operator/manual testing only; normal buyer flow opens the ItPay checkout page first
37
- - `itpay order <order_id>` — read one V3 order
38
- - `itpay orders [--limit 20] [--status <status>]` — list account-scoped orders (requires `ITPAY_BEARER_TOKEN`)
39
- - `itpay refund --order <id> [--reason <reason>]` — request a refund with an account-scoped `ITPAY_BEARER_TOKEN`; payment and amount are derived by the backend
40
-
41
- ## Hosts
42
-
43
- The CLI dispatches to a per-host renderer based on `--host`:
44
-
45
- | `--host` | Renderer | Native UI |
46
- | --- | --- | --- |
47
- | `terminal` | `render/terminal.ts` | terminal QR + summary |
48
- | `codex`, `claude-code` | `render/markdown.ts` | markdown image + links |
49
- | `telegram` | `render/telegram.ts` | openclaw `message send` with inline buttons |
50
- | `feishu`, `lark` | `render/feishu.ts` | Feishu/Lark interactive card (url + callback) |
51
- | `discord`, `whatsapp`, `plain-chat` | `render/plain_chat.ts` | text + links, no native buttons |
52
-
53
- Aliases: `tg` and `openclaw-telegram` map to `telegram`; `feishu_im` and `fs` map to `feishu`.
14
+ The default API is `https://app.itpay.ai`. Set `ITPAY_BACKEND_URL` only for an intentional test or local environment.
15
+
16
+ ## Output Contract
17
+
18
+ Normal JSON commands return one bounded envelope:
19
+
20
+ ```text
21
+ status current command state
22
+ result facts needed at this step
23
+ handoff optional human-visible URL/image fields for the current Host
24
+ instruction how the Agent must use the result
25
+ next zero or one preferred executable command
26
+ recovery exceptional recovery commands only
27
+ ```
28
+
29
+ Run `next.command` unchanged after filling only explicit placeholders or user-provided required fields. Do not inspect raw APIs or hardcode a service workflow.
30
+
31
+ Normative per-command contracts: [CLI Command Reference](docs/cli-reference/index.md).
32
+
33
+ ## Supported Agent Types
34
+
35
+ | Agent Type | Default Host |
36
+ | --- | --- |
37
+ | `codex-desktop` | `codex` |
38
+ | `codex-cli` | `terminal` |
39
+ | `claude-code-desktop` | `claude-code` |
40
+ | `claude-code-cli` | `terminal` |
41
+ | `workbuddy` | `plain-chat` |
42
+
43
+ `--agent-type` identifies the stable runtime and registered Agent instance. Every returned ItPay command preserves it. Different windows or chats of the same type reuse one Agent Instance; they are not separate identities. `--host` only selects the human presentation surface, and `--target` only routes output to a Host destination. Use `itpay install <agent_type> --json` for the exact responsibility.
44
+
45
+ The local installation keeps one Ed25519 private key. Each normalized Backend API base URL (`dev`, `test`, `app`, or local) has an independent server device registration, quota lineage, Agent instances, and sessions. A rejected session is renewed and the same request is retried once; revoked v2 registrations are never silently replaced.
46
+
47
+ ## Command Families
48
+
49
+ - `readyz`, `catalog list`: compatibility and discovery.
50
+ - `services start/invoke/action/checkout/next`: generic Service Execution flow.
51
+ - `cart add/show/remove/clear/next`, `buy`: canonical Cart and ordinary Checkout flow.
52
+ - `checkout`: authoritative payment and fulfillment recovery.
53
+ - `services read-result`: read one human-granted protected result.
54
+ - `order`, `orders`: exact order and account order views.
55
+ - `refund create/list/get/watch/cancel`: Refund Owner flow.
56
+ - `services get/events`: redacted support diagnostics; normal flows should use `services next`.
57
+ - `install`, `skill show`, `docs list/show/search`: offline packaged guidance.
58
+ - `pay`: operator escape hatch only; normal buyers use the ItPay Checkout page.
59
+
60
+ Run `itpay <command> --help` or browse [the command index](docs/cli-reference/index.md) for parameters.
61
+
62
+ ## Recovery
63
+
64
+ Use server-backed recovery before creating another resource:
65
+
66
+ ```bash
67
+ itpay --agent-type <agent_type> next --json
68
+ itpay --agent-type <agent_type> services list --json
69
+ itpay --agent-type <agent_type> services next <service_execution_id> --json
70
+ itpay --agent-type <agent_type> services checkout <service_execution_id> --resume --json
71
+ itpay checkout --id <checkout_id> --token <display_token> --json
72
+ ```
73
+
74
+ The local `~/.itpay-v3` directory stores one owner-only signing key, Backend-scoped Device registrations and Agent instances, idempotency operations, and recovery handles. Backend state remains authoritative. Do not delete or rotate this identity to recover quota.
54
75
 
55
76
  ## Environment
56
77
 
57
- - `ITPAY_BACKEND_URL` optional backend override (default `https://app.itpay.ai`)
58
- - `ITPAY_BEARER_TOKEN` account-scoped session token (only needed for `orders`)
59
- - `ITPAY_AGENT_DEVICE_ID` agent device id, used for cart/service execution quota identity and `client_context`
60
- - `ITPAY_CURRENCY` checkout currency (default `CNY`)
61
- - `ITPAY_IDEMPOTENCY_KEY` `Idempotency-Key` for pay/refund requests (auto-generated if unset)
62
- - `ITPAY_IDE_IMAGE_ATTACH` set to `0` to disable the IDE image-attach contract (e.g. read-only runner FS). Default `1`.
63
- - `ITPAY_IDE_IMAGE_DIR_OVERRIDE` override the canonical IDE image directory instead of `$TMPDIR/itpay-v3-qr`. Useful when the IDE file panel only knows one path.
64
-
65
- ## Agent next actions
66
-
67
- Service-backed flows return progressive guidance for agents:
68
-
69
- - `itpay cart add --json`, `itpay services start`, `itpay services invoke`,
70
- `itpay services action`, `itpay services get`, and
71
- `itpay services checkout --json` include top-level `next_actions`.
72
- - `itpay next`, `itpay cart next`, and `itpay services next <id>` print only
73
- the next recommended command and recovery commands.
74
- - The guidance is derived from server cart and Service Execution read models.
75
- Local `~/.itpay-v3/cart.json` only stores handles such as `cart_id`,
76
- `service_execution_id`, `checkout_id`, and `display_token`.
77
-
78
- Agents should prefer `next_actions` over hardcoded service-specific flows.
79
- For example, a service-backed cart add can return an invoke command for an
80
- agent-visible free capability, while a quote-locked execution can return the
81
- checkout handoff command.
82
-
83
- ## IDE image attach
84
-
85
- Every `itpay buy`, `itpay services checkout` (and `itpay checkout`,
86
- `itpay order`) downloads the
87
- brand checkout QR from the backend and writes it to a stable local
88
- file the agent can hand to the IDE image viewer (Trae `Read` tool,
89
- Codex, Claude Code). The contract is:
90
-
91
- - canonical file: `<os.tmpdir()>/itpay-v3-qr/itpay-v3-<kind>-<id>.png`
92
- (override with `ITPAY_IDE_IMAGE_DIR_OVERRIDE`)
93
- - when `/tmp/itpay-v3-qr` is a separate, writable location it gets a
94
- mirror of the same file under the same name
95
- - filename is stable per checkout, so re-runs overwrite the same
96
- local file rather than scattering copies across the scratch dir
97
-
98
- Outputs that carry the IDE image attach:
99
-
100
- - `itpay buy --json` — fields `brand_qr_local_path`, `brand_qr_mirrors`,
101
- `brand_qr_stable_name`, `brand_qr_status` (`downloaded` / `failed`
102
- / `disabled` / `fallback`), `brand_qr_error`, `brand_qr_data_url`,
103
- `brand_qr_must_render_reason`, `brand_qr_render_action`. Read the
104
- path with the IDE's `Read` tool so the human sees the picture.
105
- - `itpay services checkout --json` — same brand QR fields, plus
106
- `next_action: "open_human_checkout"` and the checkout-scoped
107
- `display_token`. Agents must show this ItPay checkout QR/URL to the
108
- human and must not call `itpay pay` for the normal buyer flow.
109
- - Markdown (Trae / Codex / Claude Code) — inlines a `data:image/png;base64,...`
110
- copy of the picture plus a `[ATTACH] IDE image` reference block that
111
- points at the canonical local path and mirrors.
112
- - Terminal — prints `Branded QR: /path/...png` and `QR mirrors: ...`;
113
- iTerm inline image renders the same file when the session supports it.
114
- - Telegram — the `ide_image_attach` block on `presentation.ide_image_attach`
115
- carries `status`, `local_path`, `mirrors`, `mime_type`, `source`,
116
- `caption`, `error` (when failed), `must_render_reason`, and a
117
- step-by-step `instructions` array.
118
- - Feishu / Lark — same `ide_image_attach` block on the
119
- `message.ide_image_attach` envelope.
120
-
121
- Disable the contract with `ITPAY_IDE_IMAGE_ATTACH=0` for runners on a
122
- read-only filesystem. The plan carries `status: "disabled"` instead
123
- of `status: "downloaded"` and no PNG is downloaded.
124
-
125
- ## Layout
126
-
127
- - `src/main.ts` — `commander` entrypoint, command registration
128
- - `src/client/` — HTTP/JSON client and DTOs
129
- - `src/commands/` — one file per command family
130
- - `src/render/` — terminal formatting
131
- - `plan.ts` — `RenderPlan` contract shared by all renderers
132
- - `qr.ts` — local QR + format selection
133
- - `terminal.ts`, `markdown.ts`, `plain_chat.ts`, `telegram.ts`, `feishu.ts` — per-host renderers
134
- - `index.ts` — `dispatchRender()` picks the right renderer
135
- - `sink.ts` — `OutputSink` so tests can silence stdout
136
- - `src/state/` — local CLI config, cart session, client context
137
- - `tests/` — node:test smoke test + in-process mock backend
138
-
139
- ## Rules
140
-
141
- - commands orchestrate user intent only
142
- - render code must not issue HTTP calls
143
- - keep API access under `src/client/`
144
- - persist checkout-scoped `display_token` and last server handles only in the owner-only local cart session file
145
- - the default `buy` command must not create a payment intent unless `--pay` is explicit
146
- - the default `services checkout` command must render the ItPay
147
- checkout handoff; provider payment intents are created by the human
148
- checkout page, not by the agent
149
- - a renderer must consume the brand QR the V3 backend hands back
150
- (`qr_payload` / `qr_png_url` / `mobile_wallet_url`) and only
151
- self-generate a QR for `auth_qr` / `checkout_qr` with the explicit
152
- `--qr-file` opt-in
78
+ - `ITPAY_BACKEND_URL`: deliberate API override.
79
+ - `ITPAY_AGENT_TYPE`: stable alternative to global `--agent-type`.
80
+ - `ITPAY_BEARER_TOKEN`: account-scoped Buyer session for account-only commands such as `orders`.
81
+ - `ITPAY_CART_SESSION_PATH`: local recovery-state path override.
82
+ - `ITPAY_CURRENCY`: ordinary Cart currency, default `CNY`.
83
+ - `ITPAY_IDEMPOTENCY_KEY`: explicit operation key for deterministic testing; normal use persists operation IDs automatically.
84
+ - `ITPAY_IDE_IMAGE_ATTACH=0`: disable local Checkout image download when the runtime filesystem is read-only.
85
+ - `ITPAY_IDE_IMAGE_DIR_OVERRIDE`: override the local Checkout image directory.
86
+
87
+ Provider credentials, Buyer identity, payment provider choice, amount, refund policy, quota, grant scope, and delivery access are never client-owned environment settings.
88
+
89
+ ## Development
90
+
91
+ ```bash
92
+ npm run lint
93
+ npm test
94
+ npm run test:package
95
+ npm run pack:dry-run
96
+ ```
97
+
98
+ Source boundaries:
99
+
100
+ - `src/main.ts`: parser and error-envelope wiring.
101
+ - `src/client`: typed HTTP access.
102
+ - `src/commands`: command orchestration and public output projection.
103
+ - `src/render`: Host presentation only; no business HTTP.
104
+ - `src/state`: local Device Authority, idempotency journal, and recovery handles.
105
+ - `docs/cli-reference`: normative command contracts.
106
+ - `docs/agent/buyer`: packaged progressive workflow guidance.
@@ -32,17 +32,16 @@ export class BackendClient {
32
32
  return this.http.delete(`/v1/carts/${encodeURIComponent(cartID)}`);
33
33
  }
34
34
  // --- Checkout ---
35
- createCheckout(input) {
36
- return this.http.post("/v1/checkouts", input);
35
+ createCheckout(input, idempotencyKey) {
36
+ return this.http.post("/v1/checkouts", input, idempotencyKey ? { idempotencyKey } : undefined);
37
37
  }
38
38
  getCheckoutPresentation(checkoutID, displayToken) {
39
39
  const qs = new URLSearchParams({ display_token: displayToken });
40
40
  return this.http.get(`/v1/checkouts/${encodeURIComponent(checkoutID)}/presentation?${qs}`);
41
41
  }
42
42
  // --- Payment intents ---
43
- createPaymentIntent(checkoutID, input, idempotencyKey) {
44
- const options = idempotencyKey ? { idempotencyKey } : {};
45
- return this.http.post(`/v1/checkouts/${encodeURIComponent(checkoutID)}/payment-intents`, input, options);
43
+ createPaymentIntent(checkoutID, input) {
44
+ return this.http.post(`/v1/checkouts/${encodeURIComponent(checkoutID)}/payment-intents`, input);
46
45
  }
47
46
  // --- SSE streaming ---
48
47
  streamCheckoutEvents(checkoutID, displayToken, onEvent, signal) {
@@ -54,6 +53,9 @@ export class BackendClient {
54
53
  getOrder(orderID) {
55
54
  return this.http.get(`/v1/orders/${encodeURIComponent(orderID)}`);
56
55
  }
56
+ getOrderDeliveryAccess(orderID) {
57
+ return this.http.get(`/v1/orders/${encodeURIComponent(orderID)}/delivery-access`);
58
+ }
57
59
  listAccountOrders(limit, status, bearer) {
58
60
  const qs = new URLSearchParams({ limit: String(limit) });
59
61
  if (status) {
@@ -63,9 +65,18 @@ export class BackendClient {
63
65
  }
64
66
  // --- Refund ---
65
67
  createRefund(orderID, input, bearer, idempotencyKey) {
66
- const options = { bearer, ...(idempotencyKey ? { idempotencyKey } : {}) };
68
+ const options = { ...(bearer ? { bearer } : {}), ...(idempotencyKey ? { idempotencyKey } : {}) };
67
69
  return this.http.post(`/v1/orders/${encodeURIComponent(orderID)}/refunds`, input, options);
68
70
  }
71
+ listOrderRefunds(orderID) {
72
+ return this.http.get(`/v1/orders/${encodeURIComponent(orderID)}/refunds`);
73
+ }
74
+ getRefund(refundRequestID) {
75
+ return this.http.get(`/v1/refunds/${encodeURIComponent(refundRequestID)}`);
76
+ }
77
+ cancelRefund(refundRequestID, reason = "buyer_cancelled") {
78
+ return this.http.post(`/v1/refunds/${encodeURIComponent(refundRequestID)}/cancel`, { reason });
79
+ }
69
80
  // --- Service Execution ---
70
81
  startServiceExecution(input) {
71
82
  return this.http.post("/v1/service-executions", input);
@@ -79,14 +90,21 @@ export class BackendClient {
79
90
  createServiceExecutionCheckout(serviceExecutionID, input) {
80
91
  return this.http.post(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/checkout`, input);
81
92
  }
93
+ prepareServiceQuote(serviceExecutionID, input) {
94
+ return this.http.post(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/quotes`, input);
95
+ }
82
96
  getServiceExecution(serviceExecutionID) {
83
97
  return this.http.get(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}`);
84
98
  }
85
99
  listServiceExecutions(limit = 50) {
86
100
  return this.http.get(`/v1/service-executions?limit=${limit}`);
87
101
  }
88
- listServiceExecutionEvents(serviceExecutionID) {
89
- return this.http.get(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/events`);
102
+ listServiceExecutionEvents(serviceExecutionID, afterSequence = 0, limit = 50) {
103
+ const query = new URLSearchParams({
104
+ after_sequence: String(afterSequence),
105
+ limit: String(limit),
106
+ });
107
+ return this.http.get(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/events?${query}`);
90
108
  }
91
109
  getGrantedServiceResult(serviceExecutionID) {
92
110
  return this.http.get(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/granted-result`);
@@ -17,6 +17,7 @@ export class HttpClient {
17
17
  fetchImpl;
18
18
  defaultHeaders;
19
19
  requestAuthorizer;
20
+ recoverAuthorization;
20
21
  constructor(config) {
21
22
  this.baseURL = config.baseURL.replace(/\/$/, "");
22
23
  this.fetchImpl = config.fetchImpl ?? globalThis.fetch;
@@ -26,35 +27,40 @@ export class HttpClient {
26
27
  ...(config.defaultHeaders ?? {}),
27
28
  };
28
29
  this.requestAuthorizer = config.requestAuthorizer;
30
+ this.recoverAuthorization = config.recoverAuthorization;
29
31
  }
30
32
  async request(path, options = {}) {
31
33
  const url = path.startsWith("http") ? path : this.baseURL + path;
32
- const headers = { ...this.defaultHeaders };
33
34
  const method = options.method ?? "GET";
34
35
  const body = options.body !== undefined ? JSON.stringify(options.body) : "";
35
- if (this.requestAuthorizer) {
36
- Object.assign(headers, await this.requestAuthorizer({ method, path: new URL(url).pathname + new URL(url).search, body }));
36
+ const requestPath = new URL(url).pathname + new URL(url).search;
37
+ for (let attempt = 0; attempt < 2; attempt += 1) {
38
+ const headers = { ...this.defaultHeaders };
39
+ if (this.requestAuthorizer) {
40
+ Object.assign(headers, await this.requestAuthorizer({ method, path: requestPath, body }));
41
+ }
42
+ if (options.bearer)
43
+ headers.Authorization = `Bearer ${options.bearer}`;
44
+ if (options.idempotencyKey)
45
+ headers["Idempotency-Key"] = options.idempotencyKey;
46
+ const response = await this.fetchImpl(url, {
47
+ method,
48
+ headers,
49
+ ...(options.body !== undefined ? { body } : {}),
50
+ ...(options.signal ? { signal: options.signal } : {}),
51
+ });
52
+ const text = await response.text();
53
+ const parsed = text.length > 0 ? safeParseJson(text) : undefined;
54
+ if (response.ok)
55
+ return parsed;
56
+ const error = new HttpError(response.status, parsed, `HTTP ${response.status}`);
57
+ if (attempt === 0 && error.status === 401 && error.code === "agent_device_session_required" && this.recoverAuthorization) {
58
+ await this.recoverAuthorization();
59
+ continue;
60
+ }
61
+ throw error;
37
62
  }
38
- if (options.bearer) {
39
- headers.Authorization = `Bearer ${options.bearer}`;
40
- }
41
- if (options.idempotencyKey) {
42
- headers["Idempotency-Key"] = options.idempotencyKey;
43
- }
44
- const requestInit = {
45
- method,
46
- headers,
47
- ...(options.body !== undefined ? { body } : {}),
48
- ...(options.signal ? { signal: options.signal } : {}),
49
- };
50
- const response = await this.fetchImpl(url, requestInit);
51
- const text = await response.text();
52
- const parsed = text.length > 0 ? safeParseJson(text) : undefined;
53
- if (!response.ok) {
54
- const errPayload = parsed;
55
- throw new HttpError(response.status, errPayload, `HTTP ${response.status}`);
56
- }
57
- return parsed;
63
+ throw new Error("unreachable HTTP retry state");
58
64
  }
59
65
  get(path, options = {}) {
60
66
  return this.request(path, { ...options, method: "GET" });