2025-12-06 12:46:40 +09:00
|
|
|
|
import {
|
2025-12-08 19:52:18 +08:00
|
|
|
|
APICallError,
|
2025-12-06 12:46:40 +09:00
|
|
|
|
convertToModelMessages,
|
|
|
|
|
|
createUIMessageStream,
|
|
|
|
|
|
createUIMessageStreamResponse,
|
2025-12-14 12:34:34 +09:00
|
|
|
|
InvalidToolInputError,
|
2025-12-08 19:52:18 +08:00
|
|
|
|
LoadAPIKeyError,
|
2025-12-07 00:40:13 +09:00
|
|
|
|
stepCountIs,
|
2025-12-06 12:46:40 +09:00
|
|
|
|
streamText,
|
|
|
|
|
|
} from "ai"
|
2025-12-20 23:19:49 +09:00
|
|
|
|
import fs from "fs/promises"
|
2025-12-14 12:34:34 +09:00
|
|
|
|
import { jsonrepair } from "jsonrepair"
|
2025-12-20 23:19:49 +09:00
|
|
|
|
import path from "path"
|
2025-12-06 12:46:40 +09:00
|
|
|
|
import { z } from "zod"
|
2025-12-31 12:20:09 +09:00
|
|
|
|
import {
|
|
|
|
|
|
getAIModel,
|
feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
* feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
- Add minimax, glm, qwen, qiniu, kimi to ProviderName type
- Add provider configurations to PROVIDER_INFO with default base URLs
- Add suggested models for MiniMax in SUGGESTED_MODELS
- Add minimax/glm/qwen/qiniu/kimi cases to getAIModel using OpenAI-compatible SDK
- Update ALLOWED_CLIENT_PROVIDERS and error messages
- Add environment variable examples to env.example
Fixes: MiniMax API compatibility issue (invalid chat setting 2013)
* fix: Add missing providers to PROVIDER_ENV_VARS type
* fix: Handle null case in PROVIDER_ENV_VARS for new providers
* fix: Add minimax/glm/qwen/kimi/qiniu support to validate-model API
- Add getDefaultBaseUrl helper function
- Add validation cases for new providers in validate-model route
* fix: Add new providers to buildProviderOptions switch case
* fix: Merge multiple system messages into one for minimax/glm/qwen/kimi/qiniu
MiniMax API doesn't support multiple system messages.
This fix combines them into a single message for Chinese providers.
* fix: Handle null provider in system message check
* debug: Add logging for allMessages count
* fix: Use effective provider (including env var fallback) for isSingleSystemProvider check
* fix: apply biome formatting (line-wrapping)
* docs: add Chinese AI providers documentation (MiniMax, GLM, Qwen, Kimi, Qiniu)
- Add i18n translations for new providers in all language dictionaries
- Add provider configuration documentation in en/cn/ja docs
* fix: 改进 PR #722 的代码审查反馈
1. 删除重复的 getDefaultBaseUrl 函数,改用 model-config.ts 的 PROVIDER_INFO
2. validate-model 路由改用 AI SDK 的 createOpenAI + generateText
3. 修复 resolveBaseURL 回退逻辑,传入 PROVIDER_INFO 的 defaultBaseUrl
4. 删除无用的 .bak 备份文件
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 修正中国 AI provider 端点配置
- qiniu: api.qiniucdn.com → api.qnaigc.com
- qwen: dashscope.aliyun.com → dashscope.aliyuncs.com
- 更新 env.example 文档链接
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* feat: MiniMax 使用 Anthropic 兼容 API
- MiniMax 改用 createAnthropic (而非 createOpenAI)
- 支持 api.minimax.io/anthropic 和 api.minimaxi.com/anthropic
- 合并多个 system 消息为单个 (MiniMax/GLM/Qwen/Kimi/Qiniu)
- 更新默认模型为 MiniMax-M2.5 系列
- 支持 MINIMAX_BASE_URL 环境变量配置
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* docs: 更新 MiniMax 文档
- 添加 Anthropic 兼容 API 说明
- 更新默认模型为 MiniMax-M2.5
- 添加国际版/中国大陆版配置示例
- 更新 env.example 注释
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 完善 MiniMax 双端点支持及问题修复
- 支持 MiniMax Anthropic 兼容端点和 OpenAI 兼容端点自动切换
- 修正默认端点为 api.minimaxi.com (中国大陆可用)
- 修复端点路径缺少 /v1 的问题
- 添加前端 MiniMax logo 映射
- 移除调试日志
- 修正 env.example 默认配置
* chore: clean backup artifacts and align biome formatting
* fix: resolve effectiveProvider bug, deduplicate MiniMax URL logic, fix docs
- Fix critical bug: effectiveProvider was empty during auto-detection,
causing multi-system-message to be sent to MiniMax (which rejects it).
Now uses resolved provider from getAIModel instead of re-deriving it.
- Extract normalizeMiniMaxBaseURL() shared helper to eliminate duplication
between ai-providers.ts and validate-model/route.ts
- Add guard for undefined MiniMax baseURL to prevent hitting api.anthropic.com
- Fix docs: mark China mainland URL as default (matches code behavior)
- Restructure minimax/glm/qwen/kimi/qiniu validation to use shared pattern
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: document MiniMax dual API formats in docs and UI
- Add hint below Base URL input when MiniMax is selected, explaining
Anthropic-compatible (/anthropic) vs OpenAI-compatible (/v1) endpoints
- Update all 3 ai-providers docs (en/cn/ja) to list all 4 endpoint options
(China/International × Anthropic/OpenAI)
- Add i18n translations for the hint in all 4 locales
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: deduplicate PROVIDER_LOGO_MAP, remove unnecessary optional chaining
- Extract PROVIDER_LOGO_MAP to lib/types/model-config.ts (was duplicated
in model-config-dialog.tsx and model-selector.tsx)
- Remove unnecessary ?. on PROVIDER_INFO.minimax (it's a full Record)
---------
Co-authored-by: msga-oc <msga-oc@gitea.misakiga.top>
Co-authored-by: Shinyi <shinyi@openclaw.ai>
Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 17:53:47 +08:00
|
|
|
|
SINGLE_SYSTEM_PROVIDERS,
|
2025-12-31 12:20:09 +09:00
|
|
|
|
supportsPromptCaching,
|
|
|
|
|
|
} from "@/lib/ai-providers"
|
2025-12-06 12:46:40 +09:00
|
|
|
|
import { findCachedResponse } from "@/lib/cached-responses"
|
test: add Vitest and Playwright testing infrastructure (#512)
* test: add Vitest and Playwright testing infrastructure
- Add Vitest for unit tests (39 tests)
- cached-responses.test.ts
- ai-providers.test.ts
- chat-helpers.test.ts
- utils.test.ts
- Add Playwright for E2E tests (3 smoke tests)
- Homepage load
- Japanese locale
- Settings dialog
- Add CI workflow (.github/workflows/test.yml)
- Add vitest.config.mts and playwright.config.ts
- Update .gitignore for test artifacts
* test: add more E2E tests for UI components
- Chat panel tests (interactive elements, iframe)
- Settings tests (dark mode, language, draw.io theme)
- Save dialog tests (buttons exist)
- History dialog tests
- Model config tests
- Keyboard interaction tests
- Upload area tests
Total: 15 E2E tests, all passing
* test: fix E2E test issues from review
Fixes based on Gemini and Codex review:
- Remove brittle nth(1) selector in keyboard tests
- Remove waitForTimeout(500) race condition
- Remove if(isVisible) silent skip patterns
- Add proper assertions instead of no-op checks
- Remove expect(count >= 0) that always passes
- Remove unused hasProviderUI variable
All 14 E2E tests and 39 unit tests pass.
* style: auto-format with Biome
* fix: resolve lint errors for CI
* test(e2e): add diagram generation tests with mocked AI responses
- Add tests for generate, edit, and append diagram operations
- Use SSE mocked responses matching AI SDK UI message stream format
- Generate mxCell XML directly in tests for deterministic assertions
- Tests verify tool card rendering and 'Complete' badge state
* test: add comprehensive E2E tests for all major features
- Error handling tests (API errors, rate limits, network timeout, truncated XML)
- Multi-turn conversation tests (sequential requests, history preservation)
- File upload tests (upload button, file preview, sending with message)
- Theme switching tests (dark mode toggle, persistence, system preference)
- Language switching tests (EN/JA/ZH, persistence, locale URLs)
- Iframe interaction tests (draw.io loading, toolbar, diagram rendering)
- Copy/paste tests (chat input, XML input, special characters)
- History restore tests (new chat, persistence, browser navigation)
* refactor: extract shared test helpers and improve error assertions
- Create tests/e2e/lib/helpers.ts with shared SSE mock functions
- Add proper error UI assertions to error-handling.spec.ts
- Remove waitForTimeout calls in favor of real assertions
- Update 6 test files to use shared helpers
* docs: add testing section to CONTRIBUTING.md
* fix: improve test infrastructure based on PR review
- Fix double build in CI: remove redundant build from playwright webServer
- Export chat helpers from shared module for proper unit testing
- Replace waitForTimeout with explicit waits in E2E tests
- Add data-testid attributes to settings and new chat buttons
- Add list reporter for CI to show failures in logs
- Add Playwright browser caching to speed up CI
- Add vitest coverage configuration
- Fix conditional test assertions to use test.skip() instead of silent pass
- Remove unused variables flagged by linter
* fix: improve E2E test assertions and remove silent skips
- Replace silent test.skip() with explicit conditional skips
- Add actual persistence assertion after page reload
- Use data-testid selector for new chat button test
* refactor: add shared fixtures and test.step() patterns
- Add tests/e2e/lib/fixtures.ts with shared test helpers
- Add tests/e2e/fixtures/diagrams.ts with XML test data
- Add expectBeforeAndAfterReload() helper for persistence tests
- Add test.step() for better test reporting in complex tests
- Consolidate mock helpers into fixtures module
- Reduce code duplication across 17 test files
* fix: make persistence tests more reliable
- Remove expectBeforeAndAfterReload from mocked API tests
- Add explicit test.step() for before/after reload checks
- Add retry config for flaky clipboard tests
- Add sleep after reload for language persistence test
* test: remove flaky XML paste test
* docs: run both unit and e2e tests before PR
* chore: add type check and unit test git hooks
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-01-05 01:37:32 +09:00
|
|
|
|
import {
|
|
|
|
|
|
isMinimalDiagram,
|
|
|
|
|
|
replaceHistoricalToolInputs,
|
|
|
|
|
|
validateFileParts,
|
|
|
|
|
|
} from "@/lib/chat-helpers"
|
2025-12-23 18:36:27 +09:00
|
|
|
|
import {
|
|
|
|
|
|
checkAndIncrementRequest,
|
|
|
|
|
|
isQuotaEnabled,
|
|
|
|
|
|
recordTokenUsage,
|
|
|
|
|
|
} from "@/lib/dynamo-quota-manager"
|
2025-12-06 12:46:40 +09:00
|
|
|
|
import {
|
|
|
|
|
|
getTelemetryConfig,
|
|
|
|
|
|
setTraceInput,
|
|
|
|
|
|
setTraceOutput,
|
|
|
|
|
|
wrapWithObserve,
|
|
|
|
|
|
} from "@/lib/langfuse"
|
2026-01-15 21:28:22 +05:30
|
|
|
|
import { findServerModelById } from "@/lib/server-model-config"
|
2025-12-06 12:46:40 +09:00
|
|
|
|
import { getSystemPrompt } from "@/lib/system-prompts"
|
2025-12-25 12:20:46 +09:00
|
|
|
|
import { getUserIdFromRequest } from "@/lib/user-id"
|
2025-12-06 12:46:40 +09:00
|
|
|
|
|
2025-12-12 16:13:06 +09:00
|
|
|
|
export const maxDuration = 120
|
2025-03-19 11:03:37 +00:00
|
|
|
|
|
2025-12-01 14:07:50 +09:00
|
|
|
|
// Helper function to create cached stream response
|
|
|
|
|
|
function createCachedStreamResponse(xml: string): Response {
|
2025-12-06 12:46:40 +09:00
|
|
|
|
const toolCallId = `cached-${Date.now()}`
|
|
|
|
|
|
|
|
|
|
|
|
const stream = createUIMessageStream({
|
|
|
|
|
|
execute: async ({ writer }) => {
|
|
|
|
|
|
writer.write({ type: "start" })
|
|
|
|
|
|
writer.write({
|
|
|
|
|
|
type: "tool-input-start",
|
|
|
|
|
|
toolCallId,
|
|
|
|
|
|
toolName: "display_diagram",
|
|
|
|
|
|
})
|
|
|
|
|
|
writer.write({
|
|
|
|
|
|
type: "tool-input-delta",
|
|
|
|
|
|
toolCallId,
|
|
|
|
|
|
inputTextDelta: xml,
|
|
|
|
|
|
})
|
|
|
|
|
|
writer.write({
|
|
|
|
|
|
type: "tool-input-available",
|
|
|
|
|
|
toolCallId,
|
|
|
|
|
|
toolName: "display_diagram",
|
|
|
|
|
|
input: { xml },
|
|
|
|
|
|
})
|
|
|
|
|
|
writer.write({ type: "finish" })
|
|
|
|
|
|
},
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return createUIMessageStreamResponse({ stream })
|
2025-12-01 14:07:50 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-04 11:24:26 +09:00
|
|
|
|
// Inner handler function
|
|
|
|
|
|
async function handleChatRequest(req: Request): Promise<Response> {
|
2025-12-06 12:46:40 +09:00
|
|
|
|
// Check for access code
|
|
|
|
|
|
const accessCodes =
|
|
|
|
|
|
process.env.ACCESS_CODE_LIST?.split(",")
|
|
|
|
|
|
.map((code) => code.trim())
|
|
|
|
|
|
.filter(Boolean) || []
|
|
|
|
|
|
if (accessCodes.length > 0) {
|
|
|
|
|
|
const accessCodeHeader = req.headers.get("x-access-code")
|
|
|
|
|
|
if (!accessCodeHeader || !accessCodes.includes(accessCodeHeader)) {
|
|
|
|
|
|
return Response.json(
|
|
|
|
|
|
{
|
|
|
|
|
|
error: "Invalid or missing access code. Please configure it in Settings.",
|
|
|
|
|
|
},
|
|
|
|
|
|
{ status: 401 },
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
2025-12-05 21:09:34 +08:00
|
|
|
|
}
|
2025-12-06 12:46:40 +09:00
|
|
|
|
|
2026-03-07 19:07:54 +09:00
|
|
|
|
const body = await req.json()
|
|
|
|
|
|
const { messages, xml, previousXml, sessionId } = body
|
|
|
|
|
|
const customSystemMessage =
|
|
|
|
|
|
typeof body.customSystemMessage === "string"
|
|
|
|
|
|
? body.customSystemMessage.slice(0, 5000)
|
|
|
|
|
|
: ""
|
2025-12-06 12:46:40 +09:00
|
|
|
|
|
2025-12-25 12:20:46 +09:00
|
|
|
|
// Get user ID for Langfuse tracking and quota
|
|
|
|
|
|
const userId = getUserIdFromRequest(req)
|
2025-12-06 12:46:40 +09:00
|
|
|
|
|
|
|
|
|
|
// Validate sessionId for Langfuse (must be string, max 200 chars)
|
|
|
|
|
|
const validSessionId =
|
|
|
|
|
|
sessionId && typeof sessionId === "string" && sessionId.length <= 200
|
|
|
|
|
|
? sessionId
|
|
|
|
|
|
: undefined
|
|
|
|
|
|
|
|
|
|
|
|
// Extract user input text for Langfuse trace
|
2025-12-23 13:43:28 +09:00
|
|
|
|
// Find the last USER message, not just the last message (which could be assistant in multi-step tool flows)
|
|
|
|
|
|
const lastUserMessage = [...messages]
|
|
|
|
|
|
.reverse()
|
|
|
|
|
|
.find((m: any) => m.role === "user")
|
2025-12-06 12:46:40 +09:00
|
|
|
|
const userInputText =
|
2025-12-23 13:43:28 +09:00
|
|
|
|
lastUserMessage?.parts?.find((p: any) => p.type === "text")?.text || ""
|
2025-12-06 12:46:40 +09:00
|
|
|
|
|
|
|
|
|
|
// Update Langfuse trace with input, session, and user
|
|
|
|
|
|
setTraceInput({
|
|
|
|
|
|
input: userInputText,
|
|
|
|
|
|
sessionId: validSessionId,
|
|
|
|
|
|
userId: userId,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-12-23 18:36:27 +09:00
|
|
|
|
// === SERVER-SIDE QUOTA CHECK START ===
|
|
|
|
|
|
// Quota is opt-in: only enabled when DYNAMODB_QUOTA_TABLE env var is set
|
|
|
|
|
|
const hasOwnApiKey = !!(
|
2026-01-20 20:27:31 +09:00
|
|
|
|
req.headers.get("x-ai-provider") &&
|
|
|
|
|
|
(req.headers.get("x-ai-api-key") ||
|
|
|
|
|
|
req.headers.get("x-aws-access-key-id") ||
|
|
|
|
|
|
req.headers.get("x-vertex-api-key"))
|
2025-12-23 18:36:27 +09:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// Skip quota check if: quota disabled, user has own API key, or is anonymous
|
|
|
|
|
|
if (isQuotaEnabled() && !hasOwnApiKey && userId !== "anonymous") {
|
|
|
|
|
|
const quotaCheck = await checkAndIncrementRequest(userId, {
|
|
|
|
|
|
requests: Number(process.env.DAILY_REQUEST_LIMIT) || 10,
|
|
|
|
|
|
tokens: Number(process.env.DAILY_TOKEN_LIMIT) || 200000,
|
|
|
|
|
|
tpm: Number(process.env.TPM_LIMIT) || 20000,
|
|
|
|
|
|
})
|
|
|
|
|
|
if (!quotaCheck.allowed) {
|
|
|
|
|
|
return Response.json(
|
|
|
|
|
|
{
|
|
|
|
|
|
error: quotaCheck.error,
|
|
|
|
|
|
type: quotaCheck.type,
|
|
|
|
|
|
used: quotaCheck.used,
|
|
|
|
|
|
limit: quotaCheck.limit,
|
|
|
|
|
|
},
|
|
|
|
|
|
{ status: 429 },
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// === SERVER-SIDE QUOTA CHECK END ===
|
|
|
|
|
|
|
2025-12-06 12:46:40 +09:00
|
|
|
|
// === FILE VALIDATION START ===
|
|
|
|
|
|
const fileValidation = validateFileParts(messages)
|
|
|
|
|
|
if (!fileValidation.valid) {
|
|
|
|
|
|
return Response.json({ error: fileValidation.error }, { status: 400 })
|
2025-12-01 14:07:50 +09:00
|
|
|
|
}
|
2025-12-06 12:46:40 +09:00
|
|
|
|
// === FILE VALIDATION END ===
|
|
|
|
|
|
|
|
|
|
|
|
// === CACHE CHECK START ===
|
|
|
|
|
|
const isFirstMessage = messages.length === 1
|
|
|
|
|
|
const isEmptyDiagram = !xml || xml.trim() === "" || isMinimalDiagram(xml)
|
2025-12-04 13:26:06 +09:00
|
|
|
|
|
2025-12-06 12:46:40 +09:00
|
|
|
|
if (isFirstMessage && isEmptyDiagram) {
|
|
|
|
|
|
const lastMessage = messages[0]
|
|
|
|
|
|
const textPart = lastMessage.parts?.find((p: any) => p.type === "text")
|
|
|
|
|
|
const filePart = lastMessage.parts?.find((p: any) => p.type === "file")
|
2025-12-04 13:26:06 +09:00
|
|
|
|
|
2025-12-06 12:46:40 +09:00
|
|
|
|
const cached = findCachedResponse(textPart?.text || "", !!filePart)
|
2025-12-04 13:26:06 +09:00
|
|
|
|
|
2025-12-06 12:46:40 +09:00
|
|
|
|
if (cached) {
|
|
|
|
|
|
return createCachedStreamResponse(cached.xml)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// === CACHE CHECK END ===
|
2025-12-04 13:26:06 +09:00
|
|
|
|
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
|
// Read client AI provider overrides from headers
|
2025-12-30 21:13:22 +08:00
|
|
|
|
const provider = req.headers.get("x-ai-provider")
|
|
|
|
|
|
let baseUrl = req.headers.get("x-ai-base-url")
|
2026-01-15 21:28:22 +05:30
|
|
|
|
const selectedModelId = req.headers.get("x-selected-model-id")
|
2025-12-30 21:13:22 +08:00
|
|
|
|
|
|
|
|
|
|
// For EdgeOne provider, construct full URL from request origin
|
|
|
|
|
|
// because createOpenAI needs absolute URL, not relative path
|
|
|
|
|
|
if (provider === "edgeone" && !baseUrl) {
|
|
|
|
|
|
const origin = req.headers.get("origin") || new URL(req.url).origin
|
|
|
|
|
|
baseUrl = `${origin}/api/edgeai`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Get cookie header for EdgeOne authentication (eo_token, eo_time)
|
|
|
|
|
|
const cookieHeader = req.headers.get("cookie")
|
|
|
|
|
|
|
2026-01-15 21:28:22 +05:30
|
|
|
|
// Check if this is a server model with custom env var names
|
|
|
|
|
|
let serverModelConfig: {
|
2026-02-02 15:54:35 +09:00
|
|
|
|
apiKeyEnv?: string | string[]
|
2026-01-15 21:28:22 +05:30
|
|
|
|
baseUrlEnv?: string
|
|
|
|
|
|
provider?: string
|
|
|
|
|
|
} = {}
|
|
|
|
|
|
if (selectedModelId?.startsWith("server:")) {
|
|
|
|
|
|
const serverModel = await findServerModelById(selectedModelId)
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
`[Server Model Lookup] ID: ${selectedModelId}, Found: ${!!serverModel}, Provider: ${serverModel?.provider}`,
|
|
|
|
|
|
)
|
|
|
|
|
|
if (serverModel) {
|
|
|
|
|
|
serverModelConfig = {
|
|
|
|
|
|
apiKeyEnv: serverModel.apiKeyEnv,
|
|
|
|
|
|
baseUrlEnv: serverModel.baseUrlEnv,
|
|
|
|
|
|
// Use actual provider from config (client header may have incorrect value due to ID format change)
|
|
|
|
|
|
provider: serverModel.provider,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
|
const clientOverrides = {
|
2026-01-15 21:28:22 +05:30
|
|
|
|
// Server model provider takes precedence over client header
|
|
|
|
|
|
provider: serverModelConfig.provider || provider,
|
2025-12-30 21:13:22 +08:00
|
|
|
|
baseUrl,
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
|
apiKey: req.headers.get("x-ai-api-key"),
|
|
|
|
|
|
modelId: req.headers.get("x-ai-model"),
|
feat: multi-provider model configuration with UI/UX improvements (#355)
* feat: add multi-provider model configuration
- Add model config dialog for managing multiple AI providers
- Support for OpenAI, Anthropic, Google, Azure, Bedrock, OpenRouter, DeepSeek, SiliconFlow, Ollama, and AI Gateway
- Add model selector dropdown in chat panel header
- Add API key validation endpoint
- Add custom model ID input with keyboard navigation
- Fix hover highlight in Command component
- Add suggested models for each provider including latest Claude 4.5 series
- Store configuration locally in browser
* feat: improve model config UI and move selector to chat input
- Move model selector from header to chat input (left of send button)
- Add per-model validation status (queued, running, valid, invalid)
- Filter model selector to only show verified models
- Add editable model IDs in config dialog
- Add custom model input field alongside suggested models dropdown
- Fix hover states on provider buttons and select triggers
- Update OpenAI suggested models with GPT-5 series
- Add alert-dialog component for delete confirmation
* refactor: revert shadcn component changes, apply hover fix at usage site
* feat: add AWS credentials support for Bedrock provider
- Add AWS Access Key ID, Secret Access Key, Region fields for Bedrock
- Show different credential fields based on provider type
- Update validation API to handle Bedrock with AWS credentials
- Add region selector with common AWS regions
* fix: reset Test button after validation completes
* fix: reset validation button to Test after success
* fix: complete bedrock support and UI/UX improvements
- Add bedrock to ALLOWED_CLIENT_PROVIDERS for client credentials
- Pass AWS credentials through full chain (headers → API → provider)
- Replace non-existent GPT-5 models with real ones (o1, o3-mini)
- Add accessibility: aria-labels, focus-visible rings, inline errors
- Add more AWS regions (Ohio, London, Paris, Mumbai, Seoul, São Paulo)
- Fix setTimeout cleanup with useRef on component unmount
- Fix TypeScript type consistency in getSelectedAIConfig fallback
* chore: remove unused code
- Remove unused setAccessCodeRequired state in chat-panel.tsx
- Remove unused getSelectedModel export in model-config.ts
* fix: UI/UX improvements for model configuration dialog
- Add gradient header styling with icon badge
- Change Configuration section icon from Key to Settings2
- Add duplicate model detection with warning banner and inline removal
- Filter out already-added models from suggestions dropdown
- Add type-to-confirm for deleting providers with 3+ models
- Enhance delete confirmation dialog with warning icon
- Improve model selector discoverability (show model name + chevron)
- Add truncation for long model names with title tooltip
- Remove AI provider settings from Settings dialog (now in Model Config)
- Extract ValidationButton into reusable component
* fix: prevent duplicate model IDs within same provider
- Block adding model if ID already exists in provider
- Block editing model ID to match existing model in provider
* fix: improve duplicate model ID notifications
- Add toast notification when trying to add duplicate model
- Allow free typing when editing model ID, validate on blur
- Show warning toast instead of blocking input
* fix: improve duplicate model validation UX in config dialog
- Add inline error display for duplicate model IDs
- Show red border on input when error exists
- Validate on blur with shake animation for edit errors
- Prevent saving empty model names
- Clear errors when user starts typing
- Simplify error styling (small red text, no heavy chips)
2025-12-22 22:36:36 +09:00
|
|
|
|
// AWS Bedrock credentials
|
|
|
|
|
|
awsAccessKeyId: req.headers.get("x-aws-access-key-id"),
|
|
|
|
|
|
awsSecretAccessKey: req.headers.get("x-aws-secret-access-key"),
|
|
|
|
|
|
awsRegion: req.headers.get("x-aws-region"),
|
|
|
|
|
|
awsSessionToken: req.headers.get("x-aws-session-token"),
|
2026-01-15 21:28:22 +05:30
|
|
|
|
// Server model custom env var names
|
|
|
|
|
|
...serverModelConfig,
|
2026-01-14 03:06:13 -05:00
|
|
|
|
// Vertex AI credentials (Express Mode)
|
|
|
|
|
|
vertexApiKey: req.headers.get("x-vertex-api-key"),
|
2025-12-30 21:13:22 +08:00
|
|
|
|
// Pass cookies for EdgeOne Pages authentication
|
|
|
|
|
|
...(provider === "edgeone" &&
|
|
|
|
|
|
cookieHeader && {
|
|
|
|
|
|
headers: { cookie: cookieHeader },
|
|
|
|
|
|
}),
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-14 19:38:40 +09:00
|
|
|
|
// Read minimal style preference from header
|
|
|
|
|
|
const minimalStyle = req.headers.get("x-minimal-style") === "true"
|
|
|
|
|
|
|
2026-01-15 21:28:22 +05:30
|
|
|
|
console.log(
|
|
|
|
|
|
`[Client Overrides] provider: ${clientOverrides.provider}, modelId: ${clientOverrides.modelId}`,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
feat: add bring-your-own-API-key support (#186)
- Add AI provider settings to config panel (provider, model, API key, base URL)
- Support 7 providers: OpenAI, Anthropic, Google, Azure, OpenRouter, DeepSeek, SiliconFlow
- Client API keys stored in localStorage, never stored on server
- Client settings override server env vars when provided
- Skip server credential validation when client provides API key
- Bypass usage limits (request/token/TPM) when using own API key
- Add /api/config endpoint for fetching usage limits
- Add privacy notices to settings dialog, about pages, and quota toast
- Add clear settings button to reset saved API keys
- Update README files (EN/CN/JA) with BYOK documentation
Co-authored-by: dayuan.jiang <jiangdy@amazon.co.jp>
2025-12-09 17:50:07 +09:00
|
|
|
|
// Get AI model with optional client overrides
|
feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
* feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
- Add minimax, glm, qwen, qiniu, kimi to ProviderName type
- Add provider configurations to PROVIDER_INFO with default base URLs
- Add suggested models for MiniMax in SUGGESTED_MODELS
- Add minimax/glm/qwen/qiniu/kimi cases to getAIModel using OpenAI-compatible SDK
- Update ALLOWED_CLIENT_PROVIDERS and error messages
- Add environment variable examples to env.example
Fixes: MiniMax API compatibility issue (invalid chat setting 2013)
* fix: Add missing providers to PROVIDER_ENV_VARS type
* fix: Handle null case in PROVIDER_ENV_VARS for new providers
* fix: Add minimax/glm/qwen/kimi/qiniu support to validate-model API
- Add getDefaultBaseUrl helper function
- Add validation cases for new providers in validate-model route
* fix: Add new providers to buildProviderOptions switch case
* fix: Merge multiple system messages into one for minimax/glm/qwen/kimi/qiniu
MiniMax API doesn't support multiple system messages.
This fix combines them into a single message for Chinese providers.
* fix: Handle null provider in system message check
* debug: Add logging for allMessages count
* fix: Use effective provider (including env var fallback) for isSingleSystemProvider check
* fix: apply biome formatting (line-wrapping)
* docs: add Chinese AI providers documentation (MiniMax, GLM, Qwen, Kimi, Qiniu)
- Add i18n translations for new providers in all language dictionaries
- Add provider configuration documentation in en/cn/ja docs
* fix: 改进 PR #722 的代码审查反馈
1. 删除重复的 getDefaultBaseUrl 函数,改用 model-config.ts 的 PROVIDER_INFO
2. validate-model 路由改用 AI SDK 的 createOpenAI + generateText
3. 修复 resolveBaseURL 回退逻辑,传入 PROVIDER_INFO 的 defaultBaseUrl
4. 删除无用的 .bak 备份文件
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 修正中国 AI provider 端点配置
- qiniu: api.qiniucdn.com → api.qnaigc.com
- qwen: dashscope.aliyun.com → dashscope.aliyuncs.com
- 更新 env.example 文档链接
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* feat: MiniMax 使用 Anthropic 兼容 API
- MiniMax 改用 createAnthropic (而非 createOpenAI)
- 支持 api.minimax.io/anthropic 和 api.minimaxi.com/anthropic
- 合并多个 system 消息为单个 (MiniMax/GLM/Qwen/Kimi/Qiniu)
- 更新默认模型为 MiniMax-M2.5 系列
- 支持 MINIMAX_BASE_URL 环境变量配置
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* docs: 更新 MiniMax 文档
- 添加 Anthropic 兼容 API 说明
- 更新默认模型为 MiniMax-M2.5
- 添加国际版/中国大陆版配置示例
- 更新 env.example 注释
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 完善 MiniMax 双端点支持及问题修复
- 支持 MiniMax Anthropic 兼容端点和 OpenAI 兼容端点自动切换
- 修正默认端点为 api.minimaxi.com (中国大陆可用)
- 修复端点路径缺少 /v1 的问题
- 添加前端 MiniMax logo 映射
- 移除调试日志
- 修正 env.example 默认配置
* chore: clean backup artifacts and align biome formatting
* fix: resolve effectiveProvider bug, deduplicate MiniMax URL logic, fix docs
- Fix critical bug: effectiveProvider was empty during auto-detection,
causing multi-system-message to be sent to MiniMax (which rejects it).
Now uses resolved provider from getAIModel instead of re-deriving it.
- Extract normalizeMiniMaxBaseURL() shared helper to eliminate duplication
between ai-providers.ts and validate-model/route.ts
- Add guard for undefined MiniMax baseURL to prevent hitting api.anthropic.com
- Fix docs: mark China mainland URL as default (matches code behavior)
- Restructure minimax/glm/qwen/kimi/qiniu validation to use shared pattern
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: document MiniMax dual API formats in docs and UI
- Add hint below Base URL input when MiniMax is selected, explaining
Anthropic-compatible (/anthropic) vs OpenAI-compatible (/v1) endpoints
- Update all 3 ai-providers docs (en/cn/ja) to list all 4 endpoint options
(China/International × Anthropic/OpenAI)
- Add i18n translations for the hint in all 4 locales
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: deduplicate PROVIDER_LOGO_MAP, remove unnecessary optional chaining
- Extract PROVIDER_LOGO_MAP to lib/types/model-config.ts (was duplicated
in model-config-dialog.tsx and model-selector.tsx)
- Remove unnecessary ?. on PROVIDER_INFO.minimax (it's a full Record)
---------
Co-authored-by: msga-oc <msga-oc@gitea.misakiga.top>
Co-authored-by: Shinyi <shinyi@openclaw.ai>
Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 17:53:47 +08:00
|
|
|
|
const {
|
|
|
|
|
|
model,
|
|
|
|
|
|
providerOptions,
|
|
|
|
|
|
headers,
|
|
|
|
|
|
modelId,
|
|
|
|
|
|
provider: resolvedProvider,
|
|
|
|
|
|
} = getAIModel(clientOverrides)
|
2025-12-04 13:26:06 +09:00
|
|
|
|
|
2025-12-09 15:53:59 +09:00
|
|
|
|
// Check if model supports prompt caching
|
|
|
|
|
|
const shouldCache = supportsPromptCaching(modelId)
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
`[Prompt Caching] ${shouldCache ? "ENABLED" : "DISABLED"} for model: ${modelId}`,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-12-06 12:46:40 +09:00
|
|
|
|
// Get the appropriate system prompt based on model (extended for Opus/Haiku 4.5)
|
2025-12-14 19:38:40 +09:00
|
|
|
|
const systemMessage = getSystemPrompt(modelId, minimalStyle)
|
2026-03-07 19:07:54 +09:00
|
|
|
|
const finalSystemMessage = customSystemMessage
|
|
|
|
|
|
? `${systemMessage}\n\n## Custom Instructions\n${customSystemMessage}`
|
|
|
|
|
|
: systemMessage
|
2025-12-04 13:26:06 +09:00
|
|
|
|
|
2025-12-23 13:43:28 +09:00
|
|
|
|
// Extract file parts (images) from the last user message
|
2025-12-06 12:46:40 +09:00
|
|
|
|
const fileParts =
|
2025-12-23 13:43:28 +09:00
|
|
|
|
lastUserMessage?.parts?.filter((part: any) => part.type === "file") ||
|
|
|
|
|
|
[]
|
2025-12-06 12:46:40 +09:00
|
|
|
|
|
2026-06-28 00:26:23 +09:00
|
|
|
|
// Note: we used to pre-emptively reject images for models we guessed were
|
|
|
|
|
|
// text-only (by name matching). That heuristic misfired on newer models
|
|
|
|
|
|
// (see issue #874), so we now let the request through and surface the real
|
|
|
|
|
|
// provider error if the model genuinely can't accept images.
|
2025-12-31 12:20:09 +09:00
|
|
|
|
|
2025-12-06 12:46:40 +09:00
|
|
|
|
// User input only - XML is now in a separate cached system message
|
|
|
|
|
|
const formattedUserInput = `User input:
|
2025-03-24 02:38:27 +00:00
|
|
|
|
"""md
|
2025-12-13 23:28:41 +09:00
|
|
|
|
${userInputText}
|
2025-12-06 12:46:40 +09:00
|
|
|
|
"""`
|
|
|
|
|
|
|
|
|
|
|
|
// Convert UIMessages to ModelMessages and add system message
|
2025-12-23 13:31:42 +09:00
|
|
|
|
const modelMessages = await convertToModelMessages(messages)
|
2025-12-06 12:46:40 +09:00
|
|
|
|
|
2025-12-15 21:28:31 +09:00
|
|
|
|
// DEBUG: Log incoming messages structure
|
|
|
|
|
|
console.log("[route.ts] Incoming messages count:", messages.length)
|
|
|
|
|
|
messages.forEach((msg: any, idx: number) => {
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
`[route.ts] Message ${idx} role:`,
|
|
|
|
|
|
msg.role,
|
|
|
|
|
|
"parts count:",
|
|
|
|
|
|
msg.parts?.length,
|
|
|
|
|
|
)
|
|
|
|
|
|
if (msg.parts) {
|
|
|
|
|
|
msg.parts.forEach((part: any, partIdx: number) => {
|
|
|
|
|
|
if (
|
|
|
|
|
|
part.type === "tool-invocation" ||
|
|
|
|
|
|
part.type === "tool-result"
|
|
|
|
|
|
) {
|
|
|
|
|
|
console.log(`[route.ts] Part ${partIdx}:`, {
|
|
|
|
|
|
type: part.type,
|
|
|
|
|
|
toolName: part.toolName,
|
|
|
|
|
|
hasInput: !!part.input,
|
|
|
|
|
|
inputType: typeof part.input,
|
|
|
|
|
|
inputKeys:
|
|
|
|
|
|
part.input && typeof part.input === "object"
|
|
|
|
|
|
? Object.keys(part.input)
|
|
|
|
|
|
: null,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-12-11 17:36:18 +09:00
|
|
|
|
// Replace historical tool call XML with placeholders to reduce tokens
|
|
|
|
|
|
// Disabled by default - some models (e.g. minimax) copy placeholders instead of generating XML
|
|
|
|
|
|
const enableHistoryReplace =
|
|
|
|
|
|
process.env.ENABLE_HISTORY_XML_REPLACE === "true"
|
|
|
|
|
|
const placeholderMessages = enableHistoryReplace
|
2025-12-13 23:28:41 +09:00
|
|
|
|
? replaceHistoricalToolInputs(modelMessages)
|
|
|
|
|
|
: modelMessages
|
2025-12-10 18:04:37 +09:00
|
|
|
|
|
2025-12-06 12:46:40 +09:00
|
|
|
|
// Filter out messages with empty content arrays (Bedrock API rejects these)
|
|
|
|
|
|
// This is a safety measure - ideally convertToModelMessages should handle all cases
|
2025-12-10 18:04:37 +09:00
|
|
|
|
let enhancedMessages = placeholderMessages.filter(
|
2025-12-06 12:46:40 +09:00
|
|
|
|
(msg: any) =>
|
|
|
|
|
|
msg.content && Array.isArray(msg.content) && msg.content.length > 0,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-12-15 21:28:31 +09:00
|
|
|
|
// Filter out tool-calls with invalid inputs (from failed repair or interrupted streaming)
|
|
|
|
|
|
// Bedrock API rejects messages where toolUse.input is not a valid JSON object
|
|
|
|
|
|
enhancedMessages = enhancedMessages
|
|
|
|
|
|
.map((msg: any) => {
|
|
|
|
|
|
if (msg.role !== "assistant" || !Array.isArray(msg.content)) {
|
|
|
|
|
|
return msg
|
|
|
|
|
|
}
|
|
|
|
|
|
const filteredContent = msg.content.filter((part: any) => {
|
|
|
|
|
|
if (part.type === "tool-call") {
|
|
|
|
|
|
// Check if input is a valid object (not null, undefined, or empty)
|
|
|
|
|
|
if (
|
|
|
|
|
|
!part.input ||
|
|
|
|
|
|
typeof part.input !== "object" ||
|
|
|
|
|
|
Object.keys(part.input).length === 0
|
|
|
|
|
|
) {
|
|
|
|
|
|
console.warn(
|
|
|
|
|
|
`[route.ts] Filtering out tool-call with invalid input:`,
|
|
|
|
|
|
{ toolName: part.toolName, input: part.input },
|
|
|
|
|
|
)
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return true
|
|
|
|
|
|
})
|
|
|
|
|
|
return { ...msg, content: filteredContent }
|
|
|
|
|
|
})
|
|
|
|
|
|
.filter((msg: any) => msg.content && msg.content.length > 0)
|
|
|
|
|
|
|
|
|
|
|
|
// DEBUG: Log modelMessages structure (what's being sent to AI)
|
|
|
|
|
|
console.log("[route.ts] Model messages count:", enhancedMessages.length)
|
|
|
|
|
|
enhancedMessages.forEach((msg: any, idx: number) => {
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
`[route.ts] ModelMsg ${idx} role:`,
|
|
|
|
|
|
msg.role,
|
|
|
|
|
|
"content count:",
|
|
|
|
|
|
msg.content?.length,
|
|
|
|
|
|
)
|
|
|
|
|
|
if (msg.content) {
|
|
|
|
|
|
msg.content.forEach((part: any, partIdx: number) => {
|
|
|
|
|
|
if (part.type === "tool-call" || part.type === "tool-result") {
|
|
|
|
|
|
console.log(`[route.ts] Content ${partIdx}:`, {
|
|
|
|
|
|
type: part.type,
|
|
|
|
|
|
toolName: part.toolName,
|
|
|
|
|
|
hasInput: !!part.input,
|
|
|
|
|
|
inputType: typeof part.input,
|
|
|
|
|
|
inputValue:
|
|
|
|
|
|
part.input === undefined
|
|
|
|
|
|
? "undefined"
|
|
|
|
|
|
: part.input === null
|
|
|
|
|
|
? "null"
|
|
|
|
|
|
: "object",
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-12-06 12:46:40 +09:00
|
|
|
|
// Update the last message with user input only (XML moved to separate cached system message)
|
|
|
|
|
|
if (enhancedMessages.length >= 1) {
|
|
|
|
|
|
const lastModelMessage = enhancedMessages[enhancedMessages.length - 1]
|
|
|
|
|
|
if (lastModelMessage.role === "user") {
|
|
|
|
|
|
// Build content array with user input text and file parts
|
|
|
|
|
|
const contentParts: any[] = [
|
|
|
|
|
|
{ type: "text", text: formattedUserInput },
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
// Add image parts back
|
|
|
|
|
|
for (const filePart of fileParts) {
|
|
|
|
|
|
contentParts.push({
|
|
|
|
|
|
type: "image",
|
|
|
|
|
|
image: filePart.url,
|
|
|
|
|
|
mimeType: filePart.mediaType,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
enhancedMessages = [
|
|
|
|
|
|
...enhancedMessages.slice(0, -1),
|
|
|
|
|
|
{ ...lastModelMessage, content: contentParts },
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
2025-08-31 12:54:14 +09:00
|
|
|
|
}
|
2025-12-06 12:46:40 +09:00
|
|
|
|
|
|
|
|
|
|
// Add cache point to the last assistant message in conversation history
|
|
|
|
|
|
// This caches the entire conversation prefix for subsequent requests
|
|
|
|
|
|
// Strategy: system (cached) + history with last assistant (cached) + new user message
|
2025-12-09 15:53:59 +09:00
|
|
|
|
if (shouldCache && enhancedMessages.length >= 2) {
|
2025-12-06 12:46:40 +09:00
|
|
|
|
// Find the last assistant message (should be second-to-last, before current user message)
|
|
|
|
|
|
for (let i = enhancedMessages.length - 2; i >= 0; i--) {
|
|
|
|
|
|
if (enhancedMessages[i].role === "assistant") {
|
|
|
|
|
|
enhancedMessages[i] = {
|
|
|
|
|
|
...enhancedMessages[i],
|
|
|
|
|
|
providerOptions: {
|
|
|
|
|
|
bedrock: { cachePoint: { type: "default" } },
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
break // Only cache the last assistant message
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-01 10:43:33 +09:00
|
|
|
|
}
|
2025-12-06 12:46:40 +09:00
|
|
|
|
|
|
|
|
|
|
// System messages with multiple cache breakpoints for optimal caching:
|
2026-03-07 19:07:54 +09:00
|
|
|
|
// - Breakpoint 1: System instructions + custom instructions - changes when user updates custom system message
|
2025-12-06 12:46:40 +09:00
|
|
|
|
// - Breakpoint 2: Current XML context - changes per diagram, but constant within a conversation turn
|
feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
* feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
- Add minimax, glm, qwen, qiniu, kimi to ProviderName type
- Add provider configurations to PROVIDER_INFO with default base URLs
- Add suggested models for MiniMax in SUGGESTED_MODELS
- Add minimax/glm/qwen/qiniu/kimi cases to getAIModel using OpenAI-compatible SDK
- Update ALLOWED_CLIENT_PROVIDERS and error messages
- Add environment variable examples to env.example
Fixes: MiniMax API compatibility issue (invalid chat setting 2013)
* fix: Add missing providers to PROVIDER_ENV_VARS type
* fix: Handle null case in PROVIDER_ENV_VARS for new providers
* fix: Add minimax/glm/qwen/kimi/qiniu support to validate-model API
- Add getDefaultBaseUrl helper function
- Add validation cases for new providers in validate-model route
* fix: Add new providers to buildProviderOptions switch case
* fix: Merge multiple system messages into one for minimax/glm/qwen/kimi/qiniu
MiniMax API doesn't support multiple system messages.
This fix combines them into a single message for Chinese providers.
* fix: Handle null provider in system message check
* debug: Add logging for allMessages count
* fix: Use effective provider (including env var fallback) for isSingleSystemProvider check
* fix: apply biome formatting (line-wrapping)
* docs: add Chinese AI providers documentation (MiniMax, GLM, Qwen, Kimi, Qiniu)
- Add i18n translations for new providers in all language dictionaries
- Add provider configuration documentation in en/cn/ja docs
* fix: 改进 PR #722 的代码审查反馈
1. 删除重复的 getDefaultBaseUrl 函数,改用 model-config.ts 的 PROVIDER_INFO
2. validate-model 路由改用 AI SDK 的 createOpenAI + generateText
3. 修复 resolveBaseURL 回退逻辑,传入 PROVIDER_INFO 的 defaultBaseUrl
4. 删除无用的 .bak 备份文件
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 修正中国 AI provider 端点配置
- qiniu: api.qiniucdn.com → api.qnaigc.com
- qwen: dashscope.aliyun.com → dashscope.aliyuncs.com
- 更新 env.example 文档链接
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* feat: MiniMax 使用 Anthropic 兼容 API
- MiniMax 改用 createAnthropic (而非 createOpenAI)
- 支持 api.minimax.io/anthropic 和 api.minimaxi.com/anthropic
- 合并多个 system 消息为单个 (MiniMax/GLM/Qwen/Kimi/Qiniu)
- 更新默认模型为 MiniMax-M2.5 系列
- 支持 MINIMAX_BASE_URL 环境变量配置
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* docs: 更新 MiniMax 文档
- 添加 Anthropic 兼容 API 说明
- 更新默认模型为 MiniMax-M2.5
- 添加国际版/中国大陆版配置示例
- 更新 env.example 注释
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 完善 MiniMax 双端点支持及问题修复
- 支持 MiniMax Anthropic 兼容端点和 OpenAI 兼容端点自动切换
- 修正默认端点为 api.minimaxi.com (中国大陆可用)
- 修复端点路径缺少 /v1 的问题
- 添加前端 MiniMax logo 映射
- 移除调试日志
- 修正 env.example 默认配置
* chore: clean backup artifacts and align biome formatting
* fix: resolve effectiveProvider bug, deduplicate MiniMax URL logic, fix docs
- Fix critical bug: effectiveProvider was empty during auto-detection,
causing multi-system-message to be sent to MiniMax (which rejects it).
Now uses resolved provider from getAIModel instead of re-deriving it.
- Extract normalizeMiniMaxBaseURL() shared helper to eliminate duplication
between ai-providers.ts and validate-model/route.ts
- Add guard for undefined MiniMax baseURL to prevent hitting api.anthropic.com
- Fix docs: mark China mainland URL as default (matches code behavior)
- Restructure minimax/glm/qwen/kimi/qiniu validation to use shared pattern
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: document MiniMax dual API formats in docs and UI
- Add hint below Base URL input when MiniMax is selected, explaining
Anthropic-compatible (/anthropic) vs OpenAI-compatible (/v1) endpoints
- Update all 3 ai-providers docs (en/cn/ja) to list all 4 endpoint options
(China/International × Anthropic/OpenAI)
- Add i18n translations for the hint in all 4 locales
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: deduplicate PROVIDER_LOGO_MAP, remove unnecessary optional chaining
- Extract PROVIDER_LOGO_MAP to lib/types/model-config.ts (was duplicated
in model-config-dialog.tsx and model-selector.tsx)
- Remove unnecessary ?. on PROVIDER_INFO.minimax (it's a full Record)
---------
Co-authored-by: msga-oc <msga-oc@gitea.misakiga.top>
Co-authored-by: Shinyi <shinyi@openclaw.ai>
Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 17:53:47 +08:00
|
|
|
|
// Some providers (e.g. MiniMax) don't support multiple system messages
|
|
|
|
|
|
// Merge them into a single system message for compatibility
|
2026-04-02 22:27:29 -05:00
|
|
|
|
// Also merge for OpenAI-compatible providers with custom base URLs (e.g. vLLM, LMStudio)
|
|
|
|
|
|
// because open-source model chat templates (Qwen, Llama, etc.) typically reject multiple system messages
|
|
|
|
|
|
const isCustomOpenAIEndpoint =
|
|
|
|
|
|
resolvedProvider === "openai" &&
|
|
|
|
|
|
!!(
|
|
|
|
|
|
baseUrl ||
|
|
|
|
|
|
process.env.OPENAI_BASE_URL ||
|
|
|
|
|
|
(serverModelConfig.baseUrlEnv &&
|
|
|
|
|
|
process.env[serverModelConfig.baseUrlEnv])
|
|
|
|
|
|
)
|
|
|
|
|
|
const isSingleSystemProvider =
|
|
|
|
|
|
SINGLE_SYSTEM_PROVIDERS.has(resolvedProvider) || isCustomOpenAIEndpoint
|
feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
* feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
- Add minimax, glm, qwen, qiniu, kimi to ProviderName type
- Add provider configurations to PROVIDER_INFO with default base URLs
- Add suggested models for MiniMax in SUGGESTED_MODELS
- Add minimax/glm/qwen/qiniu/kimi cases to getAIModel using OpenAI-compatible SDK
- Update ALLOWED_CLIENT_PROVIDERS and error messages
- Add environment variable examples to env.example
Fixes: MiniMax API compatibility issue (invalid chat setting 2013)
* fix: Add missing providers to PROVIDER_ENV_VARS type
* fix: Handle null case in PROVIDER_ENV_VARS for new providers
* fix: Add minimax/glm/qwen/kimi/qiniu support to validate-model API
- Add getDefaultBaseUrl helper function
- Add validation cases for new providers in validate-model route
* fix: Add new providers to buildProviderOptions switch case
* fix: Merge multiple system messages into one for minimax/glm/qwen/kimi/qiniu
MiniMax API doesn't support multiple system messages.
This fix combines them into a single message for Chinese providers.
* fix: Handle null provider in system message check
* debug: Add logging for allMessages count
* fix: Use effective provider (including env var fallback) for isSingleSystemProvider check
* fix: apply biome formatting (line-wrapping)
* docs: add Chinese AI providers documentation (MiniMax, GLM, Qwen, Kimi, Qiniu)
- Add i18n translations for new providers in all language dictionaries
- Add provider configuration documentation in en/cn/ja docs
* fix: 改进 PR #722 的代码审查反馈
1. 删除重复的 getDefaultBaseUrl 函数,改用 model-config.ts 的 PROVIDER_INFO
2. validate-model 路由改用 AI SDK 的 createOpenAI + generateText
3. 修复 resolveBaseURL 回退逻辑,传入 PROVIDER_INFO 的 defaultBaseUrl
4. 删除无用的 .bak 备份文件
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 修正中国 AI provider 端点配置
- qiniu: api.qiniucdn.com → api.qnaigc.com
- qwen: dashscope.aliyun.com → dashscope.aliyuncs.com
- 更新 env.example 文档链接
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* feat: MiniMax 使用 Anthropic 兼容 API
- MiniMax 改用 createAnthropic (而非 createOpenAI)
- 支持 api.minimax.io/anthropic 和 api.minimaxi.com/anthropic
- 合并多个 system 消息为单个 (MiniMax/GLM/Qwen/Kimi/Qiniu)
- 更新默认模型为 MiniMax-M2.5 系列
- 支持 MINIMAX_BASE_URL 环境变量配置
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* docs: 更新 MiniMax 文档
- 添加 Anthropic 兼容 API 说明
- 更新默认模型为 MiniMax-M2.5
- 添加国际版/中国大陆版配置示例
- 更新 env.example 注释
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 完善 MiniMax 双端点支持及问题修复
- 支持 MiniMax Anthropic 兼容端点和 OpenAI 兼容端点自动切换
- 修正默认端点为 api.minimaxi.com (中国大陆可用)
- 修复端点路径缺少 /v1 的问题
- 添加前端 MiniMax logo 映射
- 移除调试日志
- 修正 env.example 默认配置
* chore: clean backup artifacts and align biome formatting
* fix: resolve effectiveProvider bug, deduplicate MiniMax URL logic, fix docs
- Fix critical bug: effectiveProvider was empty during auto-detection,
causing multi-system-message to be sent to MiniMax (which rejects it).
Now uses resolved provider from getAIModel instead of re-deriving it.
- Extract normalizeMiniMaxBaseURL() shared helper to eliminate duplication
between ai-providers.ts and validate-model/route.ts
- Add guard for undefined MiniMax baseURL to prevent hitting api.anthropic.com
- Fix docs: mark China mainland URL as default (matches code behavior)
- Restructure minimax/glm/qwen/kimi/qiniu validation to use shared pattern
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: document MiniMax dual API formats in docs and UI
- Add hint below Base URL input when MiniMax is selected, explaining
Anthropic-compatible (/anthropic) vs OpenAI-compatible (/v1) endpoints
- Update all 3 ai-providers docs (en/cn/ja) to list all 4 endpoint options
(China/International × Anthropic/OpenAI)
- Add i18n translations for the hint in all 4 locales
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: deduplicate PROVIDER_LOGO_MAP, remove unnecessary optional chaining
- Extract PROVIDER_LOGO_MAP to lib/types/model-config.ts (was duplicated
in model-config-dialog.tsx and model-selector.tsx)
- Remove unnecessary ?. on PROVIDER_INFO.minimax (it's a full Record)
---------
Co-authored-by: msga-oc <msga-oc@gitea.misakiga.top>
Co-authored-by: Shinyi <shinyi@openclaw.ai>
Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 17:53:47 +08:00
|
|
|
|
|
|
|
|
|
|
const xmlContext = `${
|
|
|
|
|
|
previousXml
|
|
|
|
|
|
? `Previous diagram XML (before user's last message):
|
|
|
|
|
|
"""xml
|
|
|
|
|
|
${previousXml}
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
`
|
|
|
|
|
|
: ""
|
|
|
|
|
|
}Current diagram XML (AUTHORITATIVE - the source of truth):
|
|
|
|
|
|
"""xml
|
|
|
|
|
|
${xml || ""}
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
IMPORTANT: The "Current diagram XML" is the SINGLE SOURCE OF TRUTH for what's on the canvas right now. The user can manually add, delete, or modify shapes directly in draw.io. Always count and describe elements based on the CURRENT XML, not on what you previously generated. If both previous and current XML are shown, compare them to understand what the user changed. When using edit_diagram, COPY search patterns exactly from the CURRENT XML - attribute order matters!`
|
|
|
|
|
|
|
|
|
|
|
|
const systemMessages = isSingleSystemProvider
|
|
|
|
|
|
? [
|
|
|
|
|
|
{
|
|
|
|
|
|
role: "system" as const,
|
2026-03-07 19:07:54 +09:00
|
|
|
|
content: `${finalSystemMessage}\n\n${xmlContext}`,
|
feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
* feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
- Add minimax, glm, qwen, qiniu, kimi to ProviderName type
- Add provider configurations to PROVIDER_INFO with default base URLs
- Add suggested models for MiniMax in SUGGESTED_MODELS
- Add minimax/glm/qwen/qiniu/kimi cases to getAIModel using OpenAI-compatible SDK
- Update ALLOWED_CLIENT_PROVIDERS and error messages
- Add environment variable examples to env.example
Fixes: MiniMax API compatibility issue (invalid chat setting 2013)
* fix: Add missing providers to PROVIDER_ENV_VARS type
* fix: Handle null case in PROVIDER_ENV_VARS for new providers
* fix: Add minimax/glm/qwen/kimi/qiniu support to validate-model API
- Add getDefaultBaseUrl helper function
- Add validation cases for new providers in validate-model route
* fix: Add new providers to buildProviderOptions switch case
* fix: Merge multiple system messages into one for minimax/glm/qwen/kimi/qiniu
MiniMax API doesn't support multiple system messages.
This fix combines them into a single message for Chinese providers.
* fix: Handle null provider in system message check
* debug: Add logging for allMessages count
* fix: Use effective provider (including env var fallback) for isSingleSystemProvider check
* fix: apply biome formatting (line-wrapping)
* docs: add Chinese AI providers documentation (MiniMax, GLM, Qwen, Kimi, Qiniu)
- Add i18n translations for new providers in all language dictionaries
- Add provider configuration documentation in en/cn/ja docs
* fix: 改进 PR #722 的代码审查反馈
1. 删除重复的 getDefaultBaseUrl 函数,改用 model-config.ts 的 PROVIDER_INFO
2. validate-model 路由改用 AI SDK 的 createOpenAI + generateText
3. 修复 resolveBaseURL 回退逻辑,传入 PROVIDER_INFO 的 defaultBaseUrl
4. 删除无用的 .bak 备份文件
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 修正中国 AI provider 端点配置
- qiniu: api.qiniucdn.com → api.qnaigc.com
- qwen: dashscope.aliyun.com → dashscope.aliyuncs.com
- 更新 env.example 文档链接
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* feat: MiniMax 使用 Anthropic 兼容 API
- MiniMax 改用 createAnthropic (而非 createOpenAI)
- 支持 api.minimax.io/anthropic 和 api.minimaxi.com/anthropic
- 合并多个 system 消息为单个 (MiniMax/GLM/Qwen/Kimi/Qiniu)
- 更新默认模型为 MiniMax-M2.5 系列
- 支持 MINIMAX_BASE_URL 环境变量配置
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* docs: 更新 MiniMax 文档
- 添加 Anthropic 兼容 API 说明
- 更新默认模型为 MiniMax-M2.5
- 添加国际版/中国大陆版配置示例
- 更新 env.example 注释
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 完善 MiniMax 双端点支持及问题修复
- 支持 MiniMax Anthropic 兼容端点和 OpenAI 兼容端点自动切换
- 修正默认端点为 api.minimaxi.com (中国大陆可用)
- 修复端点路径缺少 /v1 的问题
- 添加前端 MiniMax logo 映射
- 移除调试日志
- 修正 env.example 默认配置
* chore: clean backup artifacts and align biome formatting
* fix: resolve effectiveProvider bug, deduplicate MiniMax URL logic, fix docs
- Fix critical bug: effectiveProvider was empty during auto-detection,
causing multi-system-message to be sent to MiniMax (which rejects it).
Now uses resolved provider from getAIModel instead of re-deriving it.
- Extract normalizeMiniMaxBaseURL() shared helper to eliminate duplication
between ai-providers.ts and validate-model/route.ts
- Add guard for undefined MiniMax baseURL to prevent hitting api.anthropic.com
- Fix docs: mark China mainland URL as default (matches code behavior)
- Restructure minimax/glm/qwen/kimi/qiniu validation to use shared pattern
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: document MiniMax dual API formats in docs and UI
- Add hint below Base URL input when MiniMax is selected, explaining
Anthropic-compatible (/anthropic) vs OpenAI-compatible (/v1) endpoints
- Update all 3 ai-providers docs (en/cn/ja) to list all 4 endpoint options
(China/International × Anthropic/OpenAI)
- Add i18n translations for the hint in all 4 locales
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: deduplicate PROVIDER_LOGO_MAP, remove unnecessary optional chaining
- Extract PROVIDER_LOGO_MAP to lib/types/model-config.ts (was duplicated
in model-config-dialog.tsx and model-selector.tsx)
- Remove unnecessary ?. on PROVIDER_INFO.minimax (it's a full Record)
---------
Co-authored-by: msga-oc <msga-oc@gitea.misakiga.top>
Co-authored-by: Shinyi <shinyi@openclaw.ai>
Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 17:53:47 +08:00
|
|
|
|
},
|
|
|
|
|
|
]
|
|
|
|
|
|
: [
|
2026-03-07 19:07:54 +09:00
|
|
|
|
// Cache breakpoint 1: Instructions (+ optional custom instructions)
|
feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
* feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
- Add minimax, glm, qwen, qiniu, kimi to ProviderName type
- Add provider configurations to PROVIDER_INFO with default base URLs
- Add suggested models for MiniMax in SUGGESTED_MODELS
- Add minimax/glm/qwen/qiniu/kimi cases to getAIModel using OpenAI-compatible SDK
- Update ALLOWED_CLIENT_PROVIDERS and error messages
- Add environment variable examples to env.example
Fixes: MiniMax API compatibility issue (invalid chat setting 2013)
* fix: Add missing providers to PROVIDER_ENV_VARS type
* fix: Handle null case in PROVIDER_ENV_VARS for new providers
* fix: Add minimax/glm/qwen/kimi/qiniu support to validate-model API
- Add getDefaultBaseUrl helper function
- Add validation cases for new providers in validate-model route
* fix: Add new providers to buildProviderOptions switch case
* fix: Merge multiple system messages into one for minimax/glm/qwen/kimi/qiniu
MiniMax API doesn't support multiple system messages.
This fix combines them into a single message for Chinese providers.
* fix: Handle null provider in system message check
* debug: Add logging for allMessages count
* fix: Use effective provider (including env var fallback) for isSingleSystemProvider check
* fix: apply biome formatting (line-wrapping)
* docs: add Chinese AI providers documentation (MiniMax, GLM, Qwen, Kimi, Qiniu)
- Add i18n translations for new providers in all language dictionaries
- Add provider configuration documentation in en/cn/ja docs
* fix: 改进 PR #722 的代码审查反馈
1. 删除重复的 getDefaultBaseUrl 函数,改用 model-config.ts 的 PROVIDER_INFO
2. validate-model 路由改用 AI SDK 的 createOpenAI + generateText
3. 修复 resolveBaseURL 回退逻辑,传入 PROVIDER_INFO 的 defaultBaseUrl
4. 删除无用的 .bak 备份文件
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 修正中国 AI provider 端点配置
- qiniu: api.qiniucdn.com → api.qnaigc.com
- qwen: dashscope.aliyun.com → dashscope.aliyuncs.com
- 更新 env.example 文档链接
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* feat: MiniMax 使用 Anthropic 兼容 API
- MiniMax 改用 createAnthropic (而非 createOpenAI)
- 支持 api.minimax.io/anthropic 和 api.minimaxi.com/anthropic
- 合并多个 system 消息为单个 (MiniMax/GLM/Qwen/Kimi/Qiniu)
- 更新默认模型为 MiniMax-M2.5 系列
- 支持 MINIMAX_BASE_URL 环境变量配置
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* docs: 更新 MiniMax 文档
- 添加 Anthropic 兼容 API 说明
- 更新默认模型为 MiniMax-M2.5
- 添加国际版/中国大陆版配置示例
- 更新 env.example 注释
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 完善 MiniMax 双端点支持及问题修复
- 支持 MiniMax Anthropic 兼容端点和 OpenAI 兼容端点自动切换
- 修正默认端点为 api.minimaxi.com (中国大陆可用)
- 修复端点路径缺少 /v1 的问题
- 添加前端 MiniMax logo 映射
- 移除调试日志
- 修正 env.example 默认配置
* chore: clean backup artifacts and align biome formatting
* fix: resolve effectiveProvider bug, deduplicate MiniMax URL logic, fix docs
- Fix critical bug: effectiveProvider was empty during auto-detection,
causing multi-system-message to be sent to MiniMax (which rejects it).
Now uses resolved provider from getAIModel instead of re-deriving it.
- Extract normalizeMiniMaxBaseURL() shared helper to eliminate duplication
between ai-providers.ts and validate-model/route.ts
- Add guard for undefined MiniMax baseURL to prevent hitting api.anthropic.com
- Fix docs: mark China mainland URL as default (matches code behavior)
- Restructure minimax/glm/qwen/kimi/qiniu validation to use shared pattern
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: document MiniMax dual API formats in docs and UI
- Add hint below Base URL input when MiniMax is selected, explaining
Anthropic-compatible (/anthropic) vs OpenAI-compatible (/v1) endpoints
- Update all 3 ai-providers docs (en/cn/ja) to list all 4 endpoint options
(China/International × Anthropic/OpenAI)
- Add i18n translations for the hint in all 4 locales
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: deduplicate PROVIDER_LOGO_MAP, remove unnecessary optional chaining
- Extract PROVIDER_LOGO_MAP to lib/types/model-config.ts (was duplicated
in model-config-dialog.tsx and model-selector.tsx)
- Remove unnecessary ?. on PROVIDER_INFO.minimax (it's a full Record)
---------
Co-authored-by: msga-oc <msga-oc@gitea.misakiga.top>
Co-authored-by: Shinyi <shinyi@openclaw.ai>
Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 17:53:47 +08:00
|
|
|
|
{
|
|
|
|
|
|
role: "system" as const,
|
2026-03-07 19:07:54 +09:00
|
|
|
|
content: finalSystemMessage,
|
feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
* feat: Add support for Chinese AI providers (GLM, Qwen, Kimi, MiniMax, Qiniu)
- Add minimax, glm, qwen, qiniu, kimi to ProviderName type
- Add provider configurations to PROVIDER_INFO with default base URLs
- Add suggested models for MiniMax in SUGGESTED_MODELS
- Add minimax/glm/qwen/qiniu/kimi cases to getAIModel using OpenAI-compatible SDK
- Update ALLOWED_CLIENT_PROVIDERS and error messages
- Add environment variable examples to env.example
Fixes: MiniMax API compatibility issue (invalid chat setting 2013)
* fix: Add missing providers to PROVIDER_ENV_VARS type
* fix: Handle null case in PROVIDER_ENV_VARS for new providers
* fix: Add minimax/glm/qwen/kimi/qiniu support to validate-model API
- Add getDefaultBaseUrl helper function
- Add validation cases for new providers in validate-model route
* fix: Add new providers to buildProviderOptions switch case
* fix: Merge multiple system messages into one for minimax/glm/qwen/kimi/qiniu
MiniMax API doesn't support multiple system messages.
This fix combines them into a single message for Chinese providers.
* fix: Handle null provider in system message check
* debug: Add logging for allMessages count
* fix: Use effective provider (including env var fallback) for isSingleSystemProvider check
* fix: apply biome formatting (line-wrapping)
* docs: add Chinese AI providers documentation (MiniMax, GLM, Qwen, Kimi, Qiniu)
- Add i18n translations for new providers in all language dictionaries
- Add provider configuration documentation in en/cn/ja docs
* fix: 改进 PR #722 的代码审查反馈
1. 删除重复的 getDefaultBaseUrl 函数,改用 model-config.ts 的 PROVIDER_INFO
2. validate-model 路由改用 AI SDK 的 createOpenAI + generateText
3. 修复 resolveBaseURL 回退逻辑,传入 PROVIDER_INFO 的 defaultBaseUrl
4. 删除无用的 .bak 备份文件
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 修正中国 AI provider 端点配置
- qiniu: api.qiniucdn.com → api.qnaigc.com
- qwen: dashscope.aliyun.com → dashscope.aliyuncs.com
- 更新 env.example 文档链接
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* feat: MiniMax 使用 Anthropic 兼容 API
- MiniMax 改用 createAnthropic (而非 createOpenAI)
- 支持 api.minimax.io/anthropic 和 api.minimaxi.com/anthropic
- 合并多个 system 消息为单个 (MiniMax/GLM/Qwen/Kimi/Qiniu)
- 更新默认模型为 MiniMax-M2.5 系列
- 支持 MINIMAX_BASE_URL 环境变量配置
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* docs: 更新 MiniMax 文档
- 添加 Anthropic 兼容 API 说明
- 更新默认模型为 MiniMax-M2.5
- 添加国际版/中国大陆版配置示例
- 更新 env.example 注释
Co-authored-by: Shinyi <shinyi@openclaw.ai>
* fix: 完善 MiniMax 双端点支持及问题修复
- 支持 MiniMax Anthropic 兼容端点和 OpenAI 兼容端点自动切换
- 修正默认端点为 api.minimaxi.com (中国大陆可用)
- 修复端点路径缺少 /v1 的问题
- 添加前端 MiniMax logo 映射
- 移除调试日志
- 修正 env.example 默认配置
* chore: clean backup artifacts and align biome formatting
* fix: resolve effectiveProvider bug, deduplicate MiniMax URL logic, fix docs
- Fix critical bug: effectiveProvider was empty during auto-detection,
causing multi-system-message to be sent to MiniMax (which rejects it).
Now uses resolved provider from getAIModel instead of re-deriving it.
- Extract normalizeMiniMaxBaseURL() shared helper to eliminate duplication
between ai-providers.ts and validate-model/route.ts
- Add guard for undefined MiniMax baseURL to prevent hitting api.anthropic.com
- Fix docs: mark China mainland URL as default (matches code behavior)
- Restructure minimax/glm/qwen/kimi/qiniu validation to use shared pattern
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: document MiniMax dual API formats in docs and UI
- Add hint below Base URL input when MiniMax is selected, explaining
Anthropic-compatible (/anthropic) vs OpenAI-compatible (/v1) endpoints
- Update all 3 ai-providers docs (en/cn/ja) to list all 4 endpoint options
(China/International × Anthropic/OpenAI)
- Add i18n translations for the hint in all 4 locales
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: deduplicate PROVIDER_LOGO_MAP, remove unnecessary optional chaining
- Extract PROVIDER_LOGO_MAP to lib/types/model-config.ts (was duplicated
in model-config-dialog.tsx and model-selector.tsx)
- Remove unnecessary ?. on PROVIDER_INFO.minimax (it's a full Record)
---------
Co-authored-by: msga-oc <msga-oc@gitea.misakiga.top>
Co-authored-by: Shinyi <shinyi@openclaw.ai>
Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 17:53:47 +08:00
|
|
|
|
...(shouldCache && {
|
|
|
|
|
|
providerOptions: {
|
|
|
|
|
|
bedrock: { cachePoint: { type: "default" } },
|
|
|
|
|
|
},
|
|
|
|
|
|
}),
|
|
|
|
|
|
},
|
|
|
|
|
|
// Cache breakpoint 2: Previous and Current diagram XML context
|
|
|
|
|
|
{
|
|
|
|
|
|
role: "system" as const,
|
|
|
|
|
|
content: xmlContext,
|
|
|
|
|
|
...(shouldCache && {
|
|
|
|
|
|
providerOptions: {
|
|
|
|
|
|
bedrock: { cachePoint: { type: "default" } },
|
|
|
|
|
|
},
|
|
|
|
|
|
}),
|
|
|
|
|
|
},
|
|
|
|
|
|
]
|
2025-12-06 12:46:40 +09:00
|
|
|
|
|
|
|
|
|
|
const allMessages = [...systemMessages, ...enhancedMessages]
|
|
|
|
|
|
|
|
|
|
|
|
const result = streamText({
|
|
|
|
|
|
model,
|
2026-01-29 12:40:40 +08:00
|
|
|
|
abortSignal: req.signal,
|
2025-12-13 23:28:41 +09:00
|
|
|
|
...(process.env.MAX_OUTPUT_TOKENS && {
|
|
|
|
|
|
maxOutputTokens: parseInt(process.env.MAX_OUTPUT_TOKENS, 10),
|
|
|
|
|
|
}),
|
2025-12-07 00:40:13 +09:00
|
|
|
|
stopWhen: stepCountIs(5),
|
2025-12-14 12:34:34 +09:00
|
|
|
|
// Repair truncated tool calls when maxOutputTokens is reached mid-JSON
|
|
|
|
|
|
experimental_repairToolCall: async ({ toolCall, error }) => {
|
2025-12-15 21:28:31 +09:00
|
|
|
|
// DEBUG: Log what we're trying to repair
|
|
|
|
|
|
console.log(`[repairToolCall] Tool: ${toolCall.toolName}`)
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
`[repairToolCall] Error: ${error.name} - ${error.message}`,
|
|
|
|
|
|
)
|
|
|
|
|
|
console.log(`[repairToolCall] Input type: ${typeof toolCall.input}`)
|
|
|
|
|
|
console.log(`[repairToolCall] Input value:`, toolCall.input)
|
|
|
|
|
|
|
2025-12-14 12:34:34 +09:00
|
|
|
|
// Only attempt repair for invalid tool input (broken JSON from truncation)
|
|
|
|
|
|
if (
|
|
|
|
|
|
error instanceof InvalidToolInputError ||
|
|
|
|
|
|
error.name === "AI_InvalidToolInputError"
|
|
|
|
|
|
) {
|
|
|
|
|
|
try {
|
2025-12-15 21:28:31 +09:00
|
|
|
|
// Pre-process to fix common LLM JSON errors that jsonrepair can't handle
|
|
|
|
|
|
let inputToRepair = toolCall.input
|
|
|
|
|
|
if (typeof inputToRepair === "string") {
|
|
|
|
|
|
// Fix `:=` instead of `: ` (LLM sometimes generates this)
|
|
|
|
|
|
inputToRepair = inputToRepair.replace(/:=/g, ": ")
|
|
|
|
|
|
// Fix `= "` instead of `: "`
|
|
|
|
|
|
inputToRepair = inputToRepair.replace(/=\s*"/g, ': "')
|
2026-01-17 20:52:18 +09:00
|
|
|
|
// Fix inconsistent quote escaping in XML attributes within JSON strings
|
|
|
|
|
|
// Pattern: attribute="value\" where opening quote is unescaped but closing is escaped
|
|
|
|
|
|
// Example: y="-20\" should be y=\"-20\"
|
|
|
|
|
|
inputToRepair = inputToRepair.replace(
|
|
|
|
|
|
/(\w+)="([^"]*?)\\"/g,
|
|
|
|
|
|
'$1=\\"$2\\"',
|
|
|
|
|
|
)
|
2025-12-15 21:28:31 +09:00
|
|
|
|
}
|
2025-12-14 12:34:34 +09:00
|
|
|
|
// Use jsonrepair to fix truncated JSON
|
2025-12-15 21:28:31 +09:00
|
|
|
|
const repairedInput = jsonrepair(inputToRepair)
|
2025-12-14 12:34:34 +09:00
|
|
|
|
console.log(
|
|
|
|
|
|
`[repairToolCall] Repaired truncated JSON for tool: ${toolCall.toolName}`,
|
|
|
|
|
|
)
|
|
|
|
|
|
return { ...toolCall, input: repairedInput }
|
|
|
|
|
|
} catch (repairError) {
|
|
|
|
|
|
console.warn(
|
|
|
|
|
|
`[repairToolCall] Failed to repair JSON for tool: ${toolCall.toolName}`,
|
|
|
|
|
|
repairError,
|
|
|
|
|
|
)
|
2025-12-15 21:28:31 +09:00
|
|
|
|
// Return a placeholder input to avoid API errors in multi-step
|
|
|
|
|
|
// The tool will fail gracefully on client side
|
|
|
|
|
|
if (toolCall.toolName === "edit_diagram") {
|
|
|
|
|
|
return {
|
|
|
|
|
|
...toolCall,
|
|
|
|
|
|
input: {
|
|
|
|
|
|
operations: [],
|
|
|
|
|
|
_error: "JSON repair failed - no operations to apply",
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (toolCall.toolName === "display_diagram") {
|
|
|
|
|
|
return {
|
|
|
|
|
|
...toolCall,
|
|
|
|
|
|
input: {
|
|
|
|
|
|
xml: "",
|
|
|
|
|
|
_error: "JSON repair failed - empty diagram",
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-14 12:34:34 +09:00
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// Don't attempt to repair other errors (like NoSuchToolError)
|
|
|
|
|
|
return null
|
|
|
|
|
|
},
|
2025-12-06 12:46:40 +09:00
|
|
|
|
messages: allMessages,
|
2025-12-10 20:54:43 +05:30
|
|
|
|
...(providerOptions && { providerOptions }), // This now includes all reasoning configs
|
2025-12-06 12:46:40 +09:00
|
|
|
|
...(headers && { headers }),
|
|
|
|
|
|
// Langfuse telemetry config (returns undefined if not configured)
|
|
|
|
|
|
...(getTelemetryConfig({ sessionId: validSessionId, userId }) && {
|
|
|
|
|
|
experimental_telemetry: getTelemetryConfig({
|
|
|
|
|
|
sessionId: validSessionId,
|
|
|
|
|
|
userId,
|
|
|
|
|
|
}),
|
|
|
|
|
|
}),
|
2025-12-23 20:19:28 +09:00
|
|
|
|
onFinish: ({ text, totalUsage }) => {
|
2025-12-23 16:26:45 +09:00
|
|
|
|
// AI SDK 6 telemetry auto-reports token usage on its spans
|
|
|
|
|
|
setTraceOutput(text)
|
2025-12-23 18:36:27 +09:00
|
|
|
|
|
|
|
|
|
|
// Record token usage for server-side quota tracking (if enabled)
|
2025-12-23 20:19:28 +09:00
|
|
|
|
// Use totalUsage (cumulative across all steps) instead of usage (final step only)
|
|
|
|
|
|
// Include all 4 token types: input, output, cache read, cache write
|
2025-12-23 18:36:27 +09:00
|
|
|
|
if (
|
|
|
|
|
|
isQuotaEnabled() &&
|
|
|
|
|
|
!hasOwnApiKey &&
|
|
|
|
|
|
userId !== "anonymous" &&
|
2025-12-23 20:19:28 +09:00
|
|
|
|
totalUsage
|
2025-12-23 18:36:27 +09:00
|
|
|
|
) {
|
|
|
|
|
|
const totalTokens =
|
2025-12-23 20:19:28 +09:00
|
|
|
|
(totalUsage.inputTokens || 0) +
|
|
|
|
|
|
(totalUsage.outputTokens || 0) +
|
|
|
|
|
|
(totalUsage.cachedInputTokens || 0) +
|
|
|
|
|
|
(totalUsage.inputTokenDetails?.cacheWriteTokens || 0)
|
2025-12-23 18:36:27 +09:00
|
|
|
|
recordTokenUsage(userId, totalTokens)
|
|
|
|
|
|
}
|
2025-12-06 12:46:40 +09:00
|
|
|
|
},
|
|
|
|
|
|
tools: {
|
|
|
|
|
|
// Client-side tool that will be executed on the client
|
|
|
|
|
|
display_diagram: {
|
2025-12-14 14:04:44 +09:00
|
|
|
|
description: `Display a diagram on draw.io. Pass ONLY the mxCell elements - wrapper tags and root cells are added automatically.
|
2025-12-03 16:14:53 +09:00
|
|
|
|
|
|
|
|
|
|
VALIDATION RULES (XML will be rejected if violated):
|
2025-12-14 14:04:44 +09:00
|
|
|
|
1. Generate ONLY mxCell elements - NO wrapper tags (<mxfile>, <mxGraphModel>, <root>)
|
|
|
|
|
|
2. Do NOT include root cells (id="0" or id="1") - they are added automatically
|
|
|
|
|
|
3. All mxCell elements must be siblings - never nested
|
|
|
|
|
|
4. Every mxCell needs a unique id (start from "2")
|
|
|
|
|
|
5. Every mxCell needs a valid parent attribute (use "1" for top-level)
|
|
|
|
|
|
6. Escape special chars in values: < > & "
|
|
|
|
|
|
|
|
|
|
|
|
Example (generate ONLY this - no wrapper tags):
|
|
|
|
|
|
<mxCell id="lane1" value="Frontend" style="swimlane;" vertex="1" parent="1">
|
|
|
|
|
|
<mxGeometry x="40" y="40" width="200" height="200" as="geometry"/>
|
|
|
|
|
|
</mxCell>
|
|
|
|
|
|
<mxCell id="step1" value="Step 1" style="rounded=1;" vertex="1" parent="lane1">
|
|
|
|
|
|
<mxGeometry x="20" y="60" width="160" height="40" as="geometry"/>
|
|
|
|
|
|
</mxCell>
|
|
|
|
|
|
<mxCell id="lane2" value="Backend" style="swimlane;" vertex="1" parent="1">
|
|
|
|
|
|
<mxGeometry x="280" y="40" width="200" height="200" as="geometry"/>
|
|
|
|
|
|
</mxCell>
|
|
|
|
|
|
<mxCell id="step2" value="Step 2" style="rounded=1;" vertex="1" parent="lane2">
|
|
|
|
|
|
<mxGeometry x="20" y="60" width="160" height="40" as="geometry"/>
|
|
|
|
|
|
</mxCell>
|
|
|
|
|
|
<mxCell id="edge1" style="edgeStyle=orthogonalEdgeStyle;endArrow=classic;" edge="1" parent="1" source="step1" target="step2">
|
|
|
|
|
|
<mxGeometry relative="1" as="geometry"/>
|
|
|
|
|
|
</mxCell>
|
2025-12-03 16:14:53 +09:00
|
|
|
|
|
|
|
|
|
|
Notes:
|
|
|
|
|
|
- For AWS diagrams, use **AWS 2025 icons**.
|
|
|
|
|
|
- For animated connectors, add "flowAnimation=1" to edge style.
|
|
|
|
|
|
`,
|
2025-12-06 12:46:40 +09:00
|
|
|
|
inputSchema: z.object({
|
|
|
|
|
|
xml: z
|
|
|
|
|
|
.string()
|
|
|
|
|
|
.describe("XML string to be displayed on draw.io"),
|
|
|
|
|
|
}),
|
|
|
|
|
|
},
|
|
|
|
|
|
edit_diagram: {
|
2025-12-15 14:22:56 +09:00
|
|
|
|
description: `Edit the current diagram by ID-based operations (update/add/delete cells).
|
|
|
|
|
|
|
|
|
|
|
|
Operations:
|
|
|
|
|
|
- update: Replace an existing cell by its id. Provide cell_id and complete new_xml.
|
|
|
|
|
|
- add: Add a new cell. Provide cell_id (new unique id) and new_xml.
|
2025-12-30 00:03:30 +09:00
|
|
|
|
- delete: Remove a cell. Cascade is automatic: children AND edges (source/target) are auto-deleted. Only specify ONE cell_id.
|
2025-12-15 14:22:56 +09:00
|
|
|
|
|
|
|
|
|
|
For update/add, new_xml must be a complete mxCell element including mxGeometry.
|
|
|
|
|
|
|
2025-12-25 13:19:04 +09:00
|
|
|
|
⚠️ JSON ESCAPING: Every " inside new_xml MUST be escaped as \\". Example: id=\\"5\\" value=\\"Label\\"
|
|
|
|
|
|
|
|
|
|
|
|
Example - Add a rectangle:
|
|
|
|
|
|
{"operations": [{"operation": "add", "cell_id": "rect-1", "new_xml": "<mxCell id=\\"rect-1\\" value=\\"Hello\\" style=\\"rounded=0;\\" vertex=\\"1\\" parent=\\"1\\"><mxGeometry x=\\"100\\" y=\\"100\\" width=\\"120\\" height=\\"60\\" as=\\"geometry\\"/></mxCell>"}]}
|
|
|
|
|
|
|
2025-12-30 00:03:30 +09:00
|
|
|
|
Example - Delete container (children & edges auto-deleted):
|
|
|
|
|
|
{"operations": [{"operation": "delete", "cell_id": "2"}]}`,
|
2025-12-06 12:46:40 +09:00
|
|
|
|
inputSchema: z.object({
|
2025-12-15 14:22:56 +09:00
|
|
|
|
operations: z
|
2025-12-06 12:46:40 +09:00
|
|
|
|
.array(
|
|
|
|
|
|
z.object({
|
2025-12-25 13:19:04 +09:00
|
|
|
|
operation: z
|
2025-12-15 14:22:56 +09:00
|
|
|
|
.enum(["update", "add", "delete"])
|
2025-12-25 13:19:04 +09:00
|
|
|
|
.describe(
|
|
|
|
|
|
"Operation to perform: add, update, or delete",
|
|
|
|
|
|
),
|
2025-12-15 14:22:56 +09:00
|
|
|
|
cell_id: z
|
2025-12-06 12:46:40 +09:00
|
|
|
|
.string()
|
|
|
|
|
|
.describe(
|
2025-12-15 14:22:56 +09:00
|
|
|
|
"The id of the mxCell. Must match the id attribute in new_xml.",
|
2025-12-06 12:46:40 +09:00
|
|
|
|
),
|
2025-12-15 14:22:56 +09:00
|
|
|
|
new_xml: z
|
2025-12-06 12:46:40 +09:00
|
|
|
|
.string()
|
2025-12-15 14:22:56 +09:00
|
|
|
|
.optional()
|
|
|
|
|
|
.describe(
|
|
|
|
|
|
"Complete mxCell XML element (required for update/add)",
|
|
|
|
|
|
),
|
2025-12-06 12:46:40 +09:00
|
|
|
|
}),
|
|
|
|
|
|
)
|
2025-12-15 14:22:56 +09:00
|
|
|
|
.describe("Array of operations to apply"),
|
2025-12-06 12:46:40 +09:00
|
|
|
|
}),
|
|
|
|
|
|
},
|
2025-12-14 12:34:34 +09:00
|
|
|
|
append_diagram: {
|
|
|
|
|
|
description: `Continue generating diagram XML when previous display_diagram output was truncated due to length limits.
|
|
|
|
|
|
|
|
|
|
|
|
WHEN TO USE: Only call this tool after display_diagram was truncated (you'll see an error message about truncation).
|
|
|
|
|
|
|
|
|
|
|
|
CRITICAL INSTRUCTIONS:
|
2025-12-14 14:04:44 +09:00
|
|
|
|
1. Do NOT include any wrapper tags - just continue the mxCell elements
|
2025-12-14 12:34:34 +09:00
|
|
|
|
2. Continue from EXACTLY where your previous output stopped
|
2025-12-14 14:04:44 +09:00
|
|
|
|
3. Complete the remaining mxCell elements
|
2025-12-14 12:34:34 +09:00
|
|
|
|
4. If still truncated, call append_diagram again with the next fragment
|
|
|
|
|
|
|
|
|
|
|
|
Example: If previous output ended with '<mxCell id="x" style="rounded=1', continue with ';" vertex="1">...' and complete the remaining elements.`,
|
|
|
|
|
|
inputSchema: z.object({
|
|
|
|
|
|
xml: z
|
|
|
|
|
|
.string()
|
|
|
|
|
|
.describe(
|
|
|
|
|
|
"Continuation XML fragment to append (NO wrapper tags)",
|
|
|
|
|
|
),
|
|
|
|
|
|
}),
|
|
|
|
|
|
},
|
2025-12-20 23:19:49 +09:00
|
|
|
|
get_shape_library: {
|
|
|
|
|
|
description: `Get draw.io shape/icon library documentation with style syntax and shape names.
|
|
|
|
|
|
|
|
|
|
|
|
Available libraries:
|
|
|
|
|
|
- Cloud: aws4, azure2, gcp2, alibaba_cloud, openstack, salesforce
|
|
|
|
|
|
- Networking: cisco19, network, kubernetes, vvd, rack
|
|
|
|
|
|
- Business: bpmn, lean_mapping
|
|
|
|
|
|
- General: flowchart, basic, arrows2, infographic, sitemap
|
2026-02-07 13:57:00 +09:00
|
|
|
|
- UI/Mockups: android, material_design
|
2025-12-20 23:19:49 +09:00
|
|
|
|
- Enterprise: citrix, sap, mscae, atlassian
|
|
|
|
|
|
- Engineering: fluidpower, electrical, pid, cabinets, floorplan
|
|
|
|
|
|
- Icons: webicons
|
|
|
|
|
|
|
|
|
|
|
|
Call this tool to get shape names and usage syntax for a specific library.`,
|
|
|
|
|
|
inputSchema: z.object({
|
|
|
|
|
|
library: z
|
|
|
|
|
|
.string()
|
|
|
|
|
|
.describe(
|
|
|
|
|
|
"Library name (e.g., 'aws4', 'kubernetes', 'flowchart')",
|
|
|
|
|
|
),
|
|
|
|
|
|
}),
|
|
|
|
|
|
execute: async ({ library }) => {
|
|
|
|
|
|
// Sanitize input - prevent path traversal attacks
|
|
|
|
|
|
const sanitizedLibrary = library
|
|
|
|
|
|
.toLowerCase()
|
|
|
|
|
|
.replace(/[^a-z0-9_-]/g, "")
|
|
|
|
|
|
|
|
|
|
|
|
if (sanitizedLibrary !== library.toLowerCase()) {
|
|
|
|
|
|
return `Invalid library name "${library}". Use only letters, numbers, underscores, and hyphens.`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const baseDir = path.join(
|
|
|
|
|
|
process.cwd(),
|
|
|
|
|
|
"docs/shape-libraries",
|
|
|
|
|
|
)
|
|
|
|
|
|
const filePath = path.join(
|
|
|
|
|
|
baseDir,
|
|
|
|
|
|
`${sanitizedLibrary}.md`,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// Verify path stays within expected directory
|
|
|
|
|
|
const resolvedPath = path.resolve(filePath)
|
|
|
|
|
|
if (!resolvedPath.startsWith(path.resolve(baseDir))) {
|
|
|
|
|
|
return `Invalid library path.`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const content = await fs.readFile(filePath, "utf-8")
|
|
|
|
|
|
return content
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (
|
|
|
|
|
|
(error as NodeJS.ErrnoException).code === "ENOENT"
|
|
|
|
|
|
) {
|
2026-02-07 13:57:00 +09:00
|
|
|
|
return `Library "${library}" not found. Available: aws4, azure2, gcp2, alibaba_cloud, cisco19, kubernetes, network, bpmn, flowchart, basic, arrows2, vvd, salesforce, citrix, sap, mscae, atlassian, fluidpower, electrical, pid, cabinets, floorplan, webicons, infographic, sitemap, android, material_design, lean_mapping, openstack, rack`
|
2025-12-20 23:19:49 +09:00
|
|
|
|
}
|
|
|
|
|
|
console.error(
|
|
|
|
|
|
`[get_shape_library] Error loading "${library}":`,
|
|
|
|
|
|
error,
|
|
|
|
|
|
)
|
|
|
|
|
|
return `Error loading library "${library}". Please try again.`
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
2025-12-06 12:46:40 +09:00
|
|
|
|
},
|
2025-12-06 22:04:59 +05:30
|
|
|
|
...(process.env.TEMPERATURE !== undefined && {
|
|
|
|
|
|
temperature: parseFloat(process.env.TEMPERATURE),
|
|
|
|
|
|
}),
|
2025-12-06 12:46:40 +09:00
|
|
|
|
})
|
|
|
|
|
|
|
2025-12-08 18:56:34 +09:00
|
|
|
|
return result.toUIMessageStreamResponse({
|
2025-12-10 20:54:43 +05:30
|
|
|
|
sendReasoning: true,
|
2025-12-08 18:56:34 +09:00
|
|
|
|
messageMetadata: ({ part }) => {
|
|
|
|
|
|
if (part.type === "finish") {
|
|
|
|
|
|
const usage = (part as any).totalUsage
|
2025-12-23 16:26:45 +09:00
|
|
|
|
// AI SDK 6 provides totalTokens directly
|
2025-12-08 18:56:34 +09:00
|
|
|
|
return {
|
2025-12-23 16:26:45 +09:00
|
|
|
|
totalTokens: usage?.totalTokens ?? 0,
|
2025-12-14 12:34:34 +09:00
|
|
|
|
finishReason: (part as any).finishReason,
|
2025-12-08 18:56:34 +09:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return undefined
|
|
|
|
|
|
},
|
|
|
|
|
|
})
|
2025-12-04 11:24:26 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-08 19:52:18 +08:00
|
|
|
|
// Helper to categorize errors and return appropriate response
|
|
|
|
|
|
function handleError(error: unknown): Response {
|
|
|
|
|
|
console.error("Error in chat route:", error)
|
|
|
|
|
|
|
|
|
|
|
|
const isDev = process.env.NODE_ENV === "development"
|
|
|
|
|
|
|
|
|
|
|
|
// Check for specific AI SDK error types
|
|
|
|
|
|
if (APICallError.isInstance(error)) {
|
|
|
|
|
|
return Response.json(
|
|
|
|
|
|
{
|
|
|
|
|
|
error: error.message,
|
|
|
|
|
|
...(isDev && {
|
|
|
|
|
|
details: error.responseBody,
|
|
|
|
|
|
stack: error.stack,
|
|
|
|
|
|
}),
|
|
|
|
|
|
},
|
|
|
|
|
|
{ status: error.statusCode || 500 },
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (LoadAPIKeyError.isInstance(error)) {
|
|
|
|
|
|
return Response.json(
|
|
|
|
|
|
{
|
|
|
|
|
|
error: "Authentication failed. Please check your API key.",
|
|
|
|
|
|
...(isDev && {
|
|
|
|
|
|
stack: error.stack,
|
|
|
|
|
|
}),
|
|
|
|
|
|
},
|
|
|
|
|
|
{ status: 401 },
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Fallback for other errors with safety filter
|
|
|
|
|
|
const message =
|
|
|
|
|
|
error instanceof Error ? error.message : "An unexpected error occurred"
|
|
|
|
|
|
const status = (error as any)?.statusCode || (error as any)?.status || 500
|
|
|
|
|
|
|
|
|
|
|
|
// Prevent leaking API keys, tokens, or other sensitive data
|
|
|
|
|
|
const lowerMessage = message.toLowerCase()
|
|
|
|
|
|
const safeMessage =
|
|
|
|
|
|
lowerMessage.includes("key") ||
|
|
|
|
|
|
lowerMessage.includes("token") ||
|
|
|
|
|
|
lowerMessage.includes("sig") ||
|
|
|
|
|
|
lowerMessage.includes("signature") ||
|
|
|
|
|
|
lowerMessage.includes("secret") ||
|
|
|
|
|
|
lowerMessage.includes("password") ||
|
|
|
|
|
|
lowerMessage.includes("credential")
|
|
|
|
|
|
? "Authentication failed. Please check your credentials."
|
|
|
|
|
|
: message
|
|
|
|
|
|
|
|
|
|
|
|
return Response.json(
|
|
|
|
|
|
{
|
|
|
|
|
|
error: safeMessage,
|
|
|
|
|
|
...(isDev && {
|
|
|
|
|
|
details: message,
|
|
|
|
|
|
stack: error instanceof Error ? error.stack : undefined,
|
|
|
|
|
|
}),
|
|
|
|
|
|
},
|
|
|
|
|
|
{ status },
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 21:15:02 +09:00
|
|
|
|
// Wrap handler with error handling
|
|
|
|
|
|
async function safeHandler(req: Request): Promise<Response> {
|
2025-12-06 12:46:40 +09:00
|
|
|
|
try {
|
|
|
|
|
|
return await handleChatRequest(req)
|
|
|
|
|
|
} catch (error) {
|
2025-12-08 19:52:18 +08:00
|
|
|
|
return handleError(error)
|
2025-12-06 12:46:40 +09:00
|
|
|
|
}
|
2025-03-19 08:16:44 +00:00
|
|
|
|
}
|
2025-12-05 21:15:02 +09:00
|
|
|
|
|
|
|
|
|
|
// Wrap with Langfuse observe (if configured)
|
2025-12-06 12:46:40 +09:00
|
|
|
|
const observedHandler = wrapWithObserve(safeHandler)
|
2025-12-05 21:15:02 +09:00
|
|
|
|
|
|
|
|
|
|
export async function POST(req: Request) {
|
2025-12-06 12:46:40 +09:00
|
|
|
|
return observedHandler(req)
|
2025-12-05 21:15:02 +09:00
|
|
|
|
}
|