@oh-my-pi/pi-catalog 16.1.23 → 16.2.1

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [16.2.0] - 2026-06-27
6
+
7
+ ### Added
8
+
9
+ - Added GitLab Duo Agent catalog discovery, including namespace selection, live model mapping, and a bundled fallback model for fresh installs.
10
+ - Added OpenAICompat.supportsNamedToolChoice to support forced tool use on string-only OpenAI-compatible chat servers without emitting the named function-object tool_choice shape.
11
+ - Added model metadata support for provider-native remote compaction and compaction-only model selection.
12
+
13
+ ### Changed
14
+
15
+ - Disabled the thinking-effort selector for GitLab Duo Agent models since the underlying platform parameters are server-fixed.
16
+
17
+ ### Fixed
18
+
19
+ - Improved GitLab Duo Agent and Duo Workflow namespace and project discovery to robustly handle paginated groups, SSH remotes with custom ports, Git worktrees, self-managed GitLab instances with relative paths, and configuration via GITLAB_DUO_PROJECT_PATH or GITLAB_DUO_PROJECT_ID.
20
+ - Fixed built-in LiteLLM discovery to prefer rich proxy metadata from management endpoints and avoid caching stale capability data.
21
+ - Fixed GitLab Duo Workflow model specifications to resolve correct static context windows, enabling accurate context usage tracking and auto-compaction.
22
+
5
23
  ## [16.1.23] - 2026-06-26
6
24
 
7
25
  ### Added
package/README.md CHANGED
@@ -24,7 +24,7 @@ Import from subpaths (`@oh-my-pi/pi-catalog/<module>`) or the root barrel.
24
24
  Never edit `src/models.json` by hand — it is produced from upstream sources (models.dev, provider catalog discovery, OpenCode docs) by `scripts/generate-models.ts` and the resolvers in `src/provider-models/`. Regenerate with:
25
25
 
26
26
  ```sh
27
- bun --cwd=packages/catalog run generate-models
27
+ bun run gen:models
28
28
  ```
29
29
 
30
30
  To change an entry, fix the source: resolver overrides in `provider-models/openai-compat.ts`, provider entries in `provider-models/descriptors.ts`, generator fixups in `scripts/generate-models.ts`, or thinking policies in `model-thinking.ts`.
@@ -0,0 +1,30 @@
1
+ import type { FetchImpl, ModelSpec } from "../types";
2
+ type GitLabDuoWorkflowCandidateSource = "override" | "project" | "remote" | "group";
3
+ export interface GitLabDuoWorkflowModelRef {
4
+ name: string;
5
+ ref: string;
6
+ }
7
+ /**
8
+ * GitLab Duo Workflow model/namespace discovery configuration.
9
+ */
10
+ export interface GitLabDuoWorkflowDiscoveryConfig {
11
+ apiKey: string;
12
+ baseUrl?: string;
13
+ fetch?: FetchImpl;
14
+ namespaceId?: string;
15
+ projectId?: string;
16
+ projectPath?: string;
17
+ cwd?: string;
18
+ }
19
+ export interface GitLabDuoWorkflowNamespaceSelection {
20
+ rootNamespaceId: string;
21
+ namespacePath?: string;
22
+ projectPath?: string;
23
+ source: GitLabDuoWorkflowCandidateSource;
24
+ }
25
+ export declare function discoverGitLabDuoWorkflowNamespace(config: GitLabDuoWorkflowDiscoveryConfig): Promise<GitLabDuoWorkflowNamespaceSelection>;
26
+ export declare function discoverGitLabDuoWorkflowRuntimeNamespace(config: GitLabDuoWorkflowDiscoveryConfig): Promise<GitLabDuoWorkflowNamespaceSelection>;
27
+ export declare function fetchGitLabDuoWorkflowModels(config: GitLabDuoWorkflowDiscoveryConfig): Promise<readonly ModelSpec<"gitlab-duo-agent">[] | null>;
28
+ export declare function buildGitLabDuoWorkflowModelSpec(model: GitLabDuoWorkflowModelRef, baseUrl?: string, rootNamespaceId?: string): ModelSpec<"gitlab-duo-agent">;
29
+ export declare function buildGitLabDuoWorkflowFallbackModel(id?: string, name?: string, baseUrl?: string): ModelSpec<"gitlab-duo-agent">;
30
+ export {};
@@ -1,4 +1,5 @@
1
1
  export * from "./antigravity";
