@sayknow-cli/ai 0.5.2 → 0.5.8

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,29 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.5.8] - 2026-09-10
6
+
7
+ ## [0.5.7] - 2026-09-10
8
+
9
+ ### Added
10
+
11
+ - Bundled `openai-codex/gpt-6-astra` (ported from upstream #5294) so eligible ChatGPT accounts can select GPT-6 Astra without waiting for authenticated Codex discovery. The reviewed snapshot records the 272K prompt budget, 128K output cap, text-and-image input, websocket preference, low-through-max reasoning efforts, freeform `apply_patch`, and standard published pricing ($10/$50, cache read $1, cache write $12.50). Upstream's above-272K pricing tier is intentionally omitted: this fork's cost engine has no long-context tier support, so the field would be dead data. The GPT model-id parser and the generated `apply_patch` policy now recognize the `astra` variant (GPT-6), and the catalog was regenerated, which also picks up the current models.dev snapshot across gateway providers.
12
+
13
+ ### Fixed
14
+
15
+ - Bumped the advertised Claude Code client version from 2.1.219 to 2.1.267. Anthropic gates newer models (e.g. the 5.1 line) on the advertised client version and rejected requests with HTTP 400 `claude_code_version_too_old` ("version 2.1.251 or newer is required"), so those models were unusable through the Claude Code OAuth path.
16
+
17
+ ## [0.5.3] - 2026-08-28
18
+
19
+ ### Added
20
+
21
+ - Added first-class oMLX and SGLang OpenAI-compatible local providers. oMLX includes model capability enrichment and macOS profiles; SGLang discovery is credentialless only on normalized loopback origins and supports trusted `SGLANG_BASE_URL`/`SGLANG_API_KEY` configuration.
22
+ ### Fixed
23
+
24
+ - Hardened local OpenAI-compatible discovery with bounded catalogs, redirect refusal, canonical loopback URLs, safe oMLX limits, and explicit-only SGLang login; deprecated `sglang-local` credentials no longer shadow real keys.
25
+
26
+ - Anthropic's measured `invalid_request_error` prompt-overflow envelope now triggers context compaction only when its reported token usage actually exceeds the reported maximum, while auth, quota, rate-limit, and unverified prose remain authoritative non-overflow failures.
27
+
5
28
  ## [0.7.5] - 2026-06-27
6
29
 
7
30
  ### Fixed
@@ -27,6 +27,13 @@ export declare function enrichModelThinking<TApi extends Api>(model: ApiModel<TA
27
27
  * canonical rules, replacing any existing `thinking`.
28
28
  */
29
29
  export declare function refreshModelThinking<TApi extends Api>(model: ApiModel<TApi>): ApiModel<TApi>;
30
+ /**
31
+ * Extract the GLM generation from a Zhipu/ZAI model id: `glm-5.3` and
32
+ * `glm-5.3-flash` -> 5.3, `glm-5` and `glm-5-turbo` -> 5, `glm-4.7` -> 4.7.
33
+ * Returns undefined for ids that are not a plain GLM generation, including the
34
+ * vision line (`glm-5v-turbo`), so those never inherit text-model corrections.
35
+ */
36
+ export declare function glmGeneration(modelId: string): number | undefined;
30
37
  /**
31
38
  * Apply upstream metadata corrections to a mutable array of models.
32
39
  *
@@ -153,6 +153,11 @@ export interface LmStudioModelManagerConfig {
153
153
  baseUrl?: string;
154
154
  }
155
155
  export declare function lmStudioModelManagerOptions(config?: LmStudioModelManagerConfig): ModelManagerOptions<"openai-completions">;
156
+ export interface OmlxModelManagerConfig {
157
+ apiKey?: string;
158
+ baseUrl?: string;
159
+ }
160
+ export declare function omlxModelManagerOptions(config?: OmlxModelManagerConfig): ModelManagerOptions<"openai-completions">;
156
161
  export interface SyntheticModelManagerConfig {
157
162
  apiKey?: string;
158
163
  baseUrl?: string;
@@ -210,6 +215,11 @@ export interface VllmModelManagerConfig {
210
215
  baseUrl?: string;
211
216
  }
212
217
  export declare function vllmModelManagerOptions(config?: VllmModelManagerConfig): ModelManagerOptions<"openai-completions">;
218
+ export interface SglangModelManagerConfig {
219
+ apiKey?: string;
220
+ baseUrl?: string;
221
+ }
222
+ export declare function sglangModelManagerOptions(config?: SglangModelManagerConfig): ModelManagerOptions<"openai-completions">;
213
223
  export interface NanoGptModelManagerConfig {
214
224
  apiKey?: string;
215
225
  baseUrl?: string;
@@ -50,7 +50,7 @@ export declare function isAnthropicThinkingBlockMutationError(error: unknown): b
50
50
  * than only the latest one.
51
51
  */
52
52
  export declare function isAnthropicThinkingSignatureInvalidError(error: unknown): boolean;
53
- export declare const claudeCodeVersion = "2.1.219";
53
+ export declare const claudeCodeVersion = "2.1.267";
54
54
  export declare const claudeCodeEntrypoint = "sdk-cli";
55
55
  export declare const claudeToolPrefix: string;
56
56
  export declare const claudeCodeSystemInstruction = "You are a Claude agent, built on Anthropic's Claude Agent SDK.";
@@ -51,7 +51,7 @@ export interface ThinkingConfig {
51
51
  /** Provider-specific transport used to encode the selected effort. */
52
52
  mode: ThinkingControlMode;
53
53
  }
54
- export type KnownProvider = "alibaba-token-plan" | "amazon-bedrock" | "azure-openai" | "anthropic" | "google" | "google-gemini-cli" | "google-antigravity" | "google-vertex" | "openai" | "openai-codex" | "kimi-code" | "minimax-code" | "minimax-code-cn" | "github-copilot" | "fireworks" | "firepass" | "fugu" | "gitlab-duo" | "cursor" | "deepseek" | "deepinfra" | "xai" | "groq" | "cerebras" | "openrouter" | "kilo" | "vercel-ai-gateway" | "zai" | "glm-zcode" | "mistral" | "minimax" | "opencode-go" | "opencode-zen" | "opengateway" | "bizrouter" | "synthetic" | "cloudflare-ai-gateway" | "huggingface" | "litellm" | "moonshot" | "nvidia" | "nanogpt" | "ollama" | "ollama-cloud" | "qianfan" | "qwen-portal" | "together" | "venice" | "vllm" | "xiaomi" | "xiaomi-token-plan-sgp" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "zenmux" | "lm-studio";
54
+ export type KnownProvider = "alibaba-token-plan" | "amazon-bedrock" | "azure-openai" | "anthropic" | "google" | "google-gemini-cli" | "google-antigravity" | "google-vertex" | "openai" | "openai-codex" | "kimi-code" | "minimax-code" | "minimax-code-cn" | "github-copilot" | "fireworks" | "firepass" | "fugu" | "gitlab-duo" | "cursor" | "deepseek" | "deepinfra" | "xai" | "groq" | "cerebras" | "openrouter" | "kilo" | "vercel-ai-gateway" | "zai" | "glm-zcode" | "mistral" | "minimax" | "opencode-go" | "opencode-zen" | "opengateway" | "bizrouter" | "synthetic" | "cloudflare-ai-gateway" | "huggingface" | "litellm" | "moonshot" | "nvidia" | "nanogpt" | "ollama" | "ollama-cloud" | "qianfan" | "qwen-portal" | "together" | "venice" | "vllm" | "xiaomi" | "xiaomi-token-plan-sgp" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "zenmux" | "lm-studio" | "omlx" | "sglang";
55
55
  export type Provider = KnownProvider | string;
56
56
  import type { Effort } from "./model-thinking";
57
57
  /** Token budgets for each thinking level (token-based providers only) */
@@ -355,6 +355,12 @@ export interface ToolCall {
355
355
  * rejects the call with a retryable error instead.
356
356
  */
357
357
  incompleteArguments?: boolean;
358
+ /**
359
+ * Transient raw JSON for a provider-detected `\uXXXX`-escaped non-ASCII
360
+ * tool payload. The agent loop validates and removes it before persistence.
361
+ */
362
+ escapedNonAsciiArguments?: boolean;
363
+ escapedNonAsciiArgumentsRaw?: string;
358
364
  }
359
365
  export interface Usage {
360
366
  /** Non-cached input tokens (matches the bucket the provider bills as new input). */
@@ -72,3 +72,13 @@ export interface FetchOpenAICompatibleModelsOptions<TApi extends Api> {
72
72
  * Returns `[]` only when the endpoint responds successfully with no usable models.
73
73
  */
74
74
  export declare function fetchOpenAICompatibleModels<TApi extends Api>(options: FetchOpenAICompatibleModelsOptions<TApi>): Promise<Model<TApi>[] | null>;
75
+ /**
76
+ * Returns a canonical HTTP(S) OpenAI-compatible base URL without embedded URL
77
+ * credentials, query parameters, or fragments.
78
+ */
79
+ export declare function resolveCanonicalOpenAIBaseUrl(value: string | undefined): string | undefined;
80
+ /**
81
+ * Returns a local OpenAI-compatible base URL only when it is an HTTP(S)
82
+ * loopback endpoint; otherwise returns the trusted fallback.
83
+ */
84
+ export declare function resolveLoopbackOpenAIBaseUrl(value: string | undefined, fallback: string): string;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * SGLang login flow.
3
+ *
4
+ * SGLang commonly exposes an OpenAI-compatible API on a local server. It may
5
+ * require a bearer token, but local servers commonly allow unauthenticated
6
+ * access. This flow stores an API-key-style credential for auth storage.
7
+ */
8
+ import type { OAuthController } from "./types";
9
+ /**
10
+ * Login to SGLang with an explicit bearer token.
11
+ */
12
+ export declare function loginSglang(options: OAuthController): Promise<string>;
@@ -7,7 +7,7 @@ export type OAuthCredentials = {
7
7
  email?: string;
8
8
  accountId?: string;
9
9
  };
10
- export type OAuthProvider = "alibaba-token-plan" | "anthropic" | "bizrouter" | "cerebras" | "cloudflare-ai-gateway" | "cursor" | "deepseek" | "deepinfra" | "fireworks" | "firepass" | "fugu" | "github-copilot" | "google-gemini-cli" | "google-antigravity" | "gitlab-duo" | "huggingface" | "kimi-code" | "kilo" | "kagi" | "litellm" | "lm-studio" | "minimax-code" | "minimax-code-cn" | "moonshot" | "nvidia" | "nanogpt" | "ollama" | "ollama-cloud" | "openai-codex" | "openai-codex-device" | "opencode-go" | "opencode-zen" | "opengateway" | "parallel" | "perplexity" | "qianfan" | "qwen-portal" | "synthetic" | "tavily" | "together" | "venice" | "vercel-ai-gateway" | "vllm" | "xai" | "glm-zcode" | "xiaomi" | "xiaomi-token-plan-sgp" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "zenmux" | "zai";
10
+ export type OAuthProvider = "alibaba-token-plan" | "anthropic" | "bizrouter" | "cerebras" | "cloudflare-ai-gateway" | "cursor" | "deepseek" | "deepinfra" | "fireworks" | "firepass" | "fugu" | "github-copilot" | "google-gemini-cli" | "google-antigravity" | "gitlab-duo" | "huggingface" | "kimi-code" | "kilo" | "kagi" | "litellm" | "lm-studio" | "minimax-code" | "minimax-code-cn" | "moonshot" | "nvidia" | "nanogpt" | "ollama" | "ollama-cloud" | "openai-codex" | "openai-codex-device" | "opencode-go" | "opencode-zen" | "opengateway" | "parallel" | "perplexity" | "qianfan" | "qwen-portal" | "synthetic" | "tavily" | "together" | "venice" | "vercel-ai-gateway" | "vllm" | "sglang" | "xai" | "glm-zcode" | "xiaomi" | "xiaomi-token-plan-sgp" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "zenmux" | "zai";
11
11
  export type OAuthProviderId = OAuthProvider | (string & {});
12
12
  export type OAuthPrompt = {
13
13
  message: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@sayknow-cli/ai",
4
- "version": "0.5.2",
4
+ "version": "0.5.8",
5
5
  "description": "Unified LLM API with automatic model discovery and provider configuration",
6
6
  "homepage": "https://sayknow-cli.com",
7
7
  "author": "jaybeyond",
@@ -43,7 +43,7 @@
43
43
  "dependencies": {
44
44
  "@anthropic-ai/sdk": "^0.94.0",
45
45
  "@bufbuild/protobuf": "^2.12.0",
46
- "@sayknow-cli/utils": "0.5.2",
46
+ "@sayknow-cli/utils": "0.5.8",
47
47
  "openai": "^6.36.0",
48
48
  "partial-json": "^0.1.7",
49
49
  "zod": "4.4.3"
@@ -37,6 +37,12 @@ import { loginDeepSeek } from "./utils/oauth/deepseek";
37
37
  import { loginOpenAICodexDevice } from "./utils/oauth/openai-codex";
38
38
  import type { OAuthController, OAuthCredentials, OAuthProvider, OAuthProviderId } from "./utils/oauth/types";
39
39
 
40
+ const DEPRECATED_SGLANG_NO_AUTH_TOKEN = "sglang-local";
41
+
42
+ function isDeprecatedSglangNoAuthToken(provider: string, apiKey: string | undefined): boolean {
43
+ return provider === "sglang" && apiKey === DEPRECATED_SGLANG_NO_AUTH_TOKEN;
44
+ }
45
+
40
46
  // ─────────────────────────────────────────────────────────────────────────────
41
47
  // Credential Types
42
48
  // ─────────────────────────────────────────────────────────────────────────────
@@ -1351,6 +1357,11 @@ export class AuthStorage {
1351
1357
  .filter(
1352
1358
  (entry): entry is { credential: Extract<AuthCredential, { type: T }>; index: number } =>
1353
1359
  entry.credential.type === type,
1360
+ )
1361
+ .filter(
1362
+ entry =>
1363
+ type !== "api_key" ||
1364
+ !isDeprecatedSglangNoAuthToken(provider, (entry.credential as ApiKeyCredential).key),
1354
1365
  );
1355
1366
 
1356
1367
  if (credentials.length === 0) return undefined;
@@ -1950,6 +1961,12 @@ export class AuthStorage {
1950
1961
  await saveApiKeyCredential(apiKey);
1951
1962
  return;
1952
1963
  }
1964
+ case "sglang": {
1965
+ const { loginSglang } = await import("./utils/oauth/sglang");
1966
+ const apiKey = await loginSglang(ctrl);
1967
+ await saveApiKeyCredential(apiKey);
1968
+ return;
1969
+ }
1953
1970
  case "parallel": {
1954
1971
  const { loginParallel } = await import("./utils/oauth/parallel");
1955
1972
  const apiKey = await loginParallel(ctrl);
@@ -3434,7 +3451,8 @@ export class AuthStorage {
3434
3451
 
3435
3452
  const apiKeySelection = this.#selectCredentialByType(provider, "api_key");
3436
3453
  if (apiKeySelection) {
3437
- return this.#configValueResolver(apiKeySelection.credential.key);
3454
+ const apiKey = await this.#configValueResolver(apiKeySelection.credential.key);
3455
+ if (!isDeprecatedSglangNoAuthToken(provider, apiKey)) return apiKey;
3438
3456
  }
3439
3457
 
3440
3458
  // Return current OAuth access token only if it is not already expired.
@@ -3487,16 +3505,24 @@ export class AuthStorage {
3487
3505
  return configKey;
3488
3506
  }
3489
3507
 
3508
+ let skippedDeprecatedNoAuthToken = false;
3490
3509
  if (selectedCredential?.credential.type === "api_key") {
3491
- this.#recordSessionCredential(provider, sessionId, "api_key", selectedCredential.index);
3492
- return this.#configValueResolver(selectedCredential.credential.key);
3510
+ const apiKey = await this.#configValueResolver(selectedCredential.credential.key);
3511
+ if (!isDeprecatedSglangNoAuthToken(provider, apiKey)) {
3512
+ this.#recordSessionCredential(provider, sessionId, "api_key", selectedCredential.index);
3513
+ return apiKey;
3514
+ }
3515
+ skippedDeprecatedNoAuthToken = true;
3493
3516
  }
3494
3517
 
3495
- if (!selectedCredential) {
3518
+ if (!selectedCredential || skippedDeprecatedNoAuthToken) {
3496
3519
  const apiKeySelection = this.#selectCredentialByType(provider, "api_key", sessionId);
3497
3520
  if (apiKeySelection) {
3498
- this.#recordSessionCredential(provider, sessionId, "api_key", apiKeySelection.index);
3499
- return this.#configValueResolver(apiKeySelection.credential.key);
3521
+ const apiKey = await this.#configValueResolver(apiKeySelection.credential.key);
3522
+ if (!isDeprecatedSglangNoAuthToken(provider, apiKey)) {
3523
+ this.#recordSessionCredential(provider, sessionId, "api_key", apiKeySelection.index);
3524
+ return apiKey;
3525
+ }
3500
3526
  }
3501
3527
  }
3502
3528
 
@@ -64,6 +64,7 @@ type SemVer = {
64
64
  type GeminiKind = "pro" | "flash";
65
65
  type AnthropicKind = "opus" | "sonnet" | "fable";
66
66
  type OpenAIVariant =
67
+ | "astra"
67
68
  | "base"
68
69
  | "codex"
69
70
  | "codex-max"
@@ -190,6 +191,24 @@ export function refreshModelThinking<TApi extends Api>(model: ApiModel<TApi>): A
190
191
  return { ...model, thinking: inferModelThinking(model) };
191
192
  }
192
193
 
194
+ /**
195
+ * Extract the GLM generation from a Zhipu/ZAI model id: `glm-5.3` and
196
+ * `glm-5.3-flash` -> 5.3, `glm-5` and `glm-5-turbo` -> 5, `glm-4.7` -> 4.7.
197
+ * Returns undefined for ids that are not a plain GLM generation, including the
198
+ * vision line (`glm-5v-turbo`), so those never inherit text-model corrections.
199
+ */
200
+ export function glmGeneration(modelId: string): number | undefined {
201
+ const match = modelId.toLowerCase().match(/^glm-(\d+)(?:\.(\d+))?(?![\d.v])/);
202
+ if (!match) return undefined;
203
+ const [, major, minor] = match;
204
+ return minor === undefined ? Number(major) : Number(`${major}.${minor}`);
205
+ }
206
+
207
+ function glmGenerationAtLeast(modelId: string, minimum: number): boolean {
208
+ const generation = glmGeneration(modelId);
209
+ return generation !== undefined && generation >= minimum;
210
+ }
211
+
193
212
  /**
194
213
  * Apply upstream metadata corrections to a mutable array of models.
195
214
  *
@@ -198,7 +217,11 @@ export function refreshModelThinking<TApi extends Api>(model: ApiModel<TApi>): A
198
217
  */
199
218
  export function applyGeneratedModelPolicies(models: ApiModel<Api>[]): void {
200
219
  for (let index = 0; index < models.length; index++) {
201
- const model = refreshModelThinking(models[index]!);
220
+ const source = models[index]!;
221
+ if (source.provider === "omlx") {
222
+ source.reasoning = true;
223
+ }
224
+ const model = refreshModelThinking(source);
202
225
  applyGeneratedModelPolicy(model);
203
226
  models[index] = model;
204
227
  }
@@ -377,6 +400,16 @@ function applyGeneratedModelPolicy(model: ApiModel<Api>): void {
377
400
  };
378
401
  delete model.compat.thinkingFormat;
379
402
  }
403
+ if (model.provider === "omlx" && model.api === "openai-completions") {
404
+ model.compat = {
405
+ ...(model.compat ?? {}),
406
+ supportsStore: false,
407
+ supportsDeveloperRole: false,
408
+ supportsReasoningEffort: true,
409
+ thinkingFormat: "qwen-chat-template",
410
+ reasoningContentField: "reasoning_content",
411
+ };
412
+ }
380
413
  model.name = scrubGeneratedModelName(model.name);
381
414
  if (
382
415
  model.api === "openai-completions" &&
@@ -411,13 +444,31 @@ function applyGeneratedModelPolicy(model: ApiModel<Api>): void {
411
444
  if (parsedModel.family === "openai") {
412
445
  applyOpenAICatalogPolicy(model, parsedModel);
413
446
  }
414
- // GLM-5.2 (Zhipu/ZAI): ships a 1M lossless context window, but the bundled
415
- // catalog copied GLM-5.1's 200K and that stale value survives generate-models
416
- // (provider-scoped models bypass the models.dev refresh in applyGlobalModelsDevFallback).
417
- // Pin to the true 1M so context-cap / auto-compaction thresholds aren't tripped ~5x early.
418
- if (model.provider === "zai" && model.id === "glm-5.2") {
447
+ // GLM-5.2 and newer (Zhipu/ZAI): ship a 1M lossless context window, but the
448
+ // bundled catalog copied GLM-5.1's 200K and that stale value survives
449
+ // generate-models (provider-scoped models bypass the models.dev refresh in
450
+ // applyGlobalModelsDevFallback). Pin the true 1M so context-cap /
451
+ // auto-compaction thresholds aren't tripped ~5x early.
452
+ //
453
+ // Matched by generation rather than by exact id so the next GLM point
454
+ // release inherits the correct window instead of silently regressing to
455
+ // whatever the catalog happens to carry. The correction only raises a
456
+ // stale-low window: a future generation that genuinely ships more than 1M
457
+ // keeps its larger catalog value instead of being clamped down to 1M.
458
+ if (model.provider === "zai" && glmGenerationAtLeast(model.id, 5.2) && model.contextWindow < 1_000_000) {
419
459
  model.contextWindow = 1_000_000;
420
460
  }
461
+ // Alibaba Token Plan's qwen3.8-max-preview is routed through the OpenAI
462
+ // Responses API. models.dev recently started publishing the whole
463
+ // alibaba-token-plan provider, and its blanket chat-completions mapping wins
464
+ // the generate-models merge, so a regeneration silently reroutes this one
465
+ // paid model. Pin the routing that ships today and is covered by
466
+ // register-builtins' Responses lazy-path watchdog until the endpoint is
467
+ // re-verified; the catalog's other metadata (vision input, 131K output cap)
468
+ // is still inherited.
469
+ if (model.provider === "alibaba-token-plan" && model.id === "qwen3.8-max-preview") {
470
+ model.api = "openai-responses" as typeof model.api;
471
+ }
421
472
  // MiniMax-M3: MiniMax exposes a 1M context tier, but usage beyond 512K is
422
473
  // billed separately. Keep bundled/default metadata at the billing-safe 512K
423
474
  // unless an explicit paid-tier contract is added.
@@ -454,7 +505,10 @@ function inferGeneratedApplyPatchToolType(
454
505
  model: ApiModel<Api>,
455
506
  parsedModel: ParsedModel,
456
507
  ): ApiModel<Api>["applyPatchToolType"] {
457
- if (parsedModel.family !== "openai" || parsedModel.version.major !== 5) {
508
+ if (
509
+ parsedModel.family !== "openai" ||
510
+ (parsedModel.version.major !== 5 && !(parsedModel.version.major === 6 && parsedModel.variant === "astra"))
511
+ ) {
458
512
  return undefined;
459
513
  }
460
514
  if (model.provider === "openai" && model.api === "openai-responses") {
@@ -527,6 +581,9 @@ function inferDefaultEffort<TApi extends Api>(model: ApiModel<TApi>, parsedModel
527
581
  ) {
528
582
  return GPT_5_5_DEFAULT_EFFORT;
529
583
  }
584
+ if (model.provider === "omlx") {
585
+ return Effort.Medium;
586
+ }
530
587
  return undefined;
531
588
  }
532
589
 
@@ -662,6 +719,9 @@ function inferFallbackEfforts<TApi extends Api>(model: ApiModel<TApi>): readonly
662
719
  return DEFAULT_REASONING_EFFORTS;
663
720
  }
664
721
  if (model.api === "openai-completions") {
722
+ if (model.provider === "omlx") {
723
+ return [Effort.Low, Effort.Medium, Effort.High];
724
+ }
665
725
  const compat = resolveOpenAICompat(model as ApiModel<"openai-completions">);
666
726
  if (compat.thinkingFormat === "openai" && compat.supportsReasoningEffort) {
667
727
  return DEFAULT_REASONING_EFFORTS_WITH_XHIGH;
@@ -758,7 +818,7 @@ function parseAnthropicModel(modelId: string): AnthropicModel | null {
758
818
 
759
819
  function parseOpenAIModel(modelId: string): OpenAIModel | null {
760
820
  const match =
761
- /gpt-(\d+(?:\.\d+){0,2})(?:-(codex-spark|codex-mini|codex-max|codex|luna|mini|max|nano|sol|terra))?$/.exec(
821
+ /gpt-(\d+(?:\.\d+){0,2})(?:-(astra|codex-spark|codex-mini|codex-max|codex|luna|mini|max|nano|sol|terra))?$/.exec(
762
822
  modelId,
763
823
  );
764
824
  if (!match) {