@arnilo/prism-provider-kimi 0.0.3 → 0.0.5
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 +11 -0
- package/dist/models.js +1 -0
- package/dist/provider.d.ts +2 -2
- package/dist/provider.js +56 -36
- 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,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.0.5] - 2026-07-16
|
|
11
|
+
|
|
12
|
+
- Pinned the required `@arnilo/prism` peer and package metadata to 0.0.5; runtime behavior is unchanged.
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## [0.0.4] - 2026-07-14
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Migrated streaming/error handling to shared bounded transport and OpenAI/Anthropic argument helpers; documented structured-output capability, reasoning, cache, telemetry, and protected-header behavior.
|
|
20
|
+
|
|
10
21
|
## [0.0.2] - 2026-07-05
|
|
11
22
|
|
|
12
23
|
### 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 { bytesToBase64, isPdfMediaType, rejectProviderMediaBlock, resolveProviderMediaMessages, 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(/\/$/, "");
|
|
@@ -9,9 +10,12 @@ export function createKimiCodingProvider(options = {}) {
|
|
|
9
10
|
async *generate(request) {
|
|
10
11
|
if (request.signal?.aborted)
|
|
11
12
|
throw request.signal.reason ?? new Error("aborted");
|
|
12
|
-
|
|
13
|
-
const secrets = [
|
|
13
|
+
let token;
|
|
14
|
+
const secrets = [];
|
|
14
15
|
try {
|
|
16
|
+
const body = await kimiAnthropicBody(request);
|
|
17
|
+
token = await resolveCredentialValue(options.apiKey, { provider: id, name: "apiKey" });
|
|
18
|
+
secrets.push(token);
|
|
15
19
|
const response = await (options.fetch ?? fetch)(`${baseUrl}/messages`, {
|
|
16
20
|
method: "POST",
|
|
17
21
|
headers: {
|
|
@@ -20,14 +24,15 @@ export function createKimiCodingProvider(options = {}) {
|
|
|
20
24
|
"user-agent": options.userAgent ?? "KimiCLI/1.5",
|
|
21
25
|
...(token ? { authorization: `Bearer ${token}` } : {}),
|
|
22
26
|
},
|
|
23
|
-
body: JSON.stringify(
|
|
27
|
+
body: JSON.stringify(body),
|
|
24
28
|
signal: request.signal,
|
|
25
29
|
});
|
|
26
|
-
if (!response.ok)
|
|
27
|
-
return yield providerError(new Error(`Kimi request failed: ${response.status} ${await
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
return yield providerError(new Error(`Kimi request failed: ${response.status} ${await readBoundedResponseText(response, { secrets })}`), secrets);
|
|
32
|
+
}
|
|
28
33
|
if (!response.body)
|
|
29
34
|
return yield providerError(new Error("Kimi response had no body"), secrets);
|
|
30
|
-
yield* kimiAnthropicEvents(response.body);
|
|
35
|
+
yield* kimiAnthropicEvents(response.body, request.signal);
|
|
31
36
|
}
|
|
32
37
|
catch (error) {
|
|
33
38
|
yield providerError(error, secrets);
|
|
@@ -35,13 +40,15 @@ export function createKimiCodingProvider(options = {}) {
|
|
|
35
40
|
},
|
|
36
41
|
};
|
|
37
42
|
}
|
|
38
|
-
export function kimiAnthropicBody(request) {
|
|
43
|
+
export async function kimiAnthropicBody(request) {
|
|
44
|
+
assertStructuredOutputRequestSupported(request.model, request.options);
|
|
39
45
|
const preserveThinking = request.model.compat?.preserveThinking === true;
|
|
40
46
|
const { maxTokens, ...parameters } = request.model.parameters ?? {};
|
|
41
47
|
const messages = applyKimiAnthropicCacheControl(request);
|
|
48
|
+
const resolvedMedia = await resolveProviderMediaMessages(messages, request.model, { signal: request.signal });
|
|
42
49
|
return clean({
|
|
43
50
|
model: request.model.model,
|
|
44
|
-
messages: messages.filter((m) => m.role !== "system").map((message) => toMessage(message, request.model
|
|
51
|
+
messages: await Promise.all(messages.filter((m) => m.role !== "system").map((message) => toMessage(message, request.model, preserveThinking, resolvedMedia))),
|
|
45
52
|
system: messages.filter((m) => m.role === "system").map((m) => text(m, preserveThinking)).join("\n\n") || undefined,
|
|
46
53
|
tools: request.tools?.map(toTool),
|
|
47
54
|
stream: true,
|
|
@@ -51,10 +58,10 @@ export function kimiAnthropicBody(request) {
|
|
|
51
58
|
...request.options?.extra,
|
|
52
59
|
});
|
|
53
60
|
}
|
|
54
|
-
export async function* kimiAnthropicEvents(body) {
|
|
61
|
+
export async function* kimiAnthropicEvents(body, signal) {
|
|
55
62
|
const blocks = new Map();
|
|
56
63
|
let usage;
|
|
57
|
-
for await (const data of readSseData(body)) {
|
|
64
|
+
for await (const data of readSseData(body, { signal })) {
|
|
58
65
|
if (data === "[DONE]")
|
|
59
66
|
break;
|
|
60
67
|
const event = JSON.parse(data);
|
|
@@ -78,12 +85,15 @@ export async function* kimiAnthropicEvents(body) {
|
|
|
78
85
|
if (event.type === "message_delta" && usage)
|
|
79
86
|
yield providerUsage(usage);
|
|
80
87
|
}
|
|
81
|
-
for (const call of blocks.values())
|
|
82
|
-
if (call.id && call.name)
|
|
83
|
-
yield providerToolCall(toolCallContent(call.id, call.name,
|
|
88
|
+
for (const call of blocks.values()) {
|
|
89
|
+
if (call.id && call.name) {
|
|
90
|
+
yield providerToolCall(toolCallContent(call.id, call.name, parseJsonObjectArguments(call.argumentsText, { toolName: call.name })));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
84
93
|
yield providerDone(usage);
|
|
85
94
|
}
|
|
86
|
-
function toMessage(message,
|
|
95
|
+
async function toMessage(message, model, preserveThinking, resolvedMedia) {
|
|
96
|
+
const capabilities = model.capabilities ?? {};
|
|
87
97
|
if (message.role === "tool") {
|
|
88
98
|
const result = message.content.find((part) => part.type === "tool_result");
|
|
89
99
|
const last = message.content[message.content.length - 1];
|
|
@@ -112,14 +122,19 @@ function toMessage(message, capabilities = {}, preserveThinking = false) {
|
|
|
112
122
|
}
|
|
113
123
|
}
|
|
114
124
|
else if (part.type === "image") {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
const source = part.url
|
|
119
|
-
? { type: "url", url: part.url }
|
|
120
|
-
: { type: "base64", media_type: part.mimeType ?? "image/png", data: part.data ?? "" };
|
|
125
|
+
const resolved = resolvedMedia.get(part);
|
|
126
|
+
const source = { type: "base64", media_type: resolved.mediaType, data: bytesToBase64(resolved.bytes) };
|
|
121
127
|
content.push(withMarker({ type: "image", source }, marker));
|
|
122
128
|
}
|
|
129
|
+
else if (part.type === "document") {
|
|
130
|
+
content.push(withMarker(toAnthropicDocument(part, resolvedMedia), marker));
|
|
131
|
+
}
|
|
132
|
+
else if (part.type === "file") {
|
|
133
|
+
content.push(withMarker(toAnthropicFile(part, resolvedMedia), marker));
|
|
134
|
+
}
|
|
135
|
+
else if (part.type === "audio") {
|
|
136
|
+
rejectProviderMediaBlock(part, capabilities, model);
|
|
137
|
+
}
|
|
123
138
|
else if (part.type === "tool_call") {
|
|
124
139
|
content.push(withMarker({ type: "tool_use", id: part.id, name: part.name, input: part.arguments }, marker));
|
|
125
140
|
}
|
|
@@ -129,6 +144,25 @@ function toMessage(message, capabilities = {}, preserveThinking = false) {
|
|
|
129
144
|
}
|
|
130
145
|
return { role: message.role === "assistant" ? "assistant" : "user", content: content.length > 0 ? content : [{ type: "text", text: "" }] };
|
|
131
146
|
}
|
|
147
|
+
function toAnthropicDocument(part, resolvedMedia) {
|
|
148
|
+
const resolved = resolvedMedia.get(part);
|
|
149
|
+
return serializePdfDocumentWireBlock({
|
|
150
|
+
mediaType: resolved.mediaType,
|
|
151
|
+
data: bytesToBase64(resolved.bytes),
|
|
152
|
+
title: resolved.name,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
function toAnthropicFile(part, resolvedMedia) {
|
|
156
|
+
const resolved = resolvedMedia.get(part);
|
|
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.5",
|
|
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.5"
|
|
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
|