@arnilo/prism-provider-kimi 0.0.16 → 0.0.17

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
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.0.17] - 2026-07-29
4
+
5
+ ### Changed
6
+ - HTTP errors now carry numeric `code` and parsed `Retry-After` (`retryAfterMs`) for transient classification and backpressure-aware retries.
7
+ - Released with exact 0.0.17 graph.
8
+
3
9
  ## [0.0.16] - 2026-07-26
4
10
 
5
11
  ### Changed
package/dist/models.js CHANGED
@@ -24,7 +24,7 @@ export function defineKimiModel(config) {
24
24
  */
25
25
  export async function listKimiModels(options = {}) {
26
26
  const provider = options.provider ?? "moonshot";
27
- const baseUrl = (options.baseUrl ?? "https://api.moonshot.ai/v1").replace(/\/$/, "");
27
+ const baseUrl = (options.baseUrl ?? "https://api.moonshot.ai/v1").replace(/\/+$/, "");
28
28
  const token = await resolveCredentialValue(options.apiKey, { provider, name: "apiKey" });
29
29
  const response = await (options.fetch ?? fetch)(`${baseUrl}/models`, {
30
30
  method: "GET",
package/dist/moonshot.js CHANGED
@@ -1,6 +1,5 @@
1
- import { assertStructuredOutputRequestSupported, providerDone, providerError, providerTextDelta, providerThinkingDelta, providerToolCall, providerToolCallDelta, providerUsage, resolveCredentialValue, toolCallFromArgumentsText, } from "@arnilo/prism";
2
- import { applyOpenAIChatStructuredOutput, mapOpenAIChatUsage, serializeOpenAITool } from "@arnilo/prism/providers/openai";
3
- import { readBoundedResponseText, readSseData } from "@arnilo/prism/providers/transport";
1
+ import { applyOpenAIChatStructuredOutput } from "@arnilo/prism/providers/openai";
2
+ import { buildOpenAIChatBody, createOpenAICompatibleProvider, openAIChatEvents } from "@arnilo/prism/providers/openai-compatible";
4
3
  import { kimiPreserveThinking, kimiReasoningEffort, kimiThinking, stripKimiThinkingCompat } from "./thinking.js";
5
4
  /**
6
5
  * Moonshot / Kimi Open Platform Chat Completions provider (`POST /chat/completions`).
@@ -9,116 +8,39 @@ import { kimiPreserveThinking, kimiReasoningEffort, kimiThinking, stripKimiThink
9
8
  * @see https://platform.kimi.ai/docs/api/overview
10
9
  */
11
10
  export function createMoonshotProvider(options = {}) {
12
- const id = options.id ?? "moonshot";
13
- const baseUrl = (options.baseUrl ?? "https://api.moonshot.ai/v1").replace(/\/$/, "");
14
- return {
15
- id,
16
- async *generate(request) {
17
- if (request.signal?.aborted)
18
- throw request.signal.reason ?? new Error("aborted");
19
- let token;
20
- const secrets = [];
21
- try {
22
- const body = moonshotBody(request);
23
- token = await resolveCredentialValue(options.apiKey, { provider: id, name: "apiKey" });
24
- secrets.push(token);
25
- const response = await (options.fetch ?? fetch)(`${baseUrl}/chat/completions`, {
26
- method: "POST",
27
- headers: {
28
- ...request.options?.headers,
29
- "content-type": "application/json",
30
- ...(token ? { authorization: `Bearer ${token}` } : {}),
31
- },
32
- body: JSON.stringify(body),
33
- signal: request.signal,
34
- });
35
- if (!response.ok) {
36
- return yield providerError(new Error(`Moonshot request failed: ${response.status} ${await readBoundedResponseText(response, { secrets })}`), secrets);
37
- }
38
- if (!response.body)
39
- return yield providerError(new Error("Moonshot response had no body"), secrets);
40
- yield* moonshotEvents(response.body, request.signal);
41
- }
42
- catch (error) {
43
- yield providerError(error, secrets);
44
- }
45
- },
46
- };
11
+ return createOpenAICompatibleProvider({
12
+ id: options.id ?? "moonshot",
13
+ baseUrl: (options.baseUrl ?? "https://api.moonshot.ai/v1").replace(/\/+$/, ""),
14
+ apiKey: options.apiKey,
15
+ fetch: options.fetch,
16
+ strictCompletion: true,
17
+ requestFailedPrefix: "Moonshot request failed",
18
+ serializeMessage: (message, request) => serializeMoonshotMessage(message, request.model.capabilities ?? {}, kimiPreserveThinking(request)),
19
+ transformBody: (body, request) => moonshotTransform(body, request),
20
+ });
47
21
  }
48
22
  export function moonshotBody(request) {
49
- assertStructuredOutputRequestSupported(request.model, request.options);
50
- const preserveThinking = kimiPreserveThinking(request);
51
- const { maxTokens, ...parameters } = request.model.parameters ?? {};
52
- const body = {
53
- model: request.model.model,
54
- messages: request.messages.map((message) => serializeMoonshotMessage(message, request.model.capabilities ?? {}, preserveThinking)),
55
- tools: request.tools?.map(serializeOpenAITool),
56
- stream: true,
57
- stream_options: { include_usage: true },
23
+ return buildOpenAIChatBody(request, {
24
+ serializeMessage: (message, req) => serializeMoonshotMessage(message, req.model.capabilities ?? {}, kimiPreserveThinking(req)),
25
+ transformBody: (body, req) => moonshotTransform(body, req),
26
+ });
27
+ }
28
+ function moonshotTransform(body, request) {
29
+ const { maxTokens, ...rest } = body;
30
+ const transformed = {
31
+ // Resolved thinking fields first: model `parameters` may override them (legacy order).
58
32
  thinking: kimiThinking(request),
59
33
  reasoning_effort: kimiReasoningEffort(request),
60
- ...parameters,
34
+ ...rest,
61
35
  max_tokens: maxTokens ?? request.model.limits?.maxOutputTokens,
62
36
  ...stripKimiThinkingCompat(request.options?.compat),
63
37
  ...request.options?.extra,
64
38
  };
65
- applyOpenAIChatStructuredOutput(body, request.options?.structuredOutput);
66
- return clean(body);
39
+ applyOpenAIChatStructuredOutput(transformed, request.options?.structuredOutput);
40
+ return clean(transformed);
67
41
  }
68
- export async function* moonshotEvents(body, signal) {
69
- const tools = new Map();
70
- let usage;
71
- let sawDoneMarker = false;
72
- let sawFinishReason = false;
73
- for await (const data of readSseData(body, { signal })) {
74
- if (data === "[DONE]") {
75
- sawDoneMarker = true;
76
- break;
77
- }
78
- const chunk = JSON.parse(data);
79
- usage = mapOpenAIChatUsage(chunk.usage) ?? usage;
80
- if (chunk.usage) {
81
- const mapped = mapOpenAIChatUsage(chunk.usage);
82
- if (mapped)
83
- yield providerUsage(mapped);
84
- }
85
- for (const choice of chunk.choices ?? []) {
86
- if (choice.finish_reason)
87
- sawFinishReason = true;
88
- const delta = choice.delta ?? {};
89
- if (delta.content)
90
- yield providerTextDelta(delta.content);
91
- if (delta.reasoning_content)
92
- yield providerThinkingDelta(delta.reasoning_content);
93
- for (const tool of delta.tool_calls ?? []) {
94
- const index = tool.index ?? 0;
95
- const current = tools.get(index) ?? { argumentsText: "" };
96
- current.id = tool.id ?? current.id;
97
- current.name = tool.function?.name ?? current.name;
98
- current.argumentsText += tool.function?.arguments ?? "";
99
- tools.set(index, current);
100
- yield providerToolCallDelta({
101
- index,
102
- id: tool.id,
103
- name: tool.function?.name,
104
- argumentsText: tool.function?.arguments,
105
- });
106
- }
107
- }
108
- }
109
- const danglingToolCall = [...tools.values()].some((call) => !call.id || !call.name);
110
- if (!sawDoneMarker || !sawFinishReason || danglingToolCall) {
111
- // Truncated streams must fail loudly — emitting done would mark partial output as succeeded.
112
- yield providerError(new Error(`Moonshot chat stream ended without completion evidence ` +
113
- `([DONE]: ${sawDoneMarker ? "received" : "missing"}, ` +
114
- `finish_reason: ${sawFinishReason ? "received" : "missing"}, ` +
115
- `tool calls complete: ${danglingToolCall ? "no" : "yes"})`));
116
- return;
117
- }
118
- for (const call of tools.values()) {
119
- yield providerToolCall(toolCallFromArgumentsText(call.id, call.name, call.argumentsText));
120
- }
121
- yield providerDone(usage);
42
+ export function moonshotEvents(body, signal) {
43
+ return openAIChatEvents(body, { signal, strictCompletion: true });
122
44
  }
123
45
  /**
124
46
  * Open Platform message serialization. When `preserveThinking`, historical thinking
package/dist/provider.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { assertStructuredOutputRequestSupported, providerDone, providerError, providerTextDelta, providerThinkingDelta, providerToolCall, providerToolCallDelta, providerUsage, resolveCredentialValue, toolCallFromArgumentsText, } from "@arnilo/prism";
2
2
  import { bytesToBase64, isPdfMediaType, rejectProviderMediaBlock, resolveProviderMediaMessages, serializePdfDocumentWireBlock, } from "@arnilo/prism/providers/media";
3
- import { readBoundedResponseText, readSseData } from "@arnilo/prism/providers/transport";
3
+ import { httpStatusError, readBoundedResponseText, readSseData } from "@arnilo/prism/providers/transport";
4
4
  import { applyKimiAnthropicCacheControl } from "./cache.js";
5
5
  import { kimiPreserveThinking, kimiReasoningEffort, kimiThinking, stripKimiThinkingCompat } from "./thinking.js";
6
6
  export function createKimiCodingProvider(options = {}) {
7
7
  const id = options.id ?? "kimi-coding";
8
- const baseUrl = (options.baseUrl ?? "https://api.kimi.com/coding").replace(/\/$/, "");
8
+ const baseUrl = (options.baseUrl ?? "https://api.kimi.com/coding").replace(/\/+$/, "");
9
9
  return {
10
10
  id,
11
11
  async *generate(request) {
@@ -32,7 +32,7 @@ export function createKimiCodingProvider(options = {}) {
32
32
  signal: request.signal,
33
33
  });
34
34
  if (!response.ok) {
35
- return yield providerError(new Error(`Kimi request failed: ${response.status} ${await readBoundedResponseText(response, { secrets })}`), secrets);
35
+ return yield providerError(httpStatusError("Kimi request failed", response, await readBoundedResponseText(response, { secrets })), secrets);
36
36
  }
37
37
  if (!response.body)
38
38
  return yield providerError(new Error("Kimi response had no body"), secrets);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arnilo/prism-provider-kimi",
3
- "version": "0.0.16",
3
+ "version": "0.0.17",
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.16"
28
+ "@arnilo/prism": "0.0.17"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@arnilo/prism": "file:../.."