@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.
@@ -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 })