2
2
  export * from "./codex";
3
3
  export * from "./gemini";
4
+ export * from "./gitlab-duo-workflow";
4
5
  export * from "./openai-compatible";
@@ -50,7 +50,7 @@ export declare function allowsUnauthenticatedCatalogDiscovery(descriptor: Catalo
50
50
  * - Every entry is a member of `KnownProvider`.
51
51
  * - `createModelManagerOptions` present (and not `specialModelManager`) ⇒
52
52
  * appears in `PROVIDER_DESCRIPTORS` for runtime model discovery.
53
- * - `catalogDiscovery` present ⇒ participates in `generate-models.ts`.
53
+ * - \`catalogDiscovery\` present ⇒ participates in \`generate-models.ts\`.
54
54
  */
55
55
  export interface ProviderCatalogEntry {
56
56
  readonly id: string;
@@ -101,6 +101,12 @@ export declare const CATALOG_PROVIDERS: readonly [{
101
101
  readonly id: "gitlab-duo";
102
102
  readonly defaultModel: "duo-chat-opus-4-6";
103
103
  readonly envVars: readonly ["GITLAB_TOKEN"];
104
+ }, {
105
+ readonly id: "gitlab-duo-agent";
106
+ readonly defaultModel: "claude_sonnet_4_6_vertex";
107
+ readonly envVars: readonly ["GITLAB_TOKEN"];
108
+ readonly createModelManagerOptions: (config: ModelManagerConfig) => import("..").ModelManagerOptions<"gitlab-duo-agent", unknown>;
109
+ readonly dynamicModelsAuthoritative: true;
104
110
  }, {
105
111
  readonly id: "google";
106
112
  readonly defaultModel: "gemini-3.1-pro-preview";
@@ -118,7 +118,7 @@ export declare const XAI_OAUTH_CURATED_MODELS: readonly XAICuratedModel[];
118
118
  * Single source of truth for the curated to Model fan-in, consumed by both
119
119
  * - {@link xaiOAuthModelManagerOptions} (runtime static seed handed to the model
120
120
  * manager so the picker is populated on a fresh login), and
121
- * - `packages/ai/scripts/generate-models.ts` (bundles the same entries into
121
+ * - \`packages/catalog/scripts/generate-models.ts\` (bundles the same entries into
122
122
  * `models.json`, so the synchronous `ModelRegistry.#loadModels()` boot path
123
123
  * sees `xai-oauth` without waiting for a refresh — fixes the boot-time
124
124
  * default-model reset when `modelRoles.default = "xai-oauth/<id>"`).
@@ -363,6 +363,20 @@ export interface LiteLLMModelManagerConfig {
363
363
  baseUrl?: string;
364
364
  fetch?: FetchImpl;
365
365
  }
366
+ export interface FetchLiteLLMRichModelsOptions<TApi extends Api> {
367
+ api: TApi;
368
+ provider: Provider;
369
+ baseUrl: string;
370
+ apiKey?: string;
371
+ headers?: Record<string, string>;
372
+ fetch?: FetchImpl;
373
+ signal?: AbortSignal;
374
+ referenceResolver?: (modelId: string) => ModelSpec<TApi> | undefined;
375
+ }
376
+ export declare const OPENAI_COMPAT_DISCOVERY_DEFAULT_CONTEXT_WINDOW = 128000;
377
+ export declare const OPENAI_COMPAT_DISCOVERY_DEFAULT_MAX_TOKENS = 32768;
378
+ export declare function normalizeLiteLLMManagementBaseUrl(baseUrl: string): string;
379
+ export declare function fetchLiteLLMRichModels<TApi extends Api>(options: FetchLiteLLMRichModelsOptions<TApi>): Promise<ModelSpec<TApi>[] | null>;
366
380
  export declare function litellmModelManagerOptions(config?: LiteLLMModelManagerConfig): ModelManagerOptions<"openai-completions">;
