-
Notifications
You must be signed in to change notification settings - Fork 159
feat: add Umans provider #755
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import type { ModelInfo } from "../model.js" | ||
|
|
||
| export const UMANS_DEFAULT_BASE_URL = "https://api.code.umans.ai/v1" | ||
|
|
||
| // Umans | ||
| // https://api.code.umans.ai/v1/models/info | ||
| export const umansDefaultModelId = "umans-coder" | ||
|
|
||
| export const umansDefaultModelInfo: ModelInfo = { | ||
| maxTokens: 32_768, | ||
| contextWindow: 262_144, | ||
| supportsImages: true, | ||
| supportsPromptCache: false, | ||
| supportsMaxTokens: true, | ||
| inputPrice: 0.95, | ||
| outputPrice: 4.0, | ||
| description: "Umans Coder is Umans' recommended model for complex, coding-heavy workloads and coding agents.", | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| // npx vitest run api/providers/__tests__/umans.spec.ts | ||
|
|
||
| vitest.mock("../utils/timeout-config", () => ({ | ||
| getApiRequestTimeout: vitest.fn().mockReturnValue(300_000), | ||
| })) | ||
|
|
||
| const MOCK_TIMEOUT_MS = 300_000 | ||
|
|
||
| import { Anthropic } from "@anthropic-ai/sdk" | ||
| import OpenAI from "openai" | ||
|
|
||
| import { UmansHandler } from "../umans" | ||
| import type { ApiHandlerOptions } from "../../../shared/api" | ||
| import { Package } from "../../../shared/package" | ||
|
|
||
| const mockCreate = vitest.fn() | ||
|
|
||
| vitest.mock("openai", () => ({ | ||
| default: vitest.fn().mockImplementation(function () { | ||
| return { | ||
| chat: { | ||
| completions: { | ||
| create: mockCreate, | ||
| }, | ||
| }, | ||
| } | ||
| }), | ||
| })) | ||
|
|
||
| vitest.mock("../fetchers/modelCache", () => ({ | ||
| getModels: vitest.fn().mockResolvedValue({ | ||
| "umans-coder": { | ||
| maxTokens: 32768, | ||
| contextWindow: 262144, | ||
| supportsImages: true, | ||
| supportsPromptCache: false, | ||
| supportsMaxTokens: true, | ||
| inputPrice: 0.95, | ||
| outputPrice: 4, | ||
| description: "Umans Coder", | ||
| }, | ||
| "umans-glm-5.2": { | ||
| maxTokens: 131071, | ||
| contextWindow: 405504, | ||
| supportsImages: true, | ||
| supportsPromptCache: false, | ||
| supportsMaxTokens: true, | ||
| supportsReasoningEffort: ["none", "high", "max"], | ||
| reasoningEffort: "high", | ||
| inputPrice: 1.4, | ||
| outputPrice: 4.4, | ||
| description: "Umans GLM 5.2", | ||
| }, | ||
| }), | ||
| })) | ||
|
|
||
| describe("UmansHandler", () => { | ||
| const mockOptions: ApiHandlerOptions = { | ||
| umansApiKey: "test-key", | ||
| umansModelId: "umans-coder", | ||
| } | ||
|
|
||
| beforeEach(() => vitest.clearAllMocks()) | ||
|
|
||
| it("initializes with the Umans base URL and API key", () => { | ||
| new UmansHandler(mockOptions) | ||
|
|
||
| expect(OpenAI).toHaveBeenCalledWith({ | ||
| baseURL: "https://api.code.umans.ai/v1", | ||
| apiKey: "test-key", | ||
| defaultHeaders: { | ||
| "HTTP-Referer": "https://github.com/Zoo-Code-Org/Zoo-Code", | ||
| "X-Title": "Zoo Code", | ||
| "User-Agent": `ZooCode/${Package.version}`, | ||
| }, | ||
| timeout: MOCK_TIMEOUT_MS, | ||
| }) | ||
| }) | ||
|
|
||
| it("returns the default model when no options are provided", async () => { | ||
| const handler = new UmansHandler({}) | ||
| const result = await handler.fetchModel() | ||
|
|
||
| expect(result.id).toBe("umans-coder") | ||
| expect(result.info.description).toBe("Umans Coder") | ||
| }) | ||
|
|
||
| it("uses the provider's default OpenAI reasoning payload for Umans GLM models", async () => { | ||
| const handler = new UmansHandler({ | ||
| umansApiKey: "test-key", | ||
| umansModelId: "umans-glm-5.2", | ||
| reasoningEffort: "max", | ||
| }) | ||
|
|
||
| const mockStream = { | ||
| async *[Symbol.asyncIterator]() { | ||
| yield { | ||
| choices: [{ delta: { content: "done" } }], | ||
| } | ||
| }, | ||
| } | ||
|
|
||
| mockCreate.mockResolvedValue(mockStream) | ||
|
|
||
| const generator = handler.createMessage("system prompt", [{ role: "user" as const, content: "test" }]) | ||
| await generator.next() | ||
|
|
||
| expect(mockCreate).toHaveBeenCalledWith( | ||
| expect.objectContaining({ | ||
| model: "umans-glm-5.2", | ||
| reasoning_effort: "max", | ||
| stream: true, | ||
| }), | ||
| ) | ||
| }) | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ import { | |
| type AnthropicModelId, | ||
| anthropicDefaultModelId, | ||
| anthropicModels, | ||
| openAiModelInfoSaneDefaults, | ||
| ANTHROPIC_DEFAULT_MAX_TOKENS, | ||
| ApiProviderError, | ||
| } from "@roo-code/types" | ||
|
|
@@ -38,12 +39,17 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa | |
| super() | ||
| this.options = options | ||
|
|
||
| const baseURL = this.options.anthropicCustomBaseUrl || this.options.anthropicBaseUrl || undefined | ||
| const apiKey = this.options.anthropicCustomApiKey || this.options.apiKey | ||
| const apiKeyFieldName = | ||
| this.options.anthropicBaseUrl && this.options.anthropicUseAuthToken ? "authToken" : "apiKey" | ||
| baseURL && this.options.anthropicUseAuthToken && !this.options.anthropicCustomApiKey | ||
| ? "authToken" | ||
| : "apiKey" | ||
|
|
||
| this.client = new Anthropic({ | ||
| baseURL: this.options.anthropicBaseUrl || undefined, | ||
| [apiKeyFieldName]: this.options.apiKey, | ||
| baseURL, | ||
| [apiKeyFieldName]: apiKey, | ||
| defaultHeaders: this.options.anthropicCustomHeaders, | ||
| timeout: this.timeoutMs, | ||
| }) | ||
| } | ||
|
|
@@ -352,9 +358,14 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa | |
| } | ||
|
|
||
| getModel() { | ||
| const modelId = this.options.apiModelId | ||
| const id = modelId && modelId in anthropicModels ? (modelId as AnthropicModelId) : anthropicDefaultModelId | ||
| let info: ModelInfo = anthropicModels[id] | ||
| const customModelId = this.options.anthropicCustomModelId | ||
| const modelId = customModelId || this.options.apiModelId | ||
| const id = | ||
| customModelId || | ||
| (modelId && modelId in anthropicModels ? (modelId as AnthropicModelId) : anthropicDefaultModelId) | ||
| let info: ModelInfo = customModelId | ||
| ? this.options.anthropicCustomModelInfo || openAiModelInfoSaneDefaults | ||
| : anthropicModels[id as AnthropicModelId] | ||
|
Comment on lines
+366
to
+368
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Inspect getModelParams handling for ModelInfo.maxTokens === -1.
rg -n -C 8 'function getModelParams|const getModelParams|export .*getModelParams|maxTokens' src packagesRepository: Zoo-Code-Org/Zoo-Code Length of output: 50378 🏁 Script executed: ast-grep outline src/api/providers/anthropic.ts --view expandedRepository: Zoo-Code-Org/Zoo-Code Length of output: 553 🏁 Script executed: rg -n -C 6 'getModelParams|maxTokens\s*[:=]|max_tokens|anthropicCustomModelInfo|openAiModelInfoSaneDefaults' src packages/typesRepository: Zoo-Code-Org/Zoo-Code Length of output: 50378 🏁 Script executed: sed -n '340,470p' src/api/providers/anthropic.tsRepository: Zoo-Code-Org/Zoo-Code Length of output: 2856 🏁 Script executed: rg -n -C 6 'getModelParams|maxTokens|max_tokens|anthropicCustomModelInfo|openAiModelInfoSaneDefaults' src/api/providers/anthropic.ts packages/types/src/providers/openai.tsRepository: Zoo-Code-Org/Zoo-Code Length of output: 44934 🏁 Script executed: ast-grep outline src/api/transform/model-params.ts --view expandedRepository: Zoo-Code-Org/Zoo-Code Length of output: 625 🏁 Script executed: sed -n '1,260p' src/api/transform/model-params.tsRepository: Zoo-Code-Org/Zoo-Code Length of output: 6545 🏁 Script executed: ast-grep outline src/shared/api.ts --view expandedRepository: Zoo-Code-Org/Zoo-Code Length of output: 483 🏁 Script executed: rg -n -C 8 'function getModelMaxOutputTokens|getModelMaxOutputTokens|maxTokens === -1|modelMaxTokens' src/shared packages/typesRepository: Zoo-Code-Org/Zoo-Code Length of output: 30796 🏁 Script executed: sed -n '120,180p' src/shared/api.tsRepository: Zoo-Code-Org/Zoo-Code Length of output: 2536 Use an Anthropic-safe fallback for custom models. 🤖 Prompt for AI Agents |
||
|
|
||
| // If 1M context beta is enabled for supported models, update the model info | ||
| if ( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Gate
anthropicCustom*overrides to theanthropic-customprovider path.buildApiHandlerconstructsAnthropicHandler(options)for both"anthropic"and"anthropic-custom"after removingapiProvider, so these unconditional preferences can make a normal Anthropic profile use stale custom base URL/API key/headers/model settings. Pass an explicit custom-mode flag or retainapiProviderin the handler options before applying these fields.Possible direction
Also applies to: 361-368
🤖 Prompt for AI Agents