@oh-my-pi/pi-catalog 18.2.5 → 18.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/THIRD-PARTY-NOTICES.txt +0 -37
  3. package/dist/types/build.d.ts +7 -1
  4. package/dist/types/compat/auth-ids.d.ts +1 -1
  5. package/dist/types/compat/axes.d.ts +1 -1
  6. package/dist/types/compat/cascade.d.ts +3 -2
  7. package/dist/types/compat/provider-ids.d.ts +1 -1
  8. package/dist/types/compat/resolve.d.ts +2 -0
  9. package/dist/types/compat/taxonomy.d.ts +2 -2
  10. package/dist/types/compat/types.d.ts +18 -7
  11. package/dist/types/discovery/index.d.ts +1 -0
  12. package/dist/types/discovery/openai-compatible.d.ts +2 -0
  13. package/dist/types/discovery/typesafe.d.ts +26 -0
  14. package/dist/types/model-manager.d.ts +1 -1
  15. package/dist/types/provider-models/openai-compat.d.ts +6 -4
  16. package/dist/types/provider-models/special.d.ts +10 -0
  17. package/dist/types/types.d.ts +20 -0
  18. package/package.json +4 -4
  19. package/src/build.ts +27 -5
  20. package/src/compat/auth-ids.ts +2 -0
  21. package/src/compat/axes.ts +9 -1
  22. package/src/compat/cascade.ts +45 -23
  23. package/src/compat/provider-ids.ts +3 -0
  24. package/src/compat/resolve.ts +24 -14
  25. package/src/compat/rules/README.md +5 -3
  26. package/src/compat/rules/auth/local.kdl +4 -0
  27. package/src/compat/rules/auth/typesafe.kdl +3 -6
  28. package/src/compat/rules/auth/web.kdl +4 -0
  29. package/src/compat/rules/classes/qwen.kdl +6 -5
  30. package/src/compat/rules/providers/anthropic.kdl +1 -0
  31. package/src/compat/rules/providers/deepinfra.kdl +28 -0
  32. package/src/compat/rules/providers/google-antigravity.kdl +17 -0
  33. package/src/compat/rules/providers/google.kdl +9 -0
  34. package/src/compat/rules/providers/llama.cpp.kdl +23 -0
  35. package/src/compat/rules/providers/local.kdl +110 -0
  36. package/src/compat/rules/providers/openai-codex.kdl +18 -0
  37. package/src/compat/rules/providers/openai.kdl +3 -0
  38. package/src/compat/rules/providers/openrouter.kdl +24 -0
  39. package/src/compat/rules/providers/typesafe.kdl +21 -0
  40. package/src/compat/rules/providers/web.kdl +153 -0
  41. package/src/compat/rules/providers/xai-oauth.kdl +26 -0
  42. package/src/compat/rules/providers/xai.kdl +22 -0
  43. package/src/compat/rules/runtime/behavior.kdl +2 -1
  44. package/src/compat/rules/taxonomy/qwen.kdl +2 -0
  45. package/src/compat/rules.json +1 -1
  46. package/src/compat/taxonomy.ts +92 -31
  47. package/src/compat/types.ts +22 -7
  48. package/src/discovery/index.ts +1 -0
  49. package/src/discovery/openai-compatible.ts +4 -1
  50. package/src/discovery/typesafe.ts +118 -0
  51. package/src/model-manager.ts +44 -15
  52. package/src/models.json +1 -1
  53. package/src/provider-models/descriptors.ts +6 -0
  54. package/src/provider-models/openai-compat.ts +246 -79
  55. package/src/provider-models/special.ts +53 -0
  56. package/src/types.ts +33 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.2.7] - 2026-09-21
6
+
7
+ ### Added
8
+
9
+ - Added model-kind and grounded-search capability metadata, along with catalogs for local inference and search-engine models.
10
+ - Added OpenRouter image-model discovery and live TypeSafe judge-model discovery.
11
+ - Added the `buildDiscoveredModel` helper for defining custom providers.
12
+ - Added glob-based patterns for identity overrides.
13
+
14
+ ### Changed
15
+
16
+ - Updated input cost for TypeSafe models to 0.042
17
+ - Improved model routing and thinking-policy handling for llama.cpp Qwen models, Bonsai lineage aliases, and custom provider names.
18
+
5
19
  ## [18.2.5] - 2026-09-17
6
20
 
7
21
  ### Added
@@ -335,43 +335,6 @@ This license allows the work and adaptations of it to be shared and used
335
335
  commercially, as long as it is attributed to Poppy Works. The font is bundled
336
336
  here (crates/pi-natives/src/fonts/Silver.ttf) as a CJK/Unicode bitmap fallback.
337
337
 
