Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 32 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,24 @@ one request object or a mini-batch array; `serper.reviews` accepts only one
object. See the bundled [Serper guide](skills/xapi/guides/serper.md) for the
Action list, examples, and dynamic per-credit billing.

For API Actions, `/` uses the bare service ID (`solana`), while literal `/root`
uses `solana.root`; `.root` is not a root alias. Use exact discovered IDs and
endpoint UUIDs without rewriting them. A replacement serving revision can
invalidate old UUIDs, so rediscover and inspect the new schema on 404 rather
than falling back to a service alias. Multiple HTTP methods still require
`--method` when selecting an endpoint.

Single-schema lookup uses `POST /v1/actions/batch` with one ID, retaining
compatibility with backends without the dedicated `GET /v1/actions/by-id/{id}`
route. The legacy dynamic GET can collide with collection names such as
`search`, `categories` and `services`.

For typed Decisions (`state/questions/answers`, no SSE or chat translation),
use the [AI Gateway guide](skills/xapi/guides/ai_gateway.md). For dedicated
non-EVM RPC or Aptos JSON/BCS, use the [BlockPI guide](skills/xapi/guides/blockpi.md).
`call --output` saves a binary response; raw BCS request input currently needs
the service HTTP Gateway, not JSON Action execution.

Search uses `--sort default|relevance|price`. `default` is the recommended
order: it considers keyword coverage and match quality first, then favors
stable built-in capabilities when matches are otherwise comparable.
Expand Down Expand Up @@ -211,8 +229,9 @@ Saving that field alone does not re-import endpoint definitions; configure
- `--mode merge` (default) sends PATCH and preserves omitted fields/endpoints.
Existing endpoint edits require `id`, e.g.
`{"endpoints":[{"id":"ENDPOINT_ID","costPerCall":"0.002"}]}`.
- `--allow-new-endpoints` explicitly permits ID-less merge entries to create
endpoints. Repeating such a merge can create duplicates.
- `--allow-new-endpoints` permits ID-less merge entries past the CLI guard;
backend matching by stable `endpointKey` or derived method/path still applies.
Prefer IDs from the target working revision for ordinary updates.
- `--mode replace` sends PUT. If `endpoints` is provided, it replaces the
endpoint list; include every endpoint you intend to keep. Omitted fields
otherwise follow backend PUT semantics. Use full configuration for replacement.
Expand All @@ -222,6 +241,9 @@ configuration returned by the backend. Find endpoint IDs in
`currentVersion.endpoints`; `provider versions` returns working revision IDs
in `majors[].working.id`. For an already-published API, use the existing
`provider revision start <service-id> <major>` command to create a working revision.
Cloning creates new endpoint UUIDs and preserves `endpointKey`; re-read the
working contract after cloning. Use `majors[].servingRevisionId` to identify
the serving revision. A working revision in `IN_REVIEW` cannot be edited.
`provider update` without `--revision` continues to update service metadata
and rate limits. Existing `version update`, `publish`, and positional `review`
commands remain available. The `submit` and `review --revision` forms are
Expand All @@ -234,7 +256,8 @@ rebuilds the derived authentication configuration. An empty map without an
explicit `authConfig` clears those credentials. Omitting both fields preserves them.

`submit` returns `{serviceId, revisionId, submission}`. A successful submission
does not guarantee publication. `wait` checks the requested revision, succeeds
does not guarantee publication, but a passing review can automatically publish
and switch live traffic. `wait` checks the requested revision, succeeds
only for `PUBLISHED`, and outputs the review report with `success` and `reason`.
`--changelog` is limited to 2,000 characters by both the CLI and backend.
Rejection, a draft/sandbox/suspended revision, or a legacy manual-review hold
Expand All @@ -251,6 +274,12 @@ to inspect the result before repeating the operation. No npm release is implied
by a local source checkout; use `bun run src/index.ts provider ...` or build and
run `node dist/index.js provider ...` while testing unreleased changes.

Use the [AI provider reference](skills/xapi-provider/reference/ai-spec-and-models.md)
for Decisions and DYNAMIC upstream-cost billing, and
[HTTP transport](skills/xapi-provider/reference/http-transport.md) for HTTP/2
OpenAPI metadata, HTTPS/ALPN and update/clear behavior. Import and version update
already pass these fields; no HTTP/2 CLI client or flag is required.

### Sandbox Commands

Sandbox commands provide an AI-friendly cloud computer lifecycle. The fastest
Expand Down
21 changes: 16 additions & 5 deletions skills/xapi-provider/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@ scope. Do not try unrelated keys.
- Fixed, token, resource, or zero-price billing: read
[billing](reference/billing-modes.md) and
[free access](reference/free-api-access.md).
- AI models or token accounting: read
- AI models, typed Decisions, token accounting or upstream-cost billing: read
[AI services](reference/ai-spec-and-models.md).
- HTTP/2 upstreams or transport metadata in OpenAPI contracts: read
[HTTP transport](reference/http-transport.md). CLI import/update already
passes the configuration; no HTTP/2 CLI flag is needed.
- WebSocket endpoints: read
[WebSocket endpoints](reference/websocket-endpoints.md).
- OAuth user-context endpoints: read
Expand All @@ -67,7 +70,9 @@ npx xapi-to provider versions <service-id>
```

Use the returned version ID for contract changes. PATCH merges supplied fields;
`--replace` sends a full replacement:
`--replace` sends a full replacement. Preserve each endpoint's `endpointKey`
across working-revision clones and use the working endpoint ID for partial
patches; see the lifecycle reference for exact matching rules:

```bash
npx xapi-to provider version update \
Expand All @@ -76,7 +81,9 @@ npx xapi-to provider diff <service-id> <major>
```

Submit only after inspecting the diff. Publishing can change live behavior and
the CLI deliberately does not retry an ambiguous write:
the CLI deliberately does not retry an ambiguous write. A passing review can
automatically publish and switch the serving revision; submission is not a
dry run:

```bash
npx xapi-to provider publish \
Expand Down Expand Up @@ -119,9 +126,13 @@ npx xapi-to provider skill context <service-id>
```

The scaffold command refuses to overwrite an existing file unless `--force` is
explicit. Link only a Skill owned by the same provider. If `skill context`
explicit. Check its public URL, HTTP method, body and media types against the
serving contract before submitting; generated text can require correction.
Link only a Skill owned by the same provider. If `skill context`
reports contract drift, update and resubmit the Skill before recording a new
fingerprint. Use `provider skill unlink <service-id>` to remove the association.
fingerprint. `drift: false` does not prove schemas, media types or transport
are unchanged; the current fingerprint tracks host, major and endpoint keys.
Use `provider skill unlink <service-id>` to remove the association.

## Observe, recover, and earn

Expand Down
119 changes: 90 additions & 29 deletions skills/xapi-provider/reference/ai-spec-and-models.md
Original file line number Diff line number Diff line change
@@ -1,48 +1,109 @@
# AI services and model pricing
# AI interfaces, models and pricing

Create AI services with the same provider CLI workflow as other services. The
endpoint path helps xAPI identify standard interfaces such as OpenAI chat,
Responses, embeddings, images, Claude messages, and Gemini. Keep standard paths
when the upstream is compatible; non-standard paths usually require explicit
`tokenJsonPaths`.
Choose protocol and billing mode independently. Standard paths identify the
wire protocol; they do not choose prices or turn DYNAMIC into PER_TOKEN.
For outbound transport, read [HTTP transport](http-transport.md).

Before writing the contract, establish:
| Interface | Standard paths | Token usage preset |
|---|---|---|
| OpenAI chat | `/v1/chat/completions` | `usage.prompt_tokens`, `usage.completion_tokens` |
| Claude | `/v1/messages`, `/messages` | `usage.input_tokens`, `usage.output_tokens` |
| Responses | `/v1/responses`, `/responses` | `usage.input_tokens`, `usage.output_tokens` |
| Embeddings | `/v1/embeddings`, `/embeddings` | `usage.prompt_tokens` |
| Decisions | `/v1/decisions`, `/decisions`, stored `{{alpha}}/decisions` | `usage.input_tokens`, `usage.output_tokens` |

1. the upstream public base URL and authentication type;
2. the supported model names;
3. input and output USD price per token for each model;
4. the largest allowed request/output so `estimatedMaxTokens` is realistic;
5. whether streaming responses contain final usage.
The backend computes detection and raw-to-Canonical mappings. Do not invent a
Canonical ID or submit `aiSpecDetectedBy: "path"` as proof of trust. Inspect
the saved association before publication. Shared Gateway routing needs a
compatible, trusted, public serving endpoint and a resolved Canonical mapping.

Use [`templates/ai-model.json`](../templates/ai-model.json) as a starting point:
## Declare models for the billing mode

```bash
npx xapi-to provider create --file ./ai-service.json
npx xapi-to provider versions <service-id>
```
- **PER_TOKEN:** model keys in `tokenPricing` declare models and rates;
`servedModels` does not replace pricing. Obtain approved USD-per-token rates
from the user or authoritative upstream source. A `default` rate is a pricing
fallback, not proof that every unknown model is supported; use an agreed rate.
- **DYNAMIC / PER_CALL / PER_RESOURCE AI:** declare upstream names in
`servedModels`; the backend derives `aiModelMappings`. DYNAMIC charges come
from `dynamicRules`, not `tokenPricing`. Drafts can be incomplete; submission
enforces the applicable served-model requirement.

Scoped `provider create` and `provider version update` accept `servedModels`
on endpoint DTOs. The one-shot `provider import` OpenAPI flow has no served-model
extension; configure the draft afterward with a scoped endpoint payload.
See [billing modes](billing-modes.md).

Use [the AI template](../templates/ai-model.json) for PER_TOKEN chat. Replace
illustrative models and prices before creation. Tiered, cached, reasoning or
multimodal rates must follow the supported pricing contract; do not flatten them
without the user's agreement.

Recognized protocols use built-in token presets. Non-standard paths or usage
need explicit `tokenJsonPaths`. For SSE/NDJSON, verify final usage extraction;
missing usage can settle against the configured estimate. Decisions has no SSE.

## Decisions provider configuration

Decisions uses `model`, `state` and named `questions`, returning typed `answers`.
It is separate from chat, with no chat translation or streaming. Consumer
examples and discovery are in the [Gateway guide](../../xapi/guides/ai_gateway.md#decisions).

For multi-model services, include a `default` price and optional
`servedModels`. Do not invent pricing. If the upstream has tiered, cached,
reasoning, audio, image, or tool-call pricing, represent it in `tokenPricing`
instead of flattening it without the user's agreement.
For OpenRouter, preserve stored `{{alpha}}/decisions` and named alpha base
`https://openrouter.ai/api/alpha`. Public consumers use `/decisions`.
Replacing the stored path loses named-base selection. For cost passthrough,
retain the existing `usage.cost` rule, multiplier, hold estimate and caps;
do not add token prices merely because usage includes token counts.

For SSE, request upstream usage in the terminal event when its protocol
supports that option. For NDJSON, place usage in the last JSON line. If final
usage cannot be extracted, the platform may charge the configured maximum.
For a model-only PATCH, take the endpoint ID from the working copy:

Use a version patch for model or pricing updates:
```json
{
"endpoints": [{
"id": "<working-endpoint-id>",
"servedModels": ["typesafe/jev-1.14"]
}]
}
```

```bash
npx xapi-to provider revision start <service-id> <major>
npx xapi-to provider version update \
<service-id> <working-version-id> --file ./models.patch.json
<service-id> <working-revision-id> --file ./models.patch.json
npx xapi-to provider diff <service-id> <major>
npx xapi-to provider publish \
<service-id> <revision-id> --changelog "Update model catalog and pricing"
```

After a canary call, use its request ID to wait for finalized accounting:
This replaces that endpoint's declared model list. Preserve all models that
should remain supported. PATCH leaves omitted fields intact; do not use
`--replace` for this partial file. Submission is separate and may automatically
publish after review; follow [lifecycle management](lifecycle-management.md).
Check that the existing Decisions configuration has `supportsStreaming: false`;
this model-only patch does not change it or other billing/transport fields.

## Variants, reference prices and receipts

Canonical identity, upstream name and wire protocol are separate: `jev-1-14`
can map to `typesafe/jev-1.14` on a Decisions endpoint. A moving `latest`
identity must not be silently pinned to a fixed version.

Preserve complete variants. OpenRouter `:free` needs a free mapping and must
never fall back to the paid base. `:batch` is unsupported by the synchronous
Decisions Gateway. Discover current models rather than copying a fixed count.

Eligible serving OpenRouter Decisions endpoints may expose response-only
`modelQuotes`: USD-per-million-token estimates derived from Canonical reference
prices and this endpoint's simple cost-passthrough multiplier. These are not
`tokenPricing` and do not settle DYNAMIC calls. Other variants cannot borrow
base rates. Explicit free quotes are zero only when the rule can be safely
quoted; complex rules, including caps/clamps, remain unknown. Missing/null
means unknown; `0` means an explicit zero rate. Keep billing limits intact.

After an authorized canary, verify actual cost, model/Canonical attribution and
final accounting. Where the response exposes an xAPI receipt request ID:

```bash
npx xapi-to usage wait <request-id> --timeout 1m
```

The AI Gateway currently does not expose a reliable receipt request ID. Its
`X-XAPI-Cost` aggregates attempts; the final provider's `usage.cost` or response
`id` is not an aggregate xAPI receipt. Do not claim reconciliation from them.
65 changes: 63 additions & 2 deletions skills/xapi-provider/reference/billing-modes.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ valid; read [free access](free-api-access.md) before publishing a free endpoint.
}
```

Always obtain pricing from the user or an authoritative upstream source. Keep a
`default` entry for unknown model names. Standard AI paths are detected by the
Always obtain pricing from the user or an authoritative upstream source. Use an
agreed `default` rate if a pricing fallback is needed; it does not declare every
unknown model routable. Standard AI paths are detected by the
platform; use `tokenJsonPaths` only for a non-standard request or response:

```json
Expand Down Expand Up @@ -79,6 +80,66 @@ The count path may resolve to an array, whose length is charged, or a numeric
count. For streamed NDJSON/SSE, the final structured chunk must expose the
count. The estimated maximum is the pre-charge cap.

## Response-derived rules: `DYNAMIC`

Use DYNAMIC for a response-cost rule or other supported field-based pricing.
AI protocols and token counts do not force PER_TOKEN. A DYNAMIC AI endpoint
declares `servedModels`; its rates and hold estimate live in `dynamicRules`.
The backend derives the protocol/model association from the saved endpoint.

This illustrative Decisions endpoint settles upstream `usage.cost × 1.2`:

```json
{
"name": "Decisions",
"method": "POST",
"path": "/v1/decisions",
"billingType": "DYNAMIC",
"servedModels": ["typesafe/jev-1.14"],
"supportsStreaming": false,
"dynamicRules": {
"version": 2,
"fields": [{
"name": "cost", "source": "response.body", "path": "usage.cost",
"type": "number", "estimatedMax": 0.1, "clamp": {"min": 0}
}],
"rules": [{
"then": {"operands": [{"factors": [
{"kind": "number", "field": "cost"},
{"kind": "const", "value": 1.2}
]}]}
}],
"minCost": 0,
"maxCost": 10
}
}
```

Choose the actual multiplier, estimate and caps with the user; these numbers
are not a universal fee schedule. The estimate can hold funds before the real
response arrives (here 0.1 × 1.2 = USD 0.12). Final consumption follows the
configured rules and actual billing evidence. Missing/invalid evidence is not
proof of zero cost; inspect finalized accounting instead of calculating a refund
from missing usage. Preserve existing rules on a model-only update.

For OpenRouter's existing endpoint, preserve `{{alpha}}/decisions` and its
named alpha base rather than copying this example's path. Decisions is a
single non-streaming request; do not enable SSE or `:batch` to test this rule.

Reference quotes are display-only. A simple recognized passthrough can derive
USD/1M-token estimates; rules with caps/clamps, including this example, remain
unknown. Unknown/null is distinct from zero. OpenRouter `:free` must retain the
exact free mapping; never borrow the paid base's price or use it as fallback.
See [AI models](ai-spec-and-models.md) for variants and quote boundaries.

## Accounting after execution

Estimates/reservations are not actual consumption. Check the actual USD cost,
finalized receipt where an xAPI request ID is available, and released holds.
An upstream response ID is not an xAPI receipt ID. Gateway costs aggregate
attempts, so final upstream `usage.cost` or a single attempt receipt cannot
reconcile the whole request. See the [Gateway billing headers](../../xapi/guides/ai_gateway.md#routing-and-billing-headers).

After changing prices, inspect the diff before publication:

```bash
Expand Down
Loading
Loading