Skip to content

Commit 3b36302

Browse files
committed
feat(friendli): add reasoning params support
1 parent 1ceb4e6 commit 3b36302

3 files changed

Lines changed: 414 additions & 1 deletion

File tree

packages/types/src/providers/friendli.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ export const friendliModels = {
2020
outputPrice: 4.4,
2121
cacheWritesPrice: 0,
2222
cacheReadsPrice: 0.26,
23+
supportsReasoningEffort: ["minimal", "low", "medium", "high", "xhigh", "max"],
24+
reasoningEffort: "high",
2325
description:
2426
"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.",
2527
},
@@ -33,6 +35,8 @@ export const friendliModels = {
3335
outputPrice: 4.4,
3436
cacheWritesPrice: 0,
3537
cacheReadsPrice: 0.26,
38+
supportsReasoningEffort: ["minimal", "low", "medium", "high", "xhigh", "max"],
39+
reasoningEffort: "high",
3640
description:
3741
"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.",
3842
},

src/api/providers/__tests__/friendli.spec.ts

Lines changed: 224 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,16 @@ describe("FriendliHandler", () => {
142142
},
143143
])(
144144
"should expose newly added model $modelId",
145-
({ modelId, contextWindow, maxTokens, supportsMaxTokens, inputPrice, outputPrice, cacheWritesPrice, cacheReadsPrice }) => {
145+
({
146+
modelId,
147+
contextWindow,
148+
maxTokens,
149+
supportsMaxTokens,
150+
inputPrice,
151+
outputPrice,
152+
cacheWritesPrice,
153+
cacheReadsPrice,
154+
}) => {
146155
expect(friendliModels[modelId]).toBeDefined()
147156
const info = friendliModels[modelId] as import("@roo-code/types").ModelInfo
148157
expect(info.maxTokens).toBe(maxTokens)
@@ -394,3 +403,217 @@ describe("Friendli model max output tokens (clamping behavior)", () => {
394403
expect(result).toBe(80_000)
395404
})
396405
})
406+
407+
describe("FriendliHandler — Friendli-specific reasoning params", () => {
408+
beforeEach(() => {
409+
vi.clearAllMocks()
410+
})
411+
412+
it("should include reasoning_effort, chat_template_kwargs, parse_reasoning for GLM-5.2 with reasoning enabled", async () => {
413+
const handler = new FriendliHandler({
414+
apiModelId: "zai-org/GLM-5.2",
415+
friendliApiKey: "test-key",
416+
enableReasoningEffort: true,
417+
reasoningEffort: "high",
418+
})
419+
420+
mockCreate.mockImplementationOnce(() => ({
421+
[Symbol.asyncIterator]: () => ({
422+
async next() {
423+
return { done: true }
424+
},
425+
}),
426+
}))
427+
428+
await handler.createMessage("system", []).next()
429+
430+
expect(mockCreate).toHaveBeenCalledWith(
431+
expect.objectContaining({
432+
model: "zai-org/GLM-5.2",
433+
reasoning_effort: "high",
434+
chat_template_kwargs: { enable_thinking: true },
435+
parse_reasoning: true,
436+
include_reasoning: true,
437+
}),
438+
undefined,
439+
)
440+
})
441+
442+
it("should send enable_thinking: false when enableReasoningEffort is false on controllable model", async () => {
443+
const handler = new FriendliHandler({
444+
apiModelId: "zai-org/GLM-5.2",
445+
friendliApiKey: "test-...ey",
446+
enableReasoningEffort: false,
447+
})
448+
449+
mockCreate.mockImplementationOnce(() => ({
450+
[Symbol.asyncIterator]: () => ({
451+
async next() {
452+
return { done: true }
453+
},
454+
}),
455+
}))
456+
457+
await handler.createMessage("system", []).next()
458+
459+
const callArgs = mockCreate.mock.calls[0][0] as Record<string, unknown>
460+
expect(callArgs.reasoning_effort).toBeUndefined()
461+
expect(callArgs.chat_template_kwargs).toEqual({ enable_thinking: false })
462+
expect(callArgs.parse_reasoning).toBeUndefined()
463+
expect(callArgs.include_reasoning).toBeUndefined()
464+
})
465+
466+
it("should send enable_thinking: false when reasoningEffort is none on controllable model", async () => {
467+
const handler = new FriendliHandler({
468+
apiModelId: "zai-org/GLM-5.2",
469+
friendliApiKey: "test-...ey",
470+
enableReasoningEffort: true,
471+
reasoningEffort: "none",
472+
})
473+
474+
mockCreate.mockImplementationOnce(() => ({
475+
[Symbol.asyncIterator]: () => ({
476+
async next() {
477+
return { done: true }
478+
},
479+
}),
480+
}))
481+
482+
await handler.createMessage("system", []).next()
483+
484+
const callArgs = mockCreate.mock.calls[0][0] as Record<string, unknown>
485+
expect(callArgs.reasoning_effort).toBeUndefined()
486+
expect(callArgs.chat_template_kwargs).toEqual({ enable_thinking: false })
487+
expect(callArgs.parse_reasoning).toBeUndefined()
488+
expect(callArgs.include_reasoning).toBeUndefined()
489+
})
490+
491+
it("should send enable_thinking: false when reasoningEffort is disable on controllable model", async () => {
492+
const handler = new FriendliHandler({
493+
apiModelId: "zai-org/GLM-5.2",
494+
friendliApiKey: "test-...ey",
495+
enableReasoningEffort: true,
496+
reasoningEffort: "disable",
497+
})
498+
499+
mockCreate.mockImplementationOnce(() => ({
500+
[Symbol.asyncIterator]: () => ({
501+
async next() {
502+
return { done: true }
503+
},
504+
}),
505+
}))
506+
507+
await handler.createMessage("system", []).next()
508+
509+
const callArgs = mockCreate.mock.calls[0][0] as Record<string, unknown>
510+
expect(callArgs.reasoning_effort).toBeUndefined()
511+
expect(callArgs.chat_template_kwargs).toEqual({ enable_thinking: false })
512+
expect(callArgs.parse_reasoning).toBeUndefined()
513+
expect(callArgs.include_reasoning).toBeUndefined()
514+
})
515+
516+
it("should use model default reasoningEffort when no explicit settings are provided", async () => {
517+
const handler = new FriendliHandler({
518+
apiModelId: "zai-org/GLM-5.2",
519+
friendliApiKey: "test-...ey",
520+
// No enableReasoningEffort or reasoningEffort — model default "high" kicks in
521+
})
522+
523+
mockCreate.mockImplementationOnce(() => ({
524+
[Symbol.asyncIterator]: () => ({
525+
async next() {
526+
return { done: true }
527+
},
528+
}),
529+
}))
530+
531+
await handler.createMessage("system", []).next()
532+
533+
const callArgs = mockCreate.mock.calls[0][0] as Record<string, unknown>
534+
expect(callArgs.reasoning_effort).toBe("high")
535+
expect(callArgs.chat_template_kwargs).toEqual({ enable_thinking: true })
536+
expect(callArgs.parse_reasoning).toBe(true)
537+
expect(callArgs.include_reasoning).toBe(true)
538+
})
539+
540+
it("should not include any reasoning params for non-reasoning DeepSeek-V3.2", async () => {
541+
const handler = new FriendliHandler({
542+
apiModelId: "deepseek-ai/DeepSeek-V3.2",
543+
friendliApiKey: "test-key",
544+
enableReasoningEffort: true,
545+
reasoningEffort: "high",
546+
})
547+
548+
mockCreate.mockImplementationOnce(() => ({
549+
[Symbol.asyncIterator]: () => ({
550+
async next() {
551+
return { done: true }
552+
},
553+
}),
554+
}))
555+
556+
await handler.createMessage("system", []).next()
557+
558+
const callArgs = mockCreate.mock.calls[0][0] as Record<string, unknown>
559+
expect(callArgs.reasoning_effort).toBeUndefined()
560+
expect(callArgs.chat_template_kwargs).toBeUndefined()
561+
expect(callArgs.parse_reasoning).toBeUndefined()
562+
})
563+
564+
it("should handle delta.reasoning_content from parse_reasoning=true stream", async () => {
565+
const handler = new FriendliHandler({
566+
apiModelId: "zai-org/GLM-5.2",
567+
friendliApiKey: "test-key",
568+
enableReasoningEffort: true,
569+
reasoningEffort: "high",
570+
})
571+
572+
mockCreate.mockImplementationOnce(async () => ({
573+
[Symbol.asyncIterator]: async function* () {
574+
yield {
575+
choices: [{ delta: { reasoning_content: "Let me think..." } }],
576+
usage: null,
577+
}
578+
yield {
579+
choices: [{ delta: { content: "The answer is 42" } }],
580+
usage: null,
581+
}
582+
yield {
583+
choices: [{ delta: {} }],
584+
usage: { prompt_tokens: 10, completion_tokens: 20, total_tokens: 30 },
585+
}
586+
},
587+
}))
588+
589+
const stream = handler.createMessage("system", [])
590+
const chunks = []
591+
for await (const chunk of stream) {
592+
chunks.push(chunk)
593+
}
594+
595+
expect(chunks).toContainEqual({ type: "reasoning", text: "Let me think..." })
596+
expect(chunks).toContainEqual({ type: "text", text: "The answer is 42" })
597+
})
598+
599+
it("completePrompt should include reasoning params when enabled", async () => {
600+
const handler = new FriendliHandler({
601+
apiModelId: "zai-org/GLM-5.2",
602+
friendliApiKey: "test-key",
603+
enableReasoningEffort: true,
604+
reasoningEffort: "medium",
605+
})
606+
607+
mockCreate.mockResolvedValueOnce({
608+
choices: [{ message: { content: "test result" } }],
609+
})
610+
611+
await handler.completePrompt("test")
612+
613+
const callArgs = mockCreate.mock.calls[0][0] as Record<string, unknown>
614+
expect(callArgs.reasoning_effort).toBe("medium")
615+
expect(callArgs.chat_template_kwargs).toEqual({ enable_thinking: true })
616+
expect(callArgs.parse_reasoning).toBe(true)
617+
expect(callArgs.include_reasoning).toBe(true)
618+
})
619+
})

0 commit comments

Comments
 (0)