@arnilo/prism-provider-kimi 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.0.2] - 2026-07-05
11
+
10
12
  ### Added
11
13
 
12
14
  - Added `LICENSE` (MIT) and `CHANGELOG.md`.
package/README.md CHANGED
@@ -20,3 +20,9 @@ Security defaults:
20
20
  - No automatic environment, file, keychain, or shell credential lookup.
21
21
  - Kimi credentials are resolved per request from caller-supplied values or resolvers.
22
22
  - Moonshot/Open Platform model metadata is registered only with `includeMoonshotModels: true`.
23
+
24
+ Cache behavior:
25
+ - Default catalog models use implicit caching (no `cache_control`); opt in via `ModelConfig.cache.kind: "cache_control"` on the Anthropic route.
26
+ - When opted in, `cache_control` markers apply only to selected `cache.breakpoints` (`"long"` → `ttl: "1h"`); Moonshot OpenAI route sends none.
27
+ - `cache_read_input_tokens`/`cache_creation_input_tokens` map to `Usage.cacheReadTokens`/`cacheWriteTokens`.
28
+ - Provider-owned headers (`content-type`, `user-agent`, `authorization`) win over caller headers.
@@ -0,0 +1,16 @@
1
+ import type { Message, ProviderRequest } from "@arnilo/prism";
2
+ /**
3
+ * Whether Anthropic-style content `cache_control` markers may be emitted on the
4
+ * Kimi coding (`/messages`) endpoint. Default catalog models use implicit
5
+ * caching and do not declare this; hosts opt in per-model via
6
+ * `ModelConfig.cache.kind: "cache_control"` (or force `cache.mode: "on"`).
7
+ * The Moonshot OpenAI-compatible route never receives Anthropic `cache_control`.
8
+ */
9
+ export declare function kimiAnthropicCacheEnabled(request: ProviderRequest): boolean;
10
+ /**
11
+ * Apply Anthropic-style `cache_control` markers only to the caller-selected Prism
12
+ * breakpoints on the Kimi Anthropic route, using the shared `applyCacheControl`
13
+ * helper. Markers land on the last content block of each selected message; with
14
+ * no breakpoints, no markers are emitted and the endpoint relies on implicit caching.
15
+ */
16
+ export declare function applyKimiAnthropicCacheControl(request: ProviderRequest): readonly Message[];
package/dist/cache.js ADDED
@@ -0,0 +1,41 @@
1
+ import { applyCacheControl } from "@arnilo/prism";
2
+ /**
3
+ * Whether Anthropic-style content `cache_control` markers may be emitted on the
4
+ * Kimi coding (`/messages`) endpoint. Default catalog models use implicit
5
+ * caching and do not declare this; hosts opt in per-model via
6
+ * `ModelConfig.cache.kind: "cache_control"` (or force `cache.mode: "on"`).
7
+ * The Moonshot OpenAI-compatible route never receives Anthropic `cache_control`.
8
+ */
9
+ export function kimiAnthropicCacheEnabled(request) {
10
+ if (request.options?.cacheRetention === "none")
11
+ return false;
12
+ if (request.options?.cache?.mode === "off")
13
+ return false;
14
+ if (request.model.cache?.kind === "none")
15
+ return false;
16
+ return request.model.cache?.kind === "cache_control" || request.options?.cache?.mode === "on";
17
+ }
18
+ /**
19
+ * Apply Anthropic-style `cache_control` markers only to the caller-selected Prism
20
+ * breakpoints on the Kimi Anthropic route, using the shared `applyCacheControl`
21
+ * helper. Markers land on the last content block of each selected message; with
22
+ * no breakpoints, no markers are emitted and the endpoint relies on implicit caching.
23
+ */
24
+ export function applyKimiAnthropicCacheControl(request) {
25
+ if (!kimiAnthropicCacheEnabled(request))
26
+ return request.messages;
27
+ const breakpoints = request.options?.cache?.breakpoints;
28
+ if (!breakpoints?.length)
29
+ return request.messages;
30
+ const options = {
31
+ ttl: kimiAnthropicCacheTtl(request) ? "1h" : undefined,
32
+ maxBreakpoints: request.model.cache?.maxBreakpoints,
33
+ };
34
+ return applyCacheControl(request.messages, breakpoints, options);
35
+ }
36
+ function kimiAnthropicCacheTtl(request) {
37
+ if (request.options?.cacheRetention !== "long" && request.options?.cache?.retention !== "long")
38
+ return false;
39
+ return request.model.cache?.longRetention !== false;
40
+ }
41
+ //# sourceMappingURL=cache.js.map
package/dist/provider.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { providerDone, providerError, providerTextDelta, providerThinkingDelta, providerToolCall, providerToolCallDelta, providerUsage, resolveCredentialValue, toolCallContent } from "@arnilo/prism";
2
+ import { applyKimiAnthropicCacheControl } from "./cache.js";
2
3
  import { readSseData } from "./sse.js";
