@gajae-code/ai 0.4.5 → 0.5.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 (43) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/dist/types/index.d.ts +2 -0
  3. package/dist/types/providers/amazon-bedrock.d.ts +29 -5
  4. package/dist/types/providers/composer-discipline.d.ts +27 -0
  5. package/dist/types/providers/cursor.d.ts +1 -1
  6. package/dist/types/providers/google-gemini-cli.d.ts +1 -1
  7. package/dist/types/providers/google-shared.d.ts +11 -1
  8. package/dist/types/providers/ollama.d.ts +36 -1
  9. package/dist/types/providers/openai-completions-compat.d.ts +3 -1
  10. package/dist/types/providers/register-builtins.d.ts +3 -3
  11. package/dist/types/types.d.ts +25 -3
  12. package/dist/types/usage/grok-cli.d.ts +10 -0
  13. package/dist/types/utils/event-stream.d.ts +6 -1
  14. package/dist/types/utils/oauth/xai.d.ts +10 -3
  15. package/dist/types/utils/tool-choice-capability.d.ts +41 -0
  16. package/package.json +2 -2
  17. package/src/auth-storage.ts +3 -0
  18. package/src/index.ts +2 -0
  19. package/src/model-thinking.ts +9 -0
  20. package/src/models.json +116 -0
  21. package/src/models.ts +33 -7
  22. package/src/provider-models/descriptors.ts +1 -1
  23. package/src/provider-models/openai-compat.ts +9 -1
  24. package/src/providers/amazon-bedrock.ts +145 -60
  25. package/src/providers/anthropic.ts +85 -32
  26. package/src/providers/azure-openai-responses.ts +44 -3
  27. package/src/providers/composer-discipline.ts +38 -0
  28. package/src/providers/cursor.ts +10 -3
  29. package/src/providers/google-gemini-cli.ts +69 -10
  30. package/src/providers/google-shared.ts +61 -12
  31. package/src/providers/ollama.ts +60 -4
  32. package/src/providers/openai-codex-responses.ts +151 -2
  33. package/src/providers/openai-completions-compat.ts +9 -1
  34. package/src/providers/openai-completions.ts +46 -6
  35. package/src/providers/openai-request-transform.ts +1 -0
  36. package/src/providers/openai-responses.ts +54 -5
  37. package/src/providers/register-builtins.ts +5 -6
  38. package/src/rate-limit-utils.ts +11 -2
  39. package/src/types.ts +37 -3
  40. package/src/usage/grok-cli.ts +163 -0
  41. package/src/utils/event-stream.ts +35 -5
  42. package/src/utils/oauth/xai.ts +49 -13
  43. package/src/utils/tool-choice-capability.ts +220 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,36 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.5.1] - 2026-06-14
6
+
7
+ ### Fixed
8
+
9
+ - Classified model/message limit exhaustion as persistent usage-limit errors so hosts fail fast or switch credentials instead of leaving sessions in an unbounded retry/working state.
10
+
11
+ ## [0.5.0] - 2026-06-13
12
+
13
+ ### Added
14
+
15
+ - Added a generic tool-choice capability model: `toolChoiceSupport` compat enum (`none`/`auto`/`required`/`named`) available on every forced-choice-capable API, derived from the legacy `supportsToolChoice`/`supportsForcedToolChoice` booleans when absent, with a shared `resolveToolChoice` helper that clamps requested tool choices (`named` → `required` → omit) and returns structured degradation metadata.
16
+ - Added a transparent one-shot fallback for forced `tool_choice` 400s ("tool_choice forces tool use is not compatible with this model" and equivalents): transports retry once without the forced field at a pre-content streaming boundary, record the discovery in an in-memory per-process incapability registry, and emit an internal non-rendered `toolChoiceIncapability` event. Applies to Anthropic, OpenAI Completions/Responses, Azure Responses, OpenAI code Responses, Bedrock (including event-stream `validationException`), Ollama, Google, and Gemini CLI transports.
17
+ - Added bundled catalog entries for `kimi-code/kimi-k2.7-code`, `minimax-code/minimax-v3`, and `xai/grok-composer-2.5-fast`.
18
+ - Added composer-harness anchor/edit discipline injection for Cursor Composer and Grok Composer models so provider-specific coding harness priors do not override GJC hashline/edit contracts.
19
+
20
+ ### Removed
21
+
22
+ - Removed the retired `anthropic/claude-fable-5` bundled catalog entry.
23
+
24
+ ### Changed
25
+
26
+ - Moved the Claude Mythos forced-tool-use incapability knowledge out of Anthropic request code into catalog compat defaults (`toolChoiceSupport: "auto"`), applied during catalog generation, dynamic discovery, and bundled-model loading via a shared predicate.
27
+ - Google `toolConfig` mapping now sends `FunctionCallingConfig` mode `ANY` for both `required` and `any` requests instead of silently relaxing `required` to `AUTO`.
28
+ - Optimized `EventStream` queue draining with a head-indexed queue to avoid repeated array shifts in hot streaming paths.
29
+ - Clarified lazy builtin provider registration as the main provider loading path.
30
+
31
+ ### Fixed
32
+
33
+ - Stripped `OpenAI-Beta` in the `openai-proxy` request transform profile so OpenAI-compatible proxies do not receive SDK beta headers.
34
+
5
35
  ## [0.4.5] - 2026-06-12
