feat: expand DeepSeek Harness compatibility
This commit is contained in:
+6
-21
@@ -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)
|
||||
)
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user