@stellar-agent-services/provider-kit 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +506 -2
- package/dist/approve/assets/index-BazIJAI2.css +1 -0
- package/dist/approve/assets/index-CL28cSJy.js +420 -0
- package/dist/approve/favicon.svg +1 -0
- package/dist/approve/index.html +18 -0
- package/dist/cli/approval-view.d.ts +80 -0
- package/dist/cli/approval-view.d.ts.map +1 -0
- package/dist/cli/approval-view.js +5 -0
- package/dist/cli/approval-view.js.map +1 -0
- package/dist/cli/approval.d.ts +132 -0
- package/dist/cli/approval.d.ts.map +1 -0
- package/dist/cli/approval.js +542 -0
- package/dist/cli/approval.js.map +1 -0
- package/dist/cli/check.d.ts +3 -0
- package/dist/cli/check.d.ts.map +1 -0
- package/dist/cli/check.js +137 -0
- package/dist/cli/check.js.map +1 -0
- package/dist/cli/dev-account.d.ts +28 -0
- package/dist/cli/dev-account.d.ts.map +1 -0
- package/dist/cli/dev-account.js +62 -0
- package/dist/cli/dev-account.js.map +1 -0
- package/dist/cli/dev.d.ts +27 -0
- package/dist/cli/dev.d.ts.map +1 -0
- package/dist/cli/dev.js +226 -0
- package/dist/cli/dev.js.map +1 -0
- package/dist/cli/discovery.d.ts +20 -0
- package/dist/cli/discovery.d.ts.map +1 -0
- package/dist/cli/discovery.js +202 -0
- package/dist/cli/discovery.js.map +1 -0
- package/dist/cli/env-file.d.ts +16 -0
- package/dist/cli/env-file.d.ts.map +1 -0
- package/dist/cli/env-file.js +77 -0
- package/dist/cli/env-file.js.map +1 -0
- package/dist/cli/fly.d.ts +28 -0
- package/dist/cli/fly.d.ts.map +1 -0
- package/dist/cli/fly.js +92 -0
- package/dist/cli/fly.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +11 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +3 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +170 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/launch.d.ts +21 -0
- package/dist/cli/launch.d.ts.map +1 -0
- package/dist/cli/launch.js +627 -0
- package/dist/cli/launch.js.map +1 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +158 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/owner-chain.d.ts +88 -0
- package/dist/cli/owner-chain.d.ts.map +1 -0
- package/dist/cli/owner-chain.js +284 -0
- package/dist/cli/owner-chain.js.map +1 -0
- package/dist/cli/owner-steps.d.ts +33 -0
- package/dist/cli/owner-steps.d.ts.map +1 -0
- package/dist/cli/owner-steps.js +121 -0
- package/dist/cli/owner-steps.js.map +1 -0
- package/dist/cli/processes.d.ts +47 -0
- package/dist/cli/processes.d.ts.map +1 -0
- package/dist/cli/processes.js +129 -0
- package/dist/cli/processes.js.map +1 -0
- package/dist/cli/reconcile.d.ts +3 -0
- package/dist/cli/reconcile.d.ts.map +1 -0
- package/dist/cli/reconcile.js +76 -0
- package/dist/cli/reconcile.js.map +1 -0
- package/dist/cli/register.d.ts +9 -0
- package/dist/cli/register.d.ts.map +1 -0
- package/dist/cli/register.js +209 -0
- package/dist/cli/register.js.map +1 -0
- package/dist/cli/request-validation.d.ts +5 -0
- package/dist/cli/request-validation.d.ts.map +1 -0
- package/dist/cli/request-validation.js +144 -0
- package/dist/cli/request-validation.js.map +1 -0
- package/dist/cli/serve.d.ts +25 -0
- package/dist/cli/serve.d.ts.map +1 -0
- package/dist/cli/serve.js +73 -0
- package/dist/cli/serve.js.map +1 -0
- package/dist/cli/shared.d.ts +142 -0
- package/dist/cli/shared.d.ts.map +1 -0
- package/dist/cli/shared.js +110 -0
- package/dist/cli/shared.js.map +1 -0
- package/dist/cli/status.d.ts +32 -0
- package/dist/cli/status.d.ts.map +1 -0
- package/dist/cli/status.js +248 -0
- package/dist/cli/status.js.map +1 -0
- package/dist/cli/target.d.ts +14 -0
- package/dist/cli/target.d.ts.map +1 -0
- package/dist/cli/target.js +43 -0
- package/dist/cli/target.js.map +1 -0
- package/dist/cli/trustline.d.ts +3 -0
- package/dist/cli/trustline.d.ts.map +1 -0
- package/dist/cli/trustline.js +114 -0
- package/dist/cli/trustline.js.map +1 -0
- package/dist/cli/try.d.ts +54 -0
- package/dist/cli/try.d.ts.map +1 -0
- package/dist/cli/try.js +448 -0
- package/dist/cli/try.js.map +1 -0
- package/dist/cli/tunnel.d.ts +13 -0
- package/dist/cli/tunnel.d.ts.map +1 -0
- package/dist/cli/tunnel.js +43 -0
- package/dist/cli/tunnel.js.map +1 -0
- package/dist/cli/validators.d.ts +6 -0
- package/dist/cli/validators.d.ts.map +1 -0
- package/dist/cli/validators.js +14 -0
- package/dist/cli/validators.js.map +1 -0
- package/dist/define-service.d.ts +71 -0
- package/dist/define-service.d.ts.map +1 -0
- package/dist/define-service.js +86 -0
- package/dist/define-service.js.map +1 -0
- package/dist/gateway/config.d.ts +88 -0
- package/dist/gateway/config.d.ts.map +1 -0
- package/dist/gateway/config.js +201 -0
- package/dist/gateway/config.js.map +1 -0
- package/dist/gateway/openapi.d.ts +69 -0
- package/dist/gateway/openapi.d.ts.map +1 -0
- package/dist/gateway/openapi.js +677 -0
- package/dist/gateway/openapi.js.map +1 -0
- package/dist/gateway/upstream.d.ts +15 -0
- package/dist/gateway/upstream.d.ts.map +1 -0
- package/dist/gateway/upstream.js +95 -0
- package/dist/gateway/upstream.js.map +1 -0
- package/dist/http/bounded.d.ts +3 -0
- package/dist/http/bounded.d.ts.map +1 -0
- package/dist/http/bounded.js +23 -0
- package/dist/http/bounded.js.map +1 -0
- package/dist/http/node-server.d.ts +15 -0
- package/dist/http/node-server.d.ts.map +1 -0
- package/dist/http/node-server.js +120 -0
- package/dist/http/node-server.js.map +1 -0
- package/dist/http/rate-limit.d.ts +11 -0
- package/dist/http/rate-limit.d.ts.map +1 -0
- package/dist/http/rate-limit.js +35 -0
- package/dist/http/rate-limit.js.map +1 -0
- package/dist/http/router.d.ts +29 -0
- package/dist/http/router.d.ts.map +1 -0
- package/dist/http/router.js +329 -0
- package/dist/http/router.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/jobs/memory-store.d.ts +3 -0
- package/dist/jobs/memory-store.d.ts.map +1 -0
- package/dist/jobs/memory-store.js +121 -0
- package/dist/jobs/memory-store.js.map +1 -0
- package/dist/jobs/reconcile.d.ts +22 -0
- package/dist/jobs/reconcile.d.ts.map +1 -0
- package/dist/jobs/reconcile.js +82 -0
- package/dist/jobs/reconcile.js.map +1 -0
- package/dist/jobs/recovery.d.ts +19 -0
- package/dist/jobs/recovery.d.ts.map +1 -0
- package/dist/jobs/recovery.js +65 -0
- package/dist/jobs/recovery.js.map +1 -0
- package/dist/jobs/runner.d.ts +38 -0
- package/dist/jobs/runner.d.ts.map +1 -0
- package/dist/jobs/runner.js +194 -0
- package/dist/jobs/runner.js.map +1 -0
- package/dist/jobs/scheduler.d.ts +66 -0
- package/dist/jobs/scheduler.d.ts.map +1 -0
- package/dist/jobs/scheduler.js +197 -0
- package/dist/jobs/scheduler.js.map +1 -0
- package/dist/jobs/sqlite-store.d.ts +8 -0
- package/dist/jobs/sqlite-store.d.ts.map +1 -0
- package/dist/jobs/sqlite-store.js +231 -0
- package/dist/jobs/sqlite-store.js.map +1 -0
- package/dist/jobs/store.d.ts +118 -0
- package/dist/jobs/store.d.ts.map +1 -0
- package/dist/jobs/store.js +36 -0
- package/dist/jobs/store.js.map +1 -0
- package/dist/manifest.d.ts +11 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +99 -0
- package/dist/manifest.js.map +1 -0
- package/dist/payments/chain.d.ts +23 -0
- package/dist/payments/chain.d.ts.map +1 -0
- package/dist/payments/chain.js +108 -0
- package/dist/payments/chain.js.map +1 -0
- package/dist/payments/facilitator.d.ts +44 -0
- package/dist/payments/facilitator.d.ts.map +1 -0
- package/dist/payments/facilitator.js +125 -0
- package/dist/payments/facilitator.js.map +1 -0
- package/dist/payments/onchain.d.ts +36 -0
- package/dist/payments/onchain.d.ts.map +1 -0
- package/dist/payments/onchain.js +191 -0
- package/dist/payments/onchain.js.map +1 -0
- package/dist/payments/payload-checks.d.ts +22 -0
- package/dist/payments/payload-checks.d.ts.map +1 -0
- package/dist/payments/payload-checks.js +113 -0
- package/dist/payments/payload-checks.js.map +1 -0
- package/dist/payments/settlement.d.ts +22 -0
- package/dist/payments/settlement.d.ts.map +1 -0
- package/dist/payments/settlement.js +65 -0
- package/dist/payments/settlement.js.map +1 -0
- package/dist/payments/x402-server.d.ts +8 -0
- package/dist/payments/x402-server.d.ts.map +1 -0
- package/dist/payments/x402-server.js +74 -0
- package/dist/payments/x402-server.js.map +1 -0
- package/dist/provider.d.ts +63 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +177 -0
- package/dist/provider.js.map +1 -0
- package/dist/registration.d.ts +86 -0
- package/dist/registration.d.ts.map +1 -0
- package/dist/registration.js +162 -0
- package/dist/registration.js.map +1 -0
- package/dist/testing.d.ts +13 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +48 -0
- package/dist/testing.js.map +1 -0
- package/dist/validation-request.d.ts +36 -0
- package/dist/validation-request.d.ts.map +1 -0
- package/dist/validation-request.js +101 -0
- package/dist/validation-request.js.map +1 -0
- package/package.json +55 -4
- package/schemas/gateway-config.schema.json +140 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stellar Agent Services contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,507 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @stellar-agent-services/provider-kit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Turn a function, or an existing HTTP API in any language, into a paid Stellar service that follows
|
|
4
|
+
[Provider Standard v1](../../docs/provider-standard.md). The kit covers:
|
|
5
|
+
|
|
6
|
+
- the service description (manifest), checked against the standard before the server starts
|
|
7
|
+
- pricing and the x402 USDC payment, with the payment checked locally before the facilitator sees it
|
|
8
|
+
- job recovery: a client that lost its response collects the result without paying again
|
|
9
|
+
- capacity: a queue per service, and `429`/`503` before payment when it is full
|
|
10
|
+
- durable jobs: paid jobs survive restarts, and workers can run in separate processes
|
|
11
|
+
- the CLI from nothing to live: run it with no wallet, pay it once, then register, verify and deploy it in one go
|
|
12
|
+
|
|
13
|
+
Two ways to use it:
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
library: defineService(...) -> createProvider(...) -> { fetch(request) } -> Hono / Node / Workers / Deno / Bun
|
|
17
|
+
gateway: provider-kit serve --config <file> -> forwards paid jobs to your existing API (also as a Docker image)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
- The provider runtime holds **no Stellar secret keys**; it only knows its `payTo` public address.
|
|
21
|
+
- Owner operations (the USDC trustline, 8004 registration, validation requests) run from the CLI and are
|
|
22
|
+
approved in your own wallet, on a page the CLI opens on your computer. See [Approving in your wallet](#approving-in-your-wallet).
|
|
23
|
+
|
|
24
|
+
The step-by-step guide is [docs/provider-setup-guide.md](../../docs/provider-setup-guide.md).
|
|
25
|
+
|
|
26
|
+
Installed from npm into a project of your own, add the override this repository has, which `create-stellar-service`
|
|
27
|
+
also writes, to `package.json`: `"overrides": { "@trionlabs/stellar8004": { "@stellar/stellar-sdk": "16.4.1" } }`.
|
|
28
|
+
The Stellar 8004 SDK the kit uses for registration asks for Stellar SDK 15. Without the override, npm installs a second
|
|
29
|
+
SDK for it: everything still works, but the copy adds 18 MB and an axios with known vulnerabilities.
|
|
30
|
+
|
|
31
|
+
## From nothing to live
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
provider-kit dev --config provider.config.json # runs it here, paid to a throwaway testnet account: no wallet yet
|
|
35
|
+
provider-kit try # pays for one call with test money (starts the provider if needed)
|
|
36
|
+
provider-kit launch --config provider.config.json # one page in your wallet: trustline, registration, verification
|
|
37
|
+
provider-kit status # where it stands, and the one command to run next
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- **`dev`** reads `.env`. With `PAY_TO` empty, it makes a [throwaway payout account](#the-throwaway-payout-account),
|
|
41
|
+
so nothing needs a wallet until the service goes live. `--with '<command>'` runs the API a gateway calls next to
|
|
42
|
+
it, and `-- <command>` runs any other provider, such as `provider-kit dev -- node dist/server.js`. `--tunnel` also
|
|
43
|
+
serves it at a temporary public HTTPS address (cloudflared).
|
|
44
|
+
- **`launch`** takes the public address from `--url`, `fly.toml` or an `https://` `PUBLIC_ORIGIN`, builds the
|
|
45
|
+
manifest that address will serve, and opens one approval page for every owner step. It saves `PAY_TO` and
|
|
46
|
+
`AGENT_ID`, deploys with `flyctl` when there is a `fly.toml` and you agree, and waits for the public manifest. See
|
|
47
|
+
[Launching](#launching).
|
|
48
|
+
- **`status`** reads this computer, the chain and the public address: running here, paid once, registered, live,
|
|
49
|
+
verified. `--json` prints it for scripts and coding agents.
|
|
50
|
+
|
|
51
|
+
`npm create stellar-service` makes a project with these as npm scripts ([create-stellar-service](../create-stellar-service)).
|
|
52
|
+
|
|
53
|
+
## Library in short
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { createProvider, defineService, sqliteStore } from "@stellar-agent-services/provider-kit";
|
|
57
|
+
|
|
58
|
+
const textStats = defineService({
|
|
59
|
+
name: "text-stats",
|
|
60
|
+
description: "Counts the words in a text.",
|
|
61
|
+
price: "0.01", // USDC
|
|
62
|
+
charge: "on-success", // charged only if the handler succeeds
|
|
63
|
+
expectedDurationSeconds: 1,
|
|
64
|
+
inputSchema: { type: "object", required: ["text"], properties: { text: { type: "string" } } },
|
|
65
|
+
outputSchema: { type: "object", required: ["words"], properties: { words: { type: "integer" } } },
|
|
66
|
+
handler: ({ text }: { text: string }) => ({ words: text.split(/\s+/).filter(Boolean).length }),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const provider = createProvider({
|
|
70
|
+
baseUrl: "https://api.example.com",
|
|
71
|
+
network: "stellar:testnet",
|
|
72
|
+
payTo: "G...", // public address only
|
|
73
|
+
facilitator: "https://x402.org/facilitator",
|
|
74
|
+
provider: { name: "Example provider" },
|
|
75
|
+
services: [textStats],
|
|
76
|
+
store: sqliteStore("./jobs.sqlite"),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await provider.ready(); // checks the facilitator, resumes jobs a restart left behind
|
|
80
|
+
// Mount provider.fetch on any Fetch-API server, or use the kit's own: await listen(provider, { port: 8787 })
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`createProvider()` throws if the manifest it builds would fail the standard's checks (the JSON Schema and
|
|
84
|
+
rules M1–P11, examples against their schemas), so a broken provider never takes a payment. The manifest is
|
|
85
|
+
served at `/.well-known/stellar-agent-services.json`. Create-job, check-status and get-result are under
|
|
86
|
+
`baseUrl`, with CORS open for browser clients.
|
|
87
|
+
|
|
88
|
+
### When the user is charged
|
|
89
|
+
|
|
90
|
+
| `charge` | Order | If the work fails |
|
|
91
|
+
| --- | --- | --- |
|
|
92
|
+
| `on-success` | verify payment → work → settle | Not charged. The work must finish before the signed payment expires, so keep it under half of `maxTimeoutSeconds` (default 60). |
|
|
93
|
+
| `upfront` (default) | verify → settle → queue → work | Charged. Use it for long or expensive work. |
|
|
94
|
+
|
|
95
|
+
A handler gets `{ signal, jobId }`. `signal` aborts when the job runs out of time (`timeoutSeconds`, default
|
|
96
|
+
twice `expectedDurationSeconds`). An `upfront` job whose worker died is run again, so a handler with side
|
|
97
|
+
effects can use `jobId` to do them once.
|
|
98
|
+
|
|
99
|
+
## Gateway mode
|
|
100
|
+
|
|
101
|
+
For an API that already exists, in any language, write a config file instead of code:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"$schema": "./node_modules/@stellar-agent-services/provider-kit/schemas/gateway-config.schema.json",
|
|
106
|
+
"baseUrl": "https://api.example.com",
|
|
107
|
+
"network": "stellar:testnet",
|
|
108
|
+
"payTo": "env:PAY_TO",
|
|
109
|
+
"facilitator": "https://x402.org/facilitator",
|
|
110
|
+
"provider": { "name": "Example provider" },
|
|
111
|
+
"store": "./jobs.sqlite",
|
|
112
|
+
"services": [
|
|
113
|
+
{
|
|
114
|
+
"name": "summarize",
|
|
115
|
+
"description": "Summarizes English text up to 20,000 characters.",
|
|
116
|
+
"price": "0.05",
|
|
117
|
+
"charge": "upfront",
|
|
118
|
+
"expectedDurationSeconds": 20,
|
|
119
|
+
"inputSchema": "./schemas/summarize.input.json",
|
|
120
|
+
"outputSchema": "./schemas/summarize.output.json",
|
|
121
|
+
"capacity": { "concurrency": 2, "maxQueueWaitSeconds": 120 },
|
|
122
|
+
"upstream": { "url": "http://127.0.0.1:5000/summarize", "headers": { "Authorization": "env:UPSTREAM_TOKEN" } }
|
|
123
|
+
}
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
PAY_TO=G... provider-kit serve --config provider.config.json
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
- The gateway POSTs the validated input to `upstream.url` as JSON, with `Idempotency-Key: <job id>`. A 2xx
|
|
133
|
+
JSON object is the output, checked against `outputSchema`. The upstream never sees payment data or job
|
|
134
|
+
tokens.
|
|
135
|
+
- Busy upstreams (`429`, `502`–`504`, connection errors) are retried within the job's time, honoring
|
|
136
|
+
`Retry-After`. Set `capacity.concurrency` to what the upstream takes.
|
|
137
|
+
- Values written `"env:NAME"` come from the environment: the upstream's API key, `payTo`, `baseUrl`.
|
|
138
|
+
- [examples/python-upstream](../../examples/python-upstream) is a Python API behind the gateway.
|
|
139
|
+
|
|
140
|
+
**Docker:** each release publishes the gateway as `ghcr.io/algorialabs/provider-kit:<version>`, such as `0.1.0`
|
|
141
|
+
(`docker build -f packages/provider-kit/Dockerfile .` from a clone builds the same image). Mount the config at
|
|
142
|
+
`/config/provider.config.json` and a volume at `/data` (set `"store": "/data/jobs.sqlite"`). `GET /healthz` answers
|
|
143
|
+
for health checks.
|
|
144
|
+
|
|
145
|
+
### From an OpenAPI document
|
|
146
|
+
|
|
147
|
+
An API with an OpenAPI 3.0, 3.1 or 3.2 document doesn't need its config written by hand:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
provider-kit init --openapi http://127.0.0.1:8000/openapi.json # FastAPI's; or a .json or .yaml file
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
```text
|
|
154
|
+
Wrote provider.config.json with 4 services:
|
|
155
|
+
summarize POST http://127.0.0.1:8000/summarize
|
|
156
|
+
translate POST http://127.0.0.1:8000/v1/translate with X-API-Key
|
|
157
|
+
…
|
|
158
|
+
Skipped 7 operations:
|
|
159
|
+
GET /health the gateway calls the API with POST
|
|
160
|
+
POST /documents/{document_id}/summarize it has a path parameter ({document_id}); the gateway calls one fixed URL
|
|
161
|
+
POST /upload it takes multipart/form-data, not JSON
|
|
162
|
+
…
|
|
163
|
+
✓ The config passes Provider Standard v1, checked as provider-kit serve checks it.
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
- **Which operations:** each `POST` that takes a JSON object (or no body) and answers `2xx` with a JSON object, and
|
|
167
|
+
has no path parameters and no required query, header or cookie parameters: what the gateway can call. Every other
|
|
168
|
+
operation is listed with the reason. A provider has at most 25 services.
|
|
169
|
+
- **Schemas** become JSON Schema 2020-12 [S4]–[S6]: the body's own schema at the root, the components it uses under
|
|
170
|
+
`$defs`, recursion included. OpenAPI 3.0's `nullable`, `example` and boolean `exclusiveMinimum`/`exclusiveMaximum`
|
|
171
|
+
are rewritten, `readOnly` properties leave the input and `writeOnly` ones the output.
|
|
172
|
+
- **From the document:** names from paths (`/v1/text/summarize` is `text-summarize`), titles from summaries,
|
|
173
|
+
descriptions from descriptions (`init` lists the services that have none), tags, and an example when the document
|
|
174
|
+
has a matching input and output.
|
|
175
|
+
- **The API's address:** `--upstream`, else the document's server, else where the document was read from.
|
|
176
|
+
- **The API's own credentials** (an API key header, bearer or basic) become an `upstream.headers` entry read from
|
|
177
|
+
the environment, such as `"X-API-Key": "env:UPSTREAM_X_API_KEY"`. Credentials that can't be a fixed header
|
|
178
|
+
(OAuth2, cookies) get a warning to set by hand.
|
|
179
|
+
- **Checked as `serve` checks it:** a service the kit would refuse, such as a pattern JavaScript can't compile or a
|
|
180
|
+
schema over 32 KiB [S6], is skipped with the problem, so the config always loads.
|
|
181
|
+
|
|
182
|
+
Price (`0.01`), charge (`upfront`), duration and capacity are first guesses: set them before you serve.
|
|
183
|
+
|
|
184
|
+
## CLI
|
|
185
|
+
|
|
186
|
+
| Command | What it does |
|
|
187
|
+
| --- | --- |
|
|
188
|
+
| `provider-kit dev [--config <file>] [--with '<command>'] [--tunnel] [-- <command> ...]` | Runs a provider on this computer, on testnet: a gateway config (default `./provider.config.json`) in dev mode, or your provider's own command after `--`. Reads `.env`. With `PAY_TO` empty, makes a [throwaway payout account](#the-throwaway-payout-account). `--with` starts the API the gateway calls and waits for it to take connections; `--tunnel` adds a temporary public HTTPS address. |
|
|
189
|
+
| `provider-kit launch [--config <file> \| --start '<command>' \| --manifest <url>] [--url <https origin>]` | Takes it live: one approval page for the trustline, the 8004 registration (or its update) and a validation request, then saves `PAY_TO` and `AGENT_ID`, deploys with `flyctl` (`--deploy`, `--no-deploy`) and waits for the public manifest (`--no-wait` skips it). `--validator <G…>` names the verifier, `--no-validation` asks none. See [Launching](#launching). |
|
|
190
|
+
| `provider-kit status [--config <file>] [--url <https origin>] [--json]` | Where the provider stands: running here, payTo, paid once, registered [I1]–[I3], live, verified. Names the one command to run next. Reads only. |
|
|
191
|
+
| `provider-kit serve --config <file>` | Gateway mode. Graceful shutdown on SIGTERM. `--no-workers` for HTTP-only processes. |
|
|
192
|
+
| `provider-kit init [--upstream <url>] [--openapi <file \| url>]` | Writes a starter gateway config. With `--openapi`, writes one from an OpenAPI 3 document: see [From an OpenAPI document](#from-an-openapi-document). `--json` prints what it wrote and skipped as JSON. |
|
|
193
|
+
| `provider-kit check (--config <file> \| --manifest <url>)` | Self-check without paying: manifest, facilitator, `payTo` trustline [P2], 8004 registration [I1]–[I3]. A provider on this computer that isn't registered yet passes with a note. Exits 1 on any failure. `--manifest` works for any provider, library or native. Then shows [how agents see you](#how-agents-see-you). `--fix` writes the words search would miss into the config's `provider.description`; `--json` prints the result as JSON. |
|
|
194
|
+
| `provider-kit try [<service>] ['<input JSON>' \| @<file>]` | One paid call on testnet, the way any client makes it. Starts the provider for the call when nothing answers. See [Trying a paid call](#trying-a-paid-call). |
|
|
195
|
+
| `provider-kit trustline [--config \| --manifest \| --network <testnet\|mainnet>]` | Adds a USDC trustline to `payTo` [P2], approved in its wallet. A new testnet account gets test XLM from Friendbot first. Without `--config` or `--manifest`, it sets up the account the wallet connects and prints it as `PAY_TO`. Signs nothing when the trustline exists. |
|
|
196
|
+
| `provider-kit register (--config \| --manifest)` | Registers in the Stellar 8004 identity registry from the `payTo` account, or updates the registration from the agent's owner, approved in the wallet. Adds the USDC trustline first when it is missing, and saves the new `agentId` to `--config` [I2]. |
|
|
197
|
+
| `provider-kit request-validation (--config \| --manifest) --validator <G…>` | Asks a verifier for validation in the Stellar 8004 Validation Registry. Checks first that a verifier can validate the provider ([I1]–[I3]), that the connected account may ask for the agent, and that the validator is not the provider itself. Puts the request document on chain as a `data:` URI, has the owner's wallet sign it and prints the request hash for the verifier. `--out` writes the request document. |
|
|
198
|
+
| `provider-kit reconcile (--config <file> \| --store <file> --network <net>)` | Decides `payment_uncertain` jobs from on-chain data [J13]. `--dry-run` only reports; `--rpc <url>` picks the Soroban RPC. It reads the chain, writes only the job store and signs nothing, so it is safe next to a running provider. Exits 1 while a job is undecided. |
|
|
199
|
+
|
|
200
|
+
`launch` runs what `trustline`, `register` and `request-validation` run one at a time. Those three take
|
|
201
|
+
`--source <key>` to sign with the [Stellar CLI](https://developers.stellar.org/docs/tools/cli) keychain instead of a
|
|
202
|
+
wallet, for scripts and CI. With `--source`, `--dry-run` prints the Stellar CLI command. `--rpc <url>` picks the
|
|
203
|
+
Soroban RPC.
|
|
204
|
+
|
|
205
|
+
### The throwaway payout account
|
|
206
|
+
|
|
207
|
+
A provider has to be paid to an account that holds a USDC trustline [P2]. During development that needn't be yours:
|
|
208
|
+
with `PAY_TO` empty, `provider-kit dev` (and `try`, when it starts a gateway) makes a new testnet account, gets it
|
|
209
|
+
test XLM from Friendbot and signs its trustline with a key that lives in memory only. Then the key is dropped. It is
|
|
210
|
+
never written to a file or the environment, and the provider never sees it: like any provider, it knows only the
|
|
211
|
+
public address.
|
|
212
|
+
|
|
213
|
+
The address goes in `.env` as `PAY_TO`, and as `DEV_PAY_TO`, so `launch` can tell it from an account of yours and
|
|
214
|
+
replace it. What it receives is test money nobody can spend. It is never made on Mainnet.
|
|
215
|
+
|
|
216
|
+
### Launching
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
provider-kit launch --config provider.config.json # a gateway, its address from fly.toml
|
|
220
|
+
provider-kit launch --start 'node dist/server.js' --url https://api.example.com
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
```text
|
|
224
|
+
Launching Text Tools on Stellar Testnet
|
|
225
|
+
manifest https://text-tools.fly.dev/.well-known/stellar-agent-services.json
|
|
226
|
+
services summarize, translate
|
|
227
|
+
|
|
228
|
+
Approve in your wallet
|
|
229
|
+
http://localhost:8790/#…
|
|
230
|
+
✓ Connected GBQX…7KQM (Freighter)
|
|
231
|
+
✓ Get test XLM: funded by Friendbot
|
|
232
|
+
✓ Add USDC: USDC trustline added · https://stellar.expert/explorer/testnet/tx/…
|
|
233
|
+
✓ Register: agent 42 · https://stellar.expert/explorer/testnet/tx/…
|
|
234
|
+
✓ Request validation: request 3f0a9c41d2e7… · https://stellar.expert/explorer/testnet/tx/…
|
|
235
|
+
✓ Saved PAY_TO=GBQX…, AGENT_ID=42 in .env
|
|
236
|
+
✓ Saved PUBLIC_ORIGIN, PAY_TO, AGENT_ID in fly.toml's [env], for every deploy
|
|
237
|
+
|
|
238
|
+
✓ Text Tools is agent 42 on Stellar Testnet, paid to GBQX…7KQM
|
|
239
|
+
✓ Validation requested from GBRP…OX2H (request 3f0a9c41d2e7…): its answer lands in the Validation Registry
|
|
240
|
+
|
|
241
|
+
Deploy to Fly.io now (fly deploy, app text-tools)? [Y/n]
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
- **The address before the deploy.** `--url`, else `fly.toml`'s app (`https://<app>.fly.dev`), else an `https://`
|
|
245
|
+
`PUBLIC_ORIGIN`. A quick tunnel's address is refused: it changes every run.
|
|
246
|
+
- **The manifest that address will serve**, checked against the standard: from the gateway config, from your
|
|
247
|
+
provider's own command (`--start`, run on a free port with `PUBLIC_ORIGIN`, `PAY_TO` and a throwaway job database),
|
|
248
|
+
or from a deployed provider (`--manifest`). Until a wallet connects, a stand-in `payTo` builds it: the registration
|
|
249
|
+
doesn't name `payTo`, and the registry makes the registering account the agent's wallet [I3].
|
|
250
|
+
- **One page.** The steps the chain still needs, in order: test XLM, the trustline, `register_with_uri` (or
|
|
251
|
+
`set_agent_uri` when the manifest moved), and `validation_request`, built once the agent id is known. The verifier
|
|
252
|
+
is `--validator`, `STELLAR_VALIDATOR`, or the default in `src/cli/validators.ts`; a provider's own account is never
|
|
253
|
+
asked.
|
|
254
|
+
- **Saved.** `PAY_TO` and `AGENT_ID` in `.env` (`DEV_PAY_TO` goes), the agent id where the gateway config reads it,
|
|
255
|
+
and the public settings in `fly.toml`. For a provider started by its own command, that includes `STELLAR_NETWORK`
|
|
256
|
+
(and `FACILITATOR_URL` when set), so it takes payments where it registered. A new agent's id is saved even if a later
|
|
257
|
+
step stops, so running `launch` again never registers a second agent; it signs nothing when everything is in place.
|
|
258
|
+
- **Deployed and checked.** `flyctl` creates the app the first time, and what may hold a key is staged as Fly secrets
|
|
259
|
+
from `.env`: every variable a gateway config reads, such as the API's key (one that isn't set is named, since the
|
|
260
|
+
gateway can't start without it), or a command's `FACILITATOR_AUTH` and `SOROBAN_RPC_URL`. `fly deploy` runs with one
|
|
261
|
+
machine. Then `launch` waits until the public manifest serves the agent id, on the network it registered on, and
|
|
262
|
+
checks [I1]–[I3] from outside. A gateway whose `upstream.url` is on 127.0.0.1 isn't deployed: in a container
|
|
263
|
+
that is the container itself, unless the API runs inside it too, as the Python kind's does.
|
|
264
|
+
- **Mainnet.** The same steps, on `stellar:pubnet`: the wallet's account needs a few XLM, as no Friendbot funds it.
|
|
265
|
+
Mainnet's facilitator, OpenZeppelin's (`https://channels.openzeppelin.com/x402`), wants an API key: a gateway config
|
|
266
|
+
sends it with `"facilitatorHeaders": { "Authorization": "env:FACILITATOR_AUTH" }`. The provider checks it at start,
|
|
267
|
+
and a 401 says so. `npm create stellar-service -- --network mainnet` sets all of this up
|
|
268
|
+
([Mainnet](../create-stellar-service/README.md#mainnet)).
|
|
269
|
+
|
|
270
|
+
### Trying a paid call
|
|
271
|
+
|
|
272
|
+
`provider-kit try` pays for one job on testnet, as a client would, so you see the whole flow work before anyone
|
|
273
|
+
else pays:
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
provider-kit try text-stats # the service's example input
|
|
277
|
+
provider-kit try text-stats '{"text":"Hello"}' # or your own, or @input.json
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
```text
|
|
281
|
+
✓ Test XLM from Friendbot
|
|
282
|
+
✓ Test USDC from the testnet DEX: 0.01 USDC
|
|
283
|
+
✓ text-stats succeeded: 10.2 s from payment to result
|
|
284
|
+
✓ Paid 0.01 USDC, confirmed on chain: https://stellar.expert/explorer/testnet/tx/0051061a…cda9
|
|
285
|
+
|
|
286
|
+
{
|
|
287
|
+
"words": 2,
|
|
288
|
+
"sentences": 1,
|
|
289
|
+
"characters": 12
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
GDJB…XA3O (payTo) received 0.01 USDC.
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
- **The provider:** `--manifest <url>` for any provider, or `--config` for a gateway, read for its `baseUrl` only.
|
|
296
|
+
Without either, `./provider.config.json` when it exists, else `http://localhost:8787`. With several services and
|
|
297
|
+
none named, it lists them.
|
|
298
|
+
- **Not running?** When nothing answers, `try` starts the provider for the call and stops it after: the gateway
|
|
299
|
+
config in its own process (with a throwaway `payTo` when `PAY_TO` is empty), or `--start '<command>'`, such as
|
|
300
|
+
`--start 'npm run dev'`.
|
|
301
|
+
- **The payer:** a throwaway test account, made for this call. It gets test XLM from Friendbot and exactly the price
|
|
302
|
+
in test USDC from the testnet DEX. Its key stays in memory and is never written anywhere. You need no payer wallet.
|
|
303
|
+
- **The payment:** the payment client's own flow, with every check. The exact price, payee and network are shown and
|
|
304
|
+
approved first (`--yes` skips the question, for scripts), and its spending policy allows that one payment only.
|
|
305
|
+
The transfer is confirmed on chain [J16].
|
|
306
|
+
- **Testnet only.** A provider on Mainnet is refused before anything is signed.
|
|
307
|
+
|
|
308
|
+
`--json` prints the outcome as JSON on standard output (the job, its status, the payment and the output) and the
|
|
309
|
+
steps on standard error. It exits 0 only when the job succeeded and its payment is confirmed. If the answer to the paid request is lost, it
|
|
310
|
+
says the payment may have gone through and never calls it unpaid: your provider decides it from the chain
|
|
311
|
+
[J13]. If the testnet DEX has no USDC for sale, nothing is paid: try again later, or pay with `stellar-pay` from a
|
|
312
|
+
test wallet of your own, funded at [faucet.circle.com](https://faucet.circle.com) (setup guide §6).
|
|
313
|
+
|
|
314
|
+
### How agents see you
|
|
315
|
+
|
|
316
|
+
`provider-kit check` ends with the provider as agents meet it, and hints that never fail the check:
|
|
317
|
+
|
|
318
|
+
```text
|
|
319
|
+
How agents see you
|
|
320
|
+
Search matches this name and description, which provider-kit launch publishes:
|
|
321
|
+
Text Tools API
|
|
322
|
+
Summaries, translations and labels for English text.
|
|
323
|
+
· On testnet it also matches your services' names, titles, descriptions and tags, once the manifest is live
|
|
324
|
+
and names the agent [I1]–[I3].
|
|
325
|
+
! translate: a 7-word description, no example
|
|
326
|
+
! classify-tree: a 3-word description, no example
|
|
327
|
+
An agent picks a service by its description: what it does, when to use it, its limits, with no price or
|
|
328
|
+
URL [§3.4]. An example shows it what to send, and provider-kit try pays with it.
|
|
329
|
+
· On mainnet, search reads no service names, titles or tags: agents searching there for "summarize",
|
|
330
|
+
"translate", "classify", "tree", "random" or "fact" wouldn't find you. provider-kit check --fix puts those
|
|
331
|
+
words in provider.description.
|
|
332
|
+
· Ranking counts reviews from distinct paying clients only. Until the first, search lists you as new and
|
|
333
|
+
unrated.
|
|
334
|
+
· Not registered yet: provider-kit launch publishes this name and description.
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
- **Search** (stellar-agent-search's `find_agent`, and 8004 explorers) matches a query against the registration: the
|
|
338
|
+
name and description `launch` publishes from `provider.name` and `provider.description`, and the registration's
|
|
339
|
+
services. On testnet, stellar-agent-search 0.2.0 reads agents straight from the registry and also matches a bound
|
|
340
|
+
manifest's services: their names, titles, descriptions and tags. On mainnet, its explorer lists no services, so
|
|
341
|
+
only the name and description count: there, a word from a service's name, title or tags that the name and
|
|
342
|
+
description miss is a warning, and `check --fix` writes the missing words into a gateway config's description.
|
|
343
|
+
Words are matched the way stellar-agent-search 0.2.0 matches them: folded, its stopwords dropped, a crude stem,
|
|
344
|
+
then a substring test.
|
|
345
|
+
- **Once registered,** it shows the name and description the registry has, says when they are older than the
|
|
346
|
+
manifest (`launch` updates them), and prints the stellar-agent-search command that shows the live profile.
|
|
347
|
+
- **Each service:** a description too short to say what it does, when to use it and its limits, a price or URL
|
|
348
|
+
in it [§3.4], or no example.
|
|
349
|
+
|
|
350
|
+
### Approving in your wallet
|
|
351
|
+
|
|
352
|
+
Without `--source`, an owner command (`launch`, `trustline`, `register`, `request-validation`) opens a page on this
|
|
353
|
+
computer, `http://localhost:8790` (or any free port), where you connect the wallet you already use and approve each
|
|
354
|
+
transaction:
|
|
355
|
+
|
|
356
|
+
1. **Connect.** Freighter, xBull, Albedo, Rabet, Hana, MetaMask, Ledger and the other wallets
|
|
357
|
+
[Stellar Wallets Kit](https://stellarwalletskit.dev) supports without extra setup, except LOBSTR: its connector
|
|
358
|
+
is GPL-3.0, and the page ships in this MIT package. The page names the account the command needs, such as
|
|
359
|
+
`payTo`, and refuses another one before anything is built.
|
|
360
|
+
2. **Review.** Each transaction in plain words: what it does, every argument, the network, the most it can cost in
|
|
361
|
+
fees and how long it stays valid. The raw hash and XDR are there to compare with what the wallet shows.
|
|
362
|
+
3. **Approve** in the wallet. The CLI checks that what came back is that very transaction, signed by the connected
|
|
363
|
+
account for this network, then sends it and waits for the ledger. Your terminal shows each step as it lands.
|
|
364
|
+
|
|
365
|
+
How it stays safe:
|
|
366
|
+
|
|
367
|
+
- **No key in the kit.** The wallet signs. The CLI builds, checks and sends, and never sees a key.
|
|
368
|
+
- **The page answers only you.** It listens on 127.0.0.1 only. Its API needs a one-time token that reaches the page
|
|
369
|
+
in the link's fragment, and refuses other `Host` and `Origin` headers, so no website and no DNS rebinding can drive
|
|
370
|
+
it. The page runs only its own bundled scripts under a strict Content Security Policy.
|
|
371
|
+
- **Nothing is sent twice.** Each transaction is built when it is due. A refused one can be prepared again. One whose
|
|
372
|
+
fate is unknown stops the session with its hash, to look up before trying again.
|
|
373
|
+
- **Ctrl+C cancels**, after the step under way finishes. Over SSH, `--no-open` prints the link without opening a
|
|
374
|
+
browser: forward its port. `--port <n>` picks the port.
|
|
375
|
+
|
|
376
|
+
The page's source is in `approve/` (Svelte), built into `dist/approve` by `npm run build`. `npm run dev:approve`
|
|
377
|
+
serves it alone for design work.
|
|
378
|
+
|
|
379
|
+
## Payment safety
|
|
380
|
+
|
|
381
|
+
- **Local payment checks.** Before the facilitator sees a payment, the kit decodes it: exactly one USDC
|
|
382
|
+
`transfer(payer, payTo, price)`, authorized by the payer's own signed auth entry and nothing else. A payment
|
|
383
|
+
to another address, amount or asset is refused with a `402`. The signature itself is the facilitator's
|
|
384
|
+
check, since only a simulation can verify it for every kind of account.
|
|
385
|
+
- **One payment, one job.** The fingerprint is the payer's auth-entry nonce, which Soroban accepts once, so a
|
|
386
|
+
signed payment can't buy two jobs even re-wrapped in another transaction [P9].
|
|
387
|
+
- **Uncertain is never failed.** A settlement counts as failed only when the facilitator says it never
|
|
388
|
+
submitted the transaction. Timeouts, errors and unknown answers make the job `payment_uncertain`, never
|
|
389
|
+
charged again [J13]. A settlement past its deadline (`settleTimeoutSeconds`, default `maxTimeoutSeconds` + 30)
|
|
390
|
+
is uncertain too, and its answer is still recorded if it comes later.
|
|
391
|
+
- **Reconciled from the chain.** With a Soroban RPC (`rpc`, the public one by default on testnet; set your own
|
|
392
|
+
on mainnet), the provider decides `payment_uncertain` jobs after every sweep. It looks for the transaction
|
|
393
|
+
that carries the payer's signed auth entry with the job's nonce, so another job's equal transfer is never
|
|
394
|
+
mistaken for it. A paid job then runs or shows its output; one whose payment provably never landed before
|
|
395
|
+
it expired fails uncharged; anything less waits. See [docs/job-recovery.md](../../docs/job-recovery.md#reconciliation).
|
|
396
|
+
- **Timed by ledger.** A signed payment expires at a ledger, so `on-success` jobs are timed by their payment's
|
|
397
|
+
expiration ledger, read from the same RPC. Without it they fall back to the claim time plus
|
|
398
|
+
`maxTimeoutSeconds`.
|
|
399
|
+
- **Crash-safe.** Every state change is stored before the next step. A settlement interrupted by a crash
|
|
400
|
+
becomes `payment_uncertain`; an `on-success` job whose payment expired fails uncharged.
|
|
401
|
+
- **Hardened facilitator client.** HTTPS only, no redirects, deadlines on every call, bounded answers, and a
|
|
402
|
+
startup check that the facilitator settles this network with sponsored fees.
|
|
403
|
+
|
|
404
|
+
## Capacity and load
|
|
405
|
+
|
|
406
|
+
When ten agents call your service at the same time, nothing fails. Each service runs `concurrency` jobs at
|
|
407
|
+
once and queues the rest. When the queue is full, the kit answers `503` with `Retry-After` **before the `402`**,
|
|
408
|
+
so nobody pays for a job you can't run (Provider Standard §5.8).
|
|
409
|
+
|
|
410
|
+
```ts
|
|
411
|
+
const imageGen = defineService({
|
|
412
|
+
name: "image-gen",
|
|
413
|
+
// ...
|
|
414
|
+
expectedDurationSeconds: 20,
|
|
415
|
+
capacity: {
|
|
416
|
+
concurrency: 2, // jobs at once: what one GPU or one upstream quota allows (default 4)
|
|
417
|
+
maxQueued: 50, // jobs waiting for a slot (default 100)
|
|
418
|
+
maxQueueWaitSeconds: 80, // longest expected wait before answering 503 instead (default 60; 0 = no queue)
|
|
419
|
+
},
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
const provider = createProvider({
|
|
423
|
+
// ...
|
|
424
|
+
services: [imageGen],
|
|
425
|
+
// Optional: 429 for a caller sending too many create-job requests. You decide who a caller is.
|
|
426
|
+
rateLimit: { requests: 30, windowSeconds: 60, key: (request) => request.headers.get("cf-connecting-ip") ?? undefined },
|
|
427
|
+
});
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
With 10 requests at once, 2 jobs run and 8 wait. Each waiting job gets `202` with
|
|
431
|
+
`"queue": { "position", "estimatedStartAt" }` and a matching `Retry-After`. The 11th request would wait about
|
|
432
|
+
100 s, over the 80 s limit, so it gets `503` with `Retry-After` and is never asked to pay. The client sends the
|
|
433
|
+
same request again later.
|
|
434
|
+
|
|
435
|
+
- The manifest publishes `capacity: { concurrency, maxQueueWaitSeconds }` so discovery and clients can see it.
|
|
436
|
+
- `upfront` jobs settle first and then wait for a slot. `on-success` jobs wait only as long as their payment
|
|
437
|
+
lasts: until its expiration ledger, less the work and 10 s to settle. One that can't start in time fails
|
|
438
|
+
with `timeout` and is not charged.
|
|
439
|
+
- Wait estimates start from `expectedDurationSeconds` and then follow the measured durations.
|
|
440
|
+
- check-status and get-result are never limited, so paid jobs can always be collected.
|
|
441
|
+
|
|
442
|
+
### Restarts, several processes, separate workers
|
|
443
|
+
|
|
444
|
+
The queue lives in the job store. With `sqliteStore(path)`:
|
|
445
|
+
|
|
446
|
+
- a paid `upfront` job keeps its place across restarts and runs when the provider is back [Q4];
|
|
447
|
+
- several processes on one machine can share the file: `concurrency` then counts jobs across all of them;
|
|
448
|
+
- workers lease `upfront` jobs, and a job whose worker died is taken over when the lease (30 s) runs out,
|
|
449
|
+
up to 3 starts;
|
|
450
|
+
- HTTP-only processes (`runQueuedJobs: false`, or `serve --no-workers`) can take jobs that separate worker
|
|
451
|
+
processes, with the same services and store, run. `on-success` jobs always run in the process that
|
|
452
|
+
verified their payment, because that process settles it.
|
|
453
|
+
|
|
454
|
+
`memoryStore()`, the default, is for tests and local development only: it loses jobs on restart. A store for
|
|
455
|
+
another database implements the `JobStore` contract in `src/jobs/store.ts`; `test/job-store.test.ts` is the
|
|
456
|
+
contract test it must pass.
|
|
457
|
+
|
|
458
|
+
## Testing your provider
|
|
459
|
+
|
|
460
|
+
`@stellar-agent-services/provider-kit/testing` signs real x402 payments offline, so your tests run the kit's
|
|
461
|
+
payment checks without a network:
|
|
462
|
+
|
|
463
|
+
```ts
|
|
464
|
+
import { signTestPayment } from "@stellar-agent-services/provider-kit/testing";
|
|
465
|
+
const payment = await signTestPayment(requirements, Keypair.random()); // pair it with a fake facilitator
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
## Files
|
|
469
|
+
|
|
470
|
+
| File | Contents |
|
|
471
|
+
| --- | --- |
|
|
472
|
+
| `src/define-service.ts` | `defineService()`: price, charge policy, schemas, capacity; checks at definition time |
|
|
473
|
+
| `src/provider.ts` | `createProvider()`: wires everything into one `fetch` handler; startup checks |
|
|
474
|
+
| `src/manifest.ts` | Builds the manifest from the same config as the 402 challenge, and checks it |
|
|
475
|
+
| `src/http/router.ts` | Manifest, create-job, check-status and get-result routes, in §5.4 order |
|
|
476
|
+
| `src/http/node-server.ts` | `listen()`: serves a provider on Node's HTTP server, with `/healthz` and graceful close. A request body is read only as far as the router reads it, so an early answer (`413`) leaves the connection usable. |
|
|
477
|
+
| `src/payments/payload-checks.ts` | Local payment checks and the payment fingerprint |
|
|
478
|
+
| `src/payments/facilitator.ts` | The facilitator client and the startup check |
|
|
479
|
+
| `src/payments/settlement.ts` | Settlement as `settled`, `failed` or `uncertain` (never charge twice) |
|
|
480
|
+
| `src/payments/chain.ts` | Soroban RPC reads and the ledger clock |
|
|
481
|
+
| `src/payments/onchain.ts` | Finds a job's payment on chain, by the payer's signed auth entry and nonce |
|
|
482
|
+
| `src/payments/x402-server.ts` | The 402 challenge, with the Bazaar discovery extension |
|
|
483
|
+
| `src/jobs/runner.ts` | The `on-success` and `upfront` charge policies; leased workers |
|
|
484
|
+
| `src/jobs/scheduler.ts` | Capacity: admission before the 402, turns, workers, queue positions |
|
|
485
|
+
| `src/jobs/recovery.ts` | Sweeps for what a crash left behind, and retention |
|
|
486
|
+
| `src/jobs/reconcile.ts` | Decides `payment_uncertain` jobs from on-chain data |
|
|
487
|
+
| `src/jobs/memory-store.ts`, `src/jobs/sqlite-store.ts` | Job stores |
|
|
488
|
+
| `src/gateway/config.ts`, `src/gateway/upstream.ts` | Gateway mode |
|
|
489
|
+
| `src/gateway/openapi.ts` | Gateway services from an OpenAPI document, for `provider-kit init --openapi` |
|
|
490
|
+
| `src/registration.ts` | 8004 registration file, the I1–I3 check (with the file it read), and who may act for an agent |
|
|
491
|
+
| `src/cli/` | The `provider-kit` command |
|
|
492
|
+
| `src/cli/dev.ts`, `src/cli/dev-account.ts` | `provider-kit dev`, and the throwaway payout account it makes when `PAY_TO` is empty |
|
|
493
|
+
| `src/cli/launch.ts` | `provider-kit launch`: the public address, the manifest it will serve, one approval page, the settings saved, the deploy and the live check |
|
|
494
|
+
| `src/cli/status.ts` | `provider-kit status`: each stage, and the command to run next |
|
|
495
|
+
| `src/cli/serve.ts` | `provider-kit serve`, and `serveGateway()` that `dev` and `try` start a gateway with |
|
|
496
|
+
| `src/cli/env-file.ts`, `src/cli/fly.ts` | Reading and writing `.env` and `fly.toml`'s `[env]`; deploying with `flyctl` |
|
|
497
|
+
| `src/cli/processes.ts`, `src/cli/tunnel.ts` | Starting and stopping a provider's command, waiting for its manifest; the cloudflared quick tunnel |
|
|
498
|
+
| `src/cli/validators.ts` | The verifier `launch` asks by default, per network |
|
|
499
|
+
| `src/cli/approval.ts` | Approving in a wallet: the session (steps, checks, sending) and the local page's server and API |
|
|
500
|
+
| `src/cli/approval-view.ts` | What the page shows: the session as JSON, shared with the page |
|
|
501
|
+
| `src/cli/owner-chain.ts` | The owner's transactions: built, checked once signed, sent and followed into a ledger |
|
|
502
|
+
| `src/cli/owner-steps.ts` | Test XLM, the USDC trustline, registration and the validation request, as approval steps |
|
|
503
|
+
| `src/cli/try.ts` | `provider-kit try`: one paid testnet call through the payment client, from a throwaway payer |
|
|
504
|
+
| `src/cli/discovery.ts` | How agents see you, for `provider-kit check`: search text matched as stellar-agent-search matches it, and hints |
|
|
505
|
+
| `approve/` | The approval page (Svelte, Stellar Wallets Kit), built into `dist/approve` |
|
|
506
|
+
| `schemas/gateway-config.schema.json` | The gateway config file's JSON Schema |
|
|
507
|
+
| `Dockerfile` | The gateway image |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
svg.svelte-16un51p{flex:none;display:block}.body.svelte-1bpvs6g{min-width:0}.message.svelte-1bpvs6g{overflow-wrap:anywhere;margin-top:2px}.panel.svelte-1mh64aq{flex-direction:column;flex:1;gap:20px;animation:.18s ease-out rise;display:flex}.account.svelte-1mh64aq{border:1px solid var(--border);border-radius:var(--radius-lg);background:var(--surface-sunken);padding:14px 16px 16px}.account-label.svelte-1mh64aq{color:var(--text-tertiary);letter-spacing:.06em;text-transform:uppercase;font-size:11.5px;font-weight:650}.address-line.svelte-1mh64aq{align-items:center;gap:6px;margin-top:6px;display:flex}.address.svelte-1mh64aq{overflow-wrap:anywhere;flex:1;min-width:0;font-size:13.5px;font-weight:550;line-height:1.45}.reason.svelte-1mh64aq{color:var(--text-secondary);margin-top:8px;font-size:13px}.actions.svelte-1mh64aq{justify-items:center;gap:12px;margin-top:auto;padding-top:4px;display:grid}.wallets.svelte-1mh64aq{color:var(--text-tertiary);text-align:center;font-size:12.5px}.cancel.svelte-1mh64aq{color:var(--text-secondary);font-size:13px}.panel.svelte-lag6xc{text-align:center;justify-items:center;gap:8px;animation:.18s ease-out rise;display:grid}.badge.svelte-lag6xc{border:1px solid var(--border);background:var(--surface-sunken);width:52px;height:52px;color:var(--text-secondary);border-radius:14px;place-items:center;margin-bottom:8px;display:grid}.panel-lead.svelte-lag6xc{max-width:400px}.header.svelte-75mo91{z-index:10;height:var(--header-height);border-bottom:1px solid var(--border);background:color-mix(in srgb, var(--surface) 86%, transparent);-webkit-backdrop-filter:saturate(1.4)blur(10px);backdrop-filter:saturate(1.4)blur(10px);position:sticky;top:0}.inner.svelte-75mo91{justify-content:space-between;align-items:center;gap:16px;max-width:1040px;height:100%;margin:0 auto;padding:0 24px;display:flex}.brand.svelte-75mo91,.status.svelte-75mo91{align-items:center;gap:10px;min-width:0;display:flex}.logo.svelte-75mo91{color:#fff;background:linear-gradient(135deg,#6366f1,#4338ca);border-radius:9px;flex:none;place-items:center;width:30px;height:30px;display:grid;box-shadow:0 1px 2px #4338ca66}.name.svelte-75mo91{letter-spacing:-.01em;white-space:nowrap;font-size:15px;font-weight:650}.divider.svelte-75mo91{background:var(--border-strong);width:1px;height:20px}.command.svelte-75mo91{color:var(--text-secondary);text-overflow:ellipsis;white-space:nowrap;font-size:12.5px;overflow:hidden}.account.svelte-75mo91{border:1px solid var(--border);background:var(--surface);height:32px;color:var(--text-secondary);border-radius:999px;align-items:center;gap:8px;padding:0 12px;font-size:12.5px;display:inline-flex}.account.svelte-75mo91 .mono:where(.svelte-75mo91){color:var(--text);font-weight:550}.wallet.svelte-75mo91{border-left:1px solid var(--border);padding-left:8px}@media (max-width:640px){.divider.svelte-75mo91,.command.svelte-75mo91,.wallet.svelte-75mo91{display:none}.inner.svelte-75mo91{padding:0 16px}}.details.svelte-1iabob4{border:1px solid var(--border);border-radius:var(--radius-lg);background:var(--surface);margin:0}.row.svelte-1iabob4{border-bottom:1px solid var(--border);grid-template-columns:132px minmax(0,1fr);gap:16px;padding:10px 14px;display:grid}.row.svelte-1iabob4:last-child{border-bottom:0}dt.svelte-1iabob4{color:var(--text-tertiary);padding-top:1px;font-size:13px}dd.svelte-1iabob4{align-items:flex-start;gap:4px;min-width:0;margin:0;font-size:13.5px;font-weight:500;display:flex}.mono-row.svelte-1iabob4{align-items:center;margin:-6px 0}.value.svelte-1iabob4{overflow-wrap:anywhere;flex:1;min-width:0}.value.mono.svelte-1iabob4{font-size:12.5px;line-height:1.45}a.value.svelte-1iabob4{align-items:baseline;gap:4px;display:inline-flex}a.value.svelte-1iabob4 svg{opacity:.7;align-self:center}@media (max-width:560px){.row.svelte-1iabob4{grid-template-columns:minmax(0,1fr);gap:2px}.mono-row.svelte-1iabob4{margin:0 0 -6px}}.panel.svelte-o1kohj{flex-direction:column;flex:1;gap:22px;animation:.18s ease-out rise;display:flex}.hero.svelte-o1kohj{text-align:center;justify-items:center;gap:8px;padding:12px 0 4px;display:grid}.badge.svelte-o1kohj{background:var(--surface-sunken);width:56px;height:56px;color:var(--text-secondary);border-radius:999px;place-items:center;margin-bottom:6px;display:grid}.badge.success.svelte-o1kohj{background:var(--success-soft);box-shadow:0 0 0 8px color-mix(in srgb, var(--success-soft) 55%, transparent);color:var(--success)}.badge.danger.svelte-o1kohj{background:var(--danger-soft);color:var(--danger)}.title.svelte-o1kohj{letter-spacing:-.015em;font-size:21px;font-weight:650}.message.svelte-o1kohj{max-width:440px}.closing.svelte-o1kohj{border:1px dashed var(--border-strong);border-radius:var(--radius);color:var(--text-secondary);align-items:center;gap:10px;margin-top:auto;padding:12px 14px;font-size:13px;display:flex}.panel.svelte-kh3r8c{flex-direction:column;flex:1;gap:18px;animation:.18s ease-out rise;display:flex}.head.svelte-kh3r8c{align-items:flex-start;gap:14px;display:flex}.head-icon.svelte-kh3r8c{background:var(--accent-soft);width:40px;height:40px;color:var(--accent);border-radius:12px;flex:none;place-items:center;display:grid}.head-icon.blocked.svelte-kh3r8c{background:var(--danger-soft);color:var(--danger)}.raw.svelte-kh3r8c{border:1px solid var(--border);border-radius:var(--radius);background:var(--surface-sunken)}.raw.svelte-kh3r8c summary:where(.svelte-kh3r8c){color:var(--text-secondary);cursor:pointer;border-radius:var(--radius);align-items:center;gap:6px;padding:10px 12px;font-size:13px;font-weight:550;list-style:none;display:flex}.raw.svelte-kh3r8c summary:where(.svelte-kh3r8c)::-webkit-details-marker{display:none}.raw.svelte-kh3r8c summary:where(.svelte-kh3r8c) svg{transition:transform .14s}.raw[open].svelte-kh3r8c summary:where(.svelte-kh3r8c) svg{transform:rotate(90deg)}.raw-body.svelte-kh3r8c{gap:12px;padding:2px 12px 12px;display:grid}.raw-label.svelte-kh3r8c{color:var(--text-tertiary);font-size:12px;font-weight:600}.raw-value.svelte-kh3r8c{align-items:flex-start;gap:4px;margin-top:4px;display:flex}code.svelte-kh3r8c{border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--surface);min-width:0;color:var(--text-secondary);overflow-wrap:anywhere;flex:1;padding:8px 10px;font-size:11.5px;line-height:1.5}.xdr.svelte-kh3r8c{max-height:112px;display:block;overflow-y:auto}.foot.svelte-kh3r8c{gap:12px;margin-top:auto;padding-top:4px;display:grid}.actions.svelte-kh3r8c{gap:10px;display:flex}.grow.svelte-kh3r8c{flex:1}.hint.svelte-kh3r8c{color:var(--text-tertiary);text-align:center;justify-content:center;align-items:center;gap:6px;font-size:12.5px;display:flex}.hint.expired.svelte-kh3r8c{color:var(--warning)}@media (max-width:520px){.actions.svelte-kh3r8c{flex-direction:column-reverse}.actions.svelte-kh3r8c .btn:where(.svelte-kh3r8c){flex:none;width:100%}.hint.svelte-kh3r8c{text-align:left;align-items:flex-start}}.steps.svelte-1wezgpx{margin:0;padding:0;list-style:none;display:grid}.step.svelte-1wezgpx{grid-template-columns:28px minmax(0,1fr);gap:12px;padding-bottom:22px;display:grid;position:relative}.step.svelte-1wezgpx:last-child{padding-bottom:0}.step.svelte-1wezgpx:not(:last-child):before{content:"";background:var(--border-strong);width:1px;position:absolute;top:32px;bottom:4px;left:13.5px}.step.done.svelte-1wezgpx:not(:last-child):before{background:var(--success-border)}.marker.svelte-1wezgpx{border:1px solid var(--border-strong);background:var(--surface);width:28px;height:28px;color:var(--text-tertiary);font-variant-numeric:tabular-nums;border-radius:999px;place-items:center;font-size:12.5px;font-weight:600;transition:background-color .16s,border-color .16s,color .16s;display:grid}.active.svelte-1wezgpx .marker:where(.svelte-1wezgpx){border-color:var(--accent);background:var(--accent-soft);color:var(--accent);box-shadow:0 0 0 4px var(--accent-soft)}.done.svelte-1wezgpx .marker:where(.svelte-1wezgpx){border-color:var(--success);background:var(--success);color:var(--surface)}.halted.svelte-1wezgpx .marker:where(.svelte-1wezgpx){border-color:var(--border-strong);background:var(--surface);color:var(--text-tertiary);box-shadow:none}.text.svelte-1wezgpx{gap:1px;min-width:0;padding-top:4px;display:grid}.title.svelte-1wezgpx{color:var(--text-tertiary);font-weight:550}.active.svelte-1wezgpx .title:where(.svelte-1wezgpx),.done.svelte-1wezgpx .title:where(.svelte-1wezgpx){color:var(--text)}.note.svelte-1wezgpx{color:var(--text-tertiary);overflow-wrap:anywhere;font-size:12.5px}.note.current.svelte-1wezgpx{color:var(--accent);font-weight:500}.panel.svelte-audtxj{text-align:center;flex:1;place-content:center;justify-items:center;gap:8px;min-height:280px;animation:.18s ease-out rise;display:grid}.ring.svelte-audtxj{border:3px solid var(--accent-soft);border-top-color:var(--accent);border-radius:999px;width:44px;height:44px;margin-bottom:12px;animation:.9s linear infinite spin}.panel-lead.svelte-audtxj{max-width:360px}.page.svelte-1mjb4es{max-width:1040px;margin:0 auto;padding:40px 24px 48px}.frame.svelte-1mjb4es{border:1px solid var(--border);border-radius:var(--radius-xl);background:var(--surface);box-shadow:var(--shadow-md);grid-template-columns:340px minmax(0,1fr);display:grid;overflow:hidden}.frame.single.svelte-1mjb4es{grid-template-columns:minmax(0,1fr);max-width:560px;margin:48px auto 0;padding:44px 32px}.side.svelte-1mjb4es{border-right:1px solid var(--border);background:var(--surface-sunken);flex-direction:column;padding:32px 28px 28px;display:flex}.eyebrow.svelte-1mjb4es{color:var(--accent);letter-spacing:.06em;text-transform:uppercase;font-size:12px;font-weight:650}h1.svelte-1mjb4es{letter-spacing:-.02em;margin-top:8px;font-size:22px;font-weight:680;line-height:1.25}.intro.svelte-1mjb4es{color:var(--text-secondary);margin-top:8px}.steps-wrap.svelte-1mjb4es{margin-top:28px}.assurance.svelte-1mjb4es{color:var(--text-tertiary);align-items:flex-start;gap:8px;margin-top:auto;padding-top:32px;font-size:12.5px;display:flex}.assurance.svelte-1mjb4es svg{margin-top:2px}.main.svelte-1mjb4es{flex-direction:column;min-height:500px;padding:32px 36px;display:flex}.footer.svelte-1mjb4es{color:var(--text-tertiary);text-align:center;margin-top:16px;font-size:12px}@media (max-width:860px){.page.svelte-1mjb4es{padding:20px 16px 32px}.frame.svelte-1mjb4es{grid-template-columns:minmax(0,1fr)}.side.svelte-1mjb4es{border-right:0;border-bottom:1px solid var(--border);padding:24px 20px}.assurance.svelte-1mjb4es{padding-top:24px}.main.svelte-1mjb4es{min-height:0;padding:24px 20px}}:root{--font-sans:ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;--font-mono:ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;--bg:#f4f5f8;--surface:#fff;--surface-sunken:#f7f8fa;--border:#e4e7ec;--border-strong:#d0d5dd;--text:#0f172a;--text-secondary:#475467;--text-tertiary:#667085;--accent:#4f46e5;--accent-solid:#4f46e5;--accent-solid-hover:#4338ca;--accent-soft:#eef2ff;--accent-border:#c7d2fe;--on-accent:#fff;--success:#067647;--success-soft:#ecfdf3;--success-border:#abefc6;--warning:#b54708;--warning-soft:#fffaeb;--warning-border:#fedf89;--danger:#b42318;--danger-soft:#fef3f2;--danger-border:#fecdca;--ring:0 0 0 4px #4f46e52e;--shadow-sm:0 1px 2px #1018280d;--shadow-md:0 1px 3px #10182814, 0 1px 2px #1018280a;--shadow-lg:0 24px 48px -12px #10182838;--radius-sm:8px;--radius:10px;--radius-lg:16px;--radius-xl:20px;--header-height:64px;--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}@media (prefers-color-scheme:dark){:root{--bg:#0b0e14;--surface:#12161f;--surface-sunken:#0e121a;--border:#232a36;--border-strong:#333c4b;--text:#f1f5f9;--text-secondary:#c1c9d6;--text-tertiary:#8d97a8;--accent:#a5b4fc;--accent-solid:#6366f1;--accent-solid-hover:#7c7ff5;--accent-soft:#6366f124;--accent-border:#818cf866;--success:#6ce9a6;--success-soft:#12b76a1f;--success-border:#32d58352;--warning:#fec84b;--warning-soft:#f790091f;--warning-border:#fdb02252;--danger:#fda29b;--danger-soft:#f044381f;--danger-border:#f9706652;--ring:0 0 0 4px #818cf847;--shadow-sm:0 1px 2px #0006;--shadow-md:0 1px 3px #00000073;--shadow-lg:0 24px 48px -12px #0009;--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}}*,:before,:after{box-sizing:border-box}html{-webkit-text-size-adjust:100%}body{background:var(--bg);min-height:100vh;color:var(--text);font-family:var(--font-sans);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;margin:0;font-size:14px;line-height:1.5}h1,h2,h3,p{margin:0}a{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}:focus-visible{box-shadow:var(--ring);outline:none}.mono{font-family:var(--font-mono);font-size:.92em}.muted{color:var(--text-tertiary)}.visually-hidden{clip:rect(0 0 0 0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.panel-title{letter-spacing:-.01em;font-size:18px;font-weight:650}.panel-lead{color:var(--text-secondary);margin-top:4px}.btn{border-radius:var(--radius);height:40px;font:inherit;white-space:nowrap;cursor:pointer;border:1px solid #0000;justify-content:center;align-items:center;gap:8px;padding:0 16px;font-weight:550;transition:background-color .12s,border-color .12s,color .12s,box-shadow .12s;display:inline-flex}.btn:disabled{cursor:not-allowed;opacity:.55}.btn-primary{background:var(--accent-solid);color:var(--on-accent);box-shadow:var(--shadow-sm)}.btn-primary:hover:not(:disabled){background:var(--accent-solid-hover)}.btn-secondary{background:var(--surface);border-color:var(--border-strong);color:var(--text);box-shadow:var(--shadow-sm)}.btn-secondary:hover:not(:disabled){background:var(--surface-sunken)}.btn-ghost{color:var(--text-secondary);background:0 0}.btn-ghost:hover:not(:disabled){background:var(--surface-sunken);color:var(--text)}.btn-lg{height:48px;padding:0 20px;font-size:15px}.btn-block{width:100%}.btn-icon{width:32px;height:32px;padding:0}.link-button{color:var(--accent);font:inherit;cursor:pointer;background:0 0;border:0;padding:0;font-weight:500}.link-button:hover:not(:disabled){text-decoration:underline}.link-button:disabled{cursor:not-allowed;opacity:.55}.pill{border:1px solid var(--border);background:var(--surface-sunken);height:24px;color:var(--text-secondary);white-space:nowrap;border-radius:999px;align-items:center;gap:6px;padding:0 10px;font-size:12px;font-weight:550;display:inline-flex}.pill-warning{border-color:var(--warning-border);background:var(--warning-soft);color:var(--warning)}.pill-success{border-color:var(--success-border);background:var(--success-soft);color:var(--success)}.dot{background:currentColor;border-radius:999px;width:6px;height:6px}.alert{border:1px solid var(--border);border-radius:var(--radius);background:var(--surface-sunken);color:var(--text-secondary);gap:10px;padding:12px 14px;font-size:13px;display:flex}.alert>svg{flex:none;margin-top:2px}.alert-title{color:var(--text);font-weight:600}.alert-warning{border-color:var(--warning-border);background:var(--warning-soft)}.alert-warning>:first-child{color:var(--warning)}.alert-danger{border-color:var(--danger-border);background:var(--danger-soft)}.alert-danger>:first-child{color:var(--danger)}.spinner{border:2px solid;border-right-color:#0000;border-radius:999px;flex:none;width:16px;height:16px;animation:.75s linear infinite spin;display:inline-block}@keyframes spin{to{transform:rotate(360deg)}}@keyframes rise{0%{opacity:0;transform:translateY(6px)}}@media (prefers-reduced-motion:reduce){*,:before,:after{transition-duration:1ms!important;animation-duration:1ms!important;animation-iteration-count:1!important}}
|