openvibers/openvibe.sdk Commits
Add Report AI and Exchange Arena SDK (#147)
Changes
@@@ -1,5 +1,9 @@# Changelog++## 0.105.0 (2026-11-09)++++- Add Report AI leaderboard, model, provider, comparison and picks reads, plus Exchange Arena start and vote methods.++## 0.104.0 (2026-11-08)- Add `createAccountEmailsClient` to `openvibe-sdk/identity` for managing the signed-in person's verified Network email addresses.@@@ -2,7 +2,7 @@> Supported browser and server clients for the OpenVibe platform.--**Status:** alpha, v0.104.0 (the five 1.0 core entries — `core`, `auth`, `events`, `db` and `media` — are frozen: their exported names change only with a major release; roadmap Wave 2; developer apps from Wave 20; Media objects v2 from Wave 4; the Tools platform API; Search, Chat, AI, Vision and consent clients; sign-in with recovery and a client health check). Every release is tagged (see [CHANGELOG.md](CHANGELOG.md)). Tested against local stub servers and the built-in mock platform only, never against the live platform. Services pin release tags; each repository's `STATUS.json` names the one it uses.++**Status:** alpha, v0.105.0 (the five 1.0 core entries — `core`, `auth`, `events`, `db` and `media` — are frozen: their exported names change only with a major release; roadmap Wave 2; developer apps from Wave 20; Media objects v2 from Wave 4; the Tools platform API; Search, Chat, AI, Vision and consent clients; sign-in with recovery and a client health check). Every release is tagged (see [CHANGELOG.md](CHANGELOG.md)). Tested against local stub servers and the built-in mock platform only, never against the live platform. Services pin release tags; each repository's `STATUS.json` names the one it uses.**Plan:** OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026), §3.2; roadmap §30.**License:** MIT ([LICENSE](LICENSE)). This package is a library that apps outside the network embed, so it uses MIT. The OpenVibe services themselves stay AGPL-3.0.@@@ -11,7 +11,7 @@After v0.97.1 is tagged:```bash--npm install https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.104.0++npm install https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.105.0```It has no runtime dependencies. It needs Node ≥ 20, or any browser with `fetch`, Web Crypto and `TextDecoder`. There is no build step. The package is CommonJS with ESM entry points (`import` works). Each subpath has its own `.d.ts`. For a page with no bundler, `browser/openvibe-sdk.mjs` is one self-contained ES module of the browser-safe subpaths (see [Browser without a bundler](#browser-without-a-bundler)). It does not depend on `openvibe-contracts` at runtime: it copies the contract types it uses (from the temporary Contracts sharing branch, a devDependency the tests check them against).@@@ -632,3 +632,18 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by [### Locker: end-to-end encryption (Node and the browser)`openvibe-sdk/locker` seals anything and opens it again: the Locker envelope (`locker.envelope@1`), the three locks (people, password, link), streams for files, person key pairs and the eight-word fingerprint, `wrapFor` and `rewrap` for sharing, `recoveryWords` for the phrase to write down, `inspect` for what an envelope says about itself, and opening a people lock by key. `openvibe-sdk/locker/server` gives a service the header checks without a key. WebCrypto only, in Node and the browser. See [the locker module](docs/locker.md).++# AI comparison and Arena++++Read Report's public AI data and create or vote on Exchange Arena rounds:++++```js++const ov = require('openvibe-sdk');++const client = ov.createClient();++const report = ov.report.createReportClient(client);++const leaders = await report.ai.leaderboard({ kind: 'code', limit: 25 });++const headToHead = await report.ai.compare(['openai/gpt-4o', 'anthropic/claude-sonnet']);++++const exchange = require('openvibe-sdk/exchange').createExchangeClient({ developerKey });++const round = await exchange.arena.start({ prompt: 'Explain a binary search.', category: 'code', entries });++await exchange.arena.vote(round.id, 'left');++```@@@ -3,7 +3,7 @@"stage": "alpha","code": true,"wave": "W2",-- "version": "0.104.0",++ "version": "0.105.0","plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026), §3.2; roadmap §30","deployed": false,"deployment": "none: a package released as git tags; services and apps pin a tag",@@@ -1,4 +1,4 @@--// Generated by scripts/browser-bundle.js from browser.js (openvibe-sdk 0.104.0, MIT). Do not edit.++// Generated by scripts/browser-bundle.js from browser.js (openvibe-sdk 0.105.0, MIT). Do not edit.// One self-contained ES module: core at the top level plus the browser-safe subpaths as namespaces// (auth, locker, registry, trust, modules, realtime, media, community, bot, search, bench, meme, vision, themes, redact, chat, ai, credentials, jobs, tools, projects, geo). No server-only code: no service tokens, JWT verification, events, identity./* eslint-disable */@@@ -50,7 +50,7 @@ const { paginate, offsetPager } = __load(7);const semver = __load(6);/** The package version (test/version.test.js keeps it equal to package.json). */--const SDK_VERSION = '0.104.0';++const SDK_VERSION = '0.105.0';module.exports = {createClient,@@@ -290,6 +290,12 @@"createRegistryClient"]},++ "report": {++ "object": [++ "REPORT_URL",++ "createReportClient"++ ]++ },"resources": {"object": ["RESOURCE_KINDS",@@@ -927,6 +933,10 @@"openvibe-sdk/registry": {"createRegistryClient": "function"},++ "openvibe-sdk/report": {++ "REPORT_URL": "string",++ "createReportClient": "function"++ },"openvibe-sdk/reports": {"cleanReport": "function","createReportsClient": "function"@@@ -4,15 +4,15 @@ import cjs from '../index.js';export const {createClient, OpenVibeError, isOpenVibeError, paginate, offsetPager, parseTraceparent,startSpan, contextFromHeaders, ulid, newEventId, newIdempotencyKey, isActingSubjectId,-- satisfiesRange, compareVersions, CONTRACTS_RANGE, DEFAULT_NETWORK, SDK_VERSION, share,-- locker, auth, registry, identity, trust, modules,-- events, realtime, media, community, bot, search,-- bench, meme, vision, themes, redact, chat,-- chatApps, ai, credentials, jobs, tools, projects,-- resources, vip, quest, live, inventory, tips,-- blog, reviews, wiki, coupons, deals, trade,-- space, host, news, food, actor, run,-- video, work, zone, play, download, openre,-- geo, usage, commerce, telemetry,++ satisfiesRange, compareVersions, CONTRACTS_RANGE, DEFAULT_NETWORK, SDK_VERSION, report,++ share, locker, auth, registry, identity, trust,++ modules, events, realtime, media, community, bot,++ search, bench, meme, vision, themes, redact,++ chat, chatApps, ai, credentials, jobs, tools,++ projects, resources, vip, quest, live, inventory,++ tips, blog, reviews, wiki, coupons, deals,++ trade, space, host, news, food, actor,++ run, video, work, zone, play, download,++ openre, geo, usage, commerce, telemetry,} = cjs;export default cjs;nnew file mode 100644@@@ -0,0 +1,7 @@++// Generated by scripts/esm.js from src/report.js. Do not edit.++import cjs from '../src/report.js';++++export const {++ createReportClient, REPORT_URL,++} = cjs;++export default cjs;@@@ -8,6 +8,7 @@ const core = require('./src/core');module.exports = {...core,++ report: require('./src/report'),share: require('./src/share').share,locker: require('./src/locker'),auth: require('./src/auth'),@@@ -1,12 +1,12 @@{"name": "openvibe-sdk",-- "version": "0.104.0",++ "version": "0.105.0","lockfileVersion": 3,"requires": true,"packages": {"": {"name": "openvibe-sdk",-- "version": "0.104.0",++ "version": "0.105.0","license": "MIT","bin": {"openvibe-download": "bin/openvibe-download.js"@@@ -14,7 +14,7 @@"devDependencies": {"@electric-sql/pglite": "^0.5.8","iovalkey": "^0.4.0",-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.338.0",++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.351.0","pg": "^8.23.0"},"engines": {@@@ -198,9 +198,9 @@"license": "MIT"},"node_modules/openvibe-contracts": {-- "version": "0.338.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.338.0",-- "integrity": "sha512-75FD6IxMsXoud3NFXKDs0/D0rhJtF2D+WL8M92ArIyabkUnM/sZvw6E6L97EqwQ3O7M/kB+uwsNSxGudbil+ng==",++ "version": "0.351.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.351.0",++ "integrity": "sha512-XfI7OqI78iYHCJ/+n++R0Zrot6OR/dH1DMdL1guZdEX6x/YJ3UF3FCmRjljk4r0GEXmrUlNr1iL6CR9Jfx0uOg==","dev": true,"license": "AGPL-3.0-only","dependencies": {@@@ -1,6 +1,6 @@{"name": "openvibe-sdk",-- "version": "0.104.0",++ "version": "0.105.0","description": "Supported browser and server clients for the OpenVibe platform: auth (incl. developer apps), registry discovery, identity, user modules, events, realtime, media, community, Bot robots, search, chat, AI runs, Tools (registry, run API and jobs), VIP memberships and developer projects, the async PostgreSQL data layer (db) and the Valkey cache, limits store, queue and pub/sub, with deadlines, safe retries, problem errors and trace propagation.","main": "index.js","types": "types/index.d.ts",@@@ -198,6 +198,11 @@"import": "./esm/exchange.mjs","default": "./src/exchange.js"},++ "./report": {++ "types": "./types/report.d.ts",++ "import": "./esm/report.mjs",++ "default": "./src/report.js"++ },"./delivery": {"types": "./types/delivery.d.ts","browser": null,@@@ -573,7 +578,7 @@"devDependencies": {"@electric-sql/pglite": "^0.5.8","iovalkey": "^0.4.0",-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.338.0",++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.351.0","pg": "^8.23.0"},"peerDependencies": {@@@ -52,6 +52,7 @@ const ENTRIES = {'storage.mjs': '../src/storage.js','commerce.mjs': '../src/commerce.js','exchange.mjs': '../src/exchange.js',++ 'report.mjs': '../src/report.js','vibes.mjs': '../src/vibes.js','delivery.mjs': '../src/delivery.js','node.mjs': '../src/node.js',@@@ -8,7 +8,7 @@ const { paginate, offsetPager } = require('./paginate');const semver = require('./semver');/** The package version (test/version.test.js keeps it equal to package.json). */--const SDK_VERSION = '0.104.0';++const SDK_VERSION = '0.105.0';module.exports = {createClient,@@@ -541,6 +541,20 @@ function createExchangeClient({};return {++ arena: {++ /** Start a blind comparison round using the caller's prompt and pair. */++ async start({ prompt, category = 'chat', entries } = {}, { signal } = {}) {++ if (typeof prompt !== 'string' || !prompt.trim()) throw new TypeError('arena.start: prompt is required');++ if (!Array.isArray(entries) || entries.length < 2) throw new TypeError('arena.start: at least two entries are required');++ return await jsonCall('POST', '/api/v1/arena/rounds', { json: { prompt, category, entries }, signal });++ },++ /** Submit a blind vote. choice is left, right, tie or both_bad. */++ async vote(roundId, choice, { signal } = {}) {++ if (!roundId) throw new TypeError('arena.vote: roundId is required');++ if (!['left', 'right', 'tie', 'both_bad'].includes(choice)) throw new TypeError('arena.vote: choice must be left, right, tie or both_bad');++ return await jsonCall('POST', `/api/v1/arena/rounds/${encodeURIComponent(roundId)}/votes`, { json: { outcome: choice }, signal });++ },++ },/*** One chat completion. The body is the OpenAI shape; stream: true answers an async iterable of* chunks instead of the finished completion.nnew file mode 100644@@@ -0,0 +1,15 @@++'use strict';++++const REPORT_URL = 'https://openvibe.report';++function createReportClient(client, { baseUrl = REPORT_URL } = {}) {++ if (!client || typeof client.json !== 'function') throw new TypeError('createReportClient: pass an openvibe-sdk/core client');++ const call = (path, query, signal) => client.json({ baseUrl, path, query, signal, service: 'report' });++ return { ai: {++ leaderboard(opts = {}, { signal } = {}) { return call('/api/v1/ai/leaderboard', opts, signal); },++ model(id, { signal } = {}) { if (!id) throw new TypeError('report.ai.model: id is required'); return call(`/api/v1/ai/models/${encodeURIComponent(id)}`, null, signal); },++ providers({ signal } = {}) { return call('/api/v1/ai/providers', null, signal); },++ compare(ids, { signal } = {}) { const models = (Array.isArray(ids) ? ids : [ids]).map(String).join(','); return call('/api/v1/ai/compare', { models }, signal); },++ picks({ signal } = {}) { return call('/api/v1/ai/picks', null, signal); },++ } };++}++module.exports = { createReportClient, REPORT_URL };@@@ -72,7 +72,7 @@ run([})().catch((err) => { console.error(err); process.exit(1); });`;const res = execFileSync(process.execPath, ['-e', script], { cwd: app, encoding: 'utf8' });assert.match(res, new RegExp(`consumer ok: ${subpaths.length} subpaths \\+ the browser bundle`));-- assert.equal(subpaths.length, 91); // Adds the browser-safe trust client (0.102.0) and the AI credentials client.++ assert.equal(subpaths.length, 92); // Adds the Report AI comparison client (0.105.0); 91 had the trust and AI credentials clients.assert.ok(!fs.existsSync(path.join(app, 'node_modules/openvibe-contracts')), 'the optional peer is not installed');} finally {fs.rmSync(tmp, { recursive: true, force: true });nnew file mode 100644@@@ -0,0 +1,31 @@++'use strict';++const assert = require('node:assert/strict');++const { createReportClient } = require('../src/report');++const { createExchangeClient } = require('../src/exchange');++++(async () => {++ const calls = [];++ const client = { json: async (opts) => { calls.push(opts); return {}; } };++ const report = createReportClient(client, { baseUrl: 'https://report.test' });++ await report.ai.leaderboard({ kind: 'code', limit: 10 });++ await report.ai.model('org/model');++ await report.ai.providers();++ await report.ai.compare(['a', 'b']);++ await report.ai.picks();++ assert.equal(calls.length, 5);++ assert.equal(calls[0].path, '/api/v1/ai/leaderboard');++ assert.deepEqual(calls[3].query, { models: 'a,b' });++ assert.throws(() => report.ai.model(''), /id is required/);++++ const requests = [];++ const exchange = createExchangeClient({ developerKey: 'test-key', baseUrl: 'https://exchange.test', fetchImpl: async (url, options) => {++ requests.push({ url: String(url), options });++ return new Response('{}', { status: 200, headers: { 'content-type': 'application/json' } });++ }, maxRetries: 0 });++ await exchange.arena.start({ prompt: 'Compare', entries: [{ id: 'a' }, { id: 'b' }] });++ await exchange.arena.vote('rnd_1', 'left');++ assert.match(requests[0].url, /\/api\/v1\/arena\/rounds$/);++ assert.match(requests[1].url, /\/api\/v1\/arena\/rounds\/rnd_1\/votes$/);++ await assert.rejects(exchange.arena.vote('rnd_1', 'bad'), /choice must be/);++ console.log('Report AI and Exchange Arena SDK tests passed');++})().catch((error) => { console.error(error); process.exitCode = 1; });@@@ -32,7 +32,6 @@ export type SubjectRef =type: "agent";id: string;};--/** [email protected] (owner: network) *//*** Claims of a short-lived RS256 client-credentials token issued by OpenVibe.Network to a service, app, mod, node or agent principal. Replaces X-Internal-Key. App tokens (actor_type app) also carry project_id and env; receivers refuse env=sandbox unless they opted in. Agent tokens (actor_type agent, sub agent:agt_<ULID>) are minted for the agent's host and also carry project_id, env and on_behalf_of (the owner), and optionally cap_confirm and act.@@@ -108,7 +107,6 @@ export type ServiceTokenClaims = {};[k: string]: unknown | undefined;};--/** [email protected] (owner: contracts) *//*** Typed reference to another service's entity. Store this instead of a foreign key; resolve a display projection from the owner.@@@ -123,7 +121,6 @@ export interface EntityRef {*/label?: string;}--/** [email protected] (owner: contracts) *//*** RFC 9457 problem details (application/problem+json) with a stable OpenVibe error code and trace identifiers. The legacy 'error' string is allowed during migration so existing clients keep working.@@@ -147,7 +144,6 @@ export interface Problem {error?: string;[k: string]: unknown | undefined;}--/** [email protected] (owner: network) *//*** What a service is, where it lives and what it offers. The ecosystem registry is built from these, not from hard-coded route maps.@@@ -161,6 +157,16 @@ export interface ServiceManifest {domains: string[];publicOrigin?: string;health?: string;++ /**++ * Public HTTP routes implemented by this service, with their response contract where one exists.++ */++ routes?: {++ method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";++ path: string;++ contract?: string;++ cacheSeconds?: number;++ cors?: string;++ }[];ready?: string;/*** Where the service answers on its host (loopback only): the address the registry polls health and readiness from. A host inventory or an OV_<ID>_INTERNAL_URL loopback override may replace it; a service with nothing to run has none.@@@ -2184,7 +2190,6 @@ export interface ServiceManifest {};notes?: string;}--/** [email protected] (owner: network) *//*** An action a principal may invoke. Authorization is checked at the owner's boundary against the grant, never inferred from the caller's route.@@@ -2211,7 +2216,6 @@ export interface Capability {*/sensitive?: boolean;}--/** [email protected] (owner: events) *//*** Durable platform event (OpenVibe.Events, Wave 3). Producers write it to an outbox in the same transaction as the domain change.@@@ -2234,7 +2238,6 @@ export interface EventEnvelope {};payload: {};}--/** [email protected] (owner: network) *//*** Policy for one user-module namespace: portable per-subject summaries and preferences stored by OpenVibe.Network. Never domain truth, money or authoritative game inventory (roadmap 4.3-4.5). The person's account decides what happens to their records in every namespace: when an account is removed, Network deletes all of its records; when two accounts are merged (ADR-029), in a namespace where both have a record the survivor keeps its own and gains only the top-level fields it lacks from the other (its own values always win), a record only the absorbed account had moves to the survivor, and the absorbed account's records are deleted. Each of these changes is announced as network.module.updated (reason subject_removed or subject_merged). That is separate from onOwnerRemoved, which says what happens to a namespace's records when the service that owns the namespace is retired.@@@ -2293,7 +2296,6 @@ export interface ModuleNamespace {defaults?: {};}[];}--/** [email protected] (owner: network) *//*** One subject's value in one namespace, as returned by Network. revision increases by one on every write; a write names the revision it read (If-Match) and fails with 412 if it moved.@@@ -2310,7 +2312,6 @@ export interface ModuleRecord {*/updated_by?: string;}--/** [email protected] (owner: codes) *//*** PROPOSAL (OpenVibe.Codes, roadmap Wave 20): an app release as the platform knows it. The app is a Network developer app (ADR-014): its id is the Network app id, its capabilities are only a request (the grants in Network are the authority), and trust tiers are metadata that never change a grant check. OpenVibe.Codes validates with this schema, loaded into openvibe-contracts' validator, until Contracts publishes it as codes.app-manifest@1.@@@ -2383,7 +2384,6 @@ export interface AppManifest {sdk?: string;};}--/** [email protected] (owner: tools) *//*** One tool on openvibe.tools as its registry describes it (GET /api/v1/tools/:id, and each item of GET /api/v1/tools; capability tools.tool.read; ADR-027). There is one descriptor per catalogue tool (GET /api/catalog.json tools[].id), built from the tool's own code, so its page, the run API, openvibe-sdk/tools, the OpenAPI document and the docs all read the same facts. EXECUTION is where the tool's engine runs for its page: client (in the browser; nothing leaves it), sync (a server request answered inline) or job (an asynchronous job on a satellite, tools.job@1). API says whether POST /api/v1/tools/{id}/run exposes the tool. A client tool has api true only when it also has a server engine (a pure transform that runs in Node), and its page stays browser-only. A page-only tool (yt) has api false and run null. A job tool's run creates a job of run.job.type whose input is { ...input, ...run.job.preset, tool: run.job.operation } (the preset and the operation always win). INPUT is the JSON Schema (2020-12) that a run request's input must match, embedded or as { $ref: https://openvibe.tools/api/v1/tools/{id}/schema#/$defs/input }. GET /api/v1/tools/:id/schema answers { $schema, $id, $defs: { input, output } }, and the list uses the $ref form. When api is false, $defs.input is false (the schema that accepts nothing), since there is no run to take an input. OUTPUT.SCHEMA describes result.data for every execution: inline and job tools alike. KEYWORDS (the catalogue's search terms) and EXAMPLES (sample runs for the docs and the OpenAPI document) are optional. NOT A TOOL: there is no planned status. A planned catalogue entry has no descriptor and is not listed, and neither is a mirror (a second build of another tool). GET /api/v1/tools/:id and /schema answer 404 tools.tool.not_found for either, with a detail that says the id is planned or names the tool it mirrors, as for an id that does not exist. RULES this schema enforces: api false has no run and no examples, and api true has a run and an input; an API job tool names its job, and only job tools name one; an API tool whose output is a file runs as a job (its results are served as job files); a tools.net.probe tool fetches (egress) and is never anonymous; an anonymous egress tool has a per-target throttle (limits.perTargetPerMinute); a client tool never fetches; an unavailable tool says why (statusReason); JSON output has a schema. contracts.tools.checkDescriptor(d) also checks what depends on the id (run.path, $ref targets), files.min <= files.max, and each example: its input against an embedded input schema and limits.maxInputBytes, and its files against files (count and accept).@@@ -2603,7 +2603,6 @@ export interface ToolDescriptor {*/docs: string;}--/** [email protected] (owner: tools) *//*** The answer of GET /api/v1/tools on openvibe.tools (capability tools.tool.read; ADR-027): every tool's descriptor (tools.tool@1), with input and output schemas in the { $ref } form (GET /api/v1/tools/:id has them embedded). Filters: ?family=, ?execution=client|sync|job, ?api=true|false, ?status=, ?q= (every word must appear in the id, name, summary or keywords); family, execution and status take comma lists. An unknown execution or status value, or an api that is not true or false, is 400 tools.query.invalid. Every listed tool has one of the descriptor's statuses: there is no planned status, so planned placeholders are not tools and are not listed, and neither are mirrors (GET /api/v1/tools/:id answers 404 tools.tool.not_found for both, with a detail saying why). Cacheable (ETag); updated_at changes when any descriptor does.@@@ -2624,7 +2623,6 @@ export interface ToolList {count: number;}[];}--/** [email protected] (owner: tools) *//*** Body of POST /api/v1/tools/{id}/run on openvibe.tools (capability tools.tool.run, or tools.net.probe for network probes; ADR-027). JSON, or multipart/form-data like POST /api/v1/jobs: the text parts input (JSON text), wait_ms and idempotency_key, and uploaded files as file or files parts. The tool gets the uploaded files first, then the files references, in order; the count must fit the descriptor's files.min and files.max. input must match the tool's input schema (tools.tool@1 input). A job tool's job input is { ...input, ...run.job.preset, tool: run.job.operation } (the preset and the operation always win). The Idempotency-Key header wins over idempotency_key; ?wait_ms= on the query does the same as the field. Answers tools.run@1: 200 finished (succeeded, or failed with the tool's own error), 202 + Location while a job is queued or running, 200 + Idempotent-Replayed: true for a replay. Refusals are problem+json: 400 tools.run.invalid (not JSON, a bad field, wrong file count), 401 token.*, 403 capability.denied, 404 tools.tool.not_found, 404 tools.tool.not_runnable (api false: a page-only tool), 404 tools.run.file_not_found (a reference the caller cannot read), 409 tools.job.idempotency_conflict, 413 tools.file.too_large, 415 tools.file.unsupported_type (checked against the bytes), 422 tools.input.invalid (errors[] with JSON pointers), 429 tools.quota.exceeded (Retry-After; the caller's allowance in the tool's quota class is spent), quota.exceeded or tools.job.too_many_active (Retry-After), 503 tools.tool.unavailable (the descriptor's status is unavailable), 503 tools.unavailable (a program or upstream the tool needs is missing), 503 tools.busy (Retry-After; every slot for this kind of work is taken). A job tool's own refusals (413 tools.pdf.too_many_pages, 422 tools.pdf.wrong_password…) come back as a failed run (tools.run@1 error).@@@ -2657,7 +2655,6 @@ export interface ToolsRunRequest {*/idempotency_key?: string;}--/** [email protected] (owner: tools) *//*** The answer of POST /api/v1/tools/{id}/run (tools.run-request@1; ADR-027). Finished: { state: succeeded, tool, result, took_ms } or { state: failed|cancelled, tool, error, took_ms }, with job set when the tool ran as a job. Not finished within wait_ms: { state: queued|running, tool, job, location }, sent with 202 and a Location header; follow the job there (GET, its events, its files; tools.job@1). result carries data (kind json, matching the descriptor's output schema), text (kind text) or files (kind file or files: the job's result files, downloaded from their url). took_ms runs from the request to the answer for an inline run, and from created_at to finished_at for a job. error is problem+json from the tool: tools.job.failed, tools.job.timeout, tools.job.cancelled, or a code the tool chose (tools.…), such as 413 tools.pdf.too_many_pages or 422 tools.pdf.wrong_password; its status is the problem's, not the HTTP answer's. A request refused before the tool ran is a problem+json answer, not a run. Also: 413 tools.input.too_large (the input exceeded a worker's memory or size limit), 504 tools.run.timeout (the run passed the descriptor's timeoutMs), 403 tools.origin.refused (a cookie-authenticated mutation from a foreign Origin), 401 tools.session_required (the tool is not anonymous and the call has no browser session or token), 404 tools.run.file_not_found (a files reference names no result the caller may read), 500 tools.run.failed (the engine failed without a more specific code).@@@ -2733,7 +2730,6 @@ export type ToolsRun =*/location: string;};--/** [email protected] (owner: tools) *//*** A Tools job as the jobs API answers it (OpenVibe.Tools apps/_shared/jobs/http.js, system.view; ADR-027): POST /api/v1/jobs (202 + Location, or 200 + Idempotent-Replayed: true), GET and DELETE /api/v1/jobs/:id, POST /api/v1/jobs/:id/retry, PUT and DELETE /api/v1/jobs/:id/references/:ref, the data of every SSE event on GET /api/v1/jobs/:id/events, and the job member of tools.run@1. A job is visible only to its owner (a person, a service/app principal, or one browser session); anyone else gets the same 404 tools.job.not_found as for a job that does not exist. result is null until the job succeeded; each result file is downloaded from its url (GET /api/v1/jobs/:id/files/:n, ?inline=1 for previews) and is stored as a private OpenVibe.Media object (storage media) or on the satellite until the job expires (storage local). expires_at is null while a reference keeps the result. error is problem+json: tools.job.failed, tools.job.timeout, tools.job.cancelled, or a code the tool chose (tools.…), such as 413 tools.pdf.too_many_pages or 422 tools.pdf.wrong_password, with server paths removed.@@@ -2853,7 +2849,6 @@ export interface ToolsJob {retried_by: string | null;};}--/** [email protected] (owner: tools) *//*** Body of POST /api/v1/jobs on the Tools satellites that run jobs (img, audio, docs; capability tools.job.create; OpenVibe.Tools apps/_shared/jobs/http.js). JSON, or multipart/form-data with the same fields as text parts (input as JSON text) and the input files as file or files parts. The Idempotency-Key header wins over idempotency_key: the same (owner, key) and request returns the same job (200 + Idempotent-Replayed: true), a different request under it is 409 tools.job.idempotency_conflict. Types: img.process (input { tool: convert|compress|resize|crop, … }, one image), audio.process ({ tool, … } as /api/process takes them, one audio or video file), docs.process ({ tool, … }, one PDF, or several files for merge and img2pdf). A format host fills in its format (webp.openvibe.tools converts to WebP). Answers tools.job@1 (202 + Location). Refusals are problem+json: 400 tools.job.invalid (input not a JSON object or over 16 KB, wrong file count, the type's own validation) or tools.job.unknown_type, 401 token.* or tools.job.no_owner, 403 capability.denied, 413 tools.job.too_large, 429 tools.job.too_many_active (unfinished jobs per owner), tools.quota.exceeded (Retry-After; the owner's allowance is spent) or a rate limit, 503 tools.job.unavailable, 503 tools.unavailable (a program or upstream the job type needs is missing), 503 tools.busy (Retry-After; every slot for this kind of work is taken). A tool that refuses its input after the job was accepted fails the job with its own code (413 tools.pdf.too_many_pages, 422 tools.pdf.wrong_password…; tools.job@1 error).@@@ -333,6 +333,10 @@ export type ExchangeEarnings = {};export type ExchangeClient = {++ arena: {++ start(input: { prompt: string; category?: string; entries: unknown[] }, options?: { signal?: AbortSignal }): Promise<unknown>;++ vote(roundId: string, choice: 'left' | 'right' | 'tie' | 'both_bad', options?: { signal?: AbortSignal }): Promise<unknown>;++ };/** One chat completion; stream: true answers an async iterable of chunks. */chat(body: ExchangeChatRequest, opts?: { signal?: AbortSignal }): Promise<ExchangeChatCompletion | AsyncIterable<ExchangeChatChunk>>;/** The models the Exchange can serve. */@@@ -1,5 +1,7 @@export * from './core';export * as auth from './auth';++export * as report from './report';++export * as report from './report';export * as registry from './registry';export * as identity from './identity';export * as trust from './trust';nnew file mode 100644@@@ -0,0 +1,11 @@++import type { OpenVibeClient } from './core';++export interface ReportAI {++ leaderboard(opts?: { kind?: string; maker?: string; weights?: 'open' | 'closed'; price?: string; status?: 'current' | 'all'; uncensored?: boolean; community?: boolean; sort?: string; limit?: number; cursor?: string }, options?: { signal?: AbortSignal }): Promise<unknown>;++ model(id: string, options?: { signal?: AbortSignal }): Promise<unknown>;++ providers(options?: { signal?: AbortSignal }): Promise<unknown>;++ compare(ids: string | string[], options?: { signal?: AbortSignal }): Promise<unknown>;++ picks(options?: { signal?: AbortSignal }): Promise<unknown>;++}++export interface ReportClient { ai: ReportAI }++export declare function createReportClient(client: Pick<OpenVibeClient, 'json'>, options?: { baseUrl?: string }): ReportClient;++export declare const REPORT_URL: string;
@@ -1,5 +1,9 @@
@@ -1,5 +1,9 @@
# Changelog
# Changelog
+## 0.105.0 (2026-11-09)
+
+- Add Report AI leaderboard, model, provider, comparison and picks reads, plus Exchange Arena start and vote methods.
+
## 0.104.0 (2026-11-08)
## 0.104.0 (2026-11-08)
- Add `createAccountEmailsClient` to `openvibe-sdk/identity` for managing the signed-in person's verified Network email addresses.
- Add `createAccountEmailsClient` to `openvibe-sdk/identity` for managing the signed-in person's verified Network email addresses.
@@ -2,7 +2,7 @@
@@ -2,7 +2,7 @@
> Supported browser and server clients for the OpenVibe platform.
> Supported browser and server clients for the OpenVibe platform.
-**Status:** alpha, v0.104.0 (the five 1.0 core entries — `core`, `auth`, `events`, `db` and `media` — are frozen: their exported names change only with a major release; roadmap Wave 2; developer apps from Wave 20; Media objects v2 from Wave 4; the Tools platform API; Search, Chat, AI, Vision and consent clients; sign-in with recovery and a client health check). Every release is tagged (see [CHANGELOG.md](CHANGELOG.md)). Tested against local stub servers and the built-in mock platform only, never against the live platform. Services pin release tags; each repository's `STATUS.json` names the one it uses.
+**Status:** alpha, v0.105.0 (the five 1.0 core entries — `core`, `auth`, `events`, `db` and `media` — are frozen: their exported names change only with a major release; roadmap Wave 2; developer apps from Wave 20; Media objects v2 from Wave 4; the Tools platform API; Search, Chat, AI, Vision and consent clients; sign-in with recovery and a client health check). Every release is tagged (see [CHANGELOG.md](CHANGELOG.md)). Tested against local stub servers and the built-in mock platform only, never against the live platform. Services pin release tags; each repository's `STATUS.json` names the one it uses.
**Plan:** OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026), §3.2; roadmap §30.
**Plan:** OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026), §3.2; roadmap §30.
**License:** MIT ([LICENSE](LICENSE)). This package is a library that apps outside the network embed, so it uses MIT. The OpenVibe services themselves stay AGPL-3.0.
**License:** MIT ([LICENSE](LICENSE)). This package is a library that apps outside the network embed, so it uses MIT. The OpenVibe services themselves stay AGPL-3.0.
@@ -11,7 +11,7 @@
@@ -11,7 +11,7 @@
After v0.97.1 is tagged:
After v0.97.1 is tagged:
```bash
```bash
-npm install https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.104.0
+npm install https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.105.0
```
```
It has no runtime dependencies. It needs Node ≥ 20, or any browser with `fetch`, Web Crypto and `TextDecoder`. There is no build step. The package is CommonJS with ESM entry points (`import` works). Each subpath has its own `.d.ts`. For a page with no bundler, `browser/openvibe-sdk.mjs` is one self-contained ES module of the browser-safe subpaths (see [Browser without a bundler](#browser-without-a-bundler)). It does not depend on `openvibe-contracts` at runtime: it copies the contract types it uses (from the temporary Contracts sharing branch, a devDependency the tests check them against).
It has no runtime dependencies. It needs Node ≥ 20, or any browser with `fetch`, Web Crypto and `TextDecoder`. There is no build step. The package is CommonJS with ESM entry points (`import` works). Each subpath has its own `.d.ts`. For a page with no bundler, `browser/openvibe-sdk.mjs` is one self-contained ES module of the browser-safe subpaths (see [Browser without a bundler](#browser-without-a-bundler)). It does not depend on `openvibe-contracts` at runtime: it copies the contract types it uses (from the temporary Contracts sharing branch, a devDependency the tests check them against).
@@ -632,3 +632,18 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by [
@@ -632,3 +632,18 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by [
### Locker: end-to-end encryption (Node and the browser)
### Locker: end-to-end encryption (Node and the browser)
`openvibe-sdk/locker` seals anything and opens it again: the Locker envelope (`locker.envelope@1`), the three locks (people, password, link), streams for files, person key pairs and the eight-word fingerprint, `wrapFor` and `rewrap` for sharing, `recoveryWords` for the phrase to write down, `inspect` for what an envelope says about itself, and opening a people lock by key. `openvibe-sdk/locker/server` gives a service the header checks without a key. WebCrypto only, in Node and the browser. See [the locker module](docs/locker.md).
`openvibe-sdk/locker` seals anything and opens it again: the Locker envelope (`locker.envelope@1`), the three locks (people, password, link), streams for files, person key pairs and the eight-word fingerprint, `wrapFor` and `rewrap` for sharing, `recoveryWords` for the phrase to write down, `inspect` for what an envelope says about itself, and opening a people lock by key. `openvibe-sdk/locker/server` gives a service the header checks without a key. WebCrypto only, in Node and the browser. See [the locker module](docs/locker.md).
+# AI comparison and Arena
+
+Read Report's public AI data and create or vote on Exchange Arena rounds:
+
+```js
+const ov = require('openvibe-sdk');
+const client = ov.createClient();
+const report = ov.report.createReportClient(client);
+const leaders = await report.ai.leaderboard({ kind: 'code', limit: 25 });
+const headToHead = await report.ai.compare(['openai/gpt-4o', 'anthropic/claude-sonnet']);
+
+const exchange = require('openvibe-sdk/exchange').createExchangeClient({ developerKey });
+const round = await exchange.arena.start({ prompt: 'Explain a binary search.', category: 'code', entries });
+await exchange.arena.vote(round.id, 'left');
+```
@@ -3,7 +3,7 @@
@@ -3,7 +3,7 @@
"stage": "alpha",
"stage": "alpha",
"code": true,
"code": true,
"wave": "W2",
"wave": "W2",
- "version": "0.104.0",
+ "version": "0.105.0",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026), §3.2; roadmap §30",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026), §3.2; roadmap §30",
"deployed": false,
"deployed": false,
"deployment": "none: a package released as git tags; services and apps pin a tag",
"deployment": "none: a package released as git tags; services and apps pin a tag",
@@ -1,4 +1,4 @@
@@ -1,4 +1,4 @@
-// Generated by scripts/browser-bundle.js from browser.js (openvibe-sdk 0.104.0, MIT). Do not edit.
+// Generated by scripts/browser-bundle.js from browser.js (openvibe-sdk 0.105.0, MIT). Do not edit.
// One self-contained ES module: core at the top level plus the browser-safe subpaths as namespaces
// One self-contained ES module: core at the top level plus the browser-safe subpaths as namespaces
// (auth, locker, registry, trust, modules, realtime, media, community, bot, search, bench, meme, vision, themes, redact, chat, ai, credentials, jobs, tools, projects, geo). No server-only code: no service tokens, JWT verification, events, identity.
// (auth, locker, registry, trust, modules, realtime, media, community, bot, search, bench, meme, vision, themes, redact, chat, ai, credentials, jobs, tools, projects, geo). No server-only code: no service tokens, JWT verification, events, identity.
/* eslint-disable */
/* eslint-disable */
@@ -50,7 +50,7 @@ const { paginate, offsetPager } = __load(7);
@@ -50,7 +50,7 @@ const { paginate, offsetPager } = __load(7);
const semver = __load(6);
const semver = __load(6);
/** The package version (test/version.test.js keeps it equal to package.json). */
/** The package version (test/version.test.js keeps it equal to package.json). */
-const SDK_VERSION = '0.104.0';
+const SDK_VERSION = '0.105.0';
module.exports = {
module.exports = {
createClient,
createClient,
@@ -290,6 +290,12 @@
@@ -290,6 +290,12 @@
"createRegistryClient"
"createRegistryClient"
]
]
},
},
+ "report": {
+ "object": [
+ "REPORT_URL",
+ "createReportClient"
+ ]
+ },
"resources": {
"resources": {
"object": [
"object": [
"RESOURCE_KINDS",
"RESOURCE_KINDS",
@@ -927,6 +933,10 @@
@@ -927,6 +933,10 @@
"openvibe-sdk/registry": {
"openvibe-sdk/registry": {
"createRegistryClient": "function"
"createRegistryClient": "function"
},
},
+ "openvibe-sdk/report": {
+ "REPORT_URL": "string",
+ "createReportClient": "function"
+ },
"openvibe-sdk/reports": {
"openvibe-sdk/reports": {
"cleanReport": "function",
"cleanReport": "function",
"createReportsClient": "function"
"createReportsClient": "function"
@@ -4,15 +4,15 @@ import cjs from '../index.js';
@@ -4,15 +4,15 @@ import cjs from '../index.js';
export const {
export const {
createClient, OpenVibeError, isOpenVibeError, paginate, offsetPager, parseTraceparent,
createClient, OpenVibeError, isOpenVibeError, paginate, offsetPager, parseTraceparent,
startSpan, contextFromHeaders, ulid, newEventId, newIdempotencyKey, isActingSubjectId,
startSpan, contextFromHeaders, ulid, newEventId, newIdempotencyKey, isActingSubjectId,
- satisfiesRange, compareVersions, CONTRACTS_RANGE, DEFAULT_NETWORK, SDK_VERSION, share,
- locker, auth, registry, identity, trust, modules,
- events, realtime, media, community, bot, search,
- bench, meme, vision, themes, redact, chat,
- chatApps, ai, credentials, jobs, tools, projects,
- resources, vip, quest, live, inventory, tips,
- blog, reviews, wiki, coupons, deals, trade,
- space, host, news, food, actor, run,
- video, work, zone, play, download, openre,
- geo, usage, commerce, telemetry,
+ satisfiesRange, compareVersions, CONTRACTS_RANGE, DEFAULT_NETWORK, SDK_VERSION, report,
+ share, locker, auth, registry, identity, trust,
+ modules, events, realtime, media, community, bot,
+ search, bench, meme, vision, themes, redact,
+ chat, chatApps, ai, credentials, jobs, tools,
+ projects, resources, vip, quest, live, inventory,
+ tips, blog, reviews, wiki, coupons, deals,
+ trade, space, host, news, food, actor,
+ run, video, work, zone, play, download,
+ openre, geo, usage, commerce, telemetry,
} = cjs;
} = cjs;
export default cjs;
export default cjs;
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+// Generated by scripts/esm.js from src/report.js. Do not edit.
+import cjs from '../src/report.js';
+
+export const {
+ createReportClient, REPORT_URL,
+} = cjs;
+export default cjs;
@@ -8,6 +8,7 @@ const core = require('./src/core');
@@ -8,6 +8,7 @@ const core = require('./src/core');
module.exports = {
module.exports = {
...core,
...core,
+ report: require('./src/report'),
share: require('./src/share').share,
share: require('./src/share').share,
locker: require('./src/locker'),
locker: require('./src/locker'),
auth: require('./src/auth'),
auth: require('./src/auth'),
@@ -1,12 +1,12 @@
@@ -1,12 +1,12 @@
{
{
"name": "openvibe-sdk",
"name": "openvibe-sdk",
- "version": "0.104.0",
+ "version": "0.105.0",
"lockfileVersion": 3,
"lockfileVersion": 3,
"requires": true,
"requires": true,
"packages": {
"packages": {
"": {
"": {
"name": "openvibe-sdk",
"name": "openvibe-sdk",
- "version": "0.104.0",
+ "version": "0.105.0",
"license": "MIT",
"license": "MIT",
"bin": {
"bin": {
"openvibe-download": "bin/openvibe-download.js"
"openvibe-download": "bin/openvibe-download.js"
@@ -14,7 +14,7 @@
@@ -14,7 +14,7 @@
"devDependencies": {
"devDependencies": {
"@electric-sql/pglite": "^0.5.8",
"@electric-sql/pglite": "^0.5.8",
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.338.0",
+ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.351.0",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
"engines": {
"engines": {
@@ -198,9 +198,9 @@
@@ -198,9 +198,9 @@
"license": "MIT"
"license": "MIT"
},
},
"node_modules/openvibe-contracts": {
"node_modules/openvibe-contracts": {
- "version": "0.338.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.338.0",
- "integrity": "sha512-75FD6IxMsXoud3NFXKDs0/D0rhJtF2D+WL8M92ArIyabkUnM/sZvw6E6L97EqwQ3O7M/kB+uwsNSxGudbil+ng==",
+ "version": "0.351.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.351.0",
+ "integrity": "sha512-XfI7OqI78iYHCJ/+n++R0Zrot6OR/dH1DMdL1guZdEX6x/YJ3UF3FCmRjljk4r0GEXmrUlNr1iL6CR9Jfx0uOg==",
"dev": true,
"dev": true,
"license": "AGPL-3.0-only",
"license": "AGPL-3.0-only",
"dependencies": {
"dependencies": {
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-sdk",
"name": "openvibe-sdk",
- "version": "0.104.0",
+ "version": "0.105.0",
"description": "Supported browser and server clients for the OpenVibe platform: auth (incl. developer apps), registry discovery, identity, user modules, events, realtime, media, community, Bot robots, search, chat, AI runs, Tools (registry, run API and jobs), VIP memberships and developer projects, the async PostgreSQL data layer (db) and the Valkey cache, limits store, queue and pub/sub, with deadlines, safe retries, problem errors and trace propagation.",
"description": "Supported browser and server clients for the OpenVibe platform: auth (incl. developer apps), registry discovery, identity, user modules, events, realtime, media, community, Bot robots, search, chat, AI runs, Tools (registry, run API and jobs), VIP memberships and developer projects, the async PostgreSQL data layer (db) and the Valkey cache, limits store, queue and pub/sub, with deadlines, safe retries, problem errors and trace propagation.",
"main": "index.js",
"main": "index.js",
"types": "types/index.d.ts",
"types": "types/index.d.ts",
@@ -198,6 +198,11 @@
@@ -198,6 +198,11 @@
"import": "./esm/exchange.mjs",
"import": "./esm/exchange.mjs",
"default": "./src/exchange.js"
"default": "./src/exchange.js"
},
},
+ "./report": {
+ "types": "./types/report.d.ts",
+ "import": "./esm/report.mjs",
+ "default": "./src/report.js"
+ },
"./delivery": {
"./delivery": {
"types": "./types/delivery.d.ts",
"types": "./types/delivery.d.ts",
"browser": null,
"browser": null,
@@ -573,7 +578,7 @@
@@ -573,7 +578,7 @@
"devDependencies": {
"devDependencies": {
"@electric-sql/pglite": "^0.5.8",
"@electric-sql/pglite": "^0.5.8",
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.338.0",
+ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.351.0",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
"peerDependencies": {
"peerDependencies": {
@@ -52,6 +52,7 @@ const ENTRIES = {
@@ -52,6 +52,7 @@ const ENTRIES = {
'storage.mjs': '../src/storage.js',
'storage.mjs': '../src/storage.js',
'commerce.mjs': '../src/commerce.js',
'commerce.mjs': '../src/commerce.js',
'exchange.mjs': '../src/exchange.js',
'exchange.mjs': '../src/exchange.js',
+ 'report.mjs': '../src/report.js',
'vibes.mjs': '../src/vibes.js',
'vibes.mjs': '../src/vibes.js',
'delivery.mjs': '../src/delivery.js',
'delivery.mjs': '../src/delivery.js',
'node.mjs': '../src/node.js',
'node.mjs': '../src/node.js',
@@ -8,7 +8,7 @@ const { paginate, offsetPager } = require('./paginate');
@@ -8,7 +8,7 @@ const { paginate, offsetPager } = require('./paginate');
const semver = require('./semver');
const semver = require('./semver');
/** The package version (test/version.test.js keeps it equal to package.json). */
/** The package version (test/version.test.js keeps it equal to package.json). */
-const SDK_VERSION = '0.104.0';
+const SDK_VERSION = '0.105.0';
module.exports = {
module.exports = {
createClient,
createClient,
@@ -541,6 +541,20 @@ function createExchangeClient({
@@ -541,6 +541,20 @@ function createExchangeClient({
};
};
return {
return {
+ arena: {
+ /** Start a blind comparison round using the caller's prompt and pair. */
+ async start({ prompt, category = 'chat', entries } = {}, { signal } = {}) {
+ if (typeof prompt !== 'string' || !prompt.trim()) throw new TypeError('arena.start: prompt is required');
+ if (!Array.isArray(entries) || entries.length < 2) throw new TypeError('arena.start: at least two entries are required');
+ return await jsonCall('POST', '/api/v1/arena/rounds', { json: { prompt, category, entries }, signal });
+ },
+ /** Submit a blind vote. choice is left, right, tie or both_bad. */
+ async vote(roundId, choice, { signal } = {}) {
+ if (!roundId) throw new TypeError('arena.vote: roundId is required');
+ if (!['left', 'right', 'tie', 'both_bad'].includes(choice)) throw new TypeError('arena.vote: choice must be left, right, tie or both_bad');
+ return await jsonCall('POST', `/api/v1/arena/rounds/${encodeURIComponent(roundId)}/votes`, { json: { outcome: choice }, signal });
+ },
+ },
/**
/**
* One chat completion. The body is the OpenAI shape; stream: true answers an async iterable of
* One chat completion. The body is the OpenAI shape; stream: true answers an async iterable of
* chunks instead of the finished completion.
* chunks instead of the finished completion.
new file mode 100644
new file mode 100644
@@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
+'use strict';
+
+const REPORT_URL = 'https://openvibe.report';
+function createReportClient(client, { baseUrl = REPORT_URL } = {}) {
+ if (!client || typeof client.json !== 'function') throw new TypeError('createReportClient: pass an openvibe-sdk/core client');
+ const call = (path, query, signal) => client.json({ baseUrl, path, query, signal, service: 'report' });
+ return { ai: {
+ leaderboard(opts = {}, { signal } = {}) { return call('/api/v1/ai/leaderboard', opts, signal); },
+ model(id, { signal } = {}) { if (!id) throw new TypeError('report.ai.model: id is required'); return call(`/api/v1/ai/models/${encodeURIComponent(id)}`, null, signal); },
+ providers({ signal } = {}) { return call('/api/v1/ai/providers', null, signal); },
+ compare(ids, { signal } = {}) { const models = (Array.isArray(ids) ? ids : [ids]).map(String).join(','); return call('/api/v1/ai/compare', { models }, signal); },
+ picks({ signal } = {}) { return call('/api/v1/ai/picks', null, signal); },
+ } };
+}
+module.exports = { createReportClient, REPORT_URL };
@@ -72,7 +72,7 @@ run([
@@ -72,7 +72,7 @@ run([
})().catch((err) => { console.error(err); process.exit(1); });`;
})().catch((err) => { console.error(err); process.exit(1); });`;
const res = execFileSync(process.execPath, ['-e', script], { cwd: app, encoding: 'utf8' });
const res = execFileSync(process.execPath, ['-e', script], { cwd: app, encoding: 'utf8' });
assert.match(res, new RegExp(`consumer ok: ${subpaths.length} subpaths \\+ the browser bundle`));
assert.match(res, new RegExp(`consumer ok: ${subpaths.length} subpaths \\+ the browser bundle`));
- assert.equal(subpaths.length, 91); // Adds the browser-safe trust client (0.102.0) and the AI credentials client.
+ assert.equal(subpaths.length, 92); // Adds the Report AI comparison client (0.105.0); 91 had the trust and AI credentials clients.
assert.ok(!fs.existsSync(path.join(app, 'node_modules/openvibe-contracts')), 'the optional peer is not installed');
assert.ok(!fs.existsSync(path.join(app, 'node_modules/openvibe-contracts')), 'the optional peer is not installed');
} finally {
} finally {
fs.rmSync(tmp, { recursive: true, force: true });
fs.rmSync(tmp, { recursive: true, force: true });
new file mode 100644
new file mode 100644
@@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
+'use strict';
+const assert = require('node:assert/strict');
+const { createReportClient } = require('../src/report');
+const { createExchangeClient } = require('../src/exchange');
+
+(async () => {
+ const calls = [];
+ const client = { json: async (opts) => { calls.push(opts); return {}; } };
+ const report = createReportClient(client, { baseUrl: 'https://report.test' });
+ await report.ai.leaderboard({ kind: 'code', limit: 10 });
+ await report.ai.model('org/model');
+ await report.ai.providers();
+ await report.ai.compare(['a', 'b']);
+ await report.ai.picks();
+ assert.equal(calls.length, 5);
+ assert.equal(calls[0].path, '/api/v1/ai/leaderboard');
+ assert.deepEqual(calls[3].query, { models: 'a,b' });
+ assert.throws(() => report.ai.model(''), /id is required/);
+
+ const requests = [];
+ const exchange = createExchangeClient({ developerKey: 'test-key', baseUrl: 'https://exchange.test', fetchImpl: async (url, options) => {
+ requests.push({ url: String(url), options });
+ return new Response('{}', { status: 200, headers: { 'content-type': 'application/json' } });
+ }, maxRetries: 0 });
+ await exchange.arena.start({ prompt: 'Compare', entries: [{ id: 'a' }, { id: 'b' }] });
+ await exchange.arena.vote('rnd_1', 'left');
+ assert.match(requests[0].url, /\/api\/v1\/arena\/rounds$/);
+ assert.match(requests[1].url, /\/api\/v1\/arena\/rounds\/rnd_1\/votes$/);
+ await assert.rejects(exchange.arena.vote('rnd_1', 'bad'), /choice must be/);
+ console.log('Report AI and Exchange Arena SDK tests passed');
+})().catch((error) => { console.error(error); process.exitCode = 1; });
@@ -32,7 +32,6 @@ export type SubjectRef =
@@ -32,7 +32,6 @@ export type SubjectRef =
type: "agent";
type: "agent";
id: string;
id: string;
};
};
-
/** [email protected] (owner: network) */
/** [email protected] (owner: network) */
/**
/**
* Claims of a short-lived RS256 client-credentials token issued by OpenVibe.Network to a service, app, mod, node or agent principal. Replaces X-Internal-Key. App tokens (actor_type app) also carry project_id and env; receivers refuse env=sandbox unless they opted in. Agent tokens (actor_type agent, sub agent:agt_<ULID>) are minted for the agent's host and also carry project_id, env and on_behalf_of (the owner), and optionally cap_confirm and act.
* Claims of a short-lived RS256 client-credentials token issued by OpenVibe.Network to a service, app, mod, node or agent principal. Replaces X-Internal-Key. App tokens (actor_type app) also carry project_id and env; receivers refuse env=sandbox unless they opted in. Agent tokens (actor_type agent, sub agent:agt_<ULID>) are minted for the agent's host and also carry project_id, env and on_behalf_of (the owner), and optionally cap_confirm and act.
@@ -108,7 +107,6 @@ export type ServiceTokenClaims = {
@@ -108,7 +107,6 @@ export type ServiceTokenClaims = {
};
};
[k: string]: unknown | undefined;
[k: string]: unknown | undefined;
};
};
-
/** [email protected] (owner: contracts) */
/** [email protected] (owner: contracts) */
/**
/**
* Typed reference to another service's entity. Store this instead of a foreign key; resolve a display projection from the owner.
* Typed reference to another service's entity. Store this instead of a foreign key; resolve a display projection from the owner.
@@ -123,7 +121,6 @@ export interface EntityRef {
@@ -123,7 +121,6 @@ export interface EntityRef {
*/
*/
label?: string;
label?: string;
}
}
-
/** [email protected] (owner: contracts) */
/** [email protected] (owner: contracts) */
/**
/**
* RFC 9457 problem details (application/problem+json) with a stable OpenVibe error code and trace identifiers. The legacy 'error' string is allowed during migration so existing clients keep working.
* RFC 9457 problem details (application/problem+json) with a stable OpenVibe error code and trace identifiers. The legacy 'error' string is allowed during migration so existing clients keep working.
@@ -147,7 +144,6 @@ export interface Problem {
@@ -147,7 +144,6 @@ export interface Problem {
error?: string;
error?: string;
[k: string]: unknown | undefined;
[k: string]: unknown | undefined;
}
}
-
/** [email protected] (owner: network) */
/** [email protected] (owner: network) */
/**
/**
* What a service is, where it lives and what it offers. The ecosystem registry is built from these, not from hard-coded route maps.
* What a service is, where it lives and what it offers. The ecosystem registry is built from these, not from hard-coded route maps.
@@ -161,6 +157,16 @@ export interface ServiceManifest {
@@ -161,6 +157,16 @@ export interface ServiceManifest {
domains: string[];
domains: string[];
publicOrigin?: string;
publicOrigin?: string;
health?: string;
health?: string;
+ /**
+ * Public HTTP routes implemented by this service, with their response contract where one exists.
+ */
+ routes?: {
+ method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
+ path: string;
+ contract?: string;
+ cacheSeconds?: number;
+ cors?: string;
+ }[];
ready?: string;
ready?: string;
/**
/**
* Where the service answers on its host (loopback only): the address the registry polls health and readiness from. A host inventory or an OV_<ID>_INTERNAL_URL loopback override may replace it; a service with nothing to run has none.
* Where the service answers on its host (loopback only): the address the registry polls health and readiness from. A host inventory or an OV_<ID>_INTERNAL_URL loopback override may replace it; a service with nothing to run has none.
@@ -2184,7 +2190,6 @@ export interface ServiceManifest {
@@ -2184,7 +2190,6 @@ export interface ServiceManifest {
};
};
notes?: string;
notes?: string;
}
}
-
/** [email protected] (owner: network) */
/** [email protected] (owner: network) */
/**
/**
* An action a principal may invoke. Authorization is checked at the owner's boundary against the grant, never inferred from the caller's route.
* An action a principal may invoke. Authorization is checked at the owner's boundary against the grant, never inferred from the caller's route.
@@ -2211,7 +2216,6 @@ export interface Capability {
@@ -2211,7 +2216,6 @@ export interface Capability {
*/
*/
sensitive?: boolean;
sensitive?: boolean;
}
}
-
/** [email protected] (owner: events) */
/** [email protected] (owner: events) */
/**
/**
* Durable platform event (OpenVibe.Events, Wave 3). Producers write it to an outbox in the same transaction as the domain change.
* Durable platform event (OpenVibe.Events, Wave 3). Producers write it to an outbox in the same transaction as the domain change.
@@ -2234,7 +2238,6 @@ export interface EventEnvelope {
@@ -2234,7 +2238,6 @@ export interface EventEnvelope {
};
};
payload: {};
payload: {};
}
}
-
/** [email protected] (owner: network) */
/** [email protected] (owner: network) */
/**
/**
* Policy for one user-module namespace: portable per-subject summaries and preferences stored by OpenVibe.Network. Never domain truth, money or authoritative game inventory (roadmap 4.3-4.5). The person's account decides what happens to their records in every namespace: when an account is removed, Network deletes all of its records; when two accounts are merged (ADR-029), in a namespace where both have a record the survivor keeps its own and gains only the top-level fields it lacks from the other (its own values always win), a record only the absorbed account had moves to the survivor, and the absorbed account's records are deleted. Each of these changes is announced as network.module.updated (reason subject_removed or subject_merged). That is separate from onOwnerRemoved, which says what happens to a namespace's records when the service that owns the namespace is retired.
* Policy for one user-module namespace: portable per-subject summaries and preferences stored by OpenVibe.Network. Never domain truth, money or authoritative game inventory (roadmap 4.3-4.5). The person's account decides what happens to their records in every namespace: when an account is removed, Network deletes all of its records; when two accounts are merged (ADR-029), in a namespace where both have a record the survivor keeps its own and gains only the top-level fields it lacks from the other (its own values always win), a record only the absorbed account had moves to the survivor, and the absorbed account's records are deleted. Each of these changes is announced as network.module.updated (reason subject_removed or subject_merged). That is separate from onOwnerRemoved, which says what happens to a namespace's records when the service that owns the namespace is retired.
@@ -2293,7 +2296,6 @@ export interface ModuleNamespace {
@@ -2293,7 +2296,6 @@ export interface ModuleNamespace {
defaults?: {};
defaults?: {};
}[];
}[];
}
}
-
/** [email protected] (owner: network) */
/** [email protected] (owner: network) */
/**
/**
* One subject's value in one namespace, as returned by Network. revision increases by one on every write; a write names the revision it read (If-Match) and fails with 412 if it moved.
* One subject's value in one namespace, as returned by Network. revision increases by one on every write; a write names the revision it read (If-Match) and fails with 412 if it moved.
@@ -2310,7 +2312,6 @@ export interface ModuleRecord {
@@ -2310,7 +2312,6 @@ export interface ModuleRecord {
*/
*/
updated_by?: string;
updated_by?: string;
}
}
-
/** [email protected] (owner: codes) */
/** [email protected] (owner: codes) */
/**
/**
* PROPOSAL (OpenVibe.Codes, roadmap Wave 20): an app release as the platform knows it. The app is a Network developer app (ADR-014): its id is the Network app id, its capabilities are only a request (the grants in Network are the authority), and trust tiers are metadata that never change a grant check. OpenVibe.Codes validates with this schema, loaded into openvibe-contracts' validator, until Contracts publishes it as codes.app-manifest@1.
* PROPOSAL (OpenVibe.Codes, roadmap Wave 20): an app release as the platform knows it. The app is a Network developer app (ADR-014): its id is the Network app id, its capabilities are only a request (the grants in Network are the authority), and trust tiers are metadata that never change a grant check. OpenVibe.Codes validates with this schema, loaded into openvibe-contracts' validator, until Contracts publishes it as codes.app-manifest@1.
@@ -2383,7 +2384,6 @@ export interface AppManifest {
@@ -2383,7 +2384,6 @@ export interface AppManifest {
sdk?: string;
sdk?: string;
};
};
}
}
-
/** [email protected] (owner: tools) */
/** [email protected] (owner: tools) */
/**
/**
* One tool on openvibe.tools as its registry describes it (GET /api/v1/tools/:id, and each item of GET /api/v1/tools; capability tools.tool.read; ADR-027). There is one descriptor per catalogue tool (GET /api/catalog.json tools[].id), built from the tool's own code, so its page, the run API, openvibe-sdk/tools, the OpenAPI document and the docs all read the same facts. EXECUTION is where the tool's engine runs for its page: client (in the browser; nothing leaves it), sync (a server request answered inline) or job (an asynchronous job on a satellite, tools.job@1). API says whether POST /api/v1/tools/{id}/run exposes the tool. A client tool has api true only when it also has a server engine (a pure transform that runs in Node), and its page stays browser-only. A page-only tool (yt) has api false and run null. A job tool's run creates a job of run.job.type whose input is { ...input, ...run.job.preset, tool: run.job.operation } (the preset and the operation always win). INPUT is the JSON Schema (2020-12) that a run request's input must match, embedded or as { $ref: https://openvibe.tools/api/v1/tools/{id}/schema#/$defs/input }. GET /api/v1/tools/:id/schema answers { $schema, $id, $defs: { input, output } }, and the list uses the $ref form. When api is false, $defs.input is false (the schema that accepts nothing), since there is no run to take an input. OUTPUT.SCHEMA describes result.data for every execution: inline and job tools alike. KEYWORDS (the catalogue's search terms) and EXAMPLES (sample runs for the docs and the OpenAPI document) are optional. NOT A TOOL: there is no planned status. A planned catalogue entry has no descriptor and is not listed, and neither is a mirror (a second build of another tool). GET /api/v1/tools/:id and /schema answer 404 tools.tool.not_found for either, with a detail that says the id is planned or names the tool it mirrors, as for an id that does not exist. RULES this schema enforces: api false has no run and no examples, and api true has a run and an input; an API job tool names its job, and only job tools name one; an API tool whose output is a file runs as a job (its results are served as job files); a tools.net.probe tool fetches (egress) and is never anonymous; an anonymous egress tool has a per-target throttle (limits.perTargetPerMinute); a client tool never fetches; an unavailable tool says why (statusReason); JSON output has a schema. contracts.tools.checkDescriptor(d) also checks what depends on the id (run.path, $ref targets), files.min <= files.max, and each example: its input against an embedded input schema and limits.maxInputBytes, and its files against files (count and accept).
* One tool on openvibe.tools as its registry describes it (GET /api/v1/tools/:id, and each item of GET /api/v1/tools; capability tools.tool.read; ADR-027). There is one descriptor per catalogue tool (GET /api/catalog.json tools[].id), built from the tool's own code, so its page, the run API, openvibe-sdk/tools, the OpenAPI document and the docs all read the same facts. EXECUTION is where the tool's engine runs for its page: client (in the browser; nothing leaves it), sync (a server request answered inline) or job (an asynchronous job on a satellite, tools.job@1). API says whether POST /api/v1/tools/{id}/run exposes the tool. A client tool has api true only when it also has a server engine (a pure transform that runs in Node), and its page stays browser-only. A page-only tool (yt) has api false and run null. A job tool's run creates a job of run.job.type whose input is { ...input, ...run.job.preset, tool: run.job.operation } (the preset and the operation always win). INPUT is the JSON Schema (2020-12) that a run request's input must match, embedded or as { $ref: https://openvibe.tools/api/v1/tools/{id}/schema#/$defs/input }. GET /api/v1/tools/:id/schema answers { $schema, $id, $defs: { input, output } }, and the list uses the $ref form. When api is false, $defs.input is false (the schema that accepts nothing), since there is no run to take an input. OUTPUT.SCHEMA describes result.data for every execution: inline and job tools alike. KEYWORDS (the catalogue's search terms) and EXAMPLES (sample runs for the docs and the OpenAPI document) are optional. NOT A TOOL: there is no planned status. A planned catalogue entry has no descriptor and is not listed, and neither is a mirror (a second build of another tool). GET /api/v1/tools/:id and /schema answer 404 tools.tool.not_found for either, with a detail that says the id is planned or names the tool it mirrors, as for an id that does not exist. RULES this schema enforces: api false has no run and no examples, and api true has a run and an input; an API job tool names its job, and only job tools name one; an API tool whose output is a file runs as a job (its results are served as job files); a tools.net.probe tool fetches (egress) and is never anonymous; an anonymous egress tool has a per-target throttle (limits.perTargetPerMinute); a client tool never fetches; an unavailable tool says why (statusReason); JSON output has a schema. contracts.tools.checkDescriptor(d) also checks what depends on the id (run.path, $ref targets), files.min <= files.max, and each example: its input against an embedded input schema and limits.maxInputBytes, and its files against files (count and accept).
@@ -2603,7 +2603,6 @@ export interface ToolDescriptor {
@@ -2603,7 +2603,6 @@ export interface ToolDescriptor {
*/
*/
docs: string;
docs: string;
}
}
-
/** [email protected] (owner: tools) */
/** [email protected] (owner: tools) */
/**
/**
* The answer of GET /api/v1/tools on openvibe.tools (capability tools.tool.read; ADR-027): every tool's descriptor (tools.tool@1), with input and output schemas in the { $ref } form (GET /api/v1/tools/:id has them embedded). Filters: ?family=, ?execution=client|sync|job, ?api=true|false, ?status=, ?q= (every word must appear in the id, name, summary or keywords); family, execution and status take comma lists. An unknown execution or status value, or an api that is not true or false, is 400 tools.query.invalid. Every listed tool has one of the descriptor's statuses: there is no planned status, so planned placeholders are not tools and are not listed, and neither are mirrors (GET /api/v1/tools/:id answers 404 tools.tool.not_found for both, with a detail saying why). Cacheable (ETag); updated_at changes when any descriptor does.
* The answer of GET /api/v1/tools on openvibe.tools (capability tools.tool.read; ADR-027): every tool's descriptor (tools.tool@1), with input and output schemas in the { $ref } form (GET /api/v1/tools/:id has them embedded). Filters: ?family=, ?execution=client|sync|job, ?api=true|false, ?status=, ?q= (every word must appear in the id, name, summary or keywords); family, execution and status take comma lists. An unknown execution or status value, or an api that is not true or false, is 400 tools.query.invalid. Every listed tool has one of the descriptor's statuses: there is no planned status, so planned placeholders are not tools and are not listed, and neither are mirrors (GET /api/v1/tools/:id answers 404 tools.tool.not_found for both, with a detail saying why). Cacheable (ETag); updated_at changes when any descriptor does.
@@ -2624,7 +2623,6 @@ export interface ToolList {
@@ -2624,7 +2623,6 @@ export interface ToolList {
count: number;
count: number;
}[];
}[];
}
}
-
/** [email protected] (owner: tools) */
/** [email protected] (owner: tools) */
/**
/**
* Body of POST /api/v1/tools/{id}/run on openvibe.tools (capability tools.tool.run, or tools.net.probe for network probes; ADR-027). JSON, or multipart/form-data like POST /api/v1/jobs: the text parts input (JSON text), wait_ms and idempotency_key, and uploaded files as file or files parts. The tool gets the uploaded files first, then the files references, in order; the count must fit the descriptor's files.min and files.max. input must match the tool's input schema (tools.tool@1 input). A job tool's job input is { ...input, ...run.job.preset, tool: run.job.operation } (the preset and the operation always win). The Idempotency-Key header wins over idempotency_key; ?wait_ms= on the query does the same as the field. Answers tools.run@1: 200 finished (succeeded, or failed with the tool's own error), 202 + Location while a job is queued or running, 200 + Idempotent-Replayed: true for a replay. Refusals are problem+json: 400 tools.run.invalid (not JSON, a bad field, wrong file count), 401 token.*, 403 capability.denied, 404 tools.tool.not_found, 404 tools.tool.not_runnable (api false: a page-only tool), 404 tools.run.file_not_found (a reference the caller cannot read), 409 tools.job.idempotency_conflict, 413 tools.file.too_large, 415 tools.file.unsupported_type (checked against the bytes), 422 tools.input.invalid (errors[] with JSON pointers), 429 tools.quota.exceeded (Retry-After; the caller's allowance in the tool's quota class is spent), quota.exceeded or tools.job.too_many_active (Retry-After), 503 tools.tool.unavailable (the descriptor's status is unavailable), 503 tools.unavailable (a program or upstream the tool needs is missing), 503 tools.busy (Retry-After; every slot for this kind of work is taken). A job tool's own refusals (413 tools.pdf.too_many_pages, 422 tools.pdf.wrong_password…) come back as a failed run (tools.run@1 error).
* Body of POST /api/v1/tools/{id}/run on openvibe.tools (capability tools.tool.run, or tools.net.probe for network probes; ADR-027). JSON, or multipart/form-data like POST /api/v1/jobs: the text parts input (JSON text), wait_ms and idempotency_key, and uploaded files as file or files parts. The tool gets the uploaded files first, then the files references, in order; the count must fit the descriptor's files.min and files.max. input must match the tool's input schema (tools.tool@1 input). A job tool's job input is { ...input, ...run.job.preset, tool: run.job.operation } (the preset and the operation always win). The Idempotency-Key header wins over idempotency_key; ?wait_ms= on the query does the same as the field. Answers tools.run@1: 200 finished (succeeded, or failed with the tool's own error), 202 + Location while a job is queued or running, 200 + Idempotent-Replayed: true for a replay. Refusals are problem+json: 400 tools.run.invalid (not JSON, a bad field, wrong file count), 401 token.*, 403 capability.denied, 404 tools.tool.not_found, 404 tools.tool.not_runnable (api false: a page-only tool), 404 tools.run.file_not_found (a reference the caller cannot read), 409 tools.job.idempotency_conflict, 413 tools.file.too_large, 415 tools.file.unsupported_type (checked against the bytes), 422 tools.input.invalid (errors[] with JSON pointers), 429 tools.quota.exceeded (Retry-After; the caller's allowance in the tool's quota class is spent), quota.exceeded or tools.job.too_many_active (Retry-After), 503 tools.tool.unavailable (the descriptor's status is unavailable), 503 tools.unavailable (a program or upstream the tool needs is missing), 503 tools.busy (Retry-After; every slot for this kind of work is taken). A job tool's own refusals (413 tools.pdf.too_many_pages, 422 tools.pdf.wrong_password…) come back as a failed run (tools.run@1 error).
@@ -2657,7 +2655,6 @@ export interface ToolsRunRequest {
@@ -2657,7 +2655,6 @@ export interface ToolsRunRequest {
*/
*/
idempotency_key?: string;
idempotency_key?: string;
}
}
-
/** [email protected] (owner: tools) */
/** [email protected] (owner: tools) */
/**
/**
* The answer of POST /api/v1/tools/{id}/run (tools.run-request@1; ADR-027). Finished: { state: succeeded, tool, result, took_ms } or { state: failed|cancelled, tool, error, took_ms }, with job set when the tool ran as a job. Not finished within wait_ms: { state: queued|running, tool, job, location }, sent with 202 and a Location header; follow the job there (GET, its events, its files; tools.job@1). result carries data (kind json, matching the descriptor's output schema), text (kind text) or files (kind file or files: the job's result files, downloaded from their url). took_ms runs from the request to the answer for an inline run, and from created_at to finished_at for a job. error is problem+json from the tool: tools.job.failed, tools.job.timeout, tools.job.cancelled, or a code the tool chose (tools.…), such as 413 tools.pdf.too_many_pages or 422 tools.pdf.wrong_password; its status is the problem's, not the HTTP answer's. A request refused before the tool ran is a problem+json answer, not a run. Also: 413 tools.input.too_large (the input exceeded a worker's memory or size limit), 504 tools.run.timeout (the run passed the descriptor's timeoutMs), 403 tools.origin.refused (a cookie-authenticated mutation from a foreign Origin), 401 tools.session_required (the tool is not anonymous and the call has no browser session or token), 404 tools.run.file_not_found (a files reference names no result the caller may read), 500 tools.run.failed (the engine failed without a more specific code).
* The answer of POST /api/v1/tools/{id}/run (tools.run-request@1; ADR-027). Finished: { state: succeeded, tool, result, took_ms } or { state: failed|cancelled, tool, error, took_ms }, with job set when the tool ran as a job. Not finished within wait_ms: { state: queued|running, tool, job, location }, sent with 202 and a Location header; follow the job there (GET, its events, its files; tools.job@1). result carries data (kind json, matching the descriptor's output schema), text (kind text) or files (kind file or files: the job's result files, downloaded from their url). took_ms runs from the request to the answer for an inline run, and from created_at to finished_at for a job. error is problem+json from the tool: tools.job.failed, tools.job.timeout, tools.job.cancelled, or a code the tool chose (tools.…), such as 413 tools.pdf.too_many_pages or 422 tools.pdf.wrong_password; its status is the problem's, not the HTTP answer's. A request refused before the tool ran is a problem+json answer, not a run. Also: 413 tools.input.too_large (the input exceeded a worker's memory or size limit), 504 tools.run.timeout (the run passed the descriptor's timeoutMs), 403 tools.origin.refused (a cookie-authenticated mutation from a foreign Origin), 401 tools.session_required (the tool is not anonymous and the call has no browser session or token), 404 tools.run.file_not_found (a files reference names no result the caller may read), 500 tools.run.failed (the engine failed without a more specific code).
@@ -2733,7 +2730,6 @@ export type ToolsRun =
@@ -2733,7 +2730,6 @@ export type ToolsRun =
*/
*/
location: string;
location: string;
};
};
-
/** [email protected] (owner: tools) */
/** [email protected] (owner: tools) */
/**
/**
* A Tools job as the jobs API answers it (OpenVibe.Tools apps/_shared/jobs/http.js, system.view; ADR-027): POST /api/v1/jobs (202 + Location, or 200 + Idempotent-Replayed: true), GET and DELETE /api/v1/jobs/:id, POST /api/v1/jobs/:id/retry, PUT and DELETE /api/v1/jobs/:id/references/:ref, the data of every SSE event on GET /api/v1/jobs/:id/events, and the job member of tools.run@1. A job is visible only to its owner (a person, a service/app principal, or one browser session); anyone else gets the same 404 tools.job.not_found as for a job that does not exist. result is null until the job succeeded; each result file is downloaded from its url (GET /api/v1/jobs/:id/files/:n, ?inline=1 for previews) and is stored as a private OpenVibe.Media object (storage media) or on the satellite until the job expires (storage local). expires_at is null while a reference keeps the result. error is problem+json: tools.job.failed, tools.job.timeout, tools.job.cancelled, or a code the tool chose (tools.…), such as 413 tools.pdf.too_many_pages or 422 tools.pdf.wrong_password, with server paths removed.
* A Tools job as the jobs API answers it (OpenVibe.Tools apps/_shared/jobs/http.js, system.view; ADR-027): POST /api/v1/jobs (202 + Location, or 200 + Idempotent-Replayed: true), GET and DELETE /api/v1/jobs/:id, POST /api/v1/jobs/:id/retry, PUT and DELETE /api/v1/jobs/:id/references/:ref, the data of every SSE event on GET /api/v1/jobs/:id/events, and the job member of tools.run@1. A job is visible only to its owner (a person, a service/app principal, or one browser session); anyone else gets the same 404 tools.job.not_found as for a job that does not exist. result is null until the job succeeded; each result file is downloaded from its url (GET /api/v1/jobs/:id/files/:n, ?inline=1 for previews) and is stored as a private OpenVibe.Media object (storage media) or on the satellite until the job expires (storage local). expires_at is null while a reference keeps the result. error is problem+json: tools.job.failed, tools.job.timeout, tools.job.cancelled, or a code the tool chose (tools.…), such as 413 tools.pdf.too_many_pages or 422 tools.pdf.wrong_password, with server paths removed.
@@ -2853,7 +2849,6 @@ export interface ToolsJob {
@@ -2853,7 +2849,6 @@ export interface ToolsJob {
retried_by: string | null;
retried_by: string | null;
};
};
}
}
-
/** [email protected] (owner: tools) */
/** [email protected] (owner: tools) */
/**
/**
* Body of POST /api/v1/jobs on the Tools satellites that run jobs (img, audio, docs; capability tools.job.create; OpenVibe.Tools apps/_shared/jobs/http.js). JSON, or multipart/form-data with the same fields as text parts (input as JSON text) and the input files as file or files parts. The Idempotency-Key header wins over idempotency_key: the same (owner, key) and request returns the same job (200 + Idempotent-Replayed: true), a different request under it is 409 tools.job.idempotency_conflict. Types: img.process (input { tool: convert|compress|resize|crop, … }, one image), audio.process ({ tool, … } as /api/process takes them, one audio or video file), docs.process ({ tool, … }, one PDF, or several files for merge and img2pdf). A format host fills in its format (webp.openvibe.tools converts to WebP). Answers tools.job@1 (202 + Location). Refusals are problem+json: 400 tools.job.invalid (input not a JSON object or over 16 KB, wrong file count, the type's own validation) or tools.job.unknown_type, 401 token.* or tools.job.no_owner, 403 capability.denied, 413 tools.job.too_large, 429 tools.job.too_many_active (unfinished jobs per owner), tools.quota.exceeded (Retry-After; the owner's allowance is spent) or a rate limit, 503 tools.job.unavailable, 503 tools.unavailable (a program or upstream the job type needs is missing), 503 tools.busy (Retry-After; every slot for this kind of work is taken). A tool that refuses its input after the job was accepted fails the job with its own code (413 tools.pdf.too_many_pages, 422 tools.pdf.wrong_password…; tools.job@1 error).
* Body of POST /api/v1/jobs on the Tools satellites that run jobs (img, audio, docs; capability tools.job.create; OpenVibe.Tools apps/_shared/jobs/http.js). JSON, or multipart/form-data with the same fields as text parts (input as JSON text) and the input files as file or files parts. The Idempotency-Key header wins over idempotency_key: the same (owner, key) and request returns the same job (200 + Idempotent-Replayed: true), a different request under it is 409 tools.job.idempotency_conflict. Types: img.process (input { tool: convert|compress|resize|crop, … }, one image), audio.process ({ tool, … } as /api/process takes them, one audio or video file), docs.process ({ tool, … }, one PDF, or several files for merge and img2pdf). A format host fills in its format (webp.openvibe.tools converts to WebP). Answers tools.job@1 (202 + Location). Refusals are problem+json: 400 tools.job.invalid (input not a JSON object or over 16 KB, wrong file count, the type's own validation) or tools.job.unknown_type, 401 token.* or tools.job.no_owner, 403 capability.denied, 413 tools.job.too_large, 429 tools.job.too_many_active (unfinished jobs per owner), tools.quota.exceeded (Retry-After; the owner's allowance is spent) or a rate limit, 503 tools.job.unavailable, 503 tools.unavailable (a program or upstream the job type needs is missing), 503 tools.busy (Retry-After; every slot for this kind of work is taken). A tool that refuses its input after the job was accepted fails the job with its own code (413 tools.pdf.too_many_pages, 422 tools.pdf.wrong_password…; tools.job@1 error).
@@ -333,6 +333,10 @@ export type ExchangeEarnings = {
@@ -333,6 +333,10 @@ export type ExchangeEarnings = {
};
};
export type ExchangeClient = {
export type ExchangeClient = {
+ arena: {
+ start(input: { prompt: string; category?: string; entries: unknown[] }, options?: { signal?: AbortSignal }): Promise<unknown>;
+ vote(roundId: string, choice: 'left' | 'right' | 'tie' | 'both_bad', options?: { signal?: AbortSignal }): Promise<unknown>;
+ };
/** One chat completion; stream: true answers an async iterable of chunks. */
/** One chat completion; stream: true answers an async iterable of chunks. */
chat(body: ExchangeChatRequest, opts?: { signal?: AbortSignal }): Promise<ExchangeChatCompletion | AsyncIterable<ExchangeChatChunk>>;
chat(body: ExchangeChatRequest, opts?: { signal?: AbortSignal }): Promise<ExchangeChatCompletion | AsyncIterable<ExchangeChatChunk>>;
/** The models the Exchange can serve. */
/** The models the Exchange can serve. */
@@ -1,5 +1,7 @@
@@ -1,5 +1,7 @@
export * from './core';
export * from './core';
export * as auth from './auth';
export * as auth from './auth';
+export * as report from './report';
+export * as report from './report';
export * as registry from './registry';
export * as registry from './registry';
export * as identity from './identity';
export * as identity from './identity';
export * as trust from './trust';
export * as trust from './trust';
new file mode 100644
new file mode 100644
@@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
+import type { OpenVibeClient } from './core';
+export interface ReportAI {
+ leaderboard(opts?: { kind?: string; maker?: string; weights?: 'open' | 'closed'; price?: string; status?: 'current' | 'all'; uncensored?: boolean; community?: boolean; sort?: string; limit?: number; cursor?: string }, options?: { signal?: AbortSignal }): Promise<unknown>;
+ model(id: string, options?: { signal?: AbortSignal }): Promise<unknown>;
+ providers(options?: { signal?: AbortSignal }): Promise<unknown>;
+ compare(ids: string | string[], options?: { signal?: AbortSignal }): Promise<unknown>;
+ picks(options?: { signal?: AbortSignal }): Promise<unknown>;
+}
+export interface ReportClient { ai: ReportAI }
+export declare function createReportClient(client: Pick<OpenVibeClient, 'json'>, options?: { baseUrl?: string }): ReportClient;
+export declare const REPORT_URL: string;