1 line
6.9 KiB
JSON
1 line
6.9 KiB
JSON
{"repo": "ardurai/ardur-agent", "files": {"crates/provider-openrouter/README.md": {"34b4b7d65e8efc7ef704dc1d72786861cd8d1fe5": {"t": 1785746528.624872, "n": 3259, "h": "14007e932f94c10e", "c": "# ardur-provider-openrouter (§3.2)\n\nA [`Provider`](../provider-runtime) backend for [OpenRouter](https://openrouter.ai) —\na multi-model gateway that exposes a single **OpenAI-compatible**\n`POST /chat/completions` endpoint routing to many upstream providers behind one\nAPI key.\n\n## Why\n\nOpenRouter unblocks dogfooding ardur-agent against cheap or free models without\nwiring a separate backend per vendor. One API key, one endpoint, dozens of\nmodels — selected per request by passing the model slug through the request's\n`ModelId`.\n\n## Usage\n\n```rust\nuse std::sync::Arc;\nuse ardur_provider_openrouter::{OpenRouterConfig, OpenRouterProvider};\nuse ardur_provider_runtime::{ModelId, Provider, ProviderRegistry};\n\n// From the OPENROUTER_API_KEY environment variable:\nlet provider = OpenRouterProvider::from_env(ModelId::new(\"anthropic/claude-3.5-sonnet\"))?;\n\n// …or from an explicit config (overridable base URL + attribution headers):\nlet provider = OpenRouterProvider::new(\n OpenRouterConfig::new(\"sk-or-...\")\n .referer(\"https://github.com/ArdurAI/ardur-agent\")\n .title(\"Ardur Agent\"),\n ModelId::new(\"openai/gpt-4o\"),\n);\n\n// It plugs into the generic registry like any other provider:\nlet mut registry = ProviderRegistry::new();\nregistry.register(Arc::new(provider)); // registered under id \"openrouter\"\n```\n\nThe model on each `CompletionRequest` selects which upstream model OpenRouter\nroutes to — the `model_id` passed at construction is only the runtime's default.\n\n## Supported models\n\nThe model string is **opaque** and passes through unchanged; OpenRouter\nvalidates it against its catalog. Any slug from\n<https://openrouter.ai/models> works. Representative examples:\n\n| Slug | Notes |\n|---|---|\n| `anthropic/claude-3.5-sonnet` | Anthropic via the gateway |\n| `openai/gpt-4o` | OpenAI |\n| `google/gemini-flash-1.5` | Google |\n| `meta-llama/llama-3.1-8b-instruct:free` | free tier (`:free` suffix) |\n| `mistralai/mistral-7b-instruct` | Mistral |\n| `deepseek/deepseek-chat` | DeepSeek |\n\n## Auth & headers\n\nEvery request sends:\n\n- `Authorization: Bearer <api-key>`\n- `HTTP-Referer: https://github.com/ArdurAI/ardur-agent` (default; OpenRouter's\n recommended attribution header, overridable)\n- `X-Title: Ardur Agent` (default, overridable)\n\n## Cost reporting\n\nOpenRouter returns `usage: { prompt_tokens, completion_tokens, total_tokens,\ncost }`. Token counts populate the billed `CostTuple`, and the dollar `cost` is\nmapped to whole US cents (`round(cost × 100)`). When `cost` is absent the call\nis billed `0` cents — Phase 1 does not reconstruct cost from a rate card.\n\n## Error mapping\n\n| HTTP | `ProviderError` |\n|---|---|\n| 401 / 403 | `Unauthorized` |\n| 429 | `RateLimited { retry_after_ms }` (from `Retry-After`) |\n| 400 | `InvalidRequest(message)` |\n| 404 | `ModelNotAvailable(model)` |\n| other | `Upstream(\"HTTP <code>: <message> (code: …)\")` |\n\nOpenRouter's `{ \"error\": { \"message\", \"code\" } }` body is parsed and its message\n+ code surfaced in the mapped error.\n\n## Not in Phase 1\n\n- **Streaming** — every request sends `stream: false`;\n `supports_streaming()` is `false`. (Phase 2.)\n- **Tool-call parsing** — a `tool_calls` finish reason surfaces as\n `FinishReason::ToolUse(vec![])`; the blocks are not decoded yet. (Phase 2.)\n"}, "e2011096735fdeba04690cf557708248b506b9e2": {"t": 1785765351.515097, "n": 3259, "h": "14007e932f94c10e", "c": "# ardur-provider-openrouter (§3.2)\n\nA [`Provider`](../provider-runtime) backend for [OpenRouter](https://openrouter.ai) —\na multi-model gateway that exposes a single **OpenAI-compatible**\n`POST /chat/completions` endpoint routing to many upstream providers behind one\nAPI key.\n\n## Why\n\nOpenRouter unblocks dogfooding ardur-agent against cheap or free models without\nwiring a separate backend per vendor. One API key, one endpoint, dozens of\nmodels — selected per request by passing the model slug through the request's\n`ModelId`.\n\n## Usage\n\n```rust\nuse std::sync::Arc;\nuse ardur_provider_openrouter::{OpenRouterConfig, OpenRouterProvider};\nuse ardur_provider_runtime::{ModelId, Provider, ProviderRegistry};\n\n// From the OPENROUTER_API_KEY environment variable:\nlet provider = OpenRouterProvider::from_env(ModelId::new(\"anthropic/claude-3.5-sonnet\"))?;\n\n// …or from an explicit config (overridable base URL + attribution headers):\nlet provider = OpenRouterProvider::new(\n OpenRouterConfig::new(\"sk-or-...\")\n .referer(\"https://github.com/ArdurAI/ardur-agent\")\n .title(\"Ardur Agent\"),\n ModelId::new(\"openai/gpt-4o\"),\n);\n\n// It plugs into the generic registry like any other provider:\nlet mut registry = ProviderRegistry::new();\nregistry.register(Arc::new(provider)); // registered under id \"openrouter\"\n```\n\nThe model on each `CompletionRequest` selects which upstream model OpenRouter\nroutes to — the `model_id` passed at construction is only the runtime's default.\n\n## Supported models\n\nThe model string is **opaque** and passes through unchanged; OpenRouter\nvalidates it against its catalog. Any slug from\n<https://openrouter.ai/models> works. Representative examples:\n\n| Slug | Notes |\n|---|---|\n| `anthropic/claude-3.5-sonnet` | Anthropic via the gateway |\n| `openai/gpt-4o` | OpenAI |\n| `google/gemini-flash-1.5` | Google |\n| `meta-llama/llama-3.1-8b-instruct:free` | free tier (`:free` suffix) |\n| `mistralai/mistral-7b-instruct` | Mistral |\n| `deepseek/deepseek-chat` | DeepSeek |\n\n## Auth & headers\n\nEvery request sends:\n\n- `Authorization: Bearer <api-key>`\n- `HTTP-Referer: https://github.com/ArdurAI/ardur-agent` (default; OpenRouter's\n recommended attribution header, overridable)\n- `X-Title: Ardur Agent` (default, overridable)\n\n## Cost reporting\n\nOpenRouter returns `usage: { prompt_tokens, completion_tokens, total_tokens,\ncost }`. Token counts populate the billed `CostTuple`, and the dollar `cost` is\nmapped to whole US cents (`round(cost × 100)`). When `cost` is absent the call\nis billed `0` cents — Phase 1 does not reconstruct cost from a rate card.\n\n## Error mapping\n\n| HTTP | `ProviderError` |\n|---|---|\n| 401 / 403 | `Unauthorized` |\n| 429 | `RateLimited { retry_after_ms }` (from `Retry-After`) |\n| 400 | `InvalidRequest(message)` |\n| 404 | `ModelNotAvailable(model)` |\n| other | `Upstream(\"HTTP <code>: <message> (code: …)\")` |\n\nOpenRouter's `{ \"error\": { \"message\", \"code\" } }` body is parsed and its message\n+ code surfaced in the mapped error.\n\n## Not in Phase 1\n\n- **Streaming** — every request sends `stream: false`;\n `supports_streaming()` is `false`. (Phase 2.)\n- **Tool-call parsing** — a `tool_calls` finish reason surfaces as\n `FinishReason::ToolUse(vec![])`; the blocks are not decoded yet. (Phase 2.)\n"}}}} |