From ebfdfd67c34aea0ceadbb3357d0ebbb6a2ae0c7b Mon Sep 17 00:00:00 2001 From: Kevin Vera Date: Thu, 9 Oct 2025 22:36:46 -0400 Subject: [PATCH 1/2] Create Instructions.md --- Instructions.md | 779 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 779 insertions(+) create mode 100644 Instructions.md diff --git a/Instructions.md b/Instructions.md new file mode 100644 index 0000000..f38c8f8 --- /dev/null +++ b/Instructions.md @@ -0,0 +1,779 @@ +# AXP (Agent eXchange Protocol) — v0.1 + +**Purpose:** Give Cursor a precise, end‑to‑end plan to implement AXP as an open-source spec + SDKs + runtime, with bridges for MCP and OpenAI AgentKit. Includes architecture, repo layout, tasks, code stubs, tests, and failure-handling. + +--- + +## 0) High-level goals +- Minimal, interoperable **message contract** for agent↔agent / agent↔tool. +- **Identity-first**: signatures, capability scopes, policy enforcement, audit. +- **Transport-agnostic** envelope with WS/HTTP2/QUIC profiles. +- **Operator-grade**: idempotency, retries, backpressure, tracing, cost metering. +- Bridges to **MCP** and pluggable **AgentKit** adapter. + +--- + +## 1) Monorepo blueprint +Use **pnpm workspaces** for TS and **Cargo** for Rust. + +``` +axp/ + README.md + LICENSE + .editorconfig + .gitignore + pnpm-workspace.yaml + package.json + /spec + axp-core.md + json-schema/ + envelope.schema.json + intent.schema.json + outcome.schema.json + error.schema.json + registry.schema.json + /sdks + /ts/axp + package.json + src/ + envelope.ts + sign.ts + verify.ts + validate.ts + transport-ws.ts + transport-http.ts + idempotency.ts + index.ts + test/ + envelope.test.ts + conformance.test.ts + /rust/axp + Cargo.toml + src/ + envelope.rs + signing.rs + verify.rs + validate.rs + transports/ + ws.rs + http.rs + quic.rs (stub) + tests/ + conformance.rs + /runtime + /axp-gateway (Rust) + Cargo.toml + src/ + main.rs + api.rs (REST/WS endpoints) + router.rs + idempotency.rs (RocksDB) + policy.rs + signer.rs + audit.rs + tracing.rs + tests/ + e2e.rs + /registry + /service (TS/Node or Rust) + package.json or Cargo.toml + src/ + server.(ts|rs) + db.(ts|rs) + openapi-import.(ts|rs) + models.(ts|rs) + prisma/ (if TS+Prisma) or migrations/ (if Rust+sqlx) + /bridges + /mcp-bridge (TS) + package.json + src/ + server.ts (MCP server exposing tools that delegate to AXP) + adapters/ + axp-tool-adapter.ts + /agentkit-bridge (TS) + package.json + src/ + connector.ts (maps AXP Registry to AgentKit connector format) + toolhost.ts (executes AXP intents from AgentKit) + /cli + axp-cli (Rust or TS) + Cargo.toml or package.json + src/ + main.(rs|ts) + commands/ + sign.(rs|ts) + send.(rs|ts) + recv.(rs|ts) + validate.(rs|ts) + /explorer + (Next.js) – live trace viewer & message inspector + /examples + /calendar + server.ts (mock calendar tools) + client.ts (sends intents) + /ci + conformance/ + fixtures/*.json + runner.(ts|rs) +``` + +**Default license:** Apache‑2.0. + +--- + +## 2) Core spec (spec/axp-core.md) + +### 2.1 Envelope (transport-agnostic) +- Fields: `axp`, `id`, `ts`, `from`, `to`, `type`, `transport`, `auth`. +- `auth.sig` covers canonicalized payload (Detached JWS-style). Canonicalization = deterministic JSON (sorted keys, UTF‑8, no whitespace), spec this explicitly. + +### 2.2 Payload types +- **intent**: `{ name, goal?, inputs, context, capabilities[], policies{}, toolplan[], stream?, reply_to? }` +- **outcome**: `{ status, data, trace{}, caps_consumed[], policy_violations[], diagnostics{} }` +- **error**: `{ code, message, retry{ after_ms?, idempotent? } }` +- **heartbeat**: `{ interval_ms, load }` +- **registry.update**: `{ op, entity, record{ id, owner, schema{input,output}, capabilities_required[], version, visibility } }` + +### 2.3 Identity & URIs +- URIs are pluggable: support `agent://` (IETF draft), `axp://agent/...`, or `did:...`. +- Spec a **URI strategy interface**; default to `agent://` in examples but allow override. + +### 2.4 Security +- Signatures: **ed25519** required; PQC (e.g., **Dilithium**) optional for hybrid. +- Replay window: accept only messages with `ts` within ±60s unless accompanied by server-issued nonce. +- Idempotency: identical `id`+`from` must return previous outcome. +- Capabilities: least-privilege list on `intent.capabilities`. +- Policies: inline or referenced ID (e.g., spend caps, PII rules, SLOs). +- Secrets: pass vault handles, never raw secrets. + +### 2.5 Observability +- `trace.steps[*]` fields: `{ tool, args_hash, latency_ms, tokens_in, tokens_out, cost_est_usd, cache_hit, error? }` +- `trace.model`, `trace.cache_hit`, `trace.eval?` (rubric+score) + +### 2.6 Error Codes +- Canonical: `BAD_INPUT, AUTH_FAILED, CAPABILITY_MISSING, POLICY_DENIED, TOOL_TIMEOUT, TOOL_UNAVAILABLE, RATE_LIMIT, INTERNAL, DEPENDENCY_ERROR, CONFLICT, NOT_FOUND`. + +### 2.7 Versioning +- Semantic version in `envelope.axp`. Tool records carry `version`. + +--- + +## 3) JSON Schemas (spec/json-schema/*.json) +Create schemas for: **envelope**, **intent**, **outcome**, **error**, **registry**. Include `$id`, `$schema`, `additionalProperties:false`. Provide rich `format` checks (ISO8601 datetime, email, uri) and enums for error codes + status. + +--- + +## 4) TypeScript SDK (/sdks/ts/axp) + +### 4.1 Dependencies +- `ajv` + `ajv-formats` (validation) +- `tweetnacl` (ed25519) +- `ulidx` or `uuid` +- `ws` (WS client) +- `cross-fetch` or `undici` + +### 4.2 Exposed API +```ts +export type Envelope = { /* envelope fields */ } +export type Intent = { /* payload */ } +export type Outcome = { /* payload */ } +export function signEnvelope(env: Envelope, privKey: Uint8Array): Envelope +export function verifyEnvelope(env: Envelope, pubKey: Uint8Array): boolean +export function validateIntent(obj: unknown): asserts obj is Intent +export class WsTransport { /* connect(url), sendIntent(), onOutcome(cb) */ } +export class HttpTransport { /* post(url), stream via SSE */ } +export class IdempotencyStore { /* memory + pluggable redis */ } +``` + +### 4.3 Helpers +- Canonical JSON serializer `canon(obj): Uint8Array`. +- `argsHash(obj)`: SHA‑256 for trace. +- Backoff util: decorrelated jitter. + +### 4.4 Tests +- Conformance against `/spec/json-schema`. +- Golden fixtures for valid/invalid envelopes. + +--- + +## 5) Rust SDK (/sdks/rust/axp) +- Crates: `serde`, `serde_json`, `ed25519-dalek`, `sha2`, `ulid`, `reqwest`, `tokio`, `axum`, `tokio-tungstenite`, `quinn` (stub). +- Mirror TS API. Provide feature flags: `transport-ws`, `transport-http`, `transport-quic`. +- Property-based tests with `proptest` for envelope round‑trips. + +--- + +## 6) AXP Gateway Runtime (/runtime/axp-gateway) + +### 6.1 Responsibilities +- Terminate transports (WS/HTTP2; QUIC optional). +- Verify signatures, enforce replay window, idempotency store (RocksDB). +- Route `intent` to target agent/tool hosts (HTTP/gRPC adapters). +- Enforce policies (rate, spend, PII), emit traces to OTLP/JSON logs. + +### 6.2 Endpoints +- `WS /v1/stream` – duplex intents/outcomes. +- `POST /v1/intent` – sync call; `Accept: text/event-stream` for streaming. +- `GET /healthz`, `GET /metrics` (Prometheus). + +### 6.3 Config +- `ACL` for which `from` can `to` whom. +- Keyring: map `kid`→public key. +- Budget rules; token→USD mappings. + +--- + +## 7) Registry Service (/registry/service) +- DB: SQLite (dev) → Postgres (prod). +- Tables: `tools(id, owner, input_schema, output_schema, caps_required, version, visibility)`; `agents(id, owner, pubkey, caps)`. +- APIs: `GET/POST /tools`, `GET/POST /agents`, `POST /import/openapi` (convert OpenAPI → tool records). +- CLI: `axp-cli registry import-openapi ./openapi.yaml`. + +--- + +## 8) Bridges + +### 8.1 MCP Bridge (/bridges/mcp-bridge) +- Implement an MCP server (TS) that: + - Exposes tools from AXP Registry as MCP tools. + - On MCP tool invocation, builds AXP `intent` and forwards via Gateway, streams back `outcome`. +- Config: MCP server reads AXP Gateway URL + keyring. +- Provide example integration with Claude/ChatGPT MCP clients. + +### 8.2 AgentKit Bridge (/bridges/agentkit-bridge) +- Provide a connector shim that: + - Syncs AXP Registry entries into AgentKit’s connector registry format. + - Hosts an adapter endpoint for AgentKit tool calls → AXP `intent`. +- Expose TypeScript types mirroring AgentKit connector/tool schemas; guard with `experimental` flag until official SDK signatures are stable. + +--- + +## 9) Explorer (Next.js) +- Views: Live Stream, Envelope Inspector (validate against schema), Traces (steps table, flamegraph), Policies view. +- WebSocket client to Gateway; server-side validation via `ajv`. + +--- + +## 10) Examples (/examples/calendar) +- `server.ts`: mock `calendar.find_slots` & `calendar.create_event` HTTP endpoints. +- `client.ts`: sends `intent` to `agent://stone.scheduler`, prints streamed outcomes; demonstrates idempotent retry. + +--- + +## 11) Security & policy details +- **Signature**: `ed25519` required. Store keys per agent in keyring. Support hybrid `sig = concat( ed25519 || pqc )` later. +- **Replay**: reject if `|now - ts| > 60s`, unless valid server nonce present. +- **Idempotency**: dedupe by `(id, from)`; return cached outcome on retry. +- **Capabilities**: enforce pre-dispatch; record consumed caps on outcome. +- **Budget**: `policies.spend_usd_max` → terminate tool calls once exceeded. +- **Backpressure**: transport-level windowing; per-connection RPS. +- **PII**: server-side redaction rules; denylist fields in traces; irreversible hashing where needed. + +--- + +## 12) Failure modes & mitigations +- **Clock skew** → Provide `/time` sync endpoint; allow ±120s with warning. +- **Partial outages** → Circuit‑break tool hosts; exponential backoff; fallbacks. +- **Message bloat** → Enforce max 512KB payload; negotiate chunked streams. +- **Tool timeouts** → Per-step `deadline_ms`; emit `TOOL_TIMEOUT` with retry hint. +- **Key rotation** → Support multiple `kid`s; rolling verification. +- **Policy drift** → Pin `policies.schema_version`; reject unknown rules. +- **Registry desync** → Version tool records; cache‑bust via ETag. + +--- + +## 13) Conformance & CI +- **Conformance runner** executes fixtures over WS/HTTP. +- Golden cases: valid/invalid envelopes, idempotent retry, policy denial, capability miss, streaming partials, merged outcomes. +- GitHub Actions: run TS and Rust tests, lint (Biome/ESLint, Clippy), schema drift checks. + +--- + +## 14) Publishing +- `npm publish @stone/axp` +- `crates.io` publish `axp` +- Docker images: `axp-gateway`, `axp-registry`, `mcp-bridge`. +- QuickStart in README: spin up with `docker compose up` + run calendar example. + +--- + +## 15) URI scheme decision +- Default examples use `agent://stone.scheduler` URIs. +- Implement **URI strategy** interface so deployments can switch to `axp://agent/...` or `did:...` without code changes. +- Registry validates uniqueness of IDs across chosen scheme. + +--- + +## 16) Integration notes (agents) +- Agents can be local processes (child), containers, or remote services. +- Provide **agent-side SDK** helper to: + - Receive intents (WS/HTTP), validate caps/policies, call local tools, send outcomes. + - Offer **spawn/delegate** helpers (return `spawned_intent` to Gateway for fan‑out/delegation). +- Add **sandbox** option (e.g., Docker/Firecracker) for untrusted tools. + +--- + +## 17) Roadmap (post‑MVP) +- QUIC transport feature; gRPC profile. +- PQC hybrid signatures; HSM support. +- Deterministic replay for evals; time‑travel traces. +- Policy engine DSL + compiler. +- Multi‑tenant quotas; billing hooks; token→USD feed. +- Formal spec site + conformance badge. + +--- + +## 18) Cursor task list +- [ ] Create monorepo structure as above; add Apache‑2.0. +- [ ] Write `spec/axp-core.md` + all JSON Schemas. +- [ ] Implement TS SDK (`@stone/axp`): signing, validate, WS/HTTP transports, idempotency. +- [ ] Implement Rust SDK mirror. +- [ ] Build `axp-gateway` runtime with WS + HTTP2, RocksDB idempotency, Prom metrics. +- [ ] Build Registry service with SQLite and OpenAPI import. +- [ ] Implement MCP bridge server (TS) → exposes registry tools. +- [ ] Scaffold AgentKit bridge (types & adapters; mark experimental). +- [ ] Build Explorer (Next.js) to view traces; connect to Gateway. +- [ ] Add calendar example; write Jest + Rust tests; add CI. +- [ ] Publish pre‑release packages; write README QuickStart. + +--- + +**AXP positions Stone as the “agent OS” substrate: minimal surface, strong guarantees, and clean bridges to the rest of the ecosystem.** + + +--- + +# Cursor Execution Plan — Build **AXP** End-to-End + +The following is a copy‑pasteable, sequential checklist for Cursor to scaffold, implement, test, and publish **AXP** (Agent eXchange Protocol). It includes commands, file stubs, CI, and local run instructions. + +## 0) Prereqs (install once) +- Node 20+, pnpm 9+ +- Rust stable, cargo +- Python 3.11+ +- Docker + Docker Compose +- GitHub repo: `stoneplatforms/axp` (empty) +- Secrets you’ll add later in GitHub Actions: `NPM_TOKEN`, `CARGO_TOKEN`, `PYPI_TOKEN` (optional if publishing Python later) + +--- + +## 1) Initialize monorepo +```bash +mkdir axp && cd axp +git init +printf "MIT or Apache-2.0? Using Apache-2.0. +" && echo "Apache-2.0" > LICENSE-SPDX +cat > LICENSE <<'EOF' +Apache License 2.0 ... (paste full text) +EOF +cat > README.md <<'EOF' +# AXP — Agent eXchange Protocol +Open protocol + SDKs + runtime for secure, auditable agent↔tool calls. +EOF +pnpm init -y +cat > pnpm-workspace.yaml <<'EOF' +packages: + - "sdks/**" + - "runtime/**" + - "registry/**" + - "bridges/**" + - "cli/**" + - "explorer" + - "examples/**" + - "ci/**" +EOF +mkdir -p spec/json-schema sdks/ts/axp sdks/rust/axp runtime/axp-gateway registry/service bridges/mcp-bridge bridges/agentkit-bridge cli/axp-cli explorer examples/calendar ci/conformance +``` + +--- + +## 2) Spec & Schemas +```bash +cat > spec/axp-core.md <<'EOF' +# AXP Core Spec (v0.1) +- Envelope fields: axp, id, ts, from, to, type, transport, auth +- Payloads: intent, outcome, error, heartbeat, registry.update +- Security: ed25519 signatures, ±60s replay window, idempotency by (id, from) +- Observability: trace.steps[*] { tool, args_hash, latency_ms, tokens_in/out, cost_est_usd, cache_hit } +- Errors: BAD_INPUT, AUTH_FAILED, CAPABILITY_MISSING, POLICY_DENIED, TOOL_TIMEOUT, TOOL_UNAVAILABLE, RATE_LIMIT, INTERNAL, DEPENDENCY_ERROR, CONFLICT, NOT_FOUND +- Versioning: semantic in envelope.axp +EOF + +cat > spec/json-schema/envelope.schema.json <<'EOF' +{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "axp/envelope", "type":"object", "additionalProperties":false, + "properties":{ + "axp":{"type":"string"}, + "id":{"type":"string"}, + "ts":{"type":"string","format":"date-time"}, + "from":{"type":"string"}, + "to":{"type":"string"}, + "type":{"enum":["intent","outcome","error","heartbeat","registry.update"]}, + "transport":{"type":"object"}, + "auth":{"type":"object","properties":{"kid":{"type":"string"},"sig":{"type":"string"}},"required":["kid"]} + }, + "required":["axp","id","ts","from","to","type","auth"] +} +EOF + +# (Repeat similarly for intent/outcome/error/registry schemas later.) +``` + +--- + +## 3) TypeScript SDK (@stone/axp) +```bash +cd sdks/ts/axp +pnpm init -y +pnpm add ajv ajv-formats tweetnacl ws undici +pnpm add -D typescript vitest @types/ws @types/node +npx tsc --init --rootDir src --outDir dist --declaration --esModuleInterop +cat > package.json <<'EOF' +{ + "name": "@stone/axp", + "version": "0.1.0", + "license": "Apache-2.0", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": ["dist","README.md","LICENSE"], + "scripts": { + "build": "tsc -p tsconfig.json", + "test": "vitest run" + } +} +EOF +mkdir -p src test +cat > src/canon.ts <<'EOF' +export function canon(obj: unknown): Uint8Array { + const stable = (v: any): any => Array.isArray(v) ? v.map(stable) + : v && typeof v === 'object' ? Object.keys(v).sort().reduce((o,k)=>{o[k]=stable((v as any)[k]);return o;},{}) : v; + return new TextEncoder().encode(JSON.stringify(stable(obj))); +} +EOF +cat > src/sign.ts <<'EOF' +import nacl from 'tweetnacl'; +import { canon } from './canon.js'; +export function signEnvelope(env: any, secretKey: Uint8Array){ + const copy = JSON.parse(JSON.stringify(env)); + copy.auth ||= {}; delete copy.auth.sig; + const sig = nacl.sign.detached(canon(copy), secretKey); + env.auth ||= {}; env.auth.sig = Buffer.from(sig).toString('base64url'); + return env; +} +export function verifyEnvelope(env: any, publicKey: Uint8Array){ + const copy = JSON.parse(JSON.stringify(env)); + const sig = copy.auth?.sig; if(!sig) return false; delete copy.auth.sig; + return nacl.sign.detached.verify(canon(copy), Buffer.from(sig,'base64url'), publicKey); +} +EOF +cat > src/transport-http.ts <<'EOF' +import { request } from 'undici'; +export class HttpTransport { + constructor(private base:string){} + async postIntent(env:any, opts:{stream?:boolean}={}){ + const { body } = await request(`${this.base}`, { method:'POST', body: JSON.stringify(env), headers:{'content-type':'application/json'} }); + return await body.json(); + } +} +EOF +cat > src/index.ts <<'EOF' +export * from './sign.js'; +export * from './transport-http.js'; +EOF +pnpm run build +cd ../../../.. +``` + +--- + +## 4) Rust SDK (crates.io `axp`) +```bash +cd sdks/rust/axp +cargo init --lib +cat >> Cargo.toml <<'EOF' +[package] +name = "axp" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" +description = "AXP core SDK" +repository = "https://github.com/stoneplatforms/axp" +readme = "README.md" +[dependencies] +serde = { version = "1", features=["derive"] } +serde_json = "1" +ed25519-dalek = "2" +sha2 = "0.10" +ulid = "1" +reqwest = { version = "0.12", features=["json","rustls-tls"] } +tokio = { version = "1", features=["full"] } +EOF +cat > src/lib.rs <<'EOF' +pub const AXP_VERSION: &str = "0.1.0"; +EOF +cd ../../../.. +``` + +--- + +## 5) Gateway runtime (Rust, Axum) +```bash +cd runtime/axp-gateway +cargo init --bin +cat >> Cargo.toml <<'EOF' +[package] +name = "axp-gateway" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" +[dependencies] +axum = "0.7" +tokio = { version = "1", features=["full"] } +serde = { version = "1", features=["derive"] } +serde_json = "1" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features=["fmt","env-filter"] } +rocksdb = "0.22" +EOF +cat > src/main.rs <<'EOF' +use axum::{Router, routing::post, Json}; +use serde_json::Value; +#[tokio::main] +async fn main(){ + let app = Router::new().route("/v1/intent", post(intent)); + println!("AXP Gateway listening on :8080"); + axum::Server::bind(&"0.0.0.0:8080".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); +} +async fn intent(Json(payload): Json) -> Json { + // TODO: verify sig, idempotency, route, enforce policies + Json(serde_json::json!({"type":"outcome","outcome":{"status":"success","data":{"echo":payload}}})) +} +EOF +cd ../../.. +``` + +--- + +## 6) Registry service (TS, minimal) +```bash +cd registry/service +pnpm init -y +pnpm add fastify zod +cat > server.ts <<'EOF' +import Fastify from 'fastify'; +const app = Fastify(); +const tools: Record = {}; +app.post('/tools', async (req, rep)=>{ const b:any = req.body; tools[b.id]=b; return {ok:true}; }); +app.get('/tools', async ()=> Object.values(tools)); +app.listen({port:8090, host:'0.0.0.0'}).then(()=>console.log('AXP Registry :8090')); +EOF +cd ../../.. +``` + +--- + +## 7) Bridges +### MCP bridge (TS skeleton) +```bash +cd bridges/mcp-bridge +pnpm init -y +pnpm add ws undici +cat > server.ts <<'EOF' +// Pseudo: expose MCP tools that forward to AXP gateway +console.log('MCP→AXP bridge (skeleton)'); +EOF +cd ../../ +``` + +### AgentKit bridge (TS skeleton) +```bash +cd bridges/agentkit-bridge +pnpm init -y +pnpm add undici +cat > connector.ts <<'EOF' +export async function callAxpTool(tool:string, args:any, cfg:any){ + const env = { axp:'1.0', id: crypto.randomUUID(), ts:new Date().toISOString(), from:cfg.from, to:cfg.to, type:'intent', auth:{kid:cfg.kid}, intent:{ name:tool, inputs:args, capabilities:cfg.capabilities||[] } }; + const res = await fetch(cfg.gateway, { method:'POST', headers:{'content-type':'application/json'}, body: JSON.stringify(env) }); + return await res.json(); +} +EOF +cd ../../ +``` + +--- + +## 8) CLI (Rust) +```bash +cd cli/axp-cli +cargo init --bin +cat >> Cargo.toml <<'EOF' +[package] +name = "axp-cli" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" +[dependencies] +clap = { version = "4", features=["derive"] } +serde_json = "1" +ureq = { version = "2", features=["json"] } +EOF +cat > src/main.rs <<'EOF' +use clap::{Parser, Subcommand}; +#[derive(Parser)] +#[command(name="axp")] struct Cli { #[command(subcommand)] cmd: Cmd } +#[derive(Subcommand)] enum Cmd { Send { #[arg(long)] gateway:String, #[arg(value_name="FILE")] file:String } } +fn main(){ + let cli = Cli::parse(); + match cli.cmd { + Cmd::Send{gateway,file} => { + let txt = std::fs::read_to_string(file).unwrap(); + let v: serde_json::Value = serde_json::from_str(&txt).unwrap(); + let res: serde_json::Value = ureq::post(&gateway).send_json(v).unwrap().into_json().unwrap(); + println!("{}", serde_json::to_string_pretty(&res).unwrap()); + } + } +} +EOF +cd ../../.. +``` + +--- + +## 9) Explorer (Next.js skeleton) +```bash +cd explorer +pnpm create next-app@latest . --ts --eslint --src-dir --import-alias "@/*" +pnpm add eventsource-parser +# Add a simple page later to connect to WS and render outcomes +cd .. +``` + +--- + +## 10) Calendar example (mock) +```bash +cd examples/calendar +pnpm init -y +pnpm add fastify +cat > server.ts <<'EOF' +import Fastify from 'fastify'; +const app = Fastify(); +app.post('/calendar.create_event', async (req:any)=>({ event_id:'evt_42', meeting_link:'https://meet/abc123', inputs:req.body })); +app.listen({port:8070, host:'0.0.0.0'}).then(()=>console.log('Mock calendar :8070')); +EOF +cat > client.intent.json <<'EOF' +{ "axp":"1.0", "id":"demo-1", "ts":"2025-10-09T22:10:00Z", "from":"agent://demo.client", "to":"agent://demo.scheduler", "type":"intent", + "auth":{"kid":"demo"}, + "intent":{ "name":"calendar.create_event", "inputs":{ "title":"Intro", "start":"2025-10-14T15:00:00Z", "end":"2025-10-14T15:30:00Z", "attendees":["a@b.com"] } } +} +EOF +cd ../.. +``` + +--- + +## 11) Docker Compose (local stack) +```bash +cat > docker-compose.yml <<'EOF' +services: + gateway: + build: ./runtime/axp-gateway + command: ["/bin/sh","-c","cargo run --release"] + ports: ["8080:8080"] + registry: + image: node:20 + working_dir: /app + volumes: ["./registry/service:/app"] + command: ["pnpm","exec","node","server.ts"] + ports: ["8090:8090"] + mock-calendar: + image: node:20 + working_dir: /app + volumes: ["./examples/calendar:/app"] + command: ["pnpm","exec","node","server.ts"] + ports: ["8070:8070"] +EOF +``` + +--- + +## 12) Local run & smoke test +```bash +# Terminal 1 +pnpm exec ts-node examples/calendar/server.ts +# Terminal 2 +cargo run --manifest-path runtime/axp-gateway/Cargo.toml +# Terminal 3 +cargo run --manifest-path cli/axp-cli/Cargo.toml -- send --gateway http://localhost:8080/v1/intent examples/calendar/client.intent.json +``` +Expected: JSON `outcome` echo payload. + +--- + +## 13) Conformance fixtures +```bash +cat > ci/conformance/valid-intent.json <<'EOF' +{ "axp":"1.0", "id":"X", "ts":"2025-10-09T00:00:00Z", "from":"agent://a", "to":"agent://b", "type":"intent", "auth":{"kid":"k"}, "intent":{"name":"noop","inputs":{}} } +EOF +``` + +Add a simple Node/Rust script to validate fixtures against schemas. + +--- + +## 14) CI Workflows (GitHub Actions) +```yaml +# .github/workflows/ci.yml +name: ci +on: [push, pull_request] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: { node-version: 20 } + - run: pnpm -v || corepack enable && pnpm -v + - run: pnpm -C sdks/ts/axp i && pnpm -C sdks/ts/axp build && pnpm -C sdks/ts/axp test + - uses: dtolnay/rust-toolchain@stable + - run: cargo build --manifest-path runtime/axp-gateway/Cargo.toml --verbose +``` + +Add separate release workflows for npm/crates when ready. + +--- + +## 15) Publishing (later) +- **npm**: set `NPM_TOKEN`, then `pnpm -C sdks/ts/axp publish --access public` +- **crates**: `cargo login` then `cargo publish --manifest-path sdks/rust/axp/Cargo.toml` +- **brew**: add tap repo and formula after CLI binary release + +--- + +## 16) AgentKit & MCP Bridges +- Fill `bridges/agentkit-bridge/connector.ts` to map AgentKit tool calls → AXP `intent`. +- Build MCP server that lists AXP Registry tools and forwards invocations to Gateway. + +--- + +## 17) Hardening checklist +- Signature verification & keyring +- Replay window & idempotency (RocksDB) +- Policy engine (spend caps, rate limits, PII redaction) +- Tracing (steps, token/cost meters), Prometheus metrics +- Registry OpenAPI import + validation +- WS/SSE streaming responses +- Error taxonomy & retries with backoff + +--- + +## 18) Success criteria (MVP) +- Send signed **intent** → receive **outcome** with trace over HTTP. +- Registry stores a tool and validates inputs by JSON Schema. +- CLI can `send` and pretty‑print outcome. +- Example calendar flow returns `{event_id, meeting_link}`. + +--- + +> With this plan, Cursor can scaffold and iterate AXP quickly: spec first, SDKs next, gateway/registry after, then bridges and explorer. Publish once the happy-path demo is green. + From a312884bbfcbd4ee31bfe19f513a490f4ab272cb Mon Sep 17 00:00:00 2001 From: Kevin Vera Date: Fri, 10 Oct 2025 02:35:39 -0400 Subject: [PATCH 2/2] full build ready, needs further testing --- .editorconfig | 10 + .github/workflows/ci.yml | 16 + .gitignore | 3 + bridges/agentkit-bridge/connector.mjs | 6 + bridges/agentkit-bridge/package.json | 14 + bridges/agentkit-bridge/src/toolhost.ts | 6 + bridges/mcp-bridge/package.json | 15 + bridges/mcp-bridge/server.mjs | 36 + .../src/adapters/axp-tool-adapter.ts | 5 + bridges/mcp-bridge/src/server.ts | 3 + ci/conformance/cap-missing.json | 2 + ci/conformance/idempotent-retry.sh | 8 + ci/conformance/policy-denied.json | 2 + ci/conformance/runner.mjs | 33 + ci/conformance/valid-intent.json | 2 + cli/axp-cli/Cargo.lock | 1488 +++++++++ cli/axp-cli/Cargo.toml | 17 + cli/axp-cli/src/main.rs | 75 + docker-compose.yml | 18 + examples/calendar/client.intent.json | 2 + examples/calendar/package.json | 14 + examples/calendar/server.mjs | 5 + examples/multi-agent/agent-a.mjs | 6 + examples/multi-agent/agent-b.mjs | 6 + examples/multi-agent/orchestrator.mjs | 12 + explorer/next-env.d.ts | 5 + explorer/next.config.mjs | 4 + explorer/package.json | 21 + explorer/src/app/layout.tsx | 8 + explorer/src/app/page.tsx | 51 + explorer/tsconfig.json | 36 + package.json | 17 + pnpm-lock.yaml | 1890 ++++++++++++ pnpm-workspace.yaml | 10 + registry/service/axp-registry.db | Bin 0 -> 4096 bytes registry/service/axp-registry.db-shm | Bin 0 -> 32768 bytes registry/service/axp-registry.db-wal | Bin 0 -> 24752 bytes registry/service/db.mjs | 5 + registry/service/package.json | 17 + registry/service/server.mjs | 55 + registry/service/src/db.ts | 8 + registry/service/src/openapi-import.ts | 7 + registry/service/src/server.ts | 17 + runtime/axp-gateway/.axp-idem/000008.sst | Bin 0 -> 1398 bytes runtime/axp-gateway/.axp-idem/000013.sst | Bin 0 -> 1342 bytes runtime/axp-gateway/.axp-idem/000018.sst | Bin 0 -> 1349 bytes runtime/axp-gateway/.axp-idem/CURRENT | 1 + runtime/axp-gateway/.axp-idem/IDENTITY | 1 + runtime/axp-gateway/.axp-idem/LOCK | 0 runtime/axp-gateway/.axp-idem/LOG | 254 ++ .../.axp-idem/LOG.old.1760071927849622 | 285 ++ .../.axp-idem/LOG.old.1760071927877339 | 255 ++ .../.axp-idem/LOG.old.1760072052308710 | 255 ++ .../.axp-idem/LOG.old.1760072052337639 | 255 ++ .../.axp-idem/LOG.old.1760073192029956 | 254 ++ .../.axp-idem/LOG.old.1760073192062063 | 254 ++ runtime/axp-gateway/.axp-idem/MANIFEST-000032 | Bin 0 -> 460 bytes runtime/axp-gateway/.axp-idem/OPTIONS-000030 | 204 ++ runtime/axp-gateway/.axp-idem/OPTIONS-000034 | 204 ++ runtime/axp-gateway/Cargo.lock | 2733 +++++++++++++++++ runtime/axp-gateway/Cargo.toml | 23 + runtime/axp-gateway/config.json | 11 + runtime/axp-gateway/src/api.rs | 121 + runtime/axp-gateway/src/audit.rs | 7 + runtime/axp-gateway/src/config.rs | 26 + runtime/axp-gateway/src/idempotency.rs | 31 + runtime/axp-gateway/src/main.rs | 20 + runtime/axp-gateway/src/policy.rs | 35 + runtime/axp-gateway/src/router.rs | 13 + runtime/axp-gateway/src/signer.rs | 43 + runtime/axp-gateway/src/tracing.rs | 10 + runtime/axp-gateway/tests/e2e.rs | 12 + scripts/start-dev.sh | 25 + scripts/stop-dev.sh | 16 + sdks/rust/axp/Cargo.toml | 30 + sdks/rust/axp/src/envelope.rs | 17 + sdks/rust/axp/src/lib.rs | 11 + sdks/rust/axp/src/signing.rs | 40 + sdks/rust/axp/src/transports/http.rs | 25 + sdks/rust/axp/src/transports/quic.rs | 14 + sdks/rust/axp/src/transports/ws.rs | 24 + sdks/rust/axp/src/validate.rs | 9 + sdks/rust/axp/src/verify.rs | 30 + sdks/rust/axp/tests/conformance.rs | 8 + sdks/ts/axp/package.json | 30 + sdks/ts/axp/src/agent-server.ts | 21 + sdks/ts/axp/src/args-hash.ts | 9 + sdks/ts/axp/src/backoff.ts | 9 + sdks/ts/axp/src/canon.ts | 13 + sdks/ts/axp/src/client.ts | 30 + sdks/ts/axp/src/envelope.ts | 12 + sdks/ts/axp/src/idempotency.ts | 21 + sdks/ts/axp/src/index.ts | 11 + sdks/ts/axp/src/sign.ts | 21 + sdks/ts/axp/src/transport-http.ts | 20 + sdks/ts/axp/src/transport-sse.ts | 23 + sdks/ts/axp/src/transport-ws.ts | 31 + sdks/ts/axp/src/validate.ts | 86 + sdks/ts/axp/src/verify.ts | 3 + sdks/ts/axp/test/conformance.test.ts | 10 + sdks/ts/axp/test/envelope.test.ts | 31 + sdks/ts/axp/tsconfig.json | 15 + spec/axp-core.md | 31 + spec/json-schema/envelope.schema.json | 29 + spec/json-schema/error.schema.json | 20 + spec/json-schema/intent.schema.json | 19 + spec/json-schema/outcome.schema.json | 40 + spec/json-schema/registry.schema.json | 33 + 108 files changed, 10079 insertions(+) create mode 100644 .editorconfig create mode 100644 .github/workflows/ci.yml create mode 100644 bridges/agentkit-bridge/connector.mjs create mode 100644 bridges/agentkit-bridge/package.json create mode 100644 bridges/agentkit-bridge/src/toolhost.ts create mode 100644 bridges/mcp-bridge/package.json create mode 100644 bridges/mcp-bridge/server.mjs create mode 100644 bridges/mcp-bridge/src/adapters/axp-tool-adapter.ts create mode 100644 bridges/mcp-bridge/src/server.ts create mode 100644 ci/conformance/cap-missing.json create mode 100644 ci/conformance/idempotent-retry.sh create mode 100644 ci/conformance/policy-denied.json create mode 100644 ci/conformance/runner.mjs create mode 100644 ci/conformance/valid-intent.json create mode 100644 cli/axp-cli/Cargo.lock create mode 100644 cli/axp-cli/Cargo.toml create mode 100644 cli/axp-cli/src/main.rs create mode 100644 docker-compose.yml create mode 100644 examples/calendar/client.intent.json create mode 100644 examples/calendar/package.json create mode 100644 examples/calendar/server.mjs create mode 100644 examples/multi-agent/agent-a.mjs create mode 100644 examples/multi-agent/agent-b.mjs create mode 100644 examples/multi-agent/orchestrator.mjs create mode 100644 explorer/next-env.d.ts create mode 100644 explorer/next.config.mjs create mode 100644 explorer/package.json create mode 100644 explorer/src/app/layout.tsx create mode 100644 explorer/src/app/page.tsx create mode 100644 explorer/tsconfig.json create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 registry/service/axp-registry.db create mode 100644 registry/service/axp-registry.db-shm create mode 100644 registry/service/axp-registry.db-wal create mode 100644 registry/service/db.mjs create mode 100644 registry/service/package.json create mode 100644 registry/service/server.mjs create mode 100644 registry/service/src/db.ts create mode 100644 registry/service/src/openapi-import.ts create mode 100644 registry/service/src/server.ts create mode 100644 runtime/axp-gateway/.axp-idem/000008.sst create mode 100644 runtime/axp-gateway/.axp-idem/000013.sst create mode 100644 runtime/axp-gateway/.axp-idem/000018.sst create mode 100644 runtime/axp-gateway/.axp-idem/CURRENT create mode 100644 runtime/axp-gateway/.axp-idem/IDENTITY create mode 100644 runtime/axp-gateway/.axp-idem/LOCK create mode 100644 runtime/axp-gateway/.axp-idem/LOG create mode 100644 runtime/axp-gateway/.axp-idem/LOG.old.1760071927849622 create mode 100644 runtime/axp-gateway/.axp-idem/LOG.old.1760071927877339 create mode 100644 runtime/axp-gateway/.axp-idem/LOG.old.1760072052308710 create mode 100644 runtime/axp-gateway/.axp-idem/LOG.old.1760072052337639 create mode 100644 runtime/axp-gateway/.axp-idem/LOG.old.1760073192029956 create mode 100644 runtime/axp-gateway/.axp-idem/LOG.old.1760073192062063 create mode 100644 runtime/axp-gateway/.axp-idem/MANIFEST-000032 create mode 100644 runtime/axp-gateway/.axp-idem/OPTIONS-000030 create mode 100644 runtime/axp-gateway/.axp-idem/OPTIONS-000034 create mode 100644 runtime/axp-gateway/Cargo.lock create mode 100644 runtime/axp-gateway/Cargo.toml create mode 100644 runtime/axp-gateway/config.json create mode 100644 runtime/axp-gateway/src/api.rs create mode 100644 runtime/axp-gateway/src/audit.rs create mode 100644 runtime/axp-gateway/src/config.rs create mode 100644 runtime/axp-gateway/src/idempotency.rs create mode 100644 runtime/axp-gateway/src/main.rs create mode 100644 runtime/axp-gateway/src/policy.rs create mode 100644 runtime/axp-gateway/src/router.rs create mode 100644 runtime/axp-gateway/src/signer.rs create mode 100644 runtime/axp-gateway/src/tracing.rs create mode 100644 runtime/axp-gateway/tests/e2e.rs create mode 100755 scripts/start-dev.sh create mode 100755 scripts/stop-dev.sh create mode 100644 sdks/rust/axp/Cargo.toml create mode 100644 sdks/rust/axp/src/envelope.rs create mode 100644 sdks/rust/axp/src/lib.rs create mode 100644 sdks/rust/axp/src/signing.rs create mode 100644 sdks/rust/axp/src/transports/http.rs create mode 100644 sdks/rust/axp/src/transports/quic.rs create mode 100644 sdks/rust/axp/src/transports/ws.rs create mode 100644 sdks/rust/axp/src/validate.rs create mode 100644 sdks/rust/axp/src/verify.rs create mode 100644 sdks/rust/axp/tests/conformance.rs create mode 100644 sdks/ts/axp/package.json create mode 100644 sdks/ts/axp/src/agent-server.ts create mode 100644 sdks/ts/axp/src/args-hash.ts create mode 100644 sdks/ts/axp/src/backoff.ts create mode 100644 sdks/ts/axp/src/canon.ts create mode 100644 sdks/ts/axp/src/client.ts create mode 100644 sdks/ts/axp/src/envelope.ts create mode 100644 sdks/ts/axp/src/idempotency.ts create mode 100644 sdks/ts/axp/src/index.ts create mode 100644 sdks/ts/axp/src/sign.ts create mode 100644 sdks/ts/axp/src/transport-http.ts create mode 100644 sdks/ts/axp/src/transport-sse.ts create mode 100644 sdks/ts/axp/src/transport-ws.ts create mode 100644 sdks/ts/axp/src/validate.ts create mode 100644 sdks/ts/axp/src/verify.ts create mode 100644 sdks/ts/axp/test/conformance.test.ts create mode 100644 sdks/ts/axp/test/envelope.test.ts create mode 100644 sdks/ts/axp/tsconfig.json create mode 100644 spec/axp-core.md create mode 100644 spec/json-schema/envelope.schema.json create mode 100644 spec/json-schema/error.schema.json create mode 100644 spec/json-schema/intent.schema.json create mode 100644 spec/json-schema/outcome.schema.json create mode 100644 spec/json-schema/registry.schema.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c814d30 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..086c270 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,16 @@ +name: ci +on: [push, pull_request] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: { node-version: 20 } + - run: corepack enable && pnpm -v + - run: pnpm -C sdks/ts/axp i && pnpm -C sdks/ts/axp build && pnpm -C sdks/ts/axp test + - run: pnpm add -w ajv ajv-formats + - run: node ci/conformance/runner.mjs + - uses: dtolnay/rust-toolchain@stable + - run: cargo build --manifest-path runtime/axp-gateway/Cargo.toml --verbose + diff --git a/.gitignore b/.gitignore index 9a5aced..0bf5f52 100644 --- a/.gitignore +++ b/.gitignore @@ -137,3 +137,6 @@ dist # Vite logs files vite.config.js.timestamp-* vite.config.ts.timestamp-* + +# Rust build outputs +**/target/ diff --git a/bridges/agentkit-bridge/connector.mjs b/bridges/agentkit-bridge/connector.mjs new file mode 100644 index 0000000..a0abd8c --- /dev/null +++ b/bridges/agentkit-bridge/connector.mjs @@ -0,0 +1,6 @@ +export async function callAxpTool(tool, args, cfg){ + const env = { axp:'1.0', id: crypto.randomUUID(), ts:new Date().toISOString(), from:cfg.from, to:cfg.to, type:'intent', auth:{kid:cfg.kid}, intent:{ name:tool, inputs:args, capabilities:cfg.capabilities||[] } }; + const res = await fetch(cfg.gateway, { method:'POST', headers:{'content-type':'application/json'}, body: JSON.stringify(env) }); + return await res.json(); +} + diff --git a/bridges/agentkit-bridge/package.json b/bridges/agentkit-bridge/package.json new file mode 100644 index 0000000..d5a3ffb --- /dev/null +++ b/bridges/agentkit-bridge/package.json @@ -0,0 +1,14 @@ +{ + "name": "@stone/axp-agentkit-bridge", + "version": "0.1.0", + "license": "Apache-2.0", + "type": "module", + "private": true, + "dependencies": { + "undici": "^6.19.8" + }, + "scripts": { + "start": "node connector.mjs" + } +} + diff --git a/bridges/agentkit-bridge/src/toolhost.ts b/bridges/agentkit-bridge/src/toolhost.ts new file mode 100644 index 0000000..0d32eb5 --- /dev/null +++ b/bridges/agentkit-bridge/src/toolhost.ts @@ -0,0 +1,6 @@ +export async function executeAxpIntent(gateway: string, tool: string, args: any, cfg: any) { + const env = { axp:'1.0', id: crypto.randomUUID(), ts:new Date().toISOString(), from:cfg.from, to:cfg.to, type:'intent', auth:{kid:cfg.kid}, intent:{ name:tool, inputs:args } }; + const res = await fetch(gateway, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(env) }); + return await res.json(); +} + diff --git a/bridges/mcp-bridge/package.json b/bridges/mcp-bridge/package.json new file mode 100644 index 0000000..12cacd0 --- /dev/null +++ b/bridges/mcp-bridge/package.json @@ -0,0 +1,15 @@ +{ + "name": "@stone/axp-mcp-bridge", + "version": "0.1.0", + "license": "Apache-2.0", + "type": "module", + "private": true, + "dependencies": { + "undici": "^6.19.8", + "ws": "^8.18.0" + }, + "scripts": { + "start": "node server.mjs" + } +} + diff --git a/bridges/mcp-bridge/server.mjs b/bridges/mcp-bridge/server.mjs new file mode 100644 index 0000000..bec8287 --- /dev/null +++ b/bridges/mcp-bridge/server.mjs @@ -0,0 +1,36 @@ +import http from 'node:http'; +import { readFile } from 'node:fs/promises'; + +const REGISTRY_URL = process.env.AXP_REGISTRY_URL || 'http://localhost:8090'; +const GATEWAY_URL = process.env.AXP_GATEWAY_URL || 'http://localhost:8080/v1/intent'; +const PORT = Number(process.env.PORT || 8095); + +function json(res, code, data){ res.writeHead(code, { 'content-type': 'application/json' }); res.end(JSON.stringify(data)); } + +async function handleListTools(req, res){ + try { + const resp = await fetch(`${REGISTRY_URL}/tools`); + const data = await resp.json(); + json(res, 200, data); + } catch (e) { json(res, 500, { error: String(e) }); } +} + +async function handleCall(req, res){ + try { + const body = await new Promise((resolve, reject)=>{ let b=''; req.on('data', c=>b+=c); req.on('end',()=>resolve(b)); req.on('error',reject); }); + const { tool, args, from, to, kid, capabilities } = JSON.parse(body||'{}'); + const env = { axp:'1.0', id: crypto.randomUUID(), ts:new Date().toISOString(), from, to, type:'intent', auth:{kid}, intent:{ name:tool, inputs:args, capabilities:capabilities||[] } }; + const out = await (await fetch(GATEWAY_URL, { method:'POST', headers:{'content-type':'application/json'}, body: JSON.stringify(env) })).json(); + json(res, 200, out); + } catch (e) { json(res, 500, { error: String(e) }); } +} + +const server = http.createServer(async (req, res) => { + const url = new URL(req.url||'/', `http://${req.headers.host}`); + if (req.method==='GET' && url.pathname==='/tools') return handleListTools(req, res); + if (req.method==='POST' && url.pathname==='/call') return handleCall(req, res); + json(res, 404, { error: 'not found' }); +}); + +server.listen(PORT, ()=> console.log(`MCP→AXP bridge listening :${PORT}`)); + diff --git a/bridges/mcp-bridge/src/adapters/axp-tool-adapter.ts b/bridges/mcp-bridge/src/adapters/axp-tool-adapter.ts new file mode 100644 index 0000000..d5602c5 --- /dev/null +++ b/bridges/mcp-bridge/src/adapters/axp-tool-adapter.ts @@ -0,0 +1,5 @@ +export async function forwardToAxpGateway(gateway: string, env: any) { + const res = await fetch(gateway, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(env) }); + return await res.json(); +} + diff --git a/bridges/mcp-bridge/src/server.ts b/bridges/mcp-bridge/src/server.ts new file mode 100644 index 0000000..1cafe6a --- /dev/null +++ b/bridges/mcp-bridge/src/server.ts @@ -0,0 +1,3 @@ +// Placeholder MCP server that would expose tools from the registry and forward calls to the AXP gateway. +console.log('MCP bridge server starting...'); + diff --git a/ci/conformance/cap-missing.json b/ci/conformance/cap-missing.json new file mode 100644 index 0000000..2c3fd93 --- /dev/null +++ b/ci/conformance/cap-missing.json @@ -0,0 +1,2 @@ +{ "axp":"1.0", "id":"C1", "ts":"2025-10-09T00:00:00Z", "from":"agent://a", "to":"agent://b", "type":"intent", "auth":{"kid":"k"}, "intent":{"name":"secure.op","inputs":{}, "capabilities": [] } } + diff --git a/ci/conformance/idempotent-retry.sh b/ci/conformance/idempotent-retry.sh new file mode 100644 index 0000000..7dca199 --- /dev/null +++ b/ci/conformance/idempotent-retry.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +FILE=${1:-ci/conformance/valid-intent.json} +OUT1=$(curl -sS -X POST -H 'content-type: application/json' http://localhost:8080/v1/intent --data-binary @${FILE}) +OUT2=$(curl -sS -X POST -H 'content-type: application/json' http://localhost:8080/v1/intent --data-binary @${FILE}) +diff <(echo "$OUT1") <(echo "$OUT2") +echo "Idempotent retry returned the same outcome" + diff --git a/ci/conformance/policy-denied.json b/ci/conformance/policy-denied.json new file mode 100644 index 0000000..f7ff9fd --- /dev/null +++ b/ci/conformance/policy-denied.json @@ -0,0 +1,2 @@ +{ "axp":"1.0", "id":"P1", "ts":"2025-10-09T00:00:00Z", "from":"agent://a", "to":"agent://b", "type":"intent", "auth":{"kid":"k"}, "intent":{"name":"noop","inputs":{}, "policies": { "spend_usd_max": 0 } } } + diff --git a/ci/conformance/runner.mjs b/ci/conformance/runner.mjs new file mode 100644 index 0000000..a0d0416 --- /dev/null +++ b/ci/conformance/runner.mjs @@ -0,0 +1,33 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; +import Ajv from 'ajv'; +import addFormats from 'ajv-formats'; + +const ajv = addFormats(new Ajv({ allErrors: true, strict: false })); + +const base = path.resolve(process.cwd(), 'spec/json-schema'); +const envelope = JSON.parse(await fs.readFile(path.join(base, 'envelope.schema.json'), 'utf8')); +const intent = JSON.parse(await fs.readFile(path.join(base, 'intent.schema.json'), 'utf8')); +const outcome = JSON.parse(await fs.readFile(path.join(base, 'outcome.schema.json'), 'utf8')); +const errorSchema = JSON.parse(await fs.readFile(path.join(base, 'error.schema.json'), 'utf8')); +ajv.addSchema(intent); +ajv.addSchema(outcome); +ajv.addSchema(errorSchema); +const validateEnvelope = ajv.compile(envelope); + +const fixtureDir = path.resolve(process.cwd(), 'ci/conformance'); +const files = (await fs.readdir(fixtureDir)).filter(f => f.endsWith('.json')); +let failures = 0; +for (const f of files) { + const txt = await fs.readFile(path.join(fixtureDir, f), 'utf8'); + const v = JSON.parse(txt); + const ok = validateEnvelope(v); + if (!ok) { + failures++; + console.error(`FAIL ${f}:`, validateEnvelope.errors); + } else { + console.log(`OK ${f}`); + } +} +if (failures) process.exit(1); + diff --git a/ci/conformance/valid-intent.json b/ci/conformance/valid-intent.json new file mode 100644 index 0000000..7b230de --- /dev/null +++ b/ci/conformance/valid-intent.json @@ -0,0 +1,2 @@ +{ "axp":"1.0", "id":"X", "ts":"2025-10-09T00:00:00Z", "from":"agent://a", "to":"agent://b", "type":"intent", "auth":{"kid":"k"}, "intent":{"name":"noop","inputs":{}} } + diff --git a/cli/axp-cli/Cargo.lock b/cli/axp-cli/Cargo.lock new file mode 100644 index 0000000..c955669 --- /dev/null +++ b/cli/axp-cli/Cargo.lock @@ -0,0 +1,1488 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.60.2", +] + +[[package]] +name = "axp-cli" +version = "0.1.0" +dependencies = [ + "base64", + "clap", + "ed25519-dalek", + "futures-executor", + "futures-util", + "serde_json", + "tokio", + "tokio-tungstenite", + "ureq", +] + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" + +[[package]] +name = "bitflags" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cc" +version = "1.2.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d05d92f4b1fd76aad469d46cdd858ca761576082cd37df81416691e50199fb" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "clap" +version = "4.5.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0399f9d26e5191ce32c498bebd31e7a3ceabc2745f0ac54af3f335126c3f24b3" + +[[package]] +name = "flate2" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-macro", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "io-uring" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.59.0", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "potential_utf" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls" +version = "0.23.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.103.7", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.47.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "pin-project-lite", + "slab", + "socket2", + "tokio-macros", + "windows-sys 0.59.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.4", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +dependencies = [ + "futures-util", + "log", + "rustls 0.22.4", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite", + "webpki-roots 0.26.11", +] + +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand", + "rustls 0.22.4", + "rustls-pki-types", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +dependencies = [ + "base64", + "flate2", + "log", + "once_cell", + "rustls 0.23.32", + "rustls-pki-types", + "serde", + "serde_json", + "url", + "webpki-roots 0.26.11", +] + +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.3", +] + +[[package]] +name = "webpki-roots" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b130c0d2d49f8b6889abc456e795e82525204f27c42cf767cf0d7734e089b8" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/cli/axp-cli/Cargo.toml b/cli/axp-cli/Cargo.toml new file mode 100644 index 0000000..7e32475 --- /dev/null +++ b/cli/axp-cli/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "axp-cli" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +[dependencies] +clap = { version = "4", features=["derive"] } +serde_json = "1" +ureq = { version = "2", features=["json"] } +tokio = { version = "1", features=["rt-multi-thread", "macros"] } +tokio-tungstenite = { version = "0.21", features=["rustls-tls-webpki-roots"] } +futures-util = "0.3" +futures-executor = "0.3" +base64 = "0.22" +ed25519-dalek = { version = "2", features=["rand_core"] } + diff --git a/cli/axp-cli/src/main.rs b/cli/axp-cli/src/main.rs new file mode 100644 index 0000000..d45cd62 --- /dev/null +++ b/cli/axp-cli/src/main.rs @@ -0,0 +1,75 @@ +use clap::{Parser, Subcommand}; +use base64::Engine; +use ed25519_dalek::Signer; + +#[derive(Parser)] +#[command(name="axp")] struct Cli { #[command(subcommand)] cmd: Cmd } +#[derive(Subcommand)] enum Cmd { + Send { #[arg(long)] gateway:String, #[arg(value_name="FILE")] file:String }, + Recv { #[arg(long)] ws_url:String, #[arg(value_name="FILE")] file:String }, + Sign { #[arg(value_name="FILE")] file:String, #[arg(long)] secret_key_b64:String }, + Validate { #[arg(value_name="FILE")] file:String } +} + +fn main(){ + let cli = Cli::parse(); + match cli.cmd { + Cmd::Send{gateway,file} => { + let txt = std::fs::read_to_string(file).expect("read file"); + let v: serde_json::Value = serde_json::from_str(&txt).expect("json"); + let res: serde_json::Value = ureq::post(&gateway).send_json(v).expect("http").into_json().expect("json"); + println!("{}", serde_json::to_string_pretty(&res).unwrap()); + }, + Cmd::Sign{file, secret_key_b64} => { + let txt = std::fs::read_to_string(file).expect("read file"); + let mut v: serde_json::Value = serde_json::from_str(&txt).expect("json"); + let key_bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(secret_key_b64.as_bytes()).expect("b64"); + let sk = ed25519_dalek::SigningKey::from_bytes(&key_bytes.try_into().expect("32 bytes")); + if let Some(a) = v.get_mut("auth") { if let Some(o) = a.as_object_mut() { o.remove("sig"); } } + let msg = canon(&v); + let sig: ed25519_dalek::Signature = sk.sign(&msg); + let sig_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(sig.to_bytes()); + let auth = v.get_mut("auth").and_then(|x| x.as_object_mut()).expect("auth object"); + auth.insert("sig".into(), serde_json::Value::String(sig_b64)); + println!("{}", serde_json::to_string_pretty(&v).unwrap()); + }, + Cmd::Recv{ws_url, file} => { + let txt = std::fs::read_to_string(file).expect("read file"); + let v: serde_json::Value = serde_json::from_str(&txt).expect("json"); + let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build().expect("rt"); + rt.block_on(async move { + use futures_util::{SinkExt, StreamExt}; + let (mut ws, _resp) = tokio_tungstenite::connect_async(&ws_url).await.expect("ws"); + ws.send(tokio_tungstenite::tungstenite::Message::Text(v.to_string())).await.expect("send"); + if let Some(Ok(msg)) = ws.next().await { + if let tokio_tungstenite::tungstenite::Message::Text(t) = msg { println!("{}", t); } + } + }); + }, + Cmd::Validate{file} => { + let txt = std::fs::read_to_string(file).expect("read file"); + let v: serde_json::Value = serde_json::from_str(&txt).expect("json"); + // minimal check for required fields + for k in ["axp","id","ts","from","to","type","auth"] { assert!(v.get(k).is_some(), "missing {}", k); } + println!("ok"); + } + } +} + +fn canon(value: &serde_json::Value) -> Vec { + fn normalize(v: &serde_json::Value) -> serde_json::Value { + match v { + serde_json::Value::Array(arr) => serde_json::Value::Array(arr.iter().map(normalize).collect()), + serde_json::Value::Object(map) => { + let mut keys: Vec<&String> = map.keys().collect(); + keys.sort(); + let mut out = serde_json::Map::with_capacity(map.len()); + for k in keys { out.insert(k.clone(), normalize(&map[k])); } + serde_json::Value::Object(out) + } + _ => v.clone() + } + } + serde_json::to_vec(&normalize(value)).unwrap() +} + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..06a5b8d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +services: + gateway: + build: ./runtime/axp-gateway + command: ["/bin/sh","-c","cargo run --release"] + ports: ["8080:8080"] + registry: + image: node:20 + working_dir: /app + volumes: ["./registry/service:/app"] + command: ["node","server.mjs"] + ports: ["8090:8090"] + mock-calendar: + image: node:20 + working_dir: /app + volumes: ["./examples/calendar:/app"] + command: ["node","server.mjs"] + ports: ["8070:8070"] + diff --git a/examples/calendar/client.intent.json b/examples/calendar/client.intent.json new file mode 100644 index 0000000..e964c86 --- /dev/null +++ b/examples/calendar/client.intent.json @@ -0,0 +1,2 @@ +{ "axp":"1.0", "id":"demo-1", "ts":"2025-10-09T22:10:00Z", "from":"agent://demo.client", "to":"agent://demo.scheduler", "type":"intent", "auth":{"kid":"demo"}, "intent":{ "name":"calendar.create_event", "inputs":{ "title":"Intro", "start":"2025-10-14T15:00:00Z", "end":"2025-10-14T15:30:00Z", "attendees":["a@b.com"] } } } + diff --git a/examples/calendar/package.json b/examples/calendar/package.json new file mode 100644 index 0000000..1cabcf3 --- /dev/null +++ b/examples/calendar/package.json @@ -0,0 +1,14 @@ +{ + "name": "@stone/axp-calendar-example", + "version": "0.1.0", + "private": true, + "license": "Apache-2.0", + "type": "module", + "dependencies": { + "fastify": "^4.28.1" + }, + "scripts": { + "start": "node server.mjs" + } +} + diff --git a/examples/calendar/server.mjs b/examples/calendar/server.mjs new file mode 100644 index 0000000..bbae40f --- /dev/null +++ b/examples/calendar/server.mjs @@ -0,0 +1,5 @@ +import Fastify from 'fastify'; +const app = Fastify(); +app.post('/calendar.create_event', async (req) => ({ event_id: 'evt_42', meeting_link: 'https://meet/abc123', inputs: req.body })); +app.listen({ port: 8070, host: '0.0.0.0' }).then(() => console.log('Mock calendar :8070')); + diff --git a/examples/multi-agent/agent-a.mjs b/examples/multi-agent/agent-a.mjs new file mode 100644 index 0000000..ce78623 --- /dev/null +++ b/examples/multi-agent/agent-a.mjs @@ -0,0 +1,6 @@ +import { createAgentServer } from '../../sdks/ts/axp/dist/index.js'; +const app = createAgentServer({ + 'hello': async (inputs)=>({ message:`Hello ${inputs.who}` }) +}); +app.listen({ port: 8181, host: '0.0.0.0' }).then(()=> console.log('agent-a :8181')); + diff --git a/examples/multi-agent/agent-b.mjs b/examples/multi-agent/agent-b.mjs new file mode 100644 index 0000000..2d34808 --- /dev/null +++ b/examples/multi-agent/agent-b.mjs @@ -0,0 +1,6 @@ +import { createAgentServer } from '../../sdks/ts/axp/dist/index.js'; +const app = createAgentServer({ + 'goodbye': async (inputs)=>({ message:`Goodbye ${inputs.who}` }) +}); +app.listen({ port: 8182, host: '0.0.0.0' }).then(()=> console.log('agent-b :8182')); + diff --git a/examples/multi-agent/orchestrator.mjs b/examples/multi-agent/orchestrator.mjs new file mode 100644 index 0000000..573bd1e --- /dev/null +++ b/examples/multi-agent/orchestrator.mjs @@ -0,0 +1,12 @@ +import { AxpClient } from '../../sdks/ts/axp/dist/index.js'; +const client = new AxpClient('http://localhost:8080/v1/intent'); +const base = { axp: '1.0', ts: new Date().toISOString(), from: 'agent://orch', type: 'intent', auth:{kid:'demo'} }; + +async function main(){ + const step1 = await client.sendIntent({ ...base, id:'orch-1', to:'agent://agent.a', intent:{ name:'hello', inputs:{ who:'world' }, capabilities:['basic'] } }); + console.log('step1', step1); + const step2 = await client.sendIntent({ ...base, id:'orch-2', to:'agent://agent.b', intent:{ name:'goodbye', inputs:{ who:'world' }, capabilities:['basic'] } }); + console.log('step2', step2); +} +main().catch(err=>{ console.error(err); process.exit(1); }); + diff --git a/explorer/next-env.d.ts b/explorer/next-env.d.ts new file mode 100644 index 0000000..4f11a03 --- /dev/null +++ b/explorer/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/explorer/next.config.mjs b/explorer/next.config.mjs new file mode 100644 index 0000000..4d33b1f --- /dev/null +++ b/explorer/next.config.mjs @@ -0,0 +1,4 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { reactStrictMode: true }; +export default nextConfig; + diff --git a/explorer/package.json b/explorer/package.json new file mode 100644 index 0000000..da94341 --- /dev/null +++ b/explorer/package.json @@ -0,0 +1,21 @@ +{ + "name": "@stone/axp-explorer", + "version": "0.1.0", + "private": true, + "license": "Apache-2.0", + "type": "module", + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start" + }, + "dependencies": { + "eventsource-parser": "^1.1.2", + "next": "14.2.5", + "react": "18.3.1", + "react-dom": "18.3.1" + }, + "devDependencies": { + "@types/react": "19.2.2" + } +} diff --git a/explorer/src/app/layout.tsx b/explorer/src/app/layout.tsx new file mode 100644 index 0000000..d314c33 --- /dev/null +++ b/explorer/src/app/layout.tsx @@ -0,0 +1,8 @@ +export default function RootLayout({ children }: { children: React.ReactNode }){ + return ( + + {children} + + ); +} + diff --git a/explorer/src/app/page.tsx b/explorer/src/app/page.tsx new file mode 100644 index 0000000..99d45ba --- /dev/null +++ b/explorer/src/app/page.tsx @@ -0,0 +1,51 @@ +"use client"; +import { useState } from 'react'; + +export default function Page(){ + const [gateway, setGateway] = useState('http://localhost:8080/v1/intent'); + const [wsUrl, setWsUrl] = useState('ws://localhost:8080/v1/stream'); + const [intent, setIntent] = useState(JSON.stringify({ axp:'1.0', id:'demo-ui', ts:new Date().toISOString(), from:'agent://ui', to:'agent://gw', type:'intent', auth:{kid:'demo'}, intent:{ name:'noop', inputs:{} } }, null, 2)); + const [httpResp, setHttpResp] = useState(''); + const [wsResp, setWsResp] = useState(''); + + async function sendHttp(){ + try { + const res = await fetch(gateway, { method:'POST', headers:{'content-type':'application/json'}, body: intent, mode:'cors' }); + const txt = await res.text(); + setHttpResp(txt); + } catch (e:any) { + setHttpResp(`HTTP error: ${e?.message||e}`); + } + } + + function sendWs(){ + try { + const ws = new WebSocket(wsUrl); + ws.onopen = ()=> ws.send(intent); + ws.onmessage = (ev)=> setWsResp(String(ev.data)); + ws.onerror = (ev)=> setWsResp(`WS error`); + } catch (e:any) { + setWsResp(`WS error: ${e?.message||e}`); + } + } + + return ( +
+

AXP Explorer

+ + +