3
4
  export function createKimiCodingProvider(options = {}) {
4
5
  const id = options.id ?? "kimi-coding";
@@ -14,9 +15,9 @@ export function createKimiCodingProvider(options = {}) {
14
15
  const response = await (options.fetch ?? fetch)(`${baseUrl}/messages`, {
15
16
  method: "POST",
16
17
  headers: {
18
+ ...request.options?.headers,
17
19
  "content-type": "application/json",
18
20
  "user-agent": options.userAgent ?? "KimiCLI/1.5",
19
- ...request.options?.headers,
20
21
  ...(token ? { authorization: `Bearer ${token}` } : {}),
21
22
  },
22
23
  body: JSON.stringify(kimiAnthropicBody(request)),
@@ -36,14 +37,16 @@ export function createKimiCodingProvider(options = {}) {
36
37
  }
37
38
  export function kimiAnthropicBody(request) {
38
39
  const preserveThinking = request.model.compat?.preserveThinking === true;
40
+ const { maxTokens, ...parameters } = request.model.parameters ?? {};
41
+ const messages = applyKimiAnthropicCacheControl(request);
39
42
  return clean({
40
43
  model: request.model.model,
41
- messages: request.messages.filter((m) => m.role !== "system").map((message) => toMessage(message, request.model.capabilities ?? {}, preserveThinking)),
42
- system: request.messages.filter((m) => m.role === "system").map((m) => text(m, preserveThinking)).join("\n\n") || undefined,
44
+ messages: messages.filter((m) => m.role !== "system").map((message) => toMessage(message, request.model.capabilities ?? {}, preserveThinking)),
45
+ system: messages.filter((m) => m.role === "system").map((m) => text(m, preserveThinking)).join("\n\n") || undefined,
43
46
  tools: request.tools?.map(toTool),
44
47
  stream: true,
45
- max_tokens: request.model.limits?.maxOutputTokens ?? 4096,
46
- ...request.model.parameters,
48
+ ...parameters,
49
+ max_tokens: maxTokens ?? request.model.limits?.maxOutputTokens ?? 4096,
47
50
  ...request.options?.compat,
48
51
  ...request.options?.extra,
49
52
  });
@@ -83,26 +86,29 @@ export async function* kimiAnthropicEvents(body) {
83
86
  function toMessage(message, capabilities = {}, preserveThinking = false) {
84
87
  if (message.role === "tool") {
85
88
  const result = message.content.find((part) => part.type === "tool_result");
89
+ const last = message.content[message.content.length - 1];
86
90
  return {
87
91
  role: "user",
88
92
  content: [{
89
93
  type: "tool_result",
90
94
  tool_use_id: result?.toolCallId ?? "",
91
95
  content: result ? JSON.stringify(result.result ?? result.error ?? null) : "",
96
+ ...(last?.cache_control ? { cache_control: last.cache_control } : {}),
92
97
  }],
93
98
  };
94
99
  }
95
100
  const content = [];
96
101
  for (const part of message.content) {
102
+ const marker = (part.cache_control ?? undefined);
97
103
  if (part.type === "text") {
98
- content.push({ type: "text", text: part.text });
104
+ content.push(withMarker({ type: "text", text: part.text }, marker));
99
105
  }
100
106
  else if (part.type === "thinking") {
101
107
  if (preserveThinking) {
102
- content.push(part.signature ? { type: "thinking", thinking: part.text, signature: part.signature } : { type: "thinking", thinking: part.text });
108
+ content.push(withMarker(part.signature ? { type: "thinking", thinking: part.text, signature: part.signature } : { type: "thinking", thinking: part.text }, marker));
103
109
  }
104
110
  else {
105
- content.push({ type: "text", text: part.text });
111
+ content.push(withMarker({ type: "text", text: part.text }, marker));
106
112
  }
107
113
  }
108
114
  else if (part.type === "image") {
@@ -112,10 +118,10 @@ function toMessage(message, capabilities = {}, preserveThinking = false) {
112
118
  const source = part.url
113
119
  ? { type: "url", url: part.url }
114
120
  : { type: "base64", media_type: part.mimeType ?? "image/png", data: part.data ?? "" };
115
- content.push({ type: "image", source });
121
+ content.push(withMarker({ type: "image", source }, marker));
116
122
  }
117
123
  else if (part.type === "tool_call") {
118
- content.push({ type: "tool_use", id: part.id, name: part.name, input: part.arguments });
124
+ content.push(withMarker({ type: "tool_use", id: part.id, name: part.name, input: part.arguments }, marker));
119
125
  }
120
126
  else if (part.type === "tool_result") {
121
127
  throw new Error("Kimi tool_result blocks must appear in role=tool messages");
@@ -123,6 +129,9 @@ function toMessage(message, capabilities = {}, preserveThinking = false) {
123
129
  }
124
130
  return { role: message.role === "assistant" ? "assistant" : "user", content: content.length > 0 ? content : [{ type: "text", text: "" }] };
125
131
  }
132
+ function withMarker(item, marker) {
133
+ return marker ? { ...item, cache_control: marker } : item;
134
+ }
126
135
  function toTool(tool) {
127
136
  return clean({ name: tool.name, description: tool.description, input_schema: tool.parameters ?? { type: "object" } });
128
137
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arnilo/prism-provider-kimi",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Kimi provider package for Prism.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -25,7 +25,7 @@
25
25
  "pack:dry-run": "npm pack --dry-run"
26
26
  },
27
27
  "peerDependencies": {
28
- "@arnilo/prism": "0.0.1"
28
+ "@arnilo/prism": "0.0.2"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@arnilo/prism": "file:../.."