coding-tool-x 3.7.10 → 3.7.12
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/CHANGELOG.md +3 -0
- package/README.md +1 -1
- package/dist/web/assets/{Analytics-CkUtnoz9.js → Analytics-8dRv1aYW.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-BwPqhE-N.js → ConfigTemplates-BQRXSICO.js} +1 -1
- package/dist/web/assets/Home-BtzWhKIa.js +1 -0
- package/dist/web/assets/Home-DYVy4cGZ.css +1 -0
- package/dist/web/assets/{PluginManager-JMXWl37b.js → PluginManager-BTa-Ygf6.js} +1 -1
- package/dist/web/assets/{ProjectList-CZwDgVvt.js → ProjectList-F_cB8ldA.js} +1 -1
- package/dist/web/assets/{SessionList-2fZmN7Au.js → SessionList-LOrbPQvu.js} +1 -1
- package/dist/web/assets/SkillManager-BsX8Qpj0.css +1 -0
- package/dist/web/assets/SkillManager-BzI_t-yV.js +1 -0
- package/dist/web/assets/{WorkspaceManager-B3lhhBD0.js → WorkspaceManager-Db9RGiz7.js} +1 -1
- package/dist/web/assets/index-DIWJmyL6.js +2 -0
- package/dist/web/assets/{index-C5nLTbOk.css → index-bJRG3RXH.css} +1 -1
- package/dist/web/index.html +2 -2
- package/docs/superpowers/plans/2026-08-01-omp-skill-scan-settings.md +759 -0
- package/docs/superpowers/specs/2026-08-01-omp-skill-scan-settings-design.md +224 -0
- package/package.json +1 -1
- package/src/commands/proxy-control.js +12 -12
- package/src/commands/toggle-proxy.js +16 -17
- package/src/commands/ui.js +11 -1
- package/src/config/default.js +1 -1
- package/src/config/paths.js +52 -29
- package/src/index.js +42 -16
- package/src/server/api/omp-channels.js +6 -4
- package/src/server/api/omp-config.js +1 -1
- package/src/server/api/omp-proxy.js +13 -42
- package/src/server/api/plugins.js +119 -47
- package/src/server/api/skills.js +63 -10
- package/src/server/index.js +3 -3
- package/src/server/omp-proxy-server.js +142 -10
- package/src/server/services/channel-scheduler.js +59 -9
- package/src/server/services/model-definition-schema.js +2 -1
- package/src/server/services/omp-auth-gateway-client.js +62 -0
- package/src/server/services/omp-channels.js +168 -12
- package/src/server/services/omp-config.js +71 -6
- package/src/server/services/omp-gateway-routing.js +295 -0
- package/src/server/services/omp-gateway.js +919 -0
- package/src/server/services/omp-native-plugin-adapter.js +456 -0
- package/src/server/services/omp-session-log-observer.js +138 -0
- package/src/server/services/omp-sessions.js +80 -0
- package/src/server/services/omp-settings-manager.js +338 -87
- package/src/server/services/omp-skill-discovery.js +358 -0
- package/src/server/services/omp-skill-settings-service.js +97 -0
- package/src/server/services/pi-omp-migration.js +273 -0
- package/src/server/services/platform-resolution.js +44 -0
- package/src/server/services/plugins-service.js +85 -561
- package/src/server/services/project-path-validation.js +65 -0
- package/src/server/services/skill-service.js +169 -77
- package/dist/web/assets/Home-BANl91zG.js +0 -1
- package/dist/web/assets/Home-CqnBXFxj.css +0 -1
- package/dist/web/assets/SkillManager-Bhzhkeis.js +0 -1
- package/dist/web/assets/SkillManager-D7pd-d_P.css +0 -1
- package/dist/web/assets/index-LYvv7wtx.js +0 -2
|
@@ -5,6 +5,11 @@ const yaml = require('js-yaml');
|
|
|
5
5
|
const { HOME_DIR, PATHS = {} } = require('../../config/paths');
|
|
6
6
|
|
|
7
7
|
const DEFAULT_OMP_COMMAND = 'omp';
|
|
8
|
+
const YAML_DUMP_OPTIONS = Object.freeze({
|
|
9
|
+
lineWidth: 120,
|
|
10
|
+
noRefs: true,
|
|
11
|
+
sortKeys: false
|
|
12
|
+
});
|
|
8
13
|
|
|
9
14
|
function expandHome(input = '') {
|
|
10
15
|
const value = String(input || '').trim();
|
|
@@ -43,7 +48,9 @@ function getDefaultOmpAgentDir(env = process.env) {
|
|
|
43
48
|
}
|
|
44
49
|
|
|
45
50
|
function getFallbackOmpAgentDir(runtime, env = process.env) {
|
|
46
|
-
const configured = expandHome(
|
|
51
|
+
const configured = expandHome(
|
|
52
|
+
env.PI_CODING_AGENT_DIR || env.OMP_CODING_AGENT_DIR || ''
|
|
53
|
+
);
|
|
47
54
|
if (configured) return configured;
|
|
48
55
|
return getDefaultOmpAgentDir(env);
|
|
49
56
|
}
|
|
@@ -121,6 +128,14 @@ function ensureOmpDir(dirPath) {
|
|
|
121
128
|
}
|
|
122
129
|
}
|
|
123
130
|
|
|
131
|
+
function isPlainObject(value) {
|
|
132
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
const prototype = Object.getPrototypeOf(value);
|
|
136
|
+
return prototype === Object.prototype || prototype === null;
|
|
137
|
+
}
|
|
138
|
+
|
|
124
139
|
function readJsonFile(filePath, fallback = {}) {
|
|
125
140
|
try {
|
|
126
141
|
if (!fs.existsSync(filePath)) return fallback;
|
|
@@ -150,11 +165,7 @@ function readYamlFile(filePath, fallback = {}) {
|
|
|
150
165
|
|
|
151
166
|
function writeYamlFile(filePath, data) {
|
|
152
167
|
ensureOmpDir(path.dirname(filePath));
|
|
153
|
-
fs.writeFileSync(filePath, yaml.dump(data || {},
|
|
154
|
-
lineWidth: 120,
|
|
155
|
-
noRefs: true,
|
|
156
|
-
sortKeys: false
|
|
157
|
-
}), 'utf8');
|
|
168
|
+
fs.writeFileSync(filePath, yaml.dump(data || {}, YAML_DUMP_OPTIONS), 'utf8');
|
|
158
169
|
}
|
|
159
170
|
|
|
160
171
|
function readOmpSettings() {
|
|
@@ -166,6 +177,58 @@ function writeOmpSettings(settings) {
|
|
|
166
177
|
writeYamlFile(getOmpPaths().settings, settings || {});
|
|
167
178
|
}
|
|
168
179
|
|
|
180
|
+
function readOmpSettingsStrict() {
|
|
181
|
+
const filePath = getOmpPaths().settings;
|
|
182
|
+
let source;
|
|
183
|
+
try {
|
|
184
|
+
source = fs.readFileSync(filePath, 'utf8');
|
|
185
|
+
} catch (error) {
|
|
186
|
+
if (error?.code === 'ENOENT') return {};
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const settings = yaml.load(source);
|
|
191
|
+
if (!isPlainObject(settings)) {
|
|
192
|
+
throw new Error('Invalid OMP config');
|
|
193
|
+
}
|
|
194
|
+
return settings;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function writeOmpSettingsAtomic(settings) {
|
|
198
|
+
const filePath = getOmpPaths().settings;
|
|
199
|
+
const directory = path.dirname(filePath);
|
|
200
|
+
ensureOmpDir(directory);
|
|
201
|
+
let mode = 0o600;
|
|
202
|
+
try {
|
|
203
|
+
mode = fs.statSync(filePath).mode & 0o777;
|
|
204
|
+
} catch (error) {
|
|
205
|
+
if (error?.code !== 'ENOENT') throw error;
|
|
206
|
+
}
|
|
207
|
+
const temporaryPath = path.join(
|
|
208
|
+
directory,
|
|
209
|
+
`.${path.basename(filePath)}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
fs.writeFileSync(temporaryPath, yaml.dump(settings, YAML_DUMP_OPTIONS), {
|
|
214
|
+
encoding: 'utf8',
|
|
215
|
+
flag: 'wx',
|
|
216
|
+
mode
|
|
217
|
+
});
|
|
218
|
+
fs.chmodSync(temporaryPath, mode);
|
|
219
|
+
fs.renameSync(temporaryPath, filePath);
|
|
220
|
+
} catch (error) {
|
|
221
|
+
try {
|
|
222
|
+
if (fs.existsSync(temporaryPath)) {
|
|
223
|
+
fs.unlinkSync(temporaryPath);
|
|
224
|
+
}
|
|
225
|
+
} catch {
|
|
226
|
+
// Preserve the original write, chmod, or rename failure.
|
|
227
|
+
}
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
169
232
|
function getOmpCommand(env = process.env) {
|
|
170
233
|
return String(env.OMP_COMMAND || DEFAULT_OMP_COMMAND).trim() || DEFAULT_OMP_COMMAND;
|
|
171
234
|
}
|
|
@@ -286,6 +349,8 @@ module.exports = {
|
|
|
286
349
|
writeYamlFile,
|
|
287
350
|
readOmpSettings,
|
|
288
351
|
writeOmpSettings,
|
|
352
|
+
readOmpSettingsStrict,
|
|
353
|
+
writeOmpSettingsAtomic,
|
|
289
354
|
getOmpCommand,
|
|
290
355
|
getOmpProfile,
|
|
291
356
|
resolveOmpRuntime,
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
|
|
5
|
+
const MANAGED_PREFIX = 'ctx-';
|
|
6
|
+
const SUPPORTED_PROVIDER_APIS = new Set([
|
|
7
|
+
'openai-completions',
|
|
8
|
+
'openai-responses',
|
|
9
|
+
'openai-codex-responses',
|
|
10
|
+
'azure-openai-responses',
|
|
11
|
+
'anthropic-messages',
|
|
12
|
+
'google-generative-ai',
|
|
13
|
+
'google-gemini-cli',
|
|
14
|
+
'google-vertex'
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
function normalizeText(value) {
|
|
18
|
+
return String(value || '').trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function normalizeProviderId(value) {
|
|
22
|
+
return normalizeText(value)
|
|
23
|
+
.toLowerCase()
|
|
24
|
+
.replace(/[^a-z0-9._-]+/g, '-')
|
|
25
|
+
.replace(/^-+|-+$/g, '') || 'provider';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getProviderKey(channel = {}) {
|
|
29
|
+
return normalizeText(channel.providerKey || channel.provider || channel.name || channel.id);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getProviderApi(channel = {}) {
|
|
33
|
+
return normalizeText(channel.providerApi || channel.api || channel.wireApi || 'openai-completions');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getRoutingGroup(channel = {}) {
|
|
37
|
+
return normalizeText(channel.routingGroup) || normalizeText(channel.id);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function stableToken(secret, namespace, value, length = 24) {
|
|
41
|
+
return crypto
|
|
42
|
+
.createHmac('sha256', String(secret || ''))
|
|
43
|
+
.update(`${namespace}\0${value}`)
|
|
44
|
+
.digest('hex')
|
|
45
|
+
.slice(0, length);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function stablePublicId(namespace, value, length = 8) {
|
|
49
|
+
return crypto
|
|
50
|
+
.createHash('sha256')
|
|
51
|
+
.update(`${namespace}\0${value}`)
|
|
52
|
+
.digest('hex')
|
|
53
|
+
.slice(0, length);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function normalizeRedirects(channel = {}) {
|
|
57
|
+
const redirects = Array.isArray(channel.modelRedirects) ? channel.modelRedirects : [];
|
|
58
|
+
return JSON.stringify(redirects);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function canonicalize(value) {
|
|
62
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
63
|
+
if (value && typeof value === 'object') {
|
|
64
|
+
return Object.fromEntries(
|
|
65
|
+
Object.keys(value).sort().map(key => [key, canonicalize(value[key])])
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getModelCapabilityMap(channel = {}) {
|
|
72
|
+
const result = new Map();
|
|
73
|
+
const add = (raw) => {
|
|
74
|
+
const model = typeof raw === 'string' ? { id: raw } : raw;
|
|
75
|
+
const id = normalizeText(model?.id || model?.name);
|
|
76
|
+
if (!id || result.has(id)) return;
|
|
77
|
+
const capability = { ...(model || {}) };
|
|
78
|
+
delete capability.id;
|
|
79
|
+
delete capability.name;
|
|
80
|
+
delete capability.baseUrl;
|
|
81
|
+
delete capability.headers;
|
|
82
|
+
result.set(id, JSON.stringify(canonicalize(capability)));
|
|
83
|
+
};
|
|
84
|
+
(Array.isArray(channel.models) ? channel.models : []).forEach(add);
|
|
85
|
+
(Array.isArray(channel.allowedModels) ? channel.allowedModels : []).forEach(add);
|
|
86
|
+
add(channel.model);
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function getGroupKey(channel = {}) {
|
|
91
|
+
return [
|
|
92
|
+
getProviderKey(channel),
|
|
93
|
+
getProviderApi(channel),
|
|
94
|
+
getRoutingGroup(channel)
|
|
95
|
+
].join('\0');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function assertCompatibleGroup(channels = []) {
|
|
99
|
+
if (channels.length <= 1) return;
|
|
100
|
+
const first = channels[0];
|
|
101
|
+
const expectedApi = getProviderApi(first);
|
|
102
|
+
const expectedProvider = getProviderKey(first);
|
|
103
|
+
const expectedRedirects = normalizeRedirects(first);
|
|
104
|
+
const expectedAuthMode = normalizeText(first.authMode) || 'api_key';
|
|
105
|
+
const expectedCapabilities = getModelCapabilityMap(first);
|
|
106
|
+
|
|
107
|
+
channels.slice(1).forEach((channel) => {
|
|
108
|
+
if (getProviderKey(channel) !== expectedProvider) {
|
|
109
|
+
throw new Error(`OMP routing group "${getRoutingGroup(first)}" mixes providerKey values`);
|
|
110
|
+
}
|
|
111
|
+
if (getProviderApi(channel) !== expectedApi) {
|
|
112
|
+
throw new Error(`OMP routing group "${getRoutingGroup(first)}" mixes providerApi values`);
|
|
113
|
+
}
|
|
114
|
+
if (normalizeRedirects(channel) !== expectedRedirects) {
|
|
115
|
+
throw new Error(`OMP routing group "${getRoutingGroup(first)}" has inconsistent modelRedirects`);
|
|
116
|
+
}
|
|
117
|
+
if ((normalizeText(channel.authMode) || 'api_key') !== expectedAuthMode) {
|
|
118
|
+
throw new Error(`OMP routing group "${getRoutingGroup(first)}" mixes authMode values`);
|
|
119
|
+
}
|
|
120
|
+
const actualCapabilities = getModelCapabilityMap(channel);
|
|
121
|
+
expectedCapabilities.forEach((capability, modelId) => {
|
|
122
|
+
if (actualCapabilities.has(modelId) && actualCapabilities.get(modelId) !== capability) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
`OMP routing group "${getRoutingGroup(first)}" has incompatible capability metadata for model "${modelId}"`
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function sanitizeProviderConfig(channel = {}) {
|
|
132
|
+
const source = channel.providerConfig && typeof channel.providerConfig === 'object'
|
|
133
|
+
&& !Array.isArray(channel.providerConfig)
|
|
134
|
+
? channel.providerConfig
|
|
135
|
+
: {};
|
|
136
|
+
const next = { ...source };
|
|
137
|
+
for (const key of ['baseUrl', 'base_url', 'apiKey', 'api_key', 'headers', 'auth']) {
|
|
138
|
+
delete next[key];
|
|
139
|
+
}
|
|
140
|
+
return next;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function sanitizeModels(channel = {}) {
|
|
144
|
+
return (Array.isArray(channel.models) ? channel.models : []).map((model) => {
|
|
145
|
+
if (!model || typeof model !== 'object' || Array.isArray(model)) return model;
|
|
146
|
+
const next = { ...model };
|
|
147
|
+
delete next.baseUrl;
|
|
148
|
+
delete next.headers;
|
|
149
|
+
return next;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function mergeGroupField(channels = [], field) {
|
|
154
|
+
const seen = new Set();
|
|
155
|
+
const result = [];
|
|
156
|
+
channels.forEach((channel) => {
|
|
157
|
+
const values = Array.isArray(channel?.[field]) ? channel[field] : [];
|
|
158
|
+
values.forEach((value) => {
|
|
159
|
+
const id = normalizeText(typeof value === 'string' ? value : (value?.id || value?.name));
|
|
160
|
+
if (!id || seen.has(id.toLowerCase())) return;
|
|
161
|
+
seen.add(id.toLowerCase());
|
|
162
|
+
result.push(value);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function buildManagedBaseUrl(host, port, routeToken) {
|
|
169
|
+
return `http://${host}:${port}/omp/${routeToken}`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function prepareManagedOmpChannels(channels = [], gateway = {}) {
|
|
173
|
+
const host = normalizeText(gateway.host) || '127.0.0.1';
|
|
174
|
+
const port = Number(gateway.port);
|
|
175
|
+
const secret = normalizeText(gateway.secret);
|
|
176
|
+
if (!Number.isInteger(port) || port <= 0) {
|
|
177
|
+
throw new Error('OMP gateway port is required before managed providers can be generated');
|
|
178
|
+
}
|
|
179
|
+
if (!secret) {
|
|
180
|
+
throw new Error('OMP gateway secret is required before managed providers can be generated');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const enabled = channels.filter(channel => channel && channel.enabled !== false && channel.baseUrl);
|
|
184
|
+
const groups = new Map();
|
|
185
|
+
enabled.forEach((channel) => {
|
|
186
|
+
const key = getGroupKey(channel);
|
|
187
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
188
|
+
groups.get(key).push(channel);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const providerCounts = new Map();
|
|
192
|
+
groups.forEach((groupChannels) => {
|
|
193
|
+
const providerId = normalizeProviderId(getProviderKey(groupChannels[0]));
|
|
194
|
+
providerCounts.set(providerId, (providerCounts.get(providerId) || 0) + 1);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const routes = [];
|
|
198
|
+
const managedChannels = [];
|
|
199
|
+
const unsupportedChannels = [];
|
|
200
|
+
|
|
201
|
+
groups.forEach((groupChannels, groupKey) => {
|
|
202
|
+
assertCompatibleGroup(groupChannels);
|
|
203
|
+
const template = groupChannels[0];
|
|
204
|
+
if (!SUPPORTED_PROVIDER_APIS.has(getProviderApi(template))) {
|
|
205
|
+
groupChannels.forEach(channel => unsupportedChannels.push({
|
|
206
|
+
id: channel.id,
|
|
207
|
+
name: channel.name,
|
|
208
|
+
reason: 'unsupported-provider-api'
|
|
209
|
+
}));
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const authMode = normalizeText(template.authMode) || 'api_key';
|
|
213
|
+
const transport = normalizeText(template.transport || template.providerConfig?.transport);
|
|
214
|
+
const supportedOAuthIds = Array.isArray(gateway.supportedOAuthChannelIds)
|
|
215
|
+
? new Set(gateway.supportedOAuthChannelIds)
|
|
216
|
+
: null;
|
|
217
|
+
const oauthGatewayUnavailable = authMode === 'oauth' && (
|
|
218
|
+
!normalizeText(template.apiKey)
|
|
219
|
+
|| transport !== 'pi-native'
|
|
220
|
+
|| (supportedOAuthIds && groupChannels.some(channel => !supportedOAuthIds.has(channel.id)))
|
|
221
|
+
);
|
|
222
|
+
if (oauthGatewayUnavailable) {
|
|
223
|
+
groupChannels.forEach(channel => unsupportedChannels.push({
|
|
224
|
+
id: channel.id,
|
|
225
|
+
name: channel.name,
|
|
226
|
+
reason: 'oauth-auth-gateway-unavailable'
|
|
227
|
+
}));
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const providerId = normalizeProviderId(getProviderKey(template));
|
|
232
|
+
const routeToken = stableToken(secret, 'route', groupKey);
|
|
233
|
+
const capability = `ctx_${stableToken(secret, 'capability', groupKey, 40)}`;
|
|
234
|
+
const multipleGroups = providerCounts.get(providerId) > 1;
|
|
235
|
+
const managedProviderId = multipleGroups
|
|
236
|
+
? `${MANAGED_PREFIX}${providerId}-${stablePublicId('provider', groupKey)}`
|
|
237
|
+
: `${MANAGED_PREFIX}${providerId}`;
|
|
238
|
+
const routingGroup = getRoutingGroup(template);
|
|
239
|
+
const mergedModels = sanitizeModels({
|
|
240
|
+
models: mergeGroupField(groupChannels, 'models')
|
|
241
|
+
});
|
|
242
|
+
const mergedAllowedModels = mergeGroupField(groupChannels, 'allowedModels');
|
|
243
|
+
|
|
244
|
+
const route = {
|
|
245
|
+
token: routeToken,
|
|
246
|
+
capability,
|
|
247
|
+
routingGroup,
|
|
248
|
+
providerKey: getProviderKey(template),
|
|
249
|
+
providerApi: getProviderApi(template),
|
|
250
|
+
managedProviderId,
|
|
251
|
+
channelIds: groupChannels.map(channel => channel.id),
|
|
252
|
+
authMode
|
|
253
|
+
};
|
|
254
|
+
routes.push(route);
|
|
255
|
+
|
|
256
|
+
managedChannels.push({
|
|
257
|
+
...template,
|
|
258
|
+
id: `omp-route-${routeToken}`,
|
|
259
|
+
managedProviderId,
|
|
260
|
+
originalProviderId: getProviderKey(template),
|
|
261
|
+
routingGroup,
|
|
262
|
+
baseUrl: buildManagedBaseUrl(host, port, routeToken),
|
|
263
|
+
apiKey: authMode === 'none' ? '' : capability,
|
|
264
|
+
authMode,
|
|
265
|
+
providerConfig: sanitizeProviderConfig(template),
|
|
266
|
+
headers: undefined,
|
|
267
|
+
auth: authMode === 'none' ? 'none' : undefined,
|
|
268
|
+
models: mergedModels,
|
|
269
|
+
allowedModels: mergedAllowedModels,
|
|
270
|
+
_ompGatewayRoute: route
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
return { routes, managedChannels, unsupportedChannels };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function resolveOmpGatewayRoute(pathname, channels, gateway) {
|
|
278
|
+
const match = String(pathname || '').match(/^\/omp\/([a-f0-9]{24})(\/.*|$)/);
|
|
279
|
+
if (!match) return null;
|
|
280
|
+
const prepared = prepareManagedOmpChannels(channels, gateway);
|
|
281
|
+
const route = prepared.routes.find(item => item.token === match[1]);
|
|
282
|
+
if (!route) return null;
|
|
283
|
+
return {
|
|
284
|
+
...route,
|
|
285
|
+
upstreamPath: match[2] || '/'
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
module.exports = {
|
|
290
|
+
prepareManagedOmpChannels,
|
|
291
|
+
resolveOmpGatewayRoute,
|
|
292
|
+
getRoutingGroup,
|
|
293
|
+
getProviderKey,
|
|
294
|
+
getProviderApi
|
|
295
|
+
};
|