@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 +6 -0
- package/dist/models.js +1 -0
- package/dist/provider.d.ts +2 -2
- package/dist/provider.js +51 -31
- package/package.json +2 -2
- package/dist/sse.d.ts +0 -1
- package/dist/sse.js +0 -24
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
|
}),
|
package/dist/provider.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|