@oh-my-pi/pi-ai 17.2.14 → 17.2.15

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,13 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [17.2.15] - 2026-08-12
6
+
7
+ ### Fixed
8
+
9
+ - Fixed an issue where AWS_BEDROCK_SKIP_AUTH failed to expose Amazon Bedrock models when AWS credential files were unavailable.
10
+ - Fixed an issue where forceReasoningOff was ignored by Anthropic and Google transports, which allowed native thinking alongside a caller-supplied external scratchpad.
11
+
5
12
  ## [17.2.14] - 2026-08-11
6
13
 
7
14
  ### Added
@@ -1,8 +1,7 @@
1
- import { resolveAwsRegistryApiKey } from "./aws.js";
2
1
  export declare const amazonBedrockProvider: {
3
2
  readonly id: "amazon-bedrock";
4
3
  readonly name: "Amazon Bedrock";
5
- readonly envKeys: typeof resolveAwsRegistryApiKey;
4
+ readonly envKeys: () => string | undefined;
6
5
  readonly mapSimpleOptions: (options: import("../index.js").SimpleStreamOptions) => {
7
6
  region: string | undefined;
8
7
  profile: string | undefined;
@@ -8,6 +8,8 @@ export interface AwsBedrockProviderOptions extends Readonly<Record<string, unkno
8
8
  }
9
9
  export declare function hasAwsCredentialSource(): boolean;
10
10
  /** Registry key marker for AWS transports that resolve their own bearer/IAM credentials. */
11
- export declare function resolveAwsRegistryApiKey(): string | undefined;
11
+ export declare function resolveAwsRegistryApiKey(options?: {
12
+ allowSkipAuth?: boolean;
13
+ }): string | undefined;
12
14
  /** Resolve a real AWS bearer token while filtering the registry's auth marker. */
13
15
  export declare function resolveAwsBearerToken(apiKey?: string, bearerToken?: string): string | undefined;
@@ -25,7 +25,7 @@ declare const ALL: ({
25
25
  } | {
26
26
  readonly id: "amazon-bedrock";
27
27
  readonly name: "Amazon Bedrock";
28
- readonly envKeys: typeof import("./aws.js").resolveAwsRegistryApiKey;
28
+ readonly envKeys: () => string | undefined;
29
29
  readonly mapSimpleOptions: (options: import("../index.js").SimpleStreamOptions) => {
30
30
  region: string | undefined;
31
31
  profile: string | undefined;
@@ -295,8 +295,9 @@ export interface StreamOptions {
295
295
  */
296
296
  statefulResponses?: boolean;
297
297
  /**
298
- * Emit `reasoning: { effort: "none" }` for OpenAI Responses and Codex requests.
299
- * Used when a caller supplies an external reasoning scratchpad; other transports ignore it.
298
+ * Disable native reasoning when the caller supplies an external scratchpad.
299
+ * OpenAI Responses emits `reasoning: { effort: "none" }`; Anthropic and
300
+ * Google transports use their native thinking-off controls.
300
301
  */
301
302
  forceReasoningOff?: boolean;
302
303
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-ai",
4
- "version": "17.2.14",
4
+ "version": "17.2.15",
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,10 +38,10 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@bufbuild/protobuf": "^2.12.1",
41
- "@oh-my-pi/omptype": "17.2.14",
42
- "@oh-my-pi/pi-catalog": "17.2.14",
43
- "@oh-my-pi/pi-utils": "17.2.14",
44
- "@oh-my-pi/pi-wire": "17.2.14"
41
+ "@oh-my-pi/omptype": "17.2.15",
42
+ "@oh-my-pi/pi-catalog": "17.2.15",
43
+ "@oh-my-pi/pi-utils": "17.2.15",
44
+ "@oh-my-pi/pi-wire": "17.2.15"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@bufbuild/protoc-gen-es": "^2.12.1",
@@ -0,0 +1 @@
1
+ TOOL-ONLY TURN. This turn accepts a tool call and nothing else; a text reply here is discarded unread and you will be re-prompted. Emit the tool call now.
@@ -36,6 +36,7 @@ import { armPreResponseTimeout, getStreamFirstEventTimeoutMs, iterateWithIdleTim
36
36
  // the stream provider trusts the access token threaded through `options.apiKey`.
37
37
  import { normalizeSchemaForCCA } from "../utils/schema";
38
38
  import { StreamMarkupHealing, type StreamMarkupHealingEvent } from "../utils/stream-markup-healing";
39
+ import forcedToolDirective from "./google-antigravity-forced-tool.md" with { type: "text" };
39
40
  import type { Content, FunctionCallingConfigMode, ThinkingConfig } from "./google-shared";
40
41
  import {
41
42
  convertMessages,
@@ -1347,6 +1348,13 @@ export function buildRequest(
1347
1348
  },
1348
1349
  };
1349
1350
  }
1351
+ // Cloud Code Assist drops `toolConfig` on Antigravity's Gemini routes:
1352
+ // the backend answers in text under `mode: "ANY"` and still emits calls
1353
+ // under `"NONE"`. Claude routes implement it, so only Gemini needs the
1354
+ // forced choice restated in the transcript.
1355
+ if (isAntigravity && !isClaudeModel(model.id) && request.toolConfig?.functionCallingConfig.mode === "ANY") {
1356
+ contents.push({ role: "user", parts: [{ text: forcedToolDirective }] });
1357
+ }
1350
1358
  }
1351
1359
  // Antigravity's default tool mode is VALIDATED (verified for Gemini and
1352
1360
  // Claude); an explicit non-auto tool choice above wins.
@@ -858,13 +858,18 @@ export function buildGoogleGenerateContentParams<T extends "google-generative-ai
858
858
  config.toolConfig = undefined;
859
859
  }
860
860
 
861
- if (options.thinking?.enabled && model.reasoning) {
862
- const cfg: ThinkingConfig = { includeThoughts: !options.hideThinkingSummary };
863
- if (options.thinking.level !== undefined) {
864
- // GoogleThinkingLevel mirrors the SDK's `ThinkingLevel` string enum values 1:1.
865
- cfg.thinkingLevel = options.thinking.level as ThinkingLevel;
866
- } else if (options.thinking.budgetTokens !== undefined) {
867
- cfg.thinkingBudget = options.thinking.budgetTokens;
861
+ const thinking = options.thinking;
862
+ if (
863
+ thinking &&
864
+ model.reasoning &&
865
+ (thinking.enabled || thinking.level !== undefined || thinking.budgetTokens !== undefined)
866
+ ) {
867
+ const cfg: ThinkingConfig = { includeThoughts: thinking.enabled && !options.hideThinkingSummary };
868
+ if (thinking.level !== undefined) {
869
+ // GoogleThinkingLevel mirrors the SDK's ThinkingLevel string enum values 1:1.
870
+ cfg.thinkingLevel = thinking.level as ThinkingLevel;
871
+ } else if (thinking.budgetTokens !== undefined) {
872
+ cfg.thinkingBudget = thinking.budgetTokens;
868
873
  }
869
874
  config.thinkingConfig = cfg;
870
875
  }
@@ -5,7 +5,7 @@ export const amazonBedrockProvider = {
5
5
  id: "amazon-bedrock",
6
6
  name: "Amazon Bedrock",
7
7
  // Amazon Bedrock accepts bearer tokens, IAM keys, profiles, ECS/IRSA credential chains.
8
- envKeys: resolveAwsRegistryApiKey,
8
+ envKeys: () => resolveAwsRegistryApiKey({ allowSkipAuth: true }),
9
9
  mapSimpleOptions: options => {
10
10
  const awsOptions = options.providerOptions as AwsBedrockProviderOptions | undefined;
11
11
  return {
@@ -1,5 +1,5 @@
1
1
  import * as fs from "node:fs";
2
- import { $env } from "@oh-my-pi/pi-utils";
2
+ import { $env, $flag } from "@oh-my-pi/pi-utils";
3
3
  import { hasConfiguredAwsProfile } from "../utils/aws-profile";
4
4
  import { AUTHENTICATED_SENTINEL } from "./types";
5
5
 
@@ -53,7 +53,8 @@ export function hasAwsCredentialSource(): boolean {
53
53
  }
54
54
 
55
55
  /** Registry key marker for AWS transports that resolve their own bearer/IAM credentials. */
56
- export function resolveAwsRegistryApiKey(): string | undefined {
56
+ export function resolveAwsRegistryApiKey(options?: { allowSkipAuth?: boolean }): string | undefined {
57
+ if (options?.allowSkipAuth && $flag("AWS_BEDROCK_SKIP_AUTH")) return AUTHENTICATED_SENTINEL;
57
58
  return hasAwsCredentialSource() ? AUTHENTICATED_SENTINEL : undefined;
58
59
  }
59
60
 
package/src/stream.ts CHANGED
@@ -1408,6 +1408,17 @@ function resolveOpenAiReasoningEffort<TApi extends Api>(
1408
1408
  return requireSupportedEffort(model, reasoning);
1409
1409
  }
1410
1410
 
1411
+ function resolveGoogleThinkingOff<TApi extends Api>(model: Model<TApi>): NonNullable<GoogleOptions["thinking"]> {
1412
+ const thinking: NonNullable<GoogleOptions["thinking"]> = { enabled: false };
1413
+ if (!model.reasoning || !model.thinking) return thinking;
1414
+ if (model.thinking.mode === "budget" && (!model.thinking.requiresEffort || model.thinking.suppressWhenOff)) {
1415
+ thinking.budgetTokens = 0;
1416
+ } else if (model.thinking.mode === "google-level" && model.thinking.suppressWhenOff) {
1417
+ thinking.level = "MINIMAL";
1418
+ }
1419
+ return thinking;
1420
+ }
1421
+
1411
1422
  const castApi = <TApi extends Api>(api: OptionsForApi<TApi>): OptionsForApi<Api> => api as OptionsForApi<Api>;
1412
1423
 
1413
1424
  /**
@@ -1428,13 +1439,13 @@ function normalizeMandatoryReasoningOptions<TApi extends Api>(
1428
1439
  !model.reasoning ||
1429
1440
  !model.thinking?.requiresEffort ||
1430
1441
  model.thinking.suppressWhenOff ||
1431
- (options?.reasoning !== undefined && !options.disableReasoning)
1442
+ (options?.reasoning !== undefined && !options.disableReasoning && !options.forceReasoningOff)
1432
1443
  ) {
1433
1444
  return options;
1434
1445
  }
1435
1446
  const floor = minimumSupportedEffort(model);
1436
1447
  if (floor === undefined) return options;
1437
- return { ...options, reasoning: floor, disableReasoning: undefined };
1448
+ return { ...options, reasoning: floor, disableReasoning: undefined, forceReasoningOff: undefined };
1438
1449
  }
1439
1450
 
1440
1451
  function supportsExplicitOpenAIResponsesPromptCache(compat: unknown): boolean {
@@ -1515,12 +1526,12 @@ function mapOptionsForApi<TApi extends Api>(
1515
1526
  switch (model.api) {
1516
1527
  case "anthropic-messages": {
1517
1528
  // Explicitly disable thinking when reasoning is not specified, the caller
1518
- // disabled it, or the model doesn't support it. `disableReasoning` is a
1519
- // SimpleStreamOptions flag that never reaches AnthropicOptions on its own,
1520
- // so it must be folded into `thinkingEnabled` here (mandatory-reasoning
1521
- // models already clamp it away in normalizeMandatoryReasoningOptions).
1529
+ // disabled it, an external scratchpad replaces it, or the model doesn't
1530
+ // support it. These SimpleStreamOptions flags never reach AnthropicOptions
1531
+ // on their own, so fold them into thinkingEnabled here (mandatory-reasoning
1532
+ // models already clamp them away in normalizeMandatoryReasoningOptions).
1522
1533
  const reasoning = options?.reasoning;
1523
- if (!reasoning || !model.reasoning || options?.disableReasoning) {
1534
+ if (!reasoning || !model.reasoning || options?.disableReasoning || options?.forceReasoningOff) {
1524
1535
  return castApi<"anthropic-messages">({
1525
1536
  ...base,
1526
1537
  requestModelId: resolveWireModelId(model, undefined),
@@ -1725,14 +1736,14 @@ function mapOptionsForApi<TApi extends Api>(
1725
1736
  });
1726
1737
 
1727
1738
  case "google-generative-ai": {
1728
- // Explicitly disable thinking when reasoning is not specified or model doesn't support it
1729
- // This is needed because Gemini has "dynamic thinking" enabled by default
1739
+ // Explicitly disable thinking when reasoning is absent, unsupported, or
1740
+ // replaced by the caller's external scratchpad. Gemini defaults thinking on.
1730
1741
  const reasoning = options?.reasoning;
1731
- if (!reasoning || !model.reasoning) {
1742
+ if (!reasoning || !model.reasoning || options?.disableReasoning || options?.forceReasoningOff) {
1732
1743
  return castApi<"google-generative-ai">({
1733
1744
  ...base,
1734
1745
  serviceTier: options?.serviceTier,
1735
- thinking: { enabled: false },
1746
+ thinking: resolveGoogleThinkingOff(model),
1736
1747
  toolChoice: mapGoogleToolChoice(options?.toolChoice),
1737
1748
  cachedContent: options?.cachedContent,
1738
1749
  });
@@ -1772,7 +1783,7 @@ function mapOptionsForApi<TApi extends Api>(
1772
1783
  case "google-gemini-cli": {
1773
1784
  const reasoning = options?.reasoning;
1774
1785
  const toolChoice = mapGoogleToolChoice(options?.toolChoice);
1775
- if (reasoning && model.reasoning) {
1786
+ if (reasoning && model.reasoning && !options?.disableReasoning && !options?.forceReasoningOff) {
1776
1787
  const effort = requireSupportedEffort(model, reasoning);
1777
1788
 
1778
1789
  // Gemini 3+ models use thinkingLevel instead of thinkingBudget
@@ -1831,13 +1842,14 @@ function mapOptionsForApi<TApi extends Api>(
1831
1842
  }
1832
1843
 
1833
1844
  case "google-vertex": {
1834
- // Explicitly disable thinking when reasoning is not specified or model doesn't support it
1845
+ // Explicitly disable thinking when reasoning is absent, unsupported, or
1846
+ // replaced by the caller's external scratchpad.
1835
1847
  const reasoning = options?.reasoning;
1836
- if (!reasoning || !model.reasoning) {
1848
+ if (!reasoning || !model.reasoning || options?.disableReasoning || options?.forceReasoningOff) {
1837
1849
  return castApi<"google-vertex">({
1838
1850
  ...base,
1839
1851
  serviceTier: options?.serviceTier,
1840
- thinking: { enabled: false },
1852
+ thinking: resolveGoogleThinkingOff(model),
1841
1853
  toolChoice: mapGoogleToolChoice(options?.toolChoice),
1842
1854
  cachedContent: options?.cachedContent,
1843
1855
  });
package/src/types.ts CHANGED
@@ -479,8 +479,9 @@ export interface StreamOptions {
479
479
  */
480
480
  statefulResponses?: boolean;
481
481
  /**
482
- * Emit `reasoning: { effort: "none" }` for OpenAI Responses and Codex requests.
483
- * Used when a caller supplies an external reasoning scratchpad; other transports ignore it.
482
+ * Disable native reasoning when the caller supplies an external scratchpad.
483
+ * OpenAI Responses emits `reasoning: { effort: "none" }`; Anthropic and
484
+ * Google transports use their native thinking-off controls.
484
485
  */
485
486
  forceReasoningOff?: boolean;
486
487
  /**