mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-09-02 01:20:23 +08:00
Compare commits
6 Commits
v0.4.16
...
refresh-su
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0956684b8e | ||
|
|
410993a3bf | ||
|
|
77e7766f9a | ||
|
|
a9ffd6a1de | ||
|
|
277ad83552 | ||
|
|
7b6eb39fa5 |
@@ -1,7 +1,7 @@
|
|||||||
import { extract } from "@extractus/article-extractor"
|
import { extract } from "@extractus/article-extractor"
|
||||||
import { NextResponse } from "next/server"
|
import { NextResponse } from "next/server"
|
||||||
import TurndownService from "turndown"
|
import TurndownService from "turndown"
|
||||||
import { allowPrivateUrls, isPrivateUrl } from "@/lib/ssrf-protection"
|
import { isPrivateUrl } from "@/lib/ssrf-protection"
|
||||||
|
|
||||||
const MAX_CONTENT_LENGTH = 150000 // Match PDF limit
|
const MAX_CONTENT_LENGTH = 150000 // Match PDF limit
|
||||||
const EXTRACT_TIMEOUT_MS = 15000
|
const EXTRACT_TIMEOUT_MS = 15000
|
||||||
@@ -28,8 +28,10 @@ export async function POST(req: Request) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SSRF protection
|
// SSRF protection: parse-url has no use case for fetching internal
|
||||||
if (!allowPrivateUrls && isPrivateUrl(url)) {
|
// hosts, so private URLs are always rejected. ALLOW_PRIVATE_URLS only
|
||||||
|
// governs LLM provider baseUrl overrides (validate-model, chat).
|
||||||
|
if (isPrivateUrl(url)) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: "Cannot access private/internal URLs" },
|
{ error: "Cannot access private/internal URLs" },
|
||||||
{ status: 400 },
|
{ status: 400 },
|
||||||
|
|||||||
@@ -53,6 +53,13 @@ ANTHROPIC_API_KEY=your_api_key
|
|||||||
AI_MODEL=claude-sonnet-4-5-20250514
|
AI_MODEL=claude-sonnet-4-5-20250514
|
||||||
```
|
```
|
||||||
|
|
||||||
|
或者使用 Bearer 认证令牌(例如通过会下发 OAuth 风格 token 的网关时)。`ANTHROPIC_AUTH_TOKEN` 会作为 `Authorization: Bearer <token>` 头发送,而 `ANTHROPIC_API_KEY` 会作为 `x-api-key` 头发送。两者互斥,只能设置其中之一:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ANTHROPIC_AUTH_TOKEN=your_auth_token
|
||||||
|
AI_MODEL=claude-sonnet-4-5-20250514
|
||||||
|
```
|
||||||
|
|
||||||
可选的自定义端点:
|
可选的自定义端点:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -215,7 +222,7 @@ MiniMax 支持两种 API 格式:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
MINIMAX_API_KEY=your_api_key
|
MINIMAX_API_KEY=your_api_key
|
||||||
AI_MODEL=MiniMax-M2.7
|
AI_MODEL=MiniMax-M3
|
||||||
```
|
```
|
||||||
|
|
||||||
可选配置:
|
可选配置:
|
||||||
|
|||||||
@@ -68,6 +68,13 @@ ANTHROPIC_API_KEY=your_api_key
|
|||||||
AI_MODEL=claude-sonnet-4-5-20250514
|
AI_MODEL=claude-sonnet-4-5-20250514
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Or use a Bearer auth token instead of an API key (e.g. when going through a gateway that issues OAuth-style tokens). `ANTHROPIC_AUTH_TOKEN` is sent as `Authorization: Bearer <token>`, while `ANTHROPIC_API_KEY` is sent as `x-api-key`. The two are mutually exclusive — set only one:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ANTHROPIC_AUTH_TOKEN=your_auth_token
|
||||||
|
AI_MODEL=claude-sonnet-4-5-20250514
|
||||||
|
```
|
||||||
|
|
||||||
Optional custom endpoint:
|
Optional custom endpoint:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -230,7 +237,7 @@ MiniMax supports two API formats:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
MINIMAX_API_KEY=your_api_key
|
MINIMAX_API_KEY=your_api_key
|
||||||
AI_MODEL=MiniMax-M2.7
|
AI_MODEL=MiniMax-M3
|
||||||
```
|
```
|
||||||
|
|
||||||
Optional configuration:
|
Optional configuration:
|
||||||
|
|||||||
@@ -53,6 +53,13 @@ ANTHROPIC_API_KEY=your_api_key
|
|||||||
AI_MODEL=claude-sonnet-4-5-20250514
|
AI_MODEL=claude-sonnet-4-5-20250514
|
||||||
```
|
```
|
||||||
|
|
||||||
|
または、Bearer 認証トークンを使用することもできます(OAuth スタイルのトークンを発行するゲートウェイ経由で利用する場合など)。`ANTHROPIC_AUTH_TOKEN` は `Authorization: Bearer <token>` ヘッダーで送信され、`ANTHROPIC_API_KEY` は `x-api-key` ヘッダーで送信されます。両者は排他的なので、いずれか一方のみを設定してください:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ANTHROPIC_AUTH_TOKEN=your_auth_token
|
||||||
|
AI_MODEL=claude-sonnet-4-5-20250514
|
||||||
|
```
|
||||||
|
|
||||||
任意のカスタムエンドポイント:
|
任意のカスタムエンドポイント:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -215,7 +222,7 @@ MiniMax は 2 つの API 形式をサポートしています:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
MINIMAX_API_KEY=your_api_key
|
MINIMAX_API_KEY=your_api_key
|
||||||
AI_MODEL=MiniMax-M2.7
|
AI_MODEL=MiniMax-M3
|
||||||
```
|
```
|
||||||
|
|
||||||
オプション設定:
|
オプション設定:
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ AI_MODEL=global.anthropic.claude-sonnet-4-5-20250929-v1:0
|
|||||||
# OPENAI_REASONING_SUMMARY=detailed # Optional: Override reasoning summary (none/brief/detailed)
|
# OPENAI_REASONING_SUMMARY=detailed # Optional: Override reasoning summary (none/brief/detailed)
|
||||||
|
|
||||||
# Anthropic (Direct) Configuration
|
# Anthropic (Direct) Configuration
|
||||||
# ANTHROPIC_API_KEY=sk-ant-...
|
# ANTHROPIC_API_KEY=sk-ant-... # Sent as `x-api-key` header
|
||||||
|
# ANTHROPIC_AUTH_TOKEN= # Alternative to ANTHROPIC_API_KEY; sent as `Authorization: Bearer` header (mutually exclusive)
|
||||||
# ANTHROPIC_BASE_URL=https://your-custom-anthropic/v1
|
# ANTHROPIC_BASE_URL=https://your-custom-anthropic/v1
|
||||||
# ANTHROPIC_THINKING_TYPE=enabled # Optional: Anthropic extended thinking (enabled)
|
# ANTHROPIC_THINKING_TYPE=enabled # Optional: Anthropic extended thinking (enabled)
|
||||||
# ANTHROPIC_THINKING_BUDGET_TOKENS=12000 # Optional: Budget for extended thinking in tokens
|
# ANTHROPIC_THINKING_BUDGET_TOKENS=12000 # Optional: Budget for extended thinking in tokens
|
||||||
|
|||||||
@@ -573,7 +573,15 @@ function detectProvider(): ProviderName | null {
|
|||||||
// Skip ollama - it doesn't require credentials
|
// Skip ollama - it doesn't require credentials
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (process.env[envVar]) {
|
// Anthropic accepts ANTHROPIC_AUTH_TOKEN (Bearer auth) as alternative to ANTHROPIC_API_KEY
|
||||||
|
const hasCredential =
|
||||||
|
provider === "anthropic"
|
||||||
|
? !!(
|
||||||
|
process.env.ANTHROPIC_API_KEY ||
|
||||||
|
process.env.ANTHROPIC_AUTH_TOKEN
|
||||||
|
)
|
||||||
|
: !!process.env[envVar]
|
||||||
|
if (hasCredential) {
|
||||||
// Azure requires additional config (baseURL or resourceName)
|
// Azure requires additional config (baseURL or resourceName)
|
||||||
if (provider === "azure") {
|
if (provider === "azure") {
|
||||||
const hasBaseUrl = !!process.env.AZURE_BASE_URL
|
const hasBaseUrl = !!process.env.AZURE_BASE_URL
|
||||||
@@ -615,13 +623,26 @@ function validateProviderCredentials(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use custom env var name if provided, otherwise use default
|
// Anthropic accepts ANTHROPIC_AUTH_TOKEN (Bearer auth) as alternative to ANTHROPIC_API_KEY
|
||||||
const requiredVar = customApiKeyEnv || PROVIDER_ENV_VARS[provider]
|
if (provider === "anthropic" && !customApiKeyEnv) {
|
||||||
if (requiredVar && !process.env[requiredVar]) {
|
const hasCredential = !!(
|
||||||
throw new Error(
|
process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN
|
||||||
`${requiredVar} environment variable is required for ${provider} provider. ` +
|
|
||||||
`Please set it in your .env.local file.`,
|
|
||||||
)
|
)
|
||||||
|
if (!hasCredential) {
|
||||||
|
throw new Error(
|
||||||
|
`Either ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN environment variable is required for anthropic provider. ` +
|
||||||
|
`Please set one in your .env.local file.`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Use custom env var name if provided, otherwise use default
|
||||||
|
const requiredVar = customApiKeyEnv || PROVIDER_ENV_VARS[provider]
|
||||||
|
if (requiredVar && !process.env[requiredVar]) {
|
||||||
|
throw new Error(
|
||||||
|
`${requiredVar} environment variable is required for ${provider} provider. ` +
|
||||||
|
`Please set it in your .env.local file.`,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Azure requires either AZURE_BASE_URL or AZURE_RESOURCE_NAME in addition to API key
|
// Azure requires either AZURE_BASE_URL or AZURE_RESOURCE_NAME in addition to API key
|
||||||
@@ -845,8 +866,16 @@ export function getAIModel(overrides?: ClientOverrides): ModelConfig {
|
|||||||
serverBaseUrl,
|
serverBaseUrl,
|
||||||
"https://api.anthropic.com/v1",
|
"https://api.anthropic.com/v1",
|
||||||
)
|
)
|
||||||
|
// Anthropic supports two auth methods (mutually exclusive):
|
||||||
|
// - apiKey: sends as `x-api-key` header
|
||||||
|
// - authToken: sends as `Authorization: Bearer <token>` header
|
||||||
|
// Prefer apiKey if present (including client overrides); fall back
|
||||||
|
// to ANTHROPIC_AUTH_TOKEN env var only when no apiKey is available.
|
||||||
|
const authToken = !apiKey
|
||||||
|
? process.env.ANTHROPIC_AUTH_TOKEN
|
||||||
|
: undefined
|
||||||
const customProvider = createAnthropic({
|
const customProvider = createAnthropic({
|
||||||
apiKey,
|
...(authToken ? { authToken } : { apiKey }),
|
||||||
baseURL,
|
baseURL,
|
||||||
headers: ANTHROPIC_BETA_HEADERS,
|
headers: ANTHROPIC_BETA_HEADERS,
|
||||||
})
|
})
|
||||||
@@ -1361,8 +1390,12 @@ export function supportsImageInput(modelId: string): boolean {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// MiniMax text models (MiniMax-M2.x series are text-only)
|
// MiniMax text models (MiniMax-M2.x series are text-only; M3 supports image input)
|
||||||
if (lowerModelId.includes("minimax") && !hasVisionIndicator) {
|
if (
|
||||||
|
lowerModelId.includes("minimax") &&
|
||||||
|
!hasVisionIndicator &&
|
||||||
|
!lowerModelId.includes("m3")
|
||||||
|
) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,17 +9,40 @@
|
|||||||
export function isPrivateUrl(urlString: string): boolean {
|
export function isPrivateUrl(urlString: string): boolean {
|
||||||
try {
|
try {
|
||||||
const url = new URL(urlString)
|
const url = new URL(urlString)
|
||||||
const hostname = url.hostname.toLowerCase()
|
// Strip a trailing dot so FQDN forms like "localhost." (which still
|
||||||
|
// resolve to 127.0.0.1) cannot bypass the equality checks below.
|
||||||
|
const hostname = url.hostname
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/^\[|\]$/g, "")
|
||||||
|
.replace(/\.$/, "")
|
||||||
|
|
||||||
// Block localhost
|
// Block localhost
|
||||||
if (
|
if (
|
||||||
hostname === "localhost" ||
|
hostname === "localhost" ||
|
||||||
hostname === "127.0.0.1" ||
|
hostname === "127.0.0.1" ||
|
||||||
hostname === "::1"
|
hostname === "::1" ||
|
||||||
|
hostname === "::"
|
||||||
) {
|
) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Block IPv6 unique-local (fc00::/7), link-local (fe80::/10),
|
||||||
|
// and IPv4-mapped (::ffff:0:0/96) hosts.
|
||||||
|
if (hostname.includes(":")) {
|
||||||
|
if (
|
||||||
|
hostname.startsWith("fc") ||
|
||||||
|
hostname.startsWith("fd") ||
|
||||||
|
hostname.startsWith("::ffff:")
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const linkLocal = hostname.match(/^fe([0-9a-f]{2}):/)
|
||||||
|
if (linkLocal) {
|
||||||
|
const high = parseInt(linkLocal[1], 16)
|
||||||
|
if (high >= 0x80 && high <= 0xbf) return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Block AWS/cloud metadata endpoints
|
// Block AWS/cloud metadata endpoints
|
||||||
if (
|
if (
|
||||||
hostname === "169.254.169.254" ||
|
hostname === "169.254.169.254" ||
|
||||||
|
|||||||
@@ -190,177 +190,202 @@ export const PROVIDER_INFO: Record<
|
|||||||
// Suggested models per provider for quick add
|
// Suggested models per provider for quick add
|
||||||
export const SUGGESTED_MODELS: Partial<Record<ProviderName, string[]>> = {
|
export const SUGGESTED_MODELS: Partial<Record<ProviderName, string[]>> = {
|
||||||
openai: [
|
openai: [
|
||||||
"gpt-5.2-pro",
|
"gpt-5.5-pro",
|
||||||
"gpt-5.2-chat-latest",
|
"gpt-5.5",
|
||||||
"gpt-5.2",
|
"gpt-5.4-pro",
|
||||||
"gpt-5.1-codex-mini",
|
"gpt-5.4",
|
||||||
"gpt-5.1-codex",
|
"gpt-5.4-mini",
|
||||||
"gpt-5.1-chat-latest",
|
"gpt-5.4-nano",
|
||||||
"gpt-5.1",
|
"gpt-5-codex-mini",
|
||||||
"gpt-5-pro",
|
|
||||||
"gpt-5",
|
|
||||||
"gpt-5-mini",
|
|
||||||
"gpt-5-nano",
|
|
||||||
"gpt-5-codex",
|
|
||||||
"gpt-5-chat-latest",
|
|
||||||
"gpt-4.1",
|
"gpt-4.1",
|
||||||
"gpt-4.1-mini",
|
"gpt-4.1-mini",
|
||||||
"gpt-4.1-nano",
|
|
||||||
"gpt-4o",
|
"gpt-4o",
|
||||||
"gpt-4o-mini",
|
"gpt-4o-mini",
|
||||||
],
|
],
|
||||||
anthropic: [
|
anthropic: [
|
||||||
// Claude 4.5 series (latest)
|
// Claude 4.8 / 4.7 / 4.6 series (latest, dateless pinned IDs)
|
||||||
"claude-opus-4-5-20250514",
|
"claude-opus-4-8",
|
||||||
"claude-sonnet-4-5-20250514",
|
"claude-sonnet-4-6",
|
||||||
// Claude 4 series
|
"claude-haiku-4-5",
|
||||||
"claude-opus-4-20250514",
|
"claude-opus-4-7",
|
||||||
"claude-sonnet-4-20250514",
|
"claude-opus-4-6",
|
||||||
|
// Claude 4.5 series
|
||||||
|
"claude-sonnet-4-5-20250929",
|
||||||
|
"claude-opus-4-5-20251101",
|
||||||
// Claude 3.7 series
|
// Claude 3.7 series
|
||||||
"claude-3-7-sonnet-20250219",
|
"claude-3-7-sonnet-20250219",
|
||||||
// Claude 3.5 series
|
// Claude 3.5 series
|
||||||
"claude-3-5-sonnet-20241022",
|
"claude-3-5-sonnet-20241022",
|
||||||
"claude-3-5-haiku-20241022",
|
"claude-3-5-haiku-20241022",
|
||||||
// Claude 3 series
|
|
||||||
"claude-3-opus-20240229",
|
|
||||||
"claude-3-sonnet-20240229",
|
|
||||||
"claude-3-haiku-20240307",
|
|
||||||
],
|
],
|
||||||
google: [
|
google: [
|
||||||
|
// Gemini 3 series
|
||||||
|
"gemini-3.1-pro",
|
||||||
|
"gemini-3.5-flash",
|
||||||
|
"gemini-3-flash",
|
||||||
|
"gemini-3.1-flash-lite",
|
||||||
// Gemini 2.5 series
|
// Gemini 2.5 series
|
||||||
"gemini-2.5-pro",
|
"gemini-2.5-pro",
|
||||||
"gemini-2.5-flash",
|
"gemini-2.5-flash",
|
||||||
"gemini-2.5-flash-preview-05-20",
|
"gemini-2.5-flash-lite",
|
||||||
// Gemini 2.0 series
|
|
||||||
"gemini-2.0-flash",
|
|
||||||
"gemini-2.0-flash-exp",
|
|
||||||
"gemini-2.0-flash-lite",
|
|
||||||
// Gemini 1.5 series
|
|
||||||
"gemini-1.5-pro",
|
|
||||||
"gemini-1.5-flash",
|
|
||||||
// Legacy
|
|
||||||
"gemini-pro",
|
|
||||||
],
|
],
|
||||||
vertexai: [
|
vertexai: [
|
||||||
|
// Gemini 3 series
|
||||||
|
"gemini-3.1-pro-preview",
|
||||||
|
"gemini-3.5-flash",
|
||||||
|
"gemini-3-flash-preview",
|
||||||
|
"gemini-3.1-flash-lite",
|
||||||
// Gemini 2.5 series
|
// Gemini 2.5 series
|
||||||
"gemini-2.5-pro",
|
"gemini-2.5-pro",
|
||||||
"gemini-2.5-flash",
|
"gemini-2.5-flash",
|
||||||
// Gemini 2.0 series
|
"gemini-2.5-flash-lite",
|
||||||
"gemini-2.0-flash",
|
],
|
||||||
"gemini-2.0-flash-exp",
|
azure: [
|
||||||
// Gemini 1.5 series
|
"gpt-5.5",
|
||||||
"gemini-1.5-pro",
|
"gpt-5.4",
|
||||||
"gemini-1.5-flash",
|
"gpt-5.1",
|
||||||
|
"gpt-5",
|
||||||
|
"gpt-5-mini",
|
||||||
|
"gpt-4.1",
|
||||||
|
"gpt-4o",
|
||||||
|
"gpt-4o-mini",
|
||||||
|
"o3",
|
||||||
|
"o4-mini",
|
||||||
],
|
],
|
||||||
azure: ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo", "gpt-4", "gpt-35-turbo"],
|
|
||||||
bedrock: [
|
bedrock: [
|
||||||
// Anthropic Claude
|
// Anthropic Claude
|
||||||
"anthropic.claude-opus-4-5-20250514-v1:0",
|
"anthropic.claude-opus-4-8",
|
||||||
"anthropic.claude-sonnet-4-5-20250514-v1:0",
|
"anthropic.claude-opus-4-7",
|
||||||
|
"anthropic.claude-sonnet-4-6",
|
||||||
|
"anthropic.claude-opus-4-6-v1",
|
||||||
|
"anthropic.claude-opus-4-5-20251101-v1:0",
|
||||||
|
"anthropic.claude-sonnet-4-5-20250929-v1:0",
|
||||||
|
"anthropic.claude-haiku-4-5-20251001-v1:0",
|
||||||
|
"anthropic.claude-opus-4-1-20250805-v1:0",
|
||||||
"anthropic.claude-opus-4-20250514-v1:0",
|
"anthropic.claude-opus-4-20250514-v1:0",
|
||||||
"anthropic.claude-sonnet-4-20250514-v1:0",
|
"anthropic.claude-sonnet-4-20250514-v1:0",
|
||||||
"anthropic.claude-3-7-sonnet-20250219-v1:0",
|
|
||||||
"anthropic.claude-3-5-sonnet-20241022-v2:0",
|
|
||||||
"anthropic.claude-3-5-haiku-20241022-v1:0",
|
"anthropic.claude-3-5-haiku-20241022-v1:0",
|
||||||
"anthropic.claude-3-opus-20240229-v1:0",
|
|
||||||
"anthropic.claude-3-sonnet-20240229-v1:0",
|
|
||||||
"anthropic.claude-3-haiku-20240307-v1:0",
|
|
||||||
// Amazon Nova
|
// Amazon Nova
|
||||||
|
"amazon.nova-2-lite-v1:0",
|
||||||
|
"amazon.nova-premier-v1:0",
|
||||||
"amazon.nova-pro-v1:0",
|
"amazon.nova-pro-v1:0",
|
||||||
"amazon.nova-lite-v1:0",
|
"amazon.nova-lite-v1:0",
|
||||||
"amazon.nova-micro-v1:0",
|
"amazon.nova-micro-v1:0",
|
||||||
// Meta Llama
|
// Meta Llama
|
||||||
|
"meta.llama4-maverick-17b-instruct-v1:0",
|
||||||
|
"meta.llama4-scout-17b-instruct-v1:0",
|
||||||
"meta.llama3-3-70b-instruct-v1:0",
|
"meta.llama3-3-70b-instruct-v1:0",
|
||||||
"meta.llama3-1-405b-instruct-v1:0",
|
|
||||||
"meta.llama3-1-70b-instruct-v1:0",
|
|
||||||
// Mistral
|
// Mistral
|
||||||
"mistral.mistral-large-2411-v1:0",
|
"mistral.mistral-large-3-675b-instruct",
|
||||||
"mistral.mistral-small-2503-v1:0",
|
"mistral.pixtral-large-2502-v1:0",
|
||||||
],
|
],
|
||||||
openrouter: [
|
openrouter: [
|
||||||
// Anthropic
|
// Anthropic
|
||||||
"anthropic/claude-sonnet-4",
|
"anthropic/claude-opus-4.8",
|
||||||
"anthropic/claude-opus-4",
|
"anthropic/claude-sonnet-4.6",
|
||||||
"anthropic/claude-3.5-sonnet",
|
"anthropic/claude-haiku-4.5",
|
||||||
"anthropic/claude-3.5-haiku",
|
|
||||||
// OpenAI
|
// OpenAI
|
||||||
"openai/gpt-4o",
|
"openai/gpt-5.5",
|
||||||
|
"openai/gpt-5.4",
|
||||||
|
"openai/gpt-5.4-mini",
|
||||||
"openai/gpt-4o-mini",
|
"openai/gpt-4o-mini",
|
||||||
"openai/o1",
|
|
||||||
"openai/o3-mini",
|
|
||||||
// Google
|
// Google
|
||||||
"google/gemini-2.5-pro",
|
"google/gemini-3.1-pro-preview",
|
||||||
"google/gemini-2.5-flash",
|
"google/gemini-3.5-flash",
|
||||||
"google/gemini-2.0-flash-exp:free",
|
"google/gemini-2.5-flash-lite",
|
||||||
|
// xAI
|
||||||
|
"x-ai/grok-4.3",
|
||||||
// Meta Llama
|
// Meta Llama
|
||||||
|
"meta-llama/llama-4-maverick",
|
||||||
|
"meta-llama/llama-4-scout",
|
||||||
"meta-llama/llama-3.3-70b-instruct",
|
"meta-llama/llama-3.3-70b-instruct",
|
||||||
"meta-llama/llama-3.1-405b-instruct",
|
|
||||||
"meta-llama/llama-3.1-70b-instruct",
|
|
||||||
// DeepSeek
|
// DeepSeek
|
||||||
"deepseek/deepseek-chat",
|
"deepseek/deepseek-v4-pro",
|
||||||
"deepseek/deepseek-r1",
|
"deepseek/deepseek-v3.2",
|
||||||
// Qwen
|
// Qwen
|
||||||
"qwen/qwen-2.5-72b-instruct",
|
"qwen/qwen3.7-max",
|
||||||
|
"qwen/qwen3-coder",
|
||||||
|
// MiniMax
|
||||||
|
"minimax/minimax-m3",
|
||||||
|
],
|
||||||
|
deepseek: [
|
||||||
|
"deepseek-v4-pro",
|
||||||
|
"deepseek-v4-flash",
|
||||||
|
"deepseek-chat",
|
||||||
|
"deepseek-reasoner",
|
||||||
],
|
],
|
||||||
deepseek: ["deepseek-chat", "deepseek-reasoner", "deepseek-coder"],
|
|
||||||
siliconflow: [
|
siliconflow: [
|
||||||
// DeepSeek
|
// DeepSeek
|
||||||
"deepseek-ai/DeepSeek-V3",
|
"deepseek-ai/DeepSeek-V4-Pro",
|
||||||
"deepseek-ai/DeepSeek-R1",
|
"deepseek-ai/DeepSeek-V4-Flash",
|
||||||
"deepseek-ai/DeepSeek-V2.5",
|
"deepseek-ai/DeepSeek-V3.2",
|
||||||
|
// MiniMax
|
||||||
|
"MiniMaxAI/MiniMax-M3",
|
||||||
|
// Moonshot
|
||||||
|
"moonshotai/Kimi-K2.6",
|
||||||
|
// Z.ai
|
||||||
|
"zai-org/GLM-5",
|
||||||
// Qwen
|
// Qwen
|
||||||
"Qwen/Qwen2.5-72B-Instruct",
|
"Qwen/Qwen3.6-35B-A3B",
|
||||||
"Qwen/Qwen2.5-32B-Instruct",
|
"Qwen/Qwen3-Coder-480B-A35B-Instruct",
|
||||||
"Qwen/Qwen2.5-Coder-32B-Instruct",
|
"Qwen/Qwen3-30B-A3B-Instruct-2507",
|
||||||
"Qwen/Qwen2.5-7B-Instruct",
|
"Qwen/Qwen3-VL-32B-Instruct",
|
||||||
"Qwen/Qwen2-VL-72B-Instruct",
|
// OpenAI open-weights
|
||||||
"qwen3.5-plus",
|
"openai/gpt-oss-120b",
|
||||||
],
|
],
|
||||||
sglang: [
|
sglang: [
|
||||||
// SGLang is OpenAI-compatible, models depend on deployment
|
// SGLang is OpenAI-compatible, models depend on deployment
|
||||||
"default",
|
"default",
|
||||||
],
|
],
|
||||||
gateway: [
|
gateway: [
|
||||||
"openai/gpt-4o",
|
"openai/gpt-5.5",
|
||||||
"openai/gpt-4o-mini",
|
"anthropic/claude-opus-4.7",
|
||||||
"anthropic/claude-sonnet-4-5",
|
"google/gemini-3.1-pro-preview",
|
||||||
"anthropic/claude-3-5-sonnet",
|
"xai/grok-4.3",
|
||||||
"google/gemini-2.0-flash",
|
"anthropic/claude-sonnet-4.6",
|
||||||
|
"anthropic/claude-haiku-4.5",
|
||||||
|
"openai/gpt-5.4-mini",
|
||||||
],
|
],
|
||||||
edgeone: ["@tx/deepseek-ai/deepseek-v32"],
|
edgeone: ["@tx/deepseek-ai/deepseek-v32"],
|
||||||
doubao: [
|
doubao: [
|
||||||
// ByteDance Doubao models
|
// ByteDance Doubao models (Volcengine Ark IDs use dash form)
|
||||||
"doubao-1.5-thinking-pro-250415",
|
"doubao-seed-2-0-pro-260215",
|
||||||
"doubao-1.5-thinking-pro-m-250428",
|
"doubao-seed-2-0-lite-260428",
|
||||||
"doubao-1.5-pro-32k-250115",
|
"doubao-seed-2-0-mini-260428",
|
||||||
"doubao-1.5-pro-256k-250115",
|
"doubao-seed-1-8-251228",
|
||||||
"doubao-pro-32k-241215",
|
"doubao-seed-1-6-251015",
|
||||||
"doubao-pro-256k-241215",
|
"doubao-seed-1-6-flash-250828",
|
||||||
|
"doubao-seed-1-6-vision-250815",
|
||||||
|
"doubao-1-5-pro-32k-250115",
|
||||||
|
"doubao-1-5-lite-32k-250115",
|
||||||
],
|
],
|
||||||
modelscope: [
|
modelscope: [
|
||||||
|
// DeepSeek
|
||||||
|
"deepseek-ai/DeepSeek-V4-Pro",
|
||||||
|
"deepseek-ai/DeepSeek-V3.2",
|
||||||
|
"deepseek-ai/DeepSeek-R1-0528",
|
||||||
|
"deepseek-ai/DeepSeek-R1",
|
||||||
// Qwen
|
// Qwen
|
||||||
"Qwen/Qwen2.5-72B-Instruct",
|
|
||||||
"Qwen/Qwen2.5-32B-Instruct",
|
|
||||||
"Qwen/Qwen3-235B-A22B-Instruct-2507",
|
"Qwen/Qwen3-235B-A22B-Instruct-2507",
|
||||||
"Qwen/Qwen3-VL-235B-A22B-Instruct",
|
"Qwen/Qwen3-VL-235B-A22B-Instruct",
|
||||||
|
"Qwen/Qwen3-Coder-30B-A3B-Instruct",
|
||||||
"Qwen/Qwen3-32B",
|
"Qwen/Qwen3-32B",
|
||||||
"qwen3.5-plus",
|
"Qwen/Qwen2.5-72B-Instruct",
|
||||||
// DeepSeek
|
|
||||||
"deepseek-ai/DeepSeek-R1-0528",
|
|
||||||
"deepseek-ai/DeepSeek-V3.2",
|
|
||||||
],
|
],
|
||||||
minimax: [
|
minimax: [
|
||||||
// MiniMax models (Anthropic-compatible API)
|
// MiniMax models (Anthropic-compatible API)
|
||||||
|
"MiniMax-M3",
|
||||||
"MiniMax-M2.7",
|
"MiniMax-M2.7",
|
||||||
"MiniMax-M2.7-highspeed",
|
"MiniMax-M2.7-highspeed",
|
||||||
"MiniMax-M2.5",
|
"MiniMax-M2.5",
|
||||||
"MiniMax-M2.5-highspeed",
|
|
||||||
],
|
],
|
||||||
novita: [
|
novita: [
|
||||||
// Novita AI models (OpenAI-compatible API)
|
// Novita AI models (OpenAI-compatible API)
|
||||||
"moonshotai/kimi-k2.5",
|
"minimax/minimax-m3",
|
||||||
"zai-org/glm-5",
|
"deepseek/deepseek-v4-pro",
|
||||||
"minimax/minimax-m2.5",
|
"zai-org/glm-5.1",
|
||||||
|
"moonshotai/kimi-k2.6",
|
||||||
|
"deepseek/deepseek-v4-flash",
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
123
package-lock.json
generated
123
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "next-ai-draw-io",
|
"name": "next-ai-draw-io",
|
||||||
"version": "0.4.15",
|
"version": "0.4.16",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "next-ai-draw-io",
|
"name": "next-ai-draw-io",
|
||||||
"version": "0.4.15",
|
"version": "0.4.16",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/amazon-bedrock": "^4.0.1",
|
"@ai-sdk/amazon-bedrock": "^4.0.1",
|
||||||
@@ -122,14 +122,15 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@ai-sdk/amazon-bedrock": {
|
"node_modules/@ai-sdk/amazon-bedrock": {
|
||||||
"version": "4.0.64",
|
"version": "4.0.113",
|
||||||
"resolved": "https://registry.npmjs.org/@ai-sdk/amazon-bedrock/-/amazon-bedrock-4.0.64.tgz",
|
"resolved": "https://registry.npmjs.org/@ai-sdk/amazon-bedrock/-/amazon-bedrock-4.0.113.tgz",
|
||||||
"integrity": "sha512-foZskBdIHPGPZLfEKqkOP18JgfMy5Duik1TTduvEQ97mX3yfEzr4go1V3oU7ktry0dAL5BiNY38WwRhl+xhy9g==",
|
"integrity": "sha512-qoeF2ghkYqHY4u68rasZopYsRuGnRwgqSfe6rhC/jM6W73Z7TU5v9QcfDYKt9dgp19YHY9EqiX3SXVC+uPGkRQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/anthropic": "3.0.47",
|
"@ai-sdk/anthropic": "3.0.81",
|
||||||
"@ai-sdk/provider": "3.0.8",
|
"@ai-sdk/openai": "3.0.68",
|
||||||
"@ai-sdk/provider-utils": "4.0.15",
|
"@ai-sdk/provider": "3.0.10",
|
||||||
|
"@ai-sdk/provider-utils": "4.0.27",
|
||||||
"@smithy/eventstream-codec": "^4.0.1",
|
"@smithy/eventstream-codec": "^4.0.1",
|
||||||
"@smithy/util-utf8": "^4.0.0",
|
"@smithy/util-utf8": "^4.0.0",
|
||||||
"aws4fetch": "^1.0.20"
|
"aws4fetch": "^1.0.20"
|
||||||
@@ -141,6 +142,67 @@
|
|||||||
"zod": "^3.25.76 || ^4.1.8"
|
"zod": "^3.25.76 || ^4.1.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@ai-sdk/amazon-bedrock/node_modules/@ai-sdk/anthropic": {
|
||||||
|
"version": "3.0.81",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-3.0.81.tgz",
|
||||||
|
"integrity": "sha512-B1JDd9Ugq9R5AgIaW3674lhGCMMYJcPUxnrZh8fzbGojgg4QvHFRv6eZahGQAUsmGHbcf74G9bdSBDLWQGY2GA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@ai-sdk/provider": "3.0.10",
|
||||||
|
"@ai-sdk/provider-utils": "4.0.27"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"zod": "^3.25.76 || ^4.1.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ai-sdk/amazon-bedrock/node_modules/@ai-sdk/openai": {
|
||||||
|
"version": "3.0.68",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-3.0.68.tgz",
|
||||||
|
"integrity": "sha512-FCs/DPr4M95UyZ/ABHJmTmCEYRCka/4J0Bna0nsd78QCdGIS0X/zhn+fVzB7mZJo7464uOWYUjROx9PGNGOb0w==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@ai-sdk/provider": "3.0.10",
|
||||||
|
"@ai-sdk/provider-utils": "4.0.27"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"zod": "^3.25.76 || ^4.1.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ai-sdk/amazon-bedrock/node_modules/@ai-sdk/provider": {
|
||||||
|
"version": "3.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.10.tgz",
|
||||||
|
"integrity": "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"json-schema": "^0.4.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ai-sdk/amazon-bedrock/node_modules/@ai-sdk/provider-utils": {
|
||||||
|
"version": "4.0.27",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.27.tgz",
|
||||||
|
"integrity": "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@ai-sdk/provider": "3.0.10",
|
||||||
|
"@standard-schema/spec": "^1.1.0",
|
||||||
|
"eventsource-parser": "^3.0.8"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"zod": "^3.25.76 || ^4.1.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@ai-sdk/anthropic": {
|
"node_modules/@ai-sdk/anthropic": {
|
||||||
"version": "3.0.47",
|
"version": "3.0.47",
|
||||||
"resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-3.0.47.tgz",
|
"resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-3.0.47.tgz",
|
||||||
@@ -2243,9 +2305,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2263,9 +2322,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2283,9 +2339,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2303,9 +2356,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -4898,9 +4948,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -4917,9 +4964,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -4936,9 +4980,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -4955,9 +4996,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -8304,9 +8342,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -13936,9 +13971,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/eventsource-parser": {
|
"node_modules/eventsource-parser": {
|
||||||
"version": "3.0.6",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz",
|
||||||
"integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==",
|
"integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
@@ -16613,9 +16648,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -16799,9 +16831,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -16822,9 +16851,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -16845,9 +16871,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|||||||
@@ -180,11 +180,14 @@ describe("supportsImageInput", () => {
|
|||||||
expect(supportsImageInput("moonshot-v1-128k")).toBe(false)
|
expect(supportsImageInput("moonshot-v1-128k")).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("returns false for MiniMax text models", () => {
|
it("returns false for MiniMax M2 text models", () => {
|
||||||
expect(supportsImageInput("MiniMax-M2.7")).toBe(false)
|
expect(supportsImageInput("MiniMax-M2.7")).toBe(false)
|
||||||
expect(supportsImageInput("MiniMax-M2.5")).toBe(false)
|
expect(supportsImageInput("MiniMax-M2.7-highspeed")).toBe(false)
|
||||||
expect(supportsImageInput("MiniMax-M2")).toBe(false)
|
expect(supportsImageInput("MiniMax-M2")).toBe(false)
|
||||||
expect(supportsImageInput("MiniMax-M2.5-highspeed")).toBe(false)
|
})
|
||||||
|
|
||||||
|
it("returns true for MiniMax M3 (supports image input)", () => {
|
||||||
|
expect(supportsImageInput("MiniMax-M3")).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("returns false for DeepSeek text models", () => {
|
it("returns false for DeepSeek text models", () => {
|
||||||
|
|||||||
21
tests/unit/ssrf-protection.test.ts
Normal file
21
tests/unit/ssrf-protection.test.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { describe, expect, it } from "vitest"
|
||||||
|
import { isPrivateUrl } from "@/lib/ssrf-protection"
|
||||||
|
|
||||||
|
describe("isPrivateUrl", () => {
|
||||||
|
it("blocks private IPv6 URLs", () => {
|
||||||
|
expect(isPrivateUrl("http://[::1]/")).toBe(true)
|
||||||
|
expect(isPrivateUrl("http://[0:0:0:0:0:0:0:1]/")).toBe(true)
|
||||||
|
expect(isPrivateUrl("http://[::]/")).toBe(true)
|
||||||
|
expect(isPrivateUrl("http://[::ffff:127.0.0.1]/")).toBe(true)
|
||||||
|
expect(isPrivateUrl("http://[fc00::1]/")).toBe(true)
|
||||||
|
expect(isPrivateUrl("http://[fd12:3456:789a::1]/")).toBe(true)
|
||||||
|
expect(isPrivateUrl("http://[fe80::1]/")).toBe(true)
|
||||||
|
expect(isPrivateUrl("http://[fe9f::1]/")).toBe(true)
|
||||||
|
expect(isPrivateUrl("http://[febf::1]/")).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("allows public URLs", () => {
|
||||||
|
expect(isPrivateUrl("https://example.com/article")).toBe(false)
|
||||||
|
expect(isPrivateUrl("https://fc00.example.com/article")).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user