mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-10-06 02:47:56 +00:00
Compare commits
2 commits
511472a6ce
...
3826eba06f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3826eba06f | ||
|
|
c3cae397a1 |
42 changed files with 1116 additions and 54 deletions
|
|
@ -23,6 +23,7 @@
|
|||
"clean": "rimraf dist .turbo"
|
||||
},
|
||||
"dependencies": {
|
||||
"ai-sdk-provider-poe": "2.0.18",
|
||||
"zod": "3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,15 @@ export const DEFAULT_CONSECUTIVE_MISTAKE_LIMIT = 3
|
|||
* Dynamic provider requires external API calls in order to get the model list.
|
||||
*/
|
||||
|
||||
export const dynamicProviders = ["openrouter", "vercel-ai-gateway", "litellm", "requesty", "roo", "unbound"] as const
|
||||
export const dynamicProviders = [
|
||||
"openrouter",
|
||||
"vercel-ai-gateway",
|
||||
"litellm",
|
||||
"requesty",
|
||||
"roo",
|
||||
"unbound",
|
||||
"poe",
|
||||
] as const
|
||||
|
||||
export type DynamicProvider = (typeof dynamicProviders)[number]
|
||||
|
||||
|
|
@ -306,6 +314,11 @@ const deepSeekSchema = apiModelIdProviderModelSchema.extend({
|
|||
deepSeekApiKey: z.string().optional(),
|
||||
})
|
||||
|
||||
const poeSchema = apiModelIdProviderModelSchema.extend({
|
||||
poeApiKey: z.string().optional(),
|
||||
poeBaseUrl: z.string().optional(),
|
||||
})
|
||||
|
||||
const moonshotSchema = apiModelIdProviderModelSchema.extend({
|
||||
moonshotBaseUrl: z
|
||||
.union([z.literal("https://api.moonshot.ai/v1"), z.literal("https://api.moonshot.cn/v1")])
|
||||
|
|
@ -400,6 +413,7 @@ export const providerSettingsSchemaDiscriminated = z.discriminatedUnion("apiProv
|
|||
openAiNativeSchema.merge(z.object({ apiProvider: z.literal("openai-native") })),
|
||||
mistralSchema.merge(z.object({ apiProvider: z.literal("mistral") })),
|
||||
deepSeekSchema.merge(z.object({ apiProvider: z.literal("deepseek") })),
|
||||
poeSchema.merge(z.object({ apiProvider: z.literal("poe") })),
|
||||
moonshotSchema.merge(z.object({ apiProvider: z.literal("moonshot") })),
|
||||
minimaxSchema.merge(z.object({ apiProvider: z.literal("minimax") })),
|
||||
requestySchema.merge(z.object({ apiProvider: z.literal("requesty") })),
|
||||
|
|
@ -433,6 +447,7 @@ export const providerSettingsSchema = z.object({
|
|||
...openAiNativeSchema.shape,
|
||||
...mistralSchema.shape,
|
||||
...deepSeekSchema.shape,
|
||||
...poeSchema.shape,
|
||||
...moonshotSchema.shape,
|
||||
...minimaxSchema.shape,
|
||||
...requestySchema.shape,
|
||||
|
|
@ -510,6 +525,7 @@ export const modelIdKeysByProvider: Record<TypicalProvider, ModelIdKey> = {
|
|||
moonshot: "apiModelId",
|
||||
minimax: "apiModelId",
|
||||
deepseek: "apiModelId",
|
||||
poe: "apiModelId",
|
||||
"qwen-code": "apiModelId",
|
||||
requesty: "requestyModelId",
|
||||
unbound: "unboundModelId",
|
||||
|
|
@ -632,6 +648,7 @@ export const MODELS_BY_PROVIDER: Record<
|
|||
baseten: { id: "baseten", label: "Baseten", models: Object.keys(basetenModels) },
|
||||
|
||||
// Dynamic providers; models pulled from remote APIs.
|
||||
poe: { id: "poe", label: "Poe", models: [] },
|
||||
litellm: { id: "litellm", label: "LiteLLM", models: [] },
|
||||
openrouter: { id: "openrouter", label: "OpenRouter", models: [] },
|
||||
requesty: { id: "requesty", label: "Requesty", models: [] },
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export * from "./openai.js"
|
|||
export * from "./openai-codex.js"
|
||||
export * from "./openai-codex-rate-limits.js"
|
||||
export * from "./openrouter.js"
|
||||
export * from "./poe.js"
|
||||
export * from "./qwen-code.js"
|
||||
export * from "./requesty.js"
|
||||
export * from "./roo.js"
|
||||
|
|
@ -36,6 +37,7 @@ import { mistralDefaultModelId } from "./mistral.js"
|
|||
import { moonshotDefaultModelId } from "./moonshot.js"
|
||||
import { openAiCodexDefaultModelId } from "./openai-codex.js"
|
||||
import { openRouterDefaultModelId } from "./openrouter.js"
|
||||
import { poeDefaultModelId } from "./poe.js"
|
||||
import { qwenCodeDefaultModelId } from "./qwen-code.js"
|
||||
import { requestyDefaultModelId } from "./requesty.js"
|
||||
import { rooDefaultModelId } from "./roo.js"
|
||||
|
|
@ -107,6 +109,8 @@ export function getProviderDefaultModelId(
|
|||
return rooDefaultModelId
|
||||
case "qwen-code":
|
||||
return qwenCodeDefaultModelId
|
||||
case "poe":
|
||||
return poeDefaultModelId
|
||||
case "unbound":
|
||||
return unboundDefaultModelId
|
||||
case "vercel-ai-gateway":
|
||||
|
|
|
|||
2
packages/types/src/providers/poe.ts
Normal file
2
packages/types/src/providers/poe.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export { poeDefaultModelId, POE_DEFAULT_BASE_URL, getPoeDefaultModelInfo } from "ai-sdk-provider-poe/code"
|
||||
export type { PoeDefaultModelInfo } from "ai-sdk-provider-poe/code"
|
||||
213
pnpm-lock.yaml
generated
213
pnpm-lock.yaml
generated
|
|
@ -569,7 +569,7 @@ importers:
|
|||
version: 7.0.5
|
||||
openai:
|
||||
specifier: ^5.12.2
|
||||
version: 5.12.2(ws@8.18.3)(zod@3.25.76)
|
||||
version: 5.12.2(ws@8.20.0)(zod@3.25.76)
|
||||
zod:
|
||||
specifier: 3.25.76
|
||||
version: 3.25.76
|
||||
|
|
@ -709,6 +709,9 @@ importers:
|
|||
|
||||
packages/types:
|
||||
dependencies:
|
||||
ai-sdk-provider-poe:
|
||||
specifier: 2.0.18
|
||||
version: 2.0.18(ai@6.0.77(zod@3.25.76))(zod@3.25.76)
|
||||
zod:
|
||||
specifier: 3.25.76
|
||||
version: 3.25.76
|
||||
|
|
@ -818,6 +821,9 @@ importers:
|
|||
'@vscode/codicons':
|
||||
specifier: ^0.0.36
|
||||
version: 0.0.36
|
||||
ai-sdk-provider-poe:
|
||||
specifier: 2.0.18
|
||||
version: 2.0.18(ai@6.0.77(zod@3.25.76))(zod@3.25.76)
|
||||
async-mutex:
|
||||
specifier: ^0.5.0
|
||||
version: 0.5.0
|
||||
|
|
@ -907,7 +913,7 @@ importers:
|
|||
version: 0.5.17
|
||||
openai:
|
||||
specifier: ^5.12.2
|
||||
version: 5.12.2(ws@8.18.3)(zod@3.25.76)
|
||||
version: 5.12.2(ws@8.20.0)(zod@3.25.76)
|
||||
os-name:
|
||||
specifier: ^6.0.0
|
||||
version: 6.1.0
|
||||
|
|
@ -1426,6 +1432,12 @@ packages:
|
|||
peerDependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/anthropic@3.0.42':
|
||||
resolution: {integrity: sha512-snoLXB9DmvAmmngbPN/Io8IGzZ9zWpC208EgIIztYf1e1JhwuMkgKCYkL30vGhSen4PrBafu2+sO4G/17wu45A==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/baseten@1.0.31':
|
||||
resolution: {integrity: sha512-tGbV96WBb5nnfyUYFrPyBxrhw53YlKSJbMC+rH3HhQlUaIs8+m/Bm4M0isrek9owIIf4MmmSDZ5VZL08zz7eFQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -1480,6 +1492,18 @@ packages:
|
|||
peerDependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/openai-compatible@2.0.37':
|
||||
resolution: {integrity: sha512-+POSFVcgiu47BK64dhsI6OpcDC0/VAE2ZSaXdXGNNhpC/ava++uSRJYks0k2bpfY0wwCTgpAWZsXn/dG2Yppiw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/openai@3.0.27':
|
||||
resolution: {integrity: sha512-pLMxWOypwroXiK9dxNpn60/HGhWWWDEOJ3lo9vZLoxvpJNtKnLKojwVIvlW3yEjlD7ll1+jUO2uzsABNTaP5Yg==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/provider-utils@3.0.5':
|
||||
resolution: {integrity: sha512-HliwB/yzufw3iwczbFVE2Fiwf1XqROB/I6ng8EKUsPM5+2wnIa8f4VbljZcDx+grhFrPV+PnRZH7zBqi8WZM7Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -1492,6 +1516,12 @@ packages:
|
|||
peerDependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/provider-utils@4.0.21':
|
||||
resolution: {integrity: sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/provider@2.0.0':
|
||||
resolution: {integrity: sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -1726,6 +1756,10 @@ packages:
|
|||
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/code-frame@7.29.0':
|
||||
resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/compat-data@7.27.2':
|
||||
resolution: {integrity: sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
|
@ -1764,6 +1798,10 @@ packages:
|
|||
resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-validator-identifier@7.28.5':
|
||||
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-validator-option@7.27.1':
|
||||
resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
|
@ -1805,6 +1843,10 @@ packages:
|
|||
resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/runtime@7.29.2':
|
||||
resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/template@7.27.2':
|
||||
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
|
@ -2532,13 +2574,13 @@ packages:
|
|||
'@libsql/core@0.15.15':
|
||||
resolution: {integrity: sha512-C88Z6UKl+OyuKKPwz224riz02ih/zHYI3Ho/LAcVOgjsunIRZoBw7fjRfaH9oPMmSNeQfhGklSG2il1URoOIsA==}
|
||||
|
||||
'@libsql/darwin-arm64@0.5.22':
|
||||
resolution: {integrity: sha512-4B8ZlX3nIDPndfct7GNe0nI3Yw6ibocEicWdC4fvQbSs/jdq/RC2oCsoJxJ4NzXkvktX70C1J4FcmmoBy069UA==}
|
||||
'@libsql/darwin-arm64@0.5.29':
|
||||
resolution: {integrity: sha512-K+2RIB1OGFPYQbfay48GakLhqf3ArcbHqPFu7EZiaUcRgFcdw8RoltsMyvbj5ix2fY0HV3Q3Ioa/ByvQdaSM0A==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@libsql/darwin-x64@0.5.22':
|
||||
resolution: {integrity: sha512-ny2HYWt6lFSIdNFzUFIJ04uiW6finXfMNJ7wypkAD8Pqdm6nAByO+Fdqu8t7sD0sqJGeUCiOg480icjyQ2/8VA==}
|
||||
'@libsql/darwin-x64@0.5.29':
|
||||
resolution: {integrity: sha512-OtT+KFHsKFy1R5FVadr8FJ2Bb1mghtXTyJkxv0trocq7NuHntSki1eUbxpO5ezJesDvBlqFjnWaYYY516QNLhQ==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
|
|
@ -2552,38 +2594,38 @@ packages:
|
|||
'@libsql/isomorphic-ws@0.1.5':
|
||||
resolution: {integrity: sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==}
|
||||
|
||||
'@libsql/linux-arm-gnueabihf@0.5.22':
|
||||
resolution: {integrity: sha512-3Uo3SoDPJe/zBnyZKosziRGtszXaEtv57raWrZIahtQDsjxBVjuzYQinCm9LRCJCUT5t2r5Z5nLDPJi2CwZVoA==}
|
||||
'@libsql/linux-arm-gnueabihf@0.5.29':
|
||||
resolution: {integrity: sha512-CD4n4zj7SJTHso4nf5cuMoWoMSS7asn5hHygsDuhRl8jjjCTT3yE+xdUvI4J7zsyb53VO5ISh4cwwOtf6k2UhQ==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@libsql/linux-arm-musleabihf@0.5.22':
|
||||
resolution: {integrity: sha512-LCsXh07jvSojTNJptT9CowOzwITznD+YFGGW+1XxUr7fS+7/ydUrpDfsMX7UqTqjm7xG17eq86VkWJgHJfvpNg==}
|
||||
'@libsql/linux-arm-musleabihf@0.5.29':
|
||||
resolution: {integrity: sha512-2Z9qBVpEJV7OeflzIR3+l5yAd4uTOLxklScYTwpZnkm2vDSGlC1PRlueLaufc4EFITkLKXK2MWBpexuNJfMVcg==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@libsql/linux-arm64-gnu@0.5.22':
|
||||
resolution: {integrity: sha512-KSdnOMy88c9mpOFKUEzPskSaF3VLflfSUCBwas/pn1/sV3pEhtMF6H8VUCd2rsedwoukeeCSEONqX7LLnQwRMA==}
|
||||
'@libsql/linux-arm64-gnu@0.5.29':
|
||||
resolution: {integrity: sha512-gURBqaiXIGGwFNEaUj8Ldk7Hps4STtG+31aEidCk5evMMdtsdfL3HPCpvys+ZF/tkOs2MWlRWoSq7SOuCE9k3w==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@libsql/linux-arm64-musl@0.5.22':
|
||||
resolution: {integrity: sha512-mCHSMAsDTLK5YH//lcV3eFEgiR23Ym0U9oEvgZA0667gqRZg/2px+7LshDvErEKv2XZ8ixzw3p1IrBzLQHGSsw==}
|
||||
'@libsql/linux-arm64-musl@0.5.29':
|
||||
resolution: {integrity: sha512-fwgYZ0H8mUkyVqXZHF3mT/92iIh1N94Owi/f66cPVNsk9BdGKq5gVpoKO+7UxaNzuEH1roJp2QEwsCZMvBLpqg==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@libsql/linux-x64-gnu@0.5.22':
|
||||
resolution: {integrity: sha512-kNBHaIkSg78Y4BqAdgjcR2mBilZXs4HYkAmi58J+4GRwDQZh5fIUWbnQvB9f95DkWUIGVeenqLRFY2pcTmlsew==}
|
||||
'@libsql/linux-x64-gnu@0.5.29':
|
||||
resolution: {integrity: sha512-y14V0vY0nmMC6G0pHeJcEarcnGU2H6cm21ZceRkacWHvQAEhAG0latQkCtoS2njFOXiYIg+JYPfAoWKbi82rkg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@libsql/linux-x64-musl@0.5.22':
|
||||
resolution: {integrity: sha512-UZ4Xdxm4pu3pQXjvfJiyCzZop/9j/eA2JjmhMaAhe3EVLH2g11Fy4fwyUp9sT1QJYR1kpc2JLuybPM0kuXv/Tg==}
|
||||
'@libsql/linux-x64-musl@0.5.29':
|
||||
resolution: {integrity: sha512-gquqwA/39tH4pFl+J9n3SOMSymjX+6kZ3kWgY3b94nXFTwac9bnFNMffIomgvlFaC4ArVqMnOZD3nuJ3H3VO1w==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@libsql/win32-x64-msvc@0.5.22':
|
||||
resolution: {integrity: sha512-Fj0j8RnBpo43tVZUVoNK6BV/9AtDUM5S7DF3LB4qTYg1LMSZqi3yeCneUTLJD6XomQJlZzbI4mst89yspVSAnA==}
|
||||
'@libsql/win32-x64-msvc@0.5.29':
|
||||
resolution: {integrity: sha512-4/0CvEdhi6+KjMxMaVbFM2n2Z44escBRoEYpR+gZg64DdetzGnYm8mcNLcoySaDJZNaBd6wz5DNdgRmcI4hXcg==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
|
|
@ -4886,6 +4928,11 @@ packages:
|
|||
resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==}
|
||||
engines: {node: '>= 8.0.0'}
|
||||
|
||||
ai-sdk-provider-poe@2.0.18:
|
||||
resolution: {integrity: sha512-uzFR5Zq+PD6LfrqpYAr4dt2KmfOfS9a0Wh8QJsOaGI/vOByhW02P/0mj0NtOnOWF1RIUfBkQJo647XjrgnFjjg==}
|
||||
peerDependencies:
|
||||
ai: '>=6.0.0'
|
||||
|
||||
ai@6.0.77:
|
||||
resolution: {integrity: sha512-tyyhrRpCRFVlivdNIFLK8cexSBB2jwTqO0z1qJQagk+UxZ+MW8h5V8xsvvb+xdKDY482Y8KAm0mr7TDnPKvvlw==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -7712,8 +7759,8 @@ packages:
|
|||
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
libsql@0.5.22:
|
||||
resolution: {integrity: sha512-NscWthMQt7fpU8lqd7LXMvT9pi+KhhmTHAJWUB/Lj6MWa0MKFv0F2V4C6WKKpjCVZl0VwcDz4nOI3CyaT1DDiA==}
|
||||
libsql@0.5.29:
|
||||
resolution: {integrity: sha512-8lMP8iMgiBzzoNbAPQ59qdVcj6UaE/Vnm+fiwX4doX4Narook0a4GPKWBEv+CR8a1OwbfkgL18uBfBjWdF0Fzg==}
|
||||
cpu: [x64, arm64, wasm32, arm]
|
||||
os: [darwin, linux, win32]
|
||||
|
||||
|
|
@ -8976,6 +9023,7 @@ packages:
|
|||
prebuild-install@7.1.3:
|
||||
resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==}
|
||||
engines: {node: '>=10'}
|
||||
deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available.
|
||||
hasBin: true
|
||||
|
||||
prelude-ls@1.2.1:
|
||||
|
|
@ -9565,6 +9613,11 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
semver@7.7.4:
|
||||
resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
send@1.2.0:
|
||||
resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==}
|
||||
engines: {node: '>= 18'}
|
||||
|
|
@ -10898,6 +10951,18 @@ packages:
|
|||
utf-8-validate:
|
||||
optional: true
|
||||
|
||||
ws@8.20.0:
|
||||
resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
utf-8-validate: '>=5.0.2'
|
||||
peerDependenciesMeta:
|
||||
bufferutil:
|
||||
optional: true
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
|
||||
xml-name-validator@5.0.0:
|
||||
resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -11058,6 +11123,12 @@ snapshots:
|
|||
'@ai-sdk/provider-utils': 4.0.14(zod@3.25.76)
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/anthropic@3.0.42(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 3.0.8
|
||||
'@ai-sdk/provider-utils': 4.0.14(zod@3.25.76)
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/baseten@1.0.31(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@ai-sdk/openai-compatible': 2.0.28(zod@3.25.76)
|
||||
|
|
@ -11121,6 +11192,18 @@ snapshots:
|
|||
'@ai-sdk/provider-utils': 4.0.14(zod@3.25.76)
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/openai-compatible@2.0.37(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 3.0.8
|
||||
'@ai-sdk/provider-utils': 4.0.21(zod@3.25.76)
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/openai@3.0.27(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 3.0.8
|
||||
'@ai-sdk/provider-utils': 4.0.14(zod@3.25.76)
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/provider-utils@3.0.5(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 2.0.0
|
||||
|
|
@ -11136,6 +11219,13 @@ snapshots:
|
|||
eventsource-parser: 3.0.6
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/provider-utils@4.0.21(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 3.0.8
|
||||
'@standard-schema/spec': 1.1.0
|
||||
eventsource-parser: 3.0.6
|
||||
zod: 3.25.76
|
||||
|
||||
'@ai-sdk/provider@2.0.0':
|
||||
dependencies:
|
||||
json-schema: 0.4.0
|
||||
|
|
@ -11767,6 +11857,12 @@ snapshots:
|
|||
js-tokens: 4.0.0
|
||||
picocolors: 1.1.1
|
||||
|
||||
'@babel/code-frame@7.29.0':
|
||||
dependencies:
|
||||
'@babel/helper-validator-identifier': 7.28.5
|
||||
js-tokens: 4.0.0
|
||||
picocolors: 1.1.1
|
||||
|
||||
'@babel/compat-data@7.27.2': {}
|
||||
|
||||
'@babel/core@7.27.1':
|
||||
|
|
@ -11827,6 +11923,8 @@ snapshots:
|
|||
|
||||
'@babel/helper-validator-identifier@7.27.1': {}
|
||||
|
||||
'@babel/helper-validator-identifier@7.28.5': {}
|
||||
|
||||
'@babel/helper-validator-option@7.27.1': {}
|
||||
|
||||
'@babel/helpers@7.27.1':
|
||||
|
|
@ -11856,6 +11954,8 @@ snapshots:
|
|||
|
||||
'@babel/runtime@7.28.4': {}
|
||||
|
||||
'@babel/runtime@7.29.2': {}
|
||||
|
||||
'@babel/template@7.27.2':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.27.1
|
||||
|
|
@ -12575,7 +12675,7 @@ snapshots:
|
|||
'@libsql/core': 0.15.15
|
||||
'@libsql/hrana-client': 0.7.0
|
||||
js-base64: 3.7.8
|
||||
libsql: 0.5.22
|
||||
libsql: 0.5.29
|
||||
promise-limit: 2.7.0
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
|
|
@ -12587,10 +12687,10 @@ snapshots:
|
|||
js-base64: 3.7.8
|
||||
optional: true
|
||||
|
||||
'@libsql/darwin-arm64@0.5.22':
|
||||
'@libsql/darwin-arm64@0.5.29':
|
||||
optional: true
|
||||
|
||||
'@libsql/darwin-x64@0.5.22':
|
||||
'@libsql/darwin-x64@0.5.29':
|
||||
optional: true
|
||||
|
||||
'@libsql/hrana-client@0.7.0':
|
||||
|
|
@ -12610,31 +12710,31 @@ snapshots:
|
|||
'@libsql/isomorphic-ws@0.1.5':
|
||||
dependencies:
|
||||
'@types/ws': 8.18.1
|
||||
ws: 8.18.3
|
||||
ws: 8.20.0
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- utf-8-validate
|
||||
optional: true
|
||||
|
||||
'@libsql/linux-arm-gnueabihf@0.5.22':
|
||||
'@libsql/linux-arm-gnueabihf@0.5.29':
|
||||
optional: true
|
||||
|
||||
'@libsql/linux-arm-musleabihf@0.5.22':
|
||||
'@libsql/linux-arm-musleabihf@0.5.29':
|
||||
optional: true
|
||||
|
||||
'@libsql/linux-arm64-gnu@0.5.22':
|
||||
'@libsql/linux-arm64-gnu@0.5.29':
|
||||
optional: true
|
||||
|
||||
'@libsql/linux-arm64-musl@0.5.22':
|
||||
'@libsql/linux-arm64-musl@0.5.29':
|
||||
optional: true
|
||||
|
||||
'@libsql/linux-x64-gnu@0.5.22':
|
||||
'@libsql/linux-x64-gnu@0.5.29':
|
||||
optional: true
|
||||
|
||||
'@libsql/linux-x64-musl@0.5.22':
|
||||
'@libsql/linux-x64-musl@0.5.29':
|
||||
optional: true
|
||||
|
||||
'@libsql/win32-x64-msvc@0.5.22':
|
||||
'@libsql/win32-x64-msvc@0.5.29':
|
||||
optional: true
|
||||
|
||||
'@lmstudio/lms-isomorphic@0.4.5':
|
||||
|
|
@ -14389,8 +14489,8 @@ snapshots:
|
|||
|
||||
'@testing-library/dom@10.4.0':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.27.1
|
||||
'@babel/runtime': 7.28.4
|
||||
'@babel/code-frame': 7.29.0
|
||||
'@babel/runtime': 7.29.2
|
||||
'@types/aria-query': 5.0.4
|
||||
aria-query: 5.3.0
|
||||
chalk: 4.1.2
|
||||
|
|
@ -15129,6 +15229,17 @@ snapshots:
|
|||
dependencies:
|
||||
humanize-ms: 1.2.1
|
||||
|
||||
ai-sdk-provider-poe@2.0.18(ai@6.0.77(zod@3.25.76))(zod@3.25.76):
|
||||
dependencies:
|
||||
'@ai-sdk/anthropic': 3.0.42(zod@3.25.76)
|
||||
'@ai-sdk/openai': 3.0.27(zod@3.25.76)
|
||||
'@ai-sdk/openai-compatible': 2.0.37(zod@3.25.76)
|
||||
'@ai-sdk/provider': 3.0.8
|
||||
'@ai-sdk/provider-utils': 4.0.14(zod@3.25.76)
|
||||
ai: 6.0.77(zod@3.25.76)
|
||||
transitivePeerDependencies:
|
||||
- zod
|
||||
|
||||
ai@6.0.77(zod@3.25.76):
|
||||
dependencies:
|
||||
'@ai-sdk/gateway': 3.0.39(zod@3.25.76)
|
||||
|
|
@ -17232,7 +17343,7 @@ snapshots:
|
|||
'@petamoriken/float16': 3.9.3
|
||||
debug: 4.4.3
|
||||
env-paths: 3.0.0
|
||||
semver: 7.7.3
|
||||
semver: 7.7.4
|
||||
shell-quote: 1.8.3
|
||||
which: 4.0.0
|
||||
transitivePeerDependencies:
|
||||
|
|
@ -18251,20 +18362,20 @@ snapshots:
|
|||
prelude-ls: 1.2.1
|
||||
type-check: 0.4.0
|
||||
|
||||
libsql@0.5.22:
|
||||
libsql@0.5.29:
|
||||
dependencies:
|
||||
'@neon-rs/load': 0.0.4
|
||||
detect-libc: 2.0.2
|
||||
optionalDependencies:
|
||||
'@libsql/darwin-arm64': 0.5.22
|
||||
'@libsql/darwin-x64': 0.5.22
|
||||
'@libsql/linux-arm-gnueabihf': 0.5.22
|
||||
'@libsql/linux-arm-musleabihf': 0.5.22
|
||||
'@libsql/linux-arm64-gnu': 0.5.22
|
||||
'@libsql/linux-arm64-musl': 0.5.22
|
||||
'@libsql/linux-x64-gnu': 0.5.22
|
||||
'@libsql/linux-x64-musl': 0.5.22
|
||||
'@libsql/win32-x64-msvc': 0.5.22
|
||||
'@libsql/darwin-arm64': 0.5.29
|
||||
'@libsql/darwin-x64': 0.5.29
|
||||
'@libsql/linux-arm-gnueabihf': 0.5.29
|
||||
'@libsql/linux-arm-musleabihf': 0.5.29
|
||||
'@libsql/linux-arm64-gnu': 0.5.29
|
||||
'@libsql/linux-arm64-musl': 0.5.29
|
||||
'@libsql/linux-x64-gnu': 0.5.29
|
||||
'@libsql/linux-x64-musl': 0.5.29
|
||||
'@libsql/win32-x64-msvc': 0.5.29
|
||||
optional: true
|
||||
|
||||
lie@3.3.0:
|
||||
|
|
@ -19384,9 +19495,9 @@ snapshots:
|
|||
is-inside-container: 1.0.0
|
||||
is-wsl: 3.1.0
|
||||
|
||||
openai@5.12.2(ws@8.18.3)(zod@3.25.76):
|
||||
openai@5.12.2(ws@8.20.0)(zod@3.25.76):
|
||||
optionalDependencies:
|
||||
ws: 8.18.3
|
||||
ws: 8.20.0
|
||||
zod: 3.25.76
|
||||
|
||||
option@0.2.4: {}
|
||||
|
|
@ -20538,6 +20649,9 @@ snapshots:
|
|||
|
||||
semver@7.7.3: {}
|
||||
|
||||
semver@7.7.4:
|
||||
optional: true
|
||||
|
||||
send@1.2.0:
|
||||
dependencies:
|
||||
debug: 4.4.1(supports-color@8.1.1)
|
||||
|
|
@ -22165,6 +22279,9 @@ snapshots:
|
|||
|
||||
ws@8.18.3: {}
|
||||
|
||||
ws@8.20.0:
|
||||
optional: true
|
||||
|
||||
xml-name-validator@5.0.0: {}
|
||||
|
||||
xml2js@0.5.0:
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
AnthropicHandler,
|
||||
AwsBedrockHandler,
|
||||
OpenRouterHandler,
|
||||
PoeHandler,
|
||||
VertexHandler,
|
||||
AnthropicVertexHandler,
|
||||
OpenAiHandler,
|
||||
|
|
@ -176,6 +177,8 @@ export function buildApiHandler(configuration: ProviderSettings): ApiHandler {
|
|||
return new MiniMaxHandler(options)
|
||||
case "baseten":
|
||||
return new BasetenHandler(options)
|
||||
case "poe":
|
||||
return new PoeHandler(options)
|
||||
default:
|
||||
return new AnthropicHandler(options)
|
||||
}
|
||||
|
|
|
|||
307
src/api/providers/__tests__/poe.spec.ts
Normal file
307
src/api/providers/__tests__/poe.spec.ts
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
const mockStreamText = vitest.fn()
|
||||
const mockGenerateText = vitest.fn()
|
||||
const mockCreatePoe = vitest.fn()
|
||||
|
||||
vitest.mock("ai-sdk-provider-poe", () => ({
|
||||
createPoe: (...args: unknown[]) => mockCreatePoe(...args),
|
||||
}))
|
||||
|
||||
vitest.mock("ai-sdk-provider-poe/code", () => ({
|
||||
mapToolChoice: vitest.fn((value: unknown) => value),
|
||||
extractUsageMetrics: vitest.fn((usage: any) => ({
|
||||
inputTokens: usage?.inputTokens || 0,
|
||||
outputTokens: usage?.outputTokens || 0,
|
||||
cacheReadTokens: usage?.cacheReadTokens,
|
||||
cacheWriteTokens: usage?.cacheWriteTokens,
|
||||
reasoningTokens: usage?.reasoningTokens,
|
||||
})),
|
||||
getPoeDefaultModelInfo: vitest.fn(() => ({
|
||||
maxTokens: 8192,
|
||||
contextWindow: 200_000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
inputPrice: 3,
|
||||
outputPrice: 15,
|
||||
})),
|
||||
}))
|
||||
|
||||
vitest.mock("ai", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("ai")>()
|
||||
return {
|
||||
...actual,
|
||||
streamText: (...args: unknown[]) => mockStreamText(...args),
|
||||
generateText: (...args: unknown[]) => mockGenerateText(...args),
|
||||
}
|
||||
})
|
||||
|
||||
vitest.mock("../fetchers/modelCache", () => ({
|
||||
getModelsFromCache: vitest.fn().mockReturnValue({
|
||||
"anthropic/claude-sonnet-4": {
|
||||
maxTokens: 10_000,
|
||||
contextWindow: 200_000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
supportsReasoningBudget: true,
|
||||
inputPrice: 3,
|
||||
outputPrice: 15,
|
||||
},
|
||||
"openai/gpt-4o": {
|
||||
maxTokens: 16_384,
|
||||
contextWindow: 128_000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 2.5,
|
||||
outputPrice: 10,
|
||||
},
|
||||
"openai/o3": {
|
||||
maxTokens: 100_000,
|
||||
contextWindow: 200_000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: false,
|
||||
supportsReasoningEffort: ["low", "medium", "high"],
|
||||
inputPrice: 10,
|
||||
outputPrice: 40,
|
||||
},
|
||||
}),
|
||||
}))
|
||||
|
||||
import { poeDefaultModelId } from "@roo-code/types"
|
||||
import { PoeHandler } from "../poe"
|
||||
|
||||
describe("PoeHandler", () => {
|
||||
const mockLanguageModel = { modelId: "test-model" }
|
||||
const mockPoeProvider = vitest.fn().mockReturnValue(mockLanguageModel)
|
||||
|
||||
beforeEach(() => {
|
||||
vitest.clearAllMocks()
|
||||
mockCreatePoe.mockReturnValue(mockPoeProvider)
|
||||
})
|
||||
|
||||
describe("constructor", () => {
|
||||
it("creates poe provider with api key and default base URL", () => {
|
||||
new PoeHandler({ poeApiKey: "test-key" })
|
||||
|
||||
expect(mockCreatePoe).toHaveBeenCalledWith({
|
||||
apiKey: "test-key",
|
||||
baseURL: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it("creates poe provider with custom base URL", () => {
|
||||
new PoeHandler({ poeApiKey: "key", poeBaseUrl: "https://custom.poe.com/v1" })
|
||||
|
||||
expect(mockCreatePoe).toHaveBeenCalledWith({
|
||||
apiKey: "key",
|
||||
baseURL: "https://custom.poe.com/v1",
|
||||
})
|
||||
})
|
||||
|
||||
it("uses fallback api key when not provided", () => {
|
||||
new PoeHandler({})
|
||||
|
||||
expect(mockCreatePoe).toHaveBeenCalledWith({
|
||||
apiKey: "not-provided",
|
||||
baseURL: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("getModel", () => {
|
||||
it("returns model info from cache", () => {
|
||||
const handler = new PoeHandler({ poeApiKey: "key", apiModelId: "anthropic/claude-sonnet-4" })
|
||||
const result = handler.getModel()
|
||||
|
||||
expect(result.id).toBe("anthropic/claude-sonnet-4")
|
||||
expect(result.info.contextWindow).toBe(200_000)
|
||||
expect(result.info.maxTokens).toBe(10_000)
|
||||
})
|
||||
|
||||
it("returns default model when no model ID specified", () => {
|
||||
const handler = new PoeHandler({ poeApiKey: "key" })
|
||||
const result = handler.getModel()
|
||||
|
||||
expect(result.id).toBe(poeDefaultModelId)
|
||||
})
|
||||
|
||||
it("falls back to default model info when model not in cache", () => {
|
||||
const handler = new PoeHandler({ poeApiKey: "key", apiModelId: "unknown/model" })
|
||||
const result = handler.getModel()
|
||||
|
||||
expect(result.id).toBe("unknown/model")
|
||||
expect(result.info.contextWindow).toBeGreaterThan(0)
|
||||
expect(result.info.maxTokens).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("createMessage", () => {
|
||||
it("streams text chunks", async () => {
|
||||
const handler = new PoeHandler({ poeApiKey: "key", apiModelId: "anthropic/claude-sonnet-4" })
|
||||
|
||||
const fullStream = (async function* () {
|
||||
yield { type: "text-delta", text: "Hello " }
|
||||
yield { type: "text-delta", text: "world!" }
|
||||
})()
|
||||
|
||||
mockStreamText.mockReturnValue({
|
||||
fullStream,
|
||||
usage: Promise.resolve({ inputTokens: 10, outputTokens: 5 }),
|
||||
})
|
||||
|
||||
const chunks = []
|
||||
for await (const chunk of handler.createMessage("system prompt", [
|
||||
{ role: "user" as const, content: "Hi" },
|
||||
])) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
expect(chunks).toContainEqual({ type: "text", text: "Hello " })
|
||||
expect(chunks).toContainEqual({ type: "text", text: "world!" })
|
||||
expect(chunks).toContainEqual(expect.objectContaining({ type: "usage", inputTokens: 10, outputTokens: 5 }))
|
||||
})
|
||||
})
|
||||
|
||||
describe("reasoning", () => {
|
||||
it("passes anthropic thinking config for budget models", async () => {
|
||||
const handler = new PoeHandler({
|
||||
poeApiKey: "key",
|
||||
apiModelId: "anthropic/claude-sonnet-4",
|
||||
enableReasoningEffort: true,
|
||||
modelMaxThinkingTokens: 4096,
|
||||
})
|
||||
|
||||
const fullStream = (async function* () {
|
||||
yield { type: "reasoning", text: "Let me think..." }
|
||||
yield { type: "text-delta", text: "Answer" }
|
||||
})()
|
||||
|
||||
mockStreamText.mockReturnValue({
|
||||
fullStream,
|
||||
usage: Promise.resolve({ inputTokens: 10, outputTokens: 5 }),
|
||||
})
|
||||
|
||||
const chunks = []
|
||||
const modelMaxTokens = handler.getModel().info.maxTokens ?? 0
|
||||
for await (const chunk of handler.createMessage("system", [
|
||||
{ role: "user" as const, content: "think about this" },
|
||||
])) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
const callArgs = mockStreamText.mock.calls[0][0]
|
||||
expect(callArgs.temperature).toBe(1.0)
|
||||
expect(callArgs.providerOptions).toEqual({
|
||||
poe: {
|
||||
reasoningBudgetTokens: 4096,
|
||||
},
|
||||
})
|
||||
expect(callArgs.maxOutputTokens).toBe(modelMaxTokens - 4096)
|
||||
|
||||
expect(chunks).toContainEqual({ type: "reasoning", text: "Let me think..." })
|
||||
expect(chunks).toContainEqual({ type: "text", text: "Answer" })
|
||||
})
|
||||
|
||||
it("passes openai reasoning effort for effort models", async () => {
|
||||
const handler = new PoeHandler({
|
||||
poeApiKey: "key",
|
||||
apiModelId: "openai/o3",
|
||||
enableReasoningEffort: true,
|
||||
reasoningEffort: "high",
|
||||
})
|
||||
|
||||
const fullStream = (async function* () {
|
||||
yield { type: "text-delta", text: "Answer" }
|
||||
})()
|
||||
|
||||
mockStreamText.mockReturnValue({
|
||||
fullStream,
|
||||
usage: Promise.resolve({ inputTokens: 10, outputTokens: 5 }),
|
||||
})
|
||||
|
||||
for await (const _ of handler.createMessage("system", [{ role: "user" as const, content: "reason" }])) {
|
||||
/* drain */
|
||||
}
|
||||
|
||||
expect(mockStreamText).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
providerOptions: {
|
||||
poe: {
|
||||
reasoningEffort: "high",
|
||||
reasoningSummary: "auto",
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("does not pass providerOptions when reasoning is disabled", async () => {
|
||||
const handler = new PoeHandler({
|
||||
poeApiKey: "key",
|
||||
apiModelId: "anthropic/claude-sonnet-4",
|
||||
enableReasoningEffort: false,
|
||||
})
|
||||
|
||||
const fullStream = (async function* () {
|
||||
yield { type: "text-delta", text: "Answer" }
|
||||
})()
|
||||
|
||||
mockStreamText.mockReturnValue({
|
||||
fullStream,
|
||||
usage: Promise.resolve({ inputTokens: 1, outputTokens: 1 }),
|
||||
})
|
||||
|
||||
for await (const _ of handler.createMessage("system", [{ role: "user" as const, content: "hi" }])) {
|
||||
/* drain */
|
||||
}
|
||||
|
||||
const callArgs = mockStreamText.mock.calls[0][0]
|
||||
expect(callArgs.providerOptions).toBeUndefined()
|
||||
expect(callArgs.temperature).toBeUndefined()
|
||||
})
|
||||
|
||||
it("uses default thinking budget when not specified", async () => {
|
||||
const handler = new PoeHandler({
|
||||
poeApiKey: "key",
|
||||
apiModelId: "anthropic/claude-sonnet-4",
|
||||
enableReasoningEffort: true,
|
||||
})
|
||||
|
||||
const fullStream = (async function* () {})()
|
||||
mockStreamText.mockReturnValue({
|
||||
fullStream,
|
||||
usage: Promise.resolve({ inputTokens: 0, outputTokens: 0 }),
|
||||
})
|
||||
|
||||
for await (const _ of handler.createMessage("system", [{ role: "user" as const, content: "hi" }])) {
|
||||
/* drain */
|
||||
}
|
||||
|
||||
const callArgs = mockStreamText.mock.calls[0][0]
|
||||
expect(callArgs.providerOptions).toEqual({
|
||||
poe: {
|
||||
reasoningBudgetTokens: expect.any(Number),
|
||||
},
|
||||
})
|
||||
expect(callArgs.providerOptions.poe.reasoningBudgetTokens + callArgs.maxOutputTokens).toBe(
|
||||
handler.getModel().info.maxTokens,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("completePrompt", () => {
|
||||
it("returns generated text", async () => {
|
||||
const handler = new PoeHandler({ poeApiKey: "key", apiModelId: "openai/gpt-4o" })
|
||||
|
||||
mockGenerateText.mockResolvedValue({ text: "generated response" })
|
||||
|
||||
const result = await handler.completePrompt("complete this")
|
||||
|
||||
expect(result).toBe("generated response")
|
||||
expect(mockGenerateText).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
model: mockLanguageModel,
|
||||
prompt: "complete this",
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
131
src/api/providers/fetchers/__tests__/poe.spec.ts
Normal file
131
src/api/providers/fetchers/__tests__/poe.spec.ts
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
import { getPoeModels } from "../poe"
|
||||
|
||||
const mockFetchPoeModels = vitest.fn()
|
||||
const mockGetModels = vitest.fn()
|
||||
|
||||
vitest.mock("ai-sdk-provider-poe/code", () => ({
|
||||
fetchPoeModels: (...args: unknown[]) => mockFetchPoeModels(...args),
|
||||
getModels: () => mockGetModels(),
|
||||
}))
|
||||
|
||||
describe("getPoeModels", () => {
|
||||
beforeEach(() => vitest.clearAllMocks())
|
||||
|
||||
it("converts PoeModelInfo to ModelRecord", async () => {
|
||||
mockFetchPoeModels.mockResolvedValue([])
|
||||
mockGetModels.mockReturnValue([
|
||||
{
|
||||
id: "anthropic/claude-sonnet-4",
|
||||
rawId: "claude-sonnet-4",
|
||||
contextWindow: 200_000,
|
||||
maxOutputTokens: 8192,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
supportsReasoningBudget: true,
|
||||
pricing: {
|
||||
inputPerMillion: 3,
|
||||
outputPerMillion: 15,
|
||||
cacheReadPerMillion: 0.3,
|
||||
cacheWritePerMillion: 3.75,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "openai/gpt-4o",
|
||||
rawId: "gpt-4o",
|
||||
contextWindow: 128_000,
|
||||
maxOutputTokens: 16_384,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: false,
|
||||
pricing: {
|
||||
inputPerMillion: 2.5,
|
||||
outputPerMillion: 10,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
const models = await getPoeModels("test-key")
|
||||
|
||||
expect(mockFetchPoeModels).toHaveBeenCalledWith({ apiKey: "test-key", baseURL: undefined })
|
||||
|
||||
expect(models["anthropic/claude-sonnet-4"]).toEqual({
|
||||
contextWindow: 200_000,
|
||||
maxTokens: 8192,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
supportsReasoningBudget: true,
|
||||
inputPrice: 3,
|
||||
outputPrice: 15,
|
||||
cacheReadsPrice: 0.3,
|
||||
cacheWritesPrice: 3.75,
|
||||
})
|
||||
|
||||
expect(models["openai/gpt-4o"]).toEqual({
|
||||
contextWindow: 128_000,
|
||||
maxTokens: 16_384,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 2.5,
|
||||
outputPrice: 10,
|
||||
})
|
||||
})
|
||||
|
||||
it("passes baseURL when provided", async () => {
|
||||
mockFetchPoeModels.mockResolvedValue([])
|
||||
mockGetModels.mockReturnValue([])
|
||||
|
||||
await getPoeModels("key", "https://custom.api.com/v1")
|
||||
|
||||
expect(mockFetchPoeModels).toHaveBeenCalledWith({ apiKey: "key", baseURL: "https://custom.api.com/v1" })
|
||||
})
|
||||
|
||||
it("returns empty record on empty response", async () => {
|
||||
mockFetchPoeModels.mockResolvedValue([])
|
||||
mockGetModels.mockReturnValue([])
|
||||
|
||||
const models = await getPoeModels("key")
|
||||
|
||||
expect(models).toEqual({})
|
||||
})
|
||||
|
||||
it("handles models with missing optional fields", async () => {
|
||||
mockFetchPoeModels.mockResolvedValue([])
|
||||
mockGetModels.mockReturnValue([
|
||||
{
|
||||
id: "some-model",
|
||||
rawId: "some-model",
|
||||
contextWindow: 4096,
|
||||
maxOutputTokens: 1024,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
},
|
||||
])
|
||||
|
||||
const models = await getPoeModels("key")
|
||||
|
||||
expect(models["some-model"]).toEqual({
|
||||
contextWindow: 4096,
|
||||
maxTokens: 1024,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
})
|
||||
})
|
||||
|
||||
it("maps supportsReasoningEffort when present", async () => {
|
||||
mockFetchPoeModels.mockResolvedValue([])
|
||||
mockGetModels.mockReturnValue([
|
||||
{
|
||||
id: "openai/o3",
|
||||
rawId: "o3",
|
||||
contextWindow: 200_000,
|
||||
maxOutputTokens: 100_000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: false,
|
||||
supportsReasoningEffort: ["low", "medium", "high"],
|
||||
},
|
||||
])
|
||||
|
||||
const models = await getPoeModels("key")
|
||||
|
||||
expect(models["openai/o3"].supportsReasoningEffort).toEqual(["low", "medium", "high"])
|
||||
})
|
||||
})
|
||||
|
|
@ -24,6 +24,7 @@ import { getLiteLLMModels } from "./litellm"
|
|||
import { GetModelsOptions } from "../../../shared/api"
|
||||
import { getOllamaModels } from "./ollama"
|
||||
import { getLMStudioModels } from "./lmstudio"
|
||||
import { getPoeModels } from "./poe"
|
||||
import { getRooModels } from "./roo"
|
||||
|
||||
const memoryCache = new NodeCache({ stdTTL: 5 * 60, checkperiod: 5 * 60 })
|
||||
|
|
@ -91,6 +92,9 @@ async function fetchModelsFromProvider(options: GetModelsOptions): Promise<Model
|
|||
models = await getRooModels(rooBaseUrl, options.apiKey)
|
||||
break
|
||||
}
|
||||
case "poe":
|
||||
models = await getPoeModels(options.apiKey, options.baseUrl)
|
||||
break
|
||||
default: {
|
||||
// Ensures router is exhaustively checked if RouterName is a strict union.
|
||||
const exhaustiveCheck: never = provider
|
||||
|
|
|
|||
44
src/api/providers/fetchers/poe.ts
Normal file
44
src/api/providers/fetchers/poe.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import type { ModelInfo, ModelRecord } from "@roo-code/types"
|
||||
import { fetchPoeModels, getModels } from "ai-sdk-provider-poe/code"
|
||||
|
||||
export async function getPoeModels(apiKey?: string, baseURL?: string): Promise<ModelRecord> {
|
||||
try {
|
||||
// fetchPoeModels populates the internal model store, then getModels()
|
||||
// returns only code-capable models with camelCase fields.
|
||||
await fetchPoeModels({ apiKey, baseURL })
|
||||
const poeModels = getModels()
|
||||
const models: ModelRecord = {}
|
||||
|
||||
for (const m of poeModels) {
|
||||
// The library's applyReasoningFallbacks workaround sets
|
||||
// supportsReasoningEffort to boolean `true` for any model that
|
||||
// supports /v1/responses, even when the model has no actual
|
||||
// reasoning capability (e.g. Haiku 3/3.5). Only trust the value
|
||||
// when it is an explicit array of effort levels.
|
||||
const effort = Array.isArray(m.supportsReasoningEffort) ? m.supportsReasoningEffort : undefined
|
||||
const info: ModelInfo = {
|
||||
contextWindow: m.contextWindow,
|
||||
maxTokens: m.maxOutputTokens,
|
||||
supportsImages: m.supportsImages,
|
||||
supportsPromptCache: m.supportsPromptCache,
|
||||
...(m.supportsReasoningBudget && { supportsReasoningBudget: m.supportsReasoningBudget }),
|
||||
...(effort && {
|
||||
supportsReasoningEffort: effort as ModelInfo["supportsReasoningEffort"],
|
||||
}),
|
||||
...(m.pricing?.inputPerMillion != null && { inputPrice: m.pricing.inputPerMillion }),
|
||||
...(m.pricing?.outputPerMillion != null && { outputPrice: m.pricing.outputPerMillion }),
|
||||
...(m.pricing?.cacheReadPerMillion != null && { cacheReadsPrice: m.pricing.cacheReadPerMillion }),
|
||||
...(m.pricing?.cacheWritePerMillion != null && { cacheWritesPrice: m.pricing.cacheWritePerMillion }),
|
||||
}
|
||||
|
||||
models[m.id] = info
|
||||
}
|
||||
|
||||
return models
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[Poe] Error fetching models: ${JSON.stringify(error, Object.getOwnPropertyNames(error as object), 2)}`,
|
||||
)
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ export { OpenAiHandler } from "./openai"
|
|||
export { OpenAICompatibleHandler } from "./openai-compatible"
|
||||
export type { OpenAICompatibleConfig } from "./openai-compatible"
|
||||
export { OpenRouterHandler } from "./openrouter"
|
||||
export { PoeHandler } from "./poe"
|
||||
export { QwenCodeHandler } from "./qwen-code"
|
||||
export { RequestyHandler } from "./requesty"
|
||||
export { SambaNovaHandler } from "./sambanova"
|
||||
|
|
|
|||
151
src/api/providers/poe.ts
Normal file
151
src/api/providers/poe.ts
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { createPoe, type PoeProvider, type PoeScopedProviderOptions } from "ai-sdk-provider-poe"
|
||||
import { extractUsageMetrics, mapToolChoice } from "ai-sdk-provider-poe/code"
|
||||
import { streamText, generateText, type ToolSet } from "ai"
|
||||
|
||||
import {
|
||||
poeDefaultModelId,
|
||||
getPoeDefaultModelInfo,
|
||||
type ModelInfo,
|
||||
type ReasoningEffortExtended,
|
||||
ApiProviderError,
|
||||
} from "@roo-code/types"
|
||||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
|
||||
import { shouldUseReasoningBudget, shouldUseReasoningEffort, type ApiHandlerOptions } from "../../shared/api"
|
||||
|
||||
import { convertToAiSdkMessages, convertToolsForAiSdk, processAiSdkStreamPart } from "../transform/ai-sdk"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
import { BaseProvider } from "./base-provider"
|
||||
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
|
||||
import { getModelsFromCache } from "./fetchers/modelCache"
|
||||
|
||||
const DEFAULT_THINKING_BUDGET = 8192
|
||||
|
||||
export class PoeHandler extends BaseProvider implements SingleCompletionHandler {
|
||||
protected options: ApiHandlerOptions
|
||||
private poe: PoeProvider
|
||||
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
super()
|
||||
this.options = options
|
||||
this.poe = createPoe({
|
||||
apiKey: options.poeApiKey ?? "not-provided",
|
||||
baseURL: options.poeBaseUrl || undefined,
|
||||
})
|
||||
}
|
||||
|
||||
override getModel() {
|
||||
const id = this.options.apiModelId ?? poeDefaultModelId
|
||||
const cached = getModelsFromCache("poe")
|
||||
const info: ModelInfo = cached?.[id] ?? getPoeDefaultModelInfo()
|
||||
return { id, info }
|
||||
}
|
||||
|
||||
override async *createMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
metadata?: ApiHandlerCreateMessageMetadata,
|
||||
): ApiStream {
|
||||
const { id, info } = this.getModel()
|
||||
const languageModel = this.poe(id)
|
||||
|
||||
const aiSdkMessages = convertToAiSdkMessages(messages)
|
||||
const openAiTools = this.convertToolsForOpenAI(metadata?.tools)
|
||||
const aiSdkTools = convertToolsForAiSdk(openAiTools) as ToolSet | undefined
|
||||
|
||||
const useBudget = shouldUseReasoningBudget({ model: info, settings: this.options })
|
||||
const useEffort = !useBudget && shouldUseReasoningEffort({ model: info, settings: this.options })
|
||||
|
||||
// Only pass temperature when the user explicitly configured it.
|
||||
let temperature: number | undefined = this.options.modelTemperature ?? undefined
|
||||
let maxOutputTokens: number | undefined
|
||||
const providerOptions: NonNullable<Parameters<typeof streamText>[0]["providerOptions"]> & {
|
||||
poe?: PoeScopedProviderOptions
|
||||
} = {}
|
||||
|
||||
if (useBudget) {
|
||||
const requestedBudget = this.options.modelMaxThinkingTokens ?? DEFAULT_THINKING_BUDGET
|
||||
// maxOutputTokens is the text-only budget; reasoningBudgetTokens is
|
||||
// separate, so total output = maxOutputTokens + reasoningBudgetTokens.
|
||||
maxOutputTokens = this.options.modelMaxTokens ?? Math.max(0, (info.maxTokens ?? 0) - requestedBudget)
|
||||
providerOptions.poe = {
|
||||
reasoningBudgetTokens: requestedBudget,
|
||||
}
|
||||
temperature = 1.0
|
||||
} else if (useEffort) {
|
||||
let effort = (this.options.reasoningEffort ?? info.reasoningEffort ?? "medium") as ReasoningEffortExtended
|
||||
// Validate that the effort level is actually supported by the current model
|
||||
const supportedEfforts = info.supportsReasoningEffort
|
||||
if (Array.isArray(supportedEfforts) && !supportedEfforts.includes(effort as any)) {
|
||||
effort = (info.reasoningEffort as ReasoningEffortExtended) ?? "medium"
|
||||
}
|
||||
providerOptions.poe = {
|
||||
reasoningEffort: effort,
|
||||
reasoningSummary: "auto",
|
||||
}
|
||||
if (this.options.modelMaxTokens) {
|
||||
maxOutputTokens = this.options.modelMaxTokens
|
||||
}
|
||||
}
|
||||
|
||||
let result
|
||||
try {
|
||||
result = streamText({
|
||||
model: languageModel,
|
||||
system: systemPrompt,
|
||||
messages: aiSdkMessages,
|
||||
temperature,
|
||||
maxOutputTokens,
|
||||
tools: aiSdkTools,
|
||||
toolChoice: mapToolChoice(metadata?.tool_choice as any),
|
||||
...(Object.keys(providerOptions).length > 0 && { providerOptions }),
|
||||
})
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
TelemetryService.instance.captureException(new ApiProviderError(errorMessage, "poe", id, "createMessage"))
|
||||
throw new Error(`Poe completion error: ${errorMessage}`)
|
||||
}
|
||||
|
||||
try {
|
||||
for await (const part of result.fullStream) {
|
||||
for (const chunk of processAiSdkStreamPart(part)) {
|
||||
yield chunk
|
||||
}
|
||||
}
|
||||
|
||||
const usage = await result.usage
|
||||
if (usage) {
|
||||
const metrics = extractUsageMetrics(usage as any)
|
||||
yield {
|
||||
type: "usage" as const,
|
||||
inputTokens: metrics.inputTokens,
|
||||
outputTokens: metrics.outputTokens,
|
||||
cacheReadTokens: metrics.cacheReadTokens,
|
||||
cacheWriteTokens: metrics.cacheWriteTokens,
|
||||
reasoningTokens: metrics.reasoningTokens,
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
TelemetryService.instance.captureException(new ApiProviderError(errorMessage, "poe", id, "createMessage"))
|
||||
throw new Error(`Poe streaming error: ${errorMessage}`)
|
||||
}
|
||||
}
|
||||
|
||||
async completePrompt(prompt: string): Promise<string> {
|
||||
const { id } = this.getModel()
|
||||
try {
|
||||
const { text } = await generateText({
|
||||
model: this.poe(id),
|
||||
prompt,
|
||||
})
|
||||
return text
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
TelemetryService.instance.captureException(new ApiProviderError(errorMessage, "poe", id, "completePrompt"))
|
||||
throw new Error(`Poe completion error: ${errorMessage}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2494,6 +2494,7 @@ describe("ClineProvider - Router Models", () => {
|
|||
ollama: {},
|
||||
lmstudio: {},
|
||||
"vercel-ai-gateway": mockModels,
|
||||
poe: {},
|
||||
},
|
||||
values: undefined,
|
||||
})
|
||||
|
|
@ -2540,6 +2541,7 @@ describe("ClineProvider - Router Models", () => {
|
|||
lmstudio: {},
|
||||
litellm: {},
|
||||
"vercel-ai-gateway": mockModels,
|
||||
poe: {},
|
||||
},
|
||||
values: undefined,
|
||||
})
|
||||
|
|
@ -2634,6 +2636,7 @@ describe("ClineProvider - Router Models", () => {
|
|||
ollama: {},
|
||||
lmstudio: {},
|
||||
"vercel-ai-gateway": mockModels,
|
||||
poe: {},
|
||||
},
|
||||
values: undefined,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -346,6 +346,7 @@ describe("webviewMessageHandler - requestRouterModels", () => {
|
|||
ollama: {},
|
||||
lmstudio: {},
|
||||
"vercel-ai-gateway": mockModels,
|
||||
poe: {},
|
||||
},
|
||||
values: undefined,
|
||||
})
|
||||
|
|
@ -431,6 +432,7 @@ describe("webviewMessageHandler - requestRouterModels", () => {
|
|||
ollama: {},
|
||||
lmstudio: {},
|
||||
"vercel-ai-gateway": mockModels,
|
||||
poe: {},
|
||||
},
|
||||
values: undefined,
|
||||
})
|
||||
|
|
@ -486,6 +488,7 @@ describe("webviewMessageHandler - requestRouterModels", () => {
|
|||
ollama: {},
|
||||
lmstudio: {},
|
||||
"vercel-ai-gateway": mockModels,
|
||||
poe: {},
|
||||
},
|
||||
values: undefined,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -955,6 +955,7 @@ export const webviewMessageHandler = async (
|
|||
ollama: {},
|
||||
lmstudio: {},
|
||||
roo: {},
|
||||
poe: {},
|
||||
}
|
||||
|
||||
const safeGetModels = async (options: GetModelsOptions): Promise<ModelRecord> => {
|
||||
|
|
@ -1018,6 +1019,21 @@ export const webviewMessageHandler = async (
|
|||
})
|
||||
}
|
||||
|
||||
// Poe is conditional on apiKey
|
||||
const poeApiKey = apiConfiguration.poeApiKey || message?.values?.poeApiKey
|
||||
const poeBaseUrl = apiConfiguration.poeBaseUrl || message?.values?.poeBaseUrl
|
||||
|
||||
if (poeApiKey) {
|
||||
if (message?.values?.poeApiKey || message?.values?.poeBaseUrl) {
|
||||
await flushModels({ provider: "poe", apiKey: poeApiKey, baseUrl: poeBaseUrl }, true)
|
||||
}
|
||||
|
||||
candidates.push({
|
||||
key: "poe",
|
||||
options: { provider: "poe", apiKey: poeApiKey, baseUrl: poeBaseUrl },
|
||||
})
|
||||
}
|
||||
|
||||
// Apply single provider filter if specified
|
||||
const modelFetchPromises = providerFilter
|
||||
? candidates.filter(({ key }) => key === providerFilter)
|
||||
|
|
|
|||
|
|
@ -471,6 +471,7 @@
|
|||
"@roo-code/telemetry": "workspace:^",
|
||||
"@roo-code/types": "workspace:^",
|
||||
"@vscode/codicons": "^0.0.36",
|
||||
"ai-sdk-provider-poe": "2.0.18",
|
||||
"async-mutex": "^0.5.0",
|
||||
"axios": "^1.12.0",
|
||||
"cheerio": "^1.0.0",
|
||||
|
|
|
|||
|
|
@ -177,6 +177,7 @@ const dynamicProviderExtras = {
|
|||
ollama: {} as {}, // eslint-disable-line @typescript-eslint/no-empty-object-type
|
||||
lmstudio: {} as {}, // eslint-disable-line @typescript-eslint/no-empty-object-type
|
||||
roo: {} as { apiKey?: string; baseUrl?: string },
|
||||
poe: {} as { apiKey?: string; baseUrl?: string },
|
||||
} as const satisfies Record<RouterName, object>
|
||||
|
||||
// Build the dynamic options union from the map, intersected with CommonFetchParams
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { memo, useCallback, useEffect, useMemo, useState } from "react"
|
||||
import { convertHeadersToObject } from "./utils/headers"
|
||||
import { useDebounce } from "react-use"
|
||||
import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
import { VSCodeLink, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
import { ExternalLinkIcon } from "@radix-ui/react-icons"
|
||||
|
||||
import {
|
||||
|
|
@ -10,6 +10,7 @@ import {
|
|||
isRetiredProvider,
|
||||
DEFAULT_CONSECUTIVE_MISTAKE_LIMIT,
|
||||
openRouterDefaultModelId,
|
||||
poeDefaultModelId,
|
||||
requestyDefaultModelId,
|
||||
litellmDefaultModelId,
|
||||
openAiNativeDefaultModelId,
|
||||
|
|
@ -80,6 +81,7 @@ import {
|
|||
OpenAICompatible,
|
||||
OpenAICodex,
|
||||
OpenRouter,
|
||||
Poe,
|
||||
QwenCode,
|
||||
Requesty,
|
||||
Roo,
|
||||
|
|
@ -238,7 +240,7 @@ const ApiOptions = ({
|
|||
vscode.postMessage({ type: "requestLmStudioModels" })
|
||||
} else if (selectedProvider === "vscode-lm") {
|
||||
vscode.postMessage({ type: "requestVsCodeLmModels" })
|
||||
} else if (selectedProvider === "litellm" || selectedProvider === "roo") {
|
||||
} else if (selectedProvider === "litellm" || selectedProvider === "roo" || selectedProvider === "poe") {
|
||||
vscode.postMessage({ type: "requestRouterModels" })
|
||||
}
|
||||
},
|
||||
|
|
@ -252,6 +254,8 @@ const ApiOptions = ({
|
|||
apiConfiguration?.lmStudioBaseUrl,
|
||||
apiConfiguration?.litellmBaseUrl,
|
||||
apiConfiguration?.litellmApiKey,
|
||||
apiConfiguration?.poeApiKey,
|
||||
apiConfiguration?.poeBaseUrl,
|
||||
customHeaders,
|
||||
],
|
||||
)
|
||||
|
|
@ -356,6 +360,7 @@ const ApiOptions = ({
|
|||
: internationalZAiDefaultModelId,
|
||||
},
|
||||
fireworks: { field: "apiModelId", default: fireworksDefaultModelId },
|
||||
poe: { field: "apiModelId", default: poeDefaultModelId },
|
||||
roo: { field: "apiModelId", default: rooDefaultModelId },
|
||||
"vercel-ai-gateway": { field: "vercelAiGatewayModelId", default: vercelAiGatewayDefaultModelId },
|
||||
openai: { field: "openAiModelId" },
|
||||
|
|
@ -703,6 +708,16 @@ const ApiOptions = ({
|
|||
/>
|
||||
)}
|
||||
|
||||
{selectedProvider === "poe" && (
|
||||
<Poe
|
||||
apiConfiguration={apiConfiguration}
|
||||
setApiConfigurationField={setApiConfigurationField}
|
||||
organizationAllowList={organizationAllowList}
|
||||
modelValidationError={modelValidationError}
|
||||
simplifySettings={fromWelcomeView}
|
||||
/>
|
||||
)}
|
||||
|
||||
{selectedProvider === "roo" && (
|
||||
<Roo
|
||||
apiConfiguration={apiConfiguration}
|
||||
|
|
@ -800,6 +815,17 @@ const ApiOptions = ({
|
|||
}
|
||||
onChange={(value) => setApiConfigurationField("consecutiveMistakeLimit", value)}
|
||||
/>
|
||||
{selectedProvider === "poe" && (
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.poeBaseUrl || ""}
|
||||
onInput={handleInputChange("poeBaseUrl")}
|
||||
placeholder="https://api.poe.com/v1"
|
||||
className="w-full">
|
||||
<label className="block font-medium mb-1">
|
||||
{t("settings:providers.poeBaseUrl")}
|
||||
</label>
|
||||
</VSCodeTextField>
|
||||
)}
|
||||
{selectedProvider === "openrouter" &&
|
||||
openRouterModelProviders &&
|
||||
Object.keys(openRouterModelProviders).length > 0 && (
|
||||
|
|
|
|||
|
|
@ -65,4 +65,5 @@ export const PROVIDERS = [
|
|||
{ value: "minimax", label: "MiniMax", proxy: false },
|
||||
{ value: "baseten", label: "Baseten", proxy: false },
|
||||
{ value: "unbound", label: "Unbound", proxy: false },
|
||||
{ value: "poe", label: "Poe", proxy: false },
|
||||
].sort((a, b) => a.label.localeCompare(b.label))
|
||||
|
|
|
|||
166
webview-ui/src/components/settings/providers/Poe.tsx
Normal file
166
webview-ui/src/components/settings/providers/Poe.tsx
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
import { useCallback, useState, useEffect, useRef } from "react"
|
||||
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useQueryClient } from "@tanstack/react-query"
|
||||
|
||||
import {
|
||||
type ProviderSettings,
|
||||
type OrganizationAllowList,
|
||||
type ExtensionMessage,
|
||||
poeDefaultModelId,
|
||||
type ProviderName,
|
||||
} from "@roo-code/types"
|
||||
|
||||
import { RouterName } from "@roo/api"
|
||||
|
||||
import { useAppTranslation } from "@src/i18n/TranslationContext"
|
||||
import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink"
|
||||
import { useExtensionState } from "@src/context/ExtensionStateContext"
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
import { Button } from "@src/components/ui"
|
||||
|
||||
import { inputEventTransform } from "../transforms"
|
||||
import { ModelPicker } from "../ModelPicker"
|
||||
import { handleModelChangeSideEffects } from "../utils/providerModelConfig"
|
||||
|
||||
type PoeProps = {
|
||||
apiConfiguration: ProviderSettings
|
||||
setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void
|
||||
organizationAllowList: OrganizationAllowList
|
||||
modelValidationError?: string
|
||||
simplifySettings?: boolean
|
||||
}
|
||||
|
||||
export const Poe = ({
|
||||
apiConfiguration,
|
||||
setApiConfigurationField,
|
||||
organizationAllowList,
|
||||
modelValidationError,
|
||||
simplifySettings,
|
||||
}: PoeProps) => {
|
||||
const { t } = useAppTranslation()
|
||||
const queryClient = useQueryClient()
|
||||
const { routerModels } = useExtensionState()
|
||||
const [refreshStatus, setRefreshStatus] = useState<"idle" | "loading" | "success" | "error">("idle")
|
||||
const [refreshError, setRefreshError] = useState<string | undefined>()
|
||||
const poeErrorJustReceived = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
const handleMessage = (event: MessageEvent<ExtensionMessage>) => {
|
||||
const message = event.data
|
||||
if (message.type === "singleRouterModelFetchResponse" && !message.success) {
|
||||
const providerName = message.values?.provider as RouterName
|
||||
if (providerName === "poe") {
|
||||
poeErrorJustReceived.current = true
|
||||
setRefreshStatus("error")
|
||||
setRefreshError(message.error)
|
||||
}
|
||||
} else if (message.type === "routerModels") {
|
||||
if (refreshStatus === "loading") {
|
||||
if (!poeErrorJustReceived.current) {
|
||||
setRefreshStatus("success")
|
||||
// Invalidate the react-query router models cache so
|
||||
// validation in ApiOptions picks up the refreshed list.
|
||||
queryClient.invalidateQueries({ queryKey: ["routerModels"] })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("message", handleMessage)
|
||||
return () => {
|
||||
window.removeEventListener("message", handleMessage)
|
||||
}
|
||||
}, [refreshStatus, queryClient])
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
<K extends keyof ProviderSettings, E>(
|
||||
field: K,
|
||||
transform: (event: E) => ProviderSettings[K] = inputEventTransform,
|
||||
) =>
|
||||
(event: E | Event) => {
|
||||
setApiConfigurationField(field, transform(event as E))
|
||||
},
|
||||
[setApiConfigurationField],
|
||||
)
|
||||
|
||||
const handleRefreshModels = useCallback(() => {
|
||||
poeErrorJustReceived.current = false
|
||||
setRefreshStatus("loading")
|
||||
setRefreshError(undefined)
|
||||
|
||||
const key = apiConfiguration.poeApiKey
|
||||
|
||||
if (!key) {
|
||||
setRefreshStatus("error")
|
||||
setRefreshError(t("settings:providers.refreshModels.missingConfig"))
|
||||
return
|
||||
}
|
||||
|
||||
vscode.postMessage({
|
||||
type: "requestRouterModels",
|
||||
values: { poeApiKey: key, poeBaseUrl: apiConfiguration.poeBaseUrl },
|
||||
})
|
||||
}, [apiConfiguration, t])
|
||||
|
||||
return (
|
||||
<>
|
||||
<VSCodeTextField
|
||||
value={apiConfiguration?.poeApiKey || ""}
|
||||
type="password"
|
||||
onInput={handleInputChange("poeApiKey")}
|
||||
placeholder={t("settings:placeholders.apiKey")}
|
||||
className="w-full">
|
||||
<label className="block font-medium mb-1">{t("settings:providers.poeApiKey")}</label>
|
||||
</VSCodeTextField>
|
||||
<div className="text-sm text-vscode-descriptionForeground -mt-2">
|
||||
{t("settings:providers.apiKeyStorageNotice")}
|
||||
</div>
|
||||
{!apiConfiguration?.poeApiKey && (
|
||||
<VSCodeButtonLink href="https://poe.com/api_key" appearance="secondary">
|
||||
{t("settings:providers.getPoeApiKey")}
|
||||
</VSCodeButtonLink>
|
||||
)}
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleRefreshModels}
|
||||
disabled={refreshStatus === "loading" || !apiConfiguration.poeApiKey}>
|
||||
<div className="flex items-center gap-2">
|
||||
{refreshStatus === "loading" ? (
|
||||
<span className="codicon codicon-loading codicon-modifier-spin" />
|
||||
) : (
|
||||
<span className="codicon codicon-refresh" />
|
||||
)}
|
||||
{t("settings:providers.refreshModels.label")}
|
||||
</div>
|
||||
</Button>
|
||||
{refreshStatus === "loading" && (
|
||||
<div className="text-sm text-vscode-descriptionForeground">
|
||||
{t("settings:providers.refreshModels.loading")}
|
||||
</div>
|
||||
)}
|
||||
{refreshStatus === "success" && (
|
||||
<div className="text-sm text-vscode-foreground">{t("settings:providers.refreshModels.success")}</div>
|
||||
)}
|
||||
{refreshStatus === "error" && (
|
||||
<div className="text-sm text-vscode-errorForeground">
|
||||
{refreshError || t("settings:providers.refreshModels.error")}
|
||||
</div>
|
||||
)}
|
||||
<ModelPicker
|
||||
apiConfiguration={apiConfiguration}
|
||||
setApiConfigurationField={setApiConfigurationField}
|
||||
defaultModelId={poeDefaultModelId}
|
||||
models={routerModels?.poe ?? {}}
|
||||
modelIdKey="apiModelId"
|
||||
serviceName="Poe"
|
||||
serviceUrl="https://poe.com"
|
||||
organizationAllowList={organizationAllowList}
|
||||
errorMessage={modelValidationError}
|
||||
simplifySettings={simplifySettings}
|
||||
onModelChange={(modelId) =>
|
||||
handleModelChangeSideEffects("poe" as ProviderName, modelId, setApiConfigurationField)
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ export { OpenAI } from "./OpenAI"
|
|||
export { OpenAICodex } from "./OpenAICodex"
|
||||
export { OpenAICompatible } from "./OpenAICompatible"
|
||||
export { OpenRouter } from "./OpenRouter"
|
||||
export { Poe } from "./Poe"
|
||||
export { QwenCode } from "./QwenCode"
|
||||
export { Roo } from "./Roo"
|
||||
export { Requesty } from "./Requesty"
|
||||
|
|
|
|||
|
|
@ -150,8 +150,10 @@ export const handleModelChangeSideEffects = <K extends keyof ProviderSettings>(
|
|||
setApiConfigurationField("awsCustomArn" as K, "" as ProviderSettings[K])
|
||||
}
|
||||
|
||||
// All providers: Clear reasoning effort when switching models to allow
|
||||
// the new model's default to take effect. Different models within the
|
||||
// same provider can have different reasoning effort defaults/options.
|
||||
// All providers: Clear reasoning settings when switching models to allow
|
||||
// the new model's defaults to take effect. Different models within the
|
||||
// same provider can have different reasoning defaults/options.
|
||||
setApiConfigurationField("reasoningEffort" as K, undefined as ProviderSettings[K])
|
||||
setApiConfigurationField("modelMaxTokens" as K, undefined as ProviderSettings[K])
|
||||
setApiConfigurationField("modelMaxThinkingTokens" as K, undefined as ProviderSettings[K])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -317,6 +317,11 @@ function getSelectedModel({
|
|||
const info = routerModels.roo?.[id]
|
||||
return { id, info }
|
||||
}
|
||||
case "poe": {
|
||||
const id = apiConfiguration.apiModelId ?? defaultModelId
|
||||
const info = routerModels.poe?.[id]
|
||||
return { id, info }
|
||||
}
|
||||
case "qwen-code": {
|
||||
const id = apiConfiguration.apiModelId ?? defaultModelId
|
||||
const info = qwenCodeModels[id as keyof typeof qwenCodeModels]
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/ca/settings.json
generated
3
webview-ui/src/i18n/locales/ca/settings.json
generated
|
|
@ -380,6 +380,9 @@
|
|||
"vertex1MContextBetaDescription": "Amplia la finestra de context a 1 milió de tokens per a Claude Sonnet 4.x / Claude Opus 4.6",
|
||||
"basetenApiKey": "Clau API de Baseten",
|
||||
"getBasetenApiKey": "Obtenir clau API de Baseten",
|
||||
"poeApiKey": "Clau API de Poe",
|
||||
"getPoeApiKey": "Obtenir clau API de Poe",
|
||||
"poeBaseUrl": "URL base de Poe",
|
||||
"fireworksApiKey": "Clau API de Fireworks",
|
||||
"getFireworksApiKey": "Obtenir clau API de Fireworks",
|
||||
"deepSeekApiKey": "Clau API de DeepSeek",
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/de/settings.json
generated
3
webview-ui/src/i18n/locales/de/settings.json
generated
|
|
@ -380,6 +380,9 @@
|
|||
"vertex1MContextBetaDescription": "Erweitert das Kontextfenster für Claude Sonnet 4.x / Claude Opus 4.6 auf 1 Million Token",
|
||||
"basetenApiKey": "Baseten API-Schlüssel",
|
||||
"getBasetenApiKey": "Baseten API-Schlüssel erhalten",
|
||||
"poeApiKey": "Poe API-Schlüssel",
|
||||
"getPoeApiKey": "Poe API-Schlüssel erhalten",
|
||||
"poeBaseUrl": "Poe Basis-URL",
|
||||
"fireworksApiKey": "Fireworks API-Schlüssel",
|
||||
"getFireworksApiKey": "Fireworks API-Schlüssel erhalten",
|
||||
"deepSeekApiKey": "DeepSeek API-Schlüssel",
|
||||
|
|
|
|||
|
|
@ -443,6 +443,9 @@
|
|||
"vertex1MContextBetaDescription": "Extends context window to 1 million tokens for Claude Sonnet 4.x / Claude Opus 4.6",
|
||||
"basetenApiKey": "Baseten API Key",
|
||||
"getBasetenApiKey": "Get Baseten API Key",
|
||||
"poeApiKey": "Poe API Key",
|
||||
"getPoeApiKey": "Get Poe API Key",
|
||||
"poeBaseUrl": "Poe Base URL",
|
||||
"fireworksApiKey": "Fireworks API Key",
|
||||
"getFireworksApiKey": "Get Fireworks API Key",
|
||||
"deepSeekApiKey": "DeepSeek API Key",
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/es/settings.json
generated
3
webview-ui/src/i18n/locales/es/settings.json
generated
|
|
@ -380,6 +380,9 @@
|
|||
"vertex1MContextBetaDescription": "Amplía la ventana de contexto a 1 millón de tokens para Claude Sonnet 4.x / Claude Opus 4.6",
|
||||
"basetenApiKey": "Clave API de Baseten",
|
||||
"getBasetenApiKey": "Obtener clave API de Baseten",
|
||||
"poeApiKey": "Clave API de Poe",
|
||||
"getPoeApiKey": "Obtener clave API de Poe",
|
||||
"poeBaseUrl": "URL base de Poe",
|
||||
"fireworksApiKey": "Clave API de Fireworks",
|
||||
"getFireworksApiKey": "Obtener clave API de Fireworks",
|
||||
"deepSeekApiKey": "Clave API de DeepSeek",
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/fr/settings.json
generated
3
webview-ui/src/i18n/locales/fr/settings.json
generated
|
|
@ -380,6 +380,9 @@
|
|||
"vertex1MContextBetaDescription": "Étend la fenêtre de contexte à 1 million de tokens pour Claude Sonnet 4.x / Claude Opus 4.6",
|
||||
"basetenApiKey": "Clé API Baseten",
|
||||
"getBasetenApiKey": "Obtenir la clé API Baseten",
|
||||
"poeApiKey": "Clé API Poe",
|
||||
"getPoeApiKey": "Obtenir la clé API Poe",
|
||||
"poeBaseUrl": "URL de base Poe",
|
||||
"fireworksApiKey": "Clé API Fireworks",
|
||||
"getFireworksApiKey": "Obtenir la clé API Fireworks",
|
||||
"deepSeekApiKey": "Clé API DeepSeek",
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/hi/settings.json
generated
3
webview-ui/src/i18n/locales/hi/settings.json
generated
|
|
@ -380,6 +380,9 @@
|
|||
"vertex1MContextBetaDescription": "Claude Sonnet 4.x / Claude Opus 4.6 के लिए संदर्भ विंडो को 1 मिलियन टोकन तक बढ़ाता है",
|
||||
"basetenApiKey": "Baseten API कुंजी",
|
||||
"getBasetenApiKey": "Baseten API कुंजी प्राप्त करें",
|
||||
"poeApiKey": "Poe API कुंजी",
|
||||
"getPoeApiKey": "Poe API कुंजी प्राप्त करें",
|
||||
"poeBaseUrl": "Poe बेस URL",
|
||||
"fireworksApiKey": "Fireworks API कुंजी",
|
||||
"getFireworksApiKey": "Fireworks API कुंजी प्राप्त करें",
|
||||
"deepSeekApiKey": "DeepSeek API कुंजी",
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/id/settings.json
generated
3
webview-ui/src/i18n/locales/id/settings.json
generated
|
|
@ -380,6 +380,9 @@
|
|||
"vertex1MContextBetaDescription": "Memperluas jendela konteks menjadi 1 juta token untuk Claude Sonnet 4.x / Claude Opus 4.6",
|
||||
"basetenApiKey": "Baseten API Key",
|
||||
"getBasetenApiKey": "Dapatkan Baseten API Key",
|
||||
"poeApiKey": "Poe API Key",
|
||||
"getPoeApiKey": "Dapatkan Poe API Key",
|
||||
"poeBaseUrl": "Poe Base URL",
|
||||
"fireworksApiKey": "Fireworks API Key",
|
||||
"getFireworksApiKey": "Dapatkan Fireworks API Key",
|
||||
"deepSeekApiKey": "DeepSeek API Key",
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/it/settings.json
generated
3
webview-ui/src/i18n/locales/it/settings.json
generated
|
|
@ -380,6 +380,9 @@
|
|||
"vertex1MContextBetaDescription": "Estende la finestra di contesto a 1 milione di token per Claude Sonnet 4.x / Claude Opus 4.6",
|
||||
"basetenApiKey": "Chiave API Baseten",
|
||||
"getBasetenApiKey": "Ottieni chiave API Baseten",
|
||||
"poeApiKey": "Chiave API Poe",
|
||||
"getPoeApiKey": "Ottieni chiave API Poe",
|
||||
"poeBaseUrl": "URL base Poe",
|
||||
"fireworksApiKey": "Chiave API Fireworks",
|
||||
"getFireworksApiKey": "Ottieni chiave API Fireworks",
|
||||
"deepSeekApiKey": "Chiave API DeepSeek",
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/ja/settings.json
generated
3
webview-ui/src/i18n/locales/ja/settings.json
generated
|
|
@ -380,6 +380,9 @@
|
|||
"vertex1MContextBetaDescription": "Claude Sonnet 4.x / Claude Opus 4.6のコンテキストウィンドウを100万トークンに拡張します",
|
||||
"basetenApiKey": "Baseten APIキー",
|
||||
"getBasetenApiKey": "Baseten APIキーを取得",
|
||||
"poeApiKey": "Poe APIキー",
|
||||
"getPoeApiKey": "Poe APIキーを取得",
|
||||
"poeBaseUrl": "Poe ベースURL",
|
||||
"fireworksApiKey": "Fireworks APIキー",
|
||||
"getFireworksApiKey": "Fireworks APIキーを取得",
|
||||
"deepSeekApiKey": "DeepSeek APIキー",
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/ko/settings.json
generated
3
webview-ui/src/i18n/locales/ko/settings.json
generated
|
|
@ -380,6 +380,9 @@
|
|||
"vertex1MContextBetaDescription": "Claude Sonnet 4.x / Claude Opus 4.6의 컨텍스트 창을 100만 토큰으로 확장",
|
||||
"basetenApiKey": "Baseten API 키",
|
||||
"getBasetenApiKey": "Baseten API 키 가져오기",
|
||||
"poeApiKey": "Poe API 키",
|
||||
"getPoeApiKey": "Poe API 키 가져오기",
|
||||
"poeBaseUrl": "Poe 기본 URL",
|
||||
"fireworksApiKey": "Fireworks API 키",
|
||||
"getFireworksApiKey": "Fireworks API 키 받기",
|
||||
"deepSeekApiKey": "DeepSeek API 키",
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/nl/settings.json
generated
3
webview-ui/src/i18n/locales/nl/settings.json
generated
|
|
@ -380,6 +380,9 @@
|
|||
"vertex1MContextBetaDescription": "Breidt het contextvenster uit tot 1 miljoen tokens voor Claude Sonnet 4.x / Claude Opus 4.6",
|
||||
"basetenApiKey": "Baseten API-sleutel",
|
||||
"getBasetenApiKey": "Baseten API-sleutel verkrijgen",
|
||||
"poeApiKey": "Poe API-sleutel",
|
||||
"getPoeApiKey": "Poe API-sleutel verkrijgen",
|
||||
"poeBaseUrl": "Poe Basis-URL",
|
||||
"fireworksApiKey": "Fireworks API-sleutel",
|
||||
"getFireworksApiKey": "Fireworks API-sleutel ophalen",
|
||||
"deepSeekApiKey": "DeepSeek API-sleutel",
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/pl/settings.json
generated
3
webview-ui/src/i18n/locales/pl/settings.json
generated
|
|
@ -380,6 +380,9 @@
|
|||
"vertex1MContextBetaDescription": "Rozszerza okno kontekstowe do 1 miliona tokenów dla Claude Sonnet 4.x / Claude Opus 4.6",
|
||||
"basetenApiKey": "Klucz API Baseten",
|
||||
"getBasetenApiKey": "Uzyskaj klucz API Baseten",
|
||||
"poeApiKey": "Klucz API Poe",
|
||||
"getPoeApiKey": "Uzyskaj klucz API Poe",
|
||||
"poeBaseUrl": "Bazowy URL Poe",
|
||||
"fireworksApiKey": "Klucz API Fireworks",
|
||||
"getFireworksApiKey": "Uzyskaj klucz API Fireworks",
|
||||
"deepSeekApiKey": "Klucz API DeepSeek",
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/pt-BR/settings.json
generated
3
webview-ui/src/i18n/locales/pt-BR/settings.json
generated
|
|
@ -380,6 +380,9 @@
|
|||
"vertex1MContextBetaDescription": "Estende a janela de contexto para 1 milhão de tokens para o Claude Sonnet 4.x / Claude Opus 4.6",
|
||||
"basetenApiKey": "Chave de API Baseten",
|
||||
"getBasetenApiKey": "Obter chave de API Baseten",
|
||||
"poeApiKey": "Chave de API Poe",
|
||||
"getPoeApiKey": "Obter chave de API Poe",
|
||||
"poeBaseUrl": "URL base do Poe",
|
||||
"fireworksApiKey": "Chave de API Fireworks",
|
||||
"getFireworksApiKey": "Obter chave de API Fireworks",
|
||||
"deepSeekApiKey": "Chave de API DeepSeek",
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/ru/settings.json
generated
3
webview-ui/src/i18n/locales/ru/settings.json
generated
|
|
@ -380,6 +380,9 @@
|
|||
"vertex1MContextBetaDescription": "Расширяет контекстное окно до 1 миллиона токенов для Claude Sonnet 4.x / Claude Opus 4.6",
|
||||
"basetenApiKey": "Baseten API-ключ",
|
||||
"getBasetenApiKey": "Получить Baseten API-ключ",
|
||||
"poeApiKey": "API-ключ Poe",
|
||||
"getPoeApiKey": "Получить API-ключ Poe",
|
||||
"poeBaseUrl": "Базовый URL Poe",
|
||||
"fireworksApiKey": "Fireworks API-ключ",
|
||||
"getFireworksApiKey": "Получить Fireworks API-ключ",
|
||||
"deepSeekApiKey": "DeepSeek API-ключ",
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/tr/settings.json
generated
3
webview-ui/src/i18n/locales/tr/settings.json
generated
|
|
@ -380,6 +380,9 @@
|
|||
"vertex1MContextBetaDescription": "Claude Sonnet 4.x / Claude Opus 4.6 için bağlam penceresini 1 milyon token'a genişletir",
|
||||
"basetenApiKey": "Baseten API Anahtarı",
|
||||
"getBasetenApiKey": "Baseten API Anahtarı Al",
|
||||
"poeApiKey": "Poe API Anahtarı",
|
||||
"getPoeApiKey": "Poe API Anahtarı Al",
|
||||
"poeBaseUrl": "Poe Temel URL",
|
||||
"fireworksApiKey": "Fireworks API Anahtarı",
|
||||
"getFireworksApiKey": "Fireworks API Anahtarı Al",
|
||||
"deepSeekApiKey": "DeepSeek API Anahtarı",
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/vi/settings.json
generated
3
webview-ui/src/i18n/locales/vi/settings.json
generated
|
|
@ -380,6 +380,9 @@
|
|||
"vertex1MContextBetaDescription": "Mở rộng cửa sổ ngữ cảnh lên 1 triệu token cho Claude Sonnet 4.x / Claude Opus 4.6",
|
||||
"basetenApiKey": "Khóa API Baseten",
|
||||
"getBasetenApiKey": "Lấy khóa API Baseten",
|
||||
"poeApiKey": "Khóa API Poe",
|
||||
"getPoeApiKey": "Lấy khóa API Poe",
|
||||
"poeBaseUrl": "URL cơ sở Poe",
|
||||
"fireworksApiKey": "Khóa API Fireworks",
|
||||
"getFireworksApiKey": "Lấy khóa API Fireworks",
|
||||
"deepSeekApiKey": "Khóa API DeepSeek",
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/zh-CN/settings.json
generated
3
webview-ui/src/i18n/locales/zh-CN/settings.json
generated
|
|
@ -380,6 +380,9 @@
|
|||
"vertex1MContextBetaDescription": "为 Claude Sonnet 4.x / Claude Opus 4.6 将上下文窗口扩展至 100 万个 token",
|
||||
"basetenApiKey": "Baseten API 密钥",
|
||||
"getBasetenApiKey": "获取 Baseten API 密钥",
|
||||
"poeApiKey": "Poe API 密钥",
|
||||
"getPoeApiKey": "获取 Poe API 密钥",
|
||||
"poeBaseUrl": "Poe 基础 URL",
|
||||
"fireworksApiKey": "Fireworks API 密钥",
|
||||
"getFireworksApiKey": "获取 Fireworks API 密钥",
|
||||
"deepSeekApiKey": "DeepSeek API 密钥",
|
||||
|
|
|
|||
3
webview-ui/src/i18n/locales/zh-TW/settings.json
generated
3
webview-ui/src/i18n/locales/zh-TW/settings.json
generated
|
|
@ -390,6 +390,9 @@
|
|||
"vertex1MContextBetaDescription": "為 Claude Sonnet 4.x / Claude Opus 4.6 將上下文視窗擴展至 100 萬個 token",
|
||||
"basetenApiKey": "Baseten API 金鑰",
|
||||
"getBasetenApiKey": "取得 Baseten API 金鑰",
|
||||
"poeApiKey": "Poe API 金鑰",
|
||||
"getPoeApiKey": "取得 Poe API 金鑰",
|
||||
"poeBaseUrl": "Poe 基礎 URL",
|
||||
"fireworksApiKey": "Fireworks API 金鑰",
|
||||
"getFireworksApiKey": "取得 Fireworks API 金鑰",
|
||||
"deepSeekApiKey": "DeepSeek API 金鑰",
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ describe("Model Validation Functions", () => {
|
|||
lmstudio: {},
|
||||
"vercel-ai-gateway": {},
|
||||
roo: {},
|
||||
poe: {},
|
||||
}
|
||||
|
||||
const allowAllOrganization: OrganizationAllowList = {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue