openvibers/openvibe.contracts Commits

openvibe-contracts 0.421.0: every AI call measured everywhere (#475)

OP OpenVibers committed 13 h ago · 73469da601 · 64 files, +3638 −142

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 5baae58b..36ad2996 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -1,5 +1,31 @@
# Changelog
++## 0.421.0 — 2026-11-12
++
++**Every AI call, measured everywhere** (ai-metrics.md).
++
++- **`ai.call.recorded@1` 1.1.0** adds, all optional:
++ - `operation` (chat, completion, embedding, image, image edit, speech, transcription, translation, rerank,
++ moderation, decision, realtime, batch, tool);
++ - `streamed`, and `tps` (the decode speed after the first token);
++ - `queue_ms`;
++ - `task`, `task_id` and `task_ms`, so cost and time add up per task;
++ - `harness`, and `place` (provider, the person's machine, community, a plan, OpenVibe's hosts);
++ - `error_class`, `fallbacks` and `cache_hit` (an exact-cache answer);
++ - `tool_calls` and `region`.
++- **`ai.call-report@1`:** up to 100 calls a product made without OpenVibe.AI (Actor, Codes harness turns, Link's local
++ models through Bot, Vibe). They go to AI's `POST /api/v1/usage/calls`. AI prices any that bring no price, records
++ each once (by system + run or turn id) and publishes `ai.call.recorded@1`. The capability is `ai.usage.report`.
++- **`report.ai-performance@1`:** how calls performed for one group (model, provider, operation, system, task, place,
++ harness):
++ - success, fallback, streamed and cache-hit rates;
++ - TTFT, latency and tokens per second at p50, p90 and p95;
++ - tokens, charged and list cost, and cache savings;
++ - cost and time per task;
++ - the top errors.
++
++ Public answers group by model, provider or operation, with at least 20 calls.
++
## 0.420.0 — 2026-11-12
**One agent system, measured.** These contracts carry the decisions in `one-system-v2.md`, which come from reading 24
ddiff --git a/contracts/ai/call-report.v1.json b/contracts/ai/call-report.v1.json
nnew file mode 100644
iindex 00000000..c6b2e82e
---- /dev/null
++++ b/contracts/ai/call-report.v1.json
@@@ -0,0 +1,359 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/call-report.v1.json",
++ "title": "AiCallReport",
++ "description": "ai.call-report@1 (ai-metrics.md): up to 100 AI calls a product made without OpenVibe.AI (Actor’s own model calls, a Codes harness turn, a local model on a person’s machine through Bot, Vibe), sent to AI’s POST /api/v1/usage/calls with the capability ai.usage.report. Each call is ai.call.recorded@1’s shape; the price is optional (AI prices from its rate cards: the charged amount first, then the list price) and SYSTEM + RUN_ID or TURN_ID is the idempotency key (a call sent twice is recorded once). AI publishes each as ai.call.recorded@1.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "calls"
++ ],
++ "properties": {
++ "calls": {
++ "type": "array",
++ "minItems": 1,
++ "maxItems": 100,
++ "items": {
++ "type": "object",
++ "required": [
++ "system",
++ "provider",
++ "model",
++ "outcome",
++ "at"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "subject": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 128,
++ "description": "The person the call was made for, or null when the call was anonymous."
++ },
++ "attribution": {
++ "type": "string",
++ "maxLength": 200,
++ "description": "The site or app the call came from, as '<type>:<id>' (for example 'site:design'), empty when nothing is known."
++ },
++ "surface": {
++ "type": "string",
++ "maxLength": 200,
++ "description": "The product surface: 'design.generate', 'codes.turn', 'ai.generate'. Free text, so a new surface needs no new contract."
++ },
++ "system": {
++ "type": "string",
++ "maxLength": 100,
++ "description": "What ran: 'ai', 'harness:claude-code', 'harness:codex', 'actor', 'node'."
++ },
++ "provider": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 128
++ },
++ "model": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 200
++ },
++ "effort": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 64,
++ "description": "The reasoning effort the call asked for, when it asked for one."
++ },
++ "paid_by": {
++ "type": "string",
++ "enum": [
++ "own_key",
++ "openvibe_key",
++ "plan",
++ "own_machine",
++ "free_tier",
++ "network_allowance",
++ "org_key"
++ ]
++ },
++ "paid_by_detail": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 200,
++ "description": "Which key or plan paid, for example 'anthropic ...a1b2' or 'claude-max'. Never a whole secret."
++ },
++ "input": {
++ "type": "integer",
++ "minimum": 0,
++ "description": "The uncached input tokens. Cache reads and cache writes are counted on their own fields."
++ },
++ "output": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "reasoning": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_read": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_write": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_write_ttl": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "enum": [
++ "5m",
++ "1h",
++ null
++ ],
++ "description": "How long the written cache entry lives, when the provider says."
++ },
++ "images": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "audio_seconds": {
++ "type": "number",
++ "minimum": 0
++ },
++ "video_seconds": {
++ "type": "number",
++ "minimum": 0
++ },
++ "list_cost_usd": {
++ "type": "number",
++ "minimum": 0,
++ "description": "What the tokens cost at the human-typed list prices. Optional here: AI prices a call that brings no price from its own rate cards."
++ },
++ "charged_usd": {
++ "type": "number",
++ "minimum": 0,
++ "description": "What the payer was actually charged. A person's own key is never charged here: 0. Optional here: AI prices a call that brings no price from its own rate cards."
++ },
++ "fee_usd": {
++ "type": "number",
++ "minimum": 0,
++ "description": "The network's fee on top of the cost, when the OpenVibe key paid. Optional here: AI prices a call that brings no price from its own rate cards."
++ },
++ "cache_saved_usd": {
++ "type": "number",
++ "minimum": 0,
++ "description": "What the cached tokens saved against the plain input price. Optional here: AI prices a call that brings no price from its own rate cards."
++ },
++ "price_source": {
++ "type": "string",
++ "maxLength": 64,
++ "description": "Where the prices came from: 'models' (a person's typed model row), 'pricing' (AI_PRICING_JSON), 'flat' (the flat rates), 'no cache price' (no cache price is typed, so the cache tokens are priced at the input price). Optional here: AI prices a call that brings no price from its own rate cards."
++ },
++ "price_date": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$",
++ "description": "The day the price was checked, when it was. Optional here: AI prices a call that brings no price from its own rate cards."
++ },
++ "latency_ms": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "ttft_ms": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "Time to the first token, when the call streamed and it was measured."
++ },
++ "attempts": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "outcome": {
++ "type": "string",
++ "enum": [
++ "succeeded",
++ "failed",
++ "cancelled",
++ "cached"
++ ]
++ },
++ "run_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 100
++ },
++ "turn_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 100
++ },
++ "at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "operation": {
++ "enum": [
++ "chat",
++ "completion",
++ "embedding",
++ "image",
++ "image_edit",
++ "speech",
++ "transcription",
++ "translation",
++ "rerank",
++ "moderation",
++ "decision",
++ "realtime",
++ "batch",
++ "tool",
++ "other",
++ null
++ ],
++ "description": "Since 1.1.0: what kind of call it was."
++ },
++ "streamed": {
++ "type": [
++ "boolean",
++ "null"
++ ],
++ "description": "Since 1.1.0: whether the answer came as a stream (TTFT is only measured on one)."
++ },
++ "tps": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "Since 1.1.0: output tokens per second after the first token (decode speed); null when not streamed or too short to measure."
++ },
++ "queue_ms": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "Since 1.1.0: time waiting before the call was sent (a queue, a rate gate)."
++ },
++ "task": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 120,
++ "description": "Since 1.1.0: the task kind the call served (a task name, a workflow key, chat.reply, code.turn)."
++ },
++ "task_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 120,
++ "description": "Since 1.1.0: the task or run this call is one step of, so cost and time add up per task."
++ },
++ "task_ms": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "Since 1.1.0: on the last call of a task, the whole task’s wall time."
++ },
++ "harness": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 60,
++ "description": "Since 1.1.0: the harness that made the call (openvibe, dsh, codex, claude-code, gemini-cli, opencode …)."
++ },
++ "place": {
++ "enum": [
++ "provider",
++ "machine",
++ "community",
++ "plan",
++ "host",
++ null
++ ],
++ "description": "Since 1.1.0: where the model ran: a provider’s API, the person’s own machine, a community machine, a signed-in plan, OpenVibe’s own hosts."
++ },
++ "error_class": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 60,
++ "description": "Since 1.1.0: on a failure, its class (rate_limited, timeout, context_length, refused, auth, server, network, invalid)."
++ },
++ "fallbacks": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "maximum": 50,
++ "description": "Since 1.1.0: how many offers failed before the one that answered."
++ },
++ "cache_hit": {
++ "type": [
++ "boolean",
++ "null"
++ ],
++ "description": "Since 1.1.0: true when the whole answer came from OpenVibe’s exact answer cache (no model ran)."
++ },
++ "tool_calls": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "Since 1.1.0: tool calls the model asked for in this call."
++ },
++ "region": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 40,
++ "description": "Since 1.1.0: the provider region or data centre, when known."
++ }
++ },
++ "anyOf": [
++ {
++ "required": [
++ "run_id"
++ ]
++ },
++ {
++ "required": [
++ "turn_id"
++ ]
++ }
++ ]
++ }
++ }
++ }
++}
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex 35a075af..18cc4597 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -1643,7 +1643,7 @@
},
{
"id": "ai.call.recorded",
-- "version": "1.0.0",
++ "version": "1.1.0",
"owner": "ai",
"schema": "events/payloads/ai.call.recorded.v1.json",
"visibility": "first-party",
@@@ -1651,6 +1651,24 @@
"status": "active",
"adr": "ADR-033"
},
++ {
++ "id": "ai.call-report",
++ "version": "1.0.0",
++ "owner": "ai",
++ "schema": "ai/call-report.v1.json",
++ "visibility": "internal",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "report.ai-performance",
++ "version": "1.0.0",
++ "owner": "report",
++ "schema": "report/ai-performance.v1.json",
++ "visibility": "internal",
++ "compatibility": "backward",
++ "status": "active"
++ },
{
"id": "ai.run.queued",
"version": "1.0.0",
ddiff --git a/contracts/events/payloads/ai.call.recorded.v1.json b/contracts/events/payloads/ai.call.recorded.v1.json
iindex 79def7a4..2ba0a412 100644
---- a/contracts/events/payloads/ai.call.recorded.v1.json
++++ b/contracts/events/payloads/ai.call.recorded.v1.json
@@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/events/payloads/ai.call.recorded.v1.json",
"title": "AiCallRecordedPayload",
-- "description": "ai.call.recorded v1 (plan L40, one AI meter across the network). One AI call, wherever it happened: OpenVibe.AI's own runs, a harness turn in Codes, an Actor task, Vibe, a local model on a person's own machine. who and where: subject (the person, or null when the call is anonymous), attribution (the site or app the call came from, free text such as 'site:design'), surface (the product surface, free text such as 'design.generate' or 'codes.turn'). what ran: system (ai, harness:claude-code, harness:codex, actor, node), provider, model, effort. who paid: paid_by (own_key, openvibe_key, plan, own_machine, free_tier, network_allowance, org_key) and paid_by_detail (for example 'anthropic ...a1b2' for a person's own key, or 'claude-max' for a plan). tokens: input (the UNCACHED input tokens), output, reasoning, cache_read, cache_write (with cache_write_ttl 5m or 1h), images, audio_seconds, video_seconds. money, in USD: list_cost_usd (what the tokens cost at the human-typed list prices), charged_usd (what the payer was actually charged), fee_usd (the network's fee on top), cache_saved_usd (what the cache saved against the plain input price). price_source says where the prices came from ('models', 'pricing', 'flat', or 'no cache price' when no cache price is typed and the cache tokens are priced at the input price), price_date the day the price was checked. how it went: latency_ms, ttft_ms, attempts, outcome. ids: run_id or turn_id, and at. The record carries NO prompt and NO answer text, ever. A ledger (OpenVibe.AI) rolls the records up; a record consumed twice is stored once (idempotent by system and run_id or turn_id). Envelope: subject { type: run, id: <run_id> } or { type: user, id: <subject> }, visibility internal, priority low, actor service:ai.",
++ "description": "ai.call.recorded v1 (plan L40, one AI meter across the network). One AI call, wherever it happened: OpenVibe.AI's own runs, a harness turn in Codes, an Actor task, Vibe, a local model on a person's own machine. who and where: subject (the person, or null when the call is anonymous), attribution (the site or app the call came from, free text such as 'site:design'), surface (the product surface, free text such as 'design.generate' or 'codes.turn'). what ran: system (ai, harness:claude-code, harness:codex, actor, node), provider, model, effort. who paid: paid_by (own_key, openvibe_key, plan, own_machine, free_tier, network_allowance, org_key) and paid_by_detail (for example 'anthropic ...a1b2' for a person's own key, or 'claude-max' for a plan). tokens: input (the UNCACHED input tokens), output, reasoning, cache_read, cache_write (with cache_write_ttl 5m or 1h), images, audio_seconds, video_seconds. money, in USD: list_cost_usd (what the tokens cost at the human-typed list prices), charged_usd (what the payer was actually charged), fee_usd (the network's fee on top), cache_saved_usd (what the cache saved against the plain input price). price_source says where the prices came from ('models', 'pricing', 'flat', or 'no cache price' when no cache price is typed and the cache tokens are priced at the input price), price_date the day the price was checked. how it went: latency_ms, ttft_ms, attempts, outcome. ids: run_id or turn_id, and at. The record carries NO prompt and NO answer text, ever. A ledger (OpenVibe.AI) rolls the records up; a record consumed twice is stored once (idempotent by system and run_id or turn_id). Envelope: subject { type: run, id: <run_id> } or { type: user, id: <subject> }, visibility internal, priority low, actor service:ai. Since 1.1.0 (ai-metrics.md): OPERATION, STREAMED, TPS, QUEUE_MS, TASK, TASK_ID, TASK_MS, HARNESS, PLACE, ERROR_CLASS, FALLBACKS, CACHE_HIT, TOOL_CALLS and REGION, all optional. Products other than AI send their calls to POST /api/v1/usage/calls (ai.call-report@1); AI prices and publishes them.",
"type": "object",
"required": [
"system",
@@@ -32,7 +32,10 @@
"additionalProperties": false,
"properties": {
"subject": {
-- "type": ["string", "null"],
++ "type": [
++ "string",
++ "null"
++ ],
"maxLength": 128,
"description": "The person the call was made for, or null when the call was anonymous."
},
@@@ -51,50 +54,298 @@
"maxLength": 100,
"description": "What ran: 'ai', 'harness:claude-code', 'harness:codex', 'actor', 'node'."
},
-- "provider": { "type": ["string", "null"], "maxLength": 128 },
-- "model": { "type": ["string", "null"], "maxLength": 200 },
-- "effort": { "type": ["string", "null"], "maxLength": 64, "description": "The reasoning effort the call asked for, when it asked for one." },
++ "provider": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 128
++ },
++ "model": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 200
++ },
++ "effort": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 64,
++ "description": "The reasoning effort the call asked for, when it asked for one."
++ },
"paid_by": {
"type": "string",
-- "enum": ["own_key", "openvibe_key", "plan", "own_machine", "free_tier", "network_allowance", "org_key"]
++ "enum": [
++ "own_key",
++ "openvibe_key",
++ "plan",
++ "own_machine",
++ "free_tier",
++ "network_allowance",
++ "org_key"
++ ]
},
"paid_by_detail": {
-- "type": ["string", "null"],
++ "type": [
++ "string",
++ "null"
++ ],
"maxLength": 200,
"description": "Which key or plan paid, for example 'anthropic ...a1b2' or 'claude-max'. Never a whole secret."
},
-- "input": { "type": "integer", "minimum": 0, "description": "The uncached input tokens. Cache reads and cache writes are counted on their own fields." },
-- "output": { "type": "integer", "minimum": 0 },
-- "reasoning": { "type": "integer", "minimum": 0 },
-- "cache_read": { "type": "integer", "minimum": 0 },
-- "cache_write": { "type": "integer", "minimum": 0 },
++ "input": {
++ "type": "integer",
++ "minimum": 0,
++ "description": "The uncached input tokens. Cache reads and cache writes are counted on their own fields."
++ },
++ "output": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "reasoning": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_read": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_write": {
++ "type": "integer",
++ "minimum": 0
++ },
"cache_write_ttl": {
-- "type": ["string", "null"],
-- "enum": ["5m", "1h", null],
++ "type": [
++ "string",
++ "null"
++ ],
++ "enum": [
++ "5m",
++ "1h",
++ null
++ ],
"description": "How long the written cache entry lives, when the provider says."
},
-- "images": { "type": "integer", "minimum": 0 },
-- "audio_seconds": { "type": "number", "minimum": 0 },
-- "video_seconds": { "type": "number", "minimum": 0 },
-- "list_cost_usd": { "type": "number", "minimum": 0, "description": "What the tokens cost at the human-typed list prices." },
-- "charged_usd": { "type": "number", "minimum": 0, "description": "What the payer was actually charged. A person's own key is never charged here: 0." },
-- "fee_usd": { "type": "number", "minimum": 0, "description": "The network's fee on top of the cost, when the OpenVibe key paid." },
-- "cache_saved_usd": { "type": "number", "minimum": 0, "description": "What the cached tokens saved against the plain input price." },
++ "images": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "audio_seconds": {
++ "type": "number",
++ "minimum": 0
++ },
++ "video_seconds": {
++ "type": "number",
++ "minimum": 0
++ },
++ "list_cost_usd": {
++ "type": "number",
++ "minimum": 0,
++ "description": "What the tokens cost at the human-typed list prices."
++ },
++ "charged_usd": {
++ "type": "number",
++ "minimum": 0,
++ "description": "What the payer was actually charged. A person's own key is never charged here: 0."
++ },
++ "fee_usd": {
++ "type": "number",
++ "minimum": 0,
++ "description": "The network's fee on top of the cost, when the OpenVibe key paid."
++ },
++ "cache_saved_usd": {
++ "type": "number",
++ "minimum": 0,
++ "description": "What the cached tokens saved against the plain input price."
++ },
"price_source": {
"type": "string",
"maxLength": 64,
"description": "Where the prices came from: 'models' (a person's typed model row), 'pricing' (AI_PRICING_JSON), 'flat' (the flat rates), 'no cache price' (no cache price is typed, so the cache tokens are priced at the input price)."
},
-- "price_date": { "type": ["string", "null"], "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$", "description": "The day the price was checked, when it was." },
-- "latency_ms": { "type": ["integer", "null"], "minimum": 0 },
-- "ttft_ms": { "type": ["integer", "null"], "minimum": 0, "description": "Time to the first token, when the call streamed and it was measured." },
-- "attempts": { "type": "integer", "minimum": 0 },
++ "price_date": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$",
++ "description": "The day the price was checked, when it was."
++ },
++ "latency_ms": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "ttft_ms": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "Time to the first token, when the call streamed and it was measured."
++ },
++ "attempts": {
++ "type": "integer",
++ "minimum": 0
++ },
"outcome": {
"type": "string",
-- "enum": ["succeeded", "failed", "cancelled", "cached"]
++ "enum": [
++ "succeeded",
++ "failed",
++ "cancelled",
++ "cached"
++ ]
++ },
++ "run_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 100
++ },
++ "turn_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 100
++ },
++ "at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "operation": {
++ "enum": [
++ "chat",
++ "completion",
++ "embedding",
++ "image",
++ "image_edit",
++ "speech",
++ "transcription",
++ "translation",
++ "rerank",
++ "moderation",
++ "decision",
++ "realtime",
++ "batch",
++ "tool",
++ "other",
++ null
++ ],
++ "description": "Since 1.1.0: what kind of call it was."
++ },
++ "streamed": {
++ "type": [
++ "boolean",
++ "null"
++ ],
++ "description": "Since 1.1.0: whether the answer came as a stream (TTFT is only measured on one)."
++ },
++ "tps": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "Since 1.1.0: output tokens per second after the first token (decode speed); null when not streamed or too short to measure."
++ },
++ "queue_ms": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "Since 1.1.0: time waiting before the call was sent (a queue, a rate gate)."
++ },
++ "task": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 120,
++ "description": "Since 1.1.0: the task kind the call served (a task name, a workflow key, chat.reply, code.turn)."
++ },
++ "task_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 120,
++ "description": "Since 1.1.0: the task or run this call is one step of, so cost and time add up per task."
++ },
++ "task_ms": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "Since 1.1.0: on the last call of a task, the whole task’s wall time."
++ },
++ "harness": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 60,
++ "description": "Since 1.1.0: the harness that made the call (openvibe, dsh, codex, claude-code, gemini-cli, opencode …)."
++ },
++ "place": {
++ "enum": [
++ "provider",
++ "machine",
++ "community",
++ "plan",
++ "host",
++ null
++ ],
++ "description": "Since 1.1.0: where the model ran: a provider’s API, the person’s own machine, a community machine, a signed-in plan, OpenVibe’s own hosts."
++ },
++ "error_class": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 60,
++ "description": "Since 1.1.0: on a failure, its class (rate_limited, timeout, context_length, refused, auth, server, network, invalid)."
++ },
++ "fallbacks": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "maximum": 50,
++ "description": "Since 1.1.0: how many offers failed before the one that answered."
++ },
++ "cache_hit": {
++ "type": [
++ "boolean",
++ "null"
++ ],
++ "description": "Since 1.1.0: true when the whole answer came from OpenVibe’s exact answer cache (no model ran)."
++ },
++ "tool_calls": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "Since 1.1.0: tool calls the model asked for in this call."
},
-- "run_id": { "type": ["string", "null"], "maxLength": 100 },
-- "turn_id": { "type": ["string", "null"], "maxLength": 100 },
-- "at": { "type": "string", "format": "date-time" }
++ "region": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 40,
++ "description": "Since 1.1.0: the provider region or data centre, when known."
++ }
}
}
ddiff --git a/contracts/report/ai-performance.v1.json b/contracts/report/ai-performance.v1.json
nnew file mode 100644
iindex 00000000..e0236024
---- /dev/null
++++ b/contracts/report/ai-performance.v1.json
@@@ -0,0 +1,352 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/report/ai-performance.v1.json",
++ "title": "ReportAiPerformance",
++ "description": "report.ai-performance@1 (ai-metrics.md): how AI calls performed over a range, for one group (a model, a provider, an operation, a system, a task). Percentiles come from log-scale histograms (within one bucket’s width). Public answers group by model, provider or operation only and show a group with at least 20 calls; system and task groups are for staff. Money is USD; tokens per second is the decode speed after the first token; cost per task sums the calls that share a task id.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "group",
++ "key",
++ "calls"
++ ],
++ "properties": {
++ "group": {
++ "enum": [
++ "model",
++ "provider",
++ "operation",
++ "system",
++ "task",
++ "place",
++ "harness"
++ ]
++ },
++ "key": {
++ "type": "string",
++ "maxLength": 200
++ },
++ "provider": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 120
++ },
++ "range": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "from": {
++ "type": "string",
++ "format": "date"
++ },
++ "to": {
++ "type": "string",
++ "format": "date"
++ }
++ }
++ },
++ "calls": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "succeeded": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "errors": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "success_rate": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0,
++ "maximum": 1
++ },
++ "fallback_rate": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0,
++ "maximum": 1
++ },
++ "streamed_share": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0,
++ "maximum": 1
++ },
++ "cache_hit_rate": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0,
++ "maximum": 1
++ },
++ "cache_read_share": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0,
++ "maximum": 1,
++ "description": "Cache-read tokens as a share of all input tokens."
++ },
++ "ttft_ms": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "p50": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "p90": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "p95": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "n": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ "latency_ms": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "p50": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "p90": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "p95": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "n": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ "tps": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "p50": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "p90": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "p95": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "n": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ "tokens": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "input": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "output": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "reasoning": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_read": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_write": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ "cost": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "charged_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "list_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "cache_saved_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "per_call_usd": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "per_million_output_usd": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ }
++ }
++ },
++ "tasks": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "count": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cost_per_task_usd": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "p50": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "p90": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "p95": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "n": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ "time_per_task_ms": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "p50": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "p90": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "p95": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "n": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ }
++ }
++ },
++ "top_errors": {
++ "type": "array",
++ "maxItems": 5,
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "error_class",
++ "count"
++ ],
++ "properties": {
++ "error_class": {
++ "type": "string",
++ "maxLength": 60
++ },
++ "count": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ }
++ }
++ }
++}
ddiff --git a/fixtures/ai.call-report/invalid/an-operation-nobody-has.json b/fixtures/ai.call-report/invalid/an-operation-nobody-has.json
nnew file mode 100644
iindex 00000000..3a082d47
---- /dev/null
++++ b/fixtures/ai.call-report/invalid/an-operation-nobody-has.json
@@@ -0,0 +1,13 @@
++{
++ "calls": [
++ {
++ "system": "actor",
++ "run_id": "r1",
++ "provider": "deepseek",
++ "model": "x",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z",
++ "operation": "telepathy"
++ }
++ ]
++}
ddiff --git a/fixtures/ai.call-report/invalid/no-run-or-turn.json b/fixtures/ai.call-report/invalid/no-run-or-turn.json
nnew file mode 100644
iindex 00000000..f89b2e9c
---- /dev/null
++++ b/fixtures/ai.call-report/invalid/no-run-or-turn.json
@@@ -0,0 +1,11 @@
++{
++ "calls": [
++ {
++ "system": "actor",
++ "provider": "deepseek",
++ "model": "x",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ }
++ ]
++}
ddiff --git a/fixtures/ai.call-report/invalid/too-many-calls.json b/fixtures/ai.call-report/invalid/too-many-calls.json
nnew file mode 100644
iindex 00000000..34bbcdf0
---- /dev/null
++++ b/fixtures/ai.call-report/invalid/too-many-calls.json
@@@ -0,0 +1,812 @@
++{
++ "calls": [
++ {
++ "system": "actor",
++ "run_id": "r0",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r1",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r2",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r3",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r4",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r5",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r6",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r7",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r8",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r9",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r10",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r11",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r12",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r13",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r14",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r15",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r16",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r17",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r18",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r19",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r20",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r21",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r22",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r23",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r24",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r25",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r26",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r27",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r28",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r29",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r30",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r31",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r32",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r33",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r34",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r35",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r36",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r37",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r38",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r39",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r40",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r41",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r42",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r43",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r44",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r45",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r46",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r47",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r48",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r49",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r50",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r51",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r52",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r53",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r54",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r55",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r56",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r57",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r58",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r59",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r60",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r61",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r62",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r63",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r64",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r65",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r66",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r67",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r68",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r69",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r70",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r71",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r72",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r73",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r74",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r75",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r76",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r77",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r78",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r79",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r80",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r81",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r82",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r83",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r84",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r85",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r86",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r87",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r88",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r89",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r90",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r91",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r92",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r93",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r94",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r95",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r96",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r97",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r98",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r99",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ },
++ {
++ "system": "actor",
++ "run_id": "r100",
++ "provider": "p",
++ "model": "m",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z"
++ }
++ ]
++}
ddiff --git a/fixtures/ai.call-report/valid/an-actor-turn-and-a-local-model.json b/fixtures/ai.call-report/valid/an-actor-turn-and-a-local-model.json
nnew file mode 100644
iindex 00000000..58f8d7df
---- /dev/null
++++ b/fixtures/ai.call-report/valid/an-actor-turn-and-a-local-model.json
@@@ -0,0 +1,49 @@
++{
++ "calls": [
++ {
++ "system": "actor",
++ "turn_id": "tsk_01JD8C4QJ7Z3K9W2X5V6N1M0PA:3",
++ "provider": "deepseek",
++ "model": "deepseek-v4-flash",
++ "surface": "actor.task",
++ "paid_by": "openvibe_key",
++ "input": 12000,
++ "output": 800,
++ "reasoning": 0,
++ "cache_read": 9000,
++ "cache_write": 0,
++ "cache_write_ttl": null,
++ "images": 0,
++ "audio_seconds": 0,
++ "video_seconds": 0,
++ "latency_ms": 4100,
++ "ttft_ms": 610,
++ "attempts": 1,
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z",
++ "operation": "chat",
++ "streamed": true,
++ "tps": 88.4,
++ "task": "actor.task",
++ "task_id": "tsk_01JD8C4QJ7Z3K9W2X5V6N1M0PA",
++ "harness": "openvibe",
++ "place": "provider",
++ "tool_calls": 2
++ },
++ {
++ "system": "node",
++ "run_id": "cmd_01JD8C4QJ7Z3K9W2X5V6N1M0PB",
++ "provider": "ollama",
++ "model": "qwen2.5:3b",
++ "outcome": "succeeded",
++ "at": "2026-11-12T23:30:00Z",
++ "operation": "chat",
++ "streamed": true,
++ "ttft_ms": 900,
++ "tps": 12.5,
++ "place": "machine",
++ "input": 300,
++ "output": 120
++ }
++ ]
++}
ddiff --git a/fixtures/report.ai-performance/invalid/a-group-nobody-has.json b/fixtures/report.ai-performance/invalid/a-group-nobody-has.json
nnew file mode 100644
iindex 00000000..fa3c38eb
---- /dev/null
++++ b/fixtures/report.ai-performance/invalid/a-group-nobody-has.json
@@@ -0,0 +1,5 @@
++{
++ "group": "person",
++ "key": "usr_x",
++ "calls": 3
++}
ddiff --git a/fixtures/report.ai-performance/valid/a-model-over-a-week.json b/fixtures/report.ai-performance/valid/a-model-over-a-week.json
nnew file mode 100644
iindex 00000000..295a73e8
---- /dev/null
++++ b/fixtures/report.ai-performance/valid/a-model-over-a-week.json
@@@ -0,0 +1,74 @@
++{
++ "group": "model",
++ "key": "deepseek-v4-flash",
++ "provider": "deepseek",
++ "range": {
++ "from": "2026-11-05",
++ "to": "2026-11-12"
++ },
++ "calls": 18420,
++ "succeeded": 18311,
++ "errors": 109,
++ "success_rate": 0.9941,
++ "fallback_rate": 0.012,
++ "streamed_share": 0.71,
++ "cache_hit_rate": 0.04,
++ "cache_read_share": 0.63,
++ "ttft_ms": {
++ "p50": 540,
++ "p90": 980,
++ "p95": 1310,
++ "n": 13080
++ },
++ "latency_ms": {
++ "p50": 3900,
++ "p90": 9100,
++ "p95": 12800,
++ "n": 18420
++ },
++ "tps": {
++ "p50": 86,
++ "p90": 121,
++ "p95": 133,
++ "n": 12990
++ },
++ "tokens": {
++ "input": 210000000,
++ "output": 9100000,
++ "reasoning": 0,
++ "cache_read": 132000000,
++ "cache_write": 0
++ },
++ "cost": {
++ "charged_usd": 61.2,
++ "list_usd": 74.9,
++ "cache_saved_usd": 19.8,
++ "per_call_usd": 0.0033,
++ "per_million_output_usd": 6.72
++ },
++ "tasks": {
++ "count": 4120,
++ "cost_per_task_usd": {
++ "p50": 0.006,
++ "p90": 0.031,
++ "p95": 0.05,
++ "n": 4120
++ },
++ "time_per_task_ms": {
++ "p50": 14000,
++ "p90": 61000,
++ "p95": 98000,
++ "n": 4120
++ }
++ },
++ "top_errors": [
++ {
++ "error_class": "rate_limited",
++ "count": 71
++ },
++ {
++ "error_class": "timeout",
++ "count": 30
++ }
++ ]
++}
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex ba6eebb8..63ceb3a8 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -17336,14 +17336,14 @@
}
},
"ai.call.recorded": {
-- "version": "1.0.0",
++ "version": "1.1.0",
"owner": "ai",
"status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/events/payloads/ai.call.recorded.v1.json",
"title": "AiCallRecordedPayload",
-- "description": "ai.call.recorded v1 (plan L40, one AI meter across the network). One AI call, wherever it happened: OpenVibe.AI's own runs, a harness turn in Codes, an Actor task, Vibe, a local model on a person's own machine. who and where: subject (the person, or null when the call is anonymous), attribution (the site or app the call came from, free text such as 'site:design'), surface (the product surface, free text such as 'design.generate' or 'codes.turn'). what ran: system (ai, harness:claude-code, harness:codex, actor, node), provider, model, effort. who paid: paid_by (own_key, openvibe_key, plan, own_machine, free_tier, network_allowance, org_key) and paid_by_detail (for example 'anthropic ...a1b2' for a person's own key, or 'claude-max' for a plan). tokens: input (the UNCACHED input tokens), output, reasoning, cache_read, cache_write (with cache_write_ttl 5m or 1h), images, audio_seconds, video_seconds. money, in USD: list_cost_usd (what the tokens cost at the human-typed list prices), charged_usd (what the payer was actually charged), fee_usd (the network's fee on top), cache_saved_usd (what the cache saved against the plain input price). price_source says where the prices came from ('models', 'pricing', 'flat', or 'no cache price' when no cache price is typed and the cache tokens are priced at the input price), price_date the day the price was checked. how it went: latency_ms, ttft_ms, attempts, outcome. ids: run_id or turn_id, and at. The record carries NO prompt and NO answer text, ever. A ledger (OpenVibe.AI) rolls the records up; a record consumed twice is stored once (idempotent by system and run_id or turn_id). Envelope: subject { type: run, id: <run_id> } or { type: user, id: <subject> }, visibility internal, priority low, actor service:ai.",
++ "description": "ai.call.recorded v1 (plan L40, one AI meter across the network). One AI call, wherever it happened: OpenVibe.AI's own runs, a harness turn in Codes, an Actor task, Vibe, a local model on a person's own machine. who and where: subject (the person, or null when the call is anonymous), attribution (the site or app the call came from, free text such as 'site:design'), surface (the product surface, free text such as 'design.generate' or 'codes.turn'). what ran: system (ai, harness:claude-code, harness:codex, actor, node), provider, model, effort. who paid: paid_by (own_key, openvibe_key, plan, own_machine, free_tier, network_allowance, org_key) and paid_by_detail (for example 'anthropic ...a1b2' for a person's own key, or 'claude-max' for a plan). tokens: input (the UNCACHED input tokens), output, reasoning, cache_read, cache_write (with cache_write_ttl 5m or 1h), images, audio_seconds, video_seconds. money, in USD: list_cost_usd (what the tokens cost at the human-typed list prices), charged_usd (what the payer was actually charged), fee_usd (the network's fee on top), cache_saved_usd (what the cache saved against the plain input price). price_source says where the prices came from ('models', 'pricing', 'flat', or 'no cache price' when no cache price is typed and the cache tokens are priced at the input price), price_date the day the price was checked. how it went: latency_ms, ttft_ms, attempts, outcome. ids: run_id or turn_id, and at. The record carries NO prompt and NO answer text, ever. A ledger (OpenVibe.AI) rolls the records up; a record consumed twice is stored once (idempotent by system and run_id or turn_id). Envelope: subject { type: run, id: <run_id> } or { type: user, id: <subject> }, visibility internal, priority low, actor service:ai. Since 1.1.0 (ai-metrics.md): OPERATION, STREAMED, TPS, QUEUE_MS, TASK, TASK_ID, TASK_MS, HARNESS, PLACE, ERROR_CLASS, FALLBACKS, CACHE_HIT, TOOL_CALLS and REGION, all optional. Products other than AI send their calls to POST /api/v1/usage/calls (ai.call-report@1); AI prices and publishes them.",
"type": "object",
"required": [
"system",
@@@ -17560,6 +17560,854 @@
"at": {
"type": "string",
"format": "date-time"
++ },
++ "operation": {
++ "enum": [
++ "chat",
++ "completion",
++ "embedding",
++ "image",
++ "image_edit",
++ "speech",
++ "transcription",
++ "translation",
++ "rerank",
++ "moderation",
++ "decision",
++ "realtime",
++ "batch",
++ "tool",
++ "other",
++ null
++ ],
++ "description": "Since 1.1.0: what kind of call it was."
++ },
++ "streamed": {
++ "type": [
++ "boolean",
++ "null"
++ ],
++ "description": "Since 1.1.0: whether the answer came as a stream (TTFT is only measured on one)."
++ },
++ "tps": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "Since 1.1.0: output tokens per second after the first token (decode speed); null when not streamed or too short to measure."
++ },
++ "queue_ms": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "Since 1.1.0: time waiting before the call was sent (a queue, a rate gate)."
++ },
++ "task": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 120,
++ "description": "Since 1.1.0: the task kind the call served (a task name, a workflow key, chat.reply, code.turn)."
++ },
++ "task_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 120,
++ "description": "Since 1.1.0: the task or run this call is one step of, so cost and time add up per task."
++ },
++ "task_ms": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "Since 1.1.0: on the last call of a task, the whole task’s wall time."
++ },
++ "harness": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 60,
++ "description": "Since 1.1.0: the harness that made the call (openvibe, dsh, codex, claude-code, gemini-cli, opencode …)."
++ },
++ "place": {
++ "enum": [
++ "provider",
++ "machine",
++ "community",
++ "plan",
++ "host",
++ null
++ ],
++ "description": "Since 1.1.0: where the model ran: a provider’s API, the person’s own machine, a community machine, a signed-in plan, OpenVibe’s own hosts."
++ },
++ "error_class": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 60,
++ "description": "Since 1.1.0: on a failure, its class (rate_limited, timeout, context_length, refused, auth, server, network, invalid)."
++ },
++ "fallbacks": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "maximum": 50,
++ "description": "Since 1.1.0: how many offers failed before the one that answered."
++ },
++ "cache_hit": {
++ "type": [
++ "boolean",
++ "null"
++ ],
++ "description": "Since 1.1.0: true when the whole answer came from OpenVibe’s exact answer cache (no model ran)."
++ },
++ "tool_calls": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "Since 1.1.0: tool calls the model asked for in this call."
++ },
++ "region": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 40,
++ "description": "Since 1.1.0: the provider region or data centre, when known."
++ }
++ }
++ }
++ },
++ "ai.call-report": {
++ "version": "1.0.0",
++ "owner": "ai",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/call-report.v1.json",
++ "title": "AiCallReport",
++ "description": "ai.call-report@1 (ai-metrics.md): up to 100 AI calls a product made without OpenVibe.AI (Actor’s own model calls, a Codes harness turn, a local model on a person’s machine through Bot, Vibe), sent to AI’s POST /api/v1/usage/calls with the capability ai.usage.report. Each call is ai.call.recorded@1’s shape; the price is optional (AI prices from its rate cards: the charged amount first, then the list price) and SYSTEM + RUN_ID or TURN_ID is the idempotency key (a call sent twice is recorded once). AI publishes each as ai.call.recorded@1.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "calls"
++ ],
++ "properties": {
++ "calls": {
++ "type": "array",
++ "minItems": 1,
++ "maxItems": 100,
++ "items": {
++ "type": "object",
++ "required": [
++ "system",
++ "provider",
++ "model",
++ "outcome",
++ "at"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "subject": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 128,
++ "description": "The person the call was made for, or null when the call was anonymous."
++ },
++ "attribution": {
++ "type": "string",
++ "maxLength": 200,
++ "description": "The site or app the call came from, as '<type>:<id>' (for example 'site:design'), empty when nothing is known."
++ },
++ "surface": {
++ "type": "string",
++ "maxLength": 200,
++ "description": "The product surface: 'design.generate', 'codes.turn', 'ai.generate'. Free text, so a new surface needs no new contract."
++ },
++ "system": {
++ "type": "string",
++ "maxLength": 100,
++ "description": "What ran: 'ai', 'harness:claude-code', 'harness:codex', 'actor', 'node'."
++ },
++ "provider": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 128
++ },
++ "model": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 200
++ },
++ "effort": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 64,
++ "description": "The reasoning effort the call asked for, when it asked for one."
++ },
++ "paid_by": {
++ "type": "string",
++ "enum": [
++ "own_key",
++ "openvibe_key",
++ "plan",
++ "own_machine",
++ "free_tier",
++ "network_allowance",
++ "org_key"
++ ]
++ },
++ "paid_by_detail": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 200,
++ "description": "Which key or plan paid, for example 'anthropic ...a1b2' or 'claude-max'. Never a whole secret."
++ },
++ "input": {
++ "type": "integer",
++ "minimum": 0,
++ "description": "The uncached input tokens. Cache reads and cache writes are counted on their own fields."
++ },
++ "output": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "reasoning": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_read": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_write": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_write_ttl": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "enum": [
++ "5m",
++ "1h",
++ null
++ ],
++ "description": "How long the written cache entry lives, when the provider says."
++ },
++ "images": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "audio_seconds": {
++ "type": "number",
++ "minimum": 0
++ },
++ "video_seconds": {
++ "type": "number",
++ "minimum": 0
++ },
++ "list_cost_usd": {
++ "type": "number",
++ "minimum": 0,
++ "description": "What the tokens cost at the human-typed list prices. Optional here: AI prices a call that brings no price from its own rate cards."
++ },
++ "charged_usd": {
++ "type": "number",
++ "minimum": 0,
++ "description": "What the payer was actually charged. A person's own key is never charged here: 0. Optional here: AI prices a call that brings no price from its own rate cards."
++ },
++ "fee_usd": {
++ "type": "number",
++ "minimum": 0,
++ "description": "The network's fee on top of the cost, when the OpenVibe key paid. Optional here: AI prices a call that brings no price from its own rate cards."
++ },
++ "cache_saved_usd": {
++ "type": "number",
++ "minimum": 0,
++ "description": "What the cached tokens saved against the plain input price. Optional here: AI prices a call that brings no price from its own rate cards."
++ },
++ "price_source": {
++ "type": "string",
++ "maxLength": 64,
++ "description": "Where the prices came from: 'models' (a person's typed model row), 'pricing' (AI_PRICING_JSON), 'flat' (the flat rates), 'no cache price' (no cache price is typed, so the cache tokens are priced at the input price). Optional here: AI prices a call that brings no price from its own rate cards."
++ },
++ "price_date": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$",
++ "description": "The day the price was checked, when it was. Optional here: AI prices a call that brings no price from its own rate cards."
++ },
++ "latency_ms": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "ttft_ms": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "Time to the first token, when the call streamed and it was measured."
++ },
++ "attempts": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "outcome": {
++ "type": "string",
++ "enum": [
++ "succeeded",
++ "failed",
++ "cancelled",
++ "cached"
++ ]
++ },
++ "run_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 100
++ },
++ "turn_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 100
++ },
++ "at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "operation": {
++ "enum": [
++ "chat",
++ "completion",
++ "embedding",
++ "image",
++ "image_edit",
++ "speech",
++ "transcription",
++ "translation",
++ "rerank",
++ "moderation",
++ "decision",
++ "realtime",
++ "batch",
++ "tool",
++ "other",
++ null
++ ],
++ "description": "Since 1.1.0: what kind of call it was."
++ },
++ "streamed": {
++ "type": [
++ "boolean",
++ "null"
++ ],
++ "description": "Since 1.1.0: whether the answer came as a stream (TTFT is only measured on one)."
++ },
++ "tps": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "Since 1.1.0: output tokens per second after the first token (decode speed); null when not streamed or too short to measure."
++ },
++ "queue_ms": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "Since 1.1.0: time waiting before the call was sent (a queue, a rate gate)."
++ },
++ "task": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 120,
++ "description": "Since 1.1.0: the task kind the call served (a task name, a workflow key, chat.reply, code.turn)."
++ },
++ "task_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 120,
++ "description": "Since 1.1.0: the task or run this call is one step of, so cost and time add up per task."
++ },
++ "task_ms": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "Since 1.1.0: on the last call of a task, the whole task’s wall time."
++ },
++ "harness": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 60,
++ "description": "Since 1.1.0: the harness that made the call (openvibe, dsh, codex, claude-code, gemini-cli, opencode …)."
++ },
++ "place": {
++ "enum": [
++ "provider",
++ "machine",
++ "community",
++ "plan",
++ "host",
++ null
++ ],
++ "description": "Since 1.1.0: where the model ran: a provider’s API, the person’s own machine, a community machine, a signed-in plan, OpenVibe’s own hosts."
++ },
++ "error_class": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 60,
++ "description": "Since 1.1.0: on a failure, its class (rate_limited, timeout, context_length, refused, auth, server, network, invalid)."
++ },
++ "fallbacks": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "maximum": 50,
++ "description": "Since 1.1.0: how many offers failed before the one that answered."
++ },
++ "cache_hit": {
++ "type": [
++ "boolean",
++ "null"
++ ],
++ "description": "Since 1.1.0: true when the whole answer came from OpenVibe’s exact answer cache (no model ran)."
++ },
++ "tool_calls": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "Since 1.1.0: tool calls the model asked for in this call."
++ },
++ "region": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 40,
++ "description": "Since 1.1.0: the provider region or data centre, when known."
++ }
++ },
++ "anyOf": [
++ {
++ "required": [
++ "run_id"
++ ]
++ },
++ {
++ "required": [
++ "turn_id"
++ ]
++ }
++ ]
++ }
++ }
++ }
++ }
++ },
++ "report.ai-performance": {
++ "version": "1.0.0",
++ "owner": "report",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/report/ai-performance.v1.json",
++ "title": "ReportAiPerformance",
++ "description": "report.ai-performance@1 (ai-metrics.md): how AI calls performed over a range, for one group (a model, a provider, an operation, a system, a task). Percentiles come from log-scale histograms (within one bucket’s width). Public answers group by model, provider or operation only and show a group with at least 20 calls; system and task groups are for staff. Money is USD; tokens per second is the decode speed after the first token; cost per task sums the calls that share a task id.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "group",
++ "key",
++ "calls"
++ ],
++ "properties": {
++ "group": {
++ "enum": [
++ "model",
++ "provider",
++ "operation",
++ "system",
++ "task",
++ "place",
++ "harness"
++ ]
++ },
++ "key": {
++ "type": "string",
++ "maxLength": 200
++ },
++ "provider": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 120
++ },
++ "range": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "from": {
++ "type": "string",
++ "format": "date"
++ },
++ "to": {
++ "type": "string",
++ "format": "date"
++ }
++ }
++ },
++ "calls": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "succeeded": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "errors": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "success_rate": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0,
++ "maximum": 1
++ },
++ "fallback_rate": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0,
++ "maximum": 1
++ },
++ "streamed_share": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0,
++ "maximum": 1
++ },
++ "cache_hit_rate": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0,
++ "maximum": 1
++ },
++ "cache_read_share": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0,
++ "maximum": 1,
++ "description": "Cache-read tokens as a share of all input tokens."
++ },
++ "ttft_ms": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "p50": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "p90": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "p95": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "n": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ "latency_ms": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "p50": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "p90": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "p95": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "n": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ "tps": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "p50": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "p90": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "p95": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "n": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ "tokens": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "input": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "output": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "reasoning": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_read": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_write": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ "cost": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "charged_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "list_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "cache_saved_usd": {
++ "type": "number",
++ "minimum": 0
++ },
++ "per_call_usd": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "per_million_output_usd": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ }
++ }
++ },
++ "tasks": {
++ "type": [
++ "object",
++ "null"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "count": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cost_per_task_usd": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "p50": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "p90": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "p95": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "n": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ "time_per_task_ms": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "p50": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "p90": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "p95": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "n": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ }
++ }
++ },
++ "top_errors": {
++ "type": "array",
++ "maxItems": 5,
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "error_class",
++ "count"
++ ],
++ "properties": {
++ "error_class": {
++ "type": "string",
++ "maxLength": 60
++ },
++ "count": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ }
}
}
}
ddiff --git a/generated/openapi/actor.json b/generated/openapi/actor.json
iindex d845b30d..93ad6cd8 100644
---- a/generated/openapi/actor.json
++++ b/generated/openapi/actor.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Actor API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/ai.json b/generated/openapi/ai.json
iindex cb373ae0..cfd0b933 100644
---- a/generated/openapi/ai.json
++++ b/generated/openapi/ai.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.AI API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
@@@ -4088,6 +4088,55 @@
}
}
},
++ "/api/v1/usage/calls": {
++ "post": {
++ "operationId": "post_api_v1_usage_calls",
++ "summary": "Report AI calls a product made without OpenVibe.AI (ai.call-report@1): AI prices any that bring no price, records each once and publishes ai.call.recorded@1, so every AI call on the network is in one ",
++ "description": "**ai.usage.report** (internal): Report AI calls a product made without OpenVibe.AI (ai.call-report@1): AI prices any that bring no price, records each once and publishes ai.call.recorded@1, so every AI call on the network is in one meter.",
++ "tags": [
++ "ai.usage"
++ ],
++ "x-openvibe-capabilities": [
++ "ai.usage.report"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "ai-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "ai.usage.report"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/ai.call-report.v1"
++ }
++ }
++ }
++ },
++ "responses": {
++ "2XX": {
++ "description": "Success"
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
"/api/v1/workflows/{key}/versions": {
"post": {
"operationId": "post_api_v1_workflows_key_versions",
@@@ -5486,6 +5535,363 @@
}
}
},
++ "ai.call-report.v1": {
++ "title": "AiCallReport",
++ "description": "ai.call-report@1 (ai-metrics.md): up to 100 AI calls a product made without OpenVibe.AI (Actor’s own model calls, a Codes harness turn, a local model on a person’s machine through Bot, Vibe), sent to AI’s POST /api/v1/usage/calls with the capability ai.usage.report. Each call is ai.call.recorded@1’s shape; the price is optional (AI prices from its rate cards: the charged amount first, then the list price) and SYSTEM + RUN_ID or TURN_ID is the idempotency key (a call sent twice is recorded once). AI publishes each as ai.call.recorded@1.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "calls"
++ ],
++ "properties": {
++ "calls": {
++ "type": "array",
++ "minItems": 1,
++ "maxItems": 100,
++ "items": {
++ "type": "object",
++ "required": [
++ "system",
++ "provider",
++ "model",
++ "outcome",
++ "at"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "subject": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 128,
++ "description": "The person the call was made for, or null when the call was anonymous."
++ },
++ "attribution": {
++ "type": "string",
++ "maxLength": 200,
++ "description": "The site or app the call came from, as '<type>:<id>' (for example 'site:design'), empty when nothing is known."
++ },
++ "surface": {
++ "type": "string",
++ "maxLength": 200,
++ "description": "The product surface: 'design.generate', 'codes.turn', 'ai.generate'. Free text, so a new surface needs no new contract."
++ },
++ "system": {
++ "type": "string",
++ "maxLength": 100,
++ "description": "What ran: 'ai', 'harness:claude-code', 'harness:codex', 'actor', 'node'."
++ },
++ "provider": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 128
++ },
++ "model": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 200
++ },
++ "effort": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 64,
++ "description": "The reasoning effort the call asked for, when it asked for one."
++ },
++ "paid_by": {
++ "type": "string",
++ "enum": [
++ "own_key",
++ "openvibe_key",
++ "plan",
++ "own_machine",
++ "free_tier",
++ "network_allowance",
++ "org_key"
++ ]
++ },
++ "paid_by_detail": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 200,
++ "description": "Which key or plan paid, for example 'anthropic ...a1b2' or 'claude-max'. Never a whole secret."
++ },
++ "input": {
++ "type": "integer",
++ "minimum": 0,
++ "description": "The uncached input tokens. Cache reads and cache writes are counted on their own fields."
++ },
++ "output": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "reasoning": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_read": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_write": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_write_ttl": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "enum": [
++ "5m",
++ "1h",
++ null
++ ],
++ "description": "How long the written cache entry lives, when the provider says."
++ },
++ "images": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "audio_seconds": {
++ "type": "number",
++ "minimum": 0
++ },
++ "video_seconds": {
++ "type": "number",
++ "minimum": 0
++ },
++ "list_cost_usd": {
++ "type": "number",
++ "minimum": 0,
++ "description": "What the tokens cost at the human-typed list prices. Optional here: AI prices a call that brings no price from its own rate cards."
++ },
++ "charged_usd": {
++ "type": "number",
++ "minimum": 0,
++ "description": "What the payer was actually charged. A person's own key is never charged here: 0. Optional here: AI prices a call that brings no price from its own rate cards."
++ },
++ "fee_usd": {
++ "type": "number",
++ "minimum": 0,
++ "description": "The network's fee on top of the cost, when the OpenVibe key paid. Optional here: AI prices a call that brings no price from its own rate cards."
++ },
++ "cache_saved_usd": {
++ "type": "number",
++ "minimum": 0,
++ "description": "What the cached tokens saved against the plain input price. Optional here: AI prices a call that brings no price from its own rate cards."
++ },
++ "price_source": {
++ "type": "string",
++ "maxLength": 64,
++ "description": "Where the prices came from: 'models' (a person's typed model row), 'pricing' (AI_PRICING_JSON), 'flat' (the flat rates), 'no cache price' (no cache price is typed, so the cache tokens are priced at the input price). Optional here: AI prices a call that brings no price from its own rate cards."
++ },
++ "price_date": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$",
++ "description": "The day the price was checked, when it was. Optional here: AI prices a call that brings no price from its own rate cards."
++ },
++ "latency_ms": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "ttft_ms": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "Time to the first token, when the call streamed and it was measured."
++ },
++ "attempts": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "outcome": {
++ "type": "string",
++ "enum": [
++ "succeeded",
++ "failed",
++ "cancelled",
++ "cached"
++ ]
++ },
++ "run_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 100
++ },
++ "turn_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 100
++ },
++ "at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "operation": {
++ "enum": [
++ "chat",
++ "completion",
++ "embedding",
++ "image",
++ "image_edit",
++ "speech",
++ "transcription",
++ "translation",
++ "rerank",
++ "moderation",
++ "decision",
++ "realtime",
++ "batch",
++ "tool",
++ "other",
++ null
++ ],
++ "description": "Since 1.1.0: what kind of call it was."
++ },
++ "streamed": {
++ "type": [
++ "boolean",
++ "null"
++ ],
++ "description": "Since 1.1.0: whether the answer came as a stream (TTFT is only measured on one)."
++ },
++ "tps": {
++ "type": [
++ "number",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "Since 1.1.0: output tokens per second after the first token (decode speed); null when not streamed or too short to measure."
++ },
++ "queue_ms": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "Since 1.1.0: time waiting before the call was sent (a queue, a rate gate)."
++ },
++ "task": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 120,
++ "description": "Since 1.1.0: the task kind the call served (a task name, a workflow key, chat.reply, code.turn)."
++ },
++ "task_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 120,
++ "description": "Since 1.1.0: the task or run this call is one step of, so cost and time add up per task."
++ },
++ "task_ms": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "Since 1.1.0: on the last call of a task, the whole task’s wall time."
++ },
++ "harness": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 60,
++ "description": "Since 1.1.0: the harness that made the call (openvibe, dsh, codex, claude-code, gemini-cli, opencode …)."
++ },
++ "place": {
++ "enum": [
++ "provider",
++ "machine",
++ "community",
++ "plan",
++ "host",
++ null
++ ],
++ "description": "Since 1.1.0: where the model ran: a provider’s API, the person’s own machine, a community machine, a signed-in plan, OpenVibe’s own hosts."
++ },
++ "error_class": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 60,
++ "description": "Since 1.1.0: on a failure, its class (rate_limited, timeout, context_length, refused, auth, server, network, invalid)."
++ },
++ "fallbacks": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "maximum": 50,
++ "description": "Since 1.1.0: how many offers failed before the one that answered."
++ },
++ "cache_hit": {
++ "type": [
++ "boolean",
++ "null"
++ ],
++ "description": "Since 1.1.0: true when the whole answer came from OpenVibe’s exact answer cache (no model ran)."
++ },
++ "tool_calls": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0,
++ "description": "Since 1.1.0: tool calls the model asked for in this call."
++ },
++ "region": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 40,
++ "description": "Since 1.1.0: the provider region or data centre, when known."
++ }
++ },
++ "anyOf": [
++ {
++ "required": [
++ "run_id"
++ ]
++ },
++ {
++ "required": [
++ "turn_id"
++ ]
++ }
++ ]
++ }
++ }
++ }
++ },
"ai.choice.v1": {
"title": "AiChoiceV1",
"description": "One model and where it comes from, for a task or a run: OpenVibe's supply (openvibe), the person's own provider key (keys), or one of their own computers through OpenVibe Link (machines). A machine choice names the machine; the other sources never do. Used by ai.run-request@1, ai.task@1 and ai.task-defaults@1.",
ddiff --git a/generated/openapi/bench.json b/generated/openapi/bench.json
iindex 72e292bd..482dc674 100644
---- a/generated/openapi/bench.json
++++ b/generated/openapi/bench.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Bench API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Bench as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Bench as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/billing.json b/generated/openapi/billing.json
iindex 2e56fc2f..7da944f9 100644
---- a/generated/openapi/billing.json
++++ b/generated/openapi/billing.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Billing API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/blog.json b/generated/openapi/blog.json
iindex 99670a65..d6084f6e 100644
---- a/generated/openapi/blog.json
++++ b/generated/openapi/blog.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Blog API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/bot.json b/generated/openapi/bot.json
iindex 79f7e8c7..95376f64 100644
---- a/generated/openapi/bot.json
++++ b/generated/openapi/bot.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Bot API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/chat.json b/generated/openapi/chat.json
iindex f501f324..0b4941e0 100644
---- a/generated/openapi/chat.json
++++ b/generated/openapi/chat.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Chat API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/codes.json b/generated/openapi/codes.json
iindex e081ec8a..1384e080 100644
---- a/generated/openapi/codes.json
++++ b/generated/openapi/codes.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Codes API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/community.json b/generated/openapi/community.json
iindex 77e81928..e1d3ab34 100644
---- a/generated/openapi/community.json
++++ b/generated/openapi/community.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Community API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/connect.json b/generated/openapi/connect.json
iindex 240141d0..2003873d 100644
---- a/generated/openapi/connect.json
++++ b/generated/openapi/connect.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe Connect API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe Connect as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe Connect as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/coupons.json b/generated/openapi/coupons.json
iindex cd52b7af..36c07f14 100644
---- a/generated/openapi/coupons.json
++++ b/generated/openapi/coupons.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Coupons API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/deals.json b/generated/openapi/deals.json
iindex db947d08..95802cb5 100644
---- a/generated/openapi/deals.json
++++ b/generated/openapi/deals.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Deals API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/design.json b/generated/openapi/design.json
iindex db938d6a..1509131f 100644
---- a/generated/openapi/design.json
++++ b/generated/openapi/design.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Design API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Design as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Design as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/domains.json b/generated/openapi/domains.json
iindex 63e826bf..d90fbf42 100644
---- a/generated/openapi/domains.json
++++ b/generated/openapi/domains.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Domains API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Domains as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Domains as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/events.json b/generated/openapi/events.json
iindex ab5feca7..0ea8bea5 100644
---- a/generated/openapi/events.json
++++ b/generated/openapi/events.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Events API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/exchange.json b/generated/openapi/exchange.json
iindex 400f39b1..37550aa2 100644
---- a/generated/openapi/exchange.json
++++ b/generated/openapi/exchange.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Exchange API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Exchange as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Exchange as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/food.json b/generated/openapi/food.json
iindex 9eaae43d..c260f433 100644
---- a/generated/openapi/food.json
++++ b/generated/openapi/food.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Food API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/games.json b/generated/openapi/games.json
iindex c8fb00b1..219d0996 100644
---- a/generated/openapi/games.json
++++ b/generated/openapi/games.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Games API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/git.json b/generated/openapi/git.json
iindex 897bd7af..3fe1affe 100644
---- a/generated/openapi/git.json
++++ b/generated/openapi/git.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Git API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Git as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Git as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/host.json b/generated/openapi/host.json
iindex b18919be..456af27a 100644
---- a/generated/openapi/host.json
++++ b/generated/openapi/host.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Host API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/index.json b/generated/openapi/index.json
iindex 00f2255a..b063bbec 100644
---- a/generated/openapi/index.json
++++ b/generated/openapi/index.json
@@@ -12,8 +12,8 @@
"service": "ai",
"name": "OpenVibe.AI",
"origin": "https://ai.openvibe.services",
-- "operations": 72,
-- "capabilities": 19
++ "operations": 73,
++ "capabilities": 20
},
{
"service": "bench",
ddiff --git a/generated/openapi/inventory.json b/generated/openapi/inventory.json
iindex 8486e829..c142d562 100644
---- a/generated/openapi/inventory.json
++++ b/generated/openapi/inventory.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Inventory API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/live.json b/generated/openapi/live.json
iindex eb799220..ad9e3ecb 100644
---- a/generated/openapi/live.json
++++ b/generated/openapi/live.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Live API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/mail.json b/generated/openapi/mail.json
iindex eedaab56..e9cb3ef7 100644
---- a/generated/openapi/mail.json
++++ b/generated/openapi/mail.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Mail API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Mail as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Mail as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/media-hub.json b/generated/openapi/media-hub.json
iindex 1d579803..a78c0132 100644
---- a/generated/openapi/media-hub.json
++++ b/generated/openapi/media-hub.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.MediaHub API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/media.json b/generated/openapi/media.json
iindex 54bf50de..291084bc 100644
---- a/generated/openapi/media.json
++++ b/generated/openapi/media.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Media API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/network.json b/generated/openapi/network.json
iindex 77a1d50f..cf63c57f 100644
---- a/generated/openapi/network.json
++++ b/generated/openapi/network.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Network API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/news.json b/generated/openapi/news.json
iindex 96cc3e7d..ca3acd29 100644
---- a/generated/openapi/news.json
++++ b/generated/openapi/news.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.News API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/openre.json b/generated/openapi/openre.json
iindex 26130d25..cd4501c1 100644
---- a/generated/openapi/openre.json
++++ b/generated/openapi/openre.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenRestream API",
-- "version": "0.420.0",
-- "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/play.json b/generated/openapi/play.json
iindex c8b8c0fa..9c3313b2 100644
---- a/generated/openapi/play.json
++++ b/generated/openapi/play.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Play API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/quest.json b/generated/openapi/quest.json
iindex 619f9f0a..8c4e4fd8 100644
---- a/generated/openapi/quest.json
++++ b/generated/openapi/quest.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Quest API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/report.json b/generated/openapi/report.json
iindex 0e5dbb85..c89f7c75 100644
---- a/generated/openapi/report.json
++++ b/generated/openapi/report.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Report API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Report as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Report as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/reviews.json b/generated/openapi/reviews.json
iindex 28219243..1640fe11 100644
---- a/generated/openapi/reviews.json
++++ b/generated/openapi/reviews.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Reviews API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/run.json b/generated/openapi/run.json
iindex 11323bd9..d6d9f340 100644
---- a/generated/openapi/run.json
++++ b/generated/openapi/run.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Run API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/search.json b/generated/openapi/search.json
iindex d496a185..9168b715 100644
---- a/generated/openapi/search.json
++++ b/generated/openapi/search.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Search API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/services.json b/generated/openapi/services.json
iindex 15e29caa..3452018f 100644
---- a/generated/openapi/services.json
++++ b/generated/openapi/services.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Services API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/sources.json b/generated/openapi/sources.json
iindex 645ea05e..735aff91 100644
---- a/generated/openapi/sources.json
++++ b/generated/openapi/sources.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Sources API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/space.json b/generated/openapi/space.json
iindex 9ee27e0c..eb53ce11 100644
---- a/generated/openapi/space.json
++++ b/generated/openapi/space.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Space API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/tips.json b/generated/openapi/tips.json
iindex 042105bb..4d89dac8 100644
---- a/generated/openapi/tips.json
++++ b/generated/openapi/tips.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Tips API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/today.json b/generated/openapi/today.json
iindex ba04faf9..476c248a 100644
---- a/generated/openapi/today.json
++++ b/generated/openapi/today.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Today API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Today as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Today as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/tools.json b/generated/openapi/tools.json
iindex 37d1f54f..ea7c07fc 100644
---- a/generated/openapi/tools.json
++++ b/generated/openapi/tools.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Tools API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/trade.json b/generated/openapi/trade.json
iindex e045b182..0a033abb 100644
---- a/generated/openapi/trade.json
++++ b/generated/openapi/trade.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Trade API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/vip.json b/generated/openapi/vip.json
iindex f83445ad..5536120b 100644
---- a/generated/openapi/vip.json
++++ b/generated/openapi/vip.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.VIP API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/vision.json b/generated/openapi/vision.json
iindex 23b76fe9..9d5e673e 100644
---- a/generated/openapi/vision.json
++++ b/generated/openapi/vision.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Vision API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Vision as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Vision as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/watch.json b/generated/openapi/watch.json
iindex ac83d205..0d273089 100644
---- a/generated/openapi/watch.json
++++ b/generated/openapi/watch.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Watch API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/wiki.json b/generated/openapi/wiki.json
iindex 61abae99..5135ac0a 100644
---- a/generated/openapi/wiki.json
++++ b/generated/openapi/wiki.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Wiki API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/work.json b/generated/openapi/work.json
iindex 7190ad97..98baf33c 100644
---- a/generated/openapi/work.json
++++ b/generated/openapi/work.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Work API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/zone.json b/generated/openapi/zone.json
iindex 88f2d167..80133f99 100644
---- a/generated/openapi/zone.json
++++ b/generated/openapi/zone.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Zone API",
-- "version": "0.420.0",
-- "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.420.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.421.0",
++ "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.421.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/typescript/index.d.ts b/generated/typescript/index.d.ts
iindex 4ac87160..83d490b3 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -162831,9 +162831,9 @@ export interface AiRunError {
retry_at?: string | null;
}
--/** [email protected] (owner: ai) */
++/** [email protected] (owner: ai) */
/**
-- * ai.call.recorded v1 (plan L40, one AI meter across the network). One AI call, wherever it happened: OpenVibe.AI's own runs, a harness turn in Codes, an Actor task, Vibe, a local model on a person's own machine. who and where: subject (the person, or null when the call is anonymous), attribution (the site or app the call came from, free text such as 'site:design'), surface (the product surface, free text such as 'design.generate' or 'codes.turn'). what ran: system (ai, harness:claude-code, harness:codex, actor, node), provider, model, effort. who paid: paid_by (own_key, openvibe_key, plan, own_machine, free_tier, network_allowance, org_key) and paid_by_detail (for example 'anthropic ...a1b2' for a person's own key, or 'claude-max' for a plan). tokens: input (the UNCACHED input tokens), output, reasoning, cache_read, cache_write (with cache_write_ttl 5m or 1h), images, audio_seconds, video_seconds. money, in USD: list_cost_usd (what the tokens cost at the human-typed list prices), charged_usd (what the payer was actually charged), fee_usd (the network's fee on top), cache_saved_usd (what the cache saved against the plain input price). price_source says where the prices came from ('models', 'pricing', 'flat', or 'no cache price' when no cache price is typed and the cache tokens are priced at the input price), price_date the day the price was checked. how it went: latency_ms, ttft_ms, attempts, outcome. ids: run_id or turn_id, and at. The record carries NO prompt and NO answer text, ever. A ledger (OpenVibe.AI) rolls the records up; a record consumed twice is stored once (idempotent by system and run_id or turn_id). Envelope: subject { type: run, id: <run_id> } or { type: user, id: <subject> }, visibility internal, priority low, actor service:ai.
++ * ai.call.recorded v1 (plan L40, one AI meter across the network). One AI call, wherever it happened: OpenVibe.AI's own runs, a harness turn in Codes, an Actor task, Vibe, a local model on a person's own machine. who and where: subject (the person, or null when the call is anonymous), attribution (the site or app the call came from, free text such as 'site:design'), surface (the product surface, free text such as 'design.generate' or 'codes.turn'). what ran: system (ai, harness:claude-code, harness:codex, actor, node), provider, model, effort. who paid: paid_by (own_key, openvibe_key, plan, own_machine, free_tier, network_allowance, org_key) and paid_by_detail (for example 'anthropic ...a1b2' for a person's own key, or 'claude-max' for a plan). tokens: input (the UNCACHED input tokens), output, reasoning, cache_read, cache_write (with cache_write_ttl 5m or 1h), images, audio_seconds, video_seconds. money, in USD: list_cost_usd (what the tokens cost at the human-typed list prices), charged_usd (what the payer was actually charged), fee_usd (the network's fee on top), cache_saved_usd (what the cache saved against the plain input price). price_source says where the prices came from ('models', 'pricing', 'flat', or 'no cache price' when no cache price is typed and the cache tokens are priced at the input price), price_date the day the price was checked. how it went: latency_ms, ttft_ms, attempts, outcome. ids: run_id or turn_id, and at. The record carries NO prompt and NO answer text, ever. A ledger (OpenVibe.AI) rolls the records up; a record consumed twice is stored once (idempotent by system and run_id or turn_id). Envelope: subject { type: run, id: <run_id> } or { type: user, id: <subject> }, visibility internal, priority low, actor service:ai. Since 1.1.0 (ai-metrics.md): OPERATION, STREAMED, TPS, QUEUE_MS, TASK, TASK_ID, TASK_MS, HARNESS, PLACE, ERROR_CLASS, FALLBACKS, CACHE_HIT, TOOL_CALLS and REGION, all optional. Products other than AI send their calls to POST /api/v1/usage/calls (ai.call-report@1); AI prices and publishes them.
*/
export interface AiCallRecordedPayload {
/**
@@@ -162912,6 +162912,254 @@ export interface AiCallRecordedPayload {
run_id?: string | null;
turn_id?: string | null;
at: string;
++ /**
++ * Since 1.1.0: what kind of call it was.
++ */
++ operation?:
++ | "chat"
++ | "completion"
++ | "embedding"
++ | "image"
++ | "image_edit"
++ | "speech"
++ | "transcription"
++ | "translation"
++ | "rerank"
++ | "moderation"
++ | "decision"
++ | "realtime"
++ | "batch"
++ | "tool"
++ | "other"
++ | null;
++ /**
++ * Since 1.1.0: whether the answer came as a stream (TTFT is only measured on one).
++ */
++ streamed?: boolean | null;
++ /**
++ * Since 1.1.0: output tokens per second after the first token (decode speed); null when not streamed or too short to measure.
++ */
++ tps?: number | null;
++ /**
++ * Since 1.1.0: time waiting before the call was sent (a queue, a rate gate).
++ */
++ queue_ms?: number | null;
++ /**
++ * Since 1.1.0: the task kind the call served (a task name, a workflow key, chat.reply, code.turn).
++ */
++ task?: string | null;
++ /**
++ * Since 1.1.0: the task or run this call is one step of, so cost and time add up per task.
++ */
++ task_id?: string | null;
++ /**
++ * Since 1.1.0: on the last call of a task, the whole task’s wall time.
++ */
++ task_ms?: number | null;
++ /**
++ * Since 1.1.0: the harness that made the call (openvibe, dsh, codex, claude-code, gemini-cli, opencode …).
++ */
++ harness?: string | null;
++ /**
++ * Since 1.1.0: where the model ran: a provider’s API, the person’s own machine, a community machine, a signed-in plan, OpenVibe’s own hosts.
++ */
++ place?: "provider" | "machine" | "community" | "plan" | "host" | null;
++ /**
++ * Since 1.1.0: on a failure, its class (rate_limited, timeout, context_length, refused, auth, server, network, invalid).
++ */
++ error_class?: string | null;
++ /**
++ * Since 1.1.0: how many offers failed before the one that answered.
++ */
++ fallbacks?: number | null;
++ /**
++ * Since 1.1.0: true when the whole answer came from OpenVibe’s exact answer cache (no model ran).
++ */
++ cache_hit?: boolean | null;
++ /**
++ * Since 1.1.0: tool calls the model asked for in this call.
++ */
++ tool_calls?: number | null;
++ /**
++ * Since 1.1.0: the provider region or data centre, when known.
++ */
++ region?: string | null;
++}
++
++/** [email protected] (owner: ai) */
++/**
++ * ai.call-report@1 (ai-metrics.md): up to 100 AI calls a product made without OpenVibe.AI (Actor’s own model calls, a Codes harness turn, a local model on a person’s machine through Bot, Vibe), sent to AI’s POST /api/v1/usage/calls with the capability ai.usage.report. Each call is ai.call.recorded@1’s shape; the price is optional (AI prices from its rate cards: the charged amount first, then the list price) and SYSTEM + RUN_ID or TURN_ID is the idempotency key (a call sent twice is recorded once). AI publishes each as ai.call.recorded@1.
++ */
++export interface AiCallReport {
++ /**
++ * @minItems 1
++ * @maxItems 100
++ */
++ calls: [
++ (
++ | {
++ [k: string]: unknown | undefined;
++ }
++ | {
++ [k: string]: unknown | undefined;
++ }
++ ),
++ ...(
++ | {
++ [k: string]: unknown | undefined;
++ }
++ | {
++ [k: string]: unknown | undefined;
++ }
++ )[]
++ ];
++}
++
++/** [email protected] (owner: report) */
++/**
++ * report.ai-performance@1 (ai-metrics.md): how AI calls performed over a range, for one group (a model, a provider, an operation, a system, a task). Percentiles come from log-scale histograms (within one bucket’s width). Public answers group by model, provider or operation only and show a group with at least 20 calls; system and task groups are for staff. Money is USD; tokens per second is the decode speed after the first token; cost per task sums the calls that share a task id.
++ */
++export interface ReportAiPerformance {
++ group: "model" | "provider" | "operation" | "system" | "task" | "place" | "harness";
++ key: string;
++ provider?: string | null;
++ range?: {
++ from?: string;
++ to?: string;
++ };
++ calls: number;
++ succeeded?: number;
++ errors?: number;
++ success_rate?: number | null;
++ fallback_rate?: number | null;
++ streamed_share?: number | null;
++ cache_hit_rate?: number | null;
++ /**
++ * Cache-read tokens as a share of all input tokens.
++ */
++ cache_read_share?: number | null;
++ ttft_ms?: {
++ p50?: number | null;
++ p90?: number | null;
++ p95?: number | null;
++ n?: number;
++ };
++ latency_ms?: {
++ p50?: number | null;
++ p90?: number | null;
++ p95?: number | null;
++ n?: number;
++ };
++ tps?: {
++ p50?: number | null;
++ p90?: number | null;
++ p95?: number | null;
++ n?: number;
++ };
++ tokens?: {
++ input?: number;
++ output?: number;
++ reasoning?: number;
++ cache_read?: number;
++ cache_write?: number;
++ };
++ cost?: {
++ charged_usd?: number;
++ list_usd?: number;
++ cache_saved_usd?: number;
++ per_call_usd?: number | null;
++ per_million_output_usd?: number | null;
++ };
++ tasks?: {
++ count?: number;
++ cost_per_task_usd?: {
++ p50?: number | null;
++ p90?: number | null;
++ p95?: number | null;
++ n?: number;
++ };
++ time_per_task_ms?: {
++ p50?: number | null;
++ p90?: number | null;
++ p95?: number | null;
++ n?: number;
++ };
++ } | null;
++ /**
++ * @maxItems 5
++ */
++ top_errors?:
++ | []
++ | [
++ {
++ error_class: string;
++ count: number;
++ }
++ ]
++ | [
++ {
++ error_class: string;
++ count: number;
++ },
++ {
++ error_class: string;
++ count: number;
++ }
++ ]
++ | [
++ {
++ error_class: string;
++ count: number;
++ },
++ {
++ error_class: string;
++ count: number;
++ },
++ {
++ error_class: string;
++ count: number;
++ }
++ ]
++ | [
++ {
++ error_class: string;
++ count: number;
++ },
++ {
++ error_class: string;
++ count: number;
++ },
++ {
++ error_class: string;
++ count: number;
++ },
++ {
++ error_class: string;
++ count: number;
++ }
++ ]
++ | [
++ {
++ error_class: string;
++ count: number;
++ },
++ {
++ error_class: string;
++ count: number;
++ },
++ {
++ error_class: string;
++ count: number;
++ },
++ {
++ error_class: string;
++ count: number;
++ },
++ {
++ error_class: string;
++ count: number;
++ }
++ ];
}
/** [email protected] (owner: ai) */
ddiff --git a/manifests/capabilities/ai.usage.report.json b/manifests/capabilities/ai.usage.report.json
nnew file mode 100644
iindex 00000000..3666309d
---- /dev/null
++++ b/manifests/capabilities/ai.usage.report.json
@@@ -0,0 +1,23 @@
++{
++ "id": "ai.usage.report",
++ "version": "1.0",
++ "owner": "ai",
++ "status": "active",
++ "visibility": "internal",
++ "description": "Report AI calls a product made without OpenVibe.AI (ai.call-report@1): AI prices any that bring no price, records each once and publishes ai.call.recorded@1, so every AI call on the network is in one meter.",
++ "permissions": [
++ "ai:usage:report"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "ai-read",
++ "events": [
++ "ai.call.recorded"
++ ],
++ "implementedBy": [
++ "POST /api/v1/usage/calls"
++ ],
++ "inputSchema": "ai.call-report@1",
++ "outputSchema": "common.no-body@1"
++}
ddiff --git a/manifests/services/ai.json b/manifests/services/ai.json
iindex 768ce3dd..aeb2a450 100644
---- a/manifests/services/ai.json
++++ b/manifests/services/ai.json
@@@ -20,24 +20,25 @@
"note": "the public API (OpenAI-compatible /v1 and the six operations); its human pages moved to OpenVibe.Exchange on 2026-11-07 and answer 301 there. A run receipt (/runs/:id) and the operator console stay for the person signed in"
},
"capabilities": [
++ "ai.actions.read",
"ai.app.run",
++ "ai.batches.create",
++ "ai.batches.read",
++ "ai.config.manage",
"ai.credential.manage",
++ "ai.layout.suggest",
++ "ai.me.manage",
++ "ai.me.read",
++ "ai.memory.read",
++ "ai.memory.write",
"ai.model.read",
"ai.provider.manage",
"ai.quota.attribution.manage",
-- "ai.batches.create",
-- "ai.batches.read",
"ai.run.create",
"ai.run.read",
"ai.usage.read",
++ "ai.usage.report",
"ai.workflow.manage",
-- "ai.actions.read",
-- "ai.memory.read",
-- "ai.memory.write",
-- "ai.me.read",
-- "ai.me.manage",
-- "ai.layout.suggest",
-- "ai.config.manage",
"exchange.search.icons"
],
"eventsProduced": [
@@@ -90,14 +91,14 @@
],
"window": "automatic only while ovhost waits for readiness after the restart (30 s in host.example.json); manual afterwards, as long as no blocker applies",
"blockers": {
-- "none": "the schema code only adds (CREATE \u2026 IF NOT EXISTS, ADD COLUMN; no DROP, RENAME or table rebuild), which an older release ignores"
++ "none": "the schema code only adds (CREATE … IF NOT EXISTS, ADD COLUMN; no DROP, RENAME or table rebuild), which an older release ignores"
}
},
"contracts": {
"range": ">=0.6.0 <1.0.0"
},
"leases": {
-- "none": "no claims: one process owns its PostgreSQL database (ov_ai); a run is taken with a compare-and-set on its status (queued \u2192 running)"
++ "none": "no claims: one process owns its PostgreSQL database (ov_ai); a run is taken with a compare-and-set on its status (queued → running)"
}
},
"notes": "Providers, models, routing profiles, versioned prompt templates and workflows, runs, request logs, citations, scoped cache, quotas and audit. Workflow output is a draft/evidence package attributed to workflow + model + run. ai.run.queued|succeeded|failed|cached have payload contracts (v0.30.0, status planned): AI publishes them once its Events client is wired in; it publishes none today. ai.run.create takes ai.run-request@1 and returns ai.run@1. Produces ai.run@1 effort_requested, effort_applied, effort_rung and ladder receipts; consumes ai.effort@1 and ai.run-request@1 effort/ladder metadata. Actor consumes those receipts; AI does not authorize agent tickets.",
ddiff --git a/package.json b/package.json
iindex 99fb3360..0aba7f45 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.420.0",
++ "version": "0.421.0",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"main": "index.js",
"types": "generated/typescript/index.d.ts",