@oh-my-pi/pi-ai 15.11.6 → 15.11.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [15.11.7] - 2026-06-12
6
+
7
+ ### Added
8
+
9
+ - Added `requestModelId` and `thinking.suppress` options to `google-gemini-cli` so collapsed effort-tier variants serialize their per-effort upstream wire id, and thinking-off requests on models with `thinking.suppressWhenOff` send an explicit `thinkingConfig` (`includeThoughts: false` with `thinkingLevel: "MINIMAL"` or `thinkingBudget: 0`) — Cloud Code Assist re-applies the per-id baked server default when the config is omitted, silently thinking and billing the tokens
10
+ - Added mandatory-reasoning clamping: models baked with `thinking.requiresEffort` floor omitted or disabled reasoning to the lowest supported effort in every api mapping, and `disableReasoning` no longer emits OpenRouter `reasoning: { enabled: false }` for them — fixes `omp bench` and utility requests 400ing with "Reasoning is mandatory for this endpoint and cannot be disabled" on OpenRouter Gemini 3.x
11
+
12
+ ### Changed
13
+
14
+ - Changed `google-gemini-cli` request mapping to route per-request wire ids via `resolveWireModelId`: the session effort picks the backing variant id (collapsed `gemini-3.5-flash` at high → `gemini-3.5-flash-low`; claude pairs route off → bare id, efforts → `-thinking`) while `AssistantMessage.model` and usage attribution stay on the logical id. A thinking budget clamped to zero now falls through to the thinking-off path (off routing plus suppression) instead of only disabling thinking
15
+ - Changed `openai-completions` and `anthropic-messages` to serialize per-request wire ids via `resolveWireModelId`, so collapsed `X`/`X-thinking` pairs on aggregators and custom providers switch to the thinking SKU when reasoning is enabled (previously only `google-gemini-cli` routed effort-tier variants)
16
+
17
+ ### Fixed
18
+
19
+ - Fixed `google-gemini-cli` ignoring `Model.requestModelId` when serializing the request model id
20
+
5
21
  ## [15.11.5] - 2026-06-12
6
22
 
7
23
  ### Added
@@ -83,6 +83,11 @@ export interface AnthropicOptions extends StreamOptions {
83
83
  * Ignored for adaptive-capable models.
84
84
  */
85
85
  thinkingBudgetTokens?: number;
86
+ /**
87
+ * Upstream wire model id override for collapsed effort-tier variants.
88
+ * Serialized as `requestModelId ?? model.requestModelId ?? model.id`.
89
+ */
90
+ requestModelId?: string;
86
91
  /**
87
92
  * Effort level for adaptive thinking.
88
93
  * Controls how much thinking Claude allocates:
@@ -34,7 +34,23 @@ export interface GoogleGeminiCliOptions extends StreamOptions {
34
34
  budgetTokens?: number;
35
35
  /** Thinking level. Use for Gemini 3 models (LOW/HIGH for Pro, MINIMAL/LOW/MEDIUM/HIGH for Flash). */
36
36
  level?: GoogleThinkingLevel;
37
+ /**
38
+ * Explicit wire suppression when `enabled` is false. Cloud Code Assist
39
+ * re-applies the per-id baked server default when thinkingConfig is
40
+ * omitted, so models with `thinking.suppressWhenOff` must send
41
+ * `includeThoughts: false` plus a MINIMAL level (or zero budget).
42
+ */
43
+ suppress?: {
44
+ level: GoogleThinkingLevel;
45
+ } | {
46
+ budget: number;
47
+ };
37
48
  };
49
+ /**
50
+ * Upstream wire model id override for collapsed effort-tier variants.
51
+ * Serialized as `requestModelId ?? model.requestModelId ?? model.id`.
52
+ */
53
+ requestModelId?: string;
38
54
  projectId?: string;
39
55
  }
40
56
  export { ANTIGRAVITY_SYSTEM_INSTRUCTION, getAntigravityUserAgent, getGeminiCliHeaders, getGeminiCliUserAgent, } from "@oh-my-pi/pi-catalog/wire/gemini-headers";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-ai",
4
- "version": "15.11.6",
4
+ "version": "15.11.7",
5
5
  "description": "Unified LLM API with automatic model discovery and provider configuration",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -38,8 +38,8 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@bufbuild/protobuf": "^2.12.0",
