pi-free 2.2.2 → 2.2.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 +18 -39
- package/README.md +41 -532
- package/banner.svg +23 -20
- package/config.ts +774 -702
- package/constants.ts +11 -1
- package/index.ts +432 -419
- package/lib/model-detection.ts +296 -296
- package/lib/model-metadata.ts +10 -3
- package/lib/telemetry.ts +36 -44
- package/package.json +3 -2
- package/provider-failover/benchmark-lookup.ts +30 -15
- package/provider-helper.ts +27 -8
- package/providers/bai/bai.ts +232 -237
- package/providers/cline/cline-xml-bridge.ts +31 -25
- package/providers/cline/cline.ts +17 -8
- package/providers/kilo/kilo.ts +11 -6
- package/providers/model-fetcher.ts +1 -1
- package/providers/opencode-session.ts +2 -2
- package/providers/openmodel/openmodel.ts +525 -0
- package/providers/qoder/auth.ts +548 -0
- package/providers/qoder/cosy.ts +236 -0
- package/providers/qoder/encoding.ts +48 -0
- package/providers/qoder/models.ts +321 -0
- package/providers/qoder/qoder.ts +154 -0
- package/providers/qoder/stream.ts +677 -0
- package/providers/qoder/thinking-parser.ts +251 -0
- package/providers/qoder/transform.ts +189 -0
- package/providers/tokenrouter/tokenrouter.ts +3 -6
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Qoder Provider Extension
|
|
3
|
+
*
|
|
4
|
+
* Registers the Qoder provider with Pi, providing free access to top-tier
|
|
5
|
+
* LLM models (DeepSeek V4 Pro/Flash, Qwen3.7 Plus/Max, GLM 5.1, Kimi K2.6,
|
|
6
|
+
* MiniMax M3) through Qoder's proprietary API.
|
|
7
|
+
*
|
|
8
|
+
* Qoder uses a custom authentication protocol (PAT exchange + COSY signing)
|
|
9
|
+
* and a non-standard streaming API. All models are completely free — no
|
|
10
|
+
* paid tier exists.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* Install pi-free, then run /login qoder to authenticate
|
|
14
|
+
* (PAT paste or browser OAuth)
|
|
15
|
+
*
|
|
16
|
+
* Environment variables:
|
|
17
|
+
* QODER_PERSONAL_ACCESS_TOKEN — PAT for headless auth (optional)
|
|
18
|
+
* QODER_PAT — Alias for above
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { Api, OAuthCredentials } from "@earendil-works/pi-ai";
|
|
22
|
+
import type {
|
|
23
|
+
ExtensionAPI,
|
|
24
|
+
ProviderModelConfig,
|
|
25
|
+
} from "@earendil-works/pi-coding-agent";
|
|
26
|
+
import { BASE_URL_QODER, PROVIDER_QODER } from "../../constants.ts";
|
|
27
|
+
import {
|
|
28
|
+
getCachedModels,
|
|
29
|
+
isCacheStale,
|
|
30
|
+
updateQoderModelsCache,
|
|
31
|
+
} from "./models.ts";
|
|
32
|
+
import { getCachedCredentials, loginQoder, refreshQoderToken } from "./auth.ts";
|
|
33
|
+
import { streamQoder } from "./stream.ts";
|
|
34
|
+
import { enhanceWithCI } from "../../provider-helper.ts";
|
|
35
|
+
import { isFreeModel, registerWithGlobalToggle } from "../../lib/registry.ts";
|
|
36
|
+
|
|
37
|
+
// =============================================================================
|
|
38
|
+
// Extension Entry Point
|
|
39
|
+
// =============================================================================
|
|
40
|
+
|
|
41
|
+
export default async function qoderProvider(pi: ExtensionAPI) {
|
|
42
|
+
const logger = (await import("../../lib/logger.ts")).createLogger("qoder");
|
|
43
|
+
|
|
44
|
+
// Initial model fetch
|
|
45
|
+
let allModels: ProviderModelConfig[] = getCachedModels();
|
|
46
|
+
let freeModels: ProviderModelConfig[] = allModels.filter((m) =>
|
|
47
|
+
isFreeModel({ ...m, provider: PROVIDER_QODER }, allModels),
|
|
48
|
+
);
|
|
49
|
+
const stored = { free: freeModels, all: allModels };
|
|
50
|
+
|
|
51
|
+
// ── OAuth config (defined before reRegister so it's always available) ──
|
|
52
|
+
const oauthConfig = {
|
|
53
|
+
name: "Qoder (Browser OAuth / PAT)",
|
|
54
|
+
login: async (callbacks: any): Promise<OAuthCredentials> => {
|
|
55
|
+
const cred = await loginQoder(callbacks);
|
|
56
|
+
|
|
57
|
+
// After login, refresh models from API
|
|
58
|
+
try {
|
|
59
|
+
const accessToken = cred.access as string;
|
|
60
|
+
const creds = getCachedCredentials();
|
|
61
|
+
await refreshModels(
|
|
62
|
+
accessToken,
|
|
63
|
+
creds?.userID || "qoder-user",
|
|
64
|
+
creds?.name || "Qoder User",
|
|
65
|
+
creds?.email || "user@qoder.com",
|
|
66
|
+
);
|
|
67
|
+
} catch {
|
|
68
|
+
// Best-effort
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return cred;
|
|
72
|
+
},
|
|
73
|
+
refreshToken: refreshQoderToken,
|
|
74
|
+
getApiKey: (cred: OAuthCredentials) => cred.access,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Re-register function — called by toggle, session refresh, login
|
|
78
|
+
const reRegister = (models: ProviderModelConfig[]) => {
|
|
79
|
+
const enhanced = enhanceWithCI(models, PROVIDER_QODER);
|
|
80
|
+
pi.registerProvider(PROVIDER_QODER, {
|
|
81
|
+
baseUrl: BASE_URL_QODER,
|
|
82
|
+
api: "qoder-api" as Api,
|
|
83
|
+
models: enhanced,
|
|
84
|
+
oauth: oauthConfig,
|
|
85
|
+
streamSimple: streamQoder,
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// ── Helper: refresh models from API and re-register ──
|
|
90
|
+
const refreshModels = async (
|
|
91
|
+
accessToken: string,
|
|
92
|
+
userID: string,
|
|
93
|
+
name: string,
|
|
94
|
+
email: string,
|
|
95
|
+
) => {
|
|
96
|
+
await updateQoderModelsCache(accessToken, userID, name, email);
|
|
97
|
+
const fresh = getCachedModels();
|
|
98
|
+
if (fresh.length > 0) {
|
|
99
|
+
allModels = fresh;
|
|
100
|
+
freeModels = fresh.filter((m) =>
|
|
101
|
+
isFreeModel({ ...m, provider: PROVIDER_QODER }, fresh),
|
|
102
|
+
);
|
|
103
|
+
stored.all = allModels;
|
|
104
|
+
stored.free = freeModels;
|
|
105
|
+
reRegister(allModels);
|
|
106
|
+
logger.info(`[qoder] Models refreshed: ${allModels.length}`);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Register with global toggle system so it participates in /toggle-free
|
|
111
|
+
registerWithGlobalToggle(PROVIDER_QODER, stored, (m) => reRegister(m), false);
|
|
112
|
+
|
|
113
|
+
// If user is already authenticated and cache is stale, refresh at startup
|
|
114
|
+
// (mirrors kilo/cline pattern: check cache freshness before hitting network)
|
|
115
|
+
try {
|
|
116
|
+
const cachedCreds = getCachedCredentials();
|
|
117
|
+
if (cachedCreds?.access && isCacheStale()) {
|
|
118
|
+
await refreshModels(
|
|
119
|
+
cachedCreds.access as string,
|
|
120
|
+
cachedCreds.userID || "qoder-user",
|
|
121
|
+
cachedCreds.name || "Qoder User",
|
|
122
|
+
cachedCreds.email || "user@qoder.com",
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
} catch {
|
|
126
|
+
// Best-effort: fall back to cached / static models
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Initial registration
|
|
130
|
+
reRegister(allModels);
|
|
131
|
+
|
|
132
|
+
// Refresh models cache on session_start if stale (>1h old)
|
|
133
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
134
|
+
try {
|
|
135
|
+
const accessToken =
|
|
136
|
+
await ctx.modelRegistry.getApiKeyForProvider(PROVIDER_QODER);
|
|
137
|
+
if (!accessToken || !isCacheStale()) return;
|
|
138
|
+
const creds = getCachedCredentials();
|
|
139
|
+
await refreshModels(
|
|
140
|
+
accessToken,
|
|
141
|
+
creds?.userID || "qoder-user",
|
|
142
|
+
creds?.name || "Qoder User",
|
|
143
|
+
creds?.email || "user@qoder.com",
|
|
144
|
+
);
|
|
145
|
+
} catch {
|
|
146
|
+
// Best-effort: fall back to existing cache / static models
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
logger.info(`[qoder] Provider registered with ${allModels.length} models`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Re-export key symbols for testing
|
|
154
|
+
export { loginQoder, refreshQoderToken, getCachedCredentials, streamQoder };
|