@arnilo/prism-provider-kimi 0.0.2 → 0.0.4

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,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.0.4] - 2026-07-14
11
+
12
+ ### Changed
13
+
14
+ - Migrated streaming/error handling to shared bounded transport and OpenAI/Anthropic argument helpers; documented structured-output capability, reasoning, cache, telemetry, and protected-header behavior.
15
+
10
16
  ## [0.0.2] - 2026-07-05
11
17
 
12
18
  ### Added
package/dist/models.js CHANGED
@@ -6,6 +6,7 @@ export const kimiCodingModels = [
6
6
  provider: "kimi-coding",
7
7
  model: "kimi-k2.7-code",
8
8
  displayName: "Kimi K2.7 Code",
9
+ capabilities: { input: ["text", "document", "file"] },
9
10
  limits: { contextWindow: 256_000, maxOutputTokens: 64_000 },
10
11
  compat: { route: "anthropic", preserveThinking: true },
11
12
  }),
@@ -7,5 +7,5 @@ export interface KimiCodingProviderOptions {
7
7
  readonly userAgent?: string;
8
8
  }
9
9
  export declare function createKimiCodingProvider(options?: KimiCodingProviderOptions): AIProvider;
10
- export declare function kimiAnthropicBody(request: ProviderRequest): JsonObject;
11
- export declare function kimiAnthropicEvents(body: ReadableStream<Uint8Array>): AsyncIterable<ProviderEvent>;
10
+ export declare function kimiAnthropicBody(request: ProviderRequest): Promise<JsonObject>;
11
+ export declare function kimiAnthropicEvents(body: ReadableStream<Uint8Array>, signal?: AbortSignal): AsyncIterable<ProviderEvent>;
package/dist/provider.js CHANGED
@@ -1,6 +1,7 @@
1
- import { providerDone, providerError, providerTextDelta, providerThinkingDelta, providerToolCall, providerToolCallDelta, providerUsage, resolveCredentialValue, toolCallContent } from "@arnilo/prism";
1
+ import { assertStructuredOutputRequestSupported, providerDone, providerError, providerTextDelta, providerThinkingDelta, providerToolCall, providerToolCallDelta, providerUsage, resolveCredentialValue, toolCallContent } from "@arnilo/prism";
2
+ import { assertProviderMediaCapability, bytesToBase64, isPdfMediaType, rejectProviderMediaBlock, resolveProviderMediaBlock, serializePdfDocumentWireBlock, } from "@arnilo/prism/providers/media";
3
+ import { parseJsonObjectArguments, readBoundedResponseText, readSseData } from "@arnilo/prism/providers/transport";
2
4
  import { applyKimiAnthropicCacheControl } from "./cache.js";
3
- import { readSseData } from "./sse.js";
4
5
  export function createKimiCodingProvider(options = {}) {
5
6
  const id = options.id ?? "kimi-coding";
6
7
  const baseUrl = (options.baseUrl ?? "https://api.kimi.com/coding").replace(/\/$/, "");
@@ -20,14 +21,15 @@ export function createKimiCodingProvider(options = {}) {
20
21
  "user-agent": options.userAgent ?? "KimiCLI/1.5",
21
22
  ...(token ? { authorization: `Bearer ${token}` } : {}),
22
23
  },
23
- body: JSON.stringify(kimiAnthropicBody(request)),
24
+ body: JSON.stringify(await kimiAnthropicBody(request)),
24
25
  signal: request.signal,
25
26
  });
26
- if (!response.ok)
27
- return yield providerError(new Error(`Kimi request failed: ${response.status} ${await safeText(response)}`), secrets);
27
+ if (!response.ok) {
28
+ return yield providerError(new Error(`Kimi request failed: ${response.status} ${await readBoundedResponseText(response, { secrets })}`), secrets);
29
+ }
28
30
  if (!response.body)
29
31
  return yield providerError(new Error("Kimi response had no body"), secrets);
