@omnicross/core 0.1.1 → 0.1.3
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/dist/ApiConverter.cjs +3 -778
- package/dist/ApiConverter.js +12 -751
- package/dist/{CompletionService-DtOF_War.d.cts → CompletionService-D3a47vdU.d.cts} +2 -1
- package/dist/{CompletionService-7fCmKAP3.d.ts → CompletionService-DdWaAxq0.d.ts} +2 -1
- package/dist/auth/GeminiCodeAssistProjectResolver.cjs +32 -60
- package/dist/auth/GeminiCodeAssistProjectResolver.js +11 -10
- package/dist/builtin-web-fetch-77JZUZGM.cjs +100 -0
- package/dist/builtin-web-fetch-IGLBX2XY.js +100 -0
- package/dist/chunk-2DCNB7DF.cjs +514 -0
- package/dist/chunk-2FMI2B6V.cjs +6196 -0
- package/dist/chunk-3OPYJG76.cjs +266 -0
- package/dist/chunk-3QOSDDNI.cjs +130 -0
- package/dist/chunk-46RLHI7D.js +0 -0
- package/dist/chunk-46ZIFWYG.cjs +277 -0
- package/dist/chunk-4DECIFU7.js +6196 -0
- package/dist/chunk-4IH4EL7M.cjs +9 -0
- package/dist/chunk-4VAVYUZS.cjs +991 -0
- package/dist/chunk-5ENKBSWO.js +311 -0
- package/dist/chunk-5ERIZW3D.js +44 -0
- package/dist/chunk-5HTVET6E.js +514 -0
- package/dist/chunk-5ZQBEOMD.js +764 -0
- package/dist/chunk-6RPZADX3.cjs +92 -0
- package/dist/chunk-7VU7V2E4.js +0 -0
- package/dist/chunk-7YN22L2H.cjs +87 -0
- package/dist/chunk-A2YMUCBT.cjs +105 -0
- package/dist/chunk-AEOZIDEB.cjs +32 -0
- package/dist/chunk-AQ5TNQB7.js +13 -0
- package/dist/chunk-AVYJ4YBN.js +58 -0
- package/dist/chunk-BO4RFWBR.js +89 -0
- package/dist/chunk-C72S6TTV.cjs +49 -0
- package/dist/chunk-C7PSERMH.js +87 -0
- package/dist/chunk-CDHS2QDC.js +179 -0
- package/dist/chunk-CWM2ZDU5.js +130 -0
- package/dist/chunk-CZHZGRU7.js +383 -0
- package/dist/chunk-DK4A7DLE.js +266 -0
- package/dist/chunk-DQ5VAYZ6.cjs +42 -0
- package/dist/chunk-DRWHTIIZ.js +13 -0
- package/dist/chunk-DWKQZ2SK.cjs +254 -0
- package/dist/chunk-EYZYXJTJ.cjs +1 -0
- package/dist/chunk-FFALUUZZ.cjs +235 -0
- package/dist/chunk-FMOBD2OQ.js +39 -0
- package/dist/chunk-FTQ7IQUC.js +991 -0
- package/dist/chunk-FZ46M34D.cjs +1 -0
- package/dist/chunk-G2UTNSGU.js +277 -0
- package/dist/chunk-GBHYAGX3.cjs +26 -0
- package/dist/chunk-GMMT7RVN.cjs +39 -0
- package/dist/chunk-H5JUT3KV.js +105 -0
- package/dist/chunk-HQP3K7PU.cjs +431 -0
- package/dist/chunk-HUZZZ3JL.js +32 -0
- package/dist/chunk-IAXWICZU.js +69 -0
- package/dist/chunk-JMJBSACD.cjs +147 -0
- package/dist/chunk-JO5NLTLY.js +46 -0
- package/dist/chunk-JWC6APFQ.js +23 -0
- package/dist/chunk-KUU2RNG6.cjs +46 -0
- package/dist/chunk-L74VDN4A.cjs +32 -0
- package/dist/chunk-MQNDWYPN.js +235 -0
- package/dist/chunk-MZNPGW5Q.js +26 -0
- package/dist/chunk-N5QFKSDO.js +56 -0
- package/dist/chunk-NYUNU75A.cjs +44 -0
- package/dist/chunk-NZYN7C3P.cjs +179 -0
- package/dist/chunk-Q27JY5RG.cjs +126 -0
- package/dist/chunk-QBZJ7P2T.cjs +13 -0
- package/dist/chunk-QWBWGODS.cjs +69 -0
- package/dist/chunk-QXD4MV2X.cjs +23 -0
- package/dist/chunk-R3HK6R4B.cjs +13 -0
- package/dist/chunk-RPII3E6Z.cjs +145 -0
- package/dist/chunk-RSJ3TWJQ.cjs +186 -0
- package/dist/chunk-S4WBV25P.js +913 -0
- package/dist/chunk-S6JGLF3G.cjs +89 -0
- package/dist/chunk-SUEGJ75D.cjs +764 -0
- package/dist/chunk-SVNDB62D.cjs +58 -0
- package/dist/chunk-SVXC36MU.cjs +383 -0
- package/dist/chunk-TKJMZBAH.js +32 -0
- package/dist/chunk-UBWEBKRC.js +42 -0
- package/dist/chunk-UNEFIWXI.cjs +32 -0
- package/dist/chunk-VQY5W4YW.cjs +311 -0
- package/dist/chunk-VVEHS2LI.js +431 -0
- package/dist/chunk-WNY4WMRF.js +32 -0
- package/dist/chunk-XEKD23J5.js +9 -0
- package/dist/chunk-XTEI64OU.js +145 -0
- package/dist/chunk-XX6NQJMA.js +92 -0
- package/dist/chunk-Y6OM7EV7.cjs +56 -0
- package/dist/chunk-Y7FO65VT.js +147 -0
- package/dist/chunk-YGXJGRFI.cjs +913 -0
- package/dist/chunk-YMFAJFQT.cjs +173 -0
- package/dist/chunk-Z7LUNECE.js +49 -0
- package/dist/chunk-Z7Y2NPHY.js +186 -0
- package/dist/chunk-ZJZAOKI7.js +254 -0
- package/dist/chunk-ZLX3KGXX.js +173 -0
- package/dist/chunk-ZY6KU6P3.js +126 -0
- package/dist/completion/ApiKeyPoolService.cjs +23 -48
- package/dist/completion/ApiKeyPoolService.d.cts +150 -2
- package/dist/completion/ApiKeyPoolService.d.ts +150 -2
- package/dist/completion/BuiltinToolExecutor.cjs +9 -324
- package/dist/completion/BuiltinToolExecutor.js +6 -290
- package/dist/completion/CompletionService.cjs +45 -3487
- package/dist/completion/CompletionService.d.cts +3 -2
- package/dist/completion/CompletionService.d.ts +3 -2
- package/dist/completion/CompletionService.js +41 -3457
- package/dist/completion/NativeSearchInjector.cjs +8 -192
- package/dist/completion/NativeSearchInjector.js +7 -162
- package/dist/completion/ProviderSearchInjector.cjs +12 -39
- package/dist/completion/native-search-types.cjs +13 -66
- package/dist/completion/native-search-types.js +7 -31
- package/dist/completion/openrouter-headers.cjs +12 -71
- package/dist/completion/openrouter-headers.d.cts +9 -12
- package/dist/completion/openrouter-headers.d.ts +9 -12
- package/dist/completion/openrouter-headers.js +7 -36
- package/dist/completion/openrouter-models.cjs +11 -67
- package/dist/completion/openrouter-models.js +5 -34
- package/dist/completion/types.cjs +1 -18
- package/dist/completion/url-builder.cjs +24 -138
- package/dist/completion/url-builder.js +12 -92
- package/dist/completion.cjs +62 -3989
- package/dist/completion.d.cts +3 -2
- package/dist/completion.d.ts +3 -2
- package/dist/completion.js +70 -3953
- package/dist/index.cjs +319 -10429
- package/dist/index.d.cts +49 -12
- package/dist/index.d.ts +49 -12
- package/dist/index.js +248 -10269
- package/dist/logger-4GvQNzhE.d.cts +22 -0
- package/dist/logger-4GvQNzhE.d.ts +22 -0
- package/dist/outbound-api/routeResolver.cjs +10 -217
- package/dist/outbound-api/routeResolver.d.cts +10 -3
- package/dist/outbound-api/routeResolver.d.ts +10 -3
- package/dist/outbound-api/routeResolver.js +9 -187
- package/dist/outbound-api/subscriptionRegistryPort.cjs +7 -37
- package/dist/outbound-api/subscriptionRegistryPort.d.cts +2 -1
- package/dist/outbound-api/subscriptionRegistryPort.d.ts +2 -1
- package/dist/outbound-api/subscriptionRegistryPort.js +4 -8
- package/dist/outbound-api/types.cjs +5 -17
- package/dist/outbound-api/types.d.cts +12 -144
- package/dist/outbound-api/types.d.ts +12 -144
- package/dist/outbound-api/types.js +6 -0
- package/dist/outbound-api.cjs +197 -5265
- package/dist/outbound-api.d.cts +787 -25
- package/dist/outbound-api.d.ts +787 -25
- package/dist/outbound-api.js +173 -5195
- package/dist/pipeline/AuthSource.cjs +1 -18
- package/dist/pipeline/AuthSource.d.cts +23 -1
- package/dist/pipeline/AuthSource.d.ts +23 -1
- package/dist/pipeline/LlmConfigProviderAuth.cjs +5 -166
- package/dist/pipeline/LlmConfigProviderAuth.d.cts +2 -1
- package/dist/pipeline/LlmConfigProviderAuth.d.ts +2 -1
- package/dist/pipeline/LlmConfigProviderAuth.js +5 -139
- package/dist/pipeline/SubscriptionAccountHealth.cjs +22 -0
- package/dist/pipeline/SubscriptionAccountHealth.d.cts +235 -0
- package/dist/pipeline/SubscriptionAccountHealth.d.ts +235 -0
- package/dist/pipeline/SubscriptionAccountHealth.js +22 -0
- package/dist/pipeline/SubscriptionAuthSource.cjs +6 -128
- package/dist/pipeline/SubscriptionAuthSource.d.cts +3 -2
- package/dist/pipeline/SubscriptionAuthSource.d.ts +3 -2
- package/dist/pipeline/SubscriptionAuthSource.js +5 -99
- package/dist/pipeline/SubscriptionAuthStrategy.cjs +1 -18
- package/dist/pipeline/SubscriptionAuthStrategy.d.cts +24 -1
- package/dist/pipeline/SubscriptionAuthStrategy.d.ts +24 -1
- package/dist/pipeline/auditSink.cjs +14 -0
- package/dist/pipeline/auditSink.d.cts +48 -0
- package/dist/pipeline/auditSink.d.ts +48 -0
- package/dist/pipeline/auditSink.js +14 -0
- package/dist/pipeline/billingEmit.cjs +14 -0
- package/dist/pipeline/billingEmit.d.cts +49 -0
- package/dist/pipeline/billingEmit.d.ts +49 -0
- package/dist/pipeline/billingEmit.js +14 -0
- package/dist/pipeline/executeProviderCall.cjs +5 -69
- package/dist/pipeline/executeProviderCall.js +3 -42
- package/dist/pipeline/resolveProviderChain.cjs +5 -46
- package/dist/pipeline/resolveProviderChain.js +3 -19
- package/dist/pipeline/resolveSubscriptionChain.cjs +5 -67
- package/dist/pipeline/resolveSubscriptionChain.js +3 -40
- package/dist/ports/gemini-code-assist-resolver.cjs +7 -37
- package/dist/ports/gemini-code-assist-resolver.js +4 -8
- package/dist/ports/pricing-store.cjs +1 -0
- package/dist/ports/pricing-store.d.cts +50 -0
- package/dist/ports/pricing-store.d.ts +50 -0
- package/dist/ports/pricing-store.js +0 -0
- package/dist/ports/provider-config-source.cjs +1 -18
- package/dist/ports/usage-event-store.cjs +1 -0
- package/dist/ports/usage-event-store.d.cts +54 -0
- package/dist/ports/usage-event-store.d.ts +54 -0
- package/dist/ports/usage-event-store.js +0 -0
- package/dist/ports/web-search-backend.cjs +1 -18
- package/dist/ports.cjs +1 -18
- package/dist/ports.d.cts +13 -2
- package/dist/ports.d.ts +13 -2
- package/dist/provider-proxy/ProviderProxy.cjs +47 -4643
- package/dist/provider-proxy/ProviderProxy.d.cts +2 -1
- package/dist/provider-proxy/ProviderProxy.d.ts +2 -1
- package/dist/provider-proxy/ProviderProxy.js +42 -4613
- package/dist/provider-proxy/identity/SubscriptionIdentityStore.cjs +15 -0
- package/dist/provider-proxy/identity/SubscriptionIdentityStore.d.cts +121 -0
- package/dist/provider-proxy/identity/SubscriptionIdentityStore.d.ts +121 -0
- package/dist/provider-proxy/identity/SubscriptionIdentityStore.js +15 -0
- package/dist/provider-proxy/identity/fingerprintHeaders.cjs +20 -0
- package/dist/provider-proxy/identity/fingerprintHeaders.d.cts +89 -0
- package/dist/provider-proxy/identity/fingerprintHeaders.d.ts +89 -0
- package/dist/provider-proxy/identity/fingerprintHeaders.js +20 -0
- package/dist/provider-proxy/ingress/providerProxyShared.cjs +42 -2941
- package/dist/provider-proxy/ingress/providerProxyShared.d.cts +13 -3
- package/dist/provider-proxy/ingress/providerProxyShared.d.ts +13 -3
- package/dist/provider-proxy/ingress/providerProxyShared.js +48 -2914
- package/dist/provider-proxy/matchText.cjs +13 -72
- package/dist/provider-proxy/matchText.d.cts +14 -1
- package/dist/provider-proxy/matchText.d.ts +14 -1
- package/dist/provider-proxy/matchText.js +8 -39
- package/dist/provider-proxy/types.cjs +1 -18
- package/dist/provider-proxy/types.d.cts +25 -1
- package/dist/provider-proxy/types.d.ts +25 -1
- package/dist/provider-proxy.cjs +42 -4655
- package/dist/provider-proxy.d.cts +2 -1
- package/dist/provider-proxy.d.ts +2 -1
- package/dist/provider-proxy.js +46 -4628
- package/dist/{routeResolver-HE-ZO0fO.d.ts → routeResolver-B1NglTfe.d.ts} +36 -10
- package/dist/{routeResolver-BrbK6ja9.d.cts → routeResolver-CbG5uBgm.d.cts} +36 -10
- package/dist/serializeError.cjs +5 -81
- package/dist/serializeError.js +3 -54
- package/dist/sse-parser.cjs +7 -455
- package/dist/sse-parser.js +4 -426
- package/dist/transformer/TransformerChainExecutor.cjs +4 -318
- package/dist/transformer/TransformerChainExecutor.js +4 -291
- package/dist/transformer/TransformerService.cjs +5 -289
- package/dist/transformer/TransformerService.js +3 -262
- package/dist/transformer/anthropicBetaInject.cjs +7 -50
- package/dist/transformer/anthropicBetaInject.js +4 -21
- package/dist/transformer/transformers/AnthropicTransformer.cjs +3 -1014
- package/dist/transformer/transformers/AnthropicTransformer.js +3 -987
- package/dist/transformer/transformers/GeminiCodeAssistTransformer.cjs +4 -1106
- package/dist/transformer/transformers/GeminiCodeAssistTransformer.js +7 -1079
- package/dist/transformer/transformers/GeminiTransformer.cjs +4 -1010
- package/dist/transformer/transformers/GeminiTransformer.js +4 -983
- package/dist/transformer/transformers/OpenAIResponseTransformer.cjs +3 -535
- package/dist/transformer/transformers/OpenAIResponseTransformer.js +3 -510
- package/dist/transformer/transformers/OpenCodeGoTransformer.cjs +5 -72
- package/dist/transformer/transformers/OpenCodeGoTransformer.js +3 -45
- package/dist/transformer/transformers/ReasoningTransformer.cjs +3 -270
- package/dist/transformer/transformers/ReasoningTransformer.js +2 -249
- package/dist/transformer/transformers.cjs +10 -3177
- package/dist/transformer/transformers.js +23 -3158
- package/dist/transformer/types.cjs +1 -18
- package/dist/transformer.cjs +13 -3726
- package/dist/transformer.js +17 -3706
- package/dist/types-6OUY_xfA.d.ts +830 -0
- package/dist/types-aBFFXoBz.d.cts +830 -0
- package/dist/usage/pricing-engine.cjs +6 -0
- package/dist/usage/pricing-engine.d.cts +83 -0
- package/dist/usage/pricing-engine.d.ts +83 -0
- package/dist/usage/pricing-engine.js +6 -0
- package/dist/usage/usage-recorder.cjs +6 -0
- package/dist/usage/usage-recorder.d.cts +91 -0
- package/dist/usage/usage-recorder.d.ts +91 -0
- package/dist/usage/usage-recorder.js +6 -0
- package/dist/usage.cjs +11 -0
- package/dist/usage.d.cts +8 -0
- package/dist/usage.d.ts +8 -0
- package/dist/usage.js +11 -0
- package/package.json +4 -2
- package/dist/ApiKeyPoolService-BmMkau07.d.cts +0 -170
- package/dist/ApiKeyPoolService-BmMkau07.d.ts +0 -170
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// src/completion/BuiltinToolExecutor.ts
|
|
2
|
+
var DEFAULT_SEARCH_COUNT = 5;
|
|
3
|
+
var DEFAULT_FETCH_MAX_CHARS = 2e4;
|
|
4
|
+
var FALLBACK_ORDER = [
|
|
5
|
+
"tavily",
|
|
6
|
+
"jina",
|
|
7
|
+
"searxng",
|
|
8
|
+
"zhipu",
|
|
9
|
+
"z.ai",
|
|
10
|
+
"bocha",
|
|
11
|
+
"grok",
|
|
12
|
+
"local-google",
|
|
13
|
+
"local-bing",
|
|
14
|
+
"local-baidu",
|
|
15
|
+
"local-duckduckgo"
|
|
16
|
+
];
|
|
17
|
+
var IMPLICIT_LOCAL_FALLBACK = "local-google";
|
|
18
|
+
var WEB_SEARCH_TOOL = {
|
|
19
|
+
id: "builtin__web_search",
|
|
20
|
+
serverId: "builtin",
|
|
21
|
+
serverName: "builtin",
|
|
22
|
+
name: "web_search",
|
|
23
|
+
description: "Search the web for current information. Returns a list of results with title, URL, and snippet. Use web_fetch afterwards to get full page content if snippets are insufficient.",
|
|
24
|
+
inputSchema: {
|
|
25
|
+
type: "object",
|
|
26
|
+
properties: {
|
|
27
|
+
query: { type: "string", description: "The search query." },
|
|
28
|
+
count: { type: "integer", description: "Maximum number of results (1-10).", default: DEFAULT_SEARCH_COUNT }
|
|
29
|
+
},
|
|
30
|
+
required: ["query"]
|
|
31
|
+
},
|
|
32
|
+
type: "mcp",
|
|
33
|
+
isBuiltIn: true
|
|
34
|
+
};
|
|
35
|
+
var WEB_FETCH_TOOL = {
|
|
36
|
+
id: "builtin__web_fetch",
|
|
37
|
+
serverId: "builtin",
|
|
38
|
+
serverName: "builtin",
|
|
39
|
+
name: "web_fetch",
|
|
40
|
+
description: "Fetch and extract the main content of a web page as clean Markdown. Use this to read the full content of a URL found via web_search.",
|
|
41
|
+
inputSchema: {
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: {
|
|
44
|
+
url: { type: "string", description: "The URL to fetch (http or https)." },
|
|
45
|
+
maxChars: { type: "integer", description: "Maximum characters to return.", default: DEFAULT_FETCH_MAX_CHARS }
|
|
46
|
+
},
|
|
47
|
+
required: ["url"]
|
|
48
|
+
},
|
|
49
|
+
type: "mcp",
|
|
50
|
+
isBuiltIn: true
|
|
51
|
+
};
|
|
52
|
+
var BuiltinToolExecutor = class {
|
|
53
|
+
constructor(webSearch) {
|
|
54
|
+
this.webSearch = webSearch;
|
|
55
|
+
}
|
|
56
|
+
webSearch;
|
|
57
|
+
async execute(toolName, args) {
|
|
58
|
+
switch (toolName) {
|
|
59
|
+
case "web_search":
|
|
60
|
+
return this.executeWebSearch(args);
|
|
61
|
+
case "web_fetch":
|
|
62
|
+
return this.executeWebFetch(args);
|
|
63
|
+
default:
|
|
64
|
+
return { isError: true, content: [{ type: "text", text: `Unknown built-in tool: ${toolName}` }] };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// -----------------------------------------------------------------------
|
|
68
|
+
// web_search
|
|
69
|
+
// -----------------------------------------------------------------------
|
|
70
|
+
async executeWebSearch(args) {
|
|
71
|
+
const query = String(args.query ?? "").trim();
|
|
72
|
+
if (!query) {
|
|
73
|
+
return { isError: true, content: [{ type: "text", text: "Error: query parameter is required." }] };
|
|
74
|
+
}
|
|
75
|
+
const count = Math.min(Math.max(Number(args.count ?? DEFAULT_SEARCH_COUNT), 1), 10);
|
|
76
|
+
const chain = this.resolveProviderChain();
|
|
77
|
+
let lastError = "";
|
|
78
|
+
for (const providerId of chain) {
|
|
79
|
+
try {
|
|
80
|
+
const result = await this.webSearch.search(query, providerId, { maxResults: count });
|
|
81
|
+
if (!result.success) {
|
|
82
|
+
lastError = result.error || "Unknown error";
|
|
83
|
+
console.log(`[BuiltinToolExecutor] Search provider ${providerId} failed: ${lastError}, trying next...`);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const items = result.results.slice(0, count);
|
|
87
|
+
if (items.length === 0) {
|
|
88
|
+
console.log(`[BuiltinToolExecutor] Search provider ${providerId} returned 0 results, trying next...`);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const text = items.map((r, i) => `${i + 1}. ${r.title}
|
|
92
|
+
URL: ${r.url}
|
|
93
|
+
${r.content || ""}`).join("\n\n");
|
|
94
|
+
return { isError: false, content: [{ type: "text", text }] };
|
|
95
|
+
} catch (err) {
|
|
96
|
+
lastError = err instanceof Error ? err.message : String(err);
|
|
97
|
+
console.log(`[BuiltinToolExecutor] Search provider ${providerId} threw: ${lastError}, trying next...`);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
isError: false,
|
|
103
|
+
content: [{ type: "text", text: lastError ? `Search failed after trying ${chain.length} provider(s). Last error: ${lastError}` : `No results found for "${query}" after trying ${chain.length} provider(s). Try rephrasing your search.` }]
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
resolveProviderChain() {
|
|
107
|
+
const chain = [];
|
|
108
|
+
for (const id of FALLBACK_ORDER) {
|
|
109
|
+
if (this.webSearch.isProviderEnabled(id)) {
|
|
110
|
+
chain.push(id);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (!chain.includes(IMPLICIT_LOCAL_FALLBACK)) {
|
|
114
|
+
chain.push(IMPLICIT_LOCAL_FALLBACK);
|
|
115
|
+
}
|
|
116
|
+
return chain;
|
|
117
|
+
}
|
|
118
|
+
// -----------------------------------------------------------------------
|
|
119
|
+
// web_fetch
|
|
120
|
+
// -----------------------------------------------------------------------
|
|
121
|
+
async executeWebFetch(args) {
|
|
122
|
+
const url = String(args.url ?? "").trim();
|
|
123
|
+
if (!url) {
|
|
124
|
+
return { isError: true, content: [{ type: "text", text: "Error: url parameter is required." }] };
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
const parsed = new URL(url);
|
|
128
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
129
|
+
return { isError: true, content: [{ type: "text", text: "Error: only http and https URLs are supported." }] };
|
|
130
|
+
}
|
|
131
|
+
} catch {
|
|
132
|
+
return { isError: true, content: [{ type: "text", text: `Error: invalid URL "${url}".` }] };
|
|
133
|
+
}
|
|
134
|
+
const maxChars = Number(args.maxChars ?? DEFAULT_FETCH_MAX_CHARS);
|
|
135
|
+
try {
|
|
136
|
+
const jinaResult = await this.webSearch.readUrl(url, { timeout: 15e3 });
|
|
137
|
+
if (jinaResult.success && jinaResult.content) {
|
|
138
|
+
const content = jinaResult.content.length > maxChars ? jinaResult.content.slice(0, maxChars) + "\n\n[Content truncated]" : jinaResult.content;
|
|
139
|
+
const header = jinaResult.title ? `# ${jinaResult.title}
|
|
140
|
+
|
|
141
|
+
` : "";
|
|
142
|
+
return { isError: false, content: [{ type: "text", text: header + content }] };
|
|
143
|
+
}
|
|
144
|
+
const { fetchAndExtractUrl } = await import("./builtin-web-fetch-IGLBX2XY.js");
|
|
145
|
+
const text = await fetchAndExtractUrl(url, maxChars);
|
|
146
|
+
return { isError: false, content: [{ type: "text", text }] };
|
|
147
|
+
} catch (err) {
|
|
148
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
149
|
+
return { isError: true, content: [{ type: "text", text: `Fetch error: ${message}` }] };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
function getBuiltinSearchTools() {
|
|
154
|
+
return [WEB_SEARCH_TOOL, WEB_FETCH_TOOL];
|
|
155
|
+
}
|
|
156
|
+
function convertBuiltinToolsToOpenAI(tools) {
|
|
157
|
+
return tools.map((t) => ({
|
|
158
|
+
type: "function",
|
|
159
|
+
function: {
|
|
160
|
+
name: t.id,
|
|
161
|
+
description: t.description || t.name,
|
|
162
|
+
parameters: t.inputSchema
|
|
163
|
+
}
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
function convertBuiltinToolsToAnthropic(tools) {
|
|
167
|
+
return tools.map((t) => ({
|
|
168
|
+
name: t.id,
|
|
169
|
+
description: t.description || t.name,
|
|
170
|
+
input_schema: t.inputSchema
|
|
171
|
+
}));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export {
|
|
175
|
+
BuiltinToolExecutor,
|
|
176
|
+
getBuiltinSearchTools,
|
|
177
|
+
convertBuiltinToolsToOpenAI,
|
|
178
|
+
convertBuiltinToolsToAnthropic
|
|
179
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// src/pipeline/auditUsageStash.ts
|
|
2
|
+
var stash = /* @__PURE__ */ new WeakMap();
|
|
3
|
+
function stashAuditUsage(key, detail) {
|
|
4
|
+
const existing = stash.get(key);
|
|
5
|
+
stash.set(key, existing ? { ...existing, ...detail } : { ...detail });
|
|
6
|
+
}
|
|
7
|
+
function readAuditUsage(key) {
|
|
8
|
+
return stash.get(key);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// src/usage/usage-recorder.ts
|
|
12
|
+
var UsageRecorder = class {
|
|
13
|
+
constructor(store, pricing, logger, options = {}) {
|
|
14
|
+
this.store = store;
|
|
15
|
+
this.pricing = pricing;
|
|
16
|
+
this.logger = logger;
|
|
17
|
+
this.defer = options.defer ?? ((fn) => setTimeout(fn, 0));
|
|
18
|
+
this.onRecord = options.onRecord;
|
|
19
|
+
}
|
|
20
|
+
store;
|
|
21
|
+
pricing;
|
|
22
|
+
logger;
|
|
23
|
+
defer;
|
|
24
|
+
onRecord;
|
|
25
|
+
/**
|
|
26
|
+
* Record one LLM request. Returns immediately — the actual store insert is
|
|
27
|
+
* deferred so the caller's response path is unblocked.
|
|
28
|
+
*
|
|
29
|
+
* Errors during the insert are logged but never thrown.
|
|
30
|
+
*/
|
|
31
|
+
record(input) {
|
|
32
|
+
if (input.auditResponse) {
|
|
33
|
+
stashAuditUsage(input.auditResponse, {
|
|
34
|
+
inputTokens: input.usage.inputTokens,
|
|
35
|
+
outputTokens: input.usage.outputTokens,
|
|
36
|
+
model: input.model,
|
|
37
|
+
provider: input.providerId
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
this.defer(() => {
|
|
41
|
+
void this.recordAsync(input).catch((err) => {
|
|
42
|
+
this.logger.warn("[UsageRecorder] failed to persist usage event", {
|
|
43
|
+
error: err instanceof Error ? err.message : String(err)
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Same as `record()` but awaitable — preferred in tests so they can assert
|
|
50
|
+
* after the row exists.
|
|
51
|
+
*/
|
|
52
|
+
async recordAsync(input) {
|
|
53
|
+
const { costUsd, costSavedByCacheUsd } = await this.pricing.calculateCost(
|
|
54
|
+
input.providerId,
|
|
55
|
+
input.model,
|
|
56
|
+
input.usage
|
|
57
|
+
);
|
|
58
|
+
if (input.auditResponse) {
|
|
59
|
+
try {
|
|
60
|
+
stashAuditUsage(input.auditResponse, { costUsd });
|
|
61
|
+
} catch {
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (this.onRecord && input.apiKeyId) {
|
|
65
|
+
try {
|
|
66
|
+
this.onRecord(input.apiKeyId, costUsd, Date.now());
|
|
67
|
+
} catch {
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const row = {
|
|
71
|
+
messageId: input.messageId ?? null,
|
|
72
|
+
parentMessageId: input.parentMessageId ?? null,
|
|
73
|
+
sessionId: input.sessionId ?? null,
|
|
74
|
+
providerId: input.providerId,
|
|
75
|
+
model: input.model,
|
|
76
|
+
apiKeyId: input.apiKeyId ?? null,
|
|
77
|
+
engineOrigin: input.engineOrigin,
|
|
78
|
+
inputTokens: input.usage.inputTokens,
|
|
79
|
+
outputTokens: input.usage.outputTokens,
|
|
80
|
+
cacheReadTokens: input.usage.cacheReadTokens,
|
|
81
|
+
cacheCreationTokens: input.usage.cacheCreationTokens,
|
|
82
|
+
reasoningTokens: input.usage.reasoningTokens,
|
|
83
|
+
costUsd,
|
|
84
|
+
costSavedByCacheUsd,
|
|
85
|
+
rawUsage: input.rawUsage != null ? safeStringify(input.rawUsage) : null,
|
|
86
|
+
runId: input.runId ?? null,
|
|
87
|
+
eventId: input.eventId ?? null
|
|
88
|
+
};
|
|
89
|
+
try {
|
|
90
|
+
return await this.store.insert(row);
|
|
91
|
+
} catch (err) {
|
|
92
|
+
this.logger.warn("[UsageRecorder] store insert failed", {
|
|
93
|
+
error: err instanceof Error ? err.message : String(err)
|
|
94
|
+
});
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// ===== Query delegates =====
|
|
99
|
+
getTotals(range) {
|
|
100
|
+
return this.store.getTotals(range);
|
|
101
|
+
}
|
|
102
|
+
getByModel(range) {
|
|
103
|
+
return this.store.getByModel(range);
|
|
104
|
+
}
|
|
105
|
+
getByApiKey(range) {
|
|
106
|
+
return this.store.getByApiKey(range);
|
|
107
|
+
}
|
|
108
|
+
getTimeSeries(range, bucket) {
|
|
109
|
+
return this.store.getTimeSeries(range, bucket);
|
|
110
|
+
}
|
|
111
|
+
getMessagesForSession(sessionId) {
|
|
112
|
+
return this.store.getMessagesForSession(sessionId);
|
|
113
|
+
}
|
|
114
|
+
/** Cumulative cache hit/miss stats for one session. */
|
|
115
|
+
getSessionCacheStats(sessionId) {
|
|
116
|
+
return this.store.getSessionCacheStats(sessionId);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
var safeStringify = (value) => {
|
|
120
|
+
try {
|
|
121
|
+
return JSON.stringify(value);
|
|
122
|
+
} catch {
|
|
123
|
+
return "";
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export {
|
|
128
|
+
readAuditUsage,
|
|
129
|
+
UsageRecorder
|
|
130
|
+
};
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getSubscriptionRegistryForOutbound
|
|
3
|
+
} from "./chunk-AQ5TNQB7.js";
|
|
4
|
+
import {
|
|
5
|
+
ENDPOINT_MODEL_KINDS
|
|
6
|
+
} from "./chunk-XEKD23J5.js";
|
|
7
|
+
|
|
8
|
+
// src/outbound-api/kindDetection.ts
|
|
9
|
+
import { normalizeModelId as normalizeModelId2 } from "@omnicross/contracts/canonical-models";
|
|
10
|
+
|
|
11
|
+
// src/outbound-api/roleDetection.ts
|
|
12
|
+
import { normalizeModelId } from "@omnicross/contracts/canonical-models";
|
|
13
|
+
var BACKGROUND_TIER_TOKENS = /* @__PURE__ */ new Set([
|
|
14
|
+
"haiku",
|
|
15
|
+
"mini",
|
|
16
|
+
"flash",
|
|
17
|
+
"small",
|
|
18
|
+
"lite",
|
|
19
|
+
"nano",
|
|
20
|
+
"8b"
|
|
21
|
+
]);
|
|
22
|
+
function modelTokens(normalizedId) {
|
|
23
|
+
return normalizedId.split(/[-._:/\s]+/).filter(Boolean);
|
|
24
|
+
}
|
|
25
|
+
function isBackgroundTierModel(rawModelId) {
|
|
26
|
+
if (!rawModelId) return false;
|
|
27
|
+
const tokens = modelTokens(normalizeModelId(rawModelId));
|
|
28
|
+
return tokens.some((tok) => BACKGROUND_TIER_TOKENS.has(tok));
|
|
29
|
+
}
|
|
30
|
+
function extractRequestedModel(ingressFormat, body) {
|
|
31
|
+
const model = body["model"];
|
|
32
|
+
return typeof model === "string" && model ? model : void 0;
|
|
33
|
+
}
|
|
34
|
+
function detectRequestRole(ingressFormat, body, options) {
|
|
35
|
+
const requestedModel = extractRequestedModel(ingressFormat, body);
|
|
36
|
+
if (requestedModel) {
|
|
37
|
+
const overrides = options?.backgroundModelIds;
|
|
38
|
+
if (overrides && overrides.length > 0) {
|
|
39
|
+
const reqNorm = normalizeModelId(requestedModel);
|
|
40
|
+
const inOverride = overrides.some((id) => {
|
|
41
|
+
if (!id) return false;
|
|
42
|
+
const candidate = id.includes(",") ? id.slice(id.indexOf(",") + 1) : id;
|
|
43
|
+
return normalizeModelId(candidate) === reqNorm || candidate === requestedModel;
|
|
44
|
+
});
|
|
45
|
+
if (inOverride) return "background";
|
|
46
|
+
}
|
|
47
|
+
if (isBackgroundTierModel(requestedModel)) return "background";
|
|
48
|
+
}
|
|
49
|
+
return "default";
|
|
50
|
+
}
|
|
51
|
+
function endpointToIngressFormat(endpoint) {
|
|
52
|
+
switch (endpoint) {
|
|
53
|
+
case "chat":
|
|
54
|
+
return "openai-chat";
|
|
55
|
+
case "responses":
|
|
56
|
+
return "openai-responses";
|
|
57
|
+
case "messages":
|
|
58
|
+
return "anthropic-messages";
|
|
59
|
+
case "gemini":
|
|
60
|
+
return "gemini-generatecontent";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/outbound-api/kindDetection.ts
|
|
65
|
+
function isNonBlankRef(ref) {
|
|
66
|
+
return typeof ref === "string" && ref.trim() !== "";
|
|
67
|
+
}
|
|
68
|
+
function isKindMappedEndpoint(endpoint) {
|
|
69
|
+
return Object.prototype.hasOwnProperty.call(ENDPOINT_MODEL_KINDS, endpoint);
|
|
70
|
+
}
|
|
71
|
+
function modelKindsForEndpoint(endpoint) {
|
|
72
|
+
return ENDPOINT_MODEL_KINDS[endpoint];
|
|
73
|
+
}
|
|
74
|
+
function detectModelKind(endpoint, requestedModelId) {
|
|
75
|
+
if (!requestedModelId || requestedModelId.trim() === "") return void 0;
|
|
76
|
+
if (endpoint === "messages") {
|
|
77
|
+
const declared = ENDPOINT_MODEL_KINDS.messages;
|
|
78
|
+
for (const tok of modelTokens(normalizeModelId2(requestedModelId))) {
|
|
79
|
+
if (declared.includes(tok)) return tok;
|
|
80
|
+
}
|
|
81
|
+
return void 0;
|
|
82
|
+
}
|
|
83
|
+
return isBackgroundTierModel(requestedModelId) ? "mini" : "codex";
|
|
84
|
+
}
|
|
85
|
+
function validateEndpointModelConfig(config) {
|
|
86
|
+
if (!isKindMappedEndpoint(config.endpoint)) return [];
|
|
87
|
+
const map = config.modelMap ?? {};
|
|
88
|
+
return modelKindsForEndpoint(config.endpoint).filter(
|
|
89
|
+
(kind) => !isNonBlankRef(map[kind])
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
function validateServerModelConfig(config) {
|
|
93
|
+
const errors = [];
|
|
94
|
+
for (const endpoint of Object.keys(ENDPOINT_MODEL_KINDS)) {
|
|
95
|
+
const ep = config.endpoints.find((e) => e.endpoint === endpoint);
|
|
96
|
+
const declared = modelKindsForEndpoint(endpoint);
|
|
97
|
+
const missingKinds = ep ? validateEndpointModelConfig(ep) : [...declared];
|
|
98
|
+
if (missingKinds.length > 0 && missingKinds.length < declared.length) {
|
|
99
|
+
errors.push({ endpoint, missingKinds });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return errors;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/outbound-api/modelPrefixDispatch.ts
|
|
106
|
+
function classifyModelPrefix(model) {
|
|
107
|
+
if (!model) return null;
|
|
108
|
+
const id = model.trim().toLowerCase();
|
|
109
|
+
if (!id) return null;
|
|
110
|
+
if (matchesPrefix(id, "claude")) return "claude";
|
|
111
|
+
if (matchesPrefix(id, "gemini")) return "gemini";
|
|
112
|
+
if (matchesPrefix(id, "gpt") || matchesOSeries(id)) return "gpt";
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
function matchesPrefix(id, prefix) {
|
|
116
|
+
return id === prefix || id.startsWith(`${prefix}-`);
|
|
117
|
+
}
|
|
118
|
+
function matchesOSeries(id) {
|
|
119
|
+
return /^o\d/.test(id);
|
|
120
|
+
}
|
|
121
|
+
function resolvePrefixTarget(targets, model) {
|
|
122
|
+
const kind = classifyModelPrefix(model);
|
|
123
|
+
if (!kind) return null;
|
|
124
|
+
const ref = targets?.[kind];
|
|
125
|
+
if (!ref || !ref.trim()) return null;
|
|
126
|
+
return { kind, ref };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/outbound-api/subscriptionSupport.ts
|
|
130
|
+
var SUBSCRIPTION_PROVIDER_IDS = [
|
|
131
|
+
"claude",
|
|
132
|
+
"codex",
|
|
133
|
+
"gemini",
|
|
134
|
+
"opencodego"
|
|
135
|
+
];
|
|
136
|
+
var SUBSCRIPTION_ID_SET = new Set(SUBSCRIPTION_PROVIDER_IDS);
|
|
137
|
+
function isSubscriptionProviderId(providerId) {
|
|
138
|
+
if (SUBSCRIPTION_ID_SET.has(providerId)) return true;
|
|
139
|
+
const registry = getSubscriptionRegistryForOutbound();
|
|
140
|
+
return registry?.getProfile(providerId) != null;
|
|
141
|
+
}
|
|
142
|
+
var SUBSCRIPTION_SUPPORTED_ENDPOINTS = /* @__PURE__ */ new Set([
|
|
143
|
+
"messages",
|
|
144
|
+
"responses",
|
|
145
|
+
"chat"
|
|
146
|
+
]);
|
|
147
|
+
function endpointSupportsSubscription(endpoint) {
|
|
148
|
+
return SUBSCRIPTION_SUPPORTED_ENDPOINTS.has(endpoint);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/outbound-api/routeResolver.ts
|
|
152
|
+
function parseModelRef(ref) {
|
|
153
|
+
if (!ref) return null;
|
|
154
|
+
const idx = ref.indexOf(",");
|
|
155
|
+
if (idx <= 0) return null;
|
|
156
|
+
const providerId = ref.slice(0, idx).trim();
|
|
157
|
+
const modelId = ref.slice(idx + 1).trim();
|
|
158
|
+
if (!providerId || !modelId) return null;
|
|
159
|
+
return { providerId, modelId };
|
|
160
|
+
}
|
|
161
|
+
var MESSAGES_FALLBACK_ORDER = ["sonnet", "opus", "haiku", "fable"];
|
|
162
|
+
function isNonBlankRef2(ref) {
|
|
163
|
+
return typeof ref === "string" && ref.trim() !== "";
|
|
164
|
+
}
|
|
165
|
+
function pickModelRefFromList(models, requestedModel) {
|
|
166
|
+
if (!requestedModel || !models || models.length === 0) return void 0;
|
|
167
|
+
const wanted = requestedModel.trim();
|
|
168
|
+
let ciHit;
|
|
169
|
+
for (const ref of models) {
|
|
170
|
+
const parsed = parseModelRef(ref);
|
|
171
|
+
if (!parsed) continue;
|
|
172
|
+
if (parsed.modelId === wanted) return ref;
|
|
173
|
+
if (!ciHit && parsed.modelId.toLowerCase() === wanted.toLowerCase()) ciHit = ref;
|
|
174
|
+
}
|
|
175
|
+
return ciHit;
|
|
176
|
+
}
|
|
177
|
+
function pickModelRefForRole(config, role) {
|
|
178
|
+
if (role === "background") {
|
|
179
|
+
return { ref: config.backgroundModel, effectiveRole: "background" };
|
|
180
|
+
}
|
|
181
|
+
return { ref: config.defaultModel, effectiveRole: "default" };
|
|
182
|
+
}
|
|
183
|
+
function pickModelRefForKind(config, endpoint, requestedModel) {
|
|
184
|
+
const map = config.modelMap ?? {};
|
|
185
|
+
const detected = detectModelKind(endpoint, requestedModel);
|
|
186
|
+
const kind = detected ?? "unmapped";
|
|
187
|
+
if (detected && isNonBlankRef2(map[detected])) {
|
|
188
|
+
return { ref: map[detected], kind: detected };
|
|
189
|
+
}
|
|
190
|
+
if (endpoint === "messages") {
|
|
191
|
+
for (const k of MESSAGES_FALLBACK_ORDER) {
|
|
192
|
+
if (isNonBlankRef2(map[k])) return { ref: map[k], kind };
|
|
193
|
+
}
|
|
194
|
+
return { ref: void 0, kind };
|
|
195
|
+
}
|
|
196
|
+
if (isNonBlankRef2(map["codex"])) return { ref: map["codex"], kind };
|
|
197
|
+
return { ref: void 0, kind };
|
|
198
|
+
}
|
|
199
|
+
async function resolveRoute(args) {
|
|
200
|
+
const { config, ingressFormat, llmConfig } = args;
|
|
201
|
+
const sessionId = args.sessionId ?? null;
|
|
202
|
+
const endpoint = config.endpoint;
|
|
203
|
+
const kindMapped = isKindMappedEndpoint(endpoint);
|
|
204
|
+
const requestedModel = kindMapped ? args.requestedModel : void 0;
|
|
205
|
+
let ref;
|
|
206
|
+
let effectiveRole = "default";
|
|
207
|
+
let noModelStatus = 503;
|
|
208
|
+
let noModelMessage;
|
|
209
|
+
let malformedMessage;
|
|
210
|
+
if (kindMapped) {
|
|
211
|
+
const picked = pickModelRefForKind(config, endpoint, args.requestedModel);
|
|
212
|
+
ref = picked.ref;
|
|
213
|
+
noModelMessage = `endpoint '${config.endpoint}' has no model configured for kind '${picked.kind}'`;
|
|
214
|
+
malformedMessage = (r) => `endpoint '${config.endpoint}' model ref for kind '${picked.kind}' is malformed: '${r}'`;
|
|
215
|
+
} else if (endpoint === "chat" && config.dispatchMode === "prefix") {
|
|
216
|
+
ref = resolvePrefixTarget(config.prefixTargets, args.requestedModel)?.ref;
|
|
217
|
+
noModelStatus = 404;
|
|
218
|
+
noModelMessage = args.requestedModel ? `model '${args.requestedModel}' has no matching prefix target (claude-* / gpt-* / gemini-*) on the 'chat' endpoint` : `request has no 'model'; a prefix-dispatch 'chat' endpoint routes claude-* / gpt-* / gemini-*`;
|
|
219
|
+
malformedMessage = (r) => `endpoint 'chat' prefix target ref is malformed: '${r}'`;
|
|
220
|
+
} else if (endpoint === "chat") {
|
|
221
|
+
ref = pickModelRefFromList(config.models, args.requestedModel);
|
|
222
|
+
noModelStatus = 404;
|
|
223
|
+
noModelMessage = args.requestedModel ? `model '${args.requestedModel}' is not in the endpoint's model list (see GET /v1/models)` : `request has no 'model'; pick one from GET /v1/models`;
|
|
224
|
+
malformedMessage = (r) => `endpoint 'chat' model ref is malformed: '${r}'`;
|
|
225
|
+
} else {
|
|
226
|
+
const picked = pickModelRefForRole(config, args.role ?? "default");
|
|
227
|
+
ref = picked.ref;
|
|
228
|
+
effectiveRole = picked.effectiveRole;
|
|
229
|
+
noModelMessage = `endpoint '${config.endpoint}' has no ${effectiveRole} model configured`;
|
|
230
|
+
malformedMessage = (r) => `endpoint '${config.endpoint}' ${effectiveRole} model ref is malformed: '${r}'`;
|
|
231
|
+
}
|
|
232
|
+
if (!ref || !ref.trim()) {
|
|
233
|
+
return { ok: false, error: { status: noModelStatus, message: noModelMessage } };
|
|
234
|
+
}
|
|
235
|
+
const parsed = parseModelRef(ref);
|
|
236
|
+
if (!parsed) {
|
|
237
|
+
return { ok: false, error: { status: 503, message: malformedMessage(ref) } };
|
|
238
|
+
}
|
|
239
|
+
const { providerId, modelId } = parsed;
|
|
240
|
+
const provider = await llmConfig.getProvider(providerId);
|
|
241
|
+
if (!provider) {
|
|
242
|
+
if (isSubscriptionProviderId(providerId)) {
|
|
243
|
+
return await resolveSubscriptionRoute({
|
|
244
|
+
config,
|
|
245
|
+
providerId,
|
|
246
|
+
modelId,
|
|
247
|
+
ingressFormat,
|
|
248
|
+
sessionId,
|
|
249
|
+
effectiveRole,
|
|
250
|
+
requestedModel,
|
|
251
|
+
apiKeyId: args.apiKeyId
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
ok: false,
|
|
256
|
+
error: {
|
|
257
|
+
status: 503,
|
|
258
|
+
message: `provider '${providerId}' not found for endpoint '${config.endpoint}'`
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
const isOfficialProvider = provider.isOfficial === true || provider.id === "anthropic";
|
|
263
|
+
const targetProviderFormat = isOfficialProvider ? "anthropic" : "transform";
|
|
264
|
+
let anthropicSdkHints = null;
|
|
265
|
+
if (ingressFormat === "anthropic-messages") {
|
|
266
|
+
anthropicSdkHints = {
|
|
267
|
+
apiKey: resolveApiKey(provider.api_key),
|
|
268
|
+
isOfficialProvider,
|
|
269
|
+
passThrough: false,
|
|
270
|
+
attribution: { sessionId, apiKeyId: args.apiKeyId ?? null }
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
const route = {
|
|
274
|
+
sessionId,
|
|
275
|
+
targetProviderFormat,
|
|
276
|
+
model: modelId,
|
|
277
|
+
ingressFormat,
|
|
278
|
+
authMode: "byo",
|
|
279
|
+
providerId,
|
|
280
|
+
// Passthrough gate: the client's ORIGINAL requested id (kind-mapped only).
|
|
281
|
+
requestedModel,
|
|
282
|
+
// Usage attribution: the verified named-key id (undefined for internal traffic).
|
|
283
|
+
apiKeyId: args.apiKeyId,
|
|
284
|
+
anthropicSdkHints
|
|
285
|
+
};
|
|
286
|
+
return { ok: true, route, usedRole: effectiveRole };
|
|
287
|
+
}
|
|
288
|
+
async function resolveSubscriptionRoute(args) {
|
|
289
|
+
const { config, providerId, modelId, ingressFormat, sessionId, effectiveRole } = args;
|
|
290
|
+
const { requestedModel, apiKeyId } = args;
|
|
291
|
+
if (!config.useSubscription) {
|
|
292
|
+
return {
|
|
293
|
+
ok: false,
|
|
294
|
+
error: {
|
|
295
|
+
status: 503,
|
|
296
|
+
message: `subscription routing disabled for endpoint '${config.endpoint}'`
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
if (!endpointSupportsSubscription(config.endpoint)) {
|
|
301
|
+
return {
|
|
302
|
+
ok: false,
|
|
303
|
+
error: {
|
|
304
|
+
status: 503,
|
|
305
|
+
message: `subscription routing is not supported on the '${config.endpoint}' endpoint; use a BYO API-key provider, or the /v1/messages or /v1/responses endpoint`
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
const registry = getSubscriptionRegistryForOutbound();
|
|
310
|
+
const profile = registry?.getProfile(providerId) ?? null;
|
|
311
|
+
if (!profile) {
|
|
312
|
+
return {
|
|
313
|
+
ok: false,
|
|
314
|
+
error: {
|
|
315
|
+
status: 503,
|
|
316
|
+
message: `subscription provider '${providerId}' is not available`
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
const hints = {
|
|
321
|
+
apiKey: "",
|
|
322
|
+
isOfficialProvider: false,
|
|
323
|
+
passThrough: false,
|
|
324
|
+
subscriptionProfile: profile,
|
|
325
|
+
attribution: { sessionId, apiKeyId: apiKeyId ?? null }
|
|
326
|
+
};
|
|
327
|
+
const subscriptionConfig = providerId === "opencodego" ? await registry?.getOpenCodeGoConfig?.() : void 0;
|
|
328
|
+
const route = {
|
|
329
|
+
sessionId,
|
|
330
|
+
targetProviderFormat: "transform",
|
|
331
|
+
model: modelId,
|
|
332
|
+
ingressFormat,
|
|
333
|
+
authMode: "subscription",
|
|
334
|
+
providerId,
|
|
335
|
+
// Passthrough gate: the client's ORIGINAL requested id (kind-mapped only).
|
|
336
|
+
requestedModel,
|
|
337
|
+
// Usage attribution: the verified named-key id (undefined for internal traffic).
|
|
338
|
+
apiKeyId,
|
|
339
|
+
// Anthropic delegation carries the dispatch profile inside
|
|
340
|
+
// `anthropicSdkHints.subscriptionProfile`. The Responses ingress reads the
|
|
341
|
+
// TOP-LEVEL `route.subscriptionProfile`, and so does the built-in
|
|
342
|
+
// (factory-absent) `/v1/messages` subscription path (RT2.1) — the registry's
|
|
343
|
+
// `SubscriptionDispatchProfile` structurally satisfies `SubscriptionAuthProfile`
|
|
344
|
+
// ({ authStrategy, resolveUpstreamUrl, providerTransformerNames }), so set it
|
|
345
|
+
// for BOTH. This is INERT when an Anthropic ingress factory is present: that
|
|
346
|
+
// delegation reads the hint-bundle field above (and uses a different
|
|
347
|
+
// route-minting path), never the top-level field. (chat/gemini never reach
|
|
348
|
+
// here — Gate 2 rejected them.)
|
|
349
|
+
subscriptionProfile: ingressFormat === "openai-responses" || ingressFormat === "anthropic-messages" || ingressFormat === "openai-chat" ? profile : void 0,
|
|
350
|
+
// Opaque per-account config (opencodego-only; `undefined` otherwise). Read by
|
|
351
|
+
// the built-in `/v1/messages` plan builder; INERT for the factory-present
|
|
352
|
+
// delegation path + non-opencodego.
|
|
353
|
+
subscriptionConfig,
|
|
354
|
+
anthropicSdkHints: hints
|
|
355
|
+
};
|
|
356
|
+
return { ok: true, route, usedRole: effectiveRole };
|
|
357
|
+
}
|
|
358
|
+
function resolveApiKey(apiKey) {
|
|
359
|
+
if (!apiKey) return "";
|
|
360
|
+
if (apiKey.startsWith("$")) {
|
|
361
|
+
return process.env[apiKey.slice(1)] || "";
|
|
362
|
+
}
|
|
363
|
+
return apiKey;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export {
|
|
367
|
+
extractRequestedModel,
|
|
368
|
+
detectRequestRole,
|
|
369
|
+
endpointToIngressFormat,
|
|
370
|
+
isKindMappedEndpoint,
|
|
371
|
+
modelKindsForEndpoint,
|
|
372
|
+
detectModelKind,
|
|
373
|
+
validateEndpointModelConfig,
|
|
374
|
+
validateServerModelConfig,
|
|
375
|
+
classifyModelPrefix,
|
|
376
|
+
resolvePrefixTarget,
|
|
377
|
+
SUBSCRIPTION_PROVIDER_IDS,
|
|
378
|
+
isSubscriptionProviderId,
|
|
379
|
+
endpointSupportsSubscription,
|
|
380
|
+
parseModelRef,
|
|
381
|
+
pickModelRefFromList,
|
|
382
|
+
resolveRoute
|
|
383
|
+
};
|