refactor(catalog)!: split model catalog from pi-ai

Move bundled models, model cache/manager, thinking metadata, effort helpers,
provider descriptors/discovery, wire constants, and model identity utilities
into the new @oh-my-pi/pi-catalog package.

Update pi-ai to keep provider runtime/auth concerns, move catalog provider
metadata into CATALOG_PROVIDERS, and migrate coding-agent, agent, stats, docs,
and tests to import catalog values from pi-catalog.

Split coding-agent model registry helpers into discovery, roles, and models
config modules while preserving registry orchestration.

BREAKING CHANGE: @oh-my-pi/pi-ai no longer exports catalog subpaths such as
/models, /model-cache, /model-manager, /model-thinking, /effort,
/provider-models*, discovery helpers, and provider wire constants; use the
matching @oh-my-pi/pi-catalog subpaths instead.
This commit is contained in:
can1357
2026-06-10 04:05:19 +02:00
parent b3166b3dd2
commit 1b9d9d0851
320 changed files with 4155 additions and 3194 deletions
+9 -6
View File
@@ -11,6 +11,7 @@ This repo contains multiple packages, but **`packages/coding-agent/`** is the pr
| Package | Description |
| ----------------------- | ---------------------------------------------------- |
| `packages/ai` | Multi-provider LLM client with streaming support |
| `packages/catalog` | Model catalog: bundled models.json, provider descriptors, model identity/classification |
| `packages/agent` | Agent runtime with tool calling and state management |
| `packages/coding-agent` | Main CLI application (primary focus) |
| `packages/tui` | Terminal UI library with differential rendering |
@@ -19,6 +20,8 @@ This repo contains multiple packages, but **`packages/coding-agent/`** is the pr
| `packages/utils` | Shared utilities (logger, streams, temp files) |
| `crates/pi-natives` | Rust crate for performance-critical text/grep ops |
**Catalog import convention**: code in this repo imports catalog *values* (bundled models, model-thinking helpers, identity, descriptors, model manager/cache) from `@oh-my-pi/pi-catalog/<module>` — never via `@oh-my-pi/pi-ai`. The pi-ai barrel re-exports only the model/effort *types* its own signatures use (`Model`, `Api`, `ThinkingConfig`, `Effort`, …); type-only imports of those from `@oh-my-pi/pi-ai` are fine.
## Code Quality
- No `any` unless absolutely necessary.
@@ -147,15 +150,15 @@ Manual reader loops only when the protocol requires it (SSE, streaming JSON-RPC)
## Generated Files
**NEVER edit `packages/ai/src/models.json` directly.** It is generated from upstream sources (models.dev, provider catalog discovery, OpenCode docs) by `packages/ai/scripts/generate-models.ts` and the descriptors/resolvers in `packages/ai/src/provider-models/`. Hand-edits get overwritten on the next regen.
**NEVER edit `packages/catalog/src/models.json` directly.** It is generated from upstream sources (models.dev, provider catalog discovery, OpenCode docs) by `packages/catalog/scripts/generate-models.ts` and the descriptors/resolvers in `packages/catalog/src/provider-models/`. Hand-edits get overwritten on the next regen.
To change an entry, fix the source:
- **Resolution rules / per-id overrides** → relevant resolver in `packages/ai/src/provider-models/openai-compat.ts` (e.g. `createOpenCodeApiResolution`'s id-override map).
- **Provider descriptors** (filtering, transforms, defaults, headers, compat overrides) → `packages/ai/src/provider-models/descriptors.ts` or the provider-specific descriptor.
- **Generator-level fixups** (premium multipliers, codex pricing fallback, fallback models, post-processing) → `packages/ai/scripts/generate-models.ts`.
- **Thinking metadata / generated policies** → `packages/ai/src/model-thinking.ts` (`applyGeneratedModelPolicies`).
- **Resolution rules / per-id overrides** → relevant resolver in `packages/catalog/src/provider-models/openai-compat.ts` (e.g. `createOpenCodeApiResolution`'s id-override map).
- **Provider catalog entries** (default model, discovery factory/flags) → the `CATALOG_PROVIDERS` table in `packages/catalog/src/provider-models/descriptors.ts`.
- **Generator-level fixups** (premium multipliers, codex pricing fallback, fallback models, post-processing) → `packages/catalog/scripts/generate-models.ts`.
- **Thinking metadata / generated policies** → `packages/catalog/src/model-thinking.ts` (`applyGeneratedModelPolicies`); model-id classification (family/version parsing) lives in `packages/catalog/src/identity/classify.ts`.
Regenerate with `bun --cwd=packages/ai run generate-models` and commit `models.json` alongside the source change. Add a regression test against the **resolver/descriptor**, not the bundled JSON, so it survives upstream metadata shifts.
Regenerate with `bun --cwd=packages/catalog run generate-models` and commit `models.json` alongside the source change. Add a regression test against the **resolver/descriptor**, not the bundled JSON, so it survives upstream metadata shifts.
## Logging
+1 -1
View File
@@ -62,7 +62,7 @@
"!**/test-sessions.ts",
"!**/template.generated.ts",
"!**/docs-index.generated.ts",
"!**/gen/agent_pb.ts",
"!**/agent_pb.ts",
"!.worktrees/**/*",
"!.wt/**/*"
]
+21 -1
View File
@@ -18,6 +18,7 @@
"version": "15.10.10",
"dependencies": {
"@oh-my-pi/pi-ai": "catalog:",
"@oh-my-pi/pi-catalog": "catalog:",
"@oh-my-pi/pi-natives": "catalog:",
"@oh-my-pi/pi-utils": "catalog:",
"@opentelemetry/api": "catalog:",
@@ -33,6 +34,7 @@
"version": "15.10.10",
"dependencies": {
"@bufbuild/protobuf": "catalog:",
"@oh-my-pi/pi-catalog": "catalog:",
"@oh-my-pi/pi-utils": "catalog:",
"openai": "catalog:",
"partial-json": "catalog:",
@@ -42,11 +44,24 @@
"@types/bun": "catalog:",
},
},
"packages/catalog": {
"name": "@oh-my-pi/pi-catalog",
"version": "15.10.10",
"dependencies": {
"@bufbuild/protobuf": "catalog:",
"@oh-my-pi/pi-utils": "catalog:",
"zod": "catalog:",
},
"devDependencies": {
"@oh-my-pi/pi-ai": "catalog:",
"@types/bun": "catalog:",
},
},
"packages/coding-agent": {
"name": "@oh-my-pi/pi-coding-agent",
"version": "15.10.10",
"bin": {
"omp": "src/cli.ts",
"omp": "dist/cli.js",
},
"dependencies": {
"@agentclientprotocol/sdk": "catalog:",
@@ -56,6 +71,7 @@
"@oh-my-pi/omp-stats": "catalog:",
"@oh-my-pi/pi-agent-core": "catalog:",
"@oh-my-pi/pi-ai": "catalog:",
"@oh-my-pi/pi-catalog": "catalog:",
"@oh-my-pi/pi-mnemopi": "catalog:",
"@oh-my-pi/pi-natives": "catalog:",
"@oh-my-pi/pi-tui": "catalog:",
@@ -132,6 +148,7 @@
},
"dependencies": {
"@oh-my-pi/pi-ai": "catalog:",
"@oh-my-pi/pi-catalog": "catalog:",
"@oh-my-pi/pi-utils": "catalog:",
"@tailwindcss/node": "catalog:",
"chart.js": "catalog:",
@@ -252,6 +269,7 @@
"@oh-my-pi/omp-stats": "15.10.10",
"@oh-my-pi/pi-agent-core": "15.10.10",
"@oh-my-pi/pi-ai": "15.10.10",
"@oh-my-pi/pi-catalog": "15.10.10",
"@oh-my-pi/pi-coding-agent": "15.10.10",
"@oh-my-pi/pi-mnemopi": "15.10.10",
"@oh-my-pi/pi-natives": "15.10.10",
@@ -641,6 +659,8 @@
"@oh-my-pi/pi-ai": ["@oh-my-pi/pi-ai@workspace:packages/ai"],
"@oh-my-pi/pi-catalog": ["@oh-my-pi/pi-catalog@workspace:packages/catalog"],
"@oh-my-pi/pi-coding-agent": ["@oh-my-pi/pi-coding-agent@workspace:packages/coding-agent"],
"@oh-my-pi/pi-mnemopi": ["@oh-my-pi/pi-mnemopi@workspace:packages/mnemopi"],
+48 -25
View File
@@ -1,26 +1,42 @@
# Adding a provider
Providers in `packages/ai` are described by a single declarative
`ProviderDefinition` and collected in one registry. Every scattered structure —
the `KnownProvider` / `OAuthProvider` type unions, `PROVIDER_DESCRIPTORS`,
`DEFAULT_MODEL_PER_PROVIDER`, the `serviceProviderMap` env-key fallbacks, the
`/login` provider list, the `refreshOAuthToken` / `AuthStorage.login` dispatch,
and the coding-agent callback maps — is **derived** from that registry.
A provider is described in two halves:
- **Catalog half** (`packages/catalog`): one entry in the `CATALOG_PROVIDERS`
table (`packages/catalog/src/provider-models/descriptors.ts`) carrying the
`id`, `defaultModel`, runtime model-discovery factory, and catalog-generation
wiring. `KnownProvider`, `PROVIDER_DESCRIPTORS`, and
`DEFAULT_MODEL_PER_PROVIDER` are derived from this table.
- **Auth half** (`packages/ai`): one declarative `ProviderDefinition` in the
registry carrying env-key fallbacks and login/refresh flows. The
`OAuthProvider` union, the env-key map, the `/login` provider list, the
`refreshOAuthToken` / `AuthStorage.login` dispatch, and the coding-agent
callback maps are derived from the registry.
**Scope.** This is for a provider that reuses an existing wire API
(`openai-completions`, `anthropic-messages`, `google-generative-ai`, …) — the
common case for gateways and API-key providers, since stream dispatch keys on
`model.api`, not `model.provider`. Adding a *new wire protocol* (a new
`KnownApi`) is a separate task that also touches `stream.ts` dispatch,
`api-registry.ts`, and `types.ts`.
`api-registry.ts`, and the catalog `types.ts`.
## Shape
For the common case, a provider is still **one new def file + one registry line**:
For the common case, a provider is **one catalog entry + one def file + one registry line**:
1. **Create `packages/ai/src/registry/<id>.ts`** exporting one
`export const <camelId>Provider = { … } as const satisfies ProviderDefinition;`.
2. **Add it to the `ALL` array** in `packages/ai/src/registry/registry.ts`
1. **Add an entry to `CATALOG_PROVIDERS`** in
`packages/catalog/src/provider-models/descriptors.ts` with the `id`,
`defaultModel`, the plain API-key env var(s) as `envVars`, and (usually) a
`createModelManagerOptions` factory. For a
simple OpenAI-compatible gateway, build the factory in
`packages/catalog/src/provider-models/openai-compat.ts` or inline with the
exported `createSimpleOpenAICompletionsOptions(providerId, baseUrl, config)`.
2. **Create `packages/ai/src/registry/<id>.ts`** exporting one
`export const <camelId>Provider = { … } as const satisfies ProviderDefinition;`
with the auth fields (`login`, …). Plain env-var names live in the catalog
entry's `envVars`; set `envKeys` only for computed resolvers (Foundry/ADC/
Bedrock-style probes).
3. **Add it to the `ALL` array** in `packages/ai/src/registry/registry.ts`
(one import + one array entry). `ALL` order is the `/login` list order for
loginable providers.
@@ -34,26 +50,33 @@ For a **non-trivial provider-local OAuth flow**, put the implementation in
file. The shared OAuth flow infrastructure it builds on lives in the same
`registry/oauth/` directory.
Either way, descriptors, default-model map, env-key map, login list, and refresh
dispatch all update automatically, and the `KnownProvider` / `OAuthProvider`
unions gain the new id by derivation.
Descriptors, the default-model map, env-key map, login list, and refresh
dispatch all update automatically; the `KnownProvider` union gains the new id
from the catalog table and `OAuthProvider` from the registry.
## `ProviderDefinition` fields
## Field reference
See `packages/ai/src/registry/types.ts` for the authoritative,
JSDoc-annotated interface. Presence of a field opts the provider into a derived
structure:
**Catalog table entry** (`ProviderCatalogEntry`, see
`packages/catalog/src/provider-models/descriptor-types.ts` for JSDoc):
| Field | Effect |
|---|---|
| `id` | Required. Member of `KnownProvider`. |
| `defaultModel` | Required. Preferred model when no explicit selection is made. |
| `envVars` | Env var name(s), in order, for the runtime API-key fallback (`getEnvApiKey`). |
| `createModelManagerOptions` | Runtime model-discovery factory. Present (and not `specialModelManager`) ⇒ appears in `PROVIDER_DESCRIPTORS`. |
| `allowUnauthenticated` | Runtime creates a model manager even without a key. |
| `dynamicModelsAuthoritative` | Successful discovery replaces bundled models. |
| `catalogDiscovery` | `{ label, envVars?, oauthProvider?, allowUnauthenticated? }` for offline catalog generation (`generate-models.ts`). `envVars` here overrides the entry-level list when generation uses different credentials (e.g. `cursor`). |
| `specialModelManager` | Bespoke runtime factory (`google-antigravity` / `google-gemini-cli` / `openai-codex`); excluded from `PROVIDER_DESCRIPTORS`. |
**Registry definition** (`ProviderDefinition`, see
`packages/ai/src/registry/types.ts`):
| Field | Effect |
|---|---|
| `id`, `name` | Required. `name` shows in the `/login` list. |
| `defaultModel` | Present ⇒ member of `KnownProvider` (a chat-model provider). |
| `createModelManagerOptions` | Runtime model-discovery factory. Present (and not `specialModelManager`) ⇒ appears in `PROVIDER_DESCRIPTORS`. |
| `allowUnauthenticated` | Runtime creates a model manager even without a key. |
| `dynamicModelsAuthoritative` | Successful discovery replaces bundled models. |
| `catalogDiscovery` | `{ label, envVars, oauthProvider?, allowUnauthenticated? }` for offline catalog generation (`generate-models.ts`). |
| `specialModelManager` | Bespoke runtime factory (`google-antigravity` / `google-gemini-cli` / `openai-codex`); excluded from `PROVIDER_DESCRIPTORS`. |
| `envKeys` | Env-var fallback for `getEnvApiKey`: a var name string or a `() => string \| undefined` resolver. |
| `envKeys` | Computed env fallback for `getEnvApiKey`, overriding the catalog entry's `envVars`: a var name string or a `() => string \| undefined` resolver. Omit when `envVars` covers it. |
| `login` | Interactive login. Present ⇒ member of `OAuthProvider`, shown in `/login`, dispatchable via `AuthStorage.login`. Returns an api-key `string` or `OAuthCredentials`. |
| `refreshToken` | OAuth refresher; omit for static-token providers (the dispatch returns credentials unchanged). |
| `storeCredentialsAs` | Store credentials under a different provider id (e.g. `openai-codex-device` ⇒ `openai-codex`). |
+2 -1
View File
@@ -24,6 +24,7 @@
"@oh-my-pi/omp-stats": "15.10.10",
"@oh-my-pi/pi-agent-core": "15.10.10",
"@oh-my-pi/pi-ai": "15.10.10",
"@oh-my-pi/pi-catalog": "15.10.10",
"@oh-my-pi/pi-coding-agent": "15.10.10",
"@oh-my-pi/pi-mnemopi": "15.10.10",
"@oh-my-pi/pi-natives": "15.10.10",
@@ -152,7 +153,7 @@
"publish": "bun run prepublishOnly && npm publish -ws --access public",
"publish:dry": "bun run prepublishOnly && npm publish -ws --access public --dry-run",
"release": "bun scripts/release.ts",
"generate-models": "bun --cwd=packages/ai run generate-models",
"generate-models": "bun --cwd=packages/catalog run generate-models",
"generate-docs-index": "bun --cwd=packages/coding-agent run generate-docs-index",
"generate-template": "bun --cwd=packages/coding-agent run generate-template",
"check-spoofed-versions": "bun scripts/check-spoofed-versions.ts"
+1
View File
@@ -5,6 +5,7 @@
### Changed
- Editorial pass over the compaction prompts: fixed garbled grammar and missing articles, RFC-keyed prohibitions, deduped restated instructions; parsed markers (`<read-files>`/`<modified-files>`/`<previous-summary>`) and all output-format headings left byte-identical
- Catalog imports moved to the new `@oh-my-pi/pi-catalog` package: subpath imports (`calculateCost`, Codex wire constants) plus catalog values previously taken from the `@oh-my-pi/pi-ai` root (`getBundledModel`, `clampThinkingLevelForModel`), which pi-ai no longer re-exports; type-only `Model`/`Api`/`Effort` imports from pi-ai are unchanged
## [15.10.8] - 2026-06-09
### Added
+1
View File
@@ -36,6 +36,7 @@
},
"dependencies": {
"@oh-my-pi/pi-ai": "catalog:",
"@oh-my-pi/pi-catalog": "catalog:",
"@oh-my-pi/pi-natives": "catalog:",
"@oh-my-pi/pi-utils": "catalog:",
"@opentelemetry/api": "catalog:"
+1 -1
View File
@@ -9,7 +9,6 @@ import {
type CursorExecHandlers,
type CursorToolResultHandler,
type Effort,
getBundledModel,
type ImageContent,
type Message,
type Model,
@@ -22,6 +21,7 @@ import {
type ToolChoice,
type ToolResultMessage,
} from "@oh-my-pi/pi-ai";
import { getBundledModel } from "@oh-my-pi/pi-catalog/models";
import { abortReasonText, agentLoop, agentLoopContinue } from "./agent-loop";
import type { AppendOnlyContextManager } from "./append-only-context";
import type { HarmonyAuditEvent } from "./harmony-leak";
+1 -1
View File
@@ -7,7 +7,6 @@
import {
type AssistantMessage,
clampThinkingLevelForModel,
Effort,
type FetchImpl,
type Message,
@@ -15,6 +14,7 @@ import {
type Model,
type Usage,
} from "@oh-my-pi/pi-ai";
import { clampThinkingLevelForModel } from "@oh-my-pi/pi-catalog/model-thinking";
import { countTokens } from "@oh-my-pi/pi-natives";
import { logger, prompt } from "@oh-my-pi/pi-utils";
import { type AgentTelemetry, instrumentedCompleteSimple } from "../telemetry";
+6 -6
View File
@@ -12,12 +12,6 @@
* with `{ summary, shortSummary? }`.
*/
import {
CODEX_BASE_URL,
getCodexAccountId,
OPENAI_HEADER_VALUES,
OPENAI_HEADERS,
} from "@oh-my-pi/pi-ai/providers/openai-codex/constants";
import { parseTextSignature } from "@oh-my-pi/pi-ai/providers/openai-responses-shared";
import { transformMessages } from "@oh-my-pi/pi-ai/providers/transform-messages";
import type { AssistantMessage, FetchImpl, Message, Model } from "@oh-my-pi/pi-ai/types";
@@ -26,6 +20,12 @@ import {
getOpenAIResponsesHistoryPayload,
normalizeResponsesToolCallId,
} from "@oh-my-pi/pi-ai/utils";
import {
CODEX_BASE_URL,
getCodexAccountId,
OPENAI_HEADER_VALUES,
OPENAI_HEADERS,
} from "@oh-my-pi/pi-catalog/wire/codex";
import { logger } from "@oh-my-pi/pi-utils";
// ============================================================================
+1 -1
View File
@@ -13,8 +13,8 @@ import {
type StopReason,
type ToolCall,
} from "@oh-my-pi/pi-ai";
import { calculateCost } from "@oh-my-pi/pi-ai/models";
import { parseStreamingJson } from "@oh-my-pi/pi-ai/utils/json-parse";
import { calculateCost } from "@oh-my-pi/pi-catalog/models";
import { readSseJson } from "@oh-my-pi/pi-utils";
// Event stream adapter for proxy SSE events
@@ -9,7 +9,7 @@ import {
} from "@oh-my-pi/pi-agent-core/compaction";
import type { AssistantMessage, Model } from "@oh-my-pi/pi-ai";
import * as ai from "@oh-my-pi/pi-ai";
import { getBundledModel } from "@oh-my-pi/pi-ai/models";
import { getBundledModel } from "@oh-my-pi/pi-catalog/models";
// Pins the fix for the "raw 401 surfaced as Compaction failed:" bug.
//
@@ -10,7 +10,7 @@ import {
import { ThinkingLevel } from "@oh-my-pi/pi-agent-core/thinking";
import type { AssistantMessage, Model } from "@oh-my-pi/pi-ai";
import * as ai from "@oh-my-pi/pi-ai";
import { getBundledModel } from "@oh-my-pi/pi-ai/models";
import { getBundledModel } from "@oh-my-pi/pi-catalog/models";
// Pins fix #1 of the compaction effort-override bug. Before this fix,
// `generateHandoff` (and the three other compaction summarizers) hardcoded
+1 -1
View File
@@ -4,7 +4,7 @@ import { AUTO_HANDOFF_THRESHOLD_FOCUS, generateHandoff, renderHandoffPrompt } fr
import type { AssistantMessage, Model, ToolCall } from "@oh-my-pi/pi-ai";
import * as ai from "@oh-my-pi/pi-ai";
import { Effort } from "@oh-my-pi/pi-ai";
import { getBundledModel } from "@oh-my-pi/pi-ai/models";
import { getBundledModel } from "@oh-my-pi/pi-catalog/models";
function createAssistantMessage(content: AssistantMessage["content"]): AssistantMessage {
return {
+1 -1
View File
@@ -9,7 +9,7 @@ import {
signalListLabel,
} from "@oh-my-pi/pi-agent-core/harmony-leak";
import type { AssistantMessage, Model, ToolCall } from "@oh-my-pi/pi-ai";
import { getBundledModel } from "@oh-my-pi/pi-ai";
import { getBundledModel } from "@oh-my-pi/pi-catalog/models";
import corpus from "./fixtures/harmony-leak-corpus.json" with { type: "json" };
import { createAssistantMessage } from "./helpers";
+7
View File
@@ -2,11 +2,18 @@
## [Unreleased]
### Breaking Changes
- The model catalog moved to the new `@oh-my-pi/pi-catalog` package. Deep subpath exports `@oh-my-pi/pi-ai/models.json`, `/models`, `/model-cache`, `/model-manager`, `/model-thinking`, `/effort`, `/provider-models*`, `/utils/discovery*`, `/providers/openai-codex/constants`, `/providers/google-gemini-headers`, and `/providers/openai-completions-compat` are gone — import the `@oh-my-pi/pi-catalog` equivalents (`/models.json`, `/models`, `/model-cache`, `/model-manager`, `/model-thinking`, `/effort`, `/provider-models*`, `/discovery*`, `/wire/codex`, `/wire/gemini-headers`, `/compat/openai`). The pi-ai root barrel re-exports only the model/effort *types* its own signatures use (`Model`, `Api`, `ThinkingConfig`, `Effort`, `Usage`, compat interfaces) — catalog *values* (`getBundledModel(s)`, `calculateCost`, `modelsAreEqual`, `clampThinkingLevelForModel`, `DEFAULT_MODEL_PER_PROVIDER`, …) must be imported from `@oh-my-pi/pi-catalog`.
- `ProviderDefinition` is now auth-only: `defaultModel`, `createModelManagerOptions`, `catalogDiscovery`, `dynamicModelsAuthoritative`, `allowUnauthenticated`, and `specialModelManager` moved to pi-catalog's `CATALOG_PROVIDERS` table, and `KnownProviderId` was replaced by pi-catalog's `KnownProvider` (registry completeness is enforced by a compile-time check against that union). The pure GitHub Copilot key/endpoint helpers moved from `registry/oauth/github-copilot` to `@oh-my-pi/pi-catalog/wire/github-copilot`.
### Changed
- Reduced idle-watchdog churn on the token hot path: the abort promise/listener is created once per stream instead of per yielded item, the deadline uses a persistent re-armed timer instead of a `setTimeout` create/destroy pair per delta, and the persistent race promises are re-minted every 1024 items so per-race reaction records cannot accumulate for the stream's whole life.
- Memoized Anthropic many-image downscaling by content-block identity, so long sessions with stable message objects no longer re-decode and re-encode every oversized image on each request and retry.
- Tool-argument validation errors now truncate embedded argument strings at 256 chars per field — a failed `write`-class call no longer echoes hundreds of KB of payload back to the model as the error message.
- Auth storage no longer issues per-boot no-op writes: the schema-version row is only rewritten when the recorded version actually changes, and the credential identity-key backfill skips rows whose derived identity is null — reopening a current-schema database now performs zero write transactions
- Plain provider env-var names moved to the catalog table: registry defs dropped their 48 `envKeys` literals (including the pure `$pickenv` pickers for `huggingface`/`qwen-portal`/`xai-oauth`), `getEnvApiKey` now derives those fallbacks from `CATALOG_PROVIDERS[].envVars`, and `envKeys` remains only for computed resolvers (Anthropic Foundry, Vertex ADC, Bedrock credential chains) and non-catalog providers (`kagi`, `tavily`, `parallel`, `perplexity`)
### Fixed
+2 -26
View File
@@ -34,11 +34,11 @@
"lint": "biome lint .",
"test": "bun test --parallel",
"fix": "biome check --write --unsafe .",
"fmt": "biome format --write .",
"generate-models": "bun scripts/generate-models.ts"
"fmt": "biome format --write ."
},
"dependencies": {
"@bufbuild/protobuf": "catalog:",
"@oh-my-pi/pi-catalog": "catalog:",
"@oh-my-pi/pi-utils": "catalog:",
"openai": "catalog:",
"partial-json": "catalog:",
@@ -80,26 +80,10 @@
"types": "./src/auth-gateway/*.ts",
"import": "./src/auth-gateway/*.ts"
},
"./models.json": {
"types": "./src/models.json.d.ts",
"import": "./src/models.json"
},
"./provider-models": {
"types": "./src/provider-models/index.ts",
"import": "./src/provider-models/index.ts"
},
"./provider-models/*": {
"types": "./src/provider-models/*.ts",
"import": "./src/provider-models/*.ts"
},
"./providers/*": {
"types": "./src/providers/*.ts",
"import": "./src/providers/*.ts"
},
"./providers/cursor/gen/*": {
"types": "./src/providers/cursor/gen/*.ts",
"import": "./src/providers/cursor/gen/*.ts"
},
"./providers/openai-codex/*": {
"types": "./src/providers/openai-codex/*.ts",
"import": "./src/providers/openai-codex/*.ts"
@@ -112,14 +96,6 @@
"types": "./src/utils/*.ts",
"import": "./src/utils/*.ts"
},
"./utils/discovery": {
"types": "./src/utils/discovery/index.ts",
"import": "./src/utils/discovery/index.ts"
},
"./utils/discovery/*": {
"types": "./src/utils/discovery/*.ts",
"import": "./src/utils/discovery/*.ts"
},
"./oauth": {
"types": "./src/registry/oauth/index.ts",
"import": "./src/registry/oauth/index.ts"
+1 -1
View File
@@ -74,7 +74,7 @@ const PASSTHROUGH_HEADER_NAMES: Record<string, true> = {
"openai-organization": true,
"openai-project": true,
"openai-beta": true,
// Codex / ChatGPT-OAuth backend headers (see openai-codex/constants.ts).
// Codex / ChatGPT-OAuth backend headers (see @oh-my-pi/pi-catalog/wire/codex).
// `session_id` and `conversation_id` thread the upstream session so prompt
// caching and per-conversation rate limiting work; `chatgpt-account-id` and
// `originator` identify the calling account and client surface.
+2 -1
View File
@@ -17,10 +17,11 @@
* POST /v1/messages → Anthropic messages in/out
* POST /v1/responses → OpenAI Responses in/out
*/
import { Effort } from "@oh-my-pi/pi-catalog/effort";
import { extractRetryHint, logger } from "@oh-my-pi/pi-utils";
import type { ApiKeyResolver } from "../auth-retry";
import type { AuthStorage } from "../auth-storage";
import { Effort } from "../effort";
import * as anthropicMessages from "../providers/anthropic-messages-server";
import * as openaiChat from "../providers/openai-chat-server";
import * as openaiResponses from "../providers/openai-responses-server";
+1 -1
View File
@@ -1,4 +1,4 @@
import type { Effort } from "../effort";
import type { Effort } from "@oh-my-pi/pi-catalog/effort";
import type {
AssistantMessage,
AssistantMessageEventStream,
-8
View File
@@ -5,13 +5,7 @@ export { type AuthGatewayBootOptions, type ModelResolver, startAuthGateway } fro
export * from "./auth-gateway/types";
export * from "./auth-retry";
export * from "./auth-storage";
export * from "./effort";
export * from "./model-cache";
export * from "./model-manager";
export * from "./model-thinking";
export * from "./models";
export * from "./provider-details";
export * from "./provider-models";
export * from "./providers/anthropic";
export * from "./providers/anthropic-client";
export * from "./providers/azure-openai-responses";
@@ -19,7 +13,6 @@ export type * from "./providers/cursor";
export * from "./providers/gitlab-duo";
export type * from "./providers/google";
export type * from "./providers/google-gemini-cli";
export * from "./providers/google-gemini-headers";
export type * from "./providers/google-vertex";
export * from "./providers/kimi";
export * from "./providers/mock";
@@ -42,7 +35,6 @@ export * from "./usage/minimax-code";
export * from "./usage/openai-codex";
export * from "./usage/zai";
export * from "./utils/anthropic-auth";
export * from "./utils/discovery";
export * from "./utils/event-stream";
export * from "./utils/overflow";
export * from "./utils/retry";
@@ -1,43 +0,0 @@
/**
* Provider descriptors and the default-model map, derived from the single-source
* provider registry (`../registry`).
*
* The descriptor/catalog types and guards now live in the registry; they are
* re-exported here for back-compat with `generate-models.ts` and existing
* `@oh-my-pi/pi-ai/provider-models` consumers.
*/
import { PROVIDER_REGISTRY } from "../registry";
import type { ProviderDescriptor } from "../registry/types";
import type { KnownProvider } from "../types";
export * from "../registry/types";
/**
* Runtime model-discovery descriptors: every registry provider that exposes a
* standard model-manager factory. Special-managed providers
* (`google-antigravity`/`google-gemini-cli`/`openai-codex`) are built bespoke in
* the coding-agent runtime and are excluded here.
*/
export const PROVIDER_DESCRIPTORS: readonly ProviderDescriptor[] = PROVIDER_REGISTRY.flatMap(provider => {
const { createModelManagerOptions } = provider;
if (!createModelManagerOptions || provider.specialModelManager) {
return [];
}
return [
{
providerId: provider.id,
defaultModel: provider.defaultModel ?? "",
createModelManagerOptions,
allowUnauthenticated: provider.allowUnauthenticated,
dynamicModelsAuthoritative: provider.dynamicModelsAuthoritative,
catalogDiscovery: provider.catalogDiscovery,
},
];
});
/** Default model IDs for all known providers, derived from the registry. */
export const DEFAULT_MODEL_PER_PROVIDER: Record<KnownProvider, string> = Object.fromEntries(
PROVIDER_REGISTRY.filter(provider => provider.defaultModel != null).map(
provider => [provider.id, provider.defaultModel] as [string, string],
),
) as Record<KnownProvider, string>;
+3 -3
View File
@@ -7,10 +7,10 @@
* Bun's native `HTTPS_PROXY` support.
*/
import type { Effort } from "@oh-my-pi/pi-catalog/effort";
import { mapEffortToAnthropicAdaptiveEffort, requireSupportedEffort } from "@oh-my-pi/pi-catalog/model-thinking";
import { calculateCost } from "@oh-my-pi/pi-catalog/models";
import { $env, $flag, extractHttpStatusFromError, fetchWithRetry } from "@oh-my-pi/pi-utils";
import type { Effort } from "../effort";
import { mapEffortToAnthropicAdaptiveEffort, requireSupportedEffort } from "../model-thinking";
import { calculateCost } from "../models";
import type {
Api,
AssistantMessage,
+10 -15
View File
@@ -2,6 +2,15 @@ import * as nodeCrypto from "node:crypto";
import * as fs from "node:fs";
import { scheduler } from "node:timers/promises";
import * as tls from "node:tls";
import {
hasOpus47ApiRestrictions,
isAnthropicFableOrMythosModel,
mapEffortToAnthropicAdaptiveEffort,
supportsMidConversationSystemMessages,
} from "@oh-my-pi/pi-catalog/model-thinking";
import { calculateCost } from "@oh-my-pi/pi-catalog/models";
import { isAnthropicOAuthToken } from "@oh-my-pi/pi-catalog/utils";
import { parseGitHubCopilotApiKey } from "@oh-my-pi/pi-catalog/wire/github-copilot";
import {
$env,
extractHttpStatusFromError,
@@ -12,15 +21,7 @@ import {
logger,
readSseEvents,
} from "@oh-my-pi/pi-utils";
import {
hasOpus47ApiRestrictions,
isAnthropicFableOrMythosModel,
mapEffortToAnthropicAdaptiveEffort,
supportsMidConversationSystemMessages,
} from "../model-thinking";
import { calculateCost } from "../models";
import { isUsageLimitError } from "../rate-limit-utils";
import { parseGitHubCopilotApiKey } from "../registry/oauth/github-copilot";
import { getEnvApiKey, OUTPUT_FALLBACK_BUFFER } from "../stream";
import type {
Api,
@@ -47,13 +48,7 @@ import type {
Usage,
} from "../types";
import { resolveServiceTier } from "../types";
import {
isAnthropicOAuthToken,
isRecord,
normalizeSystemPrompts,
normalizeToolCallId,
resolveCacheRetention,
} from "../utils";
import { isRecord, normalizeSystemPrompts, normalizeToolCallId, resolveCacheRetention } from "../utils";
import { createAbortSourceTracker } from "../utils/abort";
import { AssistantMessageEventStream } from "../utils/event-stream";
import { isFoundryEnabled } from "../utils/foundry";
+30 -30
View File
@@ -3,35 +3,7 @@ import * as fs from "node:fs/promises";
import http2 from "node:http2";
import { create, fromBinary, fromJson, type JsonValue, toBinary, toJson } from "@bufbuild/protobuf";
import { ValueSchema } from "@bufbuild/protobuf/wkt";
import { $env, extractHttpStatusFromError, sanitizeText } from "@oh-my-pi/pi-utils";
import { calculateCost } from "../models";
import type {
Api,
AssistantMessage,
Context,
CursorExecHandlerResult,
CursorExecHandlers,
CursorMcpCall,
CursorShellStreamCallbacks,
CursorToolResultHandler,
ImageContent,
Message,
Model,
StreamFunction,
StreamOptions,
TextContent,
ThinkingContent,
Tool,
ToolCall,
ToolResultMessage,
} from "../types";
import { normalizeSystemPrompts } from "../utils";
import { AssistantMessageEventStream } from "../utils/event-stream";
import { parseStreamingJson } from "../utils/json-parse";
import { createRequestDebugSession, isRequestDebugEnabled, type RequestDebugResponseLog } from "../utils/request-debug";
import { formatErrorMessageWithRetryAfter } from "../utils/retry-after";
import { toolWireSchema } from "../utils/schema/wire";
import type { McpToolDefinition } from "./cursor/gen/agent_pb";
import type { McpToolDefinition } from "@oh-my-pi/pi-catalog/discovery/cursor-gen/agent_pb";
import {
AgentClientMessageSchema,
AgentConversationTurnStructureSchema,
@@ -128,7 +100,35 @@ import {
WriteShellStdinErrorSchema,
WriteShellStdinResultSchema,
WriteSuccessSchema,
} from "./cursor/gen/agent_pb";
} from "@oh-my-pi/pi-catalog/discovery/cursor-gen/agent_pb";
import { calculateCost } from "@oh-my-pi/pi-catalog/models";
import { $env, extractHttpStatusFromError, sanitizeText } from "@oh-my-pi/pi-utils";
import type {
Api,
AssistantMessage,
Context,
CursorExecHandlerResult,
CursorExecHandlers,
CursorMcpCall,
CursorShellStreamCallbacks,
CursorToolResultHandler,
ImageContent,
Message,
Model,
StreamFunction,
StreamOptions,
TextContent,
ThinkingContent,
Tool,
ToolCall,
ToolResultMessage,
} from "../types";
import { normalizeSystemPrompts } from "../utils";
import { AssistantMessageEventStream } from "../utils/event-stream";
import { parseStreamingJson } from "../utils/json-parse";
import { createRequestDebugSession, isRequestDebugEnabled, type RequestDebugResponseLog } from "../utils/request-debug";
import { formatErrorMessageWithRetryAfter } from "../utils/retry-after";
import { toolWireSchema } from "../utils/schema/wire";
export const CURSOR_API_URL = "https://api2.cursor.sh";
export const CURSOR_CLIENT_VERSION = "cli-2026.01.09-231024f";
@@ -1,4 +1,4 @@
import { getGitHubCopilotBaseUrl, parseGitHubCopilotApiKey } from "../registry/oauth/github-copilot";
import { getGitHubCopilotBaseUrl, parseGitHubCopilotApiKey } from "@oh-my-pi/pi-catalog/wire/github-copilot";
import type { Message } from "../types";
/**
* Infer whether the current request to Copilot is user-initiated or agent-initiated.
@@ -5,8 +5,13 @@
*/
import { createHash, randomBytes, randomUUID } from "node:crypto";
import { scheduler } from "node:timers/promises";
import { calculateCost } from "@oh-my-pi/pi-catalog/models";
import {
ANTIGRAVITY_SYSTEM_INSTRUCTION,
getAntigravityUserAgent,
getGeminiCliHeaders,
} from "@oh-my-pi/pi-catalog/wire/gemini-headers";
import { extractHttpStatusFromError, fetchWithRetry, readSseJson } from "@oh-my-pi/pi-utils";
import { calculateCost } from "../models";
import type {
Api,
AssistantMessage,
@@ -24,7 +29,6 @@ import { appendRawHttpRequestDumpFor400, type RawHttpRequestDump, withHttpStatus
// Refresh is the sole responsibility of AuthStorage (broker-aware, single-flighted);
// the stream provider trusts the access token threaded through `options.apiKey`.
import { normalizeSchemaForCCA } from "../utils/schema";
import { ANTIGRAVITY_SYSTEM_INSTRUCTION, getAntigravityUserAgent, getGeminiCliHeaders } from "./google-gemini-headers";
import type { Content, FunctionCallingConfigMode, ThinkingConfig } from "./google-shared";
import {
convertMessages,
@@ -80,7 +84,7 @@ export {
getAntigravityUserAgent,
getGeminiCliHeaders,
getGeminiCliUserAgent,
} from "./google-gemini-headers";
} from "@oh-my-pi/pi-catalog/wire/gemini-headers";
// Retry configuration
const MAX_RETRIES = 3;
+1 -1
View File
@@ -2,8 +2,8 @@
* Shared utilities for Google Generative AI and Google Cloud Code Assist providers.
*/
import { calculateCost } from "@oh-my-pi/pi-catalog/models";
import { extractHttpStatusFromError, readSseJson } from "@oh-my-pi/pi-utils";
import { calculateCost } from "../models";
import type {
Api,
AssistantMessage,
@@ -1,5 +1,12 @@
import * as os from "node:os";
import { scheduler } from "node:timers/promises";
import { calculateCost } from "@oh-my-pi/pi-catalog/models";
import {
CODEX_BASE_URL,
getCodexAccountId,
OPENAI_HEADER_VALUES,
OPENAI_HEADERS,
} from "@oh-my-pi/pi-catalog/wire/codex";
import {
$env,
$flag,
@@ -20,7 +27,6 @@ import type {
ResponseReasoningItem,
} from "openai/resources/responses/responses";
import packageJson from "../../package.json" with { type: "json" };
import { calculateCost } from "../models";
import { getEnvApiKey } from "../stream";
import {
type Api,
@@ -58,7 +64,6 @@ import { createRequestDebugSession, isRequestDebugEnabled, type RequestDebugResp
import { adaptSchemaForStrict, NO_STRICT, sanitizeSchemaForOpenAIResponses, toolWireSchema } from "../utils/schema";
import { notifyRawSseEvent } from "../utils/sse-debug";
import { compactGrammarDefinition } from "./grammar";
import { CODEX_BASE_URL, getCodexAccountId, OPENAI_HEADER_VALUES, OPENAI_HEADERS } from "./openai-codex/constants";
import {
type CodexRequestOptions,
type InputItem,
@@ -1,5 +1,5 @@
import type { Effort } from "../../effort";
import { requireSupportedEffort } from "../../model-thinking";
import type { Effort } from "@oh-my-pi/pi-catalog/effort";
import { requireSupportedEffort } from "@oh-my-pi/pi-catalog/model-thinking";
import type { Api, Model } from "../../types";
export interface ReasoningConfig {
@@ -1,4 +1,4 @@
import { toNumber } from "../../utils";
import { toNumber } from "@oh-my-pi/pi-catalog/utils";
export type CodexRateLimit = {
used_percent?: number;
@@ -1,3 +1,9 @@
import { detectOpenAICompat, type ResolvedOpenAICompat, resolveOpenAICompat } from "@oh-my-pi/pi-catalog/compat/openai";
import type { Effort } from "@oh-my-pi/pi-catalog/effort";
import { toFirepassWireModelId, toFireworksWireModelId } from "@oh-my-pi/pi-catalog/fireworks-model-id";
import { getSupportedEfforts } from "@oh-my-pi/pi-catalog/model-thinking";
import { calculateCost } from "@oh-my-pi/pi-catalog/models";
import { parseGitHubCopilotApiKey } from "@oh-my-pi/pi-catalog/wire/github-copilot";
import { $env, extractHttpStatusFromError } from "@oh-my-pi/pi-utils";
import OpenAI, { APIConnectionTimeoutError as OpenAIConnectionTimeoutError } from "openai";
import type {
@@ -10,10 +16,6 @@ import type {
ChatCompletionToolMessageParam,
} from "openai/resources/chat/completions";
import packageJson from "../../package.json" with { type: "json" };
import type { Effort } from "../effort";
import { getSupportedEfforts } from "../model-thinking";
import { calculateCost } from "../models";
import { parseGitHubCopilotApiKey } from "../registry/oauth/github-copilot";
import { getKimiCommonHeaders } from "../registry/oauth/kimi";
import { getEnvApiKey } from "../stream";
import {
@@ -43,7 +45,6 @@ import {
import { normalizeSystemPrompts } from "../utils";
import { createAbortSourceTracker } from "../utils/abort";
import { AssistantMessageEventStream } from "../utils/event-stream";
import { toFirepassWireModelId, toFireworksWireModelId } from "../utils/fireworks-model-id";
import {
type CapturedHttpErrorResponse,
finalizeErrorMessage,
@@ -73,7 +74,6 @@ import {
hasCopilotVisionInput,
resolveGitHubCopilotBaseUrl,
} from "./github-copilot-headers";
import { detectOpenAICompat, type ResolvedOpenAICompat, resolveOpenAICompat } from "./openai-completions-compat";
import { createInitialResponsesAssistantMessage } from "./openai-responses-shared";
import { transformMessages } from "./transform-messages";
import {
@@ -1,3 +1,4 @@
import { calculateCost } from "@oh-my-pi/pi-catalog/models";
import { logger, structuredCloneJSON } from "@oh-my-pi/pi-utils";
import type OpenAI from "openai";
import type {
@@ -11,7 +12,6 @@ import type {
ResponseOutputMessage,
ResponseReasoningItem,
} from "openai/resources/responses/responses";
import { calculateCost } from "../models";
import {
type Api,
type AssistantMessage,
@@ -1,3 +1,4 @@
import { parseGitHubCopilotApiKey } from "@oh-my-pi/pi-catalog/wire/github-copilot";
import { $env, extractHttpStatusFromError } from "@oh-my-pi/pi-utils";
import OpenAI, { APIConnectionTimeoutError as OpenAIConnectionTimeoutError } from "openai";
import type {
@@ -6,7 +7,6 @@ import type {
ResponseInput,
ResponseStreamEvent,
} from "openai/resources/responses/responses";
import { parseGitHubCopilotApiKey } from "../registry/oauth/github-copilot";
import { getEnvApiKey } from "../stream";
import type {
AssistantMessage,
+1 -7
View File
@@ -1,12 +1,6 @@
import { aimlApiModelManagerOptions } from "../provider-models/openai-compat";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
export const aimlApiProvider = {
id: "aimlapi",
name: "AIML API",
defaultModel: "gpt-4o",
createModelManagerOptions: (config: ModelManagerConfig) => aimlApiModelManagerOptions(config),
dynamicModelsAuthoritative: true,
catalogDiscovery: { label: "AIML API", envVars: ["AIMLAPI_API_KEY"] },
envKeys: "AIMLAPI_API_KEY",
} as const satisfies ProviderDefinition;
@@ -1,7 +1,6 @@
import { alibabaCodingPlanModelManagerOptions } from "../provider-models/openai-compat";
import { validateOpenAICompatibleApiKey } from "./api-key-validation";
import type { OAuthController, OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
const AUTH_URL = "https://modelstudio.console.alibabacloud.com/";
const API_BASE_URL = "https://coding-intl.dashscope.aliyuncs.com/v1";
@@ -46,9 +45,5 @@ export async function loginAlibabaCodingPlan(options: OAuthController): Promise<
export const alibabaCodingPlanProvider = {
id: "alibaba-coding-plan",
name: "Alibaba Coding Plan",
defaultModel: "qwen3.5-plus",
createModelManagerOptions: (config: ModelManagerConfig) => alibabaCodingPlanModelManagerOptions(config),
catalogDiscovery: { label: "Alibaba Coding Plan", envVars: ["ALIBABA_CODING_PLAN_API_KEY"] },
envKeys: "ALIBABA_CODING_PLAN_API_KEY",
login: (cb: OAuthLoginCallbacks) => loginAlibabaCodingPlan(cb),
} as const satisfies ProviderDefinition;
@@ -4,7 +4,6 @@ import type { ProviderDefinition } from "./types";
export const amazonBedrockProvider = {
id: "amazon-bedrock",
name: "Amazon Bedrock",
defaultModel: "us.anthropic.claude-opus-4-6-v1",
// Amazon Bedrock accepts bearer tokens, IAM keys, profiles, ECS/IRSA credential chains.
envKeys: () => {
const hasEcsCredentials =
+1 -4
View File
@@ -1,14 +1,11 @@
import { $pickenv } from "@oh-my-pi/pi-utils";
import { anthropicModelManagerOptions } from "../provider-models/openai-compat";
import { isFoundryEnabled } from "../utils/foundry";
import type { OAuthCredentials, OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
export const anthropicProvider = {
id: "anthropic",
name: "Anthropic (Claude Pro/Max)",
defaultModel: "claude-opus-4-6",
createModelManagerOptions: (config: ModelManagerConfig) => anthropicModelManagerOptions(config),
// Foundry mode optionally switches Anthropic auth to enterprise gateway credentials.
envKeys: () =>
isFoundryEnabled()
+1 -6
View File
@@ -1,7 +1,6 @@
import { cerebrasModelManagerOptions } from "../provider-models/openai-compat";
import { createApiKeyLogin } from "./api-key-login";
import type { OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
export const loginCerebras = createApiKeyLogin({
providerLabel: "Cerebras",
@@ -20,9 +19,5 @@ export const loginCerebras = createApiKeyLogin({
export const cerebrasProvider = {
id: "cerebras",
name: "Cerebras",
defaultModel: "zai-glm-4.6",
createModelManagerOptions: (config: ModelManagerConfig) => cerebrasModelManagerOptions(config),
catalogDiscovery: { label: "Cerebras", envVars: ["CEREBRAS_API_KEY"] },
envKeys: "CEREBRAS_API_KEY",
login: (cb: OAuthLoginCallbacks) => loginCerebras(cb),
} as const satisfies ProviderDefinition;
@@ -1,6 +1,5 @@
import { cloudflareAiGatewayModelManagerOptions } from "../provider-models/openai-compat";
import type { OAuthController, OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
const AUTH_URL = "https://developers.cloudflare.com/ai-gateway/configuration/authentication/";
@@ -41,9 +40,5 @@ export async function loginCloudflareAiGateway(options: OAuthController): Promis
export const cloudflareAiGatewayProvider = {
id: "cloudflare-ai-gateway",
name: "Cloudflare AI Gateway",
defaultModel: "claude-sonnet-4-5",
createModelManagerOptions: (config: ModelManagerConfig) => cloudflareAiGatewayModelManagerOptions(config),
catalogDiscovery: { label: "Cloudflare AI Gateway", envVars: ["CLOUDFLARE_AI_GATEWAY_API_KEY"] },
envKeys: "CLOUDFLARE_AI_GATEWAY_API_KEY",
login: (cb: OAuthLoginCallbacks) => loginCloudflareAiGateway(cb),
} as const satisfies ProviderDefinition;
+1 -6
View File
@@ -1,14 +1,9 @@
import { cursorModelManagerOptions } from "../provider-models/special";
import type { OAuthCredentials, OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
export const cursorProvider = {
id: "cursor",
name: "Cursor (Claude, GPT, etc.)",
defaultModel: "claude-sonnet-4-6",
createModelManagerOptions: (config: ModelManagerConfig) => cursorModelManagerOptions(config),
catalogDiscovery: { label: "Cursor", envVars: ["CURSOR_API_KEY"], oauthProvider: "cursor" },
envKeys: "CURSOR_ACCESS_TOKEN",
login: async (cb: OAuthLoginCallbacks) => {
// Lazy import: keep heavy OAuth flow modules out of the eager registry graph.
const { loginCursor } = await import("./oauth/cursor");
+1 -6
View File
@@ -1,7 +1,6 @@
import { deepseekModelManagerOptions } from "../provider-models/openai-compat";
import { createApiKeyLogin } from "./api-key-login";
import type { OAuthController, OAuthLoginCallbacks, OAuthPrompt } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
const innerLogin = createApiKeyLogin({
providerLabel: "DeepSeek",
@@ -42,9 +41,5 @@ export const loginDeepSeek = async (options: OAuthController): Promise<string> =
export const deepseekProvider = {
id: "deepseek",
name: "DeepSeek",
defaultModel: "deepseek-v4-pro",
createModelManagerOptions: (config: ModelManagerConfig) => deepseekModelManagerOptions(config),
catalogDiscovery: { label: "DeepSeek", envVars: ["DEEPSEEK_API_KEY"] },
envKeys: "DEEPSEEK_API_KEY",
login: (cb: OAuthLoginCallbacks) => loginDeepSeek(cb),
} as const satisfies ProviderDefinition;
+1 -5
View File
@@ -1,7 +1,6 @@
import { firepassModelManagerOptions } from "../provider-models/openai-compat";
import { createApiKeyLogin } from "./api-key-login";
import type { OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
/**
* Fire Pass login flow.
@@ -29,8 +28,5 @@ export const loginFirepass = createApiKeyLogin({
export const firepassProvider = {
id: "firepass",
name: "Fire Pass (Fireworks Kimi K2.6 Turbo subscription)",
defaultModel: "kimi-k2.6-turbo",
createModelManagerOptions: (config: ModelManagerConfig) => firepassModelManagerOptions(config),
envKeys: "FIREPASS_API_KEY",
login: (cb: OAuthLoginCallbacks) => loginFirepass(cb),
} as const satisfies ProviderDefinition;
+1 -6
View File
@@ -1,7 +1,6 @@
import { fireworksModelManagerOptions } from "../provider-models/openai-compat";
import { createApiKeyLogin } from "./api-key-login";
import type { OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
export const loginFireworks = createApiKeyLogin({
providerLabel: "Fireworks",
@@ -19,9 +18,5 @@ export const loginFireworks = createApiKeyLogin({
export const fireworksProvider = {
id: "fireworks",
name: "Fireworks",
defaultModel: "kimi-k2.6",
createModelManagerOptions: (config: ModelManagerConfig) => fireworksModelManagerOptions(config),
catalogDiscovery: { label: "Fireworks", envVars: ["FIREWORKS_API_KEY"] },
envKeys: "FIREWORKS_API_KEY",
login: (cb: OAuthLoginCallbacks) => loginFireworks(cb),
} as const satisfies ProviderDefinition;
+1 -5
View File
@@ -1,13 +1,9 @@
import { githubCopilotModelManagerOptions } from "../provider-models/openai-compat";
import type { OAuthCredentials, OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
export const githubCopilotProvider = {
id: "github-copilot",
name: "GitHub Copilot",
defaultModel: "gpt-4o",
createModelManagerOptions: (config: ModelManagerConfig) => githubCopilotModelManagerOptions(config),
envKeys: "COPILOT_GITHUB_TOKEN",
login: async (cb: OAuthLoginCallbacks) => {
// Lazy import: keep heavy OAuth flow modules out of the eager registry graph.
const { loginGitHubCopilot } = await import("./oauth/github-copilot");
-2
View File
@@ -4,8 +4,6 @@ import type { ProviderDefinition } from "./types";
export const gitlabDuoProvider = {
id: "gitlab-duo",
name: "GitLab Duo",
defaultModel: "duo-chat-sonnet-4-5",
envKeys: "GITLAB_TOKEN",
login: async (cb: OAuthLoginCallbacks) => {
// Lazy import: keep heavy OAuth flow modules out of the eager registry graph.
const { loginGitLabDuo } = await import("./oauth/gitlab-duo");
@@ -4,8 +4,6 @@ import type { ProviderDefinition } from "./types";
export const googleAntigravityProvider = {
id: "google-antigravity",
name: "Antigravity (Gemini 3, Claude, GPT-OSS)",
defaultModel: "gemini-3-pro-high",
specialModelManager: true,
login: async (cb: OAuthLoginCallbacks) => {
// Lazy import: keep heavy OAuth flow modules out of the eager registry graph.
const { loginAntigravity } = await import("./oauth/google-antigravity");
@@ -4,8 +4,6 @@ import type { ProviderDefinition } from "./types";
export const googleGeminiCliProvider = {
id: "google-gemini-cli",
name: "Google Cloud Code Assist (Gemini CLI)",
defaultModel: "gemini-2.5-pro",
specialModelManager: true,
login: async (cb: OAuthLoginCallbacks) => {
// Lazy import: keep heavy OAuth flow modules out of the eager registry graph.
const { loginGeminiCli } = await import("./oauth/google-gemini-cli");
+1 -5
View File
@@ -2,8 +2,7 @@ import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { $env } from "@oh-my-pi/pi-utils";
import { googleVertexModelManagerOptions } from "../provider-models/google";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
let cachedVertexAdcCredentialsExists: boolean | null = null;
@@ -24,9 +23,6 @@ function hasVertexAdcCredentials(): boolean {
export const googleVertexProvider = {
id: "google-vertex",
name: "Google Vertex AI",
defaultModel: "gemini-3-pro-preview",
createModelManagerOptions: (config: ModelManagerConfig) => googleVertexModelManagerOptions(config),
allowUnauthenticated: true,
// Vertex AI supports either GOOGLE_CLOUD_API_KEY or Application Default Credentials.
envKeys: () => {
if ($env.GOOGLE_CLOUD_API_KEY) {
+1 -5
View File
@@ -1,10 +1,6 @@
import { googleModelManagerOptions } from "../provider-models/google";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
export const googleProvider = {
id: "google",
name: "Google Gemini",
defaultModel: "gemini-2.5-pro",
createModelManagerOptions: (config: ModelManagerConfig) => googleModelManagerOptions(config),
envKeys: "GEMINI_API_KEY",
} as const satisfies ProviderDefinition;
+1 -5
View File
@@ -1,10 +1,6 @@
import { groqModelManagerOptions } from "../provider-models/openai-compat";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
export const groqProvider = {
id: "groq",
name: "Groq",
defaultModel: "openai/gpt-oss-120b",
createModelManagerOptions: (config: ModelManagerConfig) => groqModelManagerOptions(config),
envKeys: "GROQ_API_KEY",
} as const satisfies ProviderDefinition;
+1 -7
View File
@@ -1,8 +1,6 @@
import { $pickenv } from "@oh-my-pi/pi-utils";
import { huggingfaceModelManagerOptions } from "../provider-models/openai-compat";
import { validateOpenAICompatibleApiKey } from "./api-key-validation";
import type { OAuthController, OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
const AUTH_URL =
"https://huggingface.co/settings/tokens/new?ownUserPermissions=inference.serverless.write&tokenType=fineGrained";
@@ -49,9 +47,5 @@ export async function loginHuggingface(options: OAuthController): Promise<string
export const huggingfaceProvider = {
id: "huggingface",
name: "Hugging Face Inference",
defaultModel: "deepseek-ai/DeepSeek-R1",
createModelManagerOptions: (config: ModelManagerConfig) => huggingfaceModelManagerOptions(config),
catalogDiscovery: { label: "Hugging Face", envVars: ["HUGGINGFACE_HUB_TOKEN", "HF_TOKEN"] },
envKeys: () => $pickenv("HUGGINGFACE_HUB_TOKEN", "HF_TOKEN"),
login: (cb: OAuthLoginCallbacks) => loginHuggingface(cb),
} as const satisfies ProviderDefinition;
+1 -6
View File
@@ -1,6 +1,5 @@
import { kiloModelManagerOptions } from "../provider-models/openai-compat";
import type { OAuthController, OAuthCredentials } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
const KILO_DEVICE_AUTH_BASE_URL = "https://api.kilo.ai/api/device-auth";
const POLL_INTERVAL_MS = 5000;
@@ -89,9 +88,5 @@ export async function loginKilo(callbacks: OAuthController): Promise<OAuthCreden
export const kiloProvider = {
id: "kilo",
name: "Kilo Gateway",
defaultModel: "anthropic/claude-sonnet-4.5",
createModelManagerOptions: (config: ModelManagerConfig) => kiloModelManagerOptions(config),
catalogDiscovery: { label: "Kilo Gateway", envVars: ["KILO_API_KEY"], allowUnauthenticated: true },
envKeys: "KILO_API_KEY",
login: loginKilo,
} as const satisfies ProviderDefinition;
+1 -5
View File
@@ -1,13 +1,9 @@
import { kimiCodeModelManagerOptions } from "../provider-models/openai-compat";
import type { OAuthCredentials, OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
export const kimiCodeProvider = {
id: "kimi-code",
name: "Kimi Code",
defaultModel: "kimi-k2.5",
createModelManagerOptions: (config: ModelManagerConfig) => kimiCodeModelManagerOptions(config),
catalogDiscovery: { label: "Kimi Code", envVars: ["KIMI_API_KEY"] },
login: async (cb: OAuthLoginCallbacks) => {
// Lazy import: keep heavy OAuth flow modules out of the eager registry graph.
const { loginKimi } = await import("./oauth/kimi");
+1 -6
View File
@@ -1,6 +1,5 @@
import { litellmModelManagerOptions } from "../provider-models/openai-compat";
import type { OAuthController, OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
const AUTH_URL = "https://docs.litellm.ai/docs/proxy/deploy";
@@ -40,9 +39,5 @@ export async function loginLiteLLM(options: OAuthController): Promise<string> {
export const litellmProvider = {
id: "litellm",
name: "LiteLLM",
defaultModel: "claude-opus-4-6",
createModelManagerOptions: (config: ModelManagerConfig) => litellmModelManagerOptions(config),
catalogDiscovery: { label: "LiteLLM", envVars: ["LITELLM_API_KEY"], allowUnauthenticated: true },
envKeys: "LITELLM_API_KEY",
login: (cb: OAuthLoginCallbacks) => loginLiteLLM(cb),
} as const satisfies ProviderDefinition;
+1 -6
View File
@@ -1,6 +1,5 @@
import { lmStudioModelManagerOptions } from "../provider-models/openai-compat";
import type { OAuthController, OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
const PROVIDER_ID = "lm-studio";
export const DEFAULT_LOCAL_TOKEN = "lm-studio-local";
@@ -27,9 +26,5 @@ export async function loginLmStudio(options: OAuthController): Promise<string> {
export const lmStudioProvider = {
id: "lm-studio",
name: "LM Studio (Local OpenAI-compatible)",
defaultModel: "llama-3-8b",
createModelManagerOptions: (config: ModelManagerConfig) => lmStudioModelManagerOptions(config),
allowUnauthenticated: true,
envKeys: "LM_STUDIO_API_KEY",
login: (cb: OAuthLoginCallbacks) => loginLmStudio(cb),
} as const satisfies ProviderDefinition;
@@ -4,8 +4,6 @@ import type { ProviderDefinition } from "./types";
export const minimaxCodeCnProvider = {
id: "minimax-code-cn",
name: "MiniMax Coding Plan (China)",
defaultModel: "MiniMax-M2.5",
envKeys: "MINIMAX_CODE_CN_API_KEY",
login: async (cb: OAuthLoginCallbacks) => {
// Lazy import: keep heavy OAuth flow modules out of the eager registry graph.
const { loginMiniMaxCodeCn } = await import("./oauth/minimax-code");
-2
View File
@@ -4,8 +4,6 @@ import type { ProviderDefinition } from "./types";
export const minimaxCodeProvider = {
id: "minimax-code",
name: "MiniMax Coding Plan (International)",
defaultModel: "MiniMax-M2.5",
envKeys: "MINIMAX_CODE_API_KEY",
login: async (cb: OAuthLoginCallbacks) => {
// Lazy import: keep heavy OAuth flow modules out of the eager registry graph.
const { loginMiniMaxCode } = await import("./oauth/minimax-code");
-2
View File
@@ -3,6 +3,4 @@ import type { ProviderDefinition } from "./types";
export const minimaxProvider = {
id: "minimax",
name: "MiniMax",
defaultModel: "MiniMax-M2.5",
envKeys: "MINIMAX_API_KEY",
} as const satisfies ProviderDefinition;
+1 -5
View File
@@ -1,10 +1,6 @@
import { mistralModelManagerOptions } from "../provider-models/openai-compat";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
export const mistralProvider = {
id: "mistral",
name: "Mistral",
defaultModel: "devstral-medium-latest",
createModelManagerOptions: (config: ModelManagerConfig) => mistralModelManagerOptions(config),
envKeys: "MISTRAL_API_KEY",
} as const satisfies ProviderDefinition;
+1 -6
View File
@@ -1,7 +1,6 @@
import { moonshotModelManagerOptions } from "../provider-models/openai-compat";
import { createApiKeyLogin } from "./api-key-login";
import type { OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
export const loginMoonshot = createApiKeyLogin({
providerLabel: "Moonshot",
@@ -19,9 +18,5 @@ export const loginMoonshot = createApiKeyLogin({
export const moonshotProvider = {
id: "moonshot",
name: "Moonshot (Kimi API)",
defaultModel: "kimi-k2.5",
createModelManagerOptions: (config: ModelManagerConfig) => moonshotModelManagerOptions(config),
catalogDiscovery: { label: "Moonshot", envVars: ["MOONSHOT_API_KEY"] },
envKeys: "MOONSHOT_API_KEY",
login: (cb: OAuthLoginCallbacks) => loginMoonshot(cb),
} as const satisfies ProviderDefinition;
+1 -6
View File
@@ -1,7 +1,6 @@
import { nanoGptModelManagerOptions } from "../provider-models/openai-compat";
import { createApiKeyLogin } from "./api-key-login";
import type { OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
export const loginNanoGPT = createApiKeyLogin({
providerLabel: "NanoGPT",
@@ -19,9 +18,5 @@ export const loginNanoGPT = createApiKeyLogin({
export const nanogptProvider = {
id: "nanogpt",
name: "NanoGPT",
defaultModel: "openai/gpt-5.4",
createModelManagerOptions: (config: ModelManagerConfig) => nanoGptModelManagerOptions(config),
catalogDiscovery: { label: "NanoGPT", envVars: ["NANO_GPT_API_KEY"] },
envKeys: "NANO_GPT_API_KEY",
login: (cb: OAuthLoginCallbacks) => loginNanoGPT(cb),
} as const satisfies ProviderDefinition;
+1 -6
View File
@@ -1,7 +1,6 @@
import { nvidiaModelManagerOptions } from "../provider-models/openai-compat";
import { validateOpenAICompatibleApiKey } from "./api-key-validation";
import type { OAuthController, OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
const AUTH_URL = "https://org.ngc.nvidia.com/setup/personal-keys";
const API_BASE_URL = "https://integrate.api.nvidia.com/v1";
@@ -57,9 +56,5 @@ export async function loginNvidia(options: OAuthController): Promise<string> {
export const nvidiaProvider = {
id: "nvidia",
name: "NVIDIA",
defaultModel: "nvidia/llama-3.1-nemotron-70b-instruct",
createModelManagerOptions: (config: ModelManagerConfig) => nvidiaModelManagerOptions(config),
catalogDiscovery: { label: "NVIDIA", envVars: ["NVIDIA_API_KEY"] },
envKeys: "NVIDIA_API_KEY",
login: (cb: OAuthLoginCallbacks) => loginNvidia(cb),
} as const satisfies ProviderDefinition;
@@ -2,18 +2,19 @@
* GitHub Copilot OAuth flow (opencode OAuth app)
*/
import { scheduler } from "node:timers/promises";
import { getBundledModels } from "../../models";
import { getBundledModels } from "@oh-my-pi/pi-catalog/models";
import {
getGitHubCopilotBaseUrl,
isPublicGitHubHost,
normalizeDomain,
normalizeGitHubCopilotEnterpriseDomain,
OPENCODE_HEADERS,
} from "@oh-my-pi/pi-catalog/wire/github-copilot";
import type { FetchImpl } from "../../types";
import type { OAuthCredentials } from "./types";
const CLIENT_ID = "Ov23li8tweQw6odWQebz";
export const COPILOT_USER_AGENT = "opencode/1.3.15" as const;
export const OPENCODE_HEADERS = {
"User-Agent": COPILOT_USER_AGENT,
} as const;
const INITIAL_POLL_INTERVAL_MULTIPLIER = 1.2;
const SLOW_DOWN_POLL_INTERVAL_MULTIPLIER = 1.4;
@@ -46,58 +47,6 @@ type DeviceTokenErrorResponse = {
interval?: number;
};
type GitHubCopilotApiKeyPayload = {
token?: unknown;
enterpriseUrl?: unknown;
};
export type ParsedGitHubCopilotApiKey = {
accessToken: string;
enterpriseUrl?: string;
};
const PUBLIC_GITHUB_HOSTS = new Set(["api.github.com", "github.com", "www.github.com"]);
function isPublicGitHubHost(host: string): boolean {
return PUBLIC_GITHUB_HOSTS.has(host.trim().toLowerCase());
}
export function normalizeGitHubCopilotEnterpriseDomain(input: string | undefined): string | undefined {
const trimmed = input?.trim();
if (!trimmed) return undefined;
const normalized = normalizeDomain(trimmed) ?? trimmed.toLowerCase();
if (!normalized || isPublicGitHubHost(normalized)) return undefined;
return normalized;
}
export function parseGitHubCopilotApiKey(apiKeyRaw: string): ParsedGitHubCopilotApiKey {
try {
const parsed = JSON.parse(apiKeyRaw) as GitHubCopilotApiKeyPayload;
if (typeof parsed.token === "string") {
return {
accessToken: parsed.token,
enterpriseUrl:
typeof parsed.enterpriseUrl === "string"
? normalizeGitHubCopilotEnterpriseDomain(parsed.enterpriseUrl)
: undefined,
};
}
} catch {}
return { accessToken: apiKeyRaw };
}
export function normalizeDomain(input: string): string | null {
const trimmed = input.trim();
if (!trimmed) return null;
try {
const url = trimmed.includes("://") ? new URL(trimmed) : new URL(`https://${trimmed}`);
return url.hostname;
} catch {
return null;
}
}
function getUrls(domain: string): {
deviceCodeUrl: string;
accessTokenUrl: string;
@@ -108,15 +57,6 @@ function getUrls(domain: string): {
};
}
export function getGitHubCopilotBaseUrl(enterpriseDomain?: string): string {
const normalizedEnterpriseDomain = normalizeGitHubCopilotEnterpriseDomain(enterpriseDomain);
if (!normalizedEnterpriseDomain) return "https://api.githubcopilot.com";
const host = normalizedEnterpriseDomain.startsWith("copilot-api.")
? normalizedEnterpriseDomain
: `copilot-api.${normalizedEnterpriseDomain}`;
return `https://${host}`;
}
async function fetchJson(url: string, init: RequestInit, fetchImpl: FetchImpl): Promise<unknown> {
const response = await fetchImpl(url, init);
if (!response.ok) {
@@ -2,7 +2,7 @@
* Antigravity OAuth flow (Gemini 3, Claude, GPT-OSS via Google Cloud)
* Uses different OAuth credentials than google-gemini-cli for access to additional models.
*/
import { getAntigravityUserAgent } from "../../providers/google-gemini-headers";
import { getAntigravityUserAgent } from "@oh-my-pi/pi-catalog/wire/gemini-headers";
import { runGoogleOAuthLogin } from "./google-oauth-shared";
import type { OAuthController, OAuthCredentials } from "./types";
@@ -3,8 +3,8 @@
* Standard Gemini models only (gemini-2.0-flash, gemini-2.5-*)
*/
import { getGeminiCliHeaders } from "@oh-my-pi/pi-catalog/wire/gemini-headers";
import { $env } from "@oh-my-pi/pi-utils";
import { getGeminiCliHeaders } from "../../providers/google-gemini-headers";
import { runGoogleOAuthLogin } from "./google-oauth-shared";
import type { OAuthController, OAuthCredentials } from "./types";
+1 -6
View File
@@ -1,6 +1,5 @@
import { ollamaCloudModelManagerOptions } from "../provider-models/ollama";
import type { OAuthController, OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
const OLLAMA_CLOUD_KEYS_URL = "https://ollama.com/settings/keys";
@@ -32,9 +31,5 @@ export async function loginOllamaCloud(options: OAuthController): Promise<string
export const ollamaCloudProvider = {
id: "ollama-cloud",
name: "Ollama Cloud",
defaultModel: "gpt-oss:120b",
createModelManagerOptions: (config: ModelManagerConfig) => ollamaCloudModelManagerOptions(config),
catalogDiscovery: { label: "Ollama Cloud", envVars: ["OLLAMA_CLOUD_API_KEY"], oauthProvider: "ollama-cloud" },
envKeys: "OLLAMA_CLOUD_API_KEY",
login: (cb: OAuthLoginCallbacks) => loginOllamaCloud(cb),
} as const satisfies ProviderDefinition;
+1 -6
View File
@@ -1,6 +1,5 @@
import { ollamaModelManagerOptions } from "../provider-models/openai-compat";
import type { OAuthController } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
const OLLAMA_DOCS_URL = "https://github.com/ollama/ollama/blob/main/docs/api.md";
@@ -39,9 +38,5 @@ export async function loginOllama(options: OAuthController): Promise<string> {
export const ollamaProvider = {
id: "ollama",
name: "Ollama (Local OpenAI-compatible)",
defaultModel: "gpt-oss:20b",
createModelManagerOptions: (config: ModelManagerConfig) => ollamaModelManagerOptions(config),
allowUnauthenticated: true,
login: loginOllama,
envKeys: "OLLAMA_API_KEY",
} as const satisfies ProviderDefinition;
-3
View File
@@ -4,9 +4,6 @@ import type { ProviderDefinition } from "./types";
export const openaiCodexProvider = {
id: "openai-codex",
name: "ChatGPT Plus/Pro (Codex Subscription)",
defaultModel: "gpt-5.4",
specialModelManager: true,
envKeys: "OPENAI_CODEX_OAUTH_TOKEN",
login: async (cb: OAuthLoginCallbacks) => {
// Lazy import: keep heavy OAuth flow modules out of the eager registry graph.
const { loginOpenAICodex } = await import("./oauth/openai-codex");
+1 -5
View File
@@ -1,10 +1,6 @@
import { openaiModelManagerOptions } from "../provider-models/openai-compat";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
export const openaiProvider = {
id: "openai",
name: "OpenAI",
defaultModel: "gpt-5.4",
createModelManagerOptions: (config: ModelManagerConfig) => openaiModelManagerOptions(config),
envKeys: "OPENAI_API_KEY",
} as const satisfies ProviderDefinition;
+1 -5
View File
@@ -1,13 +1,9 @@
import { opencodeGoModelManagerOptions } from "../provider-models/openai-compat";
import type { OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
export const opencodeGoProvider = {
id: "opencode-go",
name: "OpenCode Go",
defaultModel: "kimi-k2.5",
createModelManagerOptions: (config: ModelManagerConfig) => opencodeGoModelManagerOptions(config),
envKeys: "OPENCODE_API_KEY",
login: async (cb: OAuthLoginCallbacks) => {
// Lazy import: keep heavy OAuth flow modules out of the eager registry graph.
const { loginOpenCode } = await import("./oauth/opencode");
+1 -5
View File
@@ -1,13 +1,9 @@
import { opencodeZenModelManagerOptions } from "../provider-models/openai-compat";
import type { OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
export const opencodeZenProvider = {
id: "opencode-zen",
name: "OpenCode Zen",
defaultModel: "claude-sonnet-4-6",
createModelManagerOptions: (config: ModelManagerConfig) => opencodeZenModelManagerOptions(config),
envKeys: "OPENCODE_API_KEY",
login: async (cb: OAuthLoginCallbacks) => {
// Lazy import: keep heavy OAuth flow modules out of the eager registry graph.
const { loginOpenCode } = await import("./oauth/opencode");
+1 -6
View File
@@ -1,7 +1,6 @@
import { openrouterModelManagerOptions } from "../provider-models/openai-compat";
import { createApiKeyLogin } from "./api-key-login";
import type { OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
/** OpenRouter login flow (API key paste, validated via /auth/key).
*
@@ -25,9 +24,5 @@ export const loginOpenRouter = createApiKeyLogin({
export const openrouterProvider = {
id: "openrouter",
name: "OpenRouter",
defaultModel: "openai/gpt-5.4",
createModelManagerOptions: (config: ModelManagerConfig) => openrouterModelManagerOptions(config),
catalogDiscovery: { label: "OpenRouter", envVars: ["OPENROUTER_API_KEY"], allowUnauthenticated: true },
envKeys: "OPENROUTER_API_KEY",
login: (cb: OAuthLoginCallbacks) => loginOpenRouter(cb),
} as const satisfies ProviderDefinition;
+1 -6
View File
@@ -1,7 +1,6 @@
import { qianfanModelManagerOptions } from "../provider-models/openai-compat";
import { validateOpenAICompatibleApiKey } from "./api-key-validation";
import type { OAuthController, OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
const AUTH_URL = "https://console.bce.baidu.com/qianfan/ais/console/apiKey";
const API_BASE_URL = "https://qianfan.baidubce.com/v2";
@@ -46,9 +45,5 @@ export async function loginQianfan(options: OAuthController): Promise<string> {
export const qianfanProvider = {
id: "qianfan",
name: "Qianfan",
defaultModel: "deepseek-v3.2",
createModelManagerOptions: (config: ModelManagerConfig) => qianfanModelManagerOptions(config),
catalogDiscovery: { label: "Qianfan", envVars: ["QIANFAN_API_KEY"] },
envKeys: "QIANFAN_API_KEY",
login: (cb: OAuthLoginCallbacks) => loginQianfan(cb),
} as const satisfies ProviderDefinition;
+1 -11
View File
@@ -1,8 +1,6 @@
import { $pickenv } from "@oh-my-pi/pi-utils";
import { qwenPortalModelManagerOptions } from "../provider-models/openai-compat";
import { validateOpenAICompatibleApiKey } from "./api-key-validation";
import type { OAuthController, OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
const AUTH_URL = "https://chat.qwen.ai";
const API_BASE_URL = "https://portal.qwen.ai/v1";
@@ -47,13 +45,5 @@ export async function loginQwenPortal(options: OAuthController): Promise<string>
export const qwenPortalProvider = {
id: "qwen-portal",
name: "Qwen Portal",
defaultModel: "coder-model",
createModelManagerOptions: (config: ModelManagerConfig) => qwenPortalModelManagerOptions(config),
catalogDiscovery: {
label: "Qwen Portal",
envVars: ["QWEN_OAUTH_TOKEN", "QWEN_PORTAL_API_KEY"],
oauthProvider: "qwen-portal",
},
envKeys: () => $pickenv("QWEN_OAUTH_TOKEN", "QWEN_PORTAL_API_KEY"),
login: (cb: OAuthLoginCallbacks) => loginQwenPortal(cb),
} as const satisfies ProviderDefinition;
+8 -2
View File
@@ -1,3 +1,4 @@
import type { KnownProvider } from "@oh-my-pi/pi-catalog";
import { aimlApiProvider } from "./aimlapi";
import { alibabaCodingPlanProvider } from "./alibaba-coding-plan";
import { amazonBedrockProvider } from "./amazon-bedrock";
@@ -137,7 +138,12 @@ export function getProviderDefinition(id: string): ProviderDefinition | undefine
return BY_ID.get(id);
}
/** Chat-model providers (those carrying a `defaultModel`). */
export type KnownProviderId = Extract<RegistryDef, { defaultModel: string }>["id"];
/** Compile-time completeness: every catalog chat-model provider must have a registry definition. */
type _MissingCatalogProviders = Exclude<KnownProvider, RegistryDef["id"]>;
type _CheckRegistryComplete = _MissingCatalogProviders extends never
? true
: ["registry is missing catalog providers", _MissingCatalogProviders];
true satisfies _CheckRegistryComplete;
/** Loginable providers (those carrying a `login` flow). */
export type OAuthProviderUnion = Extract<RegistryDef, { login: object }>["id"];
+1 -7
View File
@@ -1,6 +1,5 @@
import { syntheticModelManagerOptions } from "../provider-models/openai-compat";
import { createApiKeyLogin } from "./api-key-login";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
export const loginSynthetic = createApiKeyLogin({
providerLabel: "Synthetic",
@@ -18,10 +17,5 @@ export const loginSynthetic = createApiKeyLogin({
export const syntheticProvider = {
id: "synthetic",
name: "Synthetic",
defaultModel: "hf:zai-org/GLM-5.1",
createModelManagerOptions: (config: ModelManagerConfig) => syntheticModelManagerOptions(config),
dynamicModelsAuthoritative: true,
catalogDiscovery: { label: "Synthetic", envVars: ["SYNTHETIC_API_KEY"] },
envKeys: "SYNTHETIC_API_KEY",
login: loginSynthetic,
} as const satisfies ProviderDefinition;
+1 -6
View File
@@ -1,6 +1,5 @@
import { togetherModelManagerOptions } from "../provider-models/openai-compat";
import { createApiKeyLogin } from "./api-key-login";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
export const loginTogether = createApiKeyLogin({
providerLabel: "Together",
@@ -19,9 +18,5 @@ export const loginTogether = createApiKeyLogin({
export const togetherProvider = {
id: "together",
name: "Together",
defaultModel: "moonshotai/Kimi-K2.5",
createModelManagerOptions: (config: ModelManagerConfig) => togetherModelManagerOptions(config),
catalogDiscovery: { label: "Together", envVars: ["TOGETHER_API_KEY"] },
envKeys: "TOGETHER_API_KEY",
login: (cb: Parameters<typeof loginTogether>[0]) => loginTogether(cb),
} as const satisfies ProviderDefinition;
+15 -77
View File
@@ -1,20 +1,16 @@
/**
* Single-source provider model. Every provider — model providers, gateways,
* search/tool credentials, and login-only flows — is described by one
* {@link ProviderDefinition}. The legacy scattered structures (the
* `KnownProvider`/`OAuthProvider` unions, `PROVIDER_DESCRIPTORS`,
* `serviceProviderMap`, `builtInOAuthProviders`, the refresh/login switches,
* and the CLI callback maps) are all *derived* from the registry of these
* definitions. Adding a provider is one new file in `./providers/` plus one
* line in `./registry.ts`.
* Single-source provider auth model. Every provider — model providers,
* gateways, search/tool credentials, and login-only flows — is described by
* one {@link ProviderDefinition}. The legacy scattered structures (the
* `OAuthProvider` union, `serviceProviderMap`, `builtInOAuthProviders`, the
* refresh/login switches, and the CLI callback maps) are all *derived* from
* the registry of these definitions. Adding a provider is one new file in
* `./providers/` plus one line in `./registry.ts`. Model-catalog metadata
* (default model, model-manager factory, catalog discovery) lives in
* `@oh-my-pi/pi-catalog`'s descriptor table.
*/
import type { ModelManagerOptions } from "../model-manager";
import type { Api, FetchImpl } from "../types";
import type { OAuthCredentials, OAuthLoginCallbacks } from "./oauth/types";
/** Config passed to a provider's runtime model-manager factory. */
export type ModelManagerConfig = { apiKey?: string; baseUrl?: string; fetch?: FetchImpl };
/**
* API-key environment fallback: either a single env var name (e.g.
* `"OPENAI_API_KEY"`) or a resolver that inspects several env vars / probes
@@ -22,53 +18,13 @@ export type ModelManagerConfig = { apiKey?: string; baseUrl?: string; fetch?: Fe
*/
export type KeyResolver = string | (() => string | undefined);
/** Catalog discovery configuration for providers that support endpoint-based model listing. */
export interface CatalogDiscoveryConfig {
/** Human-readable name for log messages. */
label: string;
/** Environment variables to check for API keys during catalog generation. */
envVars: readonly string[];
/** OAuth provider for credential refresh during catalog generation. */
oauthProvider?: string;
/** When true, catalog discovery proceeds even without credentials. */
allowUnauthenticated?: boolean;
}
/** Unified provider descriptor used by both runtime discovery and catalog generation. */
export interface ProviderDescriptor {
providerId: string;
createModelManagerOptions(config: ModelManagerConfig): ModelManagerOptions<Api>;
/** Preferred model ID when no explicit selection is made. */
defaultModel: string;
/** When true, the runtime creates a model manager even without a valid API key (e.g. ollama). */
allowUnauthenticated?: boolean;
/** When true, successful runtime discovery replaces bundled provider models instead of merging fallback-only IDs. */
dynamicModelsAuthoritative?: boolean;
/** Catalog discovery configuration. Only providers with this field participate in generate-models.ts. */
catalogDiscovery?: CatalogDiscoveryConfig;
}
/** A provider descriptor that has catalog discovery configured. */
export type CatalogProviderDescriptor = ProviderDescriptor & { catalogDiscovery: CatalogDiscoveryConfig };
/** Type guard for descriptors with catalog discovery. */
export function isCatalogDescriptor(d: ProviderDescriptor): d is CatalogProviderDescriptor {
return d.catalogDiscovery != null;
}
/** Whether catalog discovery may run without provider credentials. */
export function allowsUnauthenticatedCatalogDiscovery(descriptor: CatalogProviderDescriptor): boolean {
return descriptor.catalogDiscovery.allowUnauthenticated ?? descriptor.allowUnauthenticated ?? false;
}
/**
* Declarative description of a single provider. All fields are optional except
* `id`/`name`; presence of a field opts the provider into a derived structure:
* Declarative description of a single provider's auth/login wiring. All
* fields are optional except `id`/`name`; presence of a field opts the
* provider into a derived structure:
*
* - `defaultModel` present ⇒ member of `KnownProvider` (a chat-model provider).
* - `createModelManagerOptions` present (and not `specialModelManager`) ⇒
* appears in `PROVIDER_DESCRIPTORS` for runtime model discovery.
* - `envKeys` present ⇒ env-var fallback in `getEnvApiKey`.
* - `envKeys` present ⇒ env-var fallback in `getEnvApiKey`, overriding the
* catalog table's `envVars` for that provider.
* - `login` present ⇒ member of `OAuthProvider`, shown in the `/login` list
* (unless `showInLoginList === false`) and dispatchable via `AuthStorage.login`.
* - `callbackPort` present ⇒ entry in the auth-broker `CALLBACK_PORTS` map.
@@ -84,25 +40,7 @@ export interface ProviderDefinition {
readonly available?: boolean;
/** Whether to surface in the interactive login list. Defaults to true when `login` is present. */
readonly showInLoginList?: boolean;
// --- model discovery ---
/** Preferred model ID when no explicit selection is made. Presence ⇒ `KnownProvider` member. */
readonly defaultModel?: string;
/** Runtime model-manager factory. Omitted for login-only tools and catalog-only providers. */
readonly createModelManagerOptions?: (config: ModelManagerConfig) => ModelManagerOptions<Api>;
/** When true, the runtime creates a model manager even without a valid API key. */
readonly allowUnauthenticated?: boolean;
/** When true, successful runtime discovery replaces bundled provider models. */
readonly dynamicModelsAuthoritative?: boolean;
/** Catalog discovery configuration for generate-models.ts. */
readonly catalogDiscovery?: CatalogDiscoveryConfig;
/**
* Providers whose model manager is constructed bespoke in the coding-agent
* runtime (`google-antigravity`/`google-gemini-cli`/`openai-codex`). Excluded
* from the derived `PROVIDER_DESCRIPTORS`; the registry supplies only their
* identity/login/refresh/default-model metadata.
*/
readonly specialModelManager?: boolean;
// --- env-var fallback ---
// --- env-var fallback (the catalog table's `envVars` supplies plain names; set this only for computed resolvers) ---
readonly envKeys?: KeyResolver;
// --- interactive login (OAuthProviderInterface-compatible) ---
readonly login?: (callbacks: OAuthLoginCallbacks) => Promise<OAuthCredentials | string>;
+1 -6
View File
@@ -1,7 +1,6 @@
import { veniceModelManagerOptions } from "../provider-models/openai-compat";
import { validateOpenAICompatibleApiKey } from "./api-key-validation";
import type { OAuthController, OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
const AUTH_URL = "https://venice.ai/settings/api";
const API_BASE_URL = "https://api.venice.ai/api/v1";
@@ -52,9 +51,5 @@ export async function loginVenice(options: OAuthController): Promise<string> {
export const veniceProvider = {
id: "venice",
name: "Venice",
defaultModel: "llama-3.3-70b",
createModelManagerOptions: (config: ModelManagerConfig) => veniceModelManagerOptions(config),
catalogDiscovery: { label: "Venice", envVars: ["VENICE_API_KEY"], allowUnauthenticated: true },
envKeys: "VENICE_API_KEY",
login: (cb: OAuthLoginCallbacks) => loginVenice(cb),
} as const satisfies ProviderDefinition;
@@ -1,6 +1,5 @@
import { vercelAiGatewayModelManagerOptions } from "../provider-models/openai-compat";
import type { OAuthController, OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
const AUTH_URL = "https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai-gateway%2Fapi-keys&title=AI+Gateway+API+Keys";
@@ -34,9 +33,5 @@ export async function loginVercelAiGateway(options: OAuthController): Promise<st
export const vercelAiGatewayProvider = {
id: "vercel-ai-gateway",
name: "Vercel AI Gateway",
defaultModel: "anthropic/claude-sonnet-4-6",
createModelManagerOptions: (config: ModelManagerConfig) => vercelAiGatewayModelManagerOptions(config),
catalogDiscovery: { label: "Vercel AI Gateway", envVars: ["VERCEL_AI_GATEWAY_API_KEY"], allowUnauthenticated: true },
envKeys: "AI_GATEWAY_API_KEY",
login: (cb: OAuthLoginCallbacks) => loginVercelAiGateway(cb),
} as const satisfies ProviderDefinition;
+1 -6
View File
@@ -1,6 +1,5 @@
import { vllmModelManagerOptions } from "../provider-models/openai-compat";
import type { OAuthController, OAuthLoginCallbacks, OAuthProvider } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
const PROVIDER_ID: OAuthProvider = "vllm";
const AUTH_URL = "https://docs.vllm.ai/en/latest/serving/openai_compatible_server.html";
@@ -30,9 +29,5 @@ export async function loginVllm(options: OAuthController): Promise<string> {
export const vllmProvider = {
id: "vllm",
name: "vLLM (Local OpenAI-compatible)",
defaultModel: "gpt-oss-20b",
createModelManagerOptions: (config: ModelManagerConfig) => vllmModelManagerOptions(config),
catalogDiscovery: { label: "vLLM", envVars: ["VLLM_API_KEY"], allowUnauthenticated: true },
envKeys: "VLLM_API_KEY",
login: (cb: OAuthLoginCallbacks) => loginVllm(cb),
} as const satisfies ProviderDefinition;
+1 -6
View File
@@ -1,14 +1,9 @@
import { waferPassModelManagerOptions } from "../provider-models/openai-compat";
import type { OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
export const waferPassProvider = {
id: "wafer-pass",
name: "Wafer Pass (flat-rate subscription)",
defaultModel: "GLM-5.1",
createModelManagerOptions: (config: ModelManagerConfig) => waferPassModelManagerOptions(config),
catalogDiscovery: { label: "Wafer Pass", envVars: ["WAFER_PASS_API_KEY"], oauthProvider: "wafer-pass" },
envKeys: "WAFER_PASS_API_KEY",
login: async (cb: OAuthLoginCallbacks) => {
// Lazy import: keep heavy OAuth flow modules out of the eager registry graph.
const { loginWaferPass } = await import("./oauth/wafer");
+1 -10
View File
@@ -1,18 +1,9 @@
import { waferServerlessModelManagerOptions } from "../provider-models/openai-compat";
import type { OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
export const waferServerlessProvider = {
id: "wafer-serverless",
name: "Wafer Serverless (pay-as-you-go)",
defaultModel: "GLM-5.1",
createModelManagerOptions: (config: ModelManagerConfig) => waferServerlessModelManagerOptions(config),
catalogDiscovery: {
label: "Wafer Serverless",
envVars: ["WAFER_SERVERLESS_API_KEY"],
oauthProvider: "wafer-serverless",
},
envKeys: "WAFER_SERVERLESS_API_KEY",
login: async (cb: OAuthLoginCallbacks) => {
// Lazy import: keep heavy OAuth flow modules out of the eager registry graph.
const { loginWaferServerless } = await import("./oauth/wafer");
+1 -11
View File
@@ -1,19 +1,9 @@
import { $pickenv } from "@oh-my-pi/pi-utils";
import { xaiOAuthModelManagerOptions } from "../provider-models/openai-compat";
import type { OAuthCredentials, OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
export const xaiOauthProvider = {
id: "xai-oauth",
name: "xAI Grok OAuth (SuperGrok Subscription)",
defaultModel: "grok-4.3",
createModelManagerOptions: (config: ModelManagerConfig) => xaiOAuthModelManagerOptions(config),
catalogDiscovery: {
label: "xAI Grok OAuth (SuperGrok)",
envVars: ["XAI_OAUTH_TOKEN", "XAI_API_KEY"],
oauthProvider: "xai-oauth",
},
envKeys: () => $pickenv("XAI_OAUTH_TOKEN", "XAI_API_KEY"),
login: async (cb: OAuthLoginCallbacks) => {
// Lazy import: keep heavy OAuth flow modules out of the eager registry graph.
const { loginXAIOAuth } = await import("./oauth/xai-oauth");
+1 -5
View File
@@ -1,10 +1,6 @@
import { xaiModelManagerOptions } from "../provider-models/openai-compat";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
export const xaiProvider = {
id: "xai",
name: "xAI",
defaultModel: "grok-4-fast-non-reasoning",
createModelManagerOptions: (config: ModelManagerConfig) => xaiModelManagerOptions(config),
envKeys: "XAI_API_KEY",
} as const satisfies ProviderDefinition;
@@ -1,14 +1,9 @@
import { xiaomiModelManagerOptions } from "../provider-models/openai-compat";
import type { OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
export const xiaomiTokenPlanAmsProvider = {
id: "xiaomi-token-plan-ams",
name: "Xiaomi Token Plan (Europe)",
defaultModel: "mimo-v2.5",
createModelManagerOptions: (config: ModelManagerConfig) =>
xiaomiModelManagerOptions({ ...config, providerId: "xiaomi-token-plan-ams", tokenPlanRegion: "ams" }),
envKeys: "XIAOMI_TOKEN_PLAN_AMS_API_KEY",
login: async (cb: OAuthLoginCallbacks) => {
// Lazy import: keep heavy OAuth flow modules out of the eager registry graph.
const { loginXiaomiTokenPlan } = await import("./oauth/xiaomi");
@@ -1,14 +1,9 @@
import { xiaomiModelManagerOptions } from "../provider-models/openai-compat";
import type { OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
export const xiaomiTokenPlanCnProvider = {
id: "xiaomi-token-plan-cn",
name: "Xiaomi Token Plan (China)",
defaultModel: "mimo-v2.5",
createModelManagerOptions: (config: ModelManagerConfig) =>
xiaomiModelManagerOptions({ ...config, providerId: "xiaomi-token-plan-cn", tokenPlanRegion: "cn" }),
envKeys: "XIAOMI_TOKEN_PLAN_CN_API_KEY",
login: async (cb: OAuthLoginCallbacks) => {
// Lazy import: keep heavy OAuth flow modules out of the eager registry graph.
const { loginXiaomiTokenPlan } = await import("./oauth/xiaomi");
@@ -1,14 +1,9 @@
import { xiaomiModelManagerOptions } from "../provider-models/openai-compat";
import type { OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
export const xiaomiTokenPlanSgpProvider = {
id: "xiaomi-token-plan-sgp",
name: "Xiaomi Token Plan (Singapore)",
defaultModel: "mimo-v2.5",
createModelManagerOptions: (config: ModelManagerConfig) =>
xiaomiModelManagerOptions({ ...config, providerId: "xiaomi-token-plan-sgp", tokenPlanRegion: "sgp" }),
envKeys: "XIAOMI_TOKEN_PLAN_SGP_API_KEY",
login: async (cb: OAuthLoginCallbacks) => {
// Lazy import: keep heavy OAuth flow modules out of the eager registry graph.
const { loginXiaomiTokenPlan } = await import("./oauth/xiaomi");
+1 -6
View File
@@ -1,14 +1,9 @@
import { xiaomiModelManagerOptions } from "../provider-models/openai-compat";
import type { OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
export const xiaomiProvider = {
id: "xiaomi",
name: "Xiaomi MiMo",
defaultModel: "mimo-v2-flash",
createModelManagerOptions: (config: ModelManagerConfig) => xiaomiModelManagerOptions(config),
catalogDiscovery: { label: "Xiaomi", envVars: ["XIAOMI_API_KEY"] },
envKeys: "XIAOMI_API_KEY",
login: async (cb: OAuthLoginCallbacks) => {
// Lazy import: keep heavy OAuth flow modules out of the eager registry graph.
const { loginXiaomi } = await import("./oauth/xiaomi");
+1 -6
View File
@@ -1,7 +1,6 @@
import { zaiModelManagerOptions } from "../provider-models/special";
import { validateOpenAICompatibleApiKey } from "./api-key-validation";
import type { OAuthController, OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
const AUTH_URL = "https://z.ai/manage-apikey/apikey-list";
const API_BASE_URL = "https://api.z.ai/api/coding/paas/v4";
@@ -45,9 +44,5 @@ export async function loginZai(options: OAuthController): Promise<string> {
export const zaiProvider = {
id: "zai",
name: "Z.AI (GLM Coding Plan)",
defaultModel: "glm-5.1",
createModelManagerOptions: (config: ModelManagerConfig) => zaiModelManagerOptions(config),
catalogDiscovery: { label: "zAI", envVars: ["ZAI_API_KEY"] },
envKeys: "ZAI_API_KEY",
login: (cb: OAuthLoginCallbacks) => loginZai(cb),
} as const satisfies ProviderDefinition;
+1 -6
View File
@@ -1,7 +1,6 @@
import { zenmuxModelManagerOptions } from "../provider-models/openai-compat";
import { createApiKeyLogin } from "./api-key-login";
import type { OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
export const loginZenMux = createApiKeyLogin({
providerLabel: "ZenMux",
@@ -19,9 +18,5 @@ export const loginZenMux = createApiKeyLogin({
export const zenmuxProvider = {
id: "zenmux",
name: "ZenMux",
defaultModel: "anthropic/claude-opus-4.6",
createModelManagerOptions: (config: ModelManagerConfig) => zenmuxModelManagerOptions(config),
catalogDiscovery: { label: "ZenMux", envVars: ["ZENMUX_API_KEY"] },
envKeys: "ZENMUX_API_KEY",
login: (cb: OAuthLoginCallbacks) => loginZenMux(cb),
} as const satisfies ProviderDefinition;
@@ -1,7 +1,6 @@
import { zhipuCodingPlanModelManagerOptions } from "../provider-models/openai-compat";
import { validateOpenAICompatibleApiKey } from "./api-key-validation";
import type { OAuthController, OAuthLoginCallbacks } from "./oauth/types";
import type { ModelManagerConfig, ProviderDefinition } from "./types";
import type { ProviderDefinition } from "./types";
const AUTH_URL = "https://bigmodel.cn/coding-plan/personal/overview";
const API_BASE_URL = "https://open.bigmodel.cn/api/coding/paas/v4";
@@ -45,9 +44,5 @@ export async function loginZhipuCodingPlan(options: OAuthController): Promise<st
export const zhipuCodingPlanProvider = {
id: "zhipu-coding-plan",
name: "Zhipu Coding Plan (智谱)",
defaultModel: "glm-5.1",
createModelManagerOptions: (config: ModelManagerConfig) => zhipuCodingPlanModelManagerOptions(config),
catalogDiscovery: { label: "Zhipu Coding Plan", envVars: ["ZHIPU_API_KEY"] },
envKeys: "ZHIPU_API_KEY",
login: (cb: OAuthLoginCallbacks) => loginZhipuCodingPlan(cb),
} as const satisfies ProviderDefinition;
+19 -5
View File
@@ -1,13 +1,14 @@
import { $env, extractHttpStatusFromError } from "@oh-my-pi/pi-utils";
import { getCustomApi } from "./api-registry";
import { AUTH_RETRY_STEPS, isApiKeyResolver, resolveRetryKey } from "./auth-retry";
import type { Effort } from "./effort";
import type { Effort } from "@oh-my-pi/pi-catalog/effort";
import {
mapEffortToAnthropicAdaptiveEffort,
mapEffortToGoogleThinkingLevel,
modelOmitsReasoningEffort,
requireSupportedEffort,
} from "./model-thinking";
} from "@oh-my-pi/pi-catalog/model-thinking";
import { CATALOG_PROVIDERS, type ProviderCatalogEntry } from "@oh-my-pi/pi-catalog/provider-models";
import { $env, $pickenv, extractHttpStatusFromError } from "@oh-my-pi/pi-utils";
import { getCustomApi } from "./api-registry";
import { AUTH_RETRY_STEPS, isApiKeyResolver, resolveRetryKey } from "./auth-retry";
import type { BedrockOptions } from "./providers/amazon-bedrock";
import type { AnthropicOptions } from "./providers/anthropic";
import type { CursorOptions } from "./providers/cursor";
@@ -166,7 +167,20 @@ const LEGACY_ENV_KEYS: Record<string, KeyResolver> = {
brave: "BRAVE_API_KEY",
};
/**
* Env fallbacks derived from the catalog table — the single source for plain
* provider env-var names. Registry defs override with computed resolvers
* (Foundry/ADC/Bedrock probes); legacy non-provider keys merge last.
*/
const CATALOG_ENTRY_ENV_KEYS = (CATALOG_PROVIDERS as readonly ProviderCatalogEntry[]).flatMap(provider => {
const envVars = provider.envVars;
if (!envVars || envVars.length === 0) return [];
const resolver: KeyResolver = envVars.length === 1 ? envVars[0] : () => $pickenv(...envVars);
return [[provider.id, resolver] as [string, KeyResolver]];
});
const serviceProviderMap: Record<string, KeyResolver> = {
...Object.fromEntries(CATALOG_ENTRY_ENV_KEYS),
...Object.fromEntries(
PROVIDER_REGISTRY.flatMap(provider =>
provider.envKeys != null ? [[provider.id, provider.envKeys] as [string, KeyResolver]] : [],
+12 -339
View File
@@ -1,9 +1,6 @@
import type { ZodType, z } from "zod/v4";
import type { ApiKey } from "./auth-retry";
import type { BedrockOptions } from "./providers/amazon-bedrock";
import type { AnthropicOptions } from "./providers/anthropic";
import type { AzureOpenAIResponsesOptions } from "./providers/azure-openai-responses";
import type { CursorOptions } from "./providers/cursor";
export * from "@oh-my-pi/pi-catalog/effort";
export * from "@oh-my-pi/pi-catalog/types";
import type {
DeleteArgs,
DeleteResult,
@@ -20,7 +17,15 @@ import type {
ShellResult,
WriteArgs,
WriteResult,
} from "./providers/cursor/gen/agent_pb";
} from "@oh-my-pi/pi-catalog/discovery/cursor-gen/agent_pb";
import type { Effort } from "@oh-my-pi/pi-catalog/effort";
import type { Api, FetchImpl, KnownApi, Model, Provider, ThinkingBudgets, Usage } from "@oh-my-pi/pi-catalog/types";
import type { ZodType, z } from "zod/v4";
import type { ApiKey } from "./auth-retry";
import type { BedrockOptions } from "./providers/amazon-bedrock";
import type { AnthropicOptions } from "./providers/anthropic";
import type { AzureOpenAIResponsesOptions } from "./providers/azure-openai-responses";
import type { CursorOptions } from "./providers/cursor";
import type { GoogleOptions } from "./providers/google";
import type { GoogleGeminiCliOptions } from "./providers/google-gemini-cli";
import type { GoogleVertexOptions } from "./providers/google-vertex";
@@ -28,7 +33,6 @@ import type { OllamaChatOptions } from "./providers/ollama";
import type { OpenAICodexResponsesOptions } from "./providers/openai-codex-responses";
import type { OpenAICompletionsOptions } from "./providers/openai-completions";
import type { OpenAIResponsesOptions } from "./providers/openai-responses";
import type { KnownProviderId } from "./registry";
import type { AssistantMessageEventStream } from "./utils/event-stream";
export type { AssistantMessageEventStream } from "./utils/event-stream";
@@ -46,19 +50,6 @@ export type { AssistantMessageEventStream } from "./utils/event-stream";
*/
export const OPENAI_MAX_OUTPUT_TOKENS = 64000;
export type KnownApi =
| "openai-completions"
| "openai-responses"
| "openai-codex-responses"
| "azure-openai-responses"
| "anthropic-messages"
| "bedrock-converse-stream"
| "google-generative-ai"
| "google-gemini-cli"
| "google-vertex"
| "ollama-chat"
| "cursor-agent";
export type Api = KnownApi | (string & {});
export interface ApiOptionsMap {
"anthropic-messages": AnthropicOptions;
"bedrock-converse-stream": BedrockOptions;
@@ -84,44 +75,6 @@ export type OptionsForApi<TApi extends Api> =
| StreamOptions
| (TApi extends keyof ApiOptionsMap ? ApiOptionsMap[TApi] : never);
/** Canonical thinking transport used by a model. */
export type ThinkingControlMode =
| "effort"
| "budget"
| "google-level"
| "anthropic-adaptive"
| "anthropic-budget-effort";
/** Per-model thinking capabilities used to clamp and map user-facing effort levels. */
export interface ThinkingConfig {
/** Least intensive supported user-facing effort level. */
minLevel: Effort;
/** Most intensive supported user-facing effort level. */
maxLevel: Effort;
/**
* Optional explicit list of supported levels. When present, takes precedence over
* the `minLevel`..`maxLevel` range — used to encode discrete sets with gaps
* (e.g. Gemini 3 Pro supports `low` and `high` but not `medium`).
*/
levels?: readonly Effort[];
/** Optional default effort applied when this model is selected. Falls back to global default if absent. */
defaultLevel?: Effort;
/** Provider-specific transport used to encode the selected effort. */
mode: ThinkingControlMode;
}
export type KnownProvider = KnownProviderId;
// `Provider` is any provider-id string; `KnownProvider` enumerates the built-in model
// providers. Kept structurally `string` (the prior `KnownProvider | string` already
// collapsed to `string`) so the registry-derived `KnownProvider` can reference the model
// types below without forming a circular type-alias reference.
export type Provider = string;
import type { Effort } from "./effort";
/** Token budgets for each thinking level (token-based providers only) */
export type ThinkingBudgets = { [key in Effort]?: number };
export interface TokenTaskBudget {
type: "tokens";
total: number;
@@ -233,15 +186,6 @@ export interface RawSseEvent {
raw: string[];
}
/**
* `fetch`-compatible function. Accepts any callable matching the standard
* fetch signature; `preconnect` is optional because non-Bun runtimes (browsers,
* test mocks) won't expose it.
*/
export type FetchImpl = ((input: string | URL | Request, init?: RequestInit) => Promise<Response>) & {
preconnect?: typeof globalThis.fetch.preconnect;
};
export interface StreamOptions {
temperature?: number;
topP?: number;
@@ -484,53 +428,6 @@ export interface ToolCall {
customWireName?: string;
}
export interface Usage {
/** Non-cached input tokens (matches the bucket the provider bills as new input). */
input: number;
/** Total output tokens for the turn, including thinking, assistant text, and tool-call argument tokens. */
output: number;
/** Tokens read from the prompt cache. */
cacheRead: number;
/** Tokens written to the prompt cache (cache creation). */
cacheWrite: number;
/** Sum of input + output + cacheRead + cacheWrite. */
totalTokens: number;
/** Copilot premium-request counter, when applicable. */
premiumRequests?: number;
/**
* Reasoning/thinking tokens included in `output`, when the provider reports them
* (OpenAI `output_tokens_details.reasoning_tokens`, Google `thoughtsTokenCount`).
* Always a subset of `output` — non-reasoning output is `output - reasoningTokens`.
*
* Providers that don't expose this leave it undefined rather than guessing;
* `undefined` means unknown, NOT zero.
*/
reasoningTokens?: number;
/**
* Cache-write TTL breakdown (Anthropic only). When set, the components sum to
* `cacheWrite`. Absent providers do not populate this.
*/
cttl?: {
ephemeral5m?: number;
ephemeral1h?: number;
};
/**
* Server-side tool invocations made during this turn (Anthropic web_search /
* web_fetch, OpenAI built-in tools when reported). Counts requests, not tokens.
*/
server?: {
webSearch?: number;
webFetch?: number;
};
cost: {
input: number;
output: number;
cacheRead: number;
cacheWrite: number;
total: number;
};
}
export type StopReason = "stop" | "length" | "toolUse" | "error" | "aborted";
export interface OpenAIResponsesHistoryPayload {
@@ -724,227 +621,3 @@ export type AssistantMessageEvent =
reason: Extract<StopReason, "aborted" | "error">;
error: AssistantMessage;
};
/**
* Compatibility settings for openai-completions API.
* Use this to override URL-based auto-detection for custom providers.
*/
export interface OpenAICompat {
/** Whether the provider supports the `store` field. Default: auto-detected from URL. */
supportsStore?: boolean;
/** Whether the provider supports the `developer` role (vs `system`). Default: auto-detected from URL. */
supportsDeveloperRole?: boolean;
/**
* Whether the provider's chat-completions endpoint accepts multiple
* leading `system`/`developer` messages. When false, ordered system
* prompts are coalesced into a single message joined by `\n\n` so
* strict chat templates (e.g. Qwen-served via vLLM, MiniMax) accept
* the request. Default: detected per provider/baseUrl. Canonical
* OpenAI/Azure/OpenRouter/Cerebras/Together/Fireworks/Groq/DeepSeek/
* Mistral/xAI/Z.ai/GitHub Copilot/Zenmux are treated as `true`;
* unknown or strict-template hosts default to `false`. Setting this
* to `true` preserves separate blocks, which is preferred for
* KV-cache reuse when the trailing prompt changes between calls.
*/
supportsMultipleSystemMessages?: boolean;
/** Whether the provider supports `reasoning_effort`. Default: auto-detected from URL. */
supportsReasoningEffort?: boolean;
/** Optional mapping from pi-ai reasoning levels to provider/model-specific `reasoning_effort` values. */
reasoningEffortMap?: Partial<Record<Effort, string>>;
/** Whether the provider supports `stream_options: { include_usage: true }` for token usage in streaming responses. Default: true. */
supportsUsageInStreaming?: boolean;
/** Which field to use for max tokens. Default: auto-detected from URL. */
maxTokensField?: "max_completion_tokens" | "max_tokens";
/** Whether tool results require the `name` field. Default: auto-detected from URL. */
requiresToolResultName?: boolean;
/** Whether a user message after tool results requires an assistant message in between. Default: auto-detected from URL. */
requiresAssistantAfterToolResult?: boolean;
/** Whether thinking blocks must be converted to text blocks with <thinking> delimiters. Default: auto-detected from URL. */
requiresThinkingAsText?: boolean;
/** Whether tool call IDs must be normalized to Mistral format (exactly 9 alphanumeric chars). Default: auto-detected from URL. */
requiresMistralToolIds?: boolean;
/** Format for reasoning/thinking parameter. "openai" uses reasoning_effort, "openrouter" uses reasoning: { effort }, "zai" uses thinking: { type: "enabled" | "disabled" } (also used by Moonshot Kimi), "qwen" uses top-level enable_thinking, and "qwen-chat-template" uses chat_template_kwargs.enable_thinking. Default: "openai". */
thinkingFormat?: "openai" | "openrouter" | "zai" | "qwen" | "qwen-chat-template";
/** Optional `thinking.keep` value for Z.ai/Moonshot-style thinking params. Set false to suppress auto-detected keep. Default: auto-detected. */
thinkingKeep?: "all" | false;
/** Which reasoning content field to emit on assistant messages. Default: auto-detected. */
reasoningContentField?: "reasoning_content" | "reasoning" | "reasoning_text";
/** Whether assistant tool-call messages must include reasoning content. Default: false. */
requiresReasoningContentForToolCalls?: boolean;
/** Whether the provider accepts a synthetic placeholder (e.g. ".") for missing reasoning_content on tool-call turns. Default: true. Set to false for providers like DeepSeek that validate the exact reasoning_content value. */
allowsSyntheticReasoningContentForToolCalls?: boolean;
/** Whether assistant tool-call messages must include non-empty content. Default: false. */
requiresAssistantContentForToolCalls?: boolean;
/** Whether the provider supports the `tool_choice` parameter. Default: true. */
supportsToolChoice?: boolean;
/**
* Drop reasoning fields (`reasoning_effort`, OpenRouter `reasoning`) for
* the request when `tool_choice` forces a tool call. Mirrors the Anthropic
* `disableThinkingIfToolChoiceForced` rule for backends like Kimi that
* 400 with `tool_choice 'specified' is incompatible with thinking
* enabled` whenever both are present. Default: auto-detected (Kimi).
*/
disableReasoningOnForcedToolChoice?: boolean;
/**
* Drop reasoning fields (`reasoning_effort`, OpenRouter `reasoning`) for
* any request that sends `tool_choice`. Use for providers/models that accept
* tools and `tool_choice`, but reject `tool_choice` while thinking is enabled.
* Default: auto-detected (DeepSeek reasoning models).
*/
disableReasoningOnToolChoice?: boolean;
/** OpenRouter-specific routing preferences. Only used when baseUrl points to OpenRouter. */
openRouterRouting?: OpenRouterRouting;
/** Vercel AI Gateway routing preferences. Only used when baseUrl points to Vercel AI Gateway. */
vercelGatewayRouting?: VercelGatewayRouting;
/** Extra fields to include in request body (e.g. gateway routing hints for OpenClaw-style proxies). */
extraBody?: Record<string, unknown>;
/** Whether chat-completions payloads should include provider-specific prompt-cache markers. */
cacheControlFormat?: "anthropic" | undefined;
/** Whether the provider supports the `strict` field in tool definitions. Default: auto-detected per provider/baseUrl (conservative for unknown providers). */
supportsStrictMode?: boolean;
/** Whether tool schemas must be sent either all strict or all non-strict. Undefined keeps the existing per-tool mixed behavior. */
toolStrictMode?: "all_strict" | "none";
}
/**
* Compatibility settings for anthropic-messages API.
* Use this to disable features that strict-by-default Anthropic accepts but
* that proxy gateways (Vertex AI, AWS Bedrock-style fronts, etc.) reject.
*/
export interface AnthropicCompat {
/**
* Drop the top-level `strict: true` field on tool definitions. Vertex AI's
* Anthropic-compatible endpoint rejects unknown tool fields with
* `tools.<n>.custom.strict: Extra inputs are not permitted`.
*/
disableStrictTools?: boolean;
/**
* Map adaptive thinking (`thinking: { type: "adaptive" }`) to
* `{ type: "enabled", budget_tokens }`. Vertex AI rejects the `adaptive`
* tag with `Input tag 'adaptive' ... does not match any of the expected
* tags: 'disabled', 'enabled'`.
*/
disableAdaptiveThinking?: boolean;
/** Whether tools may include Anthropic's per-tool eager_input_streaming flag. Default: true. */
supportsEagerToolInputStreaming?: boolean;
/** Whether long prompt-cache retention (`ttl: "1h"`) is supported. Default: true for canonical Anthropic API. */
supportsLongCacheRetention?: boolean;
/**
* Whether mid-conversation `role: "system"` messages are accepted in the
* `messages` array (Claude Opus 4.8+ and Claude Fable/Mythos 5 on the
* first-party Claude API and Claude Platform on AWS). When unset,
* auto-detected from the model id and base URL. Not available on Bedrock,
* Vertex AI, or Microsoft Foundry.
*/
supportsMidConversationSystem?: boolean;
/**
* Whether the model accepts a forced `tool_choice` (`{ type: "any" }` or
* `{ type: "tool", name }`). Claude Fable/Mythos 5 reject forced tool use
* outright ("tool_choice forces tool use is not compatible with this model");
* the request builder downgrades forced choices to `auto` when this is false.
* When unset, auto-detected from the model id. Default: true.
*/
supportsForcedToolChoice?: boolean;
}
/**
* OpenRouter provider routing preferences.
* Controls which upstream providers OpenRouter routes requests to.
* @see https://openrouter.ai/docs/provider-routing
*/
export interface OpenRouterRouting {
/** List of provider slugs to exclusively use for this request (e.g., ["amazon-bedrock", "anthropic"]). */
only?: string[];
/** List of provider slugs to try in order (e.g., ["anthropic", "openai"]). */
order?: string[];
}
/**
* Vercel AI Gateway routing preferences.
* Controls which upstream providers the gateway routes requests to.
* @see https://vercel.com/docs/ai-gateway/models-and-providers/provider-options
*/
export interface VercelGatewayRouting {
/** List of provider slugs to exclusively use for this request (e.g., ["bedrock", "anthropic"]). */
only?: string[];
/** List of provider slugs to try in order (e.g., ["anthropic", "openai"]). */
order?: string[];
}
// Model interface for the unified model system
export interface Model<TApi extends Api = any> {
id: string;
name: string;
api: TApi;
provider: Provider;
baseUrl: string;
reasoning: boolean;
input: ("text" | "image")[];
cost: {
input: number; // $/million tokens
output: number; // $/million tokens
cacheRead: number; // $/million tokens
cacheWrite: number; // $/million tokens
};
/** Premium Copilot requests charged per user-initiated request (defaults to 1). */
premiumMultiplier?: number;
contextWindow: number;
maxTokens: number;
/**
* When `true`, providers MUST omit `max_output_tokens` (Responses) /
* `max_tokens` / `max_completion_tokens` (Completions) from the outbound
* request and let the upstream API decide the per-response cap. `maxTokens`
* is still used locally for budgeting (compaction, context promotion); only
* the wire field is suppressed.
*
* Use this for proxies (notably Ollama) that forward to a backend whose true
* output limit OMP cannot discover — sending the wrong value triggers 400s
* from the upstream provider.
*/
omitMaxOutputTokens?: boolean;
headers?: Record<string, string>;
/**
* Streaming transport override. When `"pi-native"`, `streamSimple` routes
* the request to the model's `baseUrl` via the auth-gateway's
* `POST /v1/pi/stream` endpoint instead of dispatching the per-API
* provider client. The `baseUrl` must point at an `omp auth-gateway`
* (or compatible) host; `headers.Authorization` (or `apiKey` resolved by
* the registry) carries the gateway bearer.
*
* Used by containerized omp installs (e.g. robomp slots) to route every
* LLM call through a sidecar gateway that holds the real provider
* credentials. The model's other metadata (pricing, context window,
* thinking config, …) still resolves locally; only the streaming
* dispatch is redirected.
*/
transport?: "pi-native";
/** Hint that websocket transport should be preferred when supported by the provider implementation. */
preferWebsockets?: boolean;
/** Preferred model to switch to when context promotion is triggered (model id or provider/id). */
contextPromotionTarget?: string;
/** Provider-assigned priority value (lower = higher priority). */
priority?: number;
/** Canonical thinking capability metadata for this model. */
thinking?: ThinkingConfig;
/** Compatibility overrides per API. If not set, auto-detected from baseUrl. */
compat?: TApi extends "openai-completions" | "openai-responses"
? OpenAICompat
: TApi extends "anthropic-messages"
? AnthropicCompat
: never;
/**
* Which shape to use when exposing the Codex `apply_patch` tool to this model.
* Generated catalog policy sets `"freeform"` for first-party GPT-5 Responses
* models that support OpenAI custom tools with a Lark grammar. The freeform
* variant sends a raw patch string with no JSON envelope.
* - `"function"` or undefined: JSON function-tool with `{input: string}` (spec §1.2).
*/
applyPatchToolType?: "freeform" | "function";
/**
* Force OAuth-style request shaping for providers whose API key prefix doesn't
* match an OAuth token (e.g. routing Anthropic traffic through a proxy that
* expects Claude Code framing). When true, the streaming layer sets
* `options.isOAuth = true` for the underlying provider call.
*/
isOAuth?: boolean;
}
+2 -1
View File
@@ -1,4 +1,5 @@
import { scheduler } from "node:timers/promises";
import { toNumber } from "@oh-my-pi/pi-catalog/utils";
import { claudeCodeVersion } from "../providers/anthropic";
import type {
CredentialRankingStrategy,
@@ -11,7 +12,7 @@ import type {
UsageStatus,
UsageWindow,
} from "../usage";
import { isRecord, toNumber } from "../utils";
import { isRecord } from "../utils";
const DEFAULT_ENDPOINT = "https://api.anthropic.com/api/oauth";
const FIVE_HOURS_MS = 5 * 60 * 60 * 1000;
+3 -2
View File
@@ -4,7 +4,8 @@
* Normalizes Copilot quota usage into the shared UsageReport schema.
*/
import { OPENCODE_HEADERS } from "../registry/oauth/github-copilot";
import { toBoolean, toNumber } from "@oh-my-pi/pi-catalog/utils";
import { OPENCODE_HEADERS } from "@oh-my-pi/pi-catalog/wire/github-copilot";
import type {
UsageAmount,
UsageFetchContext,
@@ -15,7 +16,7 @@ import type {
UsageStatus,
UsageWindow,
} from "../usage";
import { isRecord, toBoolean, toNumber } from "../utils";
import { isRecord } from "../utils";
type CopilotQuotaDetail = {
entitlement: number;
+1 -1
View File
@@ -1,4 +1,4 @@
import { getAntigravityUserAgent } from "../providers/google-gemini-headers";
import { getAntigravityUserAgent } from "@oh-my-pi/pi-catalog/wire/gemini-headers";
import type {
CredentialRankingStrategy,
UsageAmount,
+1 -1
View File
@@ -1,5 +1,5 @@
import { Buffer } from "node:buffer";
import { CODEX_BASE_URL } from "../providers/openai-codex/constants";
import { CODEX_BASE_URL } from "@oh-my-pi/pi-catalog/wire/codex";
import type {
CredentialRankingStrategy,
UsageAmount,
+2 -1
View File
@@ -1,3 +1,4 @@
import { toNumber } from "@oh-my-pi/pi-catalog/utils";
import type {
UsageAmount,
UsageFetchContext,
@@ -8,7 +9,7 @@ import type {
UsageStatus,
UsageWindow,
} from "../usage";
import { isRecord, toNumber } from "../utils";
import { isRecord } from "../utils";
const DEFAULT_ENDPOINT = "https://api.z.ai";
const QUOTA_PATH = "/api/monitor/usage/quota/limit";

Some files were not shown because too many files have changed in this diff Show More