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:
@@ -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
@@ -62,7 +62,7 @@
|
||||
"!**/test-sessions.ts",
|
||||
"!**/template.generated.ts",
|
||||
"!**/docs-index.generated.ts",
|
||||
"!**/gen/agent_pb.ts",
|
||||
"!**/agent_pb.ts",
|
||||
"!.worktrees/**/*",
|
||||
"!.wt/**/*"
|
||||
]
|
||||
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:"
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
import type { Effort } from "../effort";
|
||||
import type { Effort } from "@oh-my-pi/pi-catalog/effort";
|
||||
import type {
|
||||
AssistantMessage,
|
||||
AssistantMessageEventStream,
|
||||
|
||||
@@ -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>;
|
||||
@@ -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,
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,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,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,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,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,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,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,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,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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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,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,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,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 +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,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 +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 +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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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,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,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,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,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 +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 +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;
|
||||
|
||||
@@ -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,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,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,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,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,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,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;
|
||||
|
||||
@@ -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,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 +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;
|
||||
|
||||
@@ -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,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 +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,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,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,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,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,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,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,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;
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,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,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,
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user