@galaxy-yearn/codex-deepseek-gateway 0.1.0
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.md +207 -0
- package/bin/codex-deepseek-gateway.js +375 -0
- package/config/gateway.example.json +12 -0
- package/config/model-aliases.example.json +10 -0
- package/package.json +34 -0
- package/src/codex-config.js +83 -0
- package/src/common.js +141 -0
- package/src/config.js +31 -0
- package/src/local-config.js +51 -0
- package/src/model-map.js +152 -0
- package/src/protocol.js +1740 -0
- package/src/server.js +350 -0
- package/src/session-store.js +67 -0
- package/src/upstream.js +155 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
function unquoteTomlString(value) {
|
|
5
|
+
const trimmed = String(value || '').trim();
|
|
6
|
+
if (
|
|
7
|
+
(trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
8
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'"))
|
|
9
|
+
) {
|
|
10
|
+
return trimmed.slice(1, -1);
|
|
11
|
+
}
|
|
12
|
+
return trimmed;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function stripInlineTomlComment(value) {
|
|
16
|
+
let quote = '';
|
|
17
|
+
let escaped = false;
|
|
18
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
19
|
+
const char = value[index];
|
|
20
|
+
if (quote) {
|
|
21
|
+
if (quote === '"' && char === '\\' && !escaped) {
|
|
22
|
+
escaped = true;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (char === quote && !escaped) {
|
|
26
|
+
quote = '';
|
|
27
|
+
}
|
|
28
|
+
escaped = false;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (char === '"' || char === "'") {
|
|
32
|
+
quote = char;
|
|
33
|
+
escaped = false;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (char === '#' && (index === 0 || /\s/.test(value[index - 1]))) {
|
|
37
|
+
return value.slice(0, index).trimEnd();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return value.trimEnd();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function defaultCodexConfigPath(env = process.env) {
|
|
44
|
+
if (env.CODEX_CONFIG_FILE) return resolve(env.CODEX_CONFIG_FILE);
|
|
45
|
+
const home = env.CODEX_HOME || join(env.USERPROFILE || env.HOME || '', '.codex');
|
|
46
|
+
return join(home, 'config.toml');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function readCodexConfig(env = process.env) {
|
|
50
|
+
const filePath = defaultCodexConfigPath(env);
|
|
51
|
+
const result = {
|
|
52
|
+
modelProvider: '',
|
|
53
|
+
model: '',
|
|
54
|
+
modelReasoningEffort: '',
|
|
55
|
+
modelReasoningSummary: '',
|
|
56
|
+
modelSupportsReasoningSummaries: '',
|
|
57
|
+
hideAgentReasoning: '',
|
|
58
|
+
};
|
|
59
|
+
if (!filePath || !existsSync(filePath)) return result;
|
|
60
|
+
|
|
61
|
+
const text = readFileSync(filePath, 'utf8');
|
|
62
|
+
let inTopLevel = true;
|
|
63
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
64
|
+
const line = rawLine.trim();
|
|
65
|
+
if (!line || line.startsWith('#')) continue;
|
|
66
|
+
if (line.startsWith('[')) {
|
|
67
|
+
inTopLevel = false;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (!inTopLevel) continue;
|
|
71
|
+
const match = line.match(/^([A-Za-z0-9_.-]+)\s*=\s*(.+)$/);
|
|
72
|
+
if (!match) continue;
|
|
73
|
+
const [, key, rawValue] = match;
|
|
74
|
+
const value = unquoteTomlString(stripInlineTomlComment(rawValue));
|
|
75
|
+
if (key === 'model_provider') result.modelProvider = value;
|
|
76
|
+
if (key === 'model') result.model = value;
|
|
77
|
+
if (key === 'model_reasoning_effort') result.modelReasoningEffort = value;
|
|
78
|
+
if (key === 'model_reasoning_summary') result.modelReasoningSummary = value;
|
|
79
|
+
if (key === 'model_supports_reasoning_summaries') result.modelSupportsReasoningSummaries = value;
|
|
80
|
+
if (key === 'hide_agent_reasoning') result.hideAgentReasoning = value;
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
}
|
package/src/common.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
export function isObject(value) {
|
|
4
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function generateId(prefix) {
|
|
8
|
+
return `${prefix}_${randomUUID().replaceAll('-', '')}`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function normalizeRole(role, provider = 'generic') {
|
|
12
|
+
if (role === 'developer') return 'system';
|
|
13
|
+
return role || 'user';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function toText(content) {
|
|
17
|
+
if (content == null) return '';
|
|
18
|
+
if (typeof content === 'string') return content;
|
|
19
|
+
if (Array.isArray(content)) {
|
|
20
|
+
return content
|
|
21
|
+
.map((part) => {
|
|
22
|
+
if (typeof part === 'string') return part;
|
|
23
|
+
if (!isObject(part)) return '';
|
|
24
|
+
if (part.type === 'input_text' || part.type === 'output_text' || part.type === 'text') {
|
|
25
|
+
return String(part.text ?? part.content ?? '');
|
|
26
|
+
}
|
|
27
|
+
if (typeof part.text === 'string') return part.text;
|
|
28
|
+
if (part.type === 'message') return toText(part.content);
|
|
29
|
+
return '';
|
|
30
|
+
})
|
|
31
|
+
.filter(Boolean)
|
|
32
|
+
.join('');
|
|
33
|
+
}
|
|
34
|
+
if (isObject(content)) {
|
|
35
|
+
if (content.type === 'message') return toText(content.content);
|
|
36
|
+
if (content.type === 'input_text' || content.type === 'output_text' || content.type === 'text') {
|
|
37
|
+
return String(content.text ?? content.content ?? '');
|
|
38
|
+
}
|
|
39
|
+
if (typeof content.text === 'string') return content.text;
|
|
40
|
+
}
|
|
41
|
+
return '';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function parseBoolean(value, defaultValue = false) {
|
|
45
|
+
if (value == null || value === '') return defaultValue;
|
|
46
|
+
if (typeof value === 'boolean') return value;
|
|
47
|
+
const normalized = String(value).trim().toLowerCase();
|
|
48
|
+
if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;
|
|
49
|
+
if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
|
|
50
|
+
return defaultValue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function parseList(value) {
|
|
54
|
+
if (Array.isArray(value)) return value.map((item) => String(item).trim()).filter(Boolean);
|
|
55
|
+
if (value == null || value === '') return [];
|
|
56
|
+
return String(value)
|
|
57
|
+
.split(',')
|
|
58
|
+
.map((item) => item.trim())
|
|
59
|
+
.filter(Boolean);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function mapDeepSeekReasoningEffort(effort) {
|
|
63
|
+
if (!effort) return undefined;
|
|
64
|
+
const normalized = String(effort).toLowerCase().replaceAll('_', '-');
|
|
65
|
+
if (normalized === 'low') return undefined;
|
|
66
|
+
if (normalized === 'none' || normalized === 'disabled' || normalized === 'off') return undefined;
|
|
67
|
+
if (normalized === 'xhigh' || normalized === 'max') return 'max';
|
|
68
|
+
if (normalized === 'medium' || normalized === 'high') return 'high';
|
|
69
|
+
return normalized;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function joinUrl(baseUrl, path) {
|
|
73
|
+
const base = String(baseUrl || '').replace(/\/+$/, '');
|
|
74
|
+
const suffix = String(path || '').startsWith('/') ? String(path || '') : `/${String(path || '')}`;
|
|
75
|
+
return `${base}${suffix}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export class SseParser {
|
|
79
|
+
constructor() {
|
|
80
|
+
this.decoder = new TextDecoder();
|
|
81
|
+
this.buffer = '';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
push(chunk) {
|
|
85
|
+
this.buffer += this.decoder.decode(chunk, { stream: true });
|
|
86
|
+
this.buffer = this.buffer.replace(/\r\n/g, '\n');
|
|
87
|
+
return this.drain(false);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
end() {
|
|
91
|
+
this.buffer += this.decoder.decode();
|
|
92
|
+
this.buffer = this.buffer.replace(/\r\n/g, '\n');
|
|
93
|
+
return this.drain(true);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
drain(flush) {
|
|
97
|
+
const events = [];
|
|
98
|
+
while (true) {
|
|
99
|
+
const boundary = this.buffer.indexOf('\n\n');
|
|
100
|
+
if (boundary < 0) break;
|
|
101
|
+
const frame = this.buffer.slice(0, boundary);
|
|
102
|
+
this.buffer = this.buffer.slice(boundary + 2);
|
|
103
|
+
const parsed = this.parseFrame(frame);
|
|
104
|
+
if (parsed) events.push(parsed);
|
|
105
|
+
}
|
|
106
|
+
if (flush && this.buffer.trim()) {
|
|
107
|
+
const parsed = this.parseFrame(this.buffer);
|
|
108
|
+
if (parsed) events.push(parsed);
|
|
109
|
+
this.buffer = '';
|
|
110
|
+
}
|
|
111
|
+
return events;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
parseFrame(frame) {
|
|
115
|
+
const lines = String(frame).replace(/\r\n/g, '\n').split('\n');
|
|
116
|
+
let event = 'message';
|
|
117
|
+
const dataLines = [];
|
|
118
|
+
for (const line of lines) {
|
|
119
|
+
if (!line) continue;
|
|
120
|
+
if (line.startsWith('event:')) {
|
|
121
|
+
event = line.slice(6).trim();
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (line.startsWith('data:')) {
|
|
125
|
+
dataLines.push(line.slice(5).replace(/^\s/, ''));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (dataLines.length === 0) return null;
|
|
129
|
+
const data = dataLines.join('\n');
|
|
130
|
+
if (data === '[DONE]') return { done: true };
|
|
131
|
+
return { event, data };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function safeJsonParse(text) {
|
|
136
|
+
try {
|
|
137
|
+
return { ok: true, value: JSON.parse(text) };
|
|
138
|
+
} catch (error) {
|
|
139
|
+
return { ok: false, error };
|
|
140
|
+
}
|
|
141
|
+
}
|
package/src/config.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { loadModelAliases } from './model-map.js';
|
|
2
|
+
import { mergeLocalConfig } from './local-config.js';
|
|
3
|
+
import { parseBoolean, parseList } from './common.js';
|
|
4
|
+
import { readCodexConfig } from './codex-config.js';
|
|
5
|
+
|
|
6
|
+
export function loadConfig(env = process.env) {
|
|
7
|
+
const mergedEnv = mergeLocalConfig(env);
|
|
8
|
+
const codexConfig = readCodexConfig(mergedEnv);
|
|
9
|
+
return {
|
|
10
|
+
port: Number(mergedEnv.PORT || 3000),
|
|
11
|
+
host: mergedEnv.HOST || '127.0.0.1',
|
|
12
|
+
upstreamBaseUrl: mergedEnv.UPSTREAM_BASE_URL || 'https://api.deepseek.com',
|
|
13
|
+
upstreamApiKey: mergedEnv.UPSTREAM_API_KEY || mergedEnv.DEEPSEEK_API_KEY || '',
|
|
14
|
+
upstreamModel: mergedEnv.UPSTREAM_MODEL || '',
|
|
15
|
+
upstreamProvider: mergedEnv.UPSTREAM_PROVIDER || 'deepseek',
|
|
16
|
+
upstreamTimeoutMs: Number(mergedEnv.UPSTREAM_TIMEOUT_MS || 120000),
|
|
17
|
+
upstreamModels: parseList(mergedEnv.UPSTREAM_MODELS || mergedEnv.MODELS),
|
|
18
|
+
fetchUpstreamModels: parseBoolean(mergedEnv.FETCH_UPSTREAM_MODELS, false),
|
|
19
|
+
modelsTimeoutMs: Number(mergedEnv.MODELS_TIMEOUT_MS || 5000),
|
|
20
|
+
modelsCacheMs: Number(mergedEnv.MODELS_CACHE_MS || 60000),
|
|
21
|
+
proxyApiKey: mergedEnv.PROXY_API_KEY || '',
|
|
22
|
+
debugPayload: parseBoolean(mergedEnv.DEBUG_PAYLOAD || mergedEnv.DEBUG_DEEPSEEK_PAYLOAD, false),
|
|
23
|
+
codexModelProvider: codexConfig.modelProvider,
|
|
24
|
+
codexModel: codexConfig.model,
|
|
25
|
+
codexReasoningEffort: mergedEnv.CODEX_REASONING_EFFORT || mergedEnv.MODEL_REASONING_EFFORT || codexConfig.modelReasoningEffort,
|
|
26
|
+
codexReasoningSummary: codexConfig.modelReasoningSummary,
|
|
27
|
+
codexModelSupportsReasoningSummaries: codexConfig.modelSupportsReasoningSummaries,
|
|
28
|
+
codexHideAgentReasoning: codexConfig.hideAgentReasoning,
|
|
29
|
+
modelAliases: loadModelAliases(mergedEnv),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { isObject, safeJsonParse } from './common.js';
|
|
5
|
+
|
|
6
|
+
const PROJECT_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
7
|
+
|
|
8
|
+
function normalizeEnvName(key) {
|
|
9
|
+
return String(key)
|
|
10
|
+
.replace(/[A-Z]/g, (letter) => `_${letter}`)
|
|
11
|
+
.replace(/[-.\s]+/g, '_')
|
|
12
|
+
.replace(/^_+/, '')
|
|
13
|
+
.toUpperCase();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function flattenConfig(value, prefix = '') {
|
|
17
|
+
const entries = {};
|
|
18
|
+
if (!isObject(value)) return entries;
|
|
19
|
+
for (const [key, child] of Object.entries(value)) {
|
|
20
|
+
const childKey = prefix ? `${prefix}_${normalizeEnvName(key)}` : normalizeEnvName(key);
|
|
21
|
+
if (isObject(child)) {
|
|
22
|
+
Object.assign(entries, flattenConfig(child, childKey));
|
|
23
|
+
} else if (child !== undefined && child !== null) {
|
|
24
|
+
entries[childKey] = String(child);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return entries;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function readLocalConfigFile(path = resolve(PROJECT_ROOT, 'config', 'gateway.local.json')) {
|
|
31
|
+
if (!path || !existsSync(path)) return {};
|
|
32
|
+
const parsed = safeJsonParse(readFileSync(path, 'utf8'));
|
|
33
|
+
if (!parsed.ok || !isObject(parsed.value)) {
|
|
34
|
+
throw new Error(`${path} must contain a JSON object`);
|
|
35
|
+
}
|
|
36
|
+
return flattenConfig(parsed.value);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function mergeLocalConfig(env = process.env, cwd = PROJECT_ROOT) {
|
|
40
|
+
const configPath = env.GATEWAY_CONFIG_FILE
|
|
41
|
+
? resolve(cwd, env.GATEWAY_CONFIG_FILE)
|
|
42
|
+
: resolve(cwd, 'config', 'gateway.local.json');
|
|
43
|
+
const fileConfig = readLocalConfigFile(configPath);
|
|
44
|
+
if (fileConfig.UPSTREAM_API_KEY === 'sk-REPLACE_ME') {
|
|
45
|
+
delete fileConfig.UPSTREAM_API_KEY;
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
...fileConfig,
|
|
49
|
+
...env,
|
|
50
|
+
};
|
|
51
|
+
}
|
package/src/model-map.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { isObject, mapDeepSeekReasoningEffort } from './common.js';
|
|
4
|
+
|
|
5
|
+
const DEEPSEEK_V4_MODELS = ['deepseek-v4-flash', 'deepseek-v4-pro'];
|
|
6
|
+
const DEPRECATED_MODEL_PATTERN = /^deepseek-(?:chat|reasoner)$/;
|
|
7
|
+
|
|
8
|
+
function deepseekV4Aliases() {
|
|
9
|
+
const aliases = {};
|
|
10
|
+
for (const model of DEEPSEEK_V4_MODELS) {
|
|
11
|
+
aliases[model] = { model, thinking: 'auto' };
|
|
12
|
+
}
|
|
13
|
+
return aliases;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const DEFAULT_MODEL_ALIASES = deepseekV4Aliases();
|
|
17
|
+
|
|
18
|
+
function parseJsonObject(value, source) {
|
|
19
|
+
if (!value) return {};
|
|
20
|
+
const parsed = JSON.parse(value);
|
|
21
|
+
if (!isObject(parsed)) {
|
|
22
|
+
throw new Error(`${source} must be a JSON object`);
|
|
23
|
+
}
|
|
24
|
+
return parsed;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function readJsonFile(filePath) {
|
|
28
|
+
if (!filePath || !existsSync(filePath)) return {};
|
|
29
|
+
return parseJsonObject(readFileSync(filePath, 'utf8'), filePath);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function loadModelAliases(env = process.env, cwd = process.cwd()) {
|
|
33
|
+
const defaultFile = resolve(cwd, 'config', 'model-aliases.json');
|
|
34
|
+
const filePath = env.MODEL_ALIASES_FILE ? resolve(cwd, env.MODEL_ALIASES_FILE) : defaultFile;
|
|
35
|
+
const fileAliases = readJsonFile(filePath);
|
|
36
|
+
const envAliases = parseJsonObject(env.MODEL_ALIASES_JSON, 'MODEL_ALIASES_JSON');
|
|
37
|
+
return {
|
|
38
|
+
...DEFAULT_MODEL_ALIASES,
|
|
39
|
+
...fileAliases,
|
|
40
|
+
...envAliases,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalizeThinking(value) {
|
|
45
|
+
if (value === true) return 'enabled';
|
|
46
|
+
if (value === false) return 'disabled';
|
|
47
|
+
if (value == null) return 'auto';
|
|
48
|
+
const normalized = String(value).toLowerCase().replaceAll('_', '-');
|
|
49
|
+
if (normalized === 'on' || normalized === 'true' || normalized === 'yes') return 'enabled';
|
|
50
|
+
if (normalized === 'off' || normalized === 'false' || normalized === 'no') return 'disabled';
|
|
51
|
+
if (normalized === 'non-thinking' || normalized === 'no-thinking') return 'disabled';
|
|
52
|
+
if (normalized === 'thinking') return 'enabled';
|
|
53
|
+
return normalized;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function normalizeAlias(alias, aliasName) {
|
|
57
|
+
if (typeof alias === 'string') return { alias: aliasName, upstreamModel: alias, thinking: 'auto', extraBody: {} };
|
|
58
|
+
if (!isObject(alias)) return null;
|
|
59
|
+
return {
|
|
60
|
+
alias: aliasName,
|
|
61
|
+
upstreamModel: alias.upstreamModel || alias.upstream_model || alias.model || aliasName,
|
|
62
|
+
thinking: normalizeThinking(alias.thinking ?? alias.thinking_mode ?? alias.thinkingMode),
|
|
63
|
+
reasoningEffort: alias.reasoning_effort ?? alias.reasoningEffort ?? alias.effort,
|
|
64
|
+
extraBody: isObject(alias.extra_body) ? alias.extra_body : isObject(alias.extraBody) ? alias.extraBody : {},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function resolveModelAlias(requestedModel, config = {}) {
|
|
69
|
+
const model = requestedModel || config.upstreamModel || '';
|
|
70
|
+
const aliases = isObject(config.modelAliases) ? config.modelAliases : {};
|
|
71
|
+
const alias = normalizeAlias(aliases[model], model);
|
|
72
|
+
if (alias) return alias;
|
|
73
|
+
return {
|
|
74
|
+
alias: model,
|
|
75
|
+
upstreamModel: config.upstreamModel || model,
|
|
76
|
+
thinking: 'auto',
|
|
77
|
+
reasoningEffort: undefined,
|
|
78
|
+
extraBody: {},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function effortDisablesThinking(effort) {
|
|
83
|
+
if (effort == null) return false;
|
|
84
|
+
const normalized = String(effort).toLowerCase().replaceAll('_', '-');
|
|
85
|
+
return normalized === 'low' || normalized === 'none' || normalized === 'disabled' || normalized === 'off' || normalized === 'false';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function deepseekReasoningPayload({ alias, reasoning } = {}) {
|
|
89
|
+
const requestEffort = isObject(reasoning) ? reasoning.effort : undefined;
|
|
90
|
+
const aliasThinking = normalizeThinking(alias?.thinking);
|
|
91
|
+
const aliasEffort = alias?.reasoningEffort;
|
|
92
|
+
|
|
93
|
+
if (aliasThinking === 'disabled' || (aliasThinking === 'auto' && effortDisablesThinking(requestEffort))) {
|
|
94
|
+
return { thinking: { type: 'disabled' } };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const payload = {};
|
|
98
|
+
const effort = aliasEffort ?? (effortDisablesThinking(requestEffort) ? undefined : requestEffort);
|
|
99
|
+
if (aliasThinking === 'enabled' || effort) {
|
|
100
|
+
payload.thinking = { type: 'enabled' };
|
|
101
|
+
}
|
|
102
|
+
if (effort) {
|
|
103
|
+
const reasoningEffort = mapDeepSeekReasoningEffort(effort);
|
|
104
|
+
if (reasoningEffort) payload.reasoning_effort = reasoningEffort;
|
|
105
|
+
}
|
|
106
|
+
return payload;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function listModels(config = {}) {
|
|
110
|
+
const aliases = isObject(config.modelAliases) ? config.modelAliases : {};
|
|
111
|
+
const configuredModels = Array.isArray(config.upstreamModels) ? config.upstreamModels : [];
|
|
112
|
+
return [...new Set([...Object.keys(aliases), ...configuredModels])]
|
|
113
|
+
.filter((id) => id && !isDeprecatedModel(id))
|
|
114
|
+
.sort()
|
|
115
|
+
.map((id) => ({
|
|
116
|
+
id,
|
|
117
|
+
object: 'model',
|
|
118
|
+
created: 0,
|
|
119
|
+
owned_by: config.upstreamProvider || 'gateway',
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function isDeprecatedModel(model) {
|
|
124
|
+
return DEPRECATED_MODEL_PATTERN.test(String(model || '').trim());
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function normalizeModelList(payload, config = {}) {
|
|
128
|
+
const source = Array.isArray(payload?.data) ? payload.data : Array.isArray(payload) ? payload : [];
|
|
129
|
+
return source
|
|
130
|
+
.map((item) => {
|
|
131
|
+
if (typeof item === 'string') return { id: item, object: 'model', created: 0 };
|
|
132
|
+
if (!isObject(item) || !item.id) return null;
|
|
133
|
+
return {
|
|
134
|
+
...item,
|
|
135
|
+
object: item.object || 'model',
|
|
136
|
+
owned_by: item.owned_by || config.upstreamProvider || 'gateway',
|
|
137
|
+
};
|
|
138
|
+
})
|
|
139
|
+
.filter((item) => item?.id && !isDeprecatedModel(item.id));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function mergeModelLists(...lists) {
|
|
143
|
+
const byId = new Map();
|
|
144
|
+
for (const list of lists) {
|
|
145
|
+
for (const model of Array.isArray(list) ? list : []) {
|
|
146
|
+
if (model?.id && !isDeprecatedModel(model.id) && !byId.has(model.id)) {
|
|
147
|
+
byId.set(model.id, model);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return [...byId.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
152
|
+
}
|