@ottocode/sdk 0.1.314 → 0.1.315

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 (31) hide show
  1. package/package.json +1 -1
  2. package/src/config/src/index.ts +2 -1
  3. package/src/config/src/manager.ts +1 -0
  4. package/src/core/src/providers/resolver.ts +1 -2
  5. package/src/core/src/tools/builtin/fs/edit.txt +1 -1
  6. package/src/core/src/tools/builtin/fs/index.ts +2 -0
  7. package/src/core/src/tools/builtin/fs/multiedit.txt +1 -1
  8. package/src/core/src/tools/builtin/glob.txt +4 -0
  9. package/src/core/src/tools/builtin/patch/indentation.ts +8 -1
  10. package/src/core/src/tools/builtin/patch/normalize.ts +4 -0
  11. package/src/core/src/tools/builtin/patch/repair.ts +42 -0
  12. package/src/core/src/tools/builtin/patch.txt +2 -0
  13. package/src/core/src/tools/builtin/search.txt +4 -0
  14. package/src/core/src/tools/builtin/shell.ts +46 -10
  15. package/src/core/src/tools/builtin/shell.txt +5 -0
  16. package/src/core/src/tools/loader.ts +8 -4
  17. package/src/index.ts +2 -5
  18. package/src/prompts/src/agents/build.txt +2 -2
  19. package/src/prompts/src/providers/{moonshot.txt → kimi.txt} +1 -1
  20. package/src/prompts/src/providers.ts +5 -5
  21. package/src/providers/src/catalog-manual.ts +41 -9
  22. package/src/providers/src/catalog.ts +74 -34
  23. package/src/providers/src/env.ts +4 -7
  24. package/src/providers/src/index.ts +3 -9
  25. package/src/providers/src/{moonshot-client.ts → kimi-client.ts} +131 -15
  26. package/src/providers/src/model-merge.ts +7 -1
  27. package/src/providers/src/pricing.ts +1 -1
  28. package/src/providers/src/registry.ts +8 -19
  29. package/src/providers/src/utils.ts +7 -8
  30. package/src/types/src/config.ts +1 -0
  31. package/src/types/src/provider.ts +4 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ottocode/sdk",
3
- "version": "0.1.314",
3
+ "version": "0.1.315",
4
4
  "description": "AI agent SDK for building intelligent assistants - tree-shakable and comprehensive",
5
5
  "author": "nitishxyz",
6
6
  "license": "MIT",
@@ -25,7 +25,7 @@ const DEFAULT_PROVIDER_SETTINGS: OttoConfig['providers'] = {
25
25
  xai: { enabled: false },
26
26
  zai: { enabled: false },
27
27
  'zai-coding': { enabled: false },
28
- moonshot: { enabled: false },
28
+ kimi: { enabled: false },
29
29
  minimax: { enabled: false },
30
30
  };
31
31
 
@@ -42,6 +42,7 @@ const DEFAULTS: {
42
42
  reasoningText: true,
43
43
  reasoningLevel: 'high',
44
44
  theme: 'dark',
45
+ tuiTheme: 'tokyo-night',
45
46
  vimMode: false,
46
47
  compactThread: true,
47
48
  fontFamily: 'IBM Plex Mono',
@@ -86,6 +86,7 @@ export async function writeDefaults(
86
86
  reasoningText: boolean;
87
87
  reasoningLevel: 'minimal' | 'low' | 'medium' | 'high' | 'max' | 'xhigh';
88
88
  theme: string;
89
+ tuiTheme: string;
89
90
  vimMode: boolean;
90
91
  compactThread: boolean;
91
92
  fontFamily: string;
@@ -33,7 +33,6 @@ export type ProviderName =
33
33
  | 'xai'
34
34
  | 'zai'
35
35
  | 'zai-coding'
36
- | 'moonshot'
37
36
  | 'kimi'
38
37
  | 'minimax';
39
38
 
@@ -222,7 +221,7 @@ export async function resolveModel(
222
221
  });
223
222
  }
224
223
 
