@gajae-code/ai 0.15.6 → 0.16.1

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 (51) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/types/adapter-internals/aws-region.d.ts +7 -0
  3. package/dist/types/core.d.ts +1 -0
  4. package/dist/types/index.d.ts +1 -0
  5. package/dist/types/provider-models/openai-compat.d.ts +1 -0
  6. package/dist/types/providers/anthropic.d.ts +1 -1
  7. package/dist/types/providers/cursor.d.ts +27 -1
  8. package/dist/types/providers/google-gemini-headers.d.ts +1 -1
  9. package/dist/types/providers/openai-codex-responses.d.ts +6 -0
  10. package/dist/types/utils/discovery/openai-compatible.d.ts +21 -0
  11. package/dist/types/utils/h2-fetch.d.ts +7 -0
  12. package/dist/types/utils/schema/normalize.d.ts +0 -5
  13. package/dist/types/utils/sqlite-errors.d.ts +4 -0
  14. package/package.json +3 -3
  15. package/src/adapter-internals/aws-region.d.ts +7 -0
  16. package/src/adapter-internals/aws-region.ts +14 -0
  17. package/src/auth-broker/server.ts +10 -1
  18. package/src/auth-storage.ts +14 -14
  19. package/src/core.ts +1 -0
  20. package/src/index.ts +1 -0
  21. package/src/model-thinking.ts +8 -0
  22. package/src/models.json +201 -3
  23. package/src/provider-models/openai-compat.ts +93 -8
  24. package/src/providers/amazon-bedrock.ts +5 -1
  25. package/src/providers/anthropic.d.ts +1 -1
  26. package/src/providers/anthropic.ts +1 -1
  27. package/src/providers/aws-credentials.ts +6 -0
  28. package/src/providers/cursor.d.ts +27 -1
  29. package/src/providers/cursor.ts +234 -17
  30. package/src/providers/google-gemini-headers.d.ts +1 -1
  31. package/src/providers/google-gemini-headers.ts +1 -1
  32. package/src/providers/kiro-api-key.ts +33 -8
  33. package/src/providers/kiro-codewhisperer.ts +4 -1
  34. package/src/providers/openai-codex-responses.d.ts +6 -0
  35. package/src/providers/openai-codex-responses.ts +17 -2
  36. package/src/providers/pi-native-client.ts +24 -1
  37. package/src/utils/discovery/antigravity.ts +10 -1
  38. package/src/utils/discovery/openai-compatible.ts +38 -0
  39. package/src/utils/h2-fetch.ts +10 -0
  40. package/src/utils/oauth/callback-server.ts +8 -1
  41. package/src/utils/oauth/glm-zcode.ts +1 -1
  42. package/src/utils/oauth/kiro.ts +91 -22
  43. package/src/utils/schema/dereference.ts +169 -49
  44. package/src/utils/schema/draft.ts +46 -23
  45. package/src/utils/schema/normalize.d.ts +0 -5
  46. package/src/utils/schema/normalize.ts +396 -119
  47. package/src/utils/schema/types.ts +3 -1
  48. package/src/utils/schema/zod-decontaminate.ts +83 -29
  49. package/src/utils/sqlite-errors.d.ts +4 -0
  50. package/src/utils/sqlite-errors.ts +13 -0
  51. package/src/utils/tool-choice-capability.ts +2 -3
