@gehennawu/dsh-service 2.0.7 → 2.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +58 -10
- package/README.md +58 -10
- package/client.js +1 -1
- package/index.js +127 -7
- package/model-config-routes.js +633 -0
- package/model-config-schema.js +132 -0
- package/model-smart-fill.js +2206 -0
- package/model-workbench-routes.js +1740 -0
- package/model-workbench-settings.js +389 -0
- package/model-workbench.js +1618 -0
- package/package.json +10 -2
- package/provider-availability.js +84 -0
- package/subagent-routes.js +7 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// Editable vocabulary for pi-ai Profile configuration, not a projection of defaults.
|
|
2
|
+
export const MODEL_FIELDS = ['name', 'contextWindow', 'maxTokens', 'input', 'reasoningEfforts', 'compat']
|
|
3
|
+
export const ROUTE_FIELDS = ['displayName', 'api', 'baseURL', 'apiKeyEnv', 'defaultContextWindow', 'defaultMaxTokens', 'defaultInput', 'compat', 'headers', 'reasoning', 'thinkingBudgets', 'cacheRetention', 'transport', 'timeoutMs', 'websocketConnectTimeoutMs', 'streamIdleTimeoutMs', 'maxRequestImageBytes', 'requestImagePixelBudget', 'requestImageMaxBytes', 'retryPolicy']
|
|
4
|
+
export const LEVELS = ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max']
|
|
5
|
+
export const PROTOCOLS = ['openai-completions', 'openai-responses', 'anthropic-messages']
|
|
6
|
+
const completions = ['supportsStore', 'supportsDeveloperRole', 'supportsReasoningEffort', 'supportsUsageInStreaming', 'supportsFinishReason', 'maxTokensField', 'requiresToolResultName', 'requiresAssistantAfterToolResult', 'requiresThinkingAsText', 'requiresReasoningContentOnAssistantMessages', 'thinkingFormat', 'chatTemplateKwargs', 'chatTemplateArgs', 'supportsThinkingTokenBudget', 'thinkingTokenBudgetField', 'vllmPriority', 'supportsStrictMode', 'cacheControlFormat', 'supportsLongCacheRetention']
|
|
7
|
+
const responses = ['supportsDeveloperRole', 'supportsMaxOutputTokens', 'supportsStrictMode', 'supportsLongCacheRetention']
|
|
8
|
+
export const COMPAT_GATES = {
|
|
9
|
+
'openai-completions': completions,
|
|
10
|
+
'openai-responses': responses,
|
|
11
|
+
'azure-openai-responses': responses,
|
|
12
|
+
'openai-codex-responses': responses,
|
|
13
|
+
'anthropic-messages': ['supportsEagerToolInputStreaming', 'supportsLongCacheRetention', 'supportsCacheControlOnTools', 'supportsTemperature', 'forceAdaptiveThinking', 'allowEmptySignature', 'supportsStrictTools'],
|
|
14
|
+
'bedrock-converse-stream': ['supportsStrictMode'],
|
|
15
|
+
}
|
|
16
|
+
export const COMPAT_ENUMS = {
|
|
17
|
+
maxTokensField: ['max_completion_tokens', 'max_tokens'],
|
|
18
|
+
thinkingFormat: ['openai', 'deepseek', 'openrouter', 'together', 'baseten', 'zai', 'qwen', 'chat-template', 'qwen-chat-template', 'string-thinking', 'ant-ling'],
|
|
19
|
+
thinkingTokenBudgetField: ['thinking_token_budget', 'thinking_budget', 'thinking_budget_tokens'],
|
|
20
|
+
cacheControlFormat: ['anthropic'],
|
|
21
|
+
}
|
|
22
|
+
export const ROUTE_ENUMS = { api: PROTOCOLS, reasoning: LEVELS, cacheRetention: ['none', 'short', 'long'], transport: ['sse', 'websocket', 'websocket-cached', 'auto'] }
|
|
23
|
+
export const object = (value) => value !== null && typeof value === 'object' && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype
|
|
24
|
+
const reserved = new Set(['__proto__', 'prototype', 'constructor', '__jsExpr'])
|
|
25
|
+
const fail = (code = 'schema-invalid') => { throw new Error(code) }
|
|
26
|
+
const positive = (n) => Number.isSafeInteger(n) && n > 0
|
|
27
|
+
const timer = (n) => Number.isFinite(n) && n > 0 && n <= 2147483647
|
|
28
|
+
|
|
29
|
+
// Header names use the RFC token alphabet. Normalize punctuation as well as case
|
|
30
|
+
// so credential-like variants cannot evade masking by spelling differences.
|
|
31
|
+
export function sensitiveHeader(name) {
|
|
32
|
+
const normalized = String(name).toLowerCase().replace(/[^a-z0-9]/g, '')
|
|
33
|
+
return /authorization|cookie|apikey|token|secret|credential|password|signature/.test(normalized)
|
|
34
|
+
}
|
|
35
|
+
export function safeHeaderName(name) {
|
|
36
|
+
return typeof name === 'string' && /^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,128}$/.test(name) && !reserved.has(name)
|
|
37
|
+
}
|
|
38
|
+
export function containsSensitive(value) {
|
|
39
|
+
if (Array.isArray(value)) return value.some(containsSensitive)
|
|
40
|
+
if (!object(value)) return false
|
|
41
|
+
return Object.entries(value).some(([key, child]) => reserved.has(key) || (key !== 'apiKeyEnv' && /secret|password|credential|authorization|cookie|token|apikey/i.test(key) && !['maxTokens', 'defaultMaxTokens', 'maxTokensField', 'supportsMaxOutputTokens', 'supportsThinkingTokenBudget', 'thinkingTokenBudgetField'].includes(key) && !(/^(input|output|maxOutput|prompt|completion|cached|total)Tokens$/i.test(key) && typeof child === 'number' && Number.isFinite(child))) || (key === 'headers' && object(child) && Object.keys(child).some(sensitiveHeader)) || containsSensitive(child))
|
|
42
|
+
}
|
|
43
|
+
export function displayProfile(raw) {
|
|
44
|
+
if (!object(raw)) return {}
|
|
45
|
+
const result = {}
|
|
46
|
+
for (const key of ROUTE_FIELDS) {
|
|
47
|
+
if (raw[key] === undefined) continue
|
|
48
|
+
if (key === 'headers') {
|
|
49
|
+
result.headers = Object.fromEntries(Object.entries(raw.headers ?? {}).filter(([name]) => safeHeaderName(name) && !sensitiveHeader(name)))
|
|
50
|
+
result.maskedHeaders = Object.keys(raw.headers ?? {}).filter(sensitiveHeader).map(() => '••••')
|
|
51
|
+
} else if (key === 'compat' || key === 'thinkingBudgets' || key === 'retryPolicy') {
|
|
52
|
+
result[key] = containsSensitive(raw[key]) ? {} : structuredClone(raw[key])
|
|
53
|
+
} else result[key] = structuredClone(raw[key])
|
|
54
|
+
}
|
|
55
|
+
result.models = (Array.isArray(raw.models) ? raw.models : []).map((model) => {
|
|
56
|
+
const row = { id: model.id }
|
|
57
|
+
for (const key of MODEL_FIELDS) if (model[key] !== undefined && !containsSensitive(model[key])) row[key] = structuredClone(model[key])
|
|
58
|
+
return row
|
|
59
|
+
})
|
|
60
|
+
result.hasModelOverrides = object(raw.modelOverrides) && Object.keys(raw.modelOverrides).length > 0
|
|
61
|
+
return result
|
|
62
|
+
}
|
|
63
|
+
function validateTemplate(values) {
|
|
64
|
+
if (!object(values)) fail()
|
|
65
|
+
for (const [key, value] of Object.entries(values)) {
|
|
66
|
+
if (reserved.has(key) || key.length > 128) fail()
|
|
67
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean' || typeof value === 'number' && Number.isFinite(value)) continue
|
|
68
|
+
if (!object(value) || Object.keys(value).some((k) => !['$var', 'omitWhenOff'].includes(k)) || !['thinking.enabled', 'thinking.effort', 'thinking.budget'].includes(value.$var) || value.omitWhenOff !== undefined && typeof value.omitWhenOff !== 'boolean') fail()
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export function validateCompat(value, api, previous = {}) {
|
|
72
|
+
if (!object(value)) fail()
|
|
73
|
+
for (const [key, val] of Object.entries(value)) {
|
|
74
|
+
// Untouched unknown keys remain available in the raw baseline, never editors.
|
|
75
|
+
if (JSON.stringify(previous?.[key]) === JSON.stringify(val)) continue
|
|
76
|
+
if (!(COMPAT_GATES[api] ?? []).includes(key)) fail('compat-not-offered')
|
|
77
|
+
if (COMPAT_ENUMS[key]) { if (!COMPAT_ENUMS[key].includes(val)) fail(); continue }
|
|
78
|
+
if (key === 'vllmPriority') { if (!Number.isSafeInteger(val)) fail(); continue }
|
|
79
|
+
if (key === 'chatTemplateArgs' || key === 'chatTemplateKwargs') { validateTemplate(val); continue }
|
|
80
|
+
if (typeof val !== 'boolean') fail()
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export function validateField(field, value, { api, previous } = {}) {
|
|
84
|
+
if (value === undefined) return
|
|
85
|
+
if (ROUTE_ENUMS[field]) { if (!ROUTE_ENUMS[field].includes(value)) fail(); return }
|
|
86
|
+
if (field === 'name' || field === 'displayName') { if (typeof value !== 'string' || value.trim() === '' || value.length > 128) fail(); return }
|
|
87
|
+
if (field === 'baseURL') {
|
|
88
|
+
if (typeof value !== 'string' || value.length > 2048) fail('bad-base-url')
|
|
89
|
+
let url; try { url = new URL(value) } catch (_) { fail('bad-base-url') }
|
|
90
|
+
if (!['http:', 'https:'].includes(url.protocol) || !url.hostname || url.username || url.password || url.hash) fail('bad-base-url')
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
if (field === 'apiKeyEnv') { if (typeof value !== 'string' || !/^[A-Za-z_][A-Za-z0-9_-]{0,127}$/.test(value)) fail('bad-key-ref'); return }
|
|
94
|
+
if (['contextWindow', 'maxTokens', 'defaultContextWindow', 'defaultMaxTokens', 'maxRequestImageBytes', 'requestImagePixelBudget', 'requestImageMaxBytes'].includes(field)) { if (!positive(value)) fail(); return }
|
|
95
|
+
if (field === 'timeoutMs' || field === 'websocketConnectTimeoutMs') { if (!Number.isSafeInteger(value) || value < 0 || value > 2147483647) fail(); return }
|
|
96
|
+
if (field === 'streamIdleTimeoutMs') { if (!timer(value)) fail(); return }
|
|
97
|
+
if (field === 'input' || field === 'defaultInput') { if (!Array.isArray(value) || value.length === 0 || value.some((x) => !['text', 'image'].includes(x)) || new Set(value).size !== value.length) fail(); return }
|
|
98
|
+
if (field === 'reasoningEfforts') {
|
|
99
|
+
if (value === false) return
|
|
100
|
+
if (!object(value) || Object.keys(value).some((k) => !LEVELS.includes(k)) || Object.entries(value).some(([k, v]) => !(k === 'off' && (v === null || v === '') || typeof v === 'string' && v.length > 0 && v.length <= 128)) || !Object.entries(value).some(([k, v]) => k !== 'off' && typeof v === 'string' && v.length > 0)) fail()
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
if (field === 'compat') { validateCompat(value, api, previous); return }
|
|
104
|
+
if (field === 'headers') {
|
|
105
|
+
if (!object(value) || Object.entries(value).some(([k, v]) => !safeHeaderName(k) || sensitiveHeader(k) || typeof v !== 'string' || v.length > 4096 || /[\r\n\0]/.test(v))) fail('sensitive-header')
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
if (field === 'thinkingBudgets') { if (!object(value) || Object.entries(value).some(([k, v]) => !['minimal', 'low', 'medium', 'high'].includes(k) || !Number.isFinite(v) || v < 0)) fail(); return }
|
|
109
|
+
if (field === 'retryPolicy') {
|
|
110
|
+
if (!object(value) || !['normal', 'always'].includes(value.mode) || Object.keys(value).some((k) => !['mode', 'maxRetries', 'retryableCodes', 'backoff'].includes(k))) fail()
|
|
111
|
+
if (value.maxRetries !== undefined && (!Number.isSafeInteger(value.maxRetries) || value.maxRetries < 0)) fail()
|
|
112
|
+
if (value.retryableCodes !== undefined && (!Array.isArray(value.retryableCodes) || !value.retryableCodes.length || new Set(value.retryableCodes).size !== value.retryableCodes.length || value.retryableCodes.some((x) => typeof x !== 'string' || !x || x.length > 128))) fail()
|
|
113
|
+
if (value.backoff !== undefined) {
|
|
114
|
+
const backoff = value.backoff
|
|
115
|
+
if (!object(backoff) || Object.keys(backoff).some((k) => !['initialDelayMs', 'maxDelayMs', 'jitterRatio'].includes(k)) || backoff.initialDelayMs !== undefined && !timer(backoff.initialDelayMs) || backoff.maxDelayMs !== undefined && !timer(backoff.maxDelayMs) || backoff.jitterRatio !== undefined && (!Number.isFinite(backoff.jitterRatio) || backoff.jitterRatio < 0 || backoff.jitterRatio > 1) || (backoff.initialDelayMs ?? 500) > (backoff.maxDelayMs ?? 10000)) fail()
|
|
116
|
+
}
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
fail('unknown-field')
|
|
120
|
+
}
|
|
121
|
+
export function patchFields(raw, patch, unset, fields, api) {
|
|
122
|
+
if (!object(patch) || !Array.isArray(unset) || unset.some((k) => !fields.includes(k)) || Object.keys(patch).some((k) => !fields.includes(k)) || unset.some((k) => Object.hasOwn(patch, k))) fail('unknown-field')
|
|
123
|
+
const next = structuredClone(raw)
|
|
124
|
+
for (const field of unset) delete next[field]
|
|
125
|
+
for (const [field, value] of Object.entries(patch)) {
|
|
126
|
+
validateField(field, value, { api, previous: raw[field] })
|
|
127
|
+
if (value === undefined) fail('schema-invalid')
|
|
128
|
+
next[field] = structuredClone(value)
|
|
129
|
+
}
|
|
130
|
+
return next
|
|
131
|
+
}
|
|
132
|
+
export const schemaVocabulary = () => ({ modelFields: MODEL_FIELDS, routeFields: ROUTE_FIELDS, levels: LEVELS, protocols: PROTOCOLS, compatGates: COMPAT_GATES, compatEnums: COMPAT_ENUMS, routeEnums: ROUTE_ENUMS })
|