Skip to content

Commit cc429d4

Browse files
committed
feat: add TypeScript types and modularize translator registry
- Add type annotations to all providerModels helper functions - Introduce LegacyProvider interface in providerRegistry - Refactor translator system to use self-registering module pattern with bootstrapTranslatorRegistry() and per-file imports - Simplify translator/index.ts by delegating to modular translators - Remove hardcoded Gemini OAuth client secret for security
1 parent 88ad4cc commit cc429d4

25 files changed

+584
-258
lines changed

open-sse/config/providerModels.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { generateModels, generateAliasMap } from "./providerRegistry.ts";
1+
import { generateModels, generateAliasMap, type RegistryModel } from "./providerRegistry.ts";
22

33
// Provider models - Generated from providerRegistry.js (single source of truth)
44
export const PROVIDER_MODELS = generateModels();
@@ -7,37 +7,41 @@ export const PROVIDER_MODELS = generateModels();
77
export const PROVIDER_ID_TO_ALIAS = generateAliasMap();
88

99
// Helper functions
10-
export function getProviderModels(aliasOrId) {
10+
export function getProviderModels(aliasOrId: string): RegistryModel[] {
1111
return PROVIDER_MODELS[aliasOrId] || [];
1212
}
1313

14-
export function getDefaultModel(aliasOrId) {
14+
export function getDefaultModel(aliasOrId: string): string | null {
1515
const models = PROVIDER_MODELS[aliasOrId];
1616
return models?.[0]?.id || null;
1717
}
1818

19-
export function isValidModel(aliasOrId, modelId, passthroughProviders = new Set()) {
19+
export function isValidModel(
20+
aliasOrId: string,
21+
modelId: string,
22+
passthroughProviders = new Set<string>()
23+
): boolean {
2024
if (passthroughProviders.has(aliasOrId)) return true;
2125
const models = PROVIDER_MODELS[aliasOrId];
2226
if (!models) return false;
2327
return models.some((m) => m.id === modelId);
2428
}
2529

26-
export function findModelName(aliasOrId, modelId) {
30+
export function findModelName(aliasOrId: string, modelId: string): string {
2731
const models = PROVIDER_MODELS[aliasOrId];
2832
if (!models) return modelId;
2933
const found = models.find((m) => m.id === modelId);
3034
return found?.name || modelId;
3135
}
3236

33-
export function getModelTargetFormat(aliasOrId, modelId) {
37+
export function getModelTargetFormat(aliasOrId: string, modelId: string): string | null {
3438
const models = PROVIDER_MODELS[aliasOrId];
3539
if (!models) return null;
3640
const found = models.find((m) => m.id === modelId);
3741
return found?.targetFormat || null;
3842
}
3943

40-
export function getModelsByProviderId(providerId) {
44+
export function getModelsByProviderId(providerId: string): RegistryModel[] {
4145
const alias = PROVIDER_ID_TO_ALIAS[providerId] || providerId;
4246
return PROVIDER_MODELS[alias] || [];
4347
}

open-sse/config/providerRegistry.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,21 @@ export interface RegistryEntry {
4949
passthroughModels?: boolean;
5050
}
5151

52+
interface LegacyProvider {
53+
format: string;
54+
baseUrl?: string;
55+
baseUrls?: string[];
56+
responsesBaseUrl?: string;
57+
headers?: Record<string, string>;
58+
clientId?: string;
59+
clientSecret?: string;
60+
tokenUrl?: string;
61+
refreshUrl?: string;
62+
authUrl?: string;
63+
chatPath?: string;
64+
clientVersion?: string;
65+
}
66+
5267
// ── Registry ──────────────────────────────────────────────────────────────
5368

5469
export const REGISTRY: Record<string, RegistryEntry> = {
@@ -108,7 +123,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
108123
clientIdEnv: "GEMINI_OAUTH_CLIENT_ID",
109124
clientIdDefault: "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com",
110125
clientSecretEnv: "GEMINI_OAUTH_CLIENT_SECRET",
111-
clientSecretDefault: "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",
126+
clientSecretDefault: "",
112127
},
113128
models: [
114129
{ id: "gemini-3.1-pro", name: "Gemini 3.1 Pro" },
@@ -137,7 +152,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
137152
clientIdEnv: "GEMINI_CLI_OAUTH_CLIENT_ID",
138153
clientIdDefault: "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com",
139154
clientSecretEnv: "GEMINI_CLI_OAUTH_CLIENT_SECRET",
140-
clientSecretDefault: "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",
155+
clientSecretDefault: "",
141156
},
142157
models: [
143158
{ id: "gemini-3.1-pro", name: "Gemini 3.1 Pro" },
@@ -228,7 +243,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
228243
clientIdEnv: "IFLOW_OAUTH_CLIENT_ID",
229244
clientIdDefault: "10009311001",
230245
clientSecretEnv: "IFLOW_OAUTH_CLIENT_SECRET",
231-
clientSecretDefault: "4Z3YjXycVsQvyGF1etiNlIBB4RsqSDtW",
246+
clientSecretDefault: "",
232247
tokenUrl: "https://iflow.cn/oauth/token",
233248
authUrl: "https://iflow.cn/oauth",
234249
},
@@ -266,7 +281,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
266281
clientIdEnv: "ANTIGRAVITY_OAUTH_CLIENT_ID",
267282
clientIdDefault: "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com",
268283
clientSecretEnv: "ANTIGRAVITY_OAUTH_CLIENT_SECRET",
269-
clientSecretDefault: "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",
284+
clientSecretDefault: "",
270285
},
271286
models: [
272287
{ id: "claude-opus-4-6-thinking", name: "Claude Opus 4.6 Thinking" },
@@ -844,10 +859,10 @@ export const REGISTRY: Record<string, RegistryEntry> = {
844859
// ── Generator Functions ───────────────────────────────────────────────────
845860

846861
/** Generate legacy PROVIDERS object shape for constants.js backward compatibility */
847-
export function generateLegacyProviders(): Record<string, any> {
848-
const providers: Record<string, any> = {};
862+
export function generateLegacyProviders(): Record<string, LegacyProvider> {
863+
const providers: Record<string, LegacyProvider> = {};
849864
for (const [id, entry] of Object.entries(REGISTRY)) {
850-
const p: Record<string, any> = { format: entry.format };
865+
const p: LegacyProvider = { format: entry.format };
851866

852867
// URL(s)
853868
if (entry.baseUrls) {
@@ -917,7 +932,7 @@ export function generateAliasMap(): Record<string, string> {
917932

918933
// ── Registry Lookup Helpers ───────────────────────────────────────────────
919934

920-
const _byAlias = new Map();
935+
const _byAlias = new Map<string, RegistryEntry>();
921936
for (const entry of Object.values(REGISTRY)) {
922937
if (entry.alias && entry.alias !== entry.id) {
923938
_byAlias.set(entry.alias, entry);

open-sse/handlers/chatCore.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ export async function handleChatCore({
283283
comboName,
284284
apiKeyId: apiKeyInfo?.id || null,
285285
apiKeyName: apiKeyInfo?.name || null,
286+
noLog: apiKeyInfo?.noLog === true,
286287
}).catch(() => {});
287288
if (error.name === "AbortError") {
288289
streamController.handleError(error);
@@ -298,11 +299,14 @@ export async function handleChatCore({
298299
providerResponse.status === HTTP_STATUS.UNAUTHORIZED ||
299300
providerResponse.status === HTTP_STATUS.FORBIDDEN
300301
) {
301-
const newCredentials = await refreshWithRetry(
302+
const newCredentials = (await refreshWithRetry(
302303
() => executor.refreshCredentials(credentials, log),
303304
3,
304305
log
305-
);
306+
)) as null | {
307+
accessToken?: string;
308+
copilotToken?: string;
309+
};
306310

307311
if (newCredentials?.accessToken || newCredentials?.copilotToken) {
308312
log?.info?.("TOKEN", `${provider.toUpperCase()} | refreshed`);
@@ -363,6 +367,7 @@ export async function handleChatCore({
363367
comboName,
364368
apiKeyId: apiKeyInfo?.id || null,
365369
apiKeyName: apiKeyInfo?.name || null,
370+
noLog: apiKeyInfo?.noLog === true,
366371
}).catch(() => {});
367372
const errMsg = formatProviderError(new Error(message), provider, model, statusCode);
368373
console.log(`${COLORS.red}[ERROR] ${errMsg}${COLORS.reset}`);
@@ -454,6 +459,7 @@ export async function handleChatCore({
454459
comboName,
455460
apiKeyId: apiKeyInfo?.id || null,
456461
apiKeyName: apiKeyInfo?.name || null,
462+
noLog: apiKeyInfo?.noLog === true,
457463
}).catch(() => {});
458464
if (usage && typeof usage === "object") {
459465
const msg = `[${new Date().toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit" })}] 📊 [USAGE] ${provider.toUpperCase()} | in=${usage?.prompt_tokens || 0} | out=${usage?.completion_tokens || 0}${connectionId ? ` | account=${connectionId.slice(0, 8)}...` : ""}`;
@@ -556,6 +562,7 @@ export async function handleChatCore({
556562
comboName,
557563
apiKeyId: apiKeyInfo?.id || null,
558564
apiKeyName: apiKeyInfo?.name || null,
565+
noLog: apiKeyInfo?.noLog === true,
559566
}).catch(() => {});
560567
};
561568

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { MCP_TOOL_MAP } from "./schemas/tools.ts";
2+
3+
type AuthInfoLike = {
4+
clientId?: string;
5+
scopes?: string[];
6+
};
7+
8+
export type McpToolExtraLike = {
9+
authInfo?: AuthInfoLike;
10+
sessionId?: string;
11+
_meta?: unknown;
12+
};
13+
14+
export type ScopeSource = "authInfo" | "meta" | "env" | "none";
15+
16+
export interface CallerScopeContext {
17+
callerId: string;
18+
scopes: string[];
19+
source: ScopeSource;
20+
}
21+
22+
export interface ScopeCheckResult {
23+
allowed: boolean;
24+
required: string[];
25+
provided: string[];
26+
missing: string[];
27+
reason?: string;
28+
}
29+
30+
function normalizeScopeList(raw: unknown): string[] {
31+
if (!Array.isArray(raw)) return [];
32+
const normalized = raw
33+
.filter((value) => typeof value === "string")
34+
.map((value) => value.trim())
35+
.filter(Boolean);
36+
return Array.from(new Set(normalized));
37+
}
38+
39+
function extractMetaScopeList(meta: unknown): string[] {
40+
if (!meta || typeof meta !== "object") return [];
41+
const metaRecord = meta as Record<string, unknown>;
42+
43+
const direct = normalizeScopeList(metaRecord.scopes);
44+
if (direct.length > 0) return direct;
45+
46+
const auth = metaRecord.auth;
47+
if (auth && typeof auth === "object") {
48+
const authScopes = normalizeScopeList((auth as Record<string, unknown>).scopes);
49+
if (authScopes.length > 0) return authScopes;
50+
}
51+
52+
const omni = metaRecord.omniroute;
53+
if (omni && typeof omni === "object") {
54+
const omniScopes = normalizeScopeList((omni as Record<string, unknown>).scopes);
55+
if (omniScopes.length > 0) return omniScopes;
56+
}
57+
58+
return [];
59+
}
60+
61+
function scopeMatches(grantedScope: string, requiredScope: string): boolean {
62+
if (grantedScope === "*" || grantedScope === requiredScope) {
63+
return true;
64+
}
65+
if (grantedScope.endsWith("*")) {
66+
const prefix = grantedScope.slice(0, -1);
67+
return requiredScope.startsWith(prefix);
68+
}
69+
return false;
70+
}
71+
72+
export function resolveCallerScopeContext(
73+
extra: McpToolExtraLike | undefined,
74+
fallbackScopes: readonly string[] = []
75+
): CallerScopeContext {
76+
const callerId =
77+
(typeof extra?.authInfo?.clientId === "string" && extra.authInfo.clientId.trim()) ||
78+
(typeof extra?.sessionId === "string" && extra.sessionId.trim()) ||
79+
"anonymous";
80+
81+
const authScopes = normalizeScopeList(extra?.authInfo?.scopes);
82+
if (authScopes.length > 0) {
83+
return { callerId, scopes: authScopes, source: "authInfo" };
84+
}
85+
86+
const metaScopes = extractMetaScopeList(extra?._meta);
87+
if (metaScopes.length > 0) {
88+
return { callerId, scopes: metaScopes, source: "meta" };
89+
}
90+
91+
const fallback = normalizeScopeList(fallbackScopes);
92+
if (fallback.length > 0) {
93+
return { callerId, scopes: fallback, source: "env" };
94+
}
95+
96+
return { callerId, scopes: [], source: "none" };
97+
}
98+
99+
export function evaluateToolScopes(
100+
toolName: string,
101+
callerScopes: readonly string[],
102+
enforceScopes: boolean
103+
): ScopeCheckResult {
104+
const toolDef = MCP_TOOL_MAP[toolName];
105+
if (!toolDef) {
106+
return {
107+
allowed: false,
108+
required: [],
109+
provided: Array.from(callerScopes),
110+
missing: [],
111+
reason: "tool_definition_missing",
112+
};
113+
}
114+
115+
const required = Array.isArray(toolDef.scopes) ? Array.from(toolDef.scopes) : [];
116+
const provided = normalizeScopeList(callerScopes);
117+
118+
if (!enforceScopes || required.length === 0) {
119+
return { allowed: true, required, provided, missing: [] };
120+
}
121+
122+
const missing = required.filter(
123+
(requiredScope) => !provided.some((grantedScope) => scopeMatches(grantedScope, requiredScope))
124+
);
125+
126+
return {
127+
allowed: missing.length === 0,
128+
required,
129+
provided,
130+
missing,
131+
reason: missing.length > 0 ? "missing_scopes" : undefined,
132+
};
133+
}

0 commit comments

Comments
 (0)