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"
feat(diagram-engine): wire up restructure_diagram + stencil catalog
Closes the loop: the model can now build and edit AWS architecture diagrams by
declaring structure, and never writes an mxCell again.
catalog.ts — 983 AWS icon and 19 group stencils as a name→style map, generated from
drawio-ai-kit's catalog (itself generated from jgraph's draw.io shape index). Styles
are verbatim, so the official category colours, connection points and aspect=fixed
come along for free and nothing is hand-assembled. An invented name is rejected with
suggestions instead of rendering as a blank square, which is what draw.io does with
an unknown resIcon today.
operations.ts — what the model actually sends: add_icon / add_container / move /
link / set_dir and so on, applied in order against the tree. Guards the things that
break a diagram quietly: duplicate ids (draw.io drops one of the two cells), edges
left pointing at a removed node, and moving a container inside itself.
index.ts — the entry point. current XML → parse → apply ops → check names → layout →
render → new XML. The tree is not stored between calls; it is re-derived from the
canvas every time, so a user's manual edits are input to the next layout rather than
state to reconcile.
Token cost, measured with Claude's tokenizer rather than estimated:
- build a VPC diagram: 515 tok as operations vs 3180 as XML (6.2x)
- add one icon: 27 tok as an operation vs 3823 re-emitting (142x)
- read current state: 216 tok as an outline vs 3180 as XML (14.7x)
The 142x is the one that matters day to day: "add a Redis" is one operation, not a
rewrite of the whole diagram.
Routing in the system prompt sends AWS architecture through this path and leaves
flowcharts, BPMN, sequence diagrams, mind maps and Azure/GCP on display_diagram —
the layout engine's primitives (nested rows, columns, grids) do not model a sequence
diagram's lifelines or a mind map's radial spread, and pretending otherwise would
make those worse rather than better.
Also: added a NOTICE recording the MIT port and the AWS Architecture Icons terms,
and a narrow .gitignore exception so the generated catalog is tracked while the
root data/ directory (admin settings, contains secrets) stays ignored.
403 unit tests + 3 new e2e. Verified in the real app: a structural tool call renders
with real stencils and container markers; a second call adds one node and keeps
everything from the first; an invented name is refused and nothing is drawn. The 13
existing diagram e2e tests still pass.
2026-08-09 12:13:54 +09:00
import { OperationSchema , searchStencils } from "@/lib/diagram-engine"
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 : & lt ; & gt ; & amp ; & quot ;
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)" ,
) ,
} ) ,
} ,
feat(diagram-engine): wire up restructure_diagram + stencil catalog
Closes the loop: the model can now build and edit AWS architecture diagrams by
declaring structure, and never writes an mxCell again.
catalog.ts — 983 AWS icon and 19 group stencils as a name→style map, generated from
drawio-ai-kit's catalog (itself generated from jgraph's draw.io shape index). Styles
are verbatim, so the official category colours, connection points and aspect=fixed
come along for free and nothing is hand-assembled. An invented name is rejected with
suggestions instead of rendering as a blank square, which is what draw.io does with
an unknown resIcon today.
operations.ts — what the model actually sends: add_icon / add_container / move /
link / set_dir and so on, applied in order against the tree. Guards the things that
break a diagram quietly: duplicate ids (draw.io drops one of the two cells), edges
left pointing at a removed node, and moving a container inside itself.
index.ts — the entry point. current XML → parse → apply ops → check names → layout →
render → new XML. The tree is not stored between calls; it is re-derived from the
canvas every time, so a user's manual edits are input to the next layout rather than
state to reconcile.
Token cost, measured with Claude's tokenizer rather than estimated:
- build a VPC diagram: 515 tok as operations vs 3180 as XML (6.2x)
- add one icon: 27 tok as an operation vs 3823 re-emitting (142x)
- read current state: 216 tok as an outline vs 3180 as XML (14.7x)
The 142x is the one that matters day to day: "add a Redis" is one operation, not a
rewrite of the whole diagram.
Routing in the system prompt sends AWS architecture through this path and leaves
flowcharts, BPMN, sequence diagrams, mind maps and Azure/GCP on display_diagram —
the layout engine's primitives (nested rows, columns, grids) do not model a sequence
diagram's lifelines or a mind map's radial spread, and pretending otherwise would
make those worse rather than better.
Also: added a NOTICE recording the MIT port and the AWS Architecture Icons terms,
and a narrow .gitignore exception so the generated catalog is tracked while the
root data/ directory (admin settings, contains secrets) stays ignored.
403 unit tests + 3 new e2e. Verified in the real app: a structural tool call renders
with real stencils and container markers; a second call adds one node and keeps
everything from the first; an invented name is refused and nothing is drawn. The 13
existing diagram e2e tests still pass.
2026-08-09 12:13:54 +09:00
restructure_diagram : {
description : ` Build or edit a CLOUD ARCHITECTURE diagram (AWS) by declaring STRUCTURE. The engine computes every coordinate.
PREFER THIS over display_diagram / edit_diagram for AWS architecture diagrams . You declare what nests inside what ; layout , sizing , alignment and arrow routing are computed . Containers always fit their contents and siblings never overlap , so the usual layout problems cannot occur .
Never write coordinates , mxCell XML , or style strings . Look icon names up with search_stencils first — an invented name is rejected with suggestions .
Operations are applied in order , so you can add a container and fill it in the same call :
{ "operations" : [
{ "op" : "add_container" , "id" : "vpc" , "label" : "VPC 10.0.0.0/16" , "dir" : "col" , "gname" : "group_vpc" } ,
{ "op" : "add_icon" , "id" : "alb" , "parent" : "vpc" , "name" : "application_load_balancer" , "label" : "ALB" } ,
{ "op" : "add_icon" , "id" : "ec2" , "parent" : "vpc" , "name" : "ec2" , "label" : "EC2" } ,
{ "op" : "link" , "source" : "alb" , "target" : "ec2" , "label" : "route" , "step" : 1 }
] }
Editing an existing diagram : the structure is re - read from the canvas each time , INCLUDING anything the user moved or recoloured by hand . To add one service , send one operation — do not re - send the diagram .
Containers : dir "row" puts children side by side , "col" stacks them . An empty label makes an invisible grouping wrapper ( use it to group columns without drawing another frame ) . gname is an AWS group stencil ( group_region , group_vpc , group_availability_zone , group_subnet , group_account ) — omit it for a plain titled frame . add_grid packs children into cols columns ; use it to pack 3 - 8 related icons into one labelled area box rather than giving each its own frame . ` ,
inputSchema : z.object ( {
operations : z
. array ( OperationSchema )
. describe ( "Structural operations, applied in order" ) ,
} ) ,
} ,
search_stencils : {
description : ` Find AWS stencil names for restructure_diagram. Returns names and official colours — call this before naming an icon, and batch the whole diagram's lookups into as few calls as possible. ` ,
inputSchema : z.object ( {
query : z
. string ( )
. describe (
"Service name or keyword, e.g. 's3' or 'nat gateway'" ,
) ,
kind : z
. enum ( [ "icon" , "group" ] )
. optional ( )
. describe (
"Restrict to service icons or container frames" ,
) ,
limit : z.number ( ) . optional ( ) ,
} ) ,
execute : async ( { query , kind , limit } ) = > {
const hits = searchStencils ( query , { kind , limit } )
if ( hits . length === 0 )
return ` No stencil matches " ${ query } ". Try a shorter or more general term. `
return JSON . stringify ( hits )
} ,
} ,
2025-12-20 23:19:49 +09:00
get_shape_library : {
feat(diagram-engine): wire up restructure_diagram + stencil catalog
Closes the loop: the model can now build and edit AWS architecture diagrams by
declaring structure, and never writes an mxCell again.
catalog.ts — 983 AWS icon and 19 group stencils as a name→style map, generated from
drawio-ai-kit's catalog (itself generated from jgraph's draw.io shape index). Styles
are verbatim, so the official category colours, connection points and aspect=fixed
come along for free and nothing is hand-assembled. An invented name is rejected with
suggestions instead of rendering as a blank square, which is what draw.io does with
an unknown resIcon today.
operations.ts — what the model actually sends: add_icon / add_container / move /
link / set_dir and so on, applied in order against the tree. Guards the things that
break a diagram quietly: duplicate ids (draw.io drops one of the two cells), edges
left pointing at a removed node, and moving a container inside itself.
index.ts — the entry point. current XML → parse → apply ops → check names → layout →
render → new XML. The tree is not stored between calls; it is re-derived from the
canvas every time, so a user's manual edits are input to the next layout rather than
state to reconcile.
Token cost, measured with Claude's tokenizer rather than estimated:
- build a VPC diagram: 515 tok as operations vs 3180 as XML (6.2x)
- add one icon: 27 tok as an operation vs 3823 re-emitting (142x)
- read current state: 216 tok as an outline vs 3180 as XML (14.7x)
The 142x is the one that matters day to day: "add a Redis" is one operation, not a
rewrite of the whole diagram.
Routing in the system prompt sends AWS architecture through this path and leaves
flowcharts, BPMN, sequence diagrams, mind maps and Azure/GCP on display_diagram —
the layout engine's primitives (nested rows, columns, grids) do not model a sequence
diagram's lifelines or a mind map's radial spread, and pretending otherwise would
make those worse rather than better.
Also: added a NOTICE recording the MIT port and the AWS Architecture Icons terms,
and a narrow .gitignore exception so the generated catalog is tracked while the
root data/ directory (admin settings, contains secrets) stays ignored.
403 unit tests + 3 new e2e. Verified in the real app: a structural tool call renders
with real stencils and container markers; a second call adds one node and keeps
everything from the first; an invented name is refused and nothing is drawn. The 13
existing diagram e2e tests still pass.
2026-08-09 12:13:54 +09:00
description : ` Get draw.io shape/icon library documentation with style syntax and shape names. Use this for NON-AWS diagrams (flowcharts, BPMN, sequence, mind maps, UI mockups) that go through display_diagram. For AWS architecture, use search_stencils + restructure_diagram instead.
2025-12-20 23:19:49 +09:00
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
}