package/CHANGELOG.md CHANGED
@@ -2,6 +2,49 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.16.1] - 2026-09-03
6
+
7
+ ### Added
8
+
9
+ - Exported `fetchModelsDevPayload` from `provider-models/openai-compat` and coalesced its downloads. models.dev publishes one catalog document describing every provider, but each models.dev-backed provider downloaded it separately, so a single discovery pass transferred the same payload once per provider (and `packages/coding-agent` kept a second downloader of its own). Downloads are now shared per fetch implementation for a 60s window, failures are not retained, and `coding-agent` model discovery consumes the same fetcher.
10
+
11
+ - Exported `detectDiscoveredApiFamily` from `utils/discovery/openai-compatible`: infers the wire API family (`anthropic-messages` vs `openai-completions`) for a discovered model on a mixed OpenAI-compatible gateway, using the OpenAI `owned_by` owner first and the model id (`claude-*` vs `gpt-*`/`o1`/`codex`/…) as fallback, returning `undefined` when inconclusive. Consumed by custom-provider auto model discovery in `packages/coding-agent`.
12
+
13
+ ### Fixed
14
+
15
+ - Antigravity discovery now keeps mid-rollout models that the backend marks `isInternal` when the same response surfaces them through `agentModelSorts`. Internal models absent from the IDE's surfaced model groups remain hidden, and denylisted or retired selectors still take precedence.
16
+ - Tokenless loopback auth-broker requests carrying a browser `Origin` header are now rejected before credential reads or mutations. Native loopback clients without `Origin`, authenticated browser-origin clients, and the public health endpoint retain their existing behavior.
17
+ - `glm-zcode` login instructions now warn users who have the ZCode desktop app installed to cancel the browser's `zcode://` open prompt. The app exchanges the single-use authorization code itself, so a code pasted afterwards is rejected by the broker (`500 {"code":2007}`) and the documented paste flow failed without explanation.
18
+
19
+ - Cursor sessions now report prompt tokens instead of zero. `ConversationTokenDetails.used_tokens` counts the whole conversation, but the checkpoint handler assigned it to `usage.output` and returned early whenever token deltas had been seen — which is the normal streaming path — so `usage.input`, `usage.cacheRead`, and `usage.cacheWrite` were left at their zero initializers for every cursor request. `calculatePromptTokens` therefore fell through to its output-only fallback, so the context indicator tracked the last response's output size rather than the conversation, and automatic compaction never observed a full context. Conversation usage is now recorded through the stream and split into prompt and output tokens when the stream finalizes.
20
+
21
+ - OAuth callback responses now serialize provider-controlled result fields as safe JSON script data, preventing callback values from terminating the embedded state element while preserving exact JSON values and callback behavior.
22
+
23
+ ## [0.16.0] - 2026-09-02
24
+
25
+ ### Added
26
+
27
+ - Bundled `anthropic/claude-fable-5-1` (Claude Fable 5.1): 1M context, 128K output, `anthropic-adaptive` thinking clamped to `xhigh` on the Messages API, matching the Fable 5 effort ladder exactly. The model is served only by Anthropic's first-party API and requires a Claude Code compatibility version of at least `2.1.251`; older fingerprints are answered with an HTTP 400 `claude_code_version_too_old`, which the `2.1.257` attribution bump clears. Verified live: `2.1.219` returns the 400, `2.1.257` returns 200, `low`/`high`/`xhigh` all stream, and adaptive thinking returns visible summarized content rather than signature-only billed thinking.
28
+
29
+ ### Fixed
30
+
31
+ - Cursor's `kimi-k3-low`, `kimi-k3-high`, and `kimi-k3-max` now declare their real 1,048,576-token context window instead of 200,000. The bundled value was understated by 5.2x, so context management compacted these models at roughly a fifth of their usable window. The same underlying model already declares 1,048,576 elsewhere in this catalog (`openrouter/moonshotai/kimi-k3`, `kilo/moonshotai/kimi-k3`, and `opencode-go/kimi-k3` in `provider-models/openai-compat.ts`), so the cursor entries were the inconsistent ones. Measured against the live `cursor-agent` transport, which reports `usage.inputTokens` per request: 1,026,459 input tokens were accepted and answered correctly, while 1,161,948 tokens were silently summarized down to 20,552 and lost the answer. Output `maxTokens` is unchanged at 64,000, which this change did not measure.
32
+
33
+ - The Cursor Kimi K3 context correction is now enforced by the deterministic model-generation policy, so a future catalog regeneration cannot reintroduce the stale 200,000-token fallback.
34
+
35
+ - OpenCode Go's bundled catalog now contains all 33 models advertised by its live provider list, including the seven newly published models. Canonical endpoint metadata supplies their transport, multimodal input, limits, pricing, and reasoning capabilities, while generated output remains deterministic and preserves fail-closed selection when a live catalog is unavailable (#5144).
36
+
37
+ - SQLite corruption detection is now shared by credential and capability-cache surfaces, so callers can recognize `SQLITE_CORRUPT` and `SQLITE_NOTADB` without inspecting provider-specific error details.
38
+
39
+ - OpenAI Codex WebSockets are now opt-in on Windows. Bun 1.4.0 can segfault in its Windows TLS WebSocket handshake, so the automatic model preference uses SSE on Windows while the explicit WebSocket setting remains available for operators who have a newer safe runtime.
40
+ - MCP dispatcher tools now preserve dynamic nested `args` and `params` objects when a server declares them as propertyless JSON Schema objects. `normalizeSchemaForMCP` makes the implicit open-map semantics explicit with `additionalProperties: true` without loosening objects that declare properties or an explicit closure, so provider tool schemas no longer collapse valid nested arguments to `{}`.
41
+ - Schema normalization now copies `default`, `const`, `enum`, and `examples` payloads verbatim. These keywords carry instance data, not subschemas, so the previous recursive walk rewrote user payloads as if they were schemas (a `default` of `{"type":"object","nullable":true}` lost `nullable`, and literal `anyOf`/`const` branches collapsed into `enum`).
42
+ - Schema copies now write property names as own data properties. A `JSON.parse`d tool schema carries `__proto__` as an ordinary key, and plain assignment routed it to `Object.prototype`'s `__proto__` setter, dropping that valid property from the normalized schema.
43
+ - Schema-valued maps now remain maps even when dispatcher argument names collide with JSON Schema keywords (`type`, `default`, `const`, `enum`, or `examples`), and prototype-colliding names survive draft-upgrade, dereference, and Zod cleanup copies without polluting `Object.prototype`.
44
+ - Claude Code compatibility attribution moved from `2.1.219` to `2.1.257`, and the Gemini CLI spoofed version from `0.52.0` to `0.58.0`. Anthropic gates newer models behind a minimum client version, so a stale `claude-cli/<version>` fingerprint surfaces as an HTTP 400 on a model the account can otherwise reach. `scripts/check-spoofed-versions.ts` now covers `claudeCodeVersion` (resolved from Anthropic's `latest` native-installer release channel, with the `@anthropic-ai/claude-code` npm dist-tag as a second source and the higher of the two winning so one lagging channel cannot pin us back), spans multiple source files, and compares by semver instead of string equality. Both constants had only ever been bumped by hand and the check was never scheduled, so the new read-only daily `spoofed-version-sync` workflow now runs it and fails with a one-command remediation on drift.
45
+ - Bedrock and both Kiro credential transports now reject malformed project-aware AWS regions before interpolating them into authenticated service authorities. Valid lowercase ASCII region labels and explicit Kiro model `baseUrl` overrides are unchanged.
46
+ - Kiro Builder ID device-code login no longer aborts on the first poll. AWS SSO OIDC `CreateToken` reports the in-progress states (`authorization_pending`, `slow_down`) as HTTP 400 responses whose body carries the error code, but the poll loop went through the shared `fetchOidc` helper that throws on any non-2xx status, so the `authorization_pending` branch it already implemented was unreachable and every login failed within one interval of printing the verification URL. The poll now reads the JSON payload before classifying the response; non-2xx responses without an `error` field still fail closed.
47
+
5
48
  ## [0.15.6] - 2026-08-30
6
49
 
7
50
  ### Added
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Fail closed before an AWS region is interpolated into a credential-bearing
3
+ * service authority. Region names are one lowercase ASCII DNS label; dots,
4
+ * URL delimiters, Unicode, whitespace, and controls must never reach URL
5
+ * parsing as part of that label.
6
+ */
7
+ export declare function assertAwsRegionLabel(region: unknown): asserts region is string;
@@ -31,5 +31,6 @@ export type { OAuthCredentials, OAuthProvider, OAuthProviderId, OAuthProviderInf
31
31
  export * from "./utils/overflow";
32
32
  export * from "./utils/retry";
33
33
  export * from "./utils/schema";
34
+ export * from "./utils/sqlite-errors";
34
35
  export * from "./utils/tool-choice-capability";
35
36
  export * from "./utils/validation";
@@ -51,5 +51,6 @@ export * from "./utils/overflow";
51
51
  export * from "./utils/provider-safety-stop";
52
52
  export * from "./utils/retry";
53
53
  export * from "./utils/schema";
54
+ export * from "./utils/sqlite-errors";
54
55
  export * from "./utils/tool-choice-capability";
55
56
  export * from "./utils/validation";
@@ -23,6 +23,7 @@ export interface ModelsDevModel {
23
23
  npm?: string;
24
24
  };
25
25
  }
26
+ export declare function fetchModelsDevPayload(fetchImpl?: typeof fetch): Promise<unknown>;
26
27
  export interface OpenAIModelManagerConfig {
27
28
  apiKey?: string;
28
29
  baseUrl?: string;
@@ -107,7 +107,7 @@ export interface CpaToolAliasRestoreFailure {
107
107
  */
108
108
  export declare function parseCpaToolAliasRestoreFailure(error: unknown): CpaToolAliasRestoreFailure | undefined;
109
109
  export declare function isCpaToolAliasRestoreFailure(error: unknown): boolean;
110
- export declare const claudeCodeVersion = "2.1.219";
110
+ export declare const claudeCodeVersion = "2.1.257";
111
111
  export declare const claudeCodeEntrypoint = "sdk-cli";
112
112
  export declare const claudeToolPrefix: string;
113
113
  export declare const claudeCodeSystemInstruction = "You are a Claude agent, built on Anthropic's Claude Agent SDK.";
@@ -1,8 +1,9 @@
1
1
  import { type JsonValue } from "@bufbuild/protobuf";
2
- import type { CursorExecHandlerResult, CursorExecHandlers, CursorToolResultHandler, Message, Model, StreamFunction, StreamOptions, ToolCall, ToolResultMessage } from "../types";
2
+ import type { AssistantMessage, CursorExecHandlerResult, CursorExecHandlers, CursorToolResultHandler, Message, Model, StreamFunction, StreamOptions, ToolCall, ToolResultMessage, Usage } from "../types";
3
3
  import { kCursorExecResolved } from "../utils/block-symbols";
4
4
  import { CURSOR_CLIENT_VERSION } from "./cursor/client-version";
5
5
  import type { CursorRule, RequestedModel_ModelParameterbytes } from "./cursor/gen/agent_pb";
6
+ import { type ConversationStateStructure } from "./cursor/gen/agent_pb";
6
7
  export declare const CURSOR_API_URL = "https://api2.cursor.sh";
7
8
  export { CURSOR_CLIENT_VERSION };
8
9
  /** Drop all cached state + blob bytes for a conversation (F15 bound + session-teardown hook). */
@@ -31,6 +32,19 @@ type ToolCallState = ToolCall & {
31
32
  kind: "mcp" | "todo_write" | "native" | "cursor-exec";
32
33
  [kCursorExecResolved]?: true;
33
34
  };
35
+ interface UsageState {
36
+ sawTokenDelta: boolean;
37
+ /**
38
+ * Latest `ConversationTokenDetails.used_tokens`: the whole conversation's
39
+ * token consumption as counted by Cursor, not this turn's output.
40
+ */
41
+ conversationUsedTokens: number;
42
+ /** Output tokens already included in the latest checkpoint snapshot. */
43
+ checkpointOutputTokens: number;
44
+ /** Whether the current stream received a checkpoint, including an explicit zero. */
45
+ hasConversationCheckpoint: boolean;
46
+ pendingCheckpoint?: ConversationStateStructure;
47
+ }
34
48
  /** Exported for tests: verifies handler is invoked with correct `this` when passed as bound. */
35
49
  export declare function resolveExecHandler<TArgs, TResult>(args: TArgs, handler: ((args: TArgs) => Promise<CursorExecHandlerResult<TResult>>) | undefined, onToolResult: CursorToolResultHandler | undefined, buildFromToolResult: (toolResult: ToolResultMessage) => TResult, buildRejected: (reason: string) => TResult, buildError: (error: string) => TResult): Promise<{
36
50
  execResult: TResult;
@@ -44,6 +58,18 @@ export declare function createCursorMessageQueueForTest(onError?: (error: unknow
44
58
  /** Exported for direct regression coverage of the JSON-safety boundary. */
45
59
  export declare function cursorJsonSafeValueForTest(value: unknown): unknown;
46
60
  export declare function buildNativeToolCallBlock(toolCall: Record<string, unknown>, callId: string, index: number): ToolCallState | null;
61
+ /**
62
+ * Cursor streams output tokens as deltas and reports whole-conversation
63
+ * consumption separately as `ConversationTokenDetails.used_tokens`. Derive
64
+ * prompt tokens from the difference so context accounting and compaction see a
65
+ * real prompt size instead of zero.
66
+ */
67
+ export declare function finalizeCursorUsage(output: AssistantMessage, usageState: UsageState): void;
68
+ /** Exposes {@link finalizeCursorUsage} for tests without a live HTTP/2 stream. */
69
+ export declare function finalizeCursorUsageForTest(usedTokens: number, outputTokens: number, options?: {
70
+ checkpointOutputTokens?: number;
71
+ hasConversationCheckpoint?: boolean;
72
+ }): Usage;
47
73
  /**
48
74
  * Build `ConversationStateStructure.rootPromptMessagesJson` blob IDs for the
49
75
  * system prompt plus prior conversation history, as JSON blobs matching
@@ -5,7 +5,7 @@
5
5
  */
6
6
  export declare const GEMINI_CLI_VERSION_ENV = "GJC_AI_GEMINI_CLI_VERSION";
7
7
  export declare const LEGACY_GEMINI_CLI_VERSION_ENV = "PI_AI_GEMINI_CLI_VERSION";
8
- export declare const DEFAULT_GEMINI_CLI_VERSION = "0.52.0";
8
+ export declare const DEFAULT_GEMINI_CLI_VERSION = "0.58.0";
9
9
  export declare function getGeminiCliUserAgent(modelId?: string): string;
10
10
  export declare const getGeminiCliHeaders: (modelId?: string) => {
11
11
  "User-Agent": string;
@@ -25,6 +25,12 @@ export declare function normalizeCodexToolChoice(choice: ToolChoice | undefined,
25
25
  export declare function formatCodexUserAgent(platform: string, release: string, arch: string): string;
26
26
  export declare const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses">;
27
27
  export declare function prewarmOpenAICodexResponses(model: Model<"openai-codex-responses">, options?: Pick<OpenAICodexResponsesOptions, "apiKey" | "headers" | "sessionId" | "signal" | "preferWebsockets" | "providerSessionState">): Promise<void>;
28
+ /**
29
+ * Bun 1.4.0's Windows WebSocket client can segfault during TLS handshakes.
30
+ * Keep the model's websocket preference available on other platforms, while
31
+ * requiring an explicit opt-in on Windows until the bundled runtime is fixed.
32
+ */
33
+ export declare function isCodexWebSocketSafeByDefault(platform?: NodeJS.Platform): boolean;
28
34
  export interface OpenAICodexTransportDetails {
29
35
  websocketPreferred: boolean;
30
36
  lastTransport?: CodexTransport;
@@ -1,6 +1,27 @@
1
1
  import type { Api, FetchImpl, Model, Provider } from "../../types";
2
2
  /** Catalog identities are rendered and used for routing; unsafe values are dropped, never rewritten. */
3
3
  export declare function isSafeCatalogModelId(value: unknown): value is string;
4
+ /**
5
+ * The two wire families a mixed OpenAI-compatible gateway (e.g. CLIProxyAPI)
6
+ * can front. A gateway exposes an OpenAI-shaped `/v1/models` catalog but may
7
+ * proxy Anthropic models that must be driven through the Anthropic Messages
8
+ * transport rather than OpenAI Chat Completions.
9
+ */
10
+ export type DiscoveredApiFamily = "anthropic-messages" | "openai-completions";
11
+ /**
12
+ * Infer the wire API family for one discovered model on a mixed
13
+ * OpenAI-compatible gateway.
14
+ *
15
+ * Uses the `owned_by` owner string first (authoritative when the gateway
16
+ * populates it — `"anthropic"` / `"openai"`), then falls back to the model id
17
+ * (`claude-*` → Anthropic, `gpt-*`/`o1`/`codex`/… → OpenAI). Returns
18
+ * `undefined` when neither signal is conclusive so the caller can keep the
19
+ * provider-level default instead of guessing.
20
+ */
21
+ export declare function detectDiscoveredApiFamily(entry: {
22
+ id?: unknown;
23
+ owned_by?: unknown;
24
+ }): DiscoveredApiFamily | undefined;
4
25
  /**
5
26
  * Minimal OpenAI-style model entry shape consumed by discovery.
6
27
  *
@@ -14,6 +14,13 @@
14
14
  * or `ConnectionClosed` rather than `HTTP2Unsupported`, so we treat those
15
15
  * codes as h2-fallback triggers as well.
16
16
  *
17
+ * ALPN-refusing hosts (notably zcode.z.ai, the GLM ZCode OAuth broker) abort
18
+ * the TLS handshake entirely when the client offers ALPN h2. Bun reports that
19
+ * abort as `UNKNOWN_CERTIFICATE_VERIFICATION_ERROR` even though the host's
20
+ * certificate chain verifies fine over h1 (issue #5178), so that code is a
21
+ * fallback trigger too — never a reason to accept a bad certificate: the h1
22
+ * attempt below performs full verification on its own.
23
+ *
17
24
  * Bun negotiates h2 via ALPN over TLS only (no h2c), so plain `http://` URLs
18
25
  * skip the attempt entirely — avoids the throw/retry round-trip for localhost.
19
26
  *
@@ -25,11 +25,6 @@ export interface NormalizeSchemaOptions {
25
25
  }
26
26
  /** Copy all keys from a schema except the specified combiner key. */
27
27
  export declare function copySchemaWithout(schema: JsonObject, combiner: string): JsonObject;
28
- /**
29
- * Recursively strip any remaining anyOf/oneOf that same-type or mixed-type
30
- * collapse can handle. This is needed because object-combiner merging can
31
- * create new anyOf in merged subtrees after child normalization already ran.
32
- */
33
28
  export declare function stripResidualCombiners(value: unknown, epoch?: number): unknown;
34
29
  export declare function normalizeSchema(value: unknown, options: NormalizeSchemaOptions): unknown;
35
30
  export declare function normalizeSchemaForGoogle(value: unknown): unknown;
@@ -0,0 +1,4 @@
1
+ /** Return whether an error carries a SQLite result code. */
2
+ export declare function isSqliteError(error: unknown): boolean;
3
+ /** Return whether an error is one of SQLite's explicit database-corruption classes. */
4
+ export declare function isSqliteCorruptionError(error: unknown): boolean;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/ai",
4
- "version": "0.15.6",
4
+ "version": "0.16.1",
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.15.6",
44
- "@gajae-code/utils": "0.15.6",
43
+ "@gajae-code/natives": "0.16.1",
44
+ "@gajae-code/utils": "0.16.1",
45
45
  "openai": "^6.36.0",
46
46
  "partial-json": "^0.1.7",
47
47
  "zod": "4.4.3"
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Fail closed before an AWS region is interpolated into a credential-bearing
3
+ * service authority. Region names are one lowercase ASCII DNS label; dots,
4
+ * URL delimiters, Unicode, whitespace, and controls must never reach URL
5
+ * parsing as part of that label.
6
+ */
7
+ export declare function assertAwsRegionLabel(region: unknown): asserts region is string;
@@ -0,0 +1,14 @@
1
+ const AWS_REGION_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
2
+
3
+ /**
4
+ * Fail closed before an AWS region is interpolated into a credential-bearing
5
+ * service authority. Region names are one lowercase ASCII DNS label; dots,
6
+ * URL delimiters, Unicode, whitespace, and controls must never reach URL
7
+ * parsing as part of that label.
8
+ */
9
+ export function assertAwsRegionLabel(region: unknown): asserts region is string {
10
+ const match = typeof region === "string" ? AWS_REGION_LABEL.exec(region) : undefined;
11
+ if (!match || match[0] !== region) {
12
+ throw new Error("Invalid AWS region: expected a lowercase ASCII DNS label.");
13
+ }
14
+ }
@@ -11,7 +11,7 @@
11
11
  */
12
12
  import * as crypto from "node:crypto";
13
13
  import { logger } from "@gajae-code/utils";
14
- import { timingSafeEqual } from "../auth-gateway/http";
14
+ import { isNoAuthBrowserOriginRequest, timingSafeEqual } from "../auth-gateway/http";
15
15
  import type { AuthStorage } from "../auth-storage";
16
16
  import type { Provider } from "../types";
17
17
  import { assertAuthenticatedOrLoopback, parseBind } from "../utils/parse-bind";
@@ -594,6 +594,15 @@ export function startAuthBroker(opts: AuthBrokerServerOptions): AuthBrokerServer
594
594
  const body: HealthzResponse = { ok: true, version };
595
595
  return json(200, body);
596
596
  }
597
+ if (isNoAuthBrowserOriginRequest(req, tokens)) {
598
+ logger.info("auth-broker no-auth browser-origin request rejected", {
599
+ method: req.method,
600
+ path: pathname,
601
+ peer,
602
+ originPresent: true,
603
+ });
604
+ return json(403, { error: "no-auth rejects requests carrying Origin" });
605
+ }
597
606
  if (!isAuthorized(req, tokens)) {
598
607
  logger.info("auth-broker request unauthorized", { method: req.method, path: pathname, peer });
599
608
  return json(401, { error: "unauthorized" });
@@ -37,6 +37,7 @@ import type {
37
37
  OAuthProvider,
38
38
  OAuthProviderId,
39
39
  } from "./utils/oauth/types";
40
+ import { isSqliteError } from "./utils/sqlite-errors";
40
41
 
41
42
  // ─────────────────────────────────────────────────────────────────────────────
42
43
  // Credential Types
@@ -4944,7 +4945,9 @@ export class AuthStorage {
4944
4945
  !refreshedCredentials.persistedByLease,
4945
4946
  credentialId,
4946
4947
  );
4947
- } catch {}
4948
+ } catch (error) {
4949
+ if (isSqliteError(error)) throw error;
4950
+ }
4948
4951
  }),
4949
4952
  );
4950
4953
 
@@ -5408,6 +5411,7 @@ export class AuthStorage {
5408
5411
  this.#recordSessionCredential(provider, sessionId, "oauth", selection.index);
5409
5412
  return { apiKey: result.apiKey, credential: updated };
5410
5413
  } catch (error) {
5414
+ if (isSqliteError(error)) throw error;
5411
5415
  // Auth-broker errors retain the sanitized upstream body separately from
5412
5416
  // their transport message. Include that body for failure classification
5413
5417
  // (the broker's 500 envelope otherwise hides `invalid_grant`) while
@@ -7116,19 +7120,15 @@ export class SqliteAuthCredentialStore implements AuthCredentialStore {
7116
7120
  }
7117
7121
 
7118
7122
  updateAuthCredential(id: number, credential: AuthCredential): void {
7119
- try {
7120
- const providerRow = this.#db.prepare("SELECT provider FROM auth_credentials WHERE id = ?").get(id) as
7121
- | { provider?: string }
7122
- | undefined;
7123
- const provider = providerRow?.provider ?? "";
7124
- const serialized = serializeCredential(provider, credential);
7125
- if (!serialized) return;
7126
- this.#updateStmt.run(serialized.credentialType, serialized.data, serialized.identityKey, id);
7127
- if (provider) {
7128
- this.#purgeSupersededDisabledRows(provider, this.listAuthCredentials(provider));
7129
- }
7130
- } catch {
7131
- // Ignore update failures
7123
+ const providerRow = this.#db.prepare("SELECT provider FROM auth_credentials WHERE id = ?").get(id) as
7124
+ | { provider?: string }
7125
+ | undefined;
7126
+ const provider = providerRow?.provider ?? "";
7127
+ const serialized = serializeCredential(provider, credential);
7128
+ if (!serialized) return;
7129
+ this.#updateStmt.run(serialized.credentialType, serialized.data, serialized.identityKey, id);
7130
+ if (provider) {
7131
+ this.#purgeSupersededDisabledRows(provider, this.listAuthCredentials(provider));
7132
7132
  }
7133
7133
  }
7134
7134
 
package/src/core.ts CHANGED
@@ -40,5 +40,6 @@ export type {
40
40
  export * from "./utils/overflow";
41
41
  export * from "./utils/retry";
42
42
  export * from "./utils/schema";
43
+ export * from "./utils/sqlite-errors";
43
44
  export * from "./utils/tool-choice-capability";
44
45
  export * from "./utils/validation";
package/src/index.ts CHANGED
@@ -57,5 +57,6 @@ export * from "./utils/overflow";
57
57
  export * from "./utils/provider-safety-stop";
58
58
  export * from "./utils/retry";
59
59
  export * from "./utils/schema";
60
+ export * from "./utils/sqlite-errors";
60
61
  export * from "./utils/tool-choice-capability";
61
62
  export * from "./utils/validation";
@@ -278,6 +278,14 @@ export function applyGeneratedModelPolicies(models: ApiModel<Api>[]): void {
278
278
  if (source.provider === "omlx") {
279
279
  source.reasoning = true;
280
280
  }
281
+ if (
282
+ source.provider === "cursor" &&
283
+ (source.id === "kimi-k3-low" || source.id === "kimi-k3-high" || source.id === "kimi-k3-max")
284
+ ) {
285
+ // Cursor's GetUsableModels metadata omits numeric context limits for
286
+ // this family. Keep the measured 1M fallback across regeneration.
287
+ source.contextWindow = 1_048_576;
288
+ }
281
289
  const model = refreshModelThinking(source);
282
290
  applyGeneratedModelPolicy(model);
283
291
  models[index] = model;
package/src/models.json CHANGED
@@ -3853,6 +3853,31 @@
3853
3853
  "maxLevel": "xhigh"
3854
3854
  }
3855
3855
  },
3856
+ "claude-fable-5-1": {
3857
+ "id": "claude-fable-5-1",
3858
+ "name": "Anthropic Fable 5.1",
3859
+ "api": "anthropic-messages",
3860
+ "provider": "anthropic",
3861
+ "baseUrl": "https://api.anthropic.com",
3862
+ "reasoning": true,
3863
+ "input": [
3864
+ "text",
3865
+ "image"
3866
+ ],
3867
+ "cost": {
3868
+ "input": 10,
3869
+ "output": 50,
3870
+ "cacheRead": 0.25,
3871
+ "cacheWrite": 12.5
3872
+ },
3873
+ "contextWindow": 1000000,
3874
+ "maxTokens": 128000,
3875
+ "thinking": {
3876
+ "mode": "anthropic-adaptive",
3877
+ "minLevel": "minimal",
3878
+ "maxLevel": "xhigh"
3879
+ }
3880
+ },
3856
3881
  "claude-haiku-4-5": {
3857
3882
  "id": "claude-haiku-4-5",
3858
3883
  "name": "Anthropic Haiku 4.5 (latest)",
@@ -10900,7 +10925,7 @@
10900
10925
  "cacheRead": 0,
10901
10926
  "cacheWrite": 0
10902
10927
  },
10903
- "contextWindow": 200000,
10928
+ "contextWindow": 1048576,
10904
10929
  "maxTokens": 64000
10905
10930
  },
10906
10931
  "kimi-k3-low": {
@@ -10919,7 +10944,7 @@
10919
10944
  "cacheRead": 0,
10920
10945
  "cacheWrite": 0
10921
10946
  },
10922
- "contextWindow": 200000,
10947
+ "contextWindow": 1048576,
10923
10948
  "maxTokens": 64000
10924
10949
  },
10925
10950
  "kimi-k3-max": {
@@ -10938,7 +10963,7 @@
10938
10963
  "cacheRead": 0,
10939
10964
  "cacheWrite": 0
10940
10965
  },
10941
- "contextWindow": 200000,
10966
+ "contextWindow": 1048576,
10942
10967
  "maxTokens": 64000
10943
10968
  }
10944
10969
  },
@@ -66259,6 +66284,179 @@
66259
66284
  "minLevel": "minimal",
66260
66285
  "maxLevel": "xhigh"
66261
66286
  }
66287
+ },
66288
+ "deepseek-v4-flash-vision-exp": {
66289
+ "id": "deepseek-v4-flash-vision-exp",
66290
+ "name": "DeepSeek V4 Flash Vision Exp",
66291
+ "api": "openai-completions",
66292
+ "provider": "opencode-go",
66293
+ "baseUrl": "https://opencode.ai/zen/go/v1",
66294
+ "reasoning": true,
66295
+ "input": [
66296
+ "text",
66297
+ "image"
66298
+ ],
66299
+ "cost": {
66300
+ "input": 0.22,
66301
+ "output": 0.66,
66302
+ "cacheRead": 0.007,
66303
+ "cacheWrite": 0
66304
+ },
66305
+ "contextWindow": 1000000,
66306
+ "maxTokens": 384000,
66307
+ "thinking": {
66308
+ "mode": "effort",
66309
+ "minLevel": "minimal",
66310
+ "maxLevel": "xhigh"
66311
+ }
66312
+ },
66313
+ "glm-5.3-flash": {
66314
+ "id": "glm-5.3-flash",
66315
+ "name": "GLM-5.3-Flash (2x usage)",
66316
+ "api": "openai-completions",
66317
+ "provider": "opencode-go",
66318
+ "baseUrl": "https://opencode.ai/zen/go/v1",
66319
+ "reasoning": true,
66320
+ "input": [
66321
+ "text",
66322
+ "image"
66323
+ ],
66324
+ "cost": {
66325
+ "input": 0.075,
66326
+ "output": 0.25,
66327
+ "cacheRead": 0.015,
66328
+ "cacheWrite": 0
66329
+ },
66330
+ "contextWindow": 1000000,
66331
+ "maxTokens": 131072,
66332
+ "thinking": {
66333
+ "mode": "effort",
66334
+ "minLevel": "minimal",
66335
+ "maxLevel": "xhigh"
66336
+ }
66337
+ },
66338
+ "grok-4.6": {
66339
+ "id": "grok-4.6",
66340
+ "name": "Grok 4.6",
66341
+ "api": "openai-responses",
66342
+ "provider": "opencode-go",
66343
+ "baseUrl": "https://opencode.ai/zen/go/v1",
66344
+ "reasoning": true,
66345
+ "input": [
66346
+ "text",
66347
+ "image"
66348
+ ],
66349
+ "cost": {
66350
+ "input": 2,
66351
+ "output": 6,
66352
+ "cacheRead": 0.5,
66353
+ "cacheWrite": 0
66354
+ },
66355
+ "contextWindow": 500000,
66356
+ "maxTokens": 500000,
66357
+ "thinking": {
66358
+ "mode": "effort",
66359
+ "minLevel": "minimal",
66360
+ "maxLevel": "xhigh"
66361
+ }
66362
+ },
66363
+ "hy4-preview": {
66364
+ "id": "hy4-preview",
66365
+ "name": "Hy4 preview",
66366
+ "api": "openai-completions",
66367
+ "provider": "opencode-go",
66368
+ "baseUrl": "https://opencode.ai/zen/go/v1",
66369
+ "reasoning": true,
66370
+ "input": [
66371
+ "text"
66372
+ ],
66373
+ "cost": {
66374
+ "input": 0.834,
66375
+ "output": 2.501,
66376
+ "cacheRead": 0.042,
66377
+ "cacheWrite": 0
66378
+ },
66379
+ "contextWindow": 1024000,
66380
+ "maxTokens": 64000,
66381
+ "thinking": {
66382
+ "mode": "effort",
66383
+ "minLevel": "minimal",
66384
+ "maxLevel": "xhigh"
66385
+ }
66386
+ },
66387
+ "longcat-2.0": {
66388
+ "id": "longcat-2.0",
66389
+ "name": "LongCat-2.0",
66390
+ "api": "openai-completions",
66391
+ "provider": "opencode-go",
66392
+ "baseUrl": "https://opencode.ai/zen/go/v1",
66393
+ "reasoning": true,
66394
+ "input": [
66395
+ "text"
66396
+ ],
66397
+ "cost": {
66398
+ "input": 0.3,
66399
+ "output": 1.2,
66400
+ "cacheRead": 0.006,
66401
+ "cacheWrite": 0
66402
+ },
66403
+ "contextWindow": 1000000,
66404
+ "maxTokens": 131072,
66405
+ "thinking": {
66406
+ "mode": "effort",
66407
+ "minLevel": "minimal",
66408
+ "maxLevel": "xhigh"
66409
+ }
66410
+ },
66411
+ "muse-spark-1.2-contributor": {
66412
+ "id": "muse-spark-1.2-contributor",
66413
+ "name": "Muse Spark 1.2 Contributor",
66414
+ "api": "openai-responses",
66415
+ "provider": "opencode-go",
66416
+ "baseUrl": "https://opencode.ai/zen/go/v1",
66417
+ "reasoning": true,
66418
+ "input": [
66419
+ "text",
66420
+ "image"
66421
+ ],
66422
+ "cost": {
66423
+ "input": 0.1,
66424
+ "output": 0.2,
66425
+ "cacheRead": 0.002,
66426
+ "cacheWrite": 0
66427
+ },
66428
+ "contextWindow": 1048576,
66429
+ "maxTokens": 131072,
66430
+ "thinking": {
66431
+ "mode": "effort",
66432
+ "minLevel": "minimal",
66433
+ "maxLevel": "xhigh"
66434
+ }
66435
+ },
66436
+ "qwen3.8-flash": {
66437
+ "id": "qwen3.8-flash",
66438
+ "name": "Qwen3.8 Flash",
66439
+ "api": "anthropic-messages",
66440
+ "provider": "opencode-go",
66441
+ "baseUrl": "https://opencode.ai/zen/go",
66442
+ "reasoning": true,
66443
+ "input": [
66444
+ "text",
66445
+ "image"
66446
+ ],
66447
+ "cost": {
66448
+ "input": 0.15,
66449
+ "output": 0.47,
66450
+ "cacheRead": 0.016,
66451
+ "cacheWrite": 0.2
66452
+ },
66453
+ "contextWindow": 1000000,
66454
+ "maxTokens": 131072,
66455
+ "thinking": {
66456
+ "mode": "budget",
66457
+ "minLevel": "minimal",
66458
+ "maxLevel": "xhigh"
66459
+ }
66262
66460
  }
66263
66461
  },
66264
66462
  "opencode-zen": {