pi-cline 0.1.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 +5 -0
- package/package.json +50 -0
- package/src/api/auth.ts +242 -0
- package/src/api/cline-chat.ts +108 -0
- package/src/api/http.ts +26 -0
- package/src/api/recommended-models.ts +43 -0
- package/src/bridge/cline-to-pi/assistant-output.ts +368 -0
- package/src/bridge/cline-to-pi/map-tool-call.ts +22 -0
- package/src/bridge/cline-to-pi/replace-in-file.ts +64 -0
- package/src/bridge/cline-to-pi/xml.ts +328 -0
- package/src/bridge/pi-to-cline/prompt/environment.ts +185 -0
- package/src/bridge/pi-to-cline/prompt/runtime-info.ts +128 -0
- package/src/bridge/pi-to-cline/prompt/system/templates.ts +1154 -0
- package/src/bridge/pi-to-cline/prompt/system.ts +29 -0
- package/src/bridge/pi-to-cline/prompt/task-message.ts +80 -0
- package/src/bridge/pi-to-cline/request-body.ts +70 -0
- package/src/bridge/pi-to-cline/request-messages.ts +171 -0
- package/src/bridge/pi-to-cline/serialize-tool-call.ts +28 -0
- package/src/bridge/pi-to-cline/tool-result.ts +17 -0
- package/src/bridge/pi-to-cline/tool-results/command.ts +21 -0
- package/src/bridge/pi-to-cline/tool-results/file.ts +74 -0
- package/src/bridge/pi-to-cline/tool-results/noop.ts +8 -0
- package/src/bridge/pi-to-cline/tool-results/shared.ts +24 -0
- package/src/bridge/remote-tools/base.ts +66 -0
- package/src/bridge/remote-tools/completion-tool.ts +18 -0
- package/src/bridge/remote-tools/display-only-tool.ts +16 -0
- package/src/bridge/remote-tools/index.ts +2 -0
- package/src/bridge/remote-tools/noop-prompt-alignment-tool.ts +68 -0
- package/src/bridge/remote-tools/registry.ts +436 -0
- package/src/bridge/remote-tools/replace-in-file-tool.ts +61 -0
- package/src/bridge/remote-tools/simple-executable-tool.ts +54 -0
- package/src/bridge/remote-tools/types.ts +65 -0
- package/src/bridge/shared/command-builders.ts +66 -0
- package/src/bridge/shared/remote-tool.ts +6 -0
- package/src/bridge/shared/tool-names.ts +31 -0
- package/src/index.ts +75 -0
- package/src/lib/abort.ts +28 -0
- package/src/lib/auth/callback-server.ts +225 -0
- package/src/lib/auth/index.ts +156 -0
- package/src/lib/auth/utils.ts +21 -0
- package/src/lib/backoff.ts +35 -0
- package/src/lib/env.ts +32 -0
- package/src/lib/path.ts +19 -0
- package/src/lib/search-replace-diff.ts +66 -0
- package/src/provider/models.ts +180 -0
- package/src/provider/oauth.ts +78 -0
- package/src/provider/state.ts +142 -0
- package/src/provider/stream.ts +296 -0
package/README.md
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-cline",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Cline provider extension for pi",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "sudosubin",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/sudosubin/pi-frontier.git",
|
|
11
|
+
"directory": "pi-cline"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"pi",
|
|
15
|
+
"pi-coding-agent",
|
|
16
|
+
"pi-package",
|
|
17
|
+
"cline"
|
|
18
|
+
],
|
|
19
|
+
"pi": {
|
|
20
|
+
"extensions": [
|
|
21
|
+
"./src/index.ts"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"src/**/*.ts",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"lint": "biome check",
|
|
30
|
+
"format": "biome check --write",
|
|
31
|
+
"typecheck": "tsc",
|
|
32
|
+
"test": "tsx --test $(find test -name '*.test.ts' -o -name '*.test.mts' -o -name '*.test.js' | sort)",
|
|
33
|
+
"record:fixtures": "node --experimental-strip-types scripts/record-cline-fixtures.ts"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"openai": "^6.10.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@biomejs/biome": "^2.4.2",
|
|
40
|
+
"@tsconfig/strictest": "^2.0.8",
|
|
41
|
+
"@types/node": "^24.10.10",
|
|
42
|
+
"cline": "^2.7.0",
|
|
43
|
+
"tsx": "^4.20.6",
|
|
44
|
+
"typescript": "^5.9.3"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"@mariozechner/pi-ai": ">=0.49.0",
|
|
48
|
+
"@mariozechner/pi-coding-agent": ">=0.49.0"
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/api/auth.ts
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { fetchWithTimeout, safeJson } from "./http";
|
|
2
|
+
|
|
3
|
+
interface ClineAuthTokenData {
|
|
4
|
+
accessToken: string;
|
|
5
|
+
refreshToken: string;
|
|
6
|
+
expiresAt: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ClineAuthOrganization {
|
|
10
|
+
active: boolean;
|
|
11
|
+
memberId: string;
|
|
12
|
+
name: string;
|
|
13
|
+
organizationId: string;
|
|
14
|
+
roles: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ClineAuthUserInfo {
|
|
18
|
+
id: string;
|
|
19
|
+
email: string;
|
|
20
|
+
displayName: string;
|
|
21
|
+
createdAt: string;
|
|
22
|
+
organizations: ClineAuthOrganization[];
|
|
23
|
+
appBaseUrl?: string;
|
|
24
|
+
subject?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface ClineTokenEnvelope {
|
|
28
|
+
success?: boolean;
|
|
29
|
+
data?: {
|
|
30
|
+
accessToken?: string;
|
|
31
|
+
refreshToken?: string;
|
|
32
|
+
expiresAt?: string;
|
|
33
|
+
};
|
|
34
|
+
redirect_url?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface ClineUserEnvelope {
|
|
38
|
+
success?: boolean;
|
|
39
|
+
data?: ClineAuthUserInfo;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const AUTHORIZE_TIMEOUT_MS = 8000;
|
|
43
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 15000;
|
|
44
|
+
|
|
45
|
+
const toWorkosBearerToken = (accessToken: string): string => {
|
|
46
|
+
const normalized = accessToken.startsWith("workos:")
|
|
47
|
+
? accessToken
|
|
48
|
+
: `workos:${accessToken}`;
|
|
49
|
+
return `Bearer ${normalized}`;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const trimTrailingSlashes = (value: string): string => {
|
|
53
|
+
return value.replace(/\/+$/, "");
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export interface GetAuthorizeUrlParams {
|
|
57
|
+
callbackUrl: string;
|
|
58
|
+
headers: Record<string, string>;
|
|
59
|
+
signal?: AbortSignal;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface ExchangeAuthorizationCodeParams {
|
|
63
|
+
code: string;
|
|
64
|
+
callbackUrl: string;
|
|
65
|
+
provider: string | null;
|
|
66
|
+
headers: Record<string, string>;
|
|
67
|
+
signal?: AbortSignal;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface RefreshAccessTokenParams {
|
|
71
|
+
refreshToken: string;
|
|
72
|
+
headers: Record<string, string>;
|
|
73
|
+
signal?: AbortSignal;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface GetCurrentUserParams {
|
|
77
|
+
accessToken: string;
|
|
78
|
+
headers: Record<string, string>;
|
|
79
|
+
signal?: AbortSignal;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export class ClineAuthApi {
|
|
83
|
+
private readonly baseUrl: string;
|
|
84
|
+
|
|
85
|
+
constructor(baseUrl: string) {
|
|
86
|
+
this.baseUrl = trimTrailingSlashes(baseUrl);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async getAuthorizeUrl({
|
|
90
|
+
callbackUrl,
|
|
91
|
+
headers,
|
|
92
|
+
signal,
|
|
93
|
+
}: GetAuthorizeUrlParams): Promise<string> {
|
|
94
|
+
const url = this.buildApiUrl("auth/authorize");
|
|
95
|
+
url.searchParams.set("client_type", "extension");
|
|
96
|
+
url.searchParams.set("callback_url", callbackUrl);
|
|
97
|
+
url.searchParams.set("redirect_uri", callbackUrl);
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const response = await fetchWithTimeout(url, AUTHORIZE_TIMEOUT_MS, {
|
|
101
|
+
method: "GET",
|
|
102
|
+
redirect: "manual",
|
|
103
|
+
credentials: "include",
|
|
104
|
+
headers,
|
|
105
|
+
signal: signal ?? null,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if (response.status >= 300 && response.status < 400) {
|
|
109
|
+
const location = response.headers.get("Location");
|
|
110
|
+
if (location) {
|
|
111
|
+
return location;
|
|
112
|
+
}
|
|
113
|
+
throw new Error("No redirect URL found in auth response");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const payload = await safeJson<ClineTokenEnvelope>(response);
|
|
117
|
+
if (
|
|
118
|
+
typeof payload?.redirect_url === "string" &&
|
|
119
|
+
payload.redirect_url.length > 0
|
|
120
|
+
) {
|
|
121
|
+
return payload.redirect_url;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
throw new Error("Unexpected response from auth server");
|
|
125
|
+
} catch (error) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
`Authentication request failed: ${error instanceof Error ? error.message : "unknown error"}`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async exchangeAuthorizationCode({
|
|
133
|
+
code,
|
|
134
|
+
callbackUrl,
|
|
135
|
+
provider,
|
|
136
|
+
headers,
|
|
137
|
+
signal,
|
|
138
|
+
}: ExchangeAuthorizationCodeParams): Promise<ClineAuthTokenData> {
|
|
139
|
+
const response = await this.request("auth/token", {
|
|
140
|
+
method: "POST",
|
|
141
|
+
headers,
|
|
142
|
+
signal: signal ?? null,
|
|
143
|
+
body: JSON.stringify({
|
|
144
|
+
grant_type: "authorization_code",
|
|
145
|
+
code,
|
|
146
|
+
client_type: "extension",
|
|
147
|
+
redirect_uri: callbackUrl,
|
|
148
|
+
provider,
|
|
149
|
+
}),
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
return await this.parseTokenResponse(response, "token exchange");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async refreshAccessToken({
|
|
156
|
+
refreshToken,
|
|
157
|
+
headers,
|
|
158
|
+
signal,
|
|
159
|
+
}: RefreshAccessTokenParams): Promise<ClineAuthTokenData> {
|
|
160
|
+
const response = await this.request("auth/refresh", {
|
|
161
|
+
method: "POST",
|
|
162
|
+
headers,
|
|
163
|
+
signal: signal ?? null,
|
|
164
|
+
body: JSON.stringify({ refreshToken, grantType: "refresh_token" }),
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
return await this.parseTokenResponse(response, "token refresh");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async getCurrentUser({
|
|
171
|
+
accessToken,
|
|
172
|
+
headers,
|
|
173
|
+
signal,
|
|
174
|
+
}: GetCurrentUserParams): Promise<ClineAuthUserInfo> {
|
|
175
|
+
const response = await this.request("users/me", {
|
|
176
|
+
method: "GET",
|
|
177
|
+
headers: { ...headers, Authorization: toWorkosBearerToken(accessToken) },
|
|
178
|
+
signal: signal ?? null,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const payload = await safeJson<ClineUserEnvelope>(response);
|
|
182
|
+
if (!response.ok) {
|
|
183
|
+
const errorBody = payload
|
|
184
|
+
? JSON.stringify(payload)
|
|
185
|
+
: "invalid json response";
|
|
186
|
+
throw new Error(
|
|
187
|
+
`Cline current user lookup failed: ${response.status} ${errorBody}`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!payload?.data) {
|
|
192
|
+
throw new Error("Cline current user lookup returned invalid response");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return payload.data;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private buildApiUrl(path: string): URL {
|
|
199
|
+
const normalizedPath = path.replace(/^\/+/, "");
|
|
200
|
+
return new URL(normalizedPath, `${this.baseUrl}/`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private async request(path: string, init?: RequestInit): Promise<Response> {
|
|
204
|
+
return await fetchWithTimeout(
|
|
205
|
+
this.buildApiUrl(path),
|
|
206
|
+
DEFAULT_REQUEST_TIMEOUT_MS,
|
|
207
|
+
init,
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private async parseTokenResponse(
|
|
212
|
+
response: Response,
|
|
213
|
+
context: string,
|
|
214
|
+
): Promise<ClineAuthTokenData> {
|
|
215
|
+
const payload = await safeJson<ClineTokenEnvelope>(response);
|
|
216
|
+
|
|
217
|
+
if (!response.ok) {
|
|
218
|
+
const errorBody = payload
|
|
219
|
+
? JSON.stringify(payload)
|
|
220
|
+
: "invalid json response";
|
|
221
|
+
throw new Error(
|
|
222
|
+
`Cline ${context} failed: ${response.status} ${errorBody}`,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const data = payload?.data;
|
|
227
|
+
if (
|
|
228
|
+
!payload?.success ||
|
|
229
|
+
!data?.accessToken ||
|
|
230
|
+
!data?.refreshToken ||
|
|
231
|
+
!data.expiresAt
|
|
232
|
+
) {
|
|
233
|
+
throw new Error(`Cline ${context} returned invalid response`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
accessToken: data.accessToken,
|
|
238
|
+
refreshToken: data.refreshToken,
|
|
239
|
+
expiresAt: data.expiresAt,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
import { APIError, APIUserAbortError } from "openai/error";
|
|
3
|
+
import type {
|
|
4
|
+
ChatCompletionChunk,
|
|
5
|
+
ChatCompletionCreateParamsStreaming,
|
|
6
|
+
} from "openai/resources/chat/completions";
|
|
7
|
+
import { backoff } from "../lib/backoff";
|
|
8
|
+
import {
|
|
9
|
+
CLINE_API_BASE_URL,
|
|
10
|
+
CLINE_REQUEST_TIMEOUT_MS,
|
|
11
|
+
CLINE_RETRY_COUNT,
|
|
12
|
+
CLINE_RETRY_DELAY_MS,
|
|
13
|
+
} from "../lib/env";
|
|
14
|
+
|
|
15
|
+
export interface ClineChatMessage {
|
|
16
|
+
role: "assistant" | "system" | "tool" | "user";
|
|
17
|
+
content: string | Array<{ type: "text"; text: string }>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ClineChatCompletionRequest {
|
|
21
|
+
model: string;
|
|
22
|
+
temperature: number;
|
|
23
|
+
messages: ClineChatMessage[];
|
|
24
|
+
stream: true;
|
|
25
|
+
stream_options: { include_usage: true };
|
|
26
|
+
include_reasoning: true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type ClineRequestHeaders = Record<string, string>;
|
|
30
|
+
|
|
31
|
+
interface ClineChunkError {
|
|
32
|
+
message?: string;
|
|
33
|
+
code?: string;
|
|
34
|
+
metadata?: unknown;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type ClineChatCompletionChoice = Omit<
|
|
38
|
+
ChatCompletionChunk.Choice,
|
|
39
|
+
"delta" | "finish_reason"
|
|
40
|
+
> & {
|
|
41
|
+
delta: ChatCompletionChunk.Choice.Delta & {
|
|
42
|
+
reasoning?: string | null;
|
|
43
|
+
};
|
|
44
|
+
finish_reason: ChatCompletionChunk.Choice["finish_reason"] | "error";
|
|
45
|
+
error?: ClineChunkError;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export interface ClineChatCompletionChunk extends Omit<
|
|
49
|
+
ChatCompletionChunk,
|
|
50
|
+
"choices"
|
|
51
|
+
> {
|
|
52
|
+
error?: ClineChunkError;
|
|
53
|
+
choices: ClineChatCompletionChoice[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface CreateChatCompletionStreamArgs {
|
|
57
|
+
apiKey: string;
|
|
58
|
+
headers: ClineRequestHeaders;
|
|
59
|
+
body: ClineChatCompletionRequest;
|
|
60
|
+
signal?: AbortSignal;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ClineChatTransport {
|
|
64
|
+
createChatCompletionStream(
|
|
65
|
+
args: CreateChatCompletionStreamArgs,
|
|
66
|
+
): Promise<AsyncIterable<ClineChatCompletionChunk>>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const createClient = (apiKey: string, headers: ClineRequestHeaders) =>
|
|
70
|
+
new OpenAI({
|
|
71
|
+
apiKey,
|
|
72
|
+
baseURL: CLINE_API_BASE_URL,
|
|
73
|
+
defaultHeaders: headers,
|
|
74
|
+
maxRetries: 0,
|
|
75
|
+
timeout: CLINE_REQUEST_TIMEOUT_MS,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
function shouldRetryRequest(error: unknown) {
|
|
79
|
+
if (error instanceof APIUserAbortError) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!(error instanceof APIError)) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const status = error.status;
|
|
88
|
+
return status === undefined || status === 429 || status >= 500;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const defaultClineChatTransport: ClineChatTransport = {
|
|
92
|
+
async createChatCompletionStream({ apiKey, body, headers, signal }) {
|
|
93
|
+
return backoff(
|
|
94
|
+
async () => {
|
|
95
|
+
const client = createClient(apiKey, headers);
|
|
96
|
+
return await client.chat.completions.create(
|
|
97
|
+
body as ChatCompletionCreateParamsStreaming,
|
|
98
|
+
{ signal, maxRetries: 0, timeout: CLINE_REQUEST_TIMEOUT_MS },
|
|
99
|
+
);
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
retries: CLINE_RETRY_COUNT,
|
|
103
|
+
delayMs: CLINE_RETRY_DELAY_MS,
|
|
104
|
+
shouldRetry: (error) => !signal?.aborted && shouldRetryRequest(error),
|
|
105
|
+
},
|
|
106
|
+
);
|
|
107
|
+
},
|
|
108
|
+
};
|
package/src/api/http.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const fetchWithTimeout = async (
|
|
2
|
+
input: string | URL,
|
|
3
|
+
timeoutMs: number,
|
|
4
|
+
init?: RequestInit,
|
|
5
|
+
): Promise<Response> => {
|
|
6
|
+
const timeoutController = new AbortController();
|
|
7
|
+
const timeout = setTimeout(() => timeoutController.abort(), timeoutMs);
|
|
8
|
+
|
|
9
|
+
const signal = init?.signal
|
|
10
|
+
? AbortSignal.any([init.signal, timeoutController.signal])
|
|
11
|
+
: timeoutController.signal;
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
return await fetch(input, { ...init, signal });
|
|
15
|
+
} finally {
|
|
16
|
+
clearTimeout(timeout);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const safeJson = async <T>(response: Response): Promise<T | null> => {
|
|
21
|
+
try {
|
|
22
|
+
return (await response.json()) as T;
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { backoff } from "../lib/backoff";
|
|
2
|
+
import {
|
|
3
|
+
CLINE_RECOMMENDED_MODELS_URL,
|
|
4
|
+
CLINE_RETRY_COUNT,
|
|
5
|
+
CLINE_RETRY_DELAY_MS,
|
|
6
|
+
} from "../lib/env";
|
|
7
|
+
|
|
8
|
+
export interface ClineRecommendedModel {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
tags: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ClineRecommendedModelsData {
|
|
16
|
+
recommended: ClineRecommendedModel[];
|
|
17
|
+
free: ClineRecommendedModel[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function fetchRecommendedModels(): Promise<ClineRecommendedModelsData> {
|
|
21
|
+
return backoff(
|
|
22
|
+
async () => {
|
|
23
|
+
const response = await fetch(CLINE_RECOMMENDED_MODELS_URL, {
|
|
24
|
+
method: "GET",
|
|
25
|
+
headers: { Accept: "application/json" },
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`Recommended models request failed: ${response.status} ${response.statusText}`,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const models = (await response.json()) as ClineRecommendedModelsData;
|
|
35
|
+
if (!models || !models.free) {
|
|
36
|
+
throw new Error("Invalid recommended models response");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return models;
|
|
40
|
+
},
|
|
41
|
+
{ retries: CLINE_RETRY_COUNT, delayMs: CLINE_RETRY_DELAY_MS },
|
|
42
|
+
);
|
|
43
|
+
}
|