338
- -------------------------------------------------------------------------------
339
- packages/utils/src/vendor/mermaid-ascii/NOTICE
340
-
341
- This directory contains an in-house Mermaid-diagram-to-ASCII renderer adapted
342
- from beautiful-mermaid (https://github.com/lukilabs/beautiful-mermaid), used
343
- under the MIT License.
344
-
345
- Copyright (c) 2026 Craft Docs
346
-
347
- Only the ASCII rendering pipeline is ported (flowchart/state, sequence, class,
348
- ER, and xychart diagrams); the SVG renderer and its `elkjs` graph-layout
349
- dependency, the browser entry point, and the SVG theme/style modules were
350
- dropped. Terminal display width is reimplemented on `Bun.stringWidth`, and
351
- inline label formatting (HTML tags, markdown emphasis) is reduced to plain text
352
- for ASCII output. Layout and edge-routing logic is preserved faithfully so
353
- ASCII output matches the upstream package.
354
-
355
- MIT License
356
-
357
- Permission is hereby granted, free of charge, to any person obtaining a copy
358
- of this software and associated documentation files (the "Software"), to deal
359
- in the Software without restriction, including without limitation the rights
360
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
361
- copies of the Software, and to permit persons to whom the Software is
362
- furnished to do so, subject to the following conditions:
363
-
364
- The above copyright notice and this permission notice shall be included in all
365
- copies or substantial portions of the Software.
366
-
367
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
368
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
369
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
370
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
371
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
372
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
373
- SOFTWARE.
374
-
375
338
  -------------------------------------------------------------------------------
376
339
  packages/coding-agent/src/markit/NOTICE
377
340
 
@@ -6,7 +6,7 @@
6
6
  * Request handlers read fields — they never detect, parse ids, or allocate
7
7
  * compat per request.
8
8
  */
9
- import type { Api, Model, ModelSpec } from "./types.js";
9
+ import { type Api, type Model, type ModelSpec } from "./types.js";
10
10
  /**
11
11
  * Applies reviewed catalog-data value corrections (`cost-patch`,
12
12
  * `limits-patch`, `long-context-cost`, `context-window-floor`,
@@ -16,6 +16,12 @@ import type { Api, Model, ModelSpec } from "./types.js";
16
16
  * limits and pricing still win.
17
17
  */
18
18
  export declare function applyCatalogCorrections(model: Pick<ModelSpec<Api>, "cost" | "contextWindow" | "maxTokens" | "input">, catalog: Record<string, unknown>): void;
19
+ /**
20
+ * Build a discovered model using the backend's catalog-selected request API.
21
+ * The credential-bearing provider id remains unchanged while `providerType`
22
+ * persists the backend policy identity across cache and config round trips.
23
+ */
24
+ export declare function buildDiscoveredModel(spec: ModelSpec<Api>, providerType: string): Model<Api>;
19
25
  /**
20
26
  * Build one model from an authored spec. Bundled models.json rows are fully
21
27
  * materialized by the generator and consumed directly (see `models.ts`), so
@@ -1,4 +1,4 @@
1
1
  /** Every provider with an `auth/<id>.kdl` policy. */
2
- export type AuthProviderId = "abliteration" | "aiand" | "aimlapi" | "alibaba-coding-plan" | "alibaba-token-plan" | "amazon-bedrock" | "anthropic" | "azure" | "baseten" | "bedrock-mantle" | "cerebras" | "charm-hyper" | "cline-pass" | "cloudflare-ai-gateway" | "commandcode" | "coreweave" | "cursor" | "deepinfra" | "deepseek" | "devin" | "exa" | "firepass" | "fireworks" | "github-copilot" | "gitlab-duo" | "gitlab-duo-agent" | "gmi-cloud" | "google" | "google-antigravity" | "google-gemini-cli" | "google-vertex" | "groq" | "huggingface" | "kagi" | "kilo" | "kimi-code" | "litellm" | "llama.cpp" | "lm-studio" | "meta" | "minimax" | "minimax-code" | "minimax-code-cn" | "mistral" | "moonshot" | "muse-code" | "nanogpt" | "novita" | "nvidia" | "ollama" | "ollama-cloud" | "openai" | "openai-codex" | "openai-codex-device" | "opencode-go" | "opencode-zen" | "openrouter" | "parallel" | "perplexity" | "qianfan" | "qwen-portal" | "sakana" | "siliconflow" | "siliconflow-cn" | "stencil" | "synthetic" | "tavily" | "together" | "typesafe" | "umans" | "venice" | "vercel-ai-gateway" | "vllm" | "wafer-serverless" | "xai" | "xai-oauth" | "xiaomi" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-sgp" | "yolo-auto" | "zai" | "zai-coding-plan" | "zenmux" | "zhipu-coding-plan";
2
+ export type AuthProviderId = "abliteration" | "aiand" | "aimlapi" | "alibaba-coding-plan" | "alibaba-token-plan" | "amazon-bedrock" | "anthropic" | "azure" | "baseten" | "bedrock-mantle" | "cerebras" | "charm-hyper" | "cline-pass" | "cloudflare-ai-gateway" | "commandcode" | "coreweave" | "cursor" | "deepinfra" | "deepseek" | "devin" | "exa" | "firepass" | "fireworks" | "github-copilot" | "gitlab-duo" | "gitlab-duo-agent" | "gmi-cloud" | "google" | "google-antigravity" | "google-gemini-cli" | "google-vertex" | "groq" | "huggingface" | "kagi" | "kilo" | "kimi-code" | "litellm" | "llama.cpp" | "lm-studio" | "local" | "meta" | "minimax" | "minimax-code" | "minimax-code-cn" | "mistral" | "moonshot" | "muse-code" | "nanogpt" | "novita" | "nvidia" | "ollama" | "ollama-cloud" | "openai" | "openai-codex" | "openai-codex-device" | "opencode-go" | "opencode-zen" | "openrouter" | "parallel" | "perplexity" | "qianfan" | "qwen-portal" | "sakana" | "siliconflow" | "siliconflow-cn" | "stencil" | "synthetic" | "tavily" | "together" | "typesafe" | "umans" | "venice" | "vercel-ai-gateway" | "vllm" | "wafer-serverless" | "web" | "xai" | "xai-oauth" | "xiaomi" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-sgp" | "yolo-auto" | "zai" | "zai-coding-plan" | "zenmux" | "zhipu-coding-plan";
3
3
  /** Providers whose policy declares a `login` flow (the `/login` roster). */
4
4
  export type LoginProviderId = "abliteration" | "aiand" | "alibaba-coding-plan" | "alibaba-token-plan" | "anthropic" | "baseten" | "cerebras" | "charm-hyper" | "cline-pass" | "cloudflare-ai-gateway" | "commandcode" | "coreweave" | "cursor" | "deepinfra" | "deepseek" | "devin" | "exa" | "firepass" | "fireworks" | "github-copilot" | "gitlab-duo" | "gitlab-duo-agent" | "gmi-cloud" | "google-antigravity" | "google-gemini-cli" | "huggingface" | "kagi" | "kilo" | "kimi-code" | "litellm" | "llama.cpp" | "lm-studio" | "meta" | "minimax-code" | "minimax-code-cn" | "moonshot" | "muse-code" | "nanogpt" | "novita" | "nvidia" | "ollama" | "ollama-cloud" | "openai-codex" | "openai-codex-device" | "opencode-go" | "opencode-zen" | "openrouter" | "parallel" | "perplexity" | "qianfan" | "qwen-portal" | "sakana" | "siliconflow" | "siliconflow-cn" | "stencil" | "synthetic" | "tavily" | "together" | "typesafe" | "umans" | "venice" | "vercel-ai-gateway" | "vllm" | "wafer-serverless" | "xai" | "xai-oauth" | "xiaomi" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-sgp" | "yolo-auto" | "zai" | "zai-coding-plan" | "zenmux" | "zhipu-coding-plan";
@@ -10,7 +10,7 @@
10
10
  * one of the axis's declared records.
11
11
  */
12
12
  import type { Effort } from "../effort.js";
13
- import type { ThinkingControlMode } from "../types.js";
13
+ import { type ThinkingControlMode } from "../types.js";
14
14
  /** Value shape a directive accepts (see `rules/README.md`). */
15
15
  export type AxisShape = "scalar" | "array" | "object";
16
16
  /** Axis namespace: request-wire compat, thinking control surface, or catalog metadata. */
@@ -15,8 +15,9 @@ export declare class AmbiguousOverlapError extends Error {
15
15
  export declare function globMatch(pattern: string, value: string): boolean;
16
16
  /**
17
17
  * Resolve wire, thinking, and catalog assignments for one structured target.
18
- * Exact model effort corrections can enable reasoning; absent family/revision
19
- * facts never satisfy selectors that require them. Returned axes are caller-owned.
18
+ * Exact model effort corrections and identity-scoped neutral-upgrade policies
19
+ * can enable reasoning; absent family/revision facts never satisfy selectors
20
+ * that require them. Returned axes are caller-owned.
20
21
  *
21
22
  * @throws AmbiguousOverlapError when equal-rank rules contest one axis.
22
23
  */
@@ -1,2 +1,2 @@
1
1
  /** Every chat-model provider with a catalog entry (`default-model` in its provider rules). */
2
- export type KnownProvider = "abliteration" | "aiand" | "aimlapi" | "alibaba-coding-plan" | "alibaba-token-plan" | "amazon-bedrock" | "anthropic" | "azure" | "baseten" | "bedrock-mantle" | "cerebras" | "charm-hyper" | "cline-pass" | "cloudflare-ai-gateway" | "commandcode" | "coreweave" | "cursor" | "deepinfra" | "deepseek" | "devin" | "firepass" | "fireworks" | "github-copilot" | "gitlab-duo" | "gitlab-duo-agent" | "gmi-cloud" | "google" | "google-antigravity" | "google-gemini-cli" | "google-vertex" | "groq" | "huggingface" | "kilo" | "kimi-code" | "litellm" | "lm-studio" | "meta" | "minimax" | "minimax-code" | "minimax-code-cn" | "mistral" | "moonshot" | "muse-code" | "nanogpt" | "novita" | "nvidia" | "ollama" | "ollama-cloud" | "openai" | "openai-codex" | "opencode-go" | "opencode-zen" | "openrouter" | "qianfan" | "qwen-portal" | "sakana" | "siliconflow" | "siliconflow-cn" | "synthetic" | "together" | "umans" | "venice" | "vercel-ai-gateway" | "vllm" | "wafer-serverless" | "xai" | "xai-oauth" | "xiaomi" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-sgp" | "yolo-auto" | "zai" | "zenmux" | "zhipu-coding-plan";
2
+ export type KnownProvider = "abliteration" | "aiand" | "aimlapi" | "alibaba-coding-plan" | "alibaba-token-plan" | "amazon-bedrock" | "anthropic" | "azure" | "baseten" | "bedrock-mantle" | "cerebras" | "charm-hyper" | "cline-pass" | "cloudflare-ai-gateway" | "commandcode" | "coreweave" | "cursor" | "deepinfra" | "deepseek" | "devin" | "firepass" | "fireworks" | "github-copilot" | "gitlab-duo" | "gitlab-duo-agent" | "gmi-cloud" | "google" | "google-antigravity" | "google-gemini-cli" | "google-vertex" | "groq" | "huggingface" | "kilo" | "kimi-code" | "litellm" | "lm-studio" | "local" | "meta" | "minimax" | "minimax-code" | "minimax-code-cn" | "mistral" | "moonshot" | "muse-code" | "nanogpt" | "novita" | "nvidia" | "ollama" | "ollama-cloud" | "openai" | "openai-codex" | "opencode-go" | "opencode-zen" | "openrouter" | "qianfan" | "qwen-portal" | "sakana" | "siliconflow" | "siliconflow-cn" | "synthetic" | "together" | "typesafe" | "umans" | "venice" | "vercel-ai-gateway" | "vllm" | "wafer-serverless" | "web" | "xai" | "xai-oauth" | "xiaomi" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-sgp" | "yolo-auto" | "zai" | "zenmux" | "zhipu-coding-plan";
@@ -8,6 +8,8 @@ export interface ResolvedModelPolicy<TApi extends Api = Api> {
8
8
  /** Catalog-data axis assignments (longContext, priority, …) for generation. */
9
9
  catalog: Record<string, unknown>;
10
10
  }
11
+ /** Resolve the request adapter assigned to a discovery backend before materialization. */
12
+ export declare function resolveDiscoveryApi(spec: ModelSpec<Api>, providerType: string): Api;
11
13
  /**
12
14
  * Resolves the full policy surface for one model spec: structured identity,
13
15
  * complete compat record, thinking metadata, and catalog-data corrections.
@@ -10,12 +10,12 @@
10
10
  */
11
11
  import type { Effort } from "../effort.js";
12
12
  import type { ModelIdentity } from "./types.js";
13
- /** Two classes (or two families) tie for one model id. */
13
+ /** Two classes, families, or reviewed override patterns tie for one model id. */
14
14
  export declare class AmbiguousIdentityError extends Error {
15
15
  readonly model: string;
16
16
  readonly first: string;
17
17
  readonly second: string;
18
- constructor(model: string, first: string, second: string, kind: "class" | "family");
18
+ constructor(model: string, first: string, second: string, kind: "class" | "family" | "override");
19
19
  }
20
20
  /** Options for {@link classifyModel}. */
21
21
  export interface ClassifyOptions {
@@ -5,7 +5,7 @@
5
5
  * `behavior.ts`, `resolve.ts`) exposes to consumers.
6
6
  */
7
7
  import type { Effort } from "../effort.js";
8
- import type { KnownApi, ThinkingControlMode, TokenCost } from "../types.js";
8
+ import type { Api, ThinkingControlMode, TokenCost } from "../types.js";
9
9
  import type { RevisionOp } from "./revision.js";
10
10
  /** Class-membership matcher kinds, most to least specific. */
11
11
  export type MatcherKind = "exact" | "bounded" | "namespace" | "prefix" | "glob";
@@ -27,11 +27,9 @@ export interface CompiledRevisionPrefix {
27
27
  prefix: string;
28
28
  anywhere?: boolean;
29
29
  }
30
- /** One compiled reviewed identity correction. */
31
- export interface CompiledIdentityOverride {
30
+ interface CompiledIdentityOverrideFields {
32
31
  id: string;
33
32
  provider?: string;
34
- model: string;
35
33
  logical?: string;
36
34
  class?: string;
37
35
  family?: string;
@@ -43,6 +41,16 @@ export interface CompiledIdentityOverride {
43
41
  provenance: string;
44
42
  expiresAtMs?: number;
45
43
  }
44
+ /** One compiled reviewed identity correction with exactly one bare-model selector. */
45
+ export type CompiledIdentityOverride = CompiledIdentityOverrideFields & ({
46
+ /** Exact bare-model selector. */
47
+ model: string;
48
+ glob?: never;
49
+ } | {
50
+ model?: never;
51
+ /** Anchored, case-insensitive bare-model glob. */
52
+ glob: string;
53
+ });
46
54
  /** One compiled model class: matchers, families, revision rules, overrides. */
47
55
  export interface CompiledClass {
48
56
  id: string;
@@ -567,7 +575,7 @@ export interface CompiledProviderDiscovery {
567
575
  export interface CompiledSeedModel {
568
576
  id: string;
569
577
  name: string;
570
- api: KnownApi;
578
+ api: Api;
571
579
  provider: string;
572
580
  baseUrl: string;
573
581
  reasoning: boolean;
@@ -592,8 +600,8 @@ export interface CompiledSeed {
592
600
  models: CompiledSeedModel[];
593
601
  }
594
602
  /**
595
- * One chat-model provider's catalog entry: the non-code half of what the
596
- * runtime and generator know about a provider. A `providers/<id>.kdl` file
603
+ * One model provider's catalog entry: the non-code half of what the runtime
604
+ * and generator know about a provider. A `providers/<id>.kdl` file
597
605
  * declares one by carrying `default-model`; files without it are wire-compat
598
606
  * only (custom provider ids such as `llama.cpp`).
599
607
  */
@@ -611,6 +619,8 @@ export interface CompiledProvider {
611
619
  skipCrossProviderReferenceFills?: boolean;
612
620
  /** Present only for providers enrolled in `generate-models.ts` discovery. */
613
621
  discovery?: CompiledProviderDiscovery;
622
+ /** Non-chat model kinds mapped to their runtime transport APIs. */
623
+ kindApis?: Partial<Record<"image" | "tts" | "stt", Api>>;
614
624
  /** Authored bundled rows, when the provider cannot be discovered at generation time. */
615
625
  seed?: CompiledSeed;
616
626
  }
@@ -673,3 +683,4 @@ export interface ResolvedAxes {
673
683
  */
674
684
  reasoning: boolean;
675
685
  }
686
+ export {};
@@ -5,3 +5,4 @@ export * from "./gemini-cli.js";
5
5
  export * from "./gitlab-duo-workflow.js";
6
6
  export * from "./openai-compatible.js";
7
7
  export * from "./protobuf.js";
8
+ export * from "./typesafe.js";
@@ -59,6 +59,8 @@ export interface FetchOpenAICompatibleModelsOptions<TApi extends Api> {
59
59
  apiKey?: string;
60
60
  /** Additional request headers. */
61
61
  headers?: Record<string, string>;
62
+ /** Query parameters appended to the `/models` probe (e.g. OpenRouter's `output_modalities` roster filter). */
63
+ query?: Record<string, string>;
62
64
  /** Optional AbortSignal for request cancellation; caller owns its lifecycle. */
63
65
  signal?: AbortSignal;
64
66
  /**
@@ -0,0 +1,26 @@
1
+ import type { FetchImpl, ModelSpec } from "../types.js";
2
+ /** Public TypeSafe API root shared by discovery and judgment requests. */
3
+ export declare const TYPESAFE_DEFAULT_BASE_URL = "https://api.typesafe.ai";
4
+ /** Wire card returned by TypeSafe's authenticated `GET /v1/models`. */
5
+ export interface TypeSafeModelCard {
6
+ name: string;
7
+ description: string;
8
+ release_date: string;
9
+ }
10
+ /** Credentials and transport overrides for TypeSafe account model discovery. */
11
+ export interface FetchTypeSafeModelsOptions {
12
+ apiKey: string;
13
+ baseUrl?: string;
14
+ fetch?: FetchImpl;
15
+ signal?: AbortSignal;
16
+ }
17
+ /** Validate and de-duplicate the official `{ models: [...] }` response. */
18
+ export declare function parseTypeSafeModelCards(payload: unknown): TypeSafeModelCard[] | null;
19
+ /**
20
+ * Fetch TypeSafe's account-visible judge models.
21
+ *
22
+ * Returns `null` for transport, HTTP, or envelope failures. A valid empty
23
+ * roster returns `[]`, allowing the model manager to distinguish it from an
24
+ * unavailable discovery endpoint.
25
+ */
26
+ export declare function fetchTypeSafeModels(options: FetchTypeSafeModelsOptions): Promise<ModelSpec<"typesafe">[] | null>;
@@ -1,4 +1,4 @@
1
- import type { Api, Model, ModelSpec, Provider } from "./types.js";
1
+ import { type Api, type Model, type ModelSpec, type Provider } from "./types.js";
2
2
  /**
3
3
  * Controls when dynamic endpoint models should be fetched.
4
4
  */
@@ -1,11 +1,12 @@
1
1
  import type { ModelManagerOptions, ModelsDevFallback } from "../model-manager.js";
2
2
  import { getBundledModels } from "../models.js";
3
- import type { Api, FetchImpl, ModelSpec, Provider } from "../types.js";
3
+ import { type Api, type FetchImpl, type ModelSpec, type Provider } from "../types.js";
4
4
  import { ALIBABA_TOKEN_PLAN_BASE_URL } from "../wire/alibaba-token-plan.js";
5
5
  import type { ModelManagerConfig } from "./descriptor-types.js";
6
6
  export interface ModelsDevModel {
7
7
  id?: string;
8
8
  name?: string;
9
+ kind?: string;
9
10
  tool_call?: boolean;
10
11
  reasoning?: boolean;
11
12
  reasoning_options?: Array<{
@@ -26,6 +27,7 @@ export interface ModelsDevModel {
26
27
  };
27
28
  modalities?: {
28
29
  input?: string[];
30
+ output?: string[];
29
31
  };
30
32
  status?: string;
31
33
  provider?: {
@@ -167,8 +169,8 @@ export declare function applyXaiResponsesThinkingPolicy(model: ModelSpec<"openai
167
169
  * Render the xai-oauth KDL seed as the static runtime fallback consumed by
168
170
  * {@link xaiOAuthModelManagerOptions}.
169
171
  */
170
- export declare function buildXaiOAuthStaticSeed(baseUrl?: string): ModelSpec<"openai-responses">[];
171
- export declare function xaiOAuthModelManagerOptions(config?: XaiOAuthModelManagerConfig): ModelManagerOptions<"openai-responses">;
172
+ export declare function buildXaiOAuthStaticSeed(baseUrl?: string): ModelSpec<Api>[];
173
+ export declare function xaiOAuthModelManagerOptions(config?: XaiOAuthModelManagerConfig): ModelManagerOptions<Api>;
172
174
  export declare function isLikelyAimlApiChatModelId(id: string): boolean;
173
175
  export interface AimlApiModelManagerConfig {
174
176
  apiKey?: string;
@@ -322,7 +324,7 @@ export interface OpenRouterModelManagerConfig {
322
324
  baseUrl?: string;
323
325
  fetch?: FetchImpl;
324
326
  }
325
- export declare function openrouterModelManagerOptions(config?: OpenRouterModelManagerConfig): ModelManagerOptions<"openrouter">;
327
+ export declare function openrouterModelManagerOptions(config?: OpenRouterModelManagerConfig): ModelManagerOptions<Api>;
326
328
  export interface ZenMuxModelManagerConfig {
327
329
  apiKey?: string;
328
330
  baseUrl?: string;
@@ -55,6 +55,16 @@ export interface DevinModelManagerConfig {
55
55
  fetch?: DevinModelDiscoveryOptions["fetch"];
56
56
  }
57
57
  export declare function devinModelManagerOptions(config?: DevinModelManagerConfig): ModelManagerOptions<"devin-agent">;
58
+ export declare function localModelManagerOptions(): ModelManagerOptions<"local-inference">;
59
+ export declare function webModelManagerOptions(): ModelManagerOptions<"web-search">;
60
+ /** Credentials and endpoint overrides for the TypeSafe catalog manager. */
61
+ export interface TypeSafeModelManagerConfig {
62
+ apiKey?: string;
63
+ baseUrl?: string;
64
+ fetch?: FetchImpl;
65
+ }
66
+ /** Discover account-visible judge models while keeping the bundled offline seed. */
67
+ export declare function typesafeModelManagerOptions(config?: TypeSafeModelManagerConfig): ModelManagerOptions<"typesafe">;
58
68
  export interface ZaiModelManagerConfig {
59
69
  }
60
70
  /** Creates model-manager options for Z.AI's mixed native and Anthropic transports. */
@@ -4,6 +4,16 @@ export type { FetchImpl } from "@oh-my-pi/pi-utils";
4
4
  export type { KnownProvider } from "./provider-models/descriptors.js";
5
5
  export type KnownApi = "openai-completions" | "openai-responses" | "openrouter" | "openai-codex-responses" | "azure-openai-responses" | "anthropic-messages" | "bedrock-converse-stream" | "google-generative-ai" | "google-gemini-cli" | "google-vertex" | "ollama-chat" | "cursor-agent" | "gitlab-duo-agent" | "devin-agent";
6
6
  export type Api = KnownApi | (string & {});
7
+ /** Catalog kinds used to isolate role-specific runners from session chat models. */
8
+ export declare const MODEL_KINDS: readonly ["chat", "tiny", "image", "tts", "stt", "search", "judge"];
9
+ /** Technical capability of a catalog model; absent model kinds mean chat. */
10
+ export type ModelKind = (typeof MODEL_KINDS)[number];
11
+ /** Grounding transport available to chat models selected by the web role. */
12
+ export type WebSearchGrounding = "gemini" | "anthropic" | "codex" | "xai" | "openrouter";
13
+ /** Non-chat runner protocols accepted by catalog seeds, outside the chat dispatch union. */
14
+ export declare const RUNNER_APIS: readonly ["local-inference", "web-search", "typesafe", "openrouter-decisions", "openai-images", "openrouter-images", "xai-tts", "openai-speech"];
15
+ /** Resolve a model's kind while preserving chat semantics for existing catalog rows. */
16
+ export declare function modelKind(model: Pick<Model, "kind">): ModelKind;
7
17
  /** Canonical thinking transport used by a model. */
8
18
  export type ThinkingControlMode = "effort" | "budget" | "google-level" | "anthropic-adaptive" | "anthropic-budget-effort";
9
19
  /** Per-model thinking capabilities used to clamp and map user-facing effort levels. */
@@ -942,6 +952,10 @@ export interface ModelCost extends TokenCost {
942
952
  export type ModelTokenizer = "claude-v3" | "claude-v47" | "claude-v5" | "claude-v5-sonnet" | "qwen3" | "deepseek-v3" | "kimi-k2" | "glm5";
943
953
  export interface Model<TApi extends Api = Api> {
944
954
  id: string;
955
+ /** Role-specific runner capability; omitted for ordinary chat models. */
956
+ kind?: ModelKind;
957
+ /** Grounding transport supported by this chat model. */
958
+ webSearch?: WebSearchGrounding;
945
959
  /**
946
960
  * Structured model identity resolved by the compat engine: vendor lineage
947
961
  * class, product family, and revision. Baked into models.json rows and
@@ -984,6 +998,12 @@ export interface Model<TApi extends Api = Api> {
984
998
  name: string;
985
999
  api: TApi;
986
1000
  provider: Provider;
1001
+ /**
1002
+ * Discovery backend whose catalog policy applies when it differs from the
1003
+ * credential-bearing provider id. Persisted so cached and rebuilt custom
1004
+ * providers retain their transport backend's policy.
1005
+ */
1006
+ providerType?: string;
987
1007
  baseUrl: string;
988
1008
  reasoning: boolean;
989
1009
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-catalog",
4
- "version": "18.2.5",
4
+ "version": "18.2.7",
5
5
  "description": "Model catalog for omp: bundled model database, provider discovery descriptors, model identity, classification, and equivalence",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Stencil Labs, Inc.",
@@ -35,12 +35,12 @@
35
35
  "gen:proto": "bun scripts/generate-protocols.ts"
36
36
  },
37
37
  "dependencies": {
38
- "@oh-my-pi/omptype": "18.2.5",
39
- "@oh-my-pi/pi-utils": "18.2.5"
38
+ "@oh-my-pi/omptype": "18.2.7",
39
+ "@oh-my-pi/pi-utils": "18.2.7"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@bgotink/kdl": "0.4.0",
43
- "@oh-my-pi/pi-ai": "18.2.5",
43
+ "@oh-my-pi/pi-ai": "18.2.7",
44
44
  "@types/bun": "^1.3.14"
45
45
  },
46
46
  "engines": {
package/src/build.ts CHANGED
@@ -7,11 +7,11 @@
7
7
  * compat per request.
8
8
  */
9
9
 
10
- import { resolveModelPolicy } from "./compat/resolve";
10
+ import { resolveDiscoveryApi, resolveModelPolicy } from "./compat/resolve";
11
11
  import type { ModelIdentity } from "./compat/types";
12
12
  import { resolveModelTokenizer } from "./model-tokenizer";
13
13
  import { materializeTimeBasedCost } from "./pricing";
14
- import type { Api, Model, ModelSpec } from "./types";
14
+ import { type Api, MODEL_KINDS, type Model, type ModelSpec } from "./types";
15
15
  import { cleanModelName } from "./utils";
16
16
 
17
17
  function numberField(source: object, key: string): number | undefined {
@@ -74,6 +74,18 @@ function isInputModalities(value: unknown): value is ("text" | "image")[] {
74
74
  * only when the spec left it unset.
75
75
  */
76
76
  function applyCatalogAssignments<TApi extends Api>(model: Model<TApi>, catalog: Record<string, unknown>): void {
77
+ const kind = MODEL_KINDS.find(value => value === catalog.kind);
78
+ if (kind !== undefined) model.kind = kind;
79
+ const webSearch = catalog.webSearch;
80
+ if (
81
+ webSearch === "gemini" ||
82
+ webSearch === "anthropic" ||
83
+ webSearch === "codex" ||
84
+ webSearch === "xai" ||
85
+ webSearch === "openrouter"
86
+ ) {
87
+ model.webSearch = webSearch;
88
+ }
77
89
  const serviceTierCost = objectPayload(catalog.serviceTierCost);
78
90
  if (serviceTierCost !== undefined) {
79
91
  const flex = numberField(serviceTierCost, "flex");
@@ -273,6 +285,16 @@ function supportsOpenAIGAComputerUse(
273
285
  return wireIdentity.class === "openai" && revisionAtLeast(wireIdentity, 5, 4);
274
286
  }
275
287
 
288
+ /**
289
+ * Build a discovered model using the backend's catalog-selected request API.
290
+ * The credential-bearing provider id remains unchanged while `providerType`
291
+ * persists the backend policy identity across cache and config round trips.
292
+ */
293
+ export function buildDiscoveredModel(spec: ModelSpec<Api>, providerType: string): Model<Api> {
294
+ const api = resolveDiscoveryApi(spec, providerType);
295
+ return buildModel({ ...spec, api, providerType });
296
+ }
297
+
276
298
  /**
277
299
  * Build one model from an authored spec. Bundled models.json rows are fully
278
300
  * materialized by the generator and consumed directly (see `models.ts`), so
@@ -283,9 +305,9 @@ export function buildModel<TApi extends Api>(spec: ModelSpec<TApi>): Model<TApi>
283
305
  const supportsComputerUseConfig = explicitComputerUseConfig(spec);
284
306
  const model: Model<TApi> = {
285
307
  ...spec,
286
- // An exact `thinking-efforts` rule upgrades a stale `reasoning: false`
287
- // discovery default (see `resolveThinkingPolicy`); materialize the
288
- // correction so transports and the picker see a reasoning-capable model.
308
+ // A reviewed `thinking-upgrade-neutral` policy can repair a stale
309
+ // `reasoning: false` discovery default (see `resolveThinkingPolicy`);
310
+ // materialize the correction for transports and the picker.
289
311
  reasoning: spec.reasoning || policy.thinking !== undefined,
290
312
  name: cleanModelName(spec.name),
291
313
  identity: policy.identity,
@@ -41,6 +41,7 @@ export type AuthProviderId =
41
41
  | "litellm"
42
42
  | "llama.cpp"
43
43
  | "lm-studio"
44
+ | "local"
44
45
  | "meta"
45
46
  | "minimax"
46
47
  | "minimax-code"
@@ -76,6 +77,7 @@ export type AuthProviderId =
76
77
  | "vercel-ai-gateway"
77
78
  | "vllm"
78
79
  | "wafer-serverless"
80
+ | "web"
79
81
  | "xai"
80
82
  | "xai-oauth"
81
83
  | "xiaomi"
@@ -10,7 +10,7 @@
10
10
  * one of the axis's declared records.
11
11
  */
12
12
  import type { Effort } from "../effort";
13
- import type { ThinkingControlMode } from "../types";
13
+ import { MODEL_KINDS, type ThinkingControlMode } from "../types";
14
14
 
15
15
  /** Value shape a directive accepts (see `rules/README.md`). */
16
16
  export type AxisShape = "scalar" | "array" | "object";
@@ -290,9 +290,17 @@ export const AXES: Readonly<Record<string, AxisDef>> = {
290
290
  "cost-patch": { key: "costPatch", set: "catalog", shape: "object" },
291
291
  "cost-fallback": { key: "costFallback", set: "catalog", shape: "object" },
292
292
  "delegation-bias": { key: "delegationBias", set: "catalog", shape: "scalar", values: DELEGATION_BIASES },
293
+ "discovery-api": { key: "discoveryApi", set: "catalog", shape: "scalar" },
293
294
  "edit-prompt-variant": { key: "editPromptVariant", set: "catalog", shape: "scalar", values: ["full", "compact"] },
294
295
  "edit-revision": { key: "editRevision", set: "catalog", shape: "scalar" },
295
296
  "input-modalities": { key: "inputModalities", set: "catalog", shape: "array", values: ["text", "image"] },
297
+ kind: { key: "kind", set: "catalog", shape: "scalar", values: MODEL_KINDS },
298
+ "web-search": {
299
+ key: "webSearch",
300
+ set: "catalog",
301
+ shape: "scalar",
302
+ values: ["gemini", "anthropic", "codex", "xai", "openrouter"],
303
+ },
296
304
  "limits-patch": { key: "limitsPatch", set: "catalog", shape: "object" },
297
305
  "long-context-cost": { key: "longContext", set: "catalog", shape: "object" },
298
306
  "long-usage-limit-fallback": { key: "longUsageLimitFallback", set: "catalog", shape: "scalar" },
@@ -239,6 +239,30 @@ function rankCompare(a: readonly [number, number, number], b: readonly [number,
239
239
  return a[0] - b[0] || a[1] - b[1] || a[2] - b[2];
240
240
  }
241
241
 
242
+ function contestAxis(
243
+ winners: WinnerTable,
244
+ axis: string,
245
+ rank: readonly [number, number, number],
246
+ rule: IndexedRule,
247
+ target: ResolveTarget,
248
+ ): void {
249
+ const held = winners[axis];
250
+ if (held) {
251
+ const order = rankCompare(held.rank, rank);
252
+ if (order === 0) {
253
+ throw new AmbiguousOverlapError(
254
+ target.provider,
255
+ target.model,
256
+ axis,
257
+ held.rule.compiled.source,
258
+ rule.compiled.source,
259
+ );
260
+ }
261
+ if (order > 0) return;
262
+ }
263
+ winners[axis] = { rank, rule };
264
+ }
265
+
242
266
  function contest(
243
267
  winners: WinnerTable,
244
268
  axes: Record<string, unknown> | undefined,
@@ -247,23 +271,7 @@ function contest(
247
271
  target: ResolveTarget,
248
272
  ): void {
249
273
  if (!axes) return;
250
- for (const axis in axes) {
251
- const held = winners[axis];
252
- if (held) {
253
- const order = rankCompare(held.rank, rank);
254
- if (order === 0) {
255
- throw new AmbiguousOverlapError(
256
- target.provider,
257
- target.model,
258
- axis,
259
- held.rule.compiled.source,
260
- rule.compiled.source,
261
- );
262
- }
263
- if (order > 0) continue;
264
- }
265
- winners[axis] = { rank, rule };
266
- }
274
+ for (const axis in axes) contestAxis(winners, axis, rank, rule, target);
267
275
  }
268
276
 
269
277
  function collect(winners: WinnerTable, pick: (rule: CompiledRule) => Record<string, unknown> | undefined) {
@@ -314,8 +322,9 @@ function cloneAxes(axes: ResolvedAxes): ResolvedAxes {
314
322
 
315
323
  /**
316
324
  * Resolve wire, thinking, and catalog assignments for one structured target.
317
- * Exact model effort corrections can enable reasoning; absent family/revision
318
- * facts never satisfy selectors that require them. Returned axes are caller-owned.
325
+ * Exact model effort corrections and identity-scoped neutral-upgrade policies
326
+ * can enable reasoning; absent family/revision facts never satisfy selectors
327
+ * that require them. Returned axes are caller-owned.
319
328
  *
320
329
  * @throws AmbiguousOverlapError when equal-rank rules contest one axis.
321
330
  */
@@ -368,11 +377,24 @@ function resolveOverIndex(index: RuleIndex, target: ResolveTarget): ResolvedAxes
368
377
  const ranked = rankRelevantRules(index, prepareTarget(target));
369
378
  let reasoning = target.reasoning === true;
370
379
  if (!reasoning) {
380
+ const upgrade: WinnerTable = {};
381
+ let hasEfforts = false;
371
382
  for (const { rule, rank } of ranked) {
372
- if (rule.hasExactEffortsRule && rank[0] === 2) {
373
- reasoning = true;
374
- break;
375
- }
383
+ const thinking = rule.compiled.thinking;
384
+ if (thinking === undefined) continue;
385
+ if ("upgradeNeutral" in thinking) contestAxis(upgrade, "upgradeNeutral", rank, rule, target);
386
+ if ("efforts" in thinking) hasEfforts = true;
387
+ if (rule.hasExactEffortsRule && rank[0] === 2) reasoning = true;
388
+ }
389
+ const upgradeRule = upgrade.upgradeNeutral?.rule.compiled;
390
+ const identityScoped =
391
+ upgradeRule !== undefined &&
392
+ ((upgradeRule.class !== undefined && upgradeRule.class !== "unknown") ||
393
+ upgradeRule.family !== undefined ||
394
+ upgradeRule.revision !== undefined ||
395
+ upgradeRule.models !== undefined);
396
+ if (identityScoped && upgradeRule?.thinking?.upgradeNeutral === true && hasEfforts) {
397
+ reasoning = true;
376
398
  }
377
399
  }
378
400
  const wire: WinnerTable = {};
@@ -38,6 +38,7 @@ export type KnownProvider =
38
38
  | "kimi-code"
39
39
  | "litellm"
40
40
  | "lm-studio"
41
+ | "local"
41
42
  | "meta"
42
43
  | "minimax"
43
44
  | "minimax-code"
@@ -62,11 +63,13 @@ export type KnownProvider =
62
63
  | "siliconflow-cn"
63
64
  | "synthetic"
64
65
  | "together"
66
+ | "typesafe"
65
67
  | "umans"
66
68
  | "venice"
67
69
  | "vercel-ai-gateway"
68
70
  | "vllm"
69
71
  | "wafer-serverless"
72
+ | "web"
70
73
  | "xai"
71
74
  | "xai-oauth"
72
75
  | "xiaomi"