6
36
 
7
37
  ### Changed
@@ -10,7 +40,7 @@
10
40
 
11
41
  ### Fixed
12
42
 
13
- - Fixed direct Anthropic requests for Claude Fable/Mythos-style models that support tools but reject forced tool use by omitting forced `tool_choice` while preserving `auto`/`none` choices.
43
+ - Fixed direct Anthropic requests for Claude Mythos-style models that support tools but reject forced tool use by omitting forced `tool_choice` while preserving `auto`/`none` choices.
14
44
  - Preserved catalog transport metadata for opencode-go `qwen3.7-max` model resolution.
15
45
  - Set SQLite auth-store `busy_timeout` before enabling WAL so initialization is reliable under contention.
16
46
  - Resolved provider credentials from inherited or GJC-owned environment sources instead of trusting the caller project's `.env` overlays.
@@ -33,6 +33,7 @@ export * from "./usage/claude";
33
33
  export * from "./usage/gemini";
34
34
  export * from "./usage/github-copilot";
35
35
  export * from "./usage/google-antigravity";
36
+ export * from "./usage/grok-cli";
36
37
  export * from "./usage/kimi";
37
38
  export * from "./usage/minimax-code";
38
39
  export * from "./usage/openai-codex";
@@ -46,4 +47,5 @@ export type { OAuthCredentials, OAuthProvider, OAuthProviderId, OAuthProviderInf
46
47
  export * from "./utils/overflow";
47
48
  export * from "./utils/retry";
48
49
  export * from "./utils/schema";
50
+ export * from "./utils/tool-choice-capability";
49
51
  export * from "./utils/validation";
@@ -7,15 +7,12 @@
7
7
  * Bun's native `HTTPS_PROXY` support.
8
8
  */
9
9
  import type { Effort } from "../model-thinking";
10
- import type { StreamFunction, StreamOptions, ThinkingBudgets } from "../types";
10
+ import type { StreamFunction, StreamOptions, ThinkingBudgets, Tool, ToolChoice } from "../types";
11
11
  export type BedrockThinkingDisplay = "summarized" | "omitted";
12
12
  export interface BedrockOptions extends StreamOptions {
13
13
  region?: string;
14
14
  profile?: string;
15
- toolChoice?: "auto" | "any" | "none" | {
16
- type: "tool";
17
- name: string;
18
- };
15
+ toolChoice?: ToolChoice;
19
16
  reasoning?: Effort;
20
17
  thinkingBudgets?: ThinkingBudgets;
21
18
  interleavedThinking?: boolean;
@@ -33,4 +30,31 @@ export interface BedrockOptions extends StreamOptions {
33
30
  */
34
31
  thinkingDisplay?: BedrockThinkingDisplay;
35
32
  }
33
+ interface WireToolSpec {
34
+ toolSpec: {
35
+ name: string;
36
+ description: string;
37
+ inputSchema: {
38
+ json: unknown;
39
+ };
40
+ };
41
+ }
42
+ interface WireToolChoice {
43
+ auto?: Record<string, never>;
44
+ any?: Record<string, never>;
45
+ tool?: {
46
+ name: string;
47
+ };
48
+ }
49
+ interface WireToolConfig {
50
+ tools: WireToolSpec[];
51
+ toolChoice?: WireToolChoice;
52
+ }
36
53
  export declare const streamBedrock: StreamFunction<"bedrock-converse-stream">;
54
+ export declare function stripBedrockForcedToolChoiceForRetry<T extends {
55
+ toolConfig?: {
56
+ toolChoice?: unknown;
57
+ };
58
+ }>(body: T): T;
59
+ export declare function convertToolConfig(tools: Tool[] | undefined, toolChoice: BedrockOptions["toolChoice"]): WireToolConfig | undefined;
60
+ export {};
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Anchor/edit discipline for composer-harness models (xai grok-composer-*,
3
+ * cursor composer-*).
4
+ *
5
+ * Composer models are trained on a proprietary coding-agent harness
6
+ * (Cursor / Grok Build) and carry habits that break this agent's hashline
7
+ * edit workflow when driven through a generic provider. Observed in live
8
+ * sessions with grok-composer-2.5-fast:
9
+ *
10
+ * - they print files with shell commands (`sed -n`, `cat`, `grep -n`) or
11
+ * python heredocs whose output carries NO line anchors, then FABRICATE the
12
+ * 2-char anchor hash the edit tool requires (e.g. guessed "617hp" where
13
+ * the file had "617ca" → "Edit rejected: N anchors do not match");
14
+ * - they mutate files out-of-band via python heredocs (pathlib write_text /
15
+ * str.replace), which invalidates every previously seen anchor and defeats
16
+ * the read-cache snapshot that powers stale-anchor recovery;
17
+ * - they arithmetically renumber anchors after their own edits instead of
18
+ * copying them from the latest tool output;
19
+ * - they leak reasoning prose into heredoc bodies, producing shell/python
20
+ * syntax errors.
21
+ *
22
+ * This prompt is the per-request countermeasure, pinned ahead of the host
23
+ * system prompt on both the openai-completions path and the cursor RPC path.
24
+ */
25
+ /** Matches composer-harness model ids on any provider (xai grok-composer-*, cursor composer-*). */
26
+ export declare function isComposerHarnessModel(modelId: string): boolean;
27
+ export declare const COMPOSER_EDIT_DISCIPLINE_PROMPT = "File-editing discipline for this harness (this OVERRIDES contrary habits from your training):\n\n- Read file contents ONLY with the provided read/search tools. NEVER print files through shell commands (sed, cat, awk, head, grep) or scripts \u2014 that output carries no line anchors, and the edit tool accepts ONLY anchors.\n- Modify files ONLY with the provided edit/write tools. NEVER mutate files through shell redirection, sed -i, or inline python scripts \u2014 out-of-band writes invalidate every known anchor and break edit recovery.\n- A line anchor (e.g. \"42sr\") is a line number plus a 2-char content hash. You CANNOT compute the hash yourself: copy anchors verbatim from the MOST RECENT read/search/edit output of that exact file. NEVER guess, renumber, or arithmetically shift an anchor.\n- After ANY edit to a file (including your own), anchors you saw earlier are stale. Re-read the edited region, or copy the fresh anchors printed in the edit result, before issuing the next edit.\n- If an edit is rejected with \"anchors do not match\", the rejection message prints the current lines WITH fresh anchors. Retry using exactly those printed anchors.\n- A shell command string must contain only the command itself. NEVER interleave reasoning or commentary into command strings or heredocs.";
@@ -34,7 +34,7 @@ export declare function resolveExecHandler<TArgs, TResult>(args: TArgs, handler:
34
34
  * When no system prompts are provided, returns a single default greeting so we never emit
35
35
  * an empty `rootPromptMessagesJson` head.
36
36
  */
37
- export declare function buildCursorSystemPromptJsons(systemPrompt: readonly string[] | undefined): string[];
37
+ export declare function buildCursorSystemPromptJsons(systemPrompt: readonly string[] | undefined, modelId?: string): string[];
38
38
  /** Exported for tests: decodes Cursor history blobs built from conversation messages. */
39
39
  export declare function buildCursorHistoryForTest(messages: Message[]): {
40
40
  rootPromptMessagesJson: unknown[];
@@ -7,7 +7,7 @@ import { type GoogleThinkingLevel } from "./google-shared";
7
7
  */
8
8
  export type { GoogleThinkingLevel };
9
9
  export interface GoogleGeminiCliOptions extends StreamOptions {
10
- toolChoice?: "auto" | "none" | "any";
10
+ toolChoice?: "auto" | "none" | "any" | "required";
11
11
  /**
12
12
  * Thinking/reasoning configuration.
13
13
  * - Gemini 2.x models: use `budgetTokens` to set the thinking budget
@@ -19,7 +19,7 @@ export type GoogleThinkingLevel = "THINKING_LEVEL_UNSPECIFIED" | "MINIMAL" | "LO
19
19
  * `google-gemini-cli` uses a different transport and request shape — do not extend this for it.
20
20
  */
21
21
  export interface GoogleSharedStreamOptions extends StreamOptions {
22
- toolChoice?: "auto" | "none" | "any";
22
+ toolChoice?: "auto" | "none" | "any" | "required";
23
23
  thinking?: {
24
24
  enabled: boolean;
25
25
  budgetTokens?: number;
@@ -161,3 +161,13 @@ export declare function streamGoogleGenAI<T extends "google-generative-ai" | "go
161
161
  retainTextSignature?: boolean;
162
162
  prepare: () => GoogleGenAIRequestPlan | Promise<GoogleGenAIRequestPlan>;
163
163
  }): AssistantMessageEventStream;
164
+ /**
165
+ * Lift the SDK's `params.config` fields out of `config` and place them where the
166
+ * Gemini / Vertex AI REST API expects them on the request body. Mirrors the
167
+ * generateContentParametersTo{Mldev,Vertex} transformation in @google/genai
168
+ * for the subset of fields this codebase actually sets.
169
+ *
170
+ * `abortSignal` is intentionally dropped — the SDK propagates it via `fetch.signal`,
171
+ * which our caller already wires up through `options.signal`.
172
+ */
173
+ export declare function paramsToWireBody(params: GenerateContentParameters): Record<string, unknown>;
@@ -1,6 +1,41 @@
1
- import type { StreamFunction, StreamOptions, ToolChoice } from "../types";
1
+ import type { Context, Model, StreamFunction, StreamOptions, ToolChoice } from "../types";
2
2
  export interface OllamaChatOptions extends StreamOptions {
3
3
  reasoning?: "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
4
4
  toolChoice?: ToolChoice;
5
5
  }
6
+ type OllamaFunctionTool = {
7
+ type: "function";
8
+ function: {
9
+ name: string;
10
+ description: string;
11
+ parameters: Record<string, unknown>;
12
+ };
13
+ };
14
+ type OllamaMessage = {
15
+ role: "system" | "user" | "assistant" | "tool";
16
+ content: string;
17
+ images?: string[];
18
+ thinking?: string;
19
+ tool_calls?: Array<{
20
+ type: "function";
21
+ function: {
22
+ index?: number;
23
+ name: string;
24
+ arguments: Record<string, unknown>;
25
+ };
26
+ }>;
27
+ tool_name?: string;
28
+ };
29
+ export declare function createChatBody(model: Model<"ollama-chat">, context: Context, options: OllamaChatOptions | undefined): {
30
+ model: string;
31
+ messages: OllamaMessage[];
32
+ tools?: OllamaFunctionTool[] | undefined;
33
+ think?: "high" | "low" | "medium" | boolean | undefined;
34
+ tool_choice?: "auto" | "none" | "required" | undefined;
35
+ options?: {
36
+ num_predict: number;
37
+ } | undefined;
38
+ stream: boolean;
39
+ };
6
40
  export declare const streamOllama: StreamFunction<"ollama-chat">;
41
+ export {};
@@ -1,10 +1,12 @@
1
1
  import type { Model, OpenAICompat } from "../types";
2
2
  type ResolvedToolStrictMode = NonNullable<OpenAICompat["toolStrictMode"]> | "mixed";
3
- export type ResolvedOpenAICompat = Required<Omit<OpenAICompat, "openRouterRouting" | "vercelGatewayRouting" | "extraBody" | "toolStrictMode">> & {
3
+ export type ResolvedOpenAICompat = Required<Omit<OpenAICompat, "openRouterRouting" | "vercelGatewayRouting" | "extraBody" | "toolStrictMode" | "toolChoiceSupport">> & {
4
4
  openRouterRouting?: OpenAICompat["openRouterRouting"];
5
5
  vercelGatewayRouting?: OpenAICompat["vercelGatewayRouting"];
6
6
  extraBody?: OpenAICompat["extraBody"];
7
7
  toolStrictMode: ResolvedToolStrictMode;
8
+ /** Optional explicit capability override; resolved via deriveToolChoiceSupport. */
9
+ toolChoiceSupport?: OpenAICompat["toolChoiceSupport"];
8
10
  };
9
11
  /**
10
12
  * Detect compatibility settings from provider and baseUrl for known providers.
@@ -6,9 +6,9 @@
6
6
  * openai) at startup. The loaded module promise is cached so subsequent calls
7
7
  * reuse the same import.
8
8
  *
9
- * NOTE: stream.ts currently imports providers directly, so this file is not yet
10
- * wired into the main streaming path. It provides the infrastructure for lazy
11
- * loading that can be integrated when stream.ts is refactored.
9
+ * stream.ts imports its provider stream functions from this module (see the
10
+ * lazy wrappers below), so this file IS the main streaming path's provider
11
+ * loader: heavy SDKs stay out of the CLI startup parse graph.
12
12
  */
13
13
  import type { AssistantMessageEventStream, Context, Model, OptionsForApi } from "../types";
14
14
  import { AssistantMessageEventStream as EventStreamImpl } from "../utils/event-stream";
@@ -68,6 +68,16 @@ export type ToolChoice = "auto" | "none" | "any" | "required" | {
68
68
  type: "tool";
69
69
  name: string;
70
70
  };
71
+ export type ToolChoiceSupport = "none" | "auto" | "required" | "named";
72
+ export type ToolChoiceSupportSource = "static" | "derived" | "runtime";
73
+ export interface ToolChoiceCompat {
74
+ /** Maximum supported tool_choice level. */
75
+ toolChoiceSupport?: ToolChoiceSupport;
76
+ /** Legacy flag for accepting the tool_choice parameter. */
77
+ supportsToolChoice?: boolean;
78
+ /** Legacy flag for forced tool_choice support. */
79
+ supportsForcedToolChoice?: boolean;
80
+ }
71
81
  export type CacheRetention = "none" | "short" | "long";
72
82
  /**
73
83
  * Service tier hint for processing priority / cost control.
@@ -576,12 +586,22 @@ export type AssistantMessageEvent = {
576
586
  contentIndex?: undefined;
577
587
  reason: Extract<StopReason, "aborted" | "error">;
578
588
  error: AssistantMessage;
589
+ } | {
590
+ type: "toolChoiceIncapability";
591
+ contentIndex?: undefined;
592
+ api: string;
593
+ provider: string;
594
+ model: string;
595
+ requestedLevel: ToolChoiceSupport;
596
+ resolvedLevel: ToolChoiceSupport;
597
+ reason: string;
598
+ registryKey: string;
579
599
  };
580
600
  /**
581
601
  * Compatibility settings for openai-completions API.
582
602
  * Use this to override URL-based auto-detection for custom providers.
583
603
  */
584
- export interface OpenAICompat {
604
+ export interface OpenAICompat extends ToolChoiceCompat {
585
605
  /** Whether the provider supports the `store` field. Default: auto-detected from URL. */
586
606
  supportsStore?: boolean;
587
607
  /** Whether the provider supports the `developer` role (vs `system`). Default: auto-detected from URL. */
@@ -627,6 +647,8 @@ export interface OpenAICompat {
627
647
  requiresAssistantContentForToolCalls?: boolean;
628
648
  /** Whether the provider supports the `tool_choice` parameter. Default: true. */
629
649
  supportsToolChoice?: boolean;
650
+ /** Whether `tool_choice` may force a tool (`required` / named tool). Default: true. */
651
+ supportsForcedToolChoice?: boolean;
630
652
  /**
631
653
  * Drop reasoning fields (`reasoning_effort`, OpenRouter `reasoning`) for
632
654
  * the request when `tool_choice` forces a tool call. Mirrors the Anthropic
@@ -658,7 +680,7 @@ export interface OpenAICompat {
658
680
  * Use this to disable features that strict-by-default Anthropic accepts but
659
681
  * that proxy gateways (Vertex AI, AWS Bedrock-style fronts, etc.) reject.
660
682
  */
661
- export interface AnthropicCompat {
683
+ export interface AnthropicCompat extends ToolChoiceCompat {
662
684
  /**
663
685
  * Drop the top-level `strict: true` field on tool definitions. Vertex AI's
664
686
  * Anthropic-compatible endpoint rejects unknown tool fields with
@@ -770,7 +792,7 @@ export interface Model<TApi extends Api = any> {
770
792
  /** Canonical thinking capability metadata for this model. */
771
793
  thinking?: ThinkingConfig;
772
794
  /** Compatibility overrides per API. If not set, auto-detected from baseUrl. */
773
- compat?: TApi extends "openai-completions" | "openai-responses" ? OpenAICompat : TApi extends "anthropic-messages" ? AnthropicCompat : never;
795
+ compat?: TApi extends "openai-completions" | "openai-responses" ? OpenAICompat : TApi extends "anthropic-messages" ? AnthropicCompat : TApi extends "bedrock-converse-stream" | "google-generative-ai" | "google-gemini-cli" | "google-vertex" | "ollama-chat" | "azure-openai-responses" | "openai-codex-responses" ? ToolChoiceCompat : never;
774
796
  /**
775
797
  * Which shape to use when exposing the OpenAI code backend `apply_patch` tool to this model.
776
798
  * Generated catalog policy sets `"freeform"` for first-party GPT-5 Responses
@@ -0,0 +1,10 @@
1
+ import type { CredentialRankingStrategy, UsageProvider } from "../usage";
2
+ interface BillingUsage {
3
+ monthlyLimit: number;
4
+ used: number;
5
+ billingPeriodEnd: string;
6
+ }
7
+ export declare function parseGrokCliBillingUsage(payload: unknown): BillingUsage;
8
+ export declare const grokCliUsageProvider: UsageProvider;
9
+ export declare const grokCliRankingStrategy: CredentialRankingStrategy;
10
+ export {};
@@ -1,7 +1,6 @@
1
1
  import type { AssistantMessage, AssistantMessageEvent } from "../types";
2
2
  export declare class EventStream<T, R = T> implements AsyncIterable<T> {
3
3
  #private;
4
- queue: T[];
5
4
  waiting: Array<{
6
5
  resolve: (value: IteratorResult<T>) => void;
7
6
  reject: (err: unknown) => void;
@@ -13,6 +12,12 @@ export declare class EventStream<T, R = T> implements AsyncIterable<T> {
13
12
  isComplete: (event: T) => boolean;
14
13
  extractResult: (event: T) => R;
15
14
  constructor(isComplete: (event: T) => boolean, extractResult: (event: T) => R);
15
+ /**
16
+ * Read-only snapshot of the not-yet-consumed events. Always a fresh copy:
17
+ * external code can never mutate internal queue state or observe head-index
18
+ * tombstones, so the deque cannot desynchronize.
19
+ */
20
+ get queue(): T[];
16
21
  push(event: T): void;
17
22
  deliver(event: T): void;
18
23
  end(result?: R): void;
@@ -8,16 +8,23 @@ interface XaiDiscovery {
8
8
  authorizationEndpoint: string;
9
9
  tokenEndpoint: string;
10
10
  }
11
+ export interface XaiOAuthFlowOptions {
12
+ extraAuthorizeParams?: Readonly<Record<string, string>>;
13
+ }
14
+ export interface XaiOAuthRefreshOptions {
15
+ signal?: AbortSignal;
16
+ extraTokenParams?: Readonly<Record<string, string>>;
17
+ }
11
18
  export declare function discoverXaiOAuthEndpoints(signal?: AbortSignal): Promise<XaiDiscovery>;
12
19
  export declare class XaiOAuthFlow extends OAuthCallbackFlow {
13
20
  #private;
14
- constructor(ctrl: OAuthController);
21
+ constructor(ctrl: OAuthController, options?: XaiOAuthFlowOptions);
15
22
  generateAuthUrl(state: string, redirectUri: string): Promise<{
16
23
  url: string;
17
24
  instructions?: string;
18
25
  }>;
19
26
  exchangeToken(code: string, _state: string, redirectUri: string): Promise<OAuthCredentials>;
20
27
  }
21
- export declare function loginXai(ctrl: OAuthController): Promise<OAuthCredentials>;
22
- export declare function refreshXaiToken(refreshToken: string, signal?: AbortSignal): Promise<OAuthCredentials>;
28
+ export declare function loginXai(ctrl: OAuthController, options?: XaiOAuthFlowOptions): Promise<OAuthCredentials>;
29
+ export declare function refreshXaiToken(refreshToken: string, options?: AbortSignal | XaiOAuthRefreshOptions): Promise<OAuthCredentials>;
23
30
  export {};
@@ -0,0 +1,41 @@
1
+ import type { Api, Model, ToolChoice, ToolChoiceCompat, ToolChoiceSupport, ToolChoiceSupportSource } from "../types";
2
+ /**
3
+ * Claude Mythos accepts tools but rejects forced tool use (Anthropic 400:
4
+ * "tool_choice forces tool use is not compatible with this model"). Catalog
5
+ * generation and dynamic discovery use this to default `toolChoiceSupport`.
6
+ */
7
+ export declare function isClaudeForcedToolChoiceIncapableModelId(modelId: string): boolean;
8
+ /** Derives the effective static tool-choice support from compatibility flags. */
9
+ export declare function deriveToolChoiceSupport(compat: ToolChoiceCompat | undefined): {
10
+ support: ToolChoiceSupport;
11
+ source: "static" | "derived";
12
+ };
13
+ /** Returns the registry key used for runtime tool-choice capability overrides. */
14
+ export declare function toolChoiceRegistryKey(model: Model<Api>): string;
15
+ /** Returns the current runtime tool-choice capability override for a model. */
16
+ export declare function getToolChoiceCapabilityOverride(model: Model<Api>): ToolChoiceSupport | undefined;
17
+ /** Clears runtime tool-choice capability overrides for tests. */
18
+ export declare function clearToolChoiceIncapabilityRegistryForTests(): void;
19
+ /** Records a discovered maximum supported tool-choice level for a model. */
20
+ export declare function markToolChoiceIncapability(model: Model<Api>, maxSupport: ToolChoiceSupport, reason?: string): void;
21
+ /**
22
+ * Resolves a requested tool_choice against static and runtime capability limits.
23
+ * `compat` overrides `model.compat` for transports that layer URL/provider
24
+ * detection on top of explicit model overrides (e.g. resolveOpenAICompat).
25
+ */
26
+ export declare function resolveToolChoice(model: Model<Api>, requested: ToolChoice | undefined, compat?: ToolChoiceCompat): ResolveToolChoiceResult;
27
+ /** Detects provider errors indicating forced tool_choice is unsupported. */
28
+ export declare function isForcedToolChoiceUnsupportedError(error: unknown, sentForcedToolChoice: boolean): boolean;
29
+ export type { ToolChoiceCompat, ToolChoiceSupport, ToolChoiceSupportSource } from "../types";
30
+ export interface ResolveToolChoiceResult {
31
+ requestedChoice: ToolChoice | undefined;
32
+ requestedLevel: ToolChoiceSupport;
33
+ resolvedChoice: ToolChoice | undefined;
34
+ resolvedLevel: ToolChoiceSupport;
35
+ support: ToolChoiceSupport;
36
+ supportSource: ToolChoiceSupportSource;
37
+ degraded: boolean;
38
+ reason?: string;
39
+ registryKey: string;
40
+ targetToolName?: string;
41
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/ai",
4
- "version": "0.4.5",
4
+ "version": "0.5.1",
5
5
  "description": "Unified LLM API with automatic model discovery and provider configuration",
6
6
  "homepage": "https://gaebal-gajae.dev",
7
7
  "author": "Yeachan-Heo",
@@ -43,7 +43,7 @@
43
43
  "dependencies": {
44
44
  "@anthropic-ai/sdk": "^0.94.0",
45
45
  "@bufbuild/protobuf": "^2.12.0",
46
- "@gajae-code/utils": "0.4.5",
46
+ "@gajae-code/utils": "0.5.1",
47
47
  "openai": "^6.36.0",
48
48
  "partial-json": "^0.1.7",
49
49
  "zod": "4.4.3"
@@ -27,6 +27,7 @@ import { claudeRankingStrategy, claudeUsageProvider } from "./usage/claude";
27
27
  import { googleGeminiCliUsageProvider } from "./usage/gemini";
28
28
  import { githubCopilotUsageProvider } from "./usage/github-copilot";
29
29
  import { antigravityUsageProvider } from "./usage/google-antigravity";
30
+ import { grokCliRankingStrategy, grokCliUsageProvider } from "./usage/grok-cli";
30
31
  import { kimiUsageProvider } from "./usage/kimi";
31
32
  import { codexRankingStrategy, openaiCodexUsageProvider } from "./usage/openai-codex";
32
33
  import { zaiUsageProvider } from "./usage/zai";
@@ -370,6 +371,7 @@ const DEFAULT_USAGE_PROVIDERS: UsageProvider[] = [
370
371
  claudeUsageProvider,
371
372
  zaiUsageProvider,
372
373
  githubCopilotUsageProvider,
374
+ grokCliUsageProvider,
373
375
  ];
374
376
 
375
377
  const DEFAULT_USAGE_PROVIDER_MAP = new Map<Provider, UsageProvider>(
@@ -498,6 +500,7 @@ function resolveDefaultUsageProvider(provider: Provider): UsageProvider | undefi
498
500
  const DEFAULT_RANKING_STRATEGIES = new Map<Provider, CredentialRankingStrategy>([
499
501
  ["openai-codex", codexRankingStrategy],
500
502
  ["anthropic", claudeRankingStrategy],
503
+ ["grok-build", grokCliRankingStrategy],
501
504
  ]);
502
505
 
503
506
  function resolveDefaultRankingStrategy(provider: Provider): CredentialRankingStrategy | undefined {
package/src/index.ts CHANGED
@@ -33,6 +33,7 @@ export * from "./usage/claude";
33
33
  export * from "./usage/gemini";
34
34
  export * from "./usage/github-copilot";
35
35
  export * from "./usage/google-antigravity";
36
+ export * from "./usage/grok-cli";
36
37
  export * from "./usage/kimi";
37
38
  export * from "./usage/minimax-code";
38
39
  export * from "./usage/openai-codex";
@@ -51,4 +52,5 @@ export type {
51
52
  export * from "./utils/overflow";
52
53
  export * from "./utils/retry";
53
54
  export * from "./utils/schema";
55
+ export * from "./utils/tool-choice-capability";
54
56
  export * from "./utils/validation";
@@ -1,5 +1,6 @@
1
1
  import { resolveOpenAICompat } from "./providers/openai-completions-compat";
2
2
  import type { Api, Model as ApiModel, ThinkingConfig } from "./types";
3
+ import { isClaudeForcedToolChoiceIncapableModelId } from "./utils/tool-choice-capability";
3
4
 
4
5
  /** User-facing thinking levels, ordered least to most intensive. */
5
6
  export const enum Effort {
@@ -382,6 +383,14 @@ function applyGeneratedModelPolicy(model: ApiModel<Api>): void {
382
383
  } else {
383
384
  delete model.applyPatchToolType;
384
385
  }
386
+ if (
387
+ (model.api === "anthropic-messages" || model.api === "bedrock-converse-stream") &&
388
+ isClaudeForcedToolChoiceIncapableModelId(model.id)
389
+ ) {
390
+ // Claude Mythos accepts tools but rejects forced tool use (Anthropic
391
+ // 400: "tool_choice forces tool use is not compatible with this model").
392
+ model.compat = { ...(model.compat ?? {}), toolChoiceSupport: "auto" } as typeof model.compat;
393
+ }
385
394
  if (parsedModel.family === "anthropic") {
386
395
  applyAnthropicCatalogPolicy(model, parsedModel);
387
396
  }
package/src/models.json CHANGED
@@ -18021,6 +18021,40 @@
18021
18021
  "minLevel": "minimal",
18022
18022
  "maxLevel": "high"
18023
18023
  }
18024
+ },
18025
+ "kimi-k2.7-code": {
18026
+ "id": "kimi-k2.7-code",
18027
+ "name": "Kimi K2.7 Code",
18028
+ "api": "openai-completions",
18029
+ "provider": "kimi-code",
18030
+ "baseUrl": "https://api.kimi.com/coding/v1",
18031
+ "headers": {
18032
+ "User-Agent": "KimiCLI/1.0",
18033
+ "X-Msh-Platform": "kimi_cli"
18034
+ },
18035
+ "reasoning": true,
18036
+ "input": [
18037
+ "text",
18038
+ "image"
18039
+ ],
18040
+ "cost": {
18041
+ "input": 0,
18042
+ "output": 0,
18043
+ "cacheRead": 0,
18044
+ "cacheWrite": 0
18045
+ },
18046
+ "contextWindow": 262144,
18047
+ "maxTokens": 65536,
18048
+ "compat": {
18049
+ "thinkingFormat": "zai",
18050
+ "reasoningContentField": "reasoning_content",
18051
+ "supportsDeveloperRole": false
18052
+ },
18053
+ "thinking": {
18054
+ "mode": "effort",
18055
+ "minLevel": "minimal",
18056
+ "maxLevel": "high"
18057
+ }
18024
18058
  }
18025
18059
  },
18026
18060
  "litellm": {
@@ -35153,6 +35187,37 @@
35153
35187
  "minLevel": "minimal",
35154
35188
  "maxLevel": "high"
35155
35189
  }
35190
+ },
35191
+ "minimax-v3": {
35192
+ "id": "minimax-v3",
35193
+ "name": "MiniMax-V3",
35194
+ "api": "openai-completions",
35195
+ "provider": "minimax-code",
35196
+ "baseUrl": "https://api.minimax.io/v1",
35197
+ "reasoning": true,
35198
+ "input": [
35199
+ "text",
35200
+ "image"
35201
+ ],
35202
+ "cost": {
35203
+ "input": 0,
35204
+ "output": 0,
35205
+ "cacheRead": 0,
35206
+ "cacheWrite": 0
35207
+ },
35208
+ "contextWindow": 512000,
35209
+ "maxTokens": 128000,
35210
+ "compat": {
35211
+ "supportsStore": false,
35212
+ "supportsDeveloperRole": false,
35213
+ "supportsReasoningEffort": false,
35214
+ "reasoningContentField": "reasoning_content"
35215
+ },
35216
+ "thinking": {
35217
+ "mode": "effort",
35218
+ "minLevel": "minimal",
35219
+ "maxLevel": "high"
35220
+ }
35156
35221
  }
35157
35222
  },
35158
35223
  "minimax-code-cn": {
@@ -70520,6 +70585,33 @@
70520
70585
  "maxLevel": "high"
70521
70586
  }
70522
70587
  },
70588
+ "grok-composer-2.5-fast": {
70589
+ "id": "grok-composer-2.5-fast",
70590
+ "name": "Grok Composer 2.5 Fast",
70591
+ "api": "openai-completions",
70592
+ "provider": "xai",
70593
+ "baseUrl": "https://api.x.ai/v1",
70594
+ "reasoning": true,
70595
+ "input": [
70596
+ "text"
70597
+ ],
70598
+ "cost": {
70599
+ "input": 0,
70600
+ "output": 0,
70601
+ "cacheRead": 0,
70602
+ "cacheWrite": 0
70603
+ },
70604
+ "contextWindow": 200000,
70605
+ "maxTokens": 64000,
70606
+ "compat": {
70607
+ "supportsReasoningEffort": false
70608
+ },
70609
+ "thinking": {
70610
+ "mode": "effort",
70611
+ "minLevel": "minimal",
70612
+ "maxLevel": "high"
70613
+ }
70614
+ },
70523
70615
  "grok-vision-beta": {
70524
70616
  "id": "grok-vision-beta",
70525
70617
  "name": "Grok Vision Beta",
@@ -70956,6 +71048,30 @@
70956
71048
  "maxLevel": "xhigh"
70957
71049
  }
70958
71050
  },
71051
+ "glm-5.2": {
71052
+ "id": "glm-5.2",
71053
+ "name": "GLM-5.2",
71054
+ "api": "anthropic-messages",
71055
+ "provider": "zai",
71056
+ "baseUrl": "https://api.z.ai/api/anthropic",
71057
+ "reasoning": true,
71058
+ "input": [
71059
+ "text"
71060
+ ],
71061
+ "cost": {
71062
+ "input": 0,
71063
+ "output": 0,
71064
+ "cacheRead": 0,
71065
+ "cacheWrite": 0
71066
+ },
71067
+ "contextWindow": 200000,
71068
+ "maxTokens": 131072,
71069
+ "thinking": {
71070
+ "mode": "budget",
71071
+ "minLevel": "minimal",
71072
+ "maxLevel": "xhigh"
71073
+ }
71074
+ },
70959
71075
  "glm-5v-turbo": {
70960
71076
  "id": "glm-5v-turbo",
70961
71077
  "name": "GLM-5V-Turbo",