225
- if (provider === 'moonshot' || provider === 'kimi') {
224
+ if (provider === 'kimi') {
226
225
  return createKimiModel(model, {
227
226
  apiKey: config.apiKey,
228
227
  baseURL: config.baseURL,
@@ -1,6 +1,6 @@
1
1
  Replace an exact text block in an existing file.
2
2
 
3
- Use this for targeted edits instead of structural patch-style editing whenever possible.
3
+ Prefer `apply_patch` for most code/text edits when it is available, because it produces the clearest diff preview. Use `edit` when you have one precise old/new text replacement, when a patch would be awkward, or after patch attempts fail.
4
4
 
5
5
  Rules:
6
6
  - You must read the file first in the current session before editing it.
@@ -9,6 +9,8 @@ import { buildTreeTool } from './tree.ts';
9
9
  import { buildPwdTool } from './pwd.ts';
10
10
  import { buildCdTool } from './cd.ts';
11
11
 
12
+ export { rememberFileRead } from './read-tracker.ts';
13
+
12
14
  export function buildFsTools(
13
15
  projectRoot: string,
14
16
  ): Array<{ name: string; tool: Tool }> {
@@ -1,6 +1,6 @@
1
1
  Apply multiple exact text replacements to a single existing file atomically.
2
2
 
3
- Use this when you need several edits in one file.
3
+ Prefer `apply_patch` for most code/text edits when it is available, especially when a diff is easy to express. Use `multiedit` when you have several precise old/new replacements in one file, when a patch would be awkward, or after patch attempts fail.
4
4
 
5
5
  Rules:
6
6
  - Read the file first before editing.
@@ -6,8 +6,12 @@
6
6
 
7
7
  **Use `glob` first to discover files** before reading them, unless you already know exact paths.
8
8
 
9
+ Think of `glob` as the repository's fast local `find` replacement for filenames and paths. Use it before shelling out to `find`, `fd`, or `ls **`.
10
+
9
11
  ## Usage tips
10
12
 
11
13
  - Use `glob` for filename patterns; use `search` for file contents.
12
14
  - Combine with `path` to restrict the search to a subdirectory.
13
15
  - Prefer reading a known file directly over globbing to "find" it (check the `<project>` listing in the system prompt first).
16
+ - Instead of `find packages -name "*.ts"`, call `glob` with `pattern: "packages/**/*.ts"`.
17
+ - Instead of `find apps -name package.json`, call `glob` with `pattern: "apps/**/package.json"`.
@@ -23,6 +23,7 @@ export function adjustReplacementIndentation(
23
23
  let fileIndentChar: 'tab' | 'space' = 'space';
24
24
  const deltas: number[] = [];
25
25
  let hasAddStyleMismatch = false;
26
+ let hasContextContentMismatch = false;
26
27
  let fileIndentDetected = false;
27
28
 
28
29
  for (const fl of matchedFileLines) {
@@ -81,6 +82,7 @@ export function adjustReplacementIndentation(
81
82
  if (line.kind === 'context') {
82
83
  const fileLine = matchedFileLines[expectedIdx];
83
84
  if (fileLine !== undefined) {
85
+ if (line.content !== fileLine) hasContextContentMismatch = true;
84
86
  lastDelta = computeIndentDelta(line.content, fileLine, tabSize);
85
87
  lastFileIndentExpanded = expandWhitespace(
86
88
  getLeadingWhitespace(fileLine),
@@ -152,7 +154,12 @@ export function adjustReplacementIndentation(
152
154
  }
153
155
  }
154
156
 
155
- if (!hasDelta && !hasStyleMismatch && !hasAddStyleMismatch) {
157
+ if (
158
+ !hasDelta &&
159
+ !hasStyleMismatch &&
160
+ !hasAddStyleMismatch &&
161
+ !hasContextContentMismatch
162
+ ) {
156
163
  return hunk.lines.filter((l) => l.kind !== 'remove').map((l) => l.content);
157
164
  }
158
165
 
@@ -3,6 +3,7 @@ enum NormalizationLevel {
3
3
  TABS_ONLY = 'tabs',
4
4
  WHITESPACE = 'whitespace',
5
5
  AGGRESSIVE = 'aggressive',
6
+ COLLAPSED = 'collapsed',
6
7
  }
7
8
 
8
9
  const DEFAULT_TAB_SIZE = 2;
@@ -22,6 +23,8 @@ export function normalizeWhitespace(
22
23
  return line.replace(/\t/g, tabReplacement).replace(/\s+$/, '');
23
24
  case NormalizationLevel.AGGRESSIVE:
24
25
  return line.replace(/\t/g, tabReplacement).trim();
26
+ case NormalizationLevel.COLLAPSED:
27
+ return line.replace(/\t/g, tabReplacement).trim().replace(/\s+/g, ' ');
25
28
  default:
26
29
  return line;
27
30
  }
@@ -32,6 +35,7 @@ export const NORMALIZATION_LEVELS: NormalizationLevel[] = [
32
35
  NormalizationLevel.TABS_ONLY,
33
36
  NormalizationLevel.WHITESPACE,
34
37
  NormalizationLevel.AGGRESSIVE,
38
+ NormalizationLevel.COLLAPSED,
35
39
  ];
36
40
 
37
41
  export function getLeadingWhitespace(line: string): string {
@@ -13,10 +13,30 @@ import {
13
13
 
14
14
  export function repairPatchContent(patch: string): string {
15
15
  patch = extractPatchFromWrappedJson(patch);
16
+ patch = extractEnvelopedPatchFromText(patch);
17
+ patch = stripTrailingMarkdownFenceBeforeMissingEndMarker(patch);
16
18
  patch = appendMissingEndMarker(patch);
19
+ patch = trimAfterEndMarker(patch);
17
20
  return patch;
18
21
  }
19
22
 
23
+ function looksLikeUnifiedPatch(patch: string): boolean {
24
+ const trimmed = patch.trimStart();
25
+ return (
26
+ trimmed.startsWith('diff --git ') ||
27
+ trimmed.startsWith('--- ') ||
28
+ trimmed.startsWith('Index: ')
29
+ );
30
+ }
31
+
32
+ function extractEnvelopedPatchFromText(patch: string): string {
33
+ const beginIndex = patch.indexOf(PATCH_BEGIN_MARKER);
34
+ if (beginIndex === -1) return patch;
35
+ if (beginIndex === patch.search(/\S/)) return patch;
36
+ if (looksLikeUnifiedPatch(patch)) return patch;
37
+ return patch.slice(beginIndex);
38
+ }
39
+
20
40
  function extractPatchFromWrappedJson(patch: string): string {
21
41
  if (patch.includes(PATCH_BEGIN_MARKER)) return patch;
22
42
 
@@ -61,3 +81,25 @@ function appendMissingEndMarker(patch: string): string {
61
81
 
62
82
  return patch;
63
83
  }
84
+
85
+ function stripTrailingMarkdownFenceBeforeMissingEndMarker(
86
+ patch: string,
87
+ ): string {
88
+ const trimmed = patch.trimEnd();
89
+ if (!trimmed.trimStart().startsWith(PATCH_BEGIN_MARKER)) return patch;
90
+ if (trimmed.includes(PATCH_END_MARKER)) return patch;
91
+ const lines = trimmed.split('\n');
92
+ const last = lines.at(-1)?.trim();
93
+ if (last !== '```') return patch;
94
+ return lines.slice(0, -1).join('\n');
95
+ }
96
+
97
+ function trimAfterEndMarker(patch: string): string {
98
+ if (!patch.trimStart().startsWith(PATCH_BEGIN_MARKER)) return patch;
99
+ const endIndex = patch.indexOf(PATCH_END_MARKER);
100
+ if (endIndex === -1) return patch;
101
+ const endOfMarker = endIndex + PATCH_END_MARKER.length;
102
+ const suffix = patch.slice(endOfMarker);
103
+ if (suffix.trim().length === 0) return patch;
104
+ return patch.slice(0, endOfMarker);
105
+ }
@@ -1,5 +1,7 @@
1
1
  Apply a patch to modify one or more files.
2
2
 
3
+ Prefer this as the first-choice editing tool for code and text changes when it is available. Use exact-replacement tools (`edit`/`multiedit`) when a patch would be awkward, when you only need a precise old/new string replacement, or after patch attempts fail.
4
+
3
5
  Use the **enveloped format** by default. Standard unified diffs (`---` / `+++`) are also accepted.
4
6
 
5
7
  ## Fastest / safest mode (recommended): Replace
@@ -5,6 +5,8 @@
5
5
 
6
6
  Use this for text/code search across the codebase. It is the primary tool for repository content discovery.
7
7
 
8
+ Think of `search` as the repository's fast local `rg`/`grep` replacement: use it for exact string and regex searches before reaching for shell commands.
9
+
8
10
  ## Usage tips
9
11
 
10
12
  - Narrow broad searches with `path` and `glob` values.
@@ -12,3 +14,5 @@ Use this for text/code search across the codebase. It is the primary tool for re
12
14
  - Batch independent searches (e.g. multiple function names) in a single turn for parallel execution.
13
15
  - Use `ignoreCase: true` for case-insensitive matching; pass `glob` patterns (e.g. `["*.ts", "*.tsx"]`) to limit file types.
14
16
  - For filename/path discovery, use `glob` first when you already know the file pattern.
17
+ - Instead of `grep -r "workspace:" packages apps`, call `search` with `query: "workspace:"`, `path: "."`, and `glob: ["**/package.json"]`.
18
+ - Instead of `rg "function foo" src`, call `search` with `query: "function foo"` and `path: "src"`.
@@ -46,6 +46,7 @@ function killProcessTree(pid: number) {
46
46
  }
47
47
 
48
48
  const REDIRECTED_SEARCH_COMMANDS = new Set(['grep', 'egrep', 'fgrep', 'rg']);
49
+ const REDIRECTED_GLOB_COMMANDS = new Set(['find', 'fd']);
49
50
 
50
51
  /**
51
52
  * Detect commands that start with a standalone grep-style search binary.
@@ -53,23 +54,55 @@ const REDIRECTED_SEARCH_COMMANDS = new Set(['grep', 'egrep', 'fgrep', 'rg']);
53
54
  * with grep/rg are redirected to the dedicated `search` tool.
54
55
  */
55
56
  export function findRedirectedSearchCommand(cmd: string): string | null {
57
+ return findRepositoryDiscoveryCommand(cmd, 'search')?.command ?? null;
58
+ }
59
+
60
+ function commandTokens(segment: string): string[] {
61
+ const tokens = segment.trim().split(/\s+/).filter(Boolean);
62
+ let index = 0;
63
+ while (
64
+ index < tokens.length &&
65
+ /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[index] ?? '')
66
+ ) {
67
+ index++;
68
+ }
69
+ if (tokens[index] === 'command') index++;
70
+ return tokens.slice(index);
71
+ }
72
+
73
+ function findRepositoryDiscoveryCommand(
74
+ cmd: string,
75
+ kind?: 'search' | 'glob',
76
+ ): { command: string; tool: 'search' | 'glob' } | null {
56
77
  const segments = cmd.split(/&&|\|\||;|\n/);
57
78
  for (const segment of segments) {
58
- const tokens = segment.trim().split(/\s+/);
59
- let index = 0;
60
- while (
61
- index < tokens.length &&
62
- /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[index] ?? '')
63
- ) {
64
- index++;
79
+ const tokens = commandTokens(segment);
80
+ const bin = tokens[0]?.split('/').pop() ?? '';
81
+ const second = tokens[1] ?? '';
82
+ if ((!kind || kind === 'search') && bin === 'git' && second === 'grep') {
83
+ return { command: 'git grep', tool: 'search' };
84
+ }
85
+ if ((!kind || kind === 'search') && REDIRECTED_SEARCH_COMMANDS.has(bin)) {
86
+ return { command: bin, tool: 'search' };
87
+ }
88
+ if ((!kind || kind === 'glob') && REDIRECTED_GLOB_COMMANDS.has(bin)) {
89
+ return { command: bin, tool: 'glob' };
90
+ }
91
+ if ((!kind || kind === 'glob') && bin === 'ls' && segment.includes('**')) {
92
+ return { command: 'ls **', tool: 'glob' };
65
93
  }
66
- if (tokens[index] === 'command') index++;
67
- const bin = tokens[index]?.split('/').pop() ?? '';
68
- if (REDIRECTED_SEARCH_COMMANDS.has(bin)) return bin;
69
94
  }
70
95
  return null;
71
96
  }
72
97
 
98
+ function repositoryDiscoveryHint(cmd: string): string | undefined {
99
+ const discovery = findRepositoryDiscoveryCommand(cmd);
100
+ if (!discovery) return undefined;
101
+ return discovery.tool === 'search'
102
+ ? `Tip: For repository content search, prefer the search tool instead of shelling out to ${discovery.command}. It is indexed, faster, and returns structured file:line matches.`
103
+ : `Tip: For repository file discovery, prefer the glob tool instead of shelling out to ${discovery.command}. It returns structured paths and skips common build/cache folders.`;
104
+ }
105
+
73
106
  export type ShellOutputMode = 'auto' | 'full' | 'tail';
74
107
 
75
108
  const DEFAULT_TAIL_LINES = 100;
@@ -136,6 +169,7 @@ type ShellResult = ToolResponse<{
136
169
  stderrTruncated?: boolean;
137
170
  stderrOriginalBytes?: number;
138
171
  stderrShownBytes?: number;
172
+ discoveryHint?: string;
139
173
  }>;
140
174
 
141
175
  type ShellStreamChunk =
@@ -446,11 +480,13 @@ export function buildShellTool(projectRoot: string): {
446
480
  return;
447
481
  }
448
482
 
483
+ const discoveryHint = repositoryDiscoveryHint(finalCmd);
449
484
  settle({
450
485
  ok: true,
451
486
  exitCode: exitCode ?? 0,
452
487
  stdout,
453
488
  stderr,
489
+ ...(discoveryHint ? { discoveryHint } : {}),
454
490
  ...(outputMode === 'tail' || outputMode === 'auto'
455
491
  ? { outputMode, tailLines, maxOutputBytes }
456
492
  : { outputMode, maxOutputBytes }),
@@ -8,6 +8,11 @@ For repository discovery, use `search` for content/code search and `glob` for fi
8
8
 
9
9
  **Strongly prefer the `search` tool over `grep`/`rg`, and `glob` over `find`.** The `search` tool is indexed and faster, returns structured `file:line` matches, and supports regex, `glob` includes, `path` scoping, and `ignoreCase` — use it for all repository content search. Only fall back to `grep`/`rg` via shell for cases `search` cannot handle (e.g. gitignored files like node_modules or build output). Pipelines that filter program output (e.g. `ps aux | grep node`) are fine.
10
10
 
11
+ Mapping from common shell habits:
12
+ - `grep -r` / `rg` / `git grep` over repo files → use `search`.
13
+ - `find` / `fd` / recursive `ls` for filenames → use `glob`.
14
+ - Build, test, package manager, diagnostics, process inspection → use `shell`.
15
+
11
16
  ## Usage tips
12
17
 
13
18
  - Chain commands with `&&` to fail-fast.
@@ -142,7 +142,14 @@ async function discoverStaticProjectTools(
142
142
 
143
143
  const discoveryPromise = (async () => {
144
144
  const tools = new Map<string, Tool>();
145
- for (const { name, tool } of buildFsTools(projectRoot))
145
+ const fsTools = buildFsTools(projectRoot);
146
+ for (const { name, tool } of fsTools.filter(({ name }) => name === 'read'))
147
+ tools.set(name, tool);
148
+ // Put apply_patch before exact replacement tools so models see it as the
149
+ // default editing path after reading files.
150
+ const ap = buildApplyPatchTool(projectRoot);
151
+ tools.set(ap.name, ap.tool);
152
+ for (const { name, tool } of fsTools.filter(({ name }) => name !== 'read'))
146
153
  tools.set(name, tool);
147
154
  for (const { name, tool } of buildGitTools(projectRoot))
148
155
  tools.set(name, tool);
@@ -155,9 +162,6 @@ async function discoverStaticProjectTools(
155
162
  tools.set(search.name, search.tool);
156
163
  const glob = buildGlobTool(projectRoot);
157
164
  tools.set(glob.name, glob.tool);
158
- // Patch/apply
159
- const ap = buildApplyPatchTool(projectRoot);
160
- tools.set(ap.name, ap.tool);
161
165
  // Todo tracking
162
166
  tools.set('update_todos', updateTodosTool);
163
167
  // Web search
package/src/index.ts CHANGED
@@ -81,6 +81,7 @@ export type {
81
81
  } from './providers/src/index.ts';
82
82
  export {
83
83
  isBuiltInProviderId,
84
+ resolveBuiltInProviderCatalogId,
84
85
  getProviderSettings,
85
86
  getProviderDefinition,
86
87
  hasConfiguredProvider,
@@ -162,13 +163,9 @@ export { createOpencodeModel } from './providers/src/index.ts';
162
163
  export type { OpencodeProviderConfig } from './providers/src/index.ts';
163
164
  export {
164
165
  createKimiModel,
165
- createMoonshotModel,
166
166
  readKimiApiKeyFromEnv,
167
167
  } from './providers/src/index.ts';
168
- export type {
169
- KimiProviderConfig,
170
- MoonshotProviderConfig,
171
- } from './providers/src/index.ts';
168
+ export type { KimiProviderConfig } from './providers/src/index.ts';
172
169
  export { createMinimaxModel } from './providers/src/index.ts';
173
170
  export type { MinimaxProviderConfig } from './providers/src/index.ts';
174
171
  export {
@@ -8,8 +8,8 @@ You help with coding and build tasks.
8
8
 
9
9
  Pick the right tool for the job (each tool's description has its full contract):
10
10
 
11
- - Use the exact-replacement editing tools available to you for targeted changes in existing files.
12
- - Use patch-style editing for structural diffs, file add/delete/rename, or multi-file changes when that capability is available.
11
+ - Prefer `apply_patch` for code and text changes when it is available. It gives the clearest diff preview and handles targeted, structural, and multi-file edits.
12
+ - Use exact-replacement tools (`edit`/`multiedit`) when a patch would be awkward, when you have a precise replacement block, or after patch attempts fail.
13
13
  - Use `write` only for NEW files or >70% full-file rewrites. Never use it for targeted edits.
14
14
 
15
15
  **Always read a file immediately before editing it.** Memory and earlier context are not reliable — the file may have changed.
@@ -1,4 +1,4 @@
1
- You are Kimi, an agentic coding assistant by Moonshot AI operating in otto in Thinking mode. Precise, safe, helpful.
1
+ You are Kimi, an agentic coding assistant operating in otto in Thinking mode. Precise, safe, helpful.
2
2
 
3
3
  # Reasoning
4
4
 
@@ -14,7 +14,7 @@ import PROVIDER_ANTHROPIC from './providers/anthropic.txt' with {
14
14
  // eslint-disable-next-line @typescript-eslint/consistent-type-imports
15
15
  import PROVIDER_GOOGLE from './providers/google.txt' with { type: 'text' };
16
16
  // eslint-disable-next-line @typescript-eslint/consistent-type-imports
17
- import PROVIDER_MOONSHOT from './providers/moonshot.txt' with { type: 'text' };
17
+ import PROVIDER_KIMI from './providers/kimi.txt' with { type: 'text' };
18
18
  // eslint-disable-next-line @typescript-eslint/consistent-type-imports
19
19
  import PROVIDER_DEFAULT from './providers/default.txt' with { type: 'text' };
20
20
  // eslint-disable-next-line @typescript-eslint/consistent-type-imports
@@ -24,7 +24,7 @@ const FAMILY_PROMPTS: Record<string, string> = {
24
24
  openai: PROVIDER_OPENAI,
25
25
  anthropic: PROVIDER_ANTHROPIC,
26
26
  google: PROVIDER_GOOGLE,
27
- moonshot: PROVIDER_MOONSHOT,
27
+ kimi: PROVIDER_KIMI,
28
28
  glm: PROVIDER_GLM,
29
29
  minimax: PROVIDER_DEFAULT,
30
30
  };
@@ -142,9 +142,9 @@ export async function providerBasePrompt(
142
142
  const result = PROVIDER_GOOGLE.trim();
143
143
  return { prompt: result, resolvedType: 'google' };
144
144
  }
145
- if (id === 'moonshot') {
146
- const result = PROVIDER_MOONSHOT.trim();
147
- return { prompt: result, resolvedType: 'moonshot' };
145
+ if (id === 'kimi') {
146
+ const result = PROVIDER_KIMI.trim();
147
+ return { prompt: result, resolvedType: 'kimi' };
148
148
  }
149
149
  if (id === 'zai' || id === 'zai-coding') {
150
150
  const result = PROVIDER_GLM.trim();
@@ -46,7 +46,7 @@ const OWNER_NPM: Record<ModelOwner, string> = {
46
46
  google: '@ai-sdk/google',
47
47
  openrouter: '@openrouter/ai-sdk-provider',
48
48
  xai: '@ai-sdk/xai',
49
- moonshot: '@ai-sdk/openai-compatible',
49
+ kimi: '@ai-sdk/openai-compatible',
50
50
  qwen: '@ai-sdk/openai-compatible',
51
51
  zai: '@ai-sdk/openai-compatible',
52
52
  minimax: '@ai-sdk/anthropic',
@@ -162,21 +162,53 @@ const DEPRECATED_KIMI_MODEL_IDS = new Set([
162
162
  'kimi-k2-turbo-preview',
163
163
  ]);
164
164
 
165
+ const KIMI_MANUAL_MODELS: ModelInfo[] = [
166
+ {
167
+ id: 'kimi-k2.7-code-highspeed',
168
+ ownedBy: 'kimi',
169
+ label: 'Kimi K2.7 Code Highspeed',
170
+ modalities: { input: ['text', 'image', 'video'], output: ['text'] },
171
+ toolCall: true,
172
+ reasoningText: true,
173
+ attachment: true,
174
+ temperature: false,
175
+ knowledge: '2025-01',
176
+ openWeights: true,
177
+ cost: { input: 1.9, output: 8, cacheRead: 0.38 },
178
+ limit: { context: 262_144, output: 262_144 },
179
+ },
180
+ ];
181
+
165
182
  export function filterAvailableKimiModels(models: ModelInfo[]): ModelInfo[] {
166
183
  return models.filter((model) => !DEPRECATED_KIMI_MODEL_IDS.has(model.id));
167
184
  }
168
185
 
186
+ function appendKimiManualModels(models: ModelInfo[]): ModelInfo[] {
187
+ const manualById = new Map(
188
+ KIMI_MANUAL_MODELS.map((model) => [model.id, model]),
189
+ );
190
+ const mergedModels = models.map((model) => {
191
+ const override = manualById.get(model.id);
192
+ return override ? { ...model, ...override } : model;
193
+ });
194
+ const existingIds = new Set(mergedModels.map((model) => model.id));
195
+ const missingModels = KIMI_MANUAL_MODELS.filter(
196
+ (model) => !existingIds.has(model.id),
197
+ );
198
+ return missingModels.length
199
+ ? [...mergedModels, ...missingModels]
200
+ : mergedModels;
201
+ }
202
+
169
203
  export function applyOfficialKimiCatalogMetadata<
170
204
  T extends ProviderCatalogEntry,
171
205
  >(entry: T | undefined): T | undefined {
172
206
  if (!entry) return undefined;
173
- const env = Array.from(
174
- new Set(['KIMI_API_KEY', 'MOONSHOT_API_KEY', ...(entry.env ?? [])]),
175
- );
207
+ const env = Array.from(new Set(['KIMI_API_KEY', ...(entry.env ?? [])]));
176
208
  return {
177
209
  ...entry,
178
- models: filterAvailableKimiModels(entry.models),
179
- label: entry.label === 'Moonshot AI' ? 'Kimi' : entry.label,
210
+ models: appendKimiManualModels(filterAvailableKimiModels(entry.models)),
211
+ label: 'Kimi',
180
212
  env,
181
213
  doc: 'https://platform.kimi.ai/docs/api/overview.md',
182
214
  };
@@ -195,9 +227,9 @@ export function mergeManualCatalog(
195
227
  if (xaiEntry) {
196
228
  merged.xai = xaiEntry;
197
229
  }
198
- const moonshotEntry = applyOfficialKimiCatalogMetadata(merged.moonshot);
199
- if (moonshotEntry) {
200
- merged.moonshot = moonshotEntry;
230
+ const kimiEntry = applyOfficialKimiCatalogMetadata(merged.kimi);
231
+ if (kimiEntry) {
232
+ merged.kimi = kimiEntry;
201
233
  }
202
234
  if (manualEntry) {
203
235
  merged[OTTOROUTER_ID] = manualEntry;
@@ -2161,6 +2161,46 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
2161
2161
  output: 32768,
2162
2162
  },
2163
2163
  },
2164
+ {
2165
+ id: 'gemma-4-E2B-it',
2166
+ ownedBy: 'google',
2167
+ label: 'Gemma 4 E2B IT',
2168
+ modalities: {
2169
+ input: ['text', 'image', 'audio'],
2170
+ output: ['text'],
2171
+ },
2172
+ toolCall: true,
2173
+ reasoningText: true,
2174
+ attachment: true,
2175
+ temperature: true,
2176
+ releaseDate: '2026-04-02',
2177
+ lastUpdated: '2026-04-02',
2178
+ openWeights: true,
2179
+ limit: {
2180
+ context: 131072,
2181
+ output: 8192,
2182
+ },
2183
+ },
2184
+ {
2185
+ id: 'gemma-4-E4B-it',
2186
+ ownedBy: 'google',
2187
+ label: 'Gemma 4 E4B IT',
2188
+ modalities: {
2189
+ input: ['text', 'image', 'audio'],
2190
+ output: ['text'],
2191
+ },
2192
+ toolCall: true,
2193
+ reasoningText: true,
2194
+ attachment: true,
2195
+ temperature: true,
2196
+ releaseDate: '2026-04-02',
2197
+ lastUpdated: '2026-04-02',
2198
+ openWeights: true,
2199
+ limit: {
2200
+ context: 131072,
2201
+ output: 8192,
2202
+ },
2203
+ },
2164
2204
  ],
2165
2205
  label: 'Google',
2166
2206
  env: ['GOOGLE_API_KEY', 'GOOGLE_GENERATIVE_AI_API_KEY', 'GEMINI_API_KEY'],
@@ -2329,8 +2369,8 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
2329
2369
  },
2330
2370
  {
2331
2371
  id: '~moonshotai/kimi-latest',
2332
- ownedBy: 'moonshot',
2333
- label: 'MoonshotAI Kimi Latest',
2372
+ ownedBy: 'kimi',
2373
+ label: 'Kimi Latest',
2334
2374
  modalities: {
2335
2375
  input: ['text', 'image'],
2336
2376
  output: ['text'],
@@ -3398,13 +3438,13 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
3398
3438
  lastUpdated: '2026-04-24',
3399
3439
  openWeights: true,
3400
3440
  cost: {
3401
- input: 0.098,
3402
- output: 0.196,
3441
+ input: 0.09,
3442
+ output: 0.18,
3403
3443
  cacheRead: 0.02,
3404
3444
  },
3405
3445
  limit: {
3406
- context: 1048575,
3407
- output: 131072,
3446
+ context: 1000000,
3447
+ output: 65536,
3408
3448
  },
3409
3449
  },
3410
3450
  {
@@ -4353,7 +4393,7 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
4353
4393
  temperature: true,
4354
4394
  releaseDate: '2026-06-01',
4355
4395
  lastUpdated: '2026-06-01',
4356
- openWeights: false,
4396
+ openWeights: true,
4357
4397
  cost: {
4358
4398
  input: 0.3,
4359
4399
  output: 1.2,
@@ -4783,7 +4823,7 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
4783
4823
  },
4784
4824
  {
4785
4825
  id: 'moonshotai/kimi-k2',
4786
- ownedBy: 'moonshot',
4826
+ ownedBy: 'kimi',
4787
4827
  label: 'Kimi K2 0711',
4788
4828
  modalities: {
4789
4829
  input: ['text'],
@@ -4808,7 +4848,7 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
4808
4848
  },
4809
4849
  {
4810
4850
  id: 'moonshotai/kimi-k2-0905',
4811
- ownedBy: 'moonshot',
4851
+ ownedBy: 'kimi',
4812
4852
  label: 'Kimi K2 0905',
4813
4853
  modalities: {
4814
4854
  input: ['text'],
@@ -4833,7 +4873,7 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
4833
4873
  },
4834
4874
  {
4835
4875
  id: 'moonshotai/kimi-k2-thinking',
4836
- ownedBy: 'moonshot',
4876
+ ownedBy: 'kimi',
4837
4877
  label: 'Kimi K2 Thinking',
4838
4878
  modalities: {
4839
4879
  input: ['text'],
@@ -4858,7 +4898,7 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
4858
4898
  },
4859
4899
  {
4860
4900
  id: 'moonshotai/kimi-k2.5',
4861
- ownedBy: 'moonshot',
4901
+ ownedBy: 'kimi',
4862
4902
  label: 'Kimi K2.5',
4863
4903
  modalities: {
4864
4904
  input: ['text', 'image'],
@@ -4883,7 +4923,7 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
4883
4923
  },
4884
4924
  {
4885
4925
  id: 'moonshotai/kimi-k2.6',
4886
- ownedBy: 'moonshot',
4926
+ ownedBy: 'kimi',
4887
4927
  label: 'Kimi K2.6',
4888
4928
  modalities: {
4889
4929
  input: ['text', 'image'],
@@ -4909,7 +4949,7 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
4909
4949
  },
4910
4950
  {
4911
4951
  id: 'moonshotai/kimi-k2.7-code',
4912
- ownedBy: 'moonshot',
4952
+ ownedBy: 'kimi',
4913
4953
  label: 'Kimi K2.7 Code',
4914
4954
  modalities: {
4915
4955
  input: ['text', 'image'],
@@ -9722,7 +9762,7 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
9722
9762
  },
9723
9763
  {
9724
9764
  id: 'kimi-k2',
9725
- ownedBy: 'moonshot',
9765
+ ownedBy: 'kimi',
9726
9766
  label: 'Kimi K2',
9727
9767
  modalities: {
9728
9768
  input: ['text'],
@@ -9748,7 +9788,7 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
9748
9788
  },
9749
9789
  {
9750
9790
  id: 'kimi-k2-thinking',
9751
- ownedBy: 'moonshot',
9791
+ ownedBy: 'kimi',
9752
9792
  label: 'Kimi K2 Thinking',
9753
9793
  modalities: {
9754
9794
  input: ['text'],
@@ -9774,7 +9814,7 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
9774
9814
  },
9775
9815
  {
9776
9816
  id: 'kimi-k2.5',
9777
- ownedBy: 'moonshot',
9817
+ ownedBy: 'kimi',
9778
9818
  label: 'Kimi K2.5',
9779
9819
  modalities: {
9780
9820
  input: ['text', 'image', 'video'],
@@ -9800,7 +9840,7 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
9800
9840
  },
9801
9841
  {
9802
9842
  id: 'kimi-k2.5-free',
9803
- ownedBy: 'moonshot',
9843
+ ownedBy: 'kimi',
9804
9844
  label: 'Kimi K2.5 Free',
9805
9845
  modalities: {
9806
9846
  input: ['text', 'image', 'video'],
@@ -9826,7 +9866,7 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
9826
9866
  },
9827
9867
  {
9828
9868
  id: 'kimi-k2.6',
9829
- ownedBy: 'moonshot',
9869
+ ownedBy: 'kimi',
9830
9870
  label: 'Kimi K2.6',
9831
9871
  modalities: {
9832
9872
  input: ['text', 'image', 'video'],
@@ -11008,12 +11048,12 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
11008
11048
  api: 'https://api.z.ai/api/coding/paas/v4',
11009
11049
  doc: 'https://docs.z.ai/devpack/overview',
11010
11050
  },
11011
- moonshot: {
11012
- id: 'moonshot',
11051
+ kimi: {
11052
+ id: 'kimi',
11013
11053
  models: [
11014
11054
  {
11015
11055
  id: 'kimi-k2-0711-preview',
11016
- ownedBy: 'moonshot',
11056
+ ownedBy: 'kimi',
11017
11057
  label: 'Kimi K2 0711',
11018
11058
  modalities: {
11019
11059
  input: ['text'],
@@ -11039,7 +11079,7 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
11039
11079
  },
11040
11080
  {
11041
11081
  id: 'kimi-k2-0905-preview',
11042
- ownedBy: 'moonshot',
11082
+ ownedBy: 'kimi',
11043
11083
  label: 'Kimi K2 0905',
11044
11084
  modalities: {
11045
11085
  input: ['text'],
@@ -11065,7 +11105,7 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
11065
11105
  },
11066
11106
  {
11067
11107
  id: 'kimi-k2-thinking',
11068
- ownedBy: 'moonshot',
11108
+ ownedBy: 'kimi',
11069
11109
  label: 'Kimi K2 Thinking',
11070
11110
  modalities: {
11071
11111
  input: ['text'],
@@ -11091,7 +11131,7 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
11091
11131
  },
11092
11132
  {
11093
11133
  id: 'kimi-k2-thinking-turbo',
11094
- ownedBy: 'moonshot',
11134
+ ownedBy: 'kimi',
11095
11135
  label: 'Kimi K2 Thinking Turbo',
11096
11136
  modalities: {
11097
11137
  input: ['text'],
@@ -11117,7 +11157,7 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
11117
11157
  },
11118
11158
  {
11119
11159
  id: 'kimi-k2-turbo-preview',
11120
- ownedBy: 'moonshot',
11160
+ ownedBy: 'kimi',
11121
11161
  label: 'Kimi K2 Turbo',
11122
11162
  modalities: {
11123
11163
  input: ['text'],
@@ -11143,7 +11183,7 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
11143
11183
  },
11144
11184
  {
11145
11185
  id: 'kimi-k2.5',
11146
- ownedBy: 'moonshot',
11186
+ ownedBy: 'kimi',
11147
11187
  label: 'Kimi K2.5',
11148
11188
  modalities: {
11149
11189
  input: ['text', 'image', 'video'],
@@ -11169,7 +11209,7 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
11169
11209
  },
11170
11210
  {
11171
11211
  id: 'kimi-k2.6',
11172
- ownedBy: 'moonshot',
11212
+ ownedBy: 'kimi',
11173
11213
  label: 'Kimi K2.6',
11174
11214
  modalities: {
11175
11215
  input: ['text', 'image', 'video'],
@@ -11195,7 +11235,7 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
11195
11235
  },
11196
11236
  {
11197
11237
  id: 'kimi-k2.7-code',
11198
- ownedBy: 'moonshot',
11238
+ ownedBy: 'kimi',
11199
11239
  label: 'Kimi K2.7 Code',
11200
11240
  modalities: {
11201
11241
  input: ['text', 'image', 'video'],
@@ -11221,7 +11261,7 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
11221
11261
  },
11222
11262
  ],
11223
11263
  label: 'Kimi',
11224
- env: ['KIMI_API_KEY', 'MOONSHOT_API_KEY'],
11264
+ env: ['KIMI_API_KEY'],
11225
11265
  npm: '@ai-sdk/openai-compatible',
11226
11266
  api: 'https://api.moonshot.ai/v1',
11227
11267
  doc: 'https://platform.kimi.ai/docs/api/overview.md',
@@ -12313,7 +12353,7 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
12313
12353
  },
12314
12354
  {
12315
12355
  id: 'kimi-k2-thinking',
12316
- ownedBy: 'moonshot',
12356
+ ownedBy: 'kimi',
12317
12357
  label: 'kimi-k2-thinking',
12318
12358
  modalities: {
12319
12359
  input: ['text'],
@@ -12333,7 +12373,7 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
12333
12373
  },
12334
12374
  {
12335
12375
  id: 'kimi-k2:1t',
12336
- ownedBy: 'moonshot',
12376
+ ownedBy: 'kimi',
12337
12377
  label: 'kimi-k2:1t',
12338
12378
  modalities: {
12339
12379
  input: ['text'],
@@ -12353,7 +12393,7 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
12353
12393
  },
12354
12394
  {
12355
12395
  id: 'kimi-k2.5',
12356
- ownedBy: 'moonshot',
12396
+ ownedBy: 'kimi',
12357
12397
  label: 'kimi-k2.5',
12358
12398
  modalities: {
12359
12399
  input: ['text', 'image'],
@@ -12372,7 +12412,7 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
12372
12412
  },
12373
12413
  {
12374
12414
  id: 'kimi-k2.6',
12375
- ownedBy: 'moonshot',
12415
+ ownedBy: 'kimi',
12376
12416
  label: 'kimi-k2.6',
12377
12417
  modalities: {
12378
12418
  input: ['text', 'image'],
@@ -12391,7 +12431,7 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
12391
12431
  },
12392
12432
  {
12393
12433
  id: 'kimi-k2.7-code',
12394
- ownedBy: 'moonshot',
12434
+ ownedBy: 'kimi',
12395
12435
  label: 'kimi-k2.7-code',
12396
12436
  modalities: {
12397
12437
  input: ['text', 'image'],
@@ -1,5 +1,5 @@
1
1
  import type { BuiltInProviderId, ProviderId } from '../../types/src/index.ts';
2
- import { readKimiApiKeyFromEnv } from './moonshot-client.ts';
2
+ import { readKimiApiKeyFromEnv } from './kimi-client.ts';
3
3
 
4
4
  const ENV_VARS: Record<BuiltInProviderId, string> = {
5
5
  openai: 'OPENAI_API_KEY',
@@ -13,19 +13,16 @@ const ENV_VARS: Record<BuiltInProviderId, string> = {
13
13
  xai: 'XAI_API_KEY',
14
14
  zai: 'ZAI_API_KEY',
15
15
  'zai-coding': 'ZAI_CODING_API_KEY',
16
- moonshot: 'KIMI_API_KEY',
16
+ kimi: 'KIMI_API_KEY',
17
17
  minimax: 'MINIMAX_API_KEY',
18
18
  };
19
19
 
20
- const KIMI_PROVIDER_IDS = new Set<ProviderId>(['kimi', 'moonshot']);
21
-
22
20
  export function providerEnvVar(provider: ProviderId): string | undefined {
23
- if (provider === 'kimi') return 'KIMI_API_KEY';
24
21
  return ENV_VARS[provider as BuiltInProviderId];
25
22
  }
26
23
 
27
24
  export function readEnvKey(provider: ProviderId): string | undefined {
28
- if (KIMI_PROVIDER_IDS.has(provider)) {
25
+ if (provider === 'kimi') {
29
26
  const value = readKimiApiKeyFromEnv();
30
27
  return value.length ? value : undefined;
31
28
  }
@@ -48,7 +45,7 @@ export function readEnvKey(provider: ProviderId): string | undefined {
48
45
 
49
46
  export function setEnvKey(provider: ProviderId, value: string | undefined) {
50
47
  if (!value) return;
51
- if (KIMI_PROVIDER_IDS.has(provider)) {
48
+ if (provider === 'kimi') {
52
49
  process.env.KIMI_API_KEY = value;
53
50
  return;
54
51
  }
@@ -52,6 +52,7 @@ export type {
52
52
  } from './ollama-discovery.ts';
53
53
  export {
54
54
  isBuiltInProviderId,
55
+ resolveBuiltInProviderCatalogId,
55
56
  getProviderSettings,
56
57
  getProviderDefinition,
57
58
  hasConfiguredProvider,
@@ -123,15 +124,8 @@ export {
123
124
  export type { OpenRouterProviderConfig } from './openrouter-client.ts';
124
125
  export { createOpencodeModel } from './opencode-client.ts';
125
126
  export type { OpencodeProviderConfig } from './opencode-client.ts';
126
- export {
127
- createKimiModel,
128
- createMoonshotModel,
129
- readKimiApiKeyFromEnv,
130
- } from './moonshot-client.ts';
131
- export type {
132
- KimiProviderConfig,
133
- MoonshotProviderConfig,
134
- } from './moonshot-client.ts';
127
+ export { createKimiModel, readKimiApiKeyFromEnv } from './kimi-client.ts';
128
+ export type { KimiProviderConfig } from './kimi-client.ts';
135
129
  export { createMinimaxModel } from './minimax-client.ts';
136
130
  export type { MinimaxProviderConfig } from './minimax-client.ts';
137
131
  export { createCopilotFetch, createCopilotModel } from './copilot-client.ts';
@@ -8,15 +8,139 @@ export type KimiProviderConfig = {
8
8
  oauth?: OAuth;
9
9
  };
10
10
 
11
- /** @deprecated Use `KimiProviderConfig` */
12
- export type MoonshotProviderConfig = KimiProviderConfig;
13
-
14
11
  export function readKimiApiKeyFromEnv(): string {
15
- return process.env.KIMI_API_KEY || process.env.MOONSHOT_API_KEY || '';
12
+ return process.env.KIMI_API_KEY || '';
13
+ }
14
+
15
+ const KIMI_UNSUPPORTED_SCHEMA_KEYS = new Set([
16
+ '$schema',
17
+ '$id',
18
+ '$ref',
19
+ '$defs',
20
+ 'definitions',
21
+ 'examples',
22
+ 'title',
23
+ 'nullable',
24
+ 'format',
25
+ 'pattern',
26
+ 'minLength',
27
+ 'maxLength',
28
+ 'minimum',
29
+ 'maximum',
30
+ 'exclusiveMinimum',
31
+ 'exclusiveMaximum',
32
+ 'multipleOf',
33
+ 'minItems',
34
+ 'maxItems',
35
+ 'uniqueItems',
36
+ 'contains',
37
+ 'minContains',
38
+ 'maxContains',
39
+ 'prefixItems',
40
+ 'propertyNames',
41
+ 'patternProperties',
42
+ 'allOf',
43
+ 'not',
44
+ 'if',
45
+ 'then',
46
+ 'else',
47
+ 'dependentSchemas',
48
+ 'dependentRequired',
49
+ 'unevaluatedProperties',
50
+ ]);
51
+
52
+ function isRecord(value: unknown): value is Record<string, unknown> {
53
+ return Boolean(value && typeof value === 'object' && !Array.isArray(value));
16
54
  }
17
55
 
18
56
  /**
19
- * Kimi/Moonshot streaming responses report token usage on the final chunk's
57
+ * Converts AI SDK JSON Schema output into the smaller schema dialect accepted
58
+ * by Kimi tool calls.
59
+ */
60
+ export function sanitizeKimiToolSchema(schema: unknown): unknown {
61
+ if (Array.isArray(schema))
62
+ return schema.map((item) => sanitizeKimiToolSchema(item));
63
+ if (!isRecord(schema)) return schema;
64
+
65
+ const out: Record<string, unknown> = {};
66
+
67
+ for (const [key, value] of Object.entries(schema)) {
68
+ if (key === 'const') {
69
+ if (['number', 'string'].includes(typeof value)) out.enum = [value];
70
+ continue;
71
+ }
72
+ if (key === 'oneOf') {
73
+ const branches = toArray(value);
74
+ if (branches.length > 0) out.anyOf = sanitizeKimiToolSchema(branches);
75
+ continue;
76
+ }
77
+ if (key === 'properties' && isRecord(value)) {
78
+ out.properties = Object.fromEntries(
79
+ Object.entries(value).map(([propertyName, propertySchema]) => [
80
+ propertyName,
81
+ sanitizeKimiToolSchema(propertySchema),
82
+ ]),
83
+ );
84
+ continue;
85
+ }
86
+ if (KIMI_UNSUPPORTED_SCHEMA_KEYS.has(key)) continue;
87
+ if (key === 'type' && Array.isArray(value)) {
88
+ const types = value.filter(
89
+ (type): type is string => typeof type === 'string',
90
+ );
91
+ if (types.length === 1) out.type = types[0];
92
+ else if (types.length > 1) {
93
+ out.anyOf = types.map((type) => ({ type }));
94
+ }
95
+ continue;
96
+ }
97
+ if (key === 'enum' && Array.isArray(value)) {
98
+ out.enum = value.filter((item) => item !== null);
99
+ continue;
100
+ }
101
+ out[key] = sanitizeKimiToolSchema(value);
102
+ }
103
+
104
+ return out;
105
+ }
106
+
107
+ function toArray(value: unknown): unknown[] {
108
+ return Array.isArray(value) ? value : [];
109
+ }
110
+
111
+ function sanitizeKimiToolRequest(body: unknown): unknown {
112
+ if (!isRecord(body) || !Array.isArray(body.tools)) return body;
113
+ let changed = false;
114
+ const tools = body.tools.map((tool) => {
115
+ if (!isRecord(tool)) return tool;
116
+ const fn = tool.function;
117
+ if (!isRecord(fn) || !('parameters' in fn)) return tool;
118
+ changed = true;
119
+ return {
120
+ ...tool,
121
+ function: {
122
+ ...fn,
123
+ parameters: sanitizeKimiToolSchema(fn.parameters),
124
+ },
125
+ };
126
+ });
127
+ return changed ? { ...body, tools } : body;
128
+ }
129
+
130
+ function sanitizeKimiFetchInit(init?: RequestInit): RequestInit | undefined {
131
+ if (!init || typeof init.body !== 'string') return init;
132
+ try {
133
+ const parsed = JSON.parse(init.body) as unknown;
134
+ const sanitized = sanitizeKimiToolRequest(parsed);
135
+ if (sanitized === parsed) return init;
136
+ return { ...init, body: JSON.stringify(sanitized) };
137
+ } catch {
138
+ return init;
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Kimi streaming responses report token usage on the final chunk's
20
144
  * `choices[0].usage` instead of the OpenAI-standard top-level `usage` field.
21
145
  * The AI SDK openai-compatible parser only reads top-level `usage`, so we
22
146
  * hoist choice-level usage to the top level when it is missing.
@@ -57,7 +181,7 @@ export function createKimiUsageFetch(
57
181
  input: Parameters<typeof fetch>[0],
58
182
  init?: Parameters<typeof fetch>[1],
59
183
  ): Promise<Response> => {
60
- const response = await baseFetch(input, init);
184
+ const response = await baseFetch(input, sanitizeKimiFetchInit(init));
61
185
  const contentType = response.headers.get('content-type') ?? '';
62
186
  if (
63
187
  !response.ok ||
@@ -100,7 +224,7 @@ export function createKimiUsageFetch(
100
224
  }
101
225
 
102
226
  export function createKimiModel(model: string, config?: KimiProviderConfig) {
103
- const entry = catalog.moonshot;
227
+ const entry = catalog.kimi;
104
228
  const oauthAccess = config?.oauth?.access;
105
229
  const defaultApiBaseURL = entry?.api ?? 'https://api.moonshot.ai/v1';
106
230
  const configuredBaseURL = config?.baseURL;
@@ -123,11 +247,3 @@ export function createKimiModel(model: string, config?: KimiProviderConfig) {
123
247
 
124
248
  return instance(model);
125
249
  }
126
-
127
- /** @deprecated Use `createKimiModel` */
128
- export function createMoonshotModel(
129
- model: string,
130
- config?: MoonshotProviderConfig,
131
- ) {
132
- return createKimiModel(model, config);
133
- }
@@ -1,5 +1,11 @@
1
1
  import type { ModelInfo } from '../../types/src/index.ts';
2
2
 
3
+ function normalizeModelInfo(model: ModelInfo): ModelInfo {
4
+ return (model.ownedBy as string | undefined) === 'moonshot'
5
+ ? { ...model, ownedBy: 'kimi' }
6
+ : model;
7
+ }
8
+
3
9
  /**
4
10
  * Merge embedded/manual catalog models with cached (remote/local) models by id.
5
11
  *
@@ -13,7 +19,7 @@ export function mergeModelLists(
13
19
  cachedModels: ModelInfo[] | undefined,
14
20
  ): ModelInfo[] {
15
21
  const base = baseModels ?? [];
16
- const cached = cachedModels ?? [];
22
+ const cached = (cachedModels ?? []).map(normalizeModelInfo);
17
23
  if (!cached.length) return base;
18
24
  if (!base.length) return cached;
19
25
  const cachedById = new Map(cached.map((model) => [model.id, model]));
@@ -91,7 +91,7 @@ const pricingTable: Record<ProviderName, PricingEntry[]> = {
91
91
  'zai-coding': [
92
92
  // Pricing from catalog entries; leave empty here
93
93
  ],
94
- moonshot: [
94
+ kimi: [
95
95
  // Pricing from catalog entries; leave empty here
96
96
  ],
97
97
  minimax: [
@@ -39,7 +39,7 @@ const BUILTIN_COMPATIBILITY: Record<BuiltInProviderId, ProviderCompatibility> =
39
39
  xai: 'openai',
40
40
  zai: 'openai-compatible',
41
41
  'zai-coding': 'openai-compatible',
42
- moonshot: 'openai-compatible',
42
+ kimi: 'openai-compatible',
43
43
  minimax: 'anthropic',
44
44
  };
45
45
 
@@ -55,7 +55,7 @@ const BUILTIN_FAMILY: Record<BuiltInProviderId, ProviderPromptFamily> = {
55
55
  xai: 'openai',
56
56
  zai: 'glm',
57
57
  'zai-coding': 'glm',
58
- moonshot: 'moonshot',
58
+ kimi: 'kimi',
59
59
  minimax: 'minimax',
60
60
  };
61
61
  function normalizeOptionalText(value: string | undefined): string | undefined {
@@ -95,8 +95,6 @@ function resolveCustomFamily(
95
95
  return settings.family ?? 'default';
96
96
  }
97
97
 
98
- export const KIMI_PROVIDER_ALIAS = 'kimi' as const;
99
-
100
98
  function isCatalogBuiltInProviderId(
101
99
  value: unknown,
102
100
  ): value is BuiltInProviderId {
@@ -109,7 +107,6 @@ function isCatalogBuiltInProviderId(
109
107
  export function resolveBuiltInProviderCatalogId(
110
108
  provider: ProviderId,
111
109
  ): BuiltInProviderId | undefined {
112
- if (provider === KIMI_PROVIDER_ALIAS) return 'moonshot';
113
110
  if (isCatalogBuiltInProviderId(provider)) return provider;
114
111
  return undefined;
115
112
  }
@@ -117,7 +114,7 @@ export function resolveBuiltInProviderCatalogId(
117
114
  export function isBuiltInProviderId(
118
115
  value: unknown,
119
116
  ): value is BuiltInProviderId {
120
- return isCatalogBuiltInProviderId(value) || value === KIMI_PROVIDER_ALIAS;
117
+ return isCatalogBuiltInProviderId(value);
121
118
  }
122
119
 
123
120
  export function getProviderSettings(
@@ -138,21 +135,14 @@ export function getProviderDefinition(
138
135
  if (!entry) return undefined;
139
136
  const cachedEntry = getCachedProviderCatalogEntry(catalogProvider);
140
137
  const models = mergeModelLists(entry.models, cachedEntry?.models);
141
- const moonshotSettings =
142
- provider === KIMI_PROVIDER_ALIAS
143
- ? (getProviderSettings(cfg, 'moonshot') ?? settings)
144
- : settings;
145
- const resolvedSettings =
146
- provider === KIMI_PROVIDER_ALIAS
147
- ? (settings ?? moonshotSettings)
148
- : settings;
138
+ const resolvedSettings = settings;
149
139
  return {
150
140
  id: provider,
151
141
  label:
152
142
  resolvedSettings?.label ??
153
- (provider === KIMI_PROVIDER_ALIAS
154
- ? 'Kimi'
155
- : (cachedEntry?.label ?? entry.label ?? provider)),
143
+ cachedEntry?.label ??
144
+ entry.label ??
145
+ provider,
156
146
  source: 'built-in',
157
147
  compatibility: BUILTIN_COMPATIBILITY[catalogProvider],
158
148
  family: BUILTIN_FAMILY[catalogProvider],
@@ -202,7 +192,6 @@ export function getConfiguredProviderIds(
202
192
  const includeDisabled = options?.includeDisabled === true;
203
193
  const ids = new Set<ProviderId>([
204
194
  ...providerIds,
205
- KIMI_PROVIDER_ALIAS,
206
195
  ...Object.keys(cfg.providers),
207
196
  cfg.defaults.provider,
208
197
  ]);
@@ -280,7 +269,7 @@ export function getConfiguredProviderApiKey(
280
269
  const definition = getProviderDefinition(cfg, provider);
281
270
  if (!definition) return undefined;
282
271
  if (definition.apiKey?.length) return definition.apiKey;
283
- if (provider === KIMI_PROVIDER_ALIAS || provider === 'moonshot') {
272
+ if (provider === 'kimi') {
284
273
  const envValue = readEnvKey(provider);
285
274
  if (envValue?.length) return envValue;
286
275
  }
@@ -46,13 +46,13 @@ const PREFERRED_FAST_MODELS: Partial<Record<ProviderId, string[]>> = {
46
46
  xai: ['grok-code-fast-1', 'grok-4-fast'],
47
47
  zai: ['glm-4.5-flash'],
48
48
  copilot: ['gpt-4.1-mini'],
49
- moonshot: ['kimi-k2.7-code'],
49
+ kimi: ['kimi-k2.7-code'],
50
50
  };
51
51
 
52
52
  const PREFERRED_FAST_MODELS_OAUTH: Partial<Record<ProviderId, string[]>> = {
53
53
  openai: ['gpt-5.4-mini'],
54
54
  anthropic: ['claude-haiku-4-5'],
55
- moonshot: ['kimi-k2.7-code'],
55
+ kimi: ['kimi-k2.7-code'],
56
56
  };
57
57
 
58
58
  function preferredFastModelKey(provider: ProviderId): ProviderId {
@@ -148,7 +148,7 @@ const OWNER_TO_FAMILY: Record<ModelOwner, UnderlyingProviderKey> = {
148
148
  google: 'google',
149
149
  openrouter: 'openai-compatible',
150
150
  xai: 'openai',
151
- moonshot: 'moonshot',
151
+ kimi: 'kimi',
152
152
  qwen: 'openai-compatible',
153
153
  zai: 'glm',
154
154
  minimax: 'minimax',
@@ -161,7 +161,7 @@ const DIRECT_PROVIDER_FAMILY: Partial<
161
161
  anthropic: 'anthropic',
162
162
  google: 'google',
163
163
  'ollama-cloud': 'openai-compatible',
164
- moonshot: 'moonshot',
164
+ kimi: 'kimi',
165
165
  minimax: 'minimax',
166
166
  copilot: 'openai',
167
167
  xai: 'openai',
@@ -173,7 +173,7 @@ export type UnderlyingProviderKey =
173
173
  | 'anthropic'
174
174
  | 'openai'
175
175
  | 'google'
176
- | 'moonshot'
176
+ | 'kimi'
177
177
  | 'minimax'
178
178
  | 'glm'
179
179
  | 'openai-compatible'
@@ -193,8 +193,7 @@ function inferFromModelId(model: string): UnderlyingProviderKey {
193
193
  if (lower.includes('grok') || lower.startsWith('xai/')) return 'openai';
194
194
  if (lower.includes('qwen') || lower.startsWith('qwen/'))
195
195
  return 'openai-compatible';
196
- if (lower.includes('kimi') || lower.startsWith('moonshotai/'))
197
- return 'moonshot';
196
+ if (lower.includes('kimi') || lower.startsWith('moonshotai/')) return 'kimi';
198
197
  if (
199
198
  lower.includes('glm') ||
200
199
  lower.startsWith('z-ai/') ||
@@ -265,7 +264,7 @@ function getProviderModels(provider: ProviderId): ModelInfo[] {
265
264
  catalogProvider ?? provider,
266
265
  )?.models;
267
266
  const models = mergeModelLists(catalogModels, cachedModels);
268
- return catalogProvider === 'moonshot'
267
+ return catalogProvider === 'kimi'
269
268
  ? filterAvailableKimiModels(models)
270
269
  : models;
271
270
  }
@@ -31,6 +31,7 @@ export type DefaultConfig = {
31
31
  reasoningText?: boolean;
32
32
  reasoningLevel?: ReasoningLevel;
33
33
  theme?: string;
34
+ tuiTheme?: string;
34
35
  vimMode?: boolean;
35
36
  compactThread?: boolean;
36
37
  fontFamily?: string;
@@ -13,7 +13,7 @@ export type BuiltInProviderId =
13
13
  | 'xai'
14
14
  | 'zai'
15
15
  | 'zai-coding'
16
- | 'moonshot'
16
+ | 'kimi'
17
17
  | 'minimax';
18
18
 
19
19
  /**
@@ -40,7 +40,7 @@ export type ProviderPromptFamily =
40
40
  | 'anthropic'
41
41
  | 'openai'
42
42
  | 'google'
43
- | 'moonshot'
43
+ | 'kimi'
44
44
  | 'minimax'
45
45
  | 'glm'
46
46
  | 'openai-compatible';
@@ -52,7 +52,7 @@ export type ProviderFamily =
52
52
  | 'openai'
53
53
  | 'anthropic'
54
54
  | 'google'
55
- | 'moonshot'
55
+ | 'kimi'
56
56
  | 'minimax'
57
57
  | 'openai-compatible';
58
58
 
@@ -66,7 +66,7 @@ export type ModelOwner =
66
66
  | 'google'
67
67
  | 'openrouter'
68
68
  | 'xai'
69
- | 'moonshot'
69
+ | 'kimi'
70
70
  | 'qwen'
71
71
  | 'zai'
72
72
  | 'minimax';