pi-provider-kimi-code 0.2.2 → 0.3.0
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/README.md +9 -11
- package/index.ts +143 -86
- package/package.json +1 -3
package/README.md
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
**Reuse your [Kimi Code Plan](https://www.kimi.com/code/docs/en/) inside [pi-coding-agent](https://pi.dev/)** — no separate API credits, no second billing dashboard. Every request draws from your Kimi Code membership quota (the 5-hour token bucket) instead of billing per-token on the Moonshot Open Platform.
|
|
6
6
|
|
|
7
|
-
It's **"Claude Code for Kimi"** — log in with your Kimi account, with `KIMI_API_KEY` still supported as a fallback for CI. You get
|
|
7
|
+
It's **"Claude Code for Kimi"** — log in with your Kimi account, with `KIMI_API_KEY` still supported as a fallback for CI. You get `kimi-for-coding` (the Kimi-k2.6 latest alias, backed by the same Kimi Code Plan that covers K2.6 and K2.5) with a 262K-token context window, automatic prompt caching, automatic large-image and video upload, and compatibility with both Anthropic-style and OpenAI-style clients.
|
|
8
8
|
|
|
9
9
|
## Who is this for?
|
|
10
10
|
|
|
11
11
|
- You already pay for a **[Kimi Code Plan](https://www.kimi.com/code/docs/en/)** and want to use it inside `pi-coding-agent` instead of the official `kimi-cli` — see [MoonshotAI/kimi-cli#757](https://github.com/MoonshotAI/kimi-cli/issues/757) for the canonical feature request this extension answers.
|
|
12
12
|
- You want **"Claude Code for Kimi"**: log in with your Kimi account instead of buying separate API credits. (`KIMI_API_KEY` is also supported as a fallback for CI.)
|
|
13
|
-
- You're in the [pi / pi-mono](https://github.com/badlogic/pi-mono) ecosystem and want **Kimi K2.6
|
|
13
|
+
- You're in the [pi / pi-mono](https://github.com/badlogic/pi-mono) ecosystem and want **Kimi K2.6** (and the K2.5 coverage that comes with the Code Plan) as a pi provider.
|
|
14
14
|
|
|
15
15
|
Pay-per-token via `KIMI_API_KEY` also works if you just want to try Kimi in CI or without a subscription.
|
|
16
16
|
|
|
@@ -72,18 +72,16 @@ KIMI_API_KEY=sk-... pi
|
|
|
72
72
|
|
|
73
73
|
## Models
|
|
74
74
|
|
|
75
|
-
| ID
|
|
76
|
-
|
|
|
77
|
-
| `kimi-for-coding`
|
|
78
|
-
| `kimi-k2.6-code-preview` | Kimi K2.6 Code Preview | yes | text, image, video | 262 144 | 32 000 |
|
|
79
|
-
| `kimi-code` | Kimi Code (kimi-k2.5) | yes | text, image | 262 144 | 32 000 |
|
|
80
|
-
| `kimi-k2.5` | Kimi K2.5 | yes | text, image | 262 144 | 32 000 |
|
|
81
|
-
| `kimi-k2-thinking-turbo` | Kimi K2 Thinking Turbo | yes | text | 262 144 | 32 000 |
|
|
75
|
+
| ID | Name | Reasoning | Input | Context | Max Output |
|
|
76
|
+
| ----------------- | --------------- | --------- | ------------------ | ------- | ---------- |
|
|
77
|
+
| `kimi-for-coding` | Kimi for Coding | yes | text, image, video | 262 144 | 32 000 |
|
|
82
78
|
|
|
83
|
-
|
|
79
|
+
`kimi-for-coding` is the latest alias on the Kimi Code Plan — today it points at Kimi-k2.6. The Plan itself still covers K2.5; the upstream routes older model IDs to the current alias, but this provider only publishes the canonical `kimi-for-coding` entry.
|
|
80
|
+
|
|
81
|
+
Select it inside `pi`:
|
|
84
82
|
|
|
85
83
|
```
|
|
86
|
-
/model kimi-coding/kimi-
|
|
84
|
+
/model kimi-coding/kimi-for-coding
|
|
87
85
|
```
|
|
88
86
|
|
|
89
87
|
## Environment variables
|
package/index.ts
CHANGED
|
@@ -29,7 +29,8 @@ import {
|
|
|
29
29
|
streamSimpleOpenAICompletions,
|
|
30
30
|
AssistantMessageEventStream,
|
|
31
31
|
} from "@mariozechner/pi-ai";
|
|
32
|
-
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
32
|
+
import type { ExtensionAPI, OAuthCredential } from "@mariozechner/pi-coding-agent";
|
|
33
|
+
import { AuthStorage } from "@mariozechner/pi-coding-agent";
|
|
33
34
|
|
|
34
35
|
// =============================================================================
|
|
35
36
|
// Constants
|
|
@@ -709,6 +710,54 @@ async function* filterEmptyResponseStream(
|
|
|
709
710
|
}
|
|
710
711
|
}
|
|
711
712
|
|
|
713
|
+
// =============================================================================
|
|
714
|
+
// Auth refresh: recover from server-side token invalidation
|
|
715
|
+
// =============================================================================
|
|
716
|
+
// pi-coding-agent only refreshes an OAuth token when the locally cached
|
|
717
|
+
// `expires` is in the past. If the server rotates/revokes the access token
|
|
718
|
+
// before that (common with short-lived session tokens), every request keeps
|
|
719
|
+
// returning 401. We detect that situation by inspecting the first event of
|
|
720
|
+
// the upstream stream, force a refresh through AuthStorage (which persists
|
|
721
|
+
// the new credentials under a file lock), and retry once.
|
|
722
|
+
|
|
723
|
+
const PROVIDER_ID = "kimi-coding";
|
|
724
|
+
|
|
725
|
+
async function refreshKimiAuthToken(currentKey: string): Promise<string | null> {
|
|
726
|
+
try {
|
|
727
|
+
const storage = AuthStorage.create();
|
|
728
|
+
const cred = storage.get(PROVIDER_ID);
|
|
729
|
+
if (!cred || cred.type !== "oauth") {
|
|
730
|
+
console.error(
|
|
731
|
+
`[kimi-coding] auth refresh skipped: no OAuth credentials for ${PROVIDER_ID} on disk`,
|
|
732
|
+
);
|
|
733
|
+
return null;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// If disk already has a different valid token (e.g., another process or a
|
|
737
|
+
// previous retry refreshed it while the caller's in-memory cache went
|
|
738
|
+
// stale), reuse it without hitting the OAuth endpoint.
|
|
739
|
+
if (cred.access !== currentKey && Date.now() < cred.expires) {
|
|
740
|
+
console.error("[kimi-coding] auth refresh: reusing newer on-disk token");
|
|
741
|
+
return cred.access;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
console.error("[kimi-coding] auth refresh: requesting new access token");
|
|
745
|
+
const refreshed = await refreshAccessToken(cred.refresh);
|
|
746
|
+
const newCred: OAuthCredential = {
|
|
747
|
+
type: "oauth",
|
|
748
|
+
access: refreshed.access_token,
|
|
749
|
+
refresh: refreshed.refresh_token,
|
|
750
|
+
expires: Date.now() + refreshed.expires_in * 1000,
|
|
751
|
+
};
|
|
752
|
+
storage.set(PROVIDER_ID, newCred);
|
|
753
|
+
console.error("[kimi-coding] auth refresh: new token persisted");
|
|
754
|
+
return newCred.access;
|
|
755
|
+
} catch (err) {
|
|
756
|
+
console.error("[kimi-coding] auth refresh failed:", err);
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
712
761
|
// =============================================================================
|
|
713
762
|
// Stream wrapper: orchestrates payload mutation + event filter
|
|
714
763
|
// =============================================================================
|
|
@@ -723,7 +772,7 @@ function streamSimpleKimi(
|
|
|
723
772
|
options?: SimpleStreamOptions,
|
|
724
773
|
): AssistantMessageEventStream {
|
|
725
774
|
const filtered = new AssistantMessageEventStream();
|
|
726
|
-
const
|
|
775
|
+
const initialKey = options?.apiKey || process.env.KIMI_API_KEY || "";
|
|
727
776
|
|
|
728
777
|
const cacheKeyOverride = (
|
|
729
778
|
options as (SimpleStreamOptions & { prompt_cache_key?: unknown }) | undefined
|
|
@@ -731,61 +780,105 @@ function streamSimpleKimi(
|
|
|
731
780
|
const cacheKey = (typeof cacheKeyOverride === "string" && cacheKeyOverride) || options?.sessionId;
|
|
732
781
|
const cacheRetention = resolveCacheRetention(options?.cacheRetention);
|
|
733
782
|
const envOverrides = readEnvOverrides();
|
|
734
|
-
const upload: Uploader | undefined = apiKey
|
|
735
|
-
? (mimeType, data) => uploadKimiFile(apiKey, mimeType, data)
|
|
736
|
-
: undefined;
|
|
737
|
-
|
|
738
783
|
const originalOnPayload = options?.onPayload;
|
|
739
|
-
const patchedOptions: SimpleStreamOptions = {
|
|
740
|
-
...options,
|
|
741
|
-
onPayload: async (payload, modelData) => {
|
|
742
|
-
let nextPayload: unknown = payload;
|
|
743
|
-
|
|
744
|
-
if (isRecord(nextPayload)) {
|
|
745
|
-
await applyKimiPayloadMutations(nextPayload, {
|
|
746
|
-
api: model.api,
|
|
747
|
-
upload,
|
|
748
|
-
cacheKey,
|
|
749
|
-
cacheRetention,
|
|
750
|
-
reasoning: options?.reasoning,
|
|
751
|
-
envOverrides,
|
|
752
|
-
});
|
|
753
|
-
}
|
|
754
784
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
785
|
+
const buildPatchedOptions = (apiKey: string): SimpleStreamOptions => {
|
|
786
|
+
const upload: Uploader | undefined = apiKey
|
|
787
|
+
? (mimeType, data) => uploadKimiFile(apiKey, mimeType, data)
|
|
788
|
+
: undefined;
|
|
789
|
+
return {
|
|
790
|
+
...options,
|
|
791
|
+
apiKey,
|
|
792
|
+
onPayload: async (payload, modelData) => {
|
|
793
|
+
let nextPayload: unknown = payload;
|
|
794
|
+
|
|
795
|
+
if (isRecord(nextPayload)) {
|
|
796
|
+
await applyKimiPayloadMutations(nextPayload, {
|
|
797
|
+
api: model.api,
|
|
798
|
+
upload,
|
|
799
|
+
cacheKey,
|
|
800
|
+
cacheRetention,
|
|
801
|
+
reasoning: options?.reasoning,
|
|
802
|
+
envOverrides,
|
|
803
|
+
});
|
|
804
|
+
}
|
|
759
805
|
|
|
760
|
-
|
|
761
|
-
|
|
806
|
+
if (originalOnPayload) {
|
|
807
|
+
const res = await originalOnPayload(nextPayload, modelData);
|
|
808
|
+
if (res !== undefined) nextPayload = res;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
return nextPayload;
|
|
812
|
+
},
|
|
813
|
+
};
|
|
762
814
|
};
|
|
763
815
|
|
|
764
816
|
void (async () => {
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
817
|
+
let attempt = 0;
|
|
818
|
+
let currentKey = initialKey;
|
|
819
|
+
|
|
820
|
+
while (true) {
|
|
821
|
+
const patchedOptions = buildPatchedOptions(currentKey);
|
|
822
|
+
const upstream =
|
|
823
|
+
model.api === "openai-completions"
|
|
824
|
+
? streamSimpleOpenAICompletions(
|
|
825
|
+
model as Model<"openai-completions">,
|
|
826
|
+
context,
|
|
827
|
+
patchedOptions,
|
|
828
|
+
)
|
|
829
|
+
: streamSimpleAnthropic(model as Model<"anthropic-messages">, context, patchedOptions);
|
|
830
|
+
|
|
831
|
+
let pushedAny = false;
|
|
832
|
+
let shouldRetry = false;
|
|
833
|
+
|
|
834
|
+
try {
|
|
835
|
+
for await (const event of filterEmptyResponseStream(upstream)) {
|
|
836
|
+
// If the upstream terminates with an error before we've emitted
|
|
837
|
+
// anything downstream, speculatively try an OAuth refresh and
|
|
838
|
+
// retry once. This handles the common case of a server-side token
|
|
839
|
+
// invalidation before the local `expires` lapses, without having
|
|
840
|
+
// to pattern-match every possible error-message format.
|
|
841
|
+
//
|
|
842
|
+
// Non-auth errors (overflow, network, rate-limit, etc.) still
|
|
843
|
+
// trigger one wasted refresh, but the retried request fails the
|
|
844
|
+
// same way and we forward it — pi-coding-agent's own recovery
|
|
845
|
+
// paths (compaction, retry) take over from there.
|
|
846
|
+
if (!pushedAny && attempt === 0 && event.type === "error") {
|
|
847
|
+
console.error(
|
|
848
|
+
`[kimi-coding] upstream error on first event, attempting refresh: ${event.error?.errorMessage?.slice(0, 200)}`,
|
|
849
|
+
);
|
|
850
|
+
const refreshed = await refreshKimiAuthToken(currentKey);
|
|
851
|
+
if (refreshed && refreshed !== currentKey) {
|
|
852
|
+
console.error("[kimi-coding] retrying stream with refreshed token");
|
|
853
|
+
currentKey = refreshed;
|
|
854
|
+
shouldRetry = true;
|
|
855
|
+
break;
|
|
856
|
+
}
|
|
857
|
+
console.error(
|
|
858
|
+
"[kimi-coding] refresh did not yield a new token, forwarding original error",
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
filtered.push(event);
|
|
862
|
+
pushedAny = true;
|
|
863
|
+
}
|
|
864
|
+
} catch (err) {
|
|
865
|
+
console.error("[kimi-coding] stream error:", err);
|
|
866
|
+
filtered.push({
|
|
867
|
+
type: "error",
|
|
868
|
+
reason: "error",
|
|
869
|
+
error: {
|
|
870
|
+
content: [],
|
|
871
|
+
stopReason: "error",
|
|
872
|
+
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0 },
|
|
873
|
+
},
|
|
874
|
+
} as AssistantMessageEvent & { type: "error" });
|
|
777
875
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
content: [],
|
|
785
|
-
stopReason: "error",
|
|
786
|
-
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0 },
|
|
787
|
-
},
|
|
788
|
-
} as AssistantMessageEvent & { type: "error" });
|
|
876
|
+
|
|
877
|
+
if (shouldRetry) {
|
|
878
|
+
attempt++;
|
|
879
|
+
continue;
|
|
880
|
+
}
|
|
881
|
+
break;
|
|
789
882
|
}
|
|
790
883
|
})();
|
|
791
884
|
|
|
@@ -815,42 +908,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
815
908
|
contextWindow: 262144,
|
|
816
909
|
maxTokens: 32000,
|
|
817
910
|
},
|
|
818
|
-
{
|
|
819
|
-
id: "kimi-k2.6-code-preview",
|
|
820
|
-
name: "Kimi K2.6 Code Preview",
|
|
821
|
-
reasoning: true,
|
|
822
|
-
input: ["text", "image", "video"],
|
|
823
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
824
|
-
contextWindow: 262144,
|
|
825
|
-
maxTokens: 32000,
|
|
826
|
-
},
|
|
827
|
-
{
|
|
828
|
-
id: "kimi-code",
|
|
829
|
-
name: "Kimi Code (kimi-k2.5)",
|
|
830
|
-
reasoning: true,
|
|
831
|
-
input: ["text", "image"],
|
|
832
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
833
|
-
contextWindow: 262144,
|
|
834
|
-
maxTokens: 32000,
|
|
835
|
-
},
|
|
836
|
-
{
|
|
837
|
-
id: "kimi-k2.5",
|
|
838
|
-
name: "Kimi K2.5",
|
|
839
|
-
reasoning: true,
|
|
840
|
-
input: ["text", "image"],
|
|
841
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
842
|
-
contextWindow: 262144,
|
|
843
|
-
maxTokens: 32000,
|
|
844
|
-
},
|
|
845
|
-
{
|
|
846
|
-
id: "kimi-k2-thinking-turbo",
|
|
847
|
-
name: "Kimi K2 Thinking Turbo",
|
|
848
|
-
reasoning: true,
|
|
849
|
-
input: ["text"],
|
|
850
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
851
|
-
contextWindow: 262144,
|
|
852
|
-
maxTokens: 32000,
|
|
853
|
-
},
|
|
854
911
|
],
|
|
855
912
|
|
|
856
913
|
oauth: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-provider-kimi-code",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Pi extension for Kimi Code — reuse your Kimi Code Plan in pi-coding-agent (K2.6, K2.5)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -14,13 +14,11 @@
|
|
|
14
14
|
"llm",
|
|
15
15
|
"llm-provider",
|
|
16
16
|
"kimi",
|
|
17
|
-
"kimi-code",
|
|
18
17
|
"kimi-for-coding",
|
|
19
18
|
"kimi-coder",
|
|
20
19
|
"kimi-k2",
|
|
21
20
|
"kimi-k2.6",
|
|
22
21
|
"kimi-k2.5",
|
|
23
|
-
"kimi-k2-thinking",
|
|
24
22
|
"moonshot",
|
|
25
23
|
"moonshotai",
|
|
26
24
|
"oauth",
|