367
381
  export interface VllmModelManagerConfig {
368
382
  apiKey?: string;
@@ -1,5 +1,6 @@
1
1
  import type { DevinModelDiscoveryOptions } from "../discovery/devin";
2
2
  import type { ModelManagerOptions } from "../model-manager";
3
+ import type { FetchImpl } from "../types";
3
4
  export interface OpenAICodexModelManagerConfig {
4
5
  accessToken?: string;
5
6
  accountId?: string;
@@ -12,6 +13,15 @@ export interface CursorModelManagerConfig {
12
13
  clientVersion?: string;
13
14
  }
14
15
  export declare function cursorModelManagerOptions(config?: CursorModelManagerConfig): ModelManagerOptions<"cursor-agent">;
16
+ export interface GitLabDuoWorkflowModelManagerConfig {
17
+ apiKey?: string;
18
+ baseUrl?: string;
19
+ fetch?: FetchImpl;
20
+ namespaceId?: string;
21
+ projectId?: string;
22
+ cwd?: string;
23
+ }
24
+ export declare function gitLabDuoWorkflowModelManagerOptions(config?: GitLabDuoWorkflowModelManagerConfig): ModelManagerOptions<"gitlab-duo-agent">;
15
25
  export interface DevinModelManagerConfig {
16
26
  apiKey?: string;
17
27
  baseUrl?: string;
@@ -1,6 +1,6 @@
1
1
  import type { Effort } from "./effort";
2
2
  export type { KnownProvider } from "./provider-models/descriptors";
3
- 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" | "devin-agent";
3
+ 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";
4
4
  export type Api = KnownApi | (string & {});
5
5
  /** Canonical thinking transport used by a model. */
6
6
  export type ThinkingControlMode = "effort" | "budget" | "google-level" | "anthropic-adaptive" | "anthropic-budget-effort";
@@ -231,6 +231,14 @@ export interface OpenAICompat {
231
231
  * to provider-default auto selection. Default: true.
232
232
  */
233
233
  supportsForcedToolChoice?: boolean;
234
+ /**
235
+ * Whether the chat-completions endpoint accepts the object form that pins one
236
+ * named function (`{ type: "function", function: { name } }`). Some
237
+ * OpenAI-compatible hosts such as llama.cpp only accept string
238
+ * `tool_choice` values; request builders downgrade a named force to
239
+ * `"required"` when this is false. Default: true.
240
+ */
241
+ supportsNamedToolChoice?: boolean;
234
242
  /**
235
243
  * Drop reasoning fields (`reasoning_effort`, OpenRouter `reasoning`) for
236
244
  * the request when `tool_choice` forces a tool call. Mirrors the Anthropic
@@ -423,6 +431,7 @@ export interface ResolvedOpenAISharedCompat {
423
431
  disableReasoningOnToolChoice: boolean;
424
432
  supportsToolChoice: boolean;
425
433
  supportsForcedToolChoice: boolean;
434
+ supportsNamedToolChoice: boolean;
426
435
  reasoningContentField?: OpenAICompat["reasoningContentField"];
427
436
  requiresReasoningContentForToolCalls: boolean;
428
437
  requiresReasoningContentForAllAssistantTurns: boolean;
@@ -456,7 +465,7 @@ export interface ResolvedOpenAISharedCompat {
456
465
  * `buildModel`; request handlers read fields and never detect, resolve, or
457
466
  * allocate.
458
467
  */
459
- export type ResolvedOpenAICompat = ResolvedOpenAISharedCompat & Required<Omit<OpenAICompat, "supportsDeveloperRole" | "supportsReasoningEffort" | "reasoningEffortMap" | "supportsReasoningParams" | "thinkingFormat" | "reasoningDisableMode" | "omitReasoningEffort" | "includeEncryptedReasoning" | "filterReasoningHistory" | "disableReasoningOnForcedToolChoice" | "disableReasoningOnToolChoice" | "supportsToolChoice" | "supportsForcedToolChoice" | "reasoningContentField" | "requiresReasoningContentForToolCalls" | "requiresReasoningContentForAllAssistantTurns" | "allowsSyntheticReasoningContentForToolCalls" | "replayReasoningContent" | "qwenPreserveThinking" | "requiresThinkingAsText" | "requiresMistralToolIds" | "requiresToolResultName" | "requiresAssistantAfterToolResult" | "requiresAssistantContentForToolCalls" | "stripDeepseekSpecialTokens" | "streamMarkupHealingPattern" | "reasoningDeltasMayBeCumulative" | "emptyLengthFinishIsContextError" | "usesOpenAIToolCallIdLimit" | "promptCacheSessionHeader" | "openRouterRouting" | "isOpenRouterHost" | "supportsStrictMode" | "supportsLongPromptCacheRetention" | "alwaysSendMaxTokens" | "wireModelIdMode" | "vercelGatewayRouting" | "extraBody" | "toolStrictMode" | "toolSchemaFlavor" | "streamIdleTimeoutMs" | "cacheControlFormat" | "thinkingKeep" | "strictResponsesPairing" | "supportsImageDetailOriginal" | "requiresJuiceZeroHack" | "enableGeminiThinkingLoopGuard" | "whenThinking">> & {
468
+ export type ResolvedOpenAICompat = ResolvedOpenAISharedCompat & Required<Omit<OpenAICompat, "supportsDeveloperRole" | "supportsReasoningEffort" | "reasoningEffortMap" | "supportsReasoningParams" | "thinkingFormat" | "reasoningDisableMode" | "omitReasoningEffort" | "includeEncryptedReasoning" | "filterReasoningHistory" | "disableReasoningOnForcedToolChoice" | "disableReasoningOnToolChoice" | "supportsToolChoice" | "supportsForcedToolChoice" | "supportsNamedToolChoice" | "reasoningContentField" | "requiresReasoningContentForToolCalls" | "requiresReasoningContentForAllAssistantTurns" | "allowsSyntheticReasoningContentForToolCalls" | "replayReasoningContent" | "qwenPreserveThinking" | "requiresThinkingAsText" | "requiresMistralToolIds" | "requiresToolResultName" | "requiresAssistantAfterToolResult" | "requiresAssistantContentForToolCalls" | "stripDeepseekSpecialTokens" | "streamMarkupHealingPattern" | "reasoningDeltasMayBeCumulative" | "emptyLengthFinishIsContextError" | "usesOpenAIToolCallIdLimit" | "promptCacheSessionHeader" | "openRouterRouting" | "isOpenRouterHost" | "supportsStrictMode" | "supportsLongPromptCacheRetention" | "alwaysSendMaxTokens" | "wireModelIdMode" | "vercelGatewayRouting" | "extraBody" | "toolStrictMode" | "toolSchemaFlavor" | "streamIdleTimeoutMs" | "cacheControlFormat" | "thinkingKeep" | "strictResponsesPairing" | "supportsImageDetailOriginal" | "requiresJuiceZeroHack" | "enableGeminiThinkingLoopGuard" | "whenThinking">> & {
460
469
  vercelGatewayRouting?: OpenAICompat["vercelGatewayRouting"];
461
470
  extraBody?: OpenAICompat["extraBody"];
462
471
  cacheControlFormat?: OpenAICompat["cacheControlFormat"];
@@ -517,6 +526,17 @@ export type ResolvedDevinCompat = Required<DevinCompat>;
517
526
  export type CompatConfigOf<TApi extends Api> = TApi extends "openai-completions" | "openrouter" | "openai-responses" | "azure-openai-responses" | "openai-codex-responses" ? OpenAICompat : TApi extends "anthropic-messages" ? AnthropicCompat : TApi extends "devin-agent" ? DevinCompat : undefined;
518
527
  /** Resolved compat for a given API: complete record, materialized once by `buildModel`. */
519
528
  export type CompatOf<TApi extends Api> = TApi extends "openrouter" ? ResolvedOpenRouterCompat : TApi extends "openai-completions" ? ResolvedOpenAICompat : TApi extends "openai-responses" | "azure-openai-responses" | "openai-codex-responses" ? ResolvedOpenAIResponsesCompat : TApi extends "anthropic-messages" ? ResolvedAnthropicCompat : TApi extends "devin-agent" ? ResolvedDevinCompat : undefined;
529
+ /** Provider-native compaction endpoint configuration for one model. */
530
+ export interface RemoteCompactionConfig<TApi extends Api = Api> {
531
+ /** Enables provider-native compaction for providers not enabled by built-in policy. */
532
+ enabled?: boolean;
533
+ /** Adapter family used by the configured compaction endpoint. */
534
+ api?: TApi;
535
+ /** Absolute compact endpoint URL; when omitted, the adapter derives it from the model base URL. */
536
+ endpoint?: string;
537
+ /** Model id sent to the compaction endpoint when it differs from the active model id. */
538
+ model?: string;
539
+ }
520
540
  export interface Model<TApi extends Api = Api> {
521
541
  id: string;
522
542
  /**
@@ -546,6 +566,8 @@ export interface Model<TApi extends Api = Api> {
546
566
  * reports that native tool calling is unsupported.
547
567
  */
548
568
  supportsTools?: boolean;
569
+ /** GitLab Duo Workflow root namespace selected during catalog discovery. */
570
+ gitlabDuoWorkflowRootNamespaceId?: string;
549
571
  cost: {
550
572
  input: number;
551
573
  output: number;
@@ -588,6 +610,10 @@ export interface Model<TApi extends Api = Api> {
588
610
  preferWebsockets?: boolean;
589
611
  /** Preferred model to switch to when context promotion is triggered (model id or provider/id). */
590
612
  contextPromotionTarget?: string;
613
+ /** Preferred model to use only for compaction (model id or provider/id); the active session model is unchanged. */
614
+ compactionModel?: string;
615
+ /** Provider-native compaction endpoint configuration. */
616
+ remoteCompaction?: RemoteCompactionConfig<TApi>;
591
617
  /** Provider-assigned priority value (lower = higher priority). */
592
618
  priority?: number;
593
619
  /** Canonical thinking capability metadata for this model. */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-catalog",
4
- "version": "16.1.23",
4
+ "version": "16.2.1",
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": "Can Boluk",
@@ -30,16 +30,16 @@
30
30
  "test": "bun test --parallel",
31
31
  "fix": "biome check --write --unsafe .",
32
32
  "fmt": "biome format --write .",
33
- "generate-models": "bun scripts/generate-models.ts"
33
+ "gen:models": "bun scripts/generate-models.ts"
34
34
  },
35
35
  "dependencies": {
36
36
  "@bufbuild/protobuf": "^2.12.0",
37
- "@oh-my-pi/pi-utils": "16.1.23",
37
+ "@oh-my-pi/pi-utils": "16.2.1",
38
38
  "arktype": "^2.2.0",
39
39
  "zod": "^4"
40
40
  },
41
41
  "devDependencies": {
42
- "@oh-my-pi/pi-ai": "16.1.23",
42
+ "@oh-my-pi/pi-ai": "16.2.1",
43
43
  "@types/bun": "^1.3.14"
44
44
  },
45
45
  "engines": {
@@ -403,6 +403,7 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv
403
403
  disableReasoningOnToolChoice: isDeepseekFamily && Boolean(spec.reasoning) && !isOpenRouter,
404
404
  supportsToolChoice: !isDirectDeepseekReasoning,
405
405
  supportsForcedToolChoice: true,
406
+ supportsNamedToolChoice: provider !== "llama.cpp",
406
407
  maxTokensField: useMaxTokens ? "max_tokens" : "max_completion_tokens",
407
408
  requiresToolResultName: isMistral,
408
409
  requiresAssistantAfterToolResult: isMistral,
@@ -593,6 +594,7 @@ export function buildOpenAIResponsesCompat(spec: OpenAIResponsesSpecLike): Resol
593
594
  disableReasoningOnToolChoice: isDeepseekFamily && reasoningCapable && !isOpenRouter,
594
595
  supportsToolChoice: true,
595
596
  supportsForcedToolChoice: true,
597
+ supportsNamedToolChoice: true,
596
598
  reasoningContentField: "reasoning_content",
597
599
  requiresReasoningContentForToolCalls:
598
600
  (isKimiModel || (isDeepseekFamily && reasoningCapable) || (isOpenRouter && reasoningCapable)) &&