Skip to content

Commit ea032d4

Browse files
Lee-Si-Yoonsiyoonedelauna
authored
feat(friendli): add Friendli provider with GLM-5.2 support (#721)
* feat(friendli): add provider + 4 models Friendli OpenAI-compatible provider. Models: GLM-5.2, GLM-5.1, DeepSeek-V3.2, MiniMax-M2.5. Pricing from friendli.ai pricing API. Mirror fireworks provider pattern (commit 4a9222b): - types: model info, schema, secret key, provider name - api: BaseOpenAiCompatibleProvider handler at api.friendli.ai/serverless/v1 - webview: provider UI, model picker, validation, i18n (18 locales) - tests: handler spec, ProfileValidator entry Reasoning streams back via base handler's reasoning_content extraction. Friendli defaults parse_reasoning=true for GLM reasoning models. * docs(friendli): add docstrings to handler and UI component + revert bug_report.yml Friendli entry * docs(friendli): add docstrings to handler class and settings component * test(friendli): add wiring coverage for validate, useSelectedModel, Friendli.tsx * test(friendli): cover onInput, ApiOptions wiring, buildApiHandler switch * test: add friendli to SettingsView change-detection mock provider list * test(friendli): assert supportsMaxTokens/cacheWritesPrice, cover GLM-5.1 clamping path - Add supportsMaxTokens and cacheWritesPrice assertions to GLM-5.2 single test and it.each model table - Add getModelMaxOutputTokens integration tests verifying: - GLM-5.2: 131k < 20% of 1M context → clamp no-op - GLM-5.1: 131k > 20% of 200k context → clamp binds to 40k - GLM-5.1 with user modelMaxTokens: honors override capped at model maxTokens - Cast friendliModels[modelId] to ModelInfo to avoid union-type narrowing issue with as const satisfies * test: restrict webview-ui workers in CI to prevent JSDOM CPU starvation --------- Co-authored-by: siyoon <leesiyoon0120@gmail.com> Co-authored-by: Elliott de Launay <edelaunay@wealthsimple.com>
1 parent 15d4d8c commit ea032d4

41 files changed

Lines changed: 786 additions & 1 deletion

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/types/src/global-settings.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ export const SECRET_STATE_KEYS = [
316316
"sambaNovaApiKey",
317317
"zaiApiKey",
318318
"fireworksApiKey",
319+
"friendliApiKey",
319320
"vercelAiGatewayApiKey",
320321
"opencodeGoApiKey",
321322
"basetenApiKey",

packages/types/src/provider-settings.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
bedrockModels,
99
deepSeekModels,
1010
fireworksModels,
11+
friendliModels,
1112
geminiModels,
1213
mistralModels,
1314
moonshotModels,
@@ -119,6 +120,7 @@ export const providerNames = [
119120
"baseten",
120121
"deepseek",
121122
"fireworks",
123+
"friendli",
122124
"gemini",
123125
"gemini-cli",
124126
"mistral",
@@ -393,6 +395,10 @@ const fireworksSchema = apiModelIdProviderModelSchema.extend({
393395
fireworksApiKey: z.string().optional(),
394396
})
395397

398+
const friendliSchema = apiModelIdProviderModelSchema.extend({
399+
friendliApiKey: z.string().optional(),
400+
})
401+
396402
const qwenCodeSchema = apiModelIdProviderModelSchema.extend({
397403
qwenCodeOauthPath: z.string().optional(),
398404
})
@@ -449,6 +455,7 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv
449455
sambaNovaSchema.merge(z.object({ apiProvider: z.literal("sambanova") })),
450456
zaiSchema.merge(z.object({ apiProvider: z.literal("zai") })),
451457
fireworksSchema.merge(z.object({ apiProvider: z.literal("fireworks") })),
458+
friendliSchema.merge(z.object({ apiProvider: z.literal("friendli") })),
452459
qwenCodeSchema.merge(z.object({ apiProvider: z.literal("qwen-code") })),
453460
vercelAiGatewaySchema.merge(z.object({ apiProvider: z.literal("vercel-ai-gateway") })),
454461
opencodeGoSchema.merge(z.object({ apiProvider: z.literal("opencode-go") })),
@@ -485,6 +492,7 @@ export const providerSettingsSchema = z.object({
485492
...sambaNovaSchema.shape,
486493
...zaiSchema.shape,
487494
...fireworksSchema.shape,
495+
...friendliSchema.shape,
488496
...qwenCodeSchema.shape,
489497
...vercelAiGatewaySchema.shape,
490498
...opencodeGoSchema.shape,
@@ -565,6 +573,7 @@ export const modelIdKeysByProvider: Record<TypicalProvider, ModelIdKey> = {
565573
sambanova: "apiModelId",
566574
zai: "apiModelId",
567575
fireworks: "apiModelId",
576+
friendli: "apiModelId",
568577
"vercel-ai-gateway": "vercelAiGatewayModelId",
569578
"opencode-go": "opencodeGoModelId",
570579
"zoo-gateway": "zooGatewayModelId",
@@ -638,6 +647,11 @@ export const MODELS_BY_PROVIDER: Record<
638647
label: "Fireworks",
639648
models: Object.keys(fireworksModels),
640649
},
650+
friendli: {
651+
id: "friendli",
652+
label: "Friendli",
653+
models: Object.keys(friendliModels),
654+
},
641655
gemini: {
642656
id: "gemini",
643657
label: "Google Gemini",
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import type { ModelInfo } from "../model.js"
2+
3+
export type FriendliModelId =
4+
| "zai-org/GLM-5.2"
5+
| "zai-org/GLM-5.1"
6+
| "deepseek-ai/DeepSeek-V3.2"
7+
| "MiniMaxAI/MiniMax-M2.5"
8+
9+
export const friendliDefaultModelId: FriendliModelId = "zai-org/GLM-5.2"
10+
11+
// Pricing sourced from https://friendli.ai/api/public/model-apis (per 1M tokens).
12+
export const friendliModels = {
13+
"zai-org/GLM-5.2": {
14+
maxTokens: 131_072,
15+
contextWindow: 1_000_000,
16+
supportsImages: false,
17+
supportsPromptCache: true,
18+
supportsMaxTokens: true,
19+
inputPrice: 1.4,
20+
outputPrice: 4.4,
21+
cacheWritesPrice: 0,
22+
cacheReadsPrice: 0.26,
23+
description:
24+
"GLM-5.2 is Zhipu's flagship model with a 1M context window and 128k max output, served via Friendli Model APIs. It delivers top-tier long-context reasoning, coding, and agentic performance for extended engineering sessions.",
25+
},
26+
"zai-org/GLM-5.1": {
27+
maxTokens: 131_072,
28+
contextWindow: 200_000,
29+
supportsImages: false,
30+
supportsPromptCache: true,
31+
supportsMaxTokens: true,
32+
inputPrice: 1.4,
33+
outputPrice: 4.4,
34+
cacheWritesPrice: 0,
35+
cacheReadsPrice: 0.26,
36+
description:
37+
"GLM-5.1 is Zhipu's most capable model with a 200k context window and 128k max output, served via Friendli Model APIs. It delivers top-tier reasoning, coding, and agentic performance.",
38+
},
39+
"deepseek-ai/DeepSeek-V3.2": {
40+
maxTokens: 16384,
41+
contextWindow: 163_840,
42+
supportsImages: false,
43+
supportsPromptCache: true,
44+
inputPrice: 0.5,
45+
outputPrice: 1.5,
46+
cacheWritesPrice: 0,
47+
cacheReadsPrice: 0.25,
48+
description:
49+
"DeepSeek V3.2 is the latest iteration of the V3 model family with enhanced reasoning capabilities, improved code generation, and better instruction following, served via Friendli Model APIs.",
50+
},
51+
"MiniMaxAI/MiniMax-M2.5": {
52+
maxTokens: 4096,
53+
contextWindow: 204_800,
54+
supportsImages: false,
55+
supportsPromptCache: true,
56+
inputPrice: 0.3,
57+
outputPrice: 1.2,
58+
cacheWritesPrice: 0,
59+
cacheReadsPrice: 0.06,
60+
description:
61+
"MiniMax M2.5 is a high-performance language model with a 204.8K context window, optimized for long-context understanding and generation tasks, served via Friendli Model APIs.",
62+
},
63+
} as const satisfies Record<string, ModelInfo>

packages/types/src/providers/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export * from "./baseten.js"
33
export * from "./bedrock.js"
44
export * from "./deepseek.js"
55
export * from "./fireworks.js"
6+
export * from "./friendli.js"
67
export * from "./gemini.js"
78
export * from "./lite-llm.js"
89
export * from "./lm-studio.js"
@@ -33,6 +34,7 @@ import { basetenDefaultModelId } from "./baseten.js"
3334
import { bedrockDefaultModelId } from "./bedrock.js"
3435
import { deepSeekDefaultModelId } from "./deepseek.js"
3536
import { fireworksDefaultModelId } from "./fireworks.js"
37+
import { friendliDefaultModelId } from "./friendli.js"
3638
import { geminiDefaultModelId } from "./gemini.js"
3739
import { litellmDefaultModelId } from "./lite-llm.js"
3840
import { mistralDefaultModelId } from "./mistral.js"
@@ -111,6 +113,8 @@ export function getProviderDefaultModelId(
111113
return sambaNovaDefaultModelId
112114
case "fireworks":
113115
return fireworksDefaultModelId
116+
case "friendli":
117+
return friendliDefaultModelId
114118
case "qwen-code":
115119
return qwenCodeDefaultModelId
116120
case "poe":

src/api/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
SambaNovaHandler,
3232
ZAiHandler,
3333
FireworksHandler,
34+
FriendliHandler,
3435
VercelAiGatewayHandler,
3536
OpencodeGoHandler,
3637
ZooGatewayHandler,
@@ -189,6 +190,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
189190
return new ZAiHandler(options)
190191
case "fireworks":
191192
return new FireworksHandler(options)
193+
case "friendli":
194+
return new FriendliHandler(options)
192195
case "vercel-ai-gateway":
193196
return new VercelAiGatewayHandler(options)
194197
case "opencode-go":

0 commit comments

Comments
 (0)