@gajae-code/ai 0.16.1 → 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 (40) hide show
  1. package/CHANGELOG.md +25 -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 +22 -0
  11. package/package.json +3 -3
  12. package/src/auth-gateway/server.ts +5 -0
  13. package/src/auth-storage.ts +31 -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-codex-responses.ts +14 -5
  28. package/src/providers/openai-completions-compat.d.ts +1 -1
  29. package/src/providers/openai-completions-compat.ts +8 -1
  30. package/src/providers/openai-completions.ts +14 -1
  31. package/src/providers/openai-responses.ts +11 -1
  32. package/src/providers/opencode-go-session.d.ts +5 -0
  33. package/src/providers/opencode-go-session.ts +57 -0
  34. package/src/stream.ts +1 -0
  35. package/src/types.d.ts +7 -0
  36. package/src/types.ts +7 -0
  37. package/src/utils/codex-entitlement.d.ts +22 -0
  38. package/src/utils/codex-entitlement.ts +57 -0
  39. package/src/utils/discovery/antigravity.ts +2 -1
  40. package/src/utils/discovery/gemini.ts +2 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
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
+
24
+ ## [0.16.3] - 2026-09-04
25
+
26
+ ## [0.16.2] - 2026-09-04
27
+
28
+ - OpenAI Codex GPT-5.6 Sol selections now reject a known non-Pro ChatGPT OAuth account before dispatch, instead of allowing a binding that fails later with the provider's raw entitlement error. The same rejection is normalized for HTTP and streaming provider responses with guidance to choose a callable model or use an API-key credential.
29
+
5
30
  ## [0.16.1] - 2026-09-03
6
31
 
7
32
  ### Added
@@ -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.
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Model entitlement facts shared by Codex credential selection and provider
3
+ * error presentation.
4
+ *
5
+ * GPT-5.6 Sol is a Pro-tier ChatGPT Codex model. The usage endpoint is the
6
+ * authority for the account tier; this module only names the model policy and
7
+ * keeps the provider's deterministic rejection wording in one place.
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;
19
+ export declare function requiresOpenAICodexProModel(provider: string, modelId: string | undefined): boolean;
20
+ export declare function requiresStrictOpenAICodexProModel(provider: string, modelId: string | undefined): boolean;
21
+ export declare function isOpenAICodexChatGPTEntitlementError(message: string | undefined, code?: string): boolean;
22
+ export declare function formatOpenAICodexChatGPTEntitlementError(modelId: string | undefined): string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/ai",
4
- "version": "0.16.1",
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.1",
44
- "@gajae-code/utils": "0.16.1",
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` /
@@ -26,6 +26,12 @@ import type {
26
26
  UsageReport,
27
27
  } from "./usage";
28
28
 
29
+ import {
30
+ classifyOpenAICodexProEntitlement,
31
+ formatOpenAICodexChatGPTEntitlementError,
32
+ requiresOpenAICodexProModel,
33
+ requiresStrictOpenAICodexProModel,
34
+ } from "./utils/codex-entitlement";
29
35
  import { getOAuthApiKey, getOAuthProvider, refreshOAuthToken, resolveOAuthStorageProvider } from "./utils/oauth";
30
36
  import { loginDeepInfra } from "./utils/oauth/deepinfra";
31
37
  import { loginDeepSeek } from "./utils/oauth/deepseek";
@@ -1140,10 +1146,6 @@ export function readBrokerErrorBody(error: unknown): string | undefined {
1140
1146
  }
1141
1147
  }
1142
1148
 
1143
- function requiresOpenAICodexProModel(provider: string, modelId: string | undefined): boolean {
1144
- return provider === "openai-codex" && typeof modelId === "string" && modelId.includes("-spark");
1145
- }
1146
-
1147
1149
  function getUsagePlanType(report: UsageReport | null): string | undefined {
1148
1150
  const metadata = report?.metadata;
1149
1151
  if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) return undefined;
@@ -1152,13 +1154,18 @@ function getUsagePlanType(report: UsageReport | null): string | undefined {
1152
1154
  }
1153
1155
 
1154
1156
  function getOpenAICodexPlanPriority(report: UsageReport | null): number {
1155
- const planType = getUsagePlanType(report);
1156
- if (!planType) return 1;
1157
- 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;
1158
1161
  }
1159
1162
 
1160
1163
  function hasOpenAICodexProPlan(report: UsageReport | null): boolean {
1161
- 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";
1162
1169
  }
1163
1170
 
1164
1171
  function resolveDefaultRankingStrategy(provider: Provider): CredentialRankingStrategy | undefined {
@@ -1620,6 +1627,7 @@ export class AuthStorage {
1620
1627
  */
1621
1628
  setRuntimeApiKey(provider: string, apiKey: string): void {
1622
1629
  const storageProvider = resolveOAuthStorageProvider(provider);
1630
+ if (this.#runtimeOverrides.get(storageProvider) === apiKey) return;
1623
1631
  this.#runtimeOverrides.set(storageProvider, apiKey);
1624
1632
  this.#bumpGeneration("set-runtime-api-key", storageProvider);
1625
1633
  }
@@ -1936,6 +1944,8 @@ export class AuthStorage {
1936
1944
  throw new Error(`Preferred credential selector cannot be combined with a credential selector for ${provider}`);
1937
1945
  }
1938
1946
  this.#assertPreferredCredentialSelectorUsable(storageProvider, selector);
1947
+ const existing = this.#runtimePreferredCredentialSelectors.get(storageProvider);
1948
+ if (existing?.kind === selector.kind && existing.value === selector.value) return;
1939
1949
  this.#runtimePreferredCredentialSelectors.set(storageProvider, selector);
1940
1950
  this.#bumpGeneration("set-runtime-preferred-credential-selector", provider);
1941
1951
  }
@@ -4955,6 +4965,19 @@ export class AuthStorage {
4955
4965
  // non-Pro accounts can still attempt Spark requests (e.g. trial/grandfathered access).
4956
4966
  const enforceProRequirement =
4957
4967
  requiresProModel && candidates.some(candidate => hasOpenAICodexProPlan(candidate.usage));
4968
+ // Spark retains its historical Plus fallback for grandfathered accounts.
4969
+ // Sol is different: confirmed Free/Plus plans cannot call it, so reject the
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.
4973
+ const strictProRequirement = requiresStrictOpenAICodexProModel(provider, options?.modelId);
4974
+ if (
4975
+ strictProRequirement &&
4976
+ candidates.length > 0 &&
4977
+ candidates.every(candidate => hasKnownOpenAICodexNonProPlan(candidate.usage))
4978
+ ) {
4979
+ throw new Error(formatOpenAICodexChatGPTEntitlementError(options?.modelId));
4980
+ }
4958
4981
 
4959
4982
  const fallback = candidates[0];
4960
4983
 
@@ -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"];