30
- yield* kimiAnthropicEvents(response.body);
32
+ yield* kimiAnthropicEvents(response.body, request.signal);
31
33
  }
32
34
  catch (error) {
33
35
  yield providerError(error, secrets);
@@ -35,13 +37,14 @@ export function createKimiCodingProvider(options = {}) {
35
37
  },
36
38
  };
37
39
  }
38
- export function kimiAnthropicBody(request) {
40
+ export async function kimiAnthropicBody(request) {
41
+ assertStructuredOutputRequestSupported(request.model, request.options);
39
42
  const preserveThinking = request.model.compat?.preserveThinking === true;
40
43
  const { maxTokens, ...parameters } = request.model.parameters ?? {};
41
44
  const messages = applyKimiAnthropicCacheControl(request);
42
45
  return clean({
43
46
  model: request.model.model,
44
- messages: messages.filter((m) => m.role !== "system").map((message) => toMessage(message, request.model.capabilities ?? {}, preserveThinking)),
47
+ messages: await Promise.all(messages.filter((m) => m.role !== "system").map((message) => toMessage(message, request.model, preserveThinking, request.signal))),
45
48
  system: messages.filter((m) => m.role === "system").map((m) => text(m, preserveThinking)).join("\n\n") || undefined,
46
49
  tools: request.tools?.map(toTool),
47
50
  stream: true,
@@ -51,10 +54,10 @@ export function kimiAnthropicBody(request) {
51
54
  ...request.options?.extra,
52
55
  });
53
56
  }
54
- export async function* kimiAnthropicEvents(body) {
57
+ export async function* kimiAnthropicEvents(body, signal) {
55
58
  const blocks = new Map();
56
59
  let usage;
57
- for await (const data of readSseData(body)) {
60
+ for await (const data of readSseData(body, { signal })) {
58
61
  if (data === "[DONE]")
59
62
  break;
60
63
  const event = JSON.parse(data);
@@ -78,12 +81,15 @@ export async function* kimiAnthropicEvents(body) {
78
81
  if (event.type === "message_delta" && usage)
79
82
  yield providerUsage(usage);
80
83
  }
81
- for (const call of blocks.values())
82
- if (call.id && call.name)
83
- yield providerToolCall(toolCallContent(call.id, call.name, parseArgs(call.argumentsText)));
84
+ for (const call of blocks.values()) {
85
+ if (call.id && call.name) {
86
+ yield providerToolCall(toolCallContent(call.id, call.name, parseJsonObjectArguments(call.argumentsText, { toolName: call.name })));
87
+ }
88
+ }
84
89
  yield providerDone(usage);
85
90
  }
86
- function toMessage(message, capabilities = {}, preserveThinking = false) {
91
+ async function toMessage(message, model, preserveThinking = false, signal) {
92
+ const capabilities = model.capabilities ?? {};
87
93
  if (message.role === "tool") {
88
94
  const result = message.content.find((part) => part.type === "tool_result");
89
95
  const last = message.content[message.content.length - 1];
@@ -112,14 +118,21 @@ function toMessage(message, capabilities = {}, preserveThinking = false) {
112
118
  }
113
119
  }
114
120
  else if (part.type === "image") {
115
- if (!capabilities.input?.includes("image")) {
116
- throw new Error(`Kimi request includes image but model does not declare image input capability`);
117
- }
121
+ assertProviderMediaCapability("image", capabilities, model);
118
122
  const source = part.url
119
123
  ? { type: "url", url: part.url }
120
124
  : { type: "base64", media_type: part.mimeType ?? "image/png", data: part.data ?? "" };
121
125
  content.push(withMarker({ type: "image", source }, marker));
122
126
  }
127
+ else if (part.type === "document") {
128
+ content.push(withMarker(await toAnthropicDocument(part, model, signal), marker));
129
+ }
130
+ else if (part.type === "file") {
131
+ content.push(withMarker(await toAnthropicFile(part, model, signal), marker));
132
+ }
133
+ else if (part.type === "audio") {
134
+ rejectProviderMediaBlock(part, capabilities, model);
135
+ }
123
136
  else if (part.type === "tool_call") {
124
137
  content.push(withMarker({ type: "tool_use", id: part.id, name: part.name, input: part.arguments }, marker));
125
138
  }
@@ -129,6 +142,27 @@ function toMessage(message, capabilities = {}, preserveThinking = false) {
129
142
  }
130
143
  return { role: message.role === "assistant" ? "assistant" : "user", content: content.length > 0 ? content : [{ type: "text", text: "" }] };
131
144
  }
145
+ async function toAnthropicDocument(part, model, signal) {
146
+ assertProviderMediaCapability("document", model.capabilities ?? {}, model);
147
+ const resolved = await resolveProviderMediaBlock(part, { signal });
148
+ return serializePdfDocumentWireBlock({
149
+ mediaType: resolved.mediaType,
150
+ data: bytesToBase64(resolved.bytes),
151
+ title: resolved.name,
152
+ });
153
+ }
154
+ async function toAnthropicFile(part, model, signal) {
155
+ assertProviderMediaCapability("file", model.capabilities ?? {}, model);
156
+ const resolved = await resolveProviderMediaBlock(part, { signal });
157
+ if (!isPdfMediaType(resolved.mediaType)) {
158
+ throw new Error(`Kimi Anthropic route only maps PDF file blocks; got ${resolved.mediaType}`);
159
+ }
160
+ return serializePdfDocumentWireBlock({
161
+ mediaType: resolved.mediaType,
162
+ data: bytesToBase64(resolved.bytes),
163
+ title: resolved.name,
164
+ });
165
+ }
132
166
  function withMarker(item, marker) {
133
167
  return marker ? { ...item, cache_control: marker } : item;
134
168
  }
@@ -147,21 +181,7 @@ function text(message, preserveThinking = false) {
147
181
  function toUsage(usage) {
148
182
  return usage ? { inputTokens: usage.input_tokens, outputTokens: usage.output_tokens, cacheReadTokens: usage.cache_read_input_tokens, cacheWriteTokens: usage.cache_creation_input_tokens } : undefined;
149
183
  }
150
- function parseArgs(text) {
151
- if (!text)
152
- return {};
153
- const parsed = JSON.parse(text);
154
- return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
155
- }
156
184
  function clean(value) {
157
185
  return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined && !(Array.isArray(item) && item.length === 0)));
158
186
  }
159
- async function safeText(response) {
160
- try {
161
- return await response.text();
162
- }
163
- catch {
164
- return "";
165
- }
166
- }
167
187
  //# sourceMappingURL=provider.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arnilo/prism-provider-kimi",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
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.2"
28
+ "@arnilo/prism": "0.0.4"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@arnilo/prism": "file:../.."
package/dist/sse.d.ts DELETED
@@ -1 +0,0 @@
1
- export declare function readSseData(body: ReadableStream<Uint8Array>): AsyncIterable<string>;
package/dist/sse.js DELETED
@@ -1,24 +0,0 @@
1
- export async function* readSseData(body) {
2
- const reader = body.getReader();
3
- const decoder = new TextDecoder();
4
- let buffer = "";
5
- while (true) {
6
- const { done, value } = await reader.read();
7
- if (done)
8
- break;
9
- buffer += decoder.decode(value, { stream: true });
10
- const parts = buffer.split(/\r?\n\r?\n/);
11
- buffer = parts.pop() ?? "";
12
- for (const part of parts)
13
- yield* eventData(part);
14
- }
15
- buffer += decoder.decode();
16
- if (buffer)
17
- yield* eventData(buffer);
18
- }
19
- function* eventData(event) {
20
- const data = event.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n").trim();
21
- if (data)
22
- yield data;
23
- }
24
- //# sourceMappingURL=sse.js.map