pi-provider-kimi-code 0.2.1 → 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 +13 -13
- package/index.ts +146 -71
- package/package.json +3 -4
package/README.md
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
[](https://www.npmjs.com/package/pi-provider-kimi-code)
|
|
3
3
|
[](./LICENSE)
|
|
4
4
|
|
|
5
|
-
**Reuse your [Kimi Code
|
|
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 Kimi
|
|
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
|
-
- You already pay for a **[Kimi Code
|
|
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.
|
|
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
|
|
|
17
17
|
## Features
|
|
18
18
|
|
|
19
19
|
- **Browser login with your Kimi account** — reuse your Kimi Code Membership without buying separate API credits. Credentials are stored locally and refreshed automatically. `KIMI_API_KEY` is also accepted for pay-per-token or CI use.
|
|
20
|
-
- **262K-token context window** on all
|
|
20
|
+
- **262K-token context window** on all registered models.
|
|
21
21
|
- **Automatic prompt caching** — binds Kimi's prompt cache to your pi session so repeated calls hit the cache cheaply (observed TTL ~5-10 minutes). Honors pi's `PI_CACHE_RETENTION=none` if you want to disable caching entirely.
|
|
22
22
|
- **Automatic large-image and video upload** — images over 1 MB and all videos are uploaded to Kimi ahead of time, so you don't hit inline payload limits.
|
|
23
23
|
- **Works with both Anthropic- and OpenAI-compatible modes** — use whichever one your pi setup expects.
|
|
@@ -47,7 +47,7 @@ If you prefer not to use npm, download the tarball from the [latest release](htt
|
|
|
47
47
|
curl -L https://github.com/Leechael/pi-provider-kimi-code/releases/latest/download/pi-provider-kimi-code.tar.gz | tar -xz -C /tmp
|
|
48
48
|
|
|
49
49
|
# Install from the extracted directory
|
|
50
|
-
pi install /tmp/
|
|
50
|
+
pi install /tmp/pi-provider-kimi-code
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
## Authentication
|
|
@@ -72,16 +72,16 @@ KIMI_API_KEY=sk-... pi
|
|
|
72
72
|
|
|
73
73
|
## Models
|
|
74
74
|
|
|
75
|
-
| ID
|
|
76
|
-
|
|
|
77
|
-
| `kimi-
|
|
78
|
-
| `kimi-k2.5` | Kimi K2.5 | yes | text, image | 262 144 | 32 000 |
|
|
79
|
-
| `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 |
|
|
80
78
|
|
|
81
|
-
|
|
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`:
|
|
82
82
|
|
|
83
83
|
```
|
|
84
|
-
/model kimi-coding/kimi-
|
|
84
|
+
/model kimi-coding/kimi-for-coding
|
|
85
85
|
```
|
|
86
86
|
|
|
87
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
|
|
|
@@ -807,28 +900,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
807
900
|
|
|
808
901
|
models: [
|
|
809
902
|
{
|
|
810
|
-
id: "kimi-
|
|
811
|
-
name: "Kimi
|
|
812
|
-
reasoning: true,
|
|
813
|
-
input: ["text", "image"],
|
|
814
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
815
|
-
contextWindow: 262144,
|
|
816
|
-
maxTokens: 32000,
|
|
817
|
-
},
|
|
818
|
-
{
|
|
819
|
-
id: "kimi-k2.5",
|
|
820
|
-
name: "Kimi K2.5",
|
|
821
|
-
reasoning: true,
|
|
822
|
-
input: ["text", "image"],
|
|
823
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
824
|
-
contextWindow: 262144,
|
|
825
|
-
maxTokens: 32000,
|
|
826
|
-
},
|
|
827
|
-
{
|
|
828
|
-
id: "kimi-k2-thinking-turbo",
|
|
829
|
-
name: "Kimi K2 Thinking Turbo",
|
|
903
|
+
id: "kimi-for-coding",
|
|
904
|
+
name: "Kimi for Coding",
|
|
830
905
|
reasoning: true,
|
|
831
|
-
input: ["text"],
|
|
906
|
+
input: ["text", "image", "video"],
|
|
832
907
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
833
908
|
contextWindow: 262144,
|
|
834
909
|
maxTokens: 32000,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-provider-kimi-code",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Pi extension for Kimi Code — reuse your
|
|
3
|
+
"version": "0.3.0",
|
|
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",
|
|
7
7
|
"pi-extension",
|
|
@@ -14,12 +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",
|
|
20
|
+
"kimi-k2.6",
|
|
21
21
|
"kimi-k2.5",
|
|
22
|
-
"kimi-k2-thinking",
|
|
23
22
|
"moonshot",
|
|
24
23
|
"moonshotai",
|
|
25
24
|
"oauth",
|