@gajae-code/ai 0.16.3 → 0.16.4

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 (39) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/types/auth-gateway/server.d.ts +4 -2
  3. package/dist/types/bedrock-claude-cache-policy.d.ts +21 -0
  4. package/dist/types/openai-completions-compat.d.ts +22 -0
  5. package/dist/types/providers/amazon-bedrock.d.ts +17 -2
  6. package/dist/types/providers/anthropic.d.ts +1 -0
  7. package/dist/types/providers/openai-completions-compat.d.ts +1 -1
  8. package/dist/types/providers/opencode-go-session.d.ts +5 -0
  9. package/dist/types/types.d.ts +7 -0
  10. package/dist/types/utils/codex-entitlement.d.ts +10 -0
  11. package/package.json +3 -3
  12. package/src/auth-gateway/server.ts +5 -0
  13. package/src/auth-storage.ts +17 -8
  14. package/src/bedrock-claude-cache-policy.d.ts +21 -0
  15. package/src/bedrock-claude-cache-policy.ts +68 -0
  16. package/src/model-pricing.ts +11 -0
  17. package/src/model-thinking.ts +31 -7
  18. package/src/models.json +37 -0
  19. package/src/openai-completions-compat.d.ts +22 -0
  20. package/src/openai-completions-compat.ts +77 -3
  21. package/src/providers/amazon-bedrock.d.ts +17 -2
  22. package/src/providers/amazon-bedrock.ts +9 -9
  23. package/src/providers/anthropic.d.ts +1 -0
  24. package/src/providers/anthropic.ts +38 -21
  25. package/src/providers/cursor.ts +12 -6
  26. package/src/providers/openai-codex/response-handler.ts +1 -1
  27. package/src/providers/openai-completions-compat.d.ts +1 -1
  28. package/src/providers/openai-completions-compat.ts +8 -1
  29. package/src/providers/openai-completions.ts +14 -1
  30. package/src/providers/openai-responses.ts +11 -1
  31. package/src/providers/opencode-go-session.d.ts +5 -0
  32. package/src/providers/opencode-go-session.ts +57 -0
  33. package/src/stream.ts +1 -0
  34. package/src/types.d.ts +7 -0
  35. package/src/types.ts +7 -0
  36. package/src/utils/codex-entitlement.d.ts +10 -0
  37. package/src/utils/codex-entitlement.ts +21 -0
  38. package/src/utils/discovery/antigravity.ts +2 -1
  39. package/src/utils/discovery/gemini.ts +2 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.16.4] - 2026-09-05
6
+
7
+ ### Added
8
+
9
+ - Bundled `openai-codex/gpt-6-astra` from the authenticated Codex catalog so eligible ChatGPT accounts can select Astra before a live discovery refresh. The reviewed snapshot records Codex's 272K prompt budget, 128K output cap, text-and-image input, websocket preference, low-through-max reasoning efforts, freeform `apply_patch`, and published tiered OpenAI pricing.
10
+
11
+ ### Fixed
12
+
13
+ - OpenAI Codex HTTP failures with an empty response body and empty status text now include the HTTP status in the surfaced message instead of the opaque `Request failed`, while structured provider errors retain their existing messages and classification.
14
+ - Direct xAI reasoning effort now requires the exact production HTTPS `api.x.ai` origin: HTTP, userinfo, query/fragment components, and explicit non-default ports fail closed. Bedrock Claude cache-pricing metadata now shares the request prompt-caching generation policy, retaining only Claude 3.5 Haiku, Claude 3.7 Sonnet, and Claude 4+ support.
15
+
16
+ - OpenCode Go requests across chat completions, Responses, and Anthropic-compatible models now send the provider-required `x-opencode-session` header at their exact canonical Go endpoints using the coding-agent's existing opaque provider session id. The provider-owned identity is typed separately from generic cache affinity, so auth-gateway prompt-derived cache keys cannot become the header. The value remains stable across turns and every retry layer in one conversation, differs with the conversation lifecycle, cannot be injected or replaced by caller/model request headers or transforms on unauthorized routes, and is never added to OpenCode Zen or unrelated compatible endpoints (#5295).
17
+ - Bedrock prompt caching is now gated on the Claude generation parsed from the model id instead of version-literal fragments (`-4-`/`-4.`, `claude-haiku`, and two 3.x literals). A future generation's Bedrock id (`us.anthropic.claude-opus-5-…`, or a new model kind) matched none of the literals, so cache points were silently omitted and every request re-paid full input pricing. Behavior for the documented support set — 3.5 Haiku, 3.7 Sonnet, and 4.x naming including `us.`/`eu.`/`au.`/`jp.`/`global.` inference profiles — is unchanged, per AWS's prompt-caching support matrix. A bundled-catalog tripwire now fails when a regenerated catalog introduces a Claude id shape the parser cannot read, so shape drift is loud instead of silently disabling caching.
18
+
19
+ - Direct-xAI `reasoning_effort` support is now derived from a strictly parsed canonical grok generation (4.5+) instead of the exact ids `grok-4.5`/`grok-4.6` in the compat layer and the coding-agent thinking-choice gate; a future grok release failed the id list, so `supportsReasoningEffort` stayed `false` and the user's thinking level was silently dropped from requests with no error. The generated-policy `reasoning` pin stays scoped to the two rows whose upstream metadata shipped stale — a per-row data correction, not a family rule — so catalog `reasoning: false` flags (including the bundled grok `-non-reasoning` variants) remain authoritative for everything else. Direct capability requires the official xAI origin; private and proxy OpenAI-compatible origins fail closed unless explicitly configured, while reseller routes such as OpenRouter keep their existing audited transport behavior.
20
+
21
+ - Cursor payload hooks now receive protobuf requests as JSON-safe values, await asynchronous inspection or replacement, and validate replacement payloads before transport. Checkpoint state containing 64-bit protobuf fields no longer makes hook-side `JSON.stringify` fail on JavaScript `bigint` values.
22
+ - The OpenAI Codex GPT-5.6 Sol entitlement preflight no longer rejects Business, Enterprise, or Team ChatGPT OAuth accounts. Eligibility was decided by `plan_type.includes("pro")`, so a `business`/`enterprise`/`team` plan (which is entitled to Sol on the backend) failed credential selection before dispatch with a spurious "cannot use model" error, even though a direct request to the ChatGPT Codex backend succeeds for those plans. A shared classifier now recognizes exact Pro/Business/Enterprise/Team tier names and known Free/Plus denials. Missing or unfamiliar plan names remain unknown and reach the provider instead of gaining entitlement through a substring match or being rejected by a client-side guess.
23
+
5
24
  ## [0.16.3] - 2026-09-04