41
- "@oh-my-pi/pi-catalog": "15.11.6",
42
- "@oh-my-pi/pi-utils": "15.11.6",
41
+ "@oh-my-pi/pi-catalog": "15.11.7",
42
+ "@oh-my-pi/pi-utils": "15.11.7",
43
43
  "openai": "^6.39.0",
44
44
  "partial-json": "^0.1.7",
45
45
  "zod": "4.4.3"
@@ -973,6 +973,11 @@ export interface AnthropicOptions extends StreamOptions {
973
973
  * Ignored for adaptive-capable models.
974
974
  */
975
975
  thinkingBudgetTokens?: number;
976
+ /**
977
+ * Upstream wire model id override for collapsed effort-tier variants.
978
+ * Serialized as `requestModelId ?? model.requestModelId ?? model.id`.
979
+ */
980
+ requestModelId?: string;
976
981
  /**
977
982
  * Effort level for adaptive thinking.
978
983
  * Controls how much thinking Claude allocates:
@@ -2816,7 +2821,7 @@ function buildParams(
2816
2821
  // Build params in the canonical field order: model → messages → system → tools →
2817
2822
  // metadata → max_tokens → thinking → context_management → output_config → stream.
2818
2823
  const params: MessageCreateParamsStreaming = {
2819
- model: model.requestModelId ?? model.id,
2824
+ model: options?.requestModelId ?? model.requestModelId ?? model.id,
2820
2825
  messages: convertAnthropicMessages(context.messages, model, isOAuthToken),
2821
2826
  ...(systemBlocks && { system: systemBlocks }),
2822
2827
  ...(tools !== undefined && { tools }),
@@ -76,7 +76,19 @@ export interface GoogleGeminiCliOptions extends StreamOptions {
76
76
  budgetTokens?: number;
77
77
  /** Thinking level. Use for Gemini 3 models (LOW/HIGH for Pro, MINIMAL/LOW/MEDIUM/HIGH for Flash). */
78
78
  level?: GoogleThinkingLevel;
79
+ /**
80
+ * Explicit wire suppression when `enabled` is false. Cloud Code Assist
81
+ * re-applies the per-id baked server default when thinkingConfig is
82
+ * omitted, so models with `thinking.suppressWhenOff` must send
83
+ * `includeThoughts: false` plus a MINIMAL level (or zero budget).
84
+ */
85
+ suppress?: { level: GoogleThinkingLevel } | { budget: number };
79
86
  };
87
+ /**
88
+ * Upstream wire model id override for collapsed effort-tier variants.
89
+ * Serialized as `requestModelId ?? model.requestModelId ?? model.id`.
90
+ */
91
+ requestModelId?: string;
80
92
  projectId?: string;
81
93
  }
82
94
 
@@ -770,6 +782,17 @@ export function buildRequest(
770
782
  } else if (options.thinking.budgetTokens !== undefined) {
771
783
  generationConfig.thinkingConfig.thinkingBudget = options.thinking.budgetTokens;
772
784
  }
785
+ } else if (options.thinking?.suppress && model.reasoning) {
786
+ // Explicit off: omitting thinkingConfig re-applies the per-id baked
787
+ // server default (the model silently thinks and bills the tokens).
788
+ const suppress = options.thinking.suppress;
789
+ generationConfig.thinkingConfig = { includeThoughts: false };
790
+ if ("level" in suppress) {
791
+ // Cast to any since our GoogleThinkingLevel mirrors Google's ThinkingLevel enum values
792
+ generationConfig.thinkingConfig.thinkingLevel = suppress.level as any;
793
+ } else {
794
+ generationConfig.thinkingConfig.thinkingBudget = suppress.budget;
795
+ }
773
796
  }
774
797
 
775
798
  const request: CloudCodeAssistRequest["request"] = {
@@ -840,7 +863,7 @@ export function buildRequest(
840
863
 
841
864
  return {
842
865
  project: projectId,
843
- model: model.id,
866
+ model: options.requestModelId ?? model.requestModelId ?? model.id,
844
867
  request,
845
868
  ...(isAntigravity
846
869
  ? {
@@ -1,7 +1,7 @@
1
1
  import type { Effort } from "@oh-my-pi/pi-catalog/effort";
2
2
  import { toFirepassWireModelId, toFireworksWireModelId } from "@oh-my-pi/pi-catalog/fireworks-model-id";
3
3
  import { isDeepseekModelIdOrName } from "@oh-my-pi/pi-catalog/identity";
4
- import { getSupportedEfforts } from "@oh-my-pi/pi-catalog/model-thinking";
4
+ import { getSupportedEfforts, resolveWireModelId } from "@oh-my-pi/pi-catalog/model-thinking";
5
5
  import { calculateCost } from "@oh-my-pi/pi-catalog/models";
6
6
  import type { ResolvedOpenAICompat } from "@oh-my-pi/pi-catalog/types";
7
7
  import { parseGitHubCopilotApiKey } from "@oh-my-pi/pi-catalog/wire/github-copilot";
@@ -113,12 +113,16 @@ function resolveOpenAICompletionsModelId(
113
113
  model: Model<"openai-completions">,
114
114
  options: OpenAICompletionsOptions | undefined,
115
115
  ): string {
116
- // Catalog variants (e.g. Copilot long-context `-1m` entries) pin the wire id.
117
- if (model.requestModelId) return model.requestModelId;
118
- if (model.provider === "firepass") return toFirepassWireModelId(model.id);
119
- if (model.provider === "fireworks") return toFireworksWireModelId(model.id);
120
- if (model.provider === "openrouter") return applyOpenRouterRoutingVariant(model.id, options?.openrouterVariant);
121
- return model.id;
116
+ // Effort-tier variants route per request effort (off bare id, efforts →
117
+ // the thinking backing id); catalog variants (Copilot long-context `-1m`
118
+ // entries) pin via `requestModelId`; everything else serializes `model.id`.
119
+ const effort =
120
+ options?.reasoning && !options.disableReasoning && model.reasoning ? (options.reasoning as Effort) : undefined;
121
+ const wireId = resolveWireModelId(model, effort);
122
+ if (model.provider === "firepass") return toFirepassWireModelId(wireId);
123
+ if (model.provider === "fireworks") return toFireworksWireModelId(wireId);
124
+ if (model.provider === "openrouter") return applyOpenRouterRoutingVariant(wireId, options?.openrouterVariant);
125
+ return wireId;
122
126
  }
123
127
 
124
128
  /**
package/src/stream.ts CHANGED
@@ -3,7 +3,9 @@ import { isVertexExpressOpenAIUrl, isVertexRawPredictUrl } from "@oh-my-pi/pi-ca
3
3
  import {
4
4
  mapEffortToAnthropicAdaptiveEffort,
5
5
  mapEffortToGoogleThinkingLevel,
6
+ minimumSupportedEffort,
6
7
  requireSupportedEffort,
8
+ resolveWireModelId,
7
9
  } from "@oh-my-pi/pi-catalog/model-thinking";
8
10
  import { CATALOG_PROVIDERS, type ProviderCatalogEntry } from "@oh-my-pi/pi-catalog/provider-models";
9
11
  import { $env, $pickenv, extractHttpStatusFromError } from "@oh-my-pi/pi-utils";
@@ -667,11 +669,39 @@ function resolveOpenAiReasoningEffort<TApi extends Api>(
667
669
 
668
670
  const castApi = <TApi extends Api>(api: OptionsForApi<TApi>): OptionsForApi<Api> => api as OptionsForApi<Api>;
669
671
 
670
- function mapOptionsForApi<TApi extends Api>(
672
+ /**
673
+ * Mandatory-reasoning endpoints (`thinking.requiresEffort`) reject disabled
674
+ * or omitted thinking ("Reasoning is mandatory for this endpoint and cannot
675
+ * be disabled") — clamp to the lowest supported effort instead.
676
+ * `suppressWhenOff` models handle off provider-side via explicit wire
677
+ * suppression. Collapsed pairs interplay: pair derivation strips member
678
+ * flags (off routes to a bare SKU that CAN disable), while identity backfill
679
+ * re-flags pairs whose logical id is itself mandatory (Gemini 3.x) — there
680
+ * the clamp wins and the floored effort routes to the thinking SKU.
681
+ */
682
+ function normalizeMandatoryReasoningOptions<TApi extends Api>(
671
683
  model: Model<TApi>,
672
684
  options?: SimpleStreamOptions,
685
+ ): SimpleStreamOptions | undefined {
686
+ if (
687
+ !model.reasoning ||
688
+ !model.thinking?.requiresEffort ||
689
+ model.thinking.suppressWhenOff ||
690
+ (options?.reasoning !== undefined && !options.disableReasoning)
691
+ ) {
692
+ return options;
693
+ }
694
+ const floor = minimumSupportedEffort(model);
695
+ if (floor === undefined) return options;
696
+ return { ...options, reasoning: floor, disableReasoning: undefined };
697
+ }
698
+
699
+ function mapOptionsForApi<TApi extends Api>(
700
+ model: Model<TApi>,
701
+ rawOptions?: SimpleStreamOptions,
673
702
  apiKey?: string,
674
703
  ): OptionsForApi<TApi> {
704
+ const options = normalizeMandatoryReasoningOptions(model, rawOptions);
675
705
  const base = {
676
706
  temperature: options?.temperature,
677
707
  topP: options?.topP,
@@ -707,6 +737,7 @@ function mapOptionsForApi<TApi extends Api>(
707
737
  if (!reasoning || !model.reasoning) {
708
738
  return castApi<"anthropic-messages">({
709
739
  ...base,
740
+ requestModelId: resolveWireModelId(model, undefined),
710
741
  thinkingEnabled: false,
711
742
  toolChoice: mapAnthropicToolChoice(options?.toolChoice),
712
743
  thinkingDisplay: options?.hideThinkingSummary ? "omitted" : undefined,
@@ -718,6 +749,7 @@ function mapOptionsForApi<TApi extends Api>(
718
749
  if (thinkingBudget <= 0) {
719
750
  return castApi<"anthropic-messages">({
720
751
  ...base,
752
+ requestModelId: resolveWireModelId(model, undefined),
721
753
  thinkingEnabled: false,
722
754
  toolChoice: mapAnthropicToolChoice(options?.toolChoice),
723
755
  thinkingDisplay: options?.hideThinkingSummary ? "omitted" : undefined,
@@ -731,6 +763,7 @@ function mapOptionsForApi<TApi extends Api>(
731
763
  const effort = mapEffortToAnthropicAdaptiveEffort(model, reasoning);
732
764
  return castApi<"anthropic-messages">({
733
765
  ...base,
766
+ requestModelId: resolveWireModelId(model, reasoning),
734
767
  thinkingEnabled: true,
735
768
  effort,
736
769
  toolChoice: mapAnthropicToolChoice(options?.toolChoice),
@@ -742,6 +775,7 @@ function mapOptionsForApi<TApi extends Api>(
742
775
  if (ANTHROPIC_USE_INTERLEAVED_THINKING) {
743
776
  return castApi<"anthropic-messages">({
744
777
  ...base,
778
+ requestModelId: resolveWireModelId(model, reasoning),
745
779
  thinkingEnabled: true,
746
780
  thinkingBudgetTokens: thinkingBudget,
747
781
  toolChoice: mapAnthropicToolChoice(options?.toolChoice),
@@ -762,6 +796,7 @@ function mapOptionsForApi<TApi extends Api>(
762
796
  if (thinkingBudget <= 0) {
763
797
  return castApi<"anthropic-messages">({
764
798
  ...base,
799
+ requestModelId: resolveWireModelId(model, undefined),
765
800
  thinkingEnabled: false,
766
801
  toolChoice: mapAnthropicToolChoice(options?.toolChoice),
767
802
  thinkingDisplay: options?.hideThinkingSummary ? "omitted" : undefined,
@@ -771,6 +806,7 @@ function mapOptionsForApi<TApi extends Api>(
771
806
  return castApi<"anthropic-messages">({
772
807
  ...base,
773
808
  maxTokens,
809
+ requestModelId: resolveWireModelId(model, reasoning),
774
810
  thinkingEnabled: true,
775
811
  thinkingBudgetTokens: thinkingBudget,
776
812
  toolChoice: mapAnthropicToolChoice(options?.toolChoice),
@@ -887,53 +923,57 @@ function mapOptionsForApi<TApi extends Api>(
887
923
 
888
924
  case "google-gemini-cli": {
889
925
  const reasoning = options?.reasoning;
890
- if (!reasoning || !model.reasoning) {
891
- return castApi<"google-gemini-cli">({
892
- ...base,
893
- thinking: { enabled: false },
894
- toolChoice: mapGoogleToolChoice(options?.toolChoice),
895
- });
896
- }
897
-
898
- const effort = requireSupportedEffort(model, reasoning);
926
+ const toolChoice = mapGoogleToolChoice(options?.toolChoice);
927
+ if (reasoning && model.reasoning) {
928
+ const effort = requireSupportedEffort(model, reasoning);
929
+
930
+ // Gemini 3+ models use thinkingLevel instead of thinkingBudget
931
+ if (model.thinking?.mode === "google-level") {
932
+ return castApi<"google-gemini-cli">({
933
+ ...base,
934
+ requestModelId: resolveWireModelId(model, effort),
935
+ thinking: {
936
+ enabled: true,
937
+ level: mapEffortToGoogleThinkingLevel(effort),
938
+ },
939
+ toolChoice,
940
+ });
941
+ }
899
942
 
900
- // Gemini 3+ models use thinkingLevel instead of thinkingBudget
901
- if (model.thinking?.mode === "google-level") {
902
- return castApi<"google-gemini-cli">({
903
- ...base,
904
- thinking: {
905
- enabled: true,
906
- level: mapEffortToGoogleThinkingLevel(effort),
907
- },
908
- toolChoice: mapGoogleToolChoice(options?.toolChoice),
909
- });
910
- }
943
+ let thinkingBudget = options.thinkingBudgets?.[effort] ?? GOOGLE_THINKING[effort];
911
944
 
912
- let thinkingBudget = options.thinkingBudgets?.[effort] ?? GOOGLE_THINKING[effort];
945
+ // Caller's maxTokens is the desired output; add thinking budget on top, capped at model limit
946
+ const maxTokens = Math.min((base.maxTokens || 0) + thinkingBudget, model.maxTokens);
913
947
 
914
- // Caller's maxTokens is the desired output; add thinking budget on top, capped at model limit
915
- const maxTokens = Math.min((base.maxTokens || 0) + thinkingBudget, model.maxTokens);
948
+ // If not enough room for thinking + output, reduce thinking budget
949
+ if (maxTokens <= thinkingBudget) {
950
+ thinkingBudget = Math.max(0, maxTokens - MIN_OUTPUT_TOKENS);
951
+ }
916
952
 
917
- // If not enough room for thinking + output, reduce thinking budget
918
- if (maxTokens <= thinkingBudget) {
919
- thinkingBudget = Math.max(0, maxTokens - MIN_OUTPUT_TOKENS) ?? 0;
953
+ if (thinkingBudget > 0) {
954
+ return castApi<"google-gemini-cli">({
955
+ ...base,
956
+ maxTokens,
957
+ requestModelId: resolveWireModelId(model, effort),
958
+ thinking: { enabled: true, budgetTokens: thinkingBudget },
959
+ toolChoice,
960
+ });
961
+ }
962
+ // Budget clamped to zero — fall through to the thinking-off path.
920
963
  }
921
964
 
922
- // If thinking budget is too low, disable thinking
923
- if (thinkingBudget <= 0) {
924
- return castApi<"google-gemini-cli">({
925
- ...base,
926
- thinking: { enabled: false },
927
- toolChoice: mapGoogleToolChoice(options?.toolChoice),
928
- });
929
- } else {
930
- return castApi<"google-gemini-cli">({
931
- ...base,
932
- maxTokens,
933
- thinking: { enabled: true, budgetTokens: thinkingBudget },
934
- toolChoice: mapGoogleToolChoice(options?.toolChoice),
935
- });
965
+ const thinking: GoogleGeminiCliOptions["thinking"] = { enabled: false };
966
+ if (model.reasoning && model.thinking?.suppressWhenOff) {
967
+ // CCA re-applies the per-id baked server default when the config
968
+ // is omitted; suppression must be explicit on the wire.
969
+ thinking.suppress = model.thinking.mode === "google-level" ? { level: "MINIMAL" } : { budget: 0 };
936
970
  }
971
+ return castApi<"google-gemini-cli">({
972
+ ...base,
973
+ requestModelId: resolveWireModelId(model, undefined),
974
+ thinking,
975
+ toolChoice,
976
+ });
937
977
  }
938
978
 
939
979
  case "google-vertex": {