feat: expand DeepSeek Harness compatibility

This commit is contained in:
lofyer
2026-08-14 12:38:01 +08:00
parent 36e05d45fa
commit 45aeecb6dd
32 changed files with 554 additions and 193 deletions
+6 -21
View File
@@ -121,6 +121,11 @@ import {
agentRuntimeSelectionSchema,
type AgentRuntimeSelection
} from './runtime-selection-contracts'
import { isDeepSeekHarnessModelProfile } from './deepseek-harness-compatibility'
export {
isDeepSeekHarnessCompatibleBaseUrl,
isDeepSeekHarnessModelProfile
} from './deepseek-harness-compatibility'
export const workspaceRelativePathSchema = z
.string()
@@ -392,26 +397,6 @@ export const runtimeModelSourceSchema = z.discriminatedUnion('kind', [
.strict()
])
export function isDeepSeekHarnessModelProfile(
profile: Pick<
ModelConnectionSettings,
'baseUrl' | 'protocol' | 'authentication'
>
): boolean {
if (
profile.protocol !== 'openai-chat-completions' ||
profile.authentication !== 'api-key'
) {
return false
}
try {
return new URL(profile.baseUrl).hostname.toLowerCase() ===
'api.deepseek.com'
} catch {
return false
}
}
export const runtimeSettingsInputSchema = z
.object({
provider: runtimeProviderSchema,
@@ -590,7 +575,7 @@ export const runtimeSettingsInputSchema = z
code: 'custom',
path: ['deepseekHarnessModelSource'],
message:
'DeepSeek Harness 独立模型连接仅支持 api.deepseek.com 的 OpenAI Chat Completions 协议'
'DeepSeek Harness 仅支持使用 API Key 的安全 OpenAI 兼容 Chat Completions 连接'
})
}
}
@@ -0,0 +1,52 @@
import { describe, expect, it } from 'vitest'
import {
isDeepSeekHarnessCompatibleBaseUrl,
isDeepSeekHarnessModelProfile
} from './deepseek-harness-compatibility'
describe('DeepSeek Harness model compatibility', () => {
it.each([
'https://api.deepseek.com',
'https://gateway.example/openai/v1',
'https://gateway.example:8443/v1',
'http://127.0.0.1:11434/v1',
'http://localhost:1234/v1',
'http://[::1]:8080/v1'
])('accepts OpenAI-compatible base URL %s', (baseUrl) => {
expect(isDeepSeekHarnessCompatibleBaseUrl(baseUrl)).toBe(true)
expect(
isDeepSeekHarnessModelProfile({
baseUrl,
protocol: 'openai-chat-completions',
authentication: 'api-key'
})
).toBe(true)
})
it.each([
'http://gateway.example/v1',
'file:///private/config',
'https://user:secret@gateway.example/v1',
'https://gateway.example/v1?api-version=2025-01-01',
'https://gateway.example/v1#chat'
])('rejects unsafe or unsupported base URL %s', (baseUrl) => {
expect(isDeepSeekHarnessCompatibleBaseUrl(baseUrl)).toBe(false)
})
it('rejects other protocols and unauthenticated profiles', () => {
expect(
isDeepSeekHarnessModelProfile({
baseUrl: 'https://gateway.example/v1',
protocol: 'openai-responses',
authentication: 'api-key'
})
).toBe(false)
expect(
isDeepSeekHarnessModelProfile({
baseUrl: 'https://gateway.example/v1',
protocol: 'openai-chat-completions',
authentication: 'none'
})
).toBe(false)
})
})
@@ -0,0 +1,50 @@
type DeepSeekHarnessModelProfile = {
baseUrl: string
protocol: string
authentication: string
}
function isLoopbackHostname(hostname: string): boolean {
const normalized = hostname
.toLowerCase()
.replace(/^\[|\]$/gu, '')
return (
normalized === 'localhost' ||
normalized === '127.0.0.1' ||
normalized === '::1'
)
}
export function isDeepSeekHarnessCompatibleBaseUrl(
value: string
): boolean {
try {
const url = new URL(value)
if (
!url.hostname ||
url.username ||
url.password ||
url.search ||
url.hash
) {
return false
}
return (
url.protocol === 'https:' ||
(url.protocol === 'http:' &&
isLoopbackHostname(url.hostname))
)
} catch {
return false
}
}
export function isDeepSeekHarnessModelProfile(
profile: DeepSeekHarnessModelProfile
): boolean {
return (
profile.protocol === 'openai-chat-completions' &&
profile.authentication === 'api-key' &&
isDeepSeekHarnessCompatibleBaseUrl(profile.baseUrl)
)
}
+6 -6
View File
@@ -1,4 +1,5 @@
import { z } from 'zod'
import { isDeepSeekHarnessModelProfile } from './deepseek-harness-compatibility'
const runtimeSelectionProfileIdSchema = z.string().uuid()
@@ -82,12 +83,11 @@ function isDeepSeekHarnessRepairProfileUsable(
) {
return false
}
try {
return new URL(profile.baseUrl).hostname.toLowerCase() ===
'api.deepseek.com'
} catch {
return false
}
return isDeepSeekHarnessModelProfile({
baseUrl: profile.baseUrl,
protocol: profile.protocol,
authentication: profile.authentication
})
}
export function repairChannelRuntimeSelection(