6
25
 
7
26
  ## [0.16.2] - 2026-09-04
@@ -18,8 +18,8 @@
18
18
  * POST /v1/responses → OpenAI Responses in/out
19
19
  */
20
20
  import type { AuthStorage } from "../auth-storage";
21
- import type { Api, AssistantMessageEventStream, Model, Provider } from "../types";
22
- import type { AuthGatewayServerHandle, AuthGatewayServerOptions } from "./types";
21
+ import type { Api, AssistantMessageEventStream, Model, Provider, SimpleStreamOptions } from "../types";
22
+ import type { AuthGatewayServerHandle, AuthGatewayServerOptions, AuthGatewayParsedRequest as ParsedFormatRequest } from "./types";
23
23
  export type ModelResolver = (modelId: string) => Model<Api> | undefined;
24
24
  export interface AuthGatewayBootOptions extends AuthGatewayServerOptions {
25
25
  /** Source of credentials. Caller wires this to a broker-backed AuthStorage. */
@@ -66,5 +66,7 @@ export declare function isAuthGatewayModelBrokerConsumable(model: Pick<Model<Api
66
66
  */
67
67
  export declare function createAuthGatewayModelCatalog(provider: Provider, models: Iterable<Model<Api>>): AuthGatewayModelCatalog;
68
68
  export declare function releaseGatewayCredentialLeaseOnAdmission(events: Pick<AssistantMessageEventStream, "result">, release: () => void, signal?: AbortSignal): void;
69
+ /** Test seam for verifying translated gateway requests never acquire agent-owned provider identity. */
70
+ export declare function buildAuthGatewayStreamOptionsForTest(parsed: ParsedFormatRequest, api: Api): SimpleStreamOptions;
69
71
  export declare function startAuthGateway(opts: AuthGatewayBootOptions): AuthGatewayServerHandle;
70
72
  export declare function isSafeProviderScope(provider: unknown): provider is string;
@@ -0,0 +1,21 @@
1
+ export type BedrockClaudeGeneration = {
2
+ generation: {
3
+ major: number;
4
+ minor: number;
5
+ };
6
+ kind: string;
7
+ };
8
+ /**
9
+ * Bedrock Claude ids come in two shapes:
10
+ * - family-first (3.x era): anthropic.claude-3-5-haiku-20241022-v1:0
11
+ * - kind-first (4+ era): anthropic.claude-opus-4-20250514-v1:0,
12
+ * anthropic.claude-haiku-4-5-20251001-v1:0
13
+ * Cross-region profiles (us./eu./au./jp./apac./global. prefixes) and inference-profile
14
+ * ARNs keep the canonical model id as their final path segment.
15
+ */
16
+ export declare function parseBedrockClaudeGeneration(id: string): BedrockClaudeGeneration | undefined;
17
+ /**
18
+ * Returns undefined for non-Claude ids, false for malformed or unsupported
19
+ * Claude ids, and true only for AWS's documented cache-capable generations.
20
+ */
21
+ export declare function supportsBedrockClaudePromptCaching(id: string): boolean | undefined;
@@ -1,10 +1,32 @@
1
1
  import type { Model, OpenAICompat } from "./types";
2
2
  type ResolvedToolStrictMode = NonNullable<OpenAICompat["toolStrictMode"]> | "mixed";
3
+ export type GrokGeneration = {
4
+ major: number;
5
+ minor: number;
6
+ };
3
7
  /** Returns whether the request endpoint is an audited reasoning-control transport. */
4
8
  export declare function isAuditedOpenAIReasoningTransport(model: {
5
9
  provider: string;
6
10
  baseUrl?: string;
7
11
  }, resolvedBaseUrl?: string): boolean;
12
+ /**
13
+ * xAI's first-party API accepts `reasoning_effort` on Grok 4.5 and later.
14
+ * Provider labels are user-configurable, so both the provider and the official
15
+ * API origin must match. Unknown variants fail closed instead of inheriting a
16
+ * capability from a loose model-id prefix.
17
+ */
18
+ export declare function parseDirectXaiReasoningEffortGeneration(model: {
19
+ provider: string;
20
+ id: string;
21
+ api?: string;
22
+ baseUrl?: string;
23
+ }, resolvedBaseUrl?: string): GrokGeneration | undefined;
24
+ export declare function isDirectXaiReasoningEffortModel(model: {
25
+ provider: string;
26
+ id: string;
27
+ api?: string;
28
+ baseUrl?: string;
29
+ }, resolvedBaseUrl?: string): boolean;
8
30
  export type ResolvedOpenAICompat = Required<Omit<OpenAICompat, "openRouterRouting" | "vercelGatewayRouting" | "extraBody" | "toolStrictMode" | "toolChoiceSupport" | "supportsResponsesSessionAffinity" | "supportsServiceTier" | "reservedToolNames">> & {
9
31
  openRouterRouting?: OpenAICompat["openRouterRouting"];
10
32
  vercelGatewayRouting?: OpenAICompat["vercelGatewayRouting"];
@@ -6,8 +6,9 @@
6
6
  * No `@aws-sdk/*`, no `@smithy/*`, no `proxy-agent`. Proxies are honored via
7
7
  * Bun's native `HTTPS_PROXY` support.
8
8
  */
9
+ import { parseBedrockClaudeGeneration } from "../bedrock-claude-cache-policy";
9
10
  import type { Effort } from "../model-thinking";
10
- import type { StreamFunction, StreamOptions, ThinkingBudgets, Tool, ToolChoice } from "../types";
11
+ import type { Model, StreamFunction, StreamOptions, ThinkingBudgets, Tool, ToolChoice } from "../types";
11
12
  export type BedrockThinkingDisplay = "summarized" | "omitted";
12
13
  export interface BedrockOptions extends StreamOptions {
13
14
  region?: string;
@@ -51,10 +52,24 @@ interface WireToolConfig {
51
52
  toolChoice?: WireToolChoice;
52
53
  }
53
54
  export declare const streamBedrock: StreamFunction<"bedrock-converse-stream">;
55
+ /**
56
+ * Check if the model supports prompt caching.
57
+ * Supported: Claude 3.5 Haiku, Claude 3.7 Sonnet, and every later Claude
58
+ * generation:
59
+ * https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html
60
+ *
61
+ * For base models and system-defined inference profiles the model ID / ARN
62
+ * contains the model name, so we can decide locally.
63
+ *
64
+ * For application inference profiles (whose ARNs don't contain the model name),
65
+ * set AWS_BEDROCK_FORCE_CACHE=1 to enable cache points. Amazon Nova models
66
+ * have automatic caching and don't need explicit cache points.
67
+ */
68
+ export declare function supportsPromptCaching(model: Model<"bedrock-converse-stream">): boolean;
69
+ export { parseBedrockClaudeGeneration };
54
70
  export declare function stripBedrockForcedToolChoiceForRetry<T extends {
55
71
  toolConfig?: {
56
72
  toolChoice?: unknown;
57
73
  };
58
74
  }>(body: T): T;
59
75
  export declare function convertToolConfig(tools: Tool[] | undefined, toolChoice: BedrockOptions["toolChoice"]): WireToolConfig | undefined;
60
- export {};
@@ -203,6 +203,7 @@ export type AnthropicClientOptionsArgs = {
203
203
  maxRetryDelayMs?: number;
204
204
  streamFirstEventTimeoutMs?: number;
205
205
  streamIdleTimeoutMs?: number;
206
+ providerSessionId?: string;
206
207
  };
207
208
  export type AnthropicClientOptionsResult = {
208
209
  isOAuthToken: boolean;
@@ -3,4 +3,4 @@
3
3
  * the core-safe module so model metadata can use it without loading provider
4
4
  * implementations during startup.
5
5
  */
6
- export { detectOpenAICompat, type ResolvedOpenAICompat, resolveOpenAICompat } from "../openai-completions-compat";
6
+ export { detectOpenAICompat, type GrokGeneration, isDirectXaiReasoningEffortModel, parseDirectXaiReasoningEffortGeneration, type ResolvedOpenAICompat, resolveOpenAICompat, } from "../openai-completions-compat";
@@ -0,0 +1,5 @@
1
+ import type { FetchImpl, Model } from "../types";
2
+ export type OpenCodeGoApiFamily = "openai" | "anthropic";
3
+ export declare function resolveOpenCodeGoSessionId(model: Pick<Model, "provider">, baseUrl: string | undefined, providerSessionId: string | undefined, apiFamily: OpenCodeGoApiFamily): string | undefined;
4
+ export declare function applyOpenCodeGoSessionHeader(headers: Record<string, string>, sessionId: string | undefined): Record<string, string>;
5
+ export declare function wrapFetchForOpenCodeGoSession(baseFetch: FetchImpl, sessionId: string | undefined): FetchImpl;
@@ -239,6 +239,13 @@ export interface StreamOptions {
239
239
  * session-aware features. Ignored by providers that don't support it.
240
240
  */
241
241
  sessionId?: string;
242
+ /**
243
+ * Opaque conversation identity owned by the calling agent/session lifecycle.
244
+ * Unlike `sessionId`, this MUST NOT be synthesized from prompts, credentials,
245
+ * paths, cache keys, or other request content. Providers with a dedicated
246
+ * conversation header may use this only when their endpoint policy permits it.
247
+ */
248
+ providerSessionId?: string;
242
249
  /**
243
250
  * Provider-scoped mutable state store for this agent session.
244
251
  * Providers can use this to persist transport/session state between turns.
@@ -6,6 +6,16 @@
6
6
  * authority for the account tier; this module only names the model policy and
7
7
  * keeps the provider's deterministic rejection wording in one place.
8
8
  */
9
+ export type OpenAICodexProEntitlement = "entitled" | "denied" | "unknown";
10
+ /**
11
+ * Classify a ChatGPT `plan_type` for strict Pro-tier Codex models.
12
+ *
13
+ * The usage endpoint remains authoritative: only exact, documented tier names
14
+ * are classified. Known Free/Plus tiers can be rejected locally, while missing
15
+ * or unfamiliar values stay unknown and reach the provider instead of being
16
+ * guessed from a substring.
17
+ */
18
+ export declare function classifyOpenAICodexProEntitlement(planType: string | undefined): OpenAICodexProEntitlement;
9
19
  export declare function requiresOpenAICodexProModel(provider: string, modelId: string | undefined): boolean;
10
20
  export declare function requiresStrictOpenAICodexProModel(provider: string, modelId: string | undefined): boolean;
11
21
  export declare function isOpenAICodexChatGPTEntitlementError(message: string | undefined, code?: string): boolean;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/ai",
4
- "version": "0.16.3",
4
+ "version": "0.16.4",
5
5
  "description": "Unified LLM API with automatic model discovery and provider configuration",
6
6
  "homepage": "https://gajae-code.com",
7
7
  "author": "Yeachan-Heo and Gajae Code Contributors",
@@ -40,8 +40,8 @@
40
40
  "dependencies": {
41
41
  "@anthropic-ai/sdk": "^0.94.0",
42
42
  "@bufbuild/protobuf": "^2.12.0",
43
- "@gajae-code/natives": "0.16.3",
44
- "@gajae-code/utils": "0.16.3",
43
+ "@gajae-code/natives": "0.16.4",
44
+ "@gajae-code/utils": "0.16.4",
45
45
  "openai": "^6.36.0",
46
46
  "partial-json": "^0.1.7",
47
47
  "zod": "4.4.3"
@@ -469,6 +469,11 @@ function buildStreamOptions(parsed: ParsedFormatRequest, api: Api, signal: Abort
469
469
  return opts;
470
470
  }
471
471
 
472
+ /** Test seam for verifying translated gateway requests never acquire agent-owned provider identity. */
473
+ export function buildAuthGatewayStreamOptionsForTest(parsed: ParsedFormatRequest, api: Api): SimpleStreamOptions {
474
+ return buildStreamOptions(parsed, api, new AbortController().signal);
475
+ }
476
+
472
477
  /**
473
478
  * Classify an upstream / gateway-internal error into a status code and a
474
479
  * provider-style error type tag. Used by `handleFormatEndpoint` /
@@ -27,6 +27,7 @@ import type {
27
27
  } from "./usage";
28
28
 
29
29
  import {
30
+ classifyOpenAICodexProEntitlement,
30
31
  formatOpenAICodexChatGPTEntitlementError,
31
32
  requiresOpenAICodexProModel,
32
33
  requiresStrictOpenAICodexProModel,
@@ -1153,13 +1154,18 @@ function getUsagePlanType(report: UsageReport | null): string | undefined {
1153
1154
  }
1154
1155
 
1155
1156
  function getOpenAICodexPlanPriority(report: UsageReport | null): number {
1156
- const planType = getUsagePlanType(report);
1157
- if (!planType) return 1;
1158
- return planType.includes("pro") ? 0 : 2;
1157
+ const entitlement = classifyOpenAICodexProEntitlement(getUsagePlanType(report));
1158
+ if (entitlement === "entitled") return 0;
1159
+ if (entitlement === "denied") return 2;
1160
+ return 1;
1159
1161
  }
1160
1162
 
1161
1163
  function hasOpenAICodexProPlan(report: UsageReport | null): boolean {
1162
- return getUsagePlanType(report)?.includes("pro") === true;
1164
+ return classifyOpenAICodexProEntitlement(getUsagePlanType(report)) === "entitled";
1165
+ }
1166
+
1167
+ function hasKnownOpenAICodexNonProPlan(report: UsageReport | null): boolean {
1168
+ return classifyOpenAICodexProEntitlement(getUsagePlanType(report)) === "denied";
1163
1169
  }
1164
1170
 
1165
1171
  function resolveDefaultRankingStrategy(provider: Provider): CredentialRankingStrategy | undefined {
@@ -1621,6 +1627,7 @@ export class AuthStorage {
1621
1627
  */
1622
1628
  setRuntimeApiKey(provider: string, apiKey: string): void {
1623
1629
  const storageProvider = resolveOAuthStorageProvider(provider);
1630
+ if (this.#runtimeOverrides.get(storageProvider) === apiKey) return;
1624
1631
  this.#runtimeOverrides.set(storageProvider, apiKey);
1625
1632
  this.#bumpGeneration("set-runtime-api-key", storageProvider);
1626
1633
  }
@@ -1937,6 +1944,8 @@ export class AuthStorage {
1937
1944
  throw new Error(`Preferred credential selector cannot be combined with a credential selector for ${provider}`);
1938
1945
  }
1939
1946
  this.#assertPreferredCredentialSelectorUsable(storageProvider, selector);
1947
+ const existing = this.#runtimePreferredCredentialSelectors.get(storageProvider);
1948
+ if (existing?.kind === selector.kind && existing.value === selector.value) return;
1940
1949
  this.#runtimePreferredCredentialSelectors.set(storageProvider, selector);
1941
1950
  this.#bumpGeneration("set-runtime-preferred-credential-selector", provider);
1942
1951
  }
@@ -4957,15 +4966,15 @@ export class AuthStorage {
4957
4966
  const enforceProRequirement =
4958
4967
  requiresProModel && candidates.some(candidate => hasOpenAICodexProPlan(candidate.usage));
4959
4968
  // Spark retains its historical Plus fallback for grandfathered accounts.
4960
- // Sol is different: a confirmed non-Pro plan cannot call it, so reject the
4969
+ // Sol is different: confirmed Free/Plus plans cannot call it, so reject the
4961
4970
  // model before returning an OAuth bearer and letting the turn fail remotely.
4971
+ // Unknown plan names still reach the provider because the usage endpoint is
4972
+ // authoritative and future tiers must not be denied by a client-side guess.
4962
4973
  const strictProRequirement = requiresStrictOpenAICodexProModel(provider, options?.modelId);
4963
4974
  if (
4964
4975
  strictProRequirement &&
4965
4976
  candidates.length > 0 &&
4966
- candidates.every(
4967
- candidate => getUsagePlanType(candidate.usage) !== undefined && !hasOpenAICodexProPlan(candidate.usage),
4968
- )
4977
+ candidates.every(candidate => hasKnownOpenAICodexNonProPlan(candidate.usage))
4969
4978
  ) {
4970
4979
  throw new Error(formatOpenAICodexChatGPTEntitlementError(options?.modelId));
4971
4980
  }
@@ -0,0 +1,21 @@
1
+ export type BedrockClaudeGeneration = {
2
+ generation: {
3
+ major: number;
4
+ minor: number;
5
+ };
6
+ kind: string;
7
+ };
8
+ /**
9
+ * Bedrock Claude ids come in two shapes:
10
+ * - family-first (3.x era): anthropic.claude-3-5-haiku-20241022-v1:0
11
+ * - kind-first (4+ era): anthropic.claude-opus-4-20250514-v1:0,
12
+ * anthropic.claude-haiku-4-5-20251001-v1:0
13
+ * Cross-region profiles (us./eu./au./jp./apac./global. prefixes) and inference-profile
14
+ * ARNs keep the canonical model id as their final path segment.
15
+ */
16
+ export declare function parseBedrockClaudeGeneration(id: string): BedrockClaudeGeneration | undefined;
17
+ /**
18
+ * Returns undefined for non-Claude ids, false for malformed or unsupported
19
+ * Claude ids, and true only for AWS's documented cache-capable generations.
20
+ */
21
+ export declare function supportsBedrockClaudePromptCaching(id: string): boolean | undefined;
@@ -0,0 +1,68 @@
1
+ export type BedrockClaudeGeneration = {
2
+ generation: { major: number; minor: number };
3
+ kind: string;
4
+ };
5
+
6
+ function extractBedrockModelId(id: string): string | undefined {
7
+ if (id !== id.toLowerCase()) return undefined;
8
+ if (id.startsWith("arn:")) {
9
+ const arn =
10
+ /^arn:(?:aws|aws-us-gov|aws-cn|aws-iso|aws-iso-b|aws-eusc):bedrock:[a-z0-9-]+:(?:\d{12})?:(?:foundation-model|inference-profile)\/([^/]+)$/.exec(
11
+ id,
12
+ );
13
+ return arn?.[1];
14
+ }
15
+ return id.includes("/") ? undefined : id;
16
+ }
17
+
18
+ /**
19
+ * Bedrock Claude ids come in two shapes:
20
+ * - family-first (3.x era): anthropic.claude-3-5-haiku-20241022-v1:0
21
+ * - kind-first (4+ era): anthropic.claude-opus-4-20250514-v1:0,
22
+ * anthropic.claude-haiku-4-5-20251001-v1:0
23
+ * Cross-region profiles (us./eu./au./jp./apac./global. prefixes) and inference-profile
24
+ * ARNs keep the canonical model id as their final path segment.
25
+ */
26
+ export function parseBedrockClaudeGeneration(id: string): BedrockClaudeGeneration | undefined {
27
+ const modelId = extractBedrockModelId(id);
28
+ if (modelId === undefined) return undefined;
29
+ const prefix = "(?:(?:us|eu|au|jp|apac|global)\\.)?anthropic\\.claude-";
30
+ const component = "(?:0|[1-9]\\d?)";
31
+ const suffix = "(?:-(?:[a-z][a-z0-9]*(?::[a-z0-9]+)?|\\d{8}))*";
32
+ const familyFirst = new RegExp(`^${prefix}([1-9]\\d?)(?:-(${component}))?-([a-z][a-z0-9]*)${suffix}$`).exec(modelId);
33
+ if (familyFirst) {
34
+ return {
35
+ generation: {
36
+ major: Number(familyFirst[1]),
37
+ minor: familyFirst[2] === undefined ? 0 : Number(familyFirst[2]),
38
+ },
39
+ kind: familyFirst[3]!,
40
+ };
41
+ }
42
+ const kindFirst = new RegExp(`^${prefix}([a-z][a-z0-9]*)-([1-9]\\d?)(?:[.-](${component}))?${suffix}$`).exec(
43
+ modelId,
44
+ );
45
+ if (kindFirst) {
46
+ return {
47
+ generation: { major: Number(kindFirst[2]), minor: kindFirst[3] === undefined ? 0 : Number(kindFirst[3]) },
48
+ kind: kindFirst[1]!,
49
+ };
50
+ }
51
+ return undefined;
52
+ }
53
+
54
+ /**
55
+ * Returns undefined for non-Claude ids, false for malformed or unsupported
56
+ * Claude ids, and true only for AWS's documented cache-capable generations.
57
+ */
58
+ export function supportsBedrockClaudePromptCaching(id: string): boolean | undefined {
59
+ if (!id.toLowerCase().includes("anthropic.claude")) return undefined;
60
+ const claude = parseBedrockClaudeGeneration(id);
61
+ if (claude === undefined) return false;
62
+ if (claude.generation.major >= 4) return true;
63
+ if (claude.generation.major !== 3) return false;
64
+ return (
65
+ (claude.generation.minor === 5 && claude.kind === "haiku") ||
66
+ (claude.generation.minor === 7 && claude.kind === "sonnet")
67
+ );
68
+ }
@@ -15,8 +15,19 @@ const GPT_5_6_SOL_PRICING: TieredPricing = {
15
15
  },
16
16
  };
17
17
 
18
+ // GPT-6 Astra: $10/$50 standard, cache read $1, cache write $12.50; inputs past
19
+ // 272K apply 2x to input/cache and 1.5x to output.
20
+ const GPT_6_ASTRA_PRICING: TieredPricing = {
21
+ cost: { input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 },
22
+ longContextPricing: {
23
+ threshold: LONG_CONTEXT_THRESHOLD,
24
+ cost: { input: 20, output: 75, cacheRead: 2, cacheWrite: 25 },
25
+ },
26
+ };
27
+
18
28
  // OpenAI Standard pricing: https://developers.openai.com/api/docs/pricing
19
29
  const OPENAI_GPT_5_6_PRICING: ReadonlyMap<string, TieredPricing> = new Map([
30
+ ["gpt-6-astra", GPT_6_ASTRA_PRICING],
20
31
  ["gpt-5.6", GPT_5_6_SOL_PRICING],
21
32
  ["gpt-5.6-sol", GPT_5_6_SOL_PRICING],
22
33
  [
@@ -1,3 +1,4 @@
1
+ import { supportsBedrockClaudePromptCaching } from "./bedrock-claude-cache-policy";
1
2
  import {
2
3
  CODEX_GENERIC_CONTEXT_WINDOW,
3
4
  CODEX_GPT_5_6_CONTEXT_CAP,
@@ -5,7 +6,11 @@ import {
5
6
  isCodexProductTransport,
6
7
  } from "./context-cap-policy";
7
8
  import { applyOpenAIModelPricing } from "./model-pricing";
8
- import { isAuditedOpenAIReasoningTransport, resolveOpenAICompat } from "./openai-completions-compat";
9
+ import {
10
+ isAuditedOpenAIReasoningTransport,
11
+ parseDirectXaiReasoningEffortGeneration,
12
+ resolveOpenAICompat,
13
+ } from "./openai-completions-compat";
9
14
  import type { Api, Model as ApiModel, ThinkingConfig } from "./types";
10
15
  import { isClaudeForcedToolChoiceIncapableModelId } from "./utils/tool-choice-capability";
11
16
 
@@ -63,6 +68,12 @@ const KIMI_K3_EFFORTS: readonly Effort[] = [Effort.Low, Effort.High, Effort.Max]
63
68
  const DEEPSEEK_V4_FLASH_0731_EFFORTS: readonly Effort[] = [Effort.Low, Effort.High, Effort.Max];
64
69
  const GROK_4_5_EFFORTS: readonly Effort[] = [Effort.Low, Effort.Medium, Effort.High];
65
70
  const GROK_4_6_EFFORTS: readonly Effort[] = [Effort.Low, Effort.Medium, Effort.High, Effort.XHigh];
71
+ const GROK_4_20_EFFORTS: readonly Effort[] = [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High];
72
+ const GROK_4_20_REASONING_MODEL_IDS = new Set([
73
+ "grok-4.20-0309-reasoning",
74
+ "grok-4.20-beta-latest-reasoning",
75
+ "grok-4.20-multi-agent-beta-latest",
76
+ ]);
66
77
 
67
78
  const GPT_5_1_CODEX_MINI_EFFORTS: readonly Effort[] = [Effort.Medium, Effort.High];
68
79
  const CLOUDFLARE_AI_GATEWAY_BASE_URL = "https://gateway.ai.cloudflare.com/v1/<account>/<gateway>/anthropic";
@@ -76,6 +87,7 @@ type SemVer = {
76
87
  type GeminiKind = "pro" | "flash";
77
88
  type AnthropicKind = "opus" | "sonnet" | "fable";
78
89
  type OpenAIVariant =
90
+ | "astra"
79
91
  | "base"
80
92
  | "codex"
81
93
  | "codex-max"
@@ -539,6 +551,13 @@ function applyGeneratedModelPolicy(model: ApiModel<Api>): void {
539
551
  if (parsedModel.family === "openai") {
540
552
  applyOpenAICatalogPolicy(model, parsedModel);
541
553
  }
554
+ if (model.provider === "amazon-bedrock" && model.api === "bedrock-converse-stream") {
555
+ const cacheSupport = supportsBedrockClaudePromptCaching(model.id);
556
+ if (cacheSupport === false) {
557
+ model.cost.cacheRead = 0;
558
+ model.cost.cacheWrite = 0;
559
+ }
560
+ }
542
561
  // GLM-5.2 (Zhipu/ZAI): ships a 1M lossless context window, but the bundled
543
562
  // catalog copied GLM-5.1's 200K and that stale value survives generate-models
544
563
  // (provider-scoped models bypass the models.dev refresh in applyGlobalModelsDevFallback).
@@ -655,7 +674,10 @@ function inferGeneratedApplyPatchToolType(
655
674
  model: ApiModel<Api>,
656
675
  parsedModel: ParsedModel,
657
676
  ): ApiModel<Api>["applyPatchToolType"] {
658
- if (parsedModel.family !== "openai" || parsedModel.version.major !== 5) {
677
+ if (
678
+ parsedModel.family !== "openai" ||
679
+ (parsedModel.version.major !== 5 && !(parsedModel.version.major === 6 && parsedModel.variant === "astra"))
680
+ ) {
659
681
  return undefined;
660
682
  }
661
683
  if (model.provider === "openai" && model.api === "openai-responses") {
@@ -808,12 +830,14 @@ function expandEffortRange(thinking: ThinkingConfig): readonly Effort[] {
808
830
  }
809
831
 
810
832
  function inferSupportedEfforts<TApi extends Api>(parsedModel: ParsedModel, model: ApiModel<TApi>): readonly Effort[] {
811
- if (model.provider === "xai" && model.id === "grok-4.5") {
833
+ const grokGeneration = parseDirectXaiReasoningEffortGeneration(model);
834
+ if (grokGeneration !== undefined) {
835
+ if (grokGeneration.major === 4 && grokGeneration.minor === 6) return GROK_4_6_EFFORTS;
836
+ if (GROK_4_20_REASONING_MODEL_IDS.has(model.id)) {
837
+ return GROK_4_20_EFFORTS;
838
+ }
812
839
  return GROK_4_5_EFFORTS;
813
840
  }
814
- if (model.provider === "xai" && model.id === "grok-4.6") {
815
- return GROK_4_6_EFFORTS;
816
- }
817
841
  if (model.provider === "kimi-code" && model.id === "k3") {
818
842
  return KIMI_K3_EFFORTS;
819
843
  }
@@ -1002,7 +1026,7 @@ function parseAnthropicModel(modelId: string): AnthropicModel | null {
1002
1026
 
1003
1027
  function parseOpenAIModel(modelId: string): OpenAIModel | null {
1004
1028
  const match =
1005
- /gpt-(\d+(?:\.\d+){0,2})(?:-(codex-spark|codex-mini|codex-max|codex|luna|mini|max|nano|sol|terra))?$/.exec(
1029
+ /gpt-(\d+(?:\.\d+){0,2})(?:-(astra|codex-spark|codex-mini|codex-max|codex|luna|mini|max|nano|sol|terra))?$/.exec(
1006
1030
  modelId,
1007
1031
  );
1008
1032
  if (!match) {
package/src/models.json CHANGED
@@ -65461,6 +65461,43 @@
65461
65461
  },
65462
65462
  "applyPatchToolType": "freeform"
65463
65463
  },
65464
+ "gpt-6-astra": {
65465
+ "id": "gpt-6-astra",
65466
+ "name": "GPT-6-Astra",
65467
+ "api": "openai-codex-responses",
65468
+ "provider": "openai-codex",
65469
+ "baseUrl": "https://chatgpt.com/backend-api",
65470
+ "reasoning": true,
65471
+ "input": [
65472
+ "text",
65473
+ "image"
65474
+ ],
65475
+ "cost": {
65476
+ "input": 10,
65477
+ "output": 50,
65478
+ "cacheRead": 1,
65479
+ "cacheWrite": 12.5
65480
+ },
65481
+ "contextWindow": 272000,
65482
+ "maxTokens": 128000,
65483
+ "preferWebsockets": true,
65484
+ "priority": 1,
65485
+ "thinking": {
65486
+ "mode": "effort",
65487
+ "minLevel": "low",
65488
+ "maxLevel": "max"
65489
+ },
65490
+ "longContextPricing": {
65491
+ "threshold": 272000,
65492
+ "cost": {
65493
+ "input": 20,
65494
+ "output": 75,
65495
+ "cacheRead": 2,
65496
+ "cacheWrite": 25
65497
+ }
65498
+ },
65499
+ "applyPatchToolType": "freeform"
65500
+ },
65464
65501
  "gpt-daybreak-blue-latest": {
65465
65502
  "id": "gpt-daybreak-blue-latest",
65466
65503
  "name": "Daybreak Blue",
@@ -1,10 +1,32 @@
1
1
  import type { Model, OpenAICompat } from "./types";
2
2
  type ResolvedToolStrictMode = NonNullable<OpenAICompat["toolStrictMode"]> | "mixed";
3
+ export type GrokGeneration = {
4
+ major: number;
5
+ minor: number;
6
+ };
3
7
  /** Returns whether the request endpoint is an audited reasoning-control transport. */
4
8
  export declare function isAuditedOpenAIReasoningTransport(model: {
5
9
  provider: string;
6
10
  baseUrl?: string;
7
11
  }, resolvedBaseUrl?: string): boolean;
12
+ /**
13
+ * xAI's first-party API accepts `reasoning_effort` on Grok 4.5 and later.
14
+ * Provider labels are user-configurable, so both the provider and the official
15
+ * API origin must match. Unknown variants fail closed instead of inheriting a
16
+ * capability from a loose model-id prefix.
17
+ */
18
+ export declare function parseDirectXaiReasoningEffortGeneration(model: {
19
+ provider: string;
20
+ id: string;
21
+ api?: string;
22
+ baseUrl?: string;
23
+ }, resolvedBaseUrl?: string): GrokGeneration | undefined;
24
+ export declare function isDirectXaiReasoningEffortModel(model: {
25
+ provider: string;
26
+ id: string;
27
+ api?: string;
28
+ baseUrl?: string;
29
+ }, resolvedBaseUrl?: string): boolean;
8
30
  export type ResolvedOpenAICompat = Required<Omit<OpenAICompat, "openRouterRouting" | "vercelGatewayRouting" | "extraBody" | "toolStrictMode" | "toolChoiceSupport" | "supportsResponsesSessionAffinity" | "supportsServiceTier" | "reservedToolNames">> & {
9
31
  openRouterRouting?: OpenAICompat["openRouterRouting"];
10
32
  vercelGatewayRouting?: OpenAICompat["vercelGatewayRouting"];