manifest 5.35.2 → 5.36.1
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/backend/auth/auth.instance.js +3 -2
- package/dist/backend/database/models-dev-sync.service.js +1 -1
- package/dist/backend/database/pricing-sync.service.js +1 -1
- package/dist/backend/model-discovery/model-discovery.service.js +9 -3
- package/dist/backend/model-discovery/provider-model-fetcher.service.js +23 -10
- package/dist/backend/routing/proxy/google-adapter.js +37 -13
- package/dist/backend/routing/proxy/provider-client.js +2 -2
- package/dist/backend/routing/proxy/proxy-exception.filter.js +67 -0
- package/dist/backend/routing/proxy/proxy-fallback.service.js +4 -2
- package/dist/backend/routing/proxy/proxy-friendly-response.js +115 -0
- package/dist/backend/routing/proxy/proxy-rate-limiter.js +2 -2
- package/dist/backend/routing/proxy/proxy-response-handler.js +23 -3
- package/dist/backend/routing/proxy/proxy.controller.js +23 -8
- package/dist/backend/routing/proxy/proxy.module.js +4 -0
- package/dist/backend/routing/proxy/proxy.service.js +23 -86
- package/dist/backend/routing/proxy/thought-signature-cache.js +45 -0
- package/dist/backend/routing/routing-core/tier-auto-assign.service.js +9 -4
- package/dist/openclaw.plugin.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/public/assets/{Account-B_oSTrdf.js → Account-y_dgXY3t.js} +1 -1
- package/public/assets/{AuthBadge-CN6tvI2S.js → AuthBadge-zJLfTd7g.js} +1 -1
- package/public/assets/{CopyButton-BrMDM22y.js → CopyButton-CZjeoxZT.js} +1 -1
- package/public/assets/{Help-BCxDh7o6.js → Help-Bfmf4yw8.js} +1 -1
- package/public/assets/{Limits-HwYePaDb.js → Limits-B9jYN_38.js} +1 -1
- package/public/assets/{Login-BWgCdMOB.js → Login-U_w8MjSD.js} +1 -1
- package/public/assets/{MessageLog-TkfamA_u.js → MessageLog-BWdqhwMK.js} +1 -1
- package/public/assets/{ModelPrices-Bg-zVU9G.js → ModelPrices-BKoXr-BI.js} +1 -1
- package/public/assets/{Overview-ByF5CHrf.js → Overview-CbTkR8Az.js} +1 -1
- package/public/assets/{Pagination-CQk_Ps2L.js → Pagination-Ctujg2Tx.js} +1 -1
- package/public/assets/Register-27bT_1Vp.js +1 -0
- package/public/assets/{ResetPassword-BuNNDKRS.js → ResetPassword-ocNrLA-e.js} +1 -1
- package/public/assets/{Routing-95JQekwl.js → Routing-CXK8HKdf.js} +1 -1
- package/public/assets/Settings-D0Si7pTy.js +1 -0
- package/public/assets/{SetupStepAddProvider-CYgcYYLH.js → SetupStepAddProvider-CJemlPav.js} +1 -1
- package/public/assets/{SocialButtons-tQY9m0lz.js → SocialButtons-BkFlgg7b.js} +1 -1
- package/public/assets/{auth-B7LxODhZ.js → auth-DmX5tAfx.js} +1 -1
- package/public/assets/index-8qMFCIBN.css +1 -0
- package/public/assets/{index--P4PCJDm.js → index-DJfxuzym.js} +2 -2
- package/public/assets/{model-display-DmgfqEg8.js → model-display-CXVPJTCl.js} +1 -1
- package/public/assets/{overview-D5Ohx_UN.js → overview-M26L7EnR.js} +1 -1
- package/public/assets/{routing-Bf9SE33r.js → routing-C7oZEARV.js} +1 -1
- package/public/assets/{routing-utils-U3G1-aWH.js → routing-utils-BSvsQ0Op.js} +1 -1
- package/public/assets/vendor-pl6Q4jbW.js +1 -0
- package/public/index.html +4 -4
- package/public/assets/Register-BDPUwPD6.js +0 -1
- package/public/assets/Settings-DPVOsBPN.js +0 -1
- package/public/assets/index-BsqwVwB3.css +0 -1
- package/public/assets/vendor-K8fEFSq9.js +0 -1
|
@@ -10,6 +10,7 @@ const local_mode_constants_1 = require("../common/constants/local-mode.constants
|
|
|
10
10
|
const isLocalMode = process.env['MANIFEST_MODE'] === 'local';
|
|
11
11
|
const port = process.env['PORT'] ?? '3001';
|
|
12
12
|
const isDev = (process.env['NODE_ENV'] ?? '') !== 'production';
|
|
13
|
+
const hasEmailProvider = !!(process.env['MAILGUN_API_KEY'] && process.env['MAILGUN_DOMAIN']);
|
|
13
14
|
function createDatabaseConnection() {
|
|
14
15
|
if (isLocalMode) {
|
|
15
16
|
return null;
|
|
@@ -61,7 +62,7 @@ const authInstance = isLocalMode
|
|
|
61
62
|
emailAndPassword: {
|
|
62
63
|
enabled: true,
|
|
63
64
|
minPasswordLength: 8,
|
|
64
|
-
requireEmailVerification: !isDev && !isLocalMode,
|
|
65
|
+
requireEmailVerification: !isDev && !isLocalMode && hasEmailProvider,
|
|
65
66
|
sendResetPassword: async ({ user, url }) => {
|
|
66
67
|
const element = (0, reset_password_1.ResetPasswordEmail)({
|
|
67
68
|
userName: user.name,
|
|
@@ -78,7 +79,7 @@ const authInstance = isLocalMode
|
|
|
78
79
|
},
|
|
79
80
|
},
|
|
80
81
|
emailVerification: {
|
|
81
|
-
sendOnSignUp: !isLocalMode,
|
|
82
|
+
sendOnSignUp: !isLocalMode && hasEmailProvider,
|
|
82
83
|
autoSignInAfterVerification: true,
|
|
83
84
|
sendVerificationEmail: async ({ user, url }) => {
|
|
84
85
|
const element = (0, verify_email_1.VerifyEmailEmail)({
|
|
@@ -176,7 +176,7 @@ let ModelsDevSyncService = ModelsDevSyncService_1 = class ModelsDevSyncService {
|
|
|
176
176
|
}
|
|
177
177
|
const outputMods = model.modalities?.output?.map((m) => m.toLowerCase());
|
|
178
178
|
if (outputMods && outputMods.length > 0) {
|
|
179
|
-
return outputMods.
|
|
179
|
+
return outputMods.includes('text');
|
|
180
180
|
}
|
|
181
181
|
return true;
|
|
182
182
|
}
|
|
@@ -98,7 +98,7 @@ let PricingSyncService = PricingSyncService_1 = class PricingSyncService {
|
|
|
98
98
|
}
|
|
99
99
|
const outputModalities = model.architecture?.output_modalities?.map((m) => m.toLowerCase());
|
|
100
100
|
if (outputModalities && outputModalities.length > 0) {
|
|
101
|
-
return outputModalities.
|
|
101
|
+
return outputModalities.includes('text');
|
|
102
102
|
}
|
|
103
103
|
return true;
|
|
104
104
|
}
|
|
@@ -123,11 +123,17 @@ let ModelDiscoveryService = ModelDiscoveryService_1 = class ModelDiscoveryServic
|
|
|
123
123
|
...this.enrichModel(model, provider.provider),
|
|
124
124
|
authType: authType,
|
|
125
125
|
}));
|
|
126
|
-
|
|
126
|
+
const filtered = enriched.filter((model) => {
|
|
127
|
+
const mdEntry = this.modelsDevSync?.lookupModel(provider.provider, model.id);
|
|
128
|
+
if (mdEntry && mdEntry.toolCall === false)
|
|
129
|
+
return false;
|
|
130
|
+
return true;
|
|
131
|
+
});
|
|
132
|
+
provider.cached_models = filtered;
|
|
127
133
|
provider.models_fetched_at = new Date().toISOString();
|
|
128
134
|
await this.providerRepo.save(provider);
|
|
129
|
-
this.logger.log(`Discovered ${
|
|
130
|
-
return
|
|
135
|
+
this.logger.log(`Discovered ${filtered.length} models for provider ${provider.provider} (agent ${provider.agent_id})`);
|
|
136
|
+
return filtered;
|
|
131
137
|
}
|
|
132
138
|
async discoverAllForAgent(agentId) {
|
|
133
139
|
const providers = await this.providerRepo.find({
|
|
@@ -7,7 +7,8 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
};
|
|
8
8
|
var ProviderModelFetcherService_1;
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.ProviderModelFetcherService = exports.PROVIDER_CONFIGS = void 0;
|
|
10
|
+
exports.ProviderModelFetcherService = exports.PROVIDER_CONFIGS = exports.PROVIDER_NON_CHAT = exports.UNIVERSAL_NON_CHAT_RE = void 0;
|
|
11
|
+
exports.filterNonChatModels = filterNonChatModels;
|
|
11
12
|
const common_1 = require("@nestjs/common");
|
|
12
13
|
const ollama_1 = require("../common/constants/ollama");
|
|
13
14
|
const provider_base_url_1 = require("../routing/provider-base-url");
|
|
@@ -48,11 +49,10 @@ const parseOpenAI = createModelParser({
|
|
|
48
49
|
getId: (entry) => entry.id,
|
|
49
50
|
getDisplayName: (_entry, id) => id,
|
|
50
51
|
});
|
|
51
|
-
const OPENAI_NON_CHAT_RE = /(?:embed|tts|whisper|dall-e|moderation|davinci|babbage|^text-|audio|realtime|-transcribe|^sora|^gpt-3\.5-turbo-instruct)/i;
|
|
52
52
|
const OPENAI_DATE_SUFFIX_RE = /-\d{4}-\d{2}-\d{2}$/;
|
|
53
53
|
const OPENAI_RESPONSES_ONLY_RE = /(?:-codex(?!-mini-latest)|^gpt-5[^/]*-pro(?:-|$))/i;
|
|
54
|
-
function
|
|
55
|
-
const filtered = parseOpenAI(body, provider).filter((m) => !
|
|
54
|
+
function parseOpenAIDeduped(body, provider) {
|
|
55
|
+
const filtered = parseOpenAI(body, provider).filter((m) => !OPENAI_RESPONSES_ONLY_RE.test(m.id));
|
|
56
56
|
const ids = new Set(filtered.map((m) => m.id));
|
|
57
57
|
return filtered.filter((m) => {
|
|
58
58
|
if (!OPENAI_DATE_SUFFIX_RE.test(m.id))
|
|
@@ -61,9 +61,22 @@ function parseOpenAIChatOnly(body, provider) {
|
|
|
61
61
|
return !ids.has(alias);
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
exports.UNIVERSAL_NON_CHAT_RE = /(?:embed|tts|whisper|dall-e|imagen|cogview|wanx|sambert|paraformer|text-embedding|speech-to|voice-|audio-turbo)/i;
|
|
65
|
+
exports.PROVIDER_NON_CHAT = {
|
|
66
|
+
openai: /(?:moderation|davinci|babbage|^text-|realtime|-transcribe|^sora|^gpt-3\.5-turbo-instruct|audio)/i,
|
|
67
|
+
'openai-subscription': /(?:moderation|davinci|babbage|^text-|realtime|-transcribe|^sora|audio)/i,
|
|
68
|
+
gemini: /(?:^aqs-|nano-banana)/i,
|
|
69
|
+
mistral: /(?:^mistral-ocr)/i,
|
|
70
|
+
};
|
|
71
|
+
function filterNonChatModels(models, configKey) {
|
|
72
|
+
const providerFilter = exports.PROVIDER_NON_CHAT[configKey];
|
|
73
|
+
return models.filter((m) => {
|
|
74
|
+
if (exports.UNIVERSAL_NON_CHAT_RE.test(m.id))
|
|
75
|
+
return false;
|
|
76
|
+
if (providerFilter && providerFilter.test(m.id))
|
|
77
|
+
return false;
|
|
78
|
+
return true;
|
|
79
|
+
});
|
|
67
80
|
}
|
|
68
81
|
function bearerHeaders(key) {
|
|
69
82
|
return { Authorization: `Bearer ${key}` };
|
|
@@ -174,7 +187,7 @@ exports.PROVIDER_CONFIGS = {
|
|
|
174
187
|
openai: {
|
|
175
188
|
endpoint: 'https://api.openai.com/v1/models',
|
|
176
189
|
buildHeaders: bearerHeaders,
|
|
177
|
-
parse:
|
|
190
|
+
parse: parseOpenAIDeduped,
|
|
178
191
|
},
|
|
179
192
|
'openai-subscription': {
|
|
180
193
|
endpoint: 'https://chatgpt.com/backend-api/codex/models?client_version=0.99.0',
|
|
@@ -194,7 +207,7 @@ exports.PROVIDER_CONFIGS = {
|
|
|
194
207
|
mistral: {
|
|
195
208
|
endpoint: 'https://api.mistral.ai/v1/models',
|
|
196
209
|
buildHeaders: bearerHeaders,
|
|
197
|
-
parse:
|
|
210
|
+
parse: parseOpenAI,
|
|
198
211
|
},
|
|
199
212
|
moonshot: {
|
|
200
213
|
endpoint: 'https://api.moonshot.ai/v1/models',
|
|
@@ -321,7 +334,7 @@ let ProviderModelFetcherService = ProviderModelFetcherService_1 = class Provider
|
|
|
321
334
|
return [];
|
|
322
335
|
}
|
|
323
336
|
const body = await res.json();
|
|
324
|
-
return config.parse(body, providerId);
|
|
337
|
+
return filterNonChatModels(config.parse(body, providerId), configKey);
|
|
325
338
|
}
|
|
326
339
|
catch (err) {
|
|
327
340
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -60,7 +60,7 @@ function mapRole(role) {
|
|
|
60
60
|
return 'user';
|
|
61
61
|
return 'user';
|
|
62
62
|
}
|
|
63
|
-
function messageToContent(msg) {
|
|
63
|
+
function messageToContent(msg, signatureLookup) {
|
|
64
64
|
const parts = [];
|
|
65
65
|
if (typeof msg.content === 'string') {
|
|
66
66
|
parts.push({ text: msg.content });
|
|
@@ -74,12 +74,20 @@ function messageToContent(msg) {
|
|
|
74
74
|
}
|
|
75
75
|
if (Array.isArray(msg.tool_calls)) {
|
|
76
76
|
for (const tc of msg.tool_calls) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
77
|
+
const functionCall = {
|
|
78
|
+
name: tc.function.name,
|
|
79
|
+
args: safeParseArgs(tc.function.arguments),
|
|
80
|
+
};
|
|
81
|
+
const sig = tc.thought_signature;
|
|
82
|
+
if (sig) {
|
|
83
|
+
functionCall.thought_signature = sig;
|
|
84
|
+
}
|
|
85
|
+
else if (signatureLookup) {
|
|
86
|
+
const cached = signatureLookup(tc.id);
|
|
87
|
+
if (cached)
|
|
88
|
+
functionCall.thought_signature = cached;
|
|
89
|
+
}
|
|
90
|
+
parts.push({ functionCall });
|
|
83
91
|
}
|
|
84
92
|
}
|
|
85
93
|
if (msg.role === 'tool' && typeof msg.content === 'string') {
|
|
@@ -118,7 +126,7 @@ function convertTools(tools) {
|
|
|
118
126
|
return undefined;
|
|
119
127
|
return [{ functionDeclarations: declarations }];
|
|
120
128
|
}
|
|
121
|
-
function toGoogleRequest(body, _model) {
|
|
129
|
+
function toGoogleRequest(body, _model, signatureLookup) {
|
|
122
130
|
const messages = body.messages || [];
|
|
123
131
|
const contents = [];
|
|
124
132
|
const systemMsgs = messages.filter((m) => m.role === 'system');
|
|
@@ -129,7 +137,7 @@ function toGoogleRequest(body, _model) {
|
|
|
129
137
|
for (const msg of messages) {
|
|
130
138
|
if (msg.role === 'system')
|
|
131
139
|
continue;
|
|
132
|
-
const content = messageToContent(msg);
|
|
140
|
+
const content = messageToContent(msg, signatureLookup);
|
|
133
141
|
if (content)
|
|
134
142
|
contents.push(content);
|
|
135
143
|
}
|
|
@@ -174,16 +182,28 @@ function fromGoogleResponse(googleResp, model) {
|
|
|
174
182
|
textContent += part.text;
|
|
175
183
|
if (part.functionCall) {
|
|
176
184
|
const fc = part.functionCall;
|
|
177
|
-
|
|
185
|
+
const toolCall = {
|
|
178
186
|
id: `call_${(0, crypto_1.randomUUID)()}`,
|
|
179
187
|
type: 'function',
|
|
180
188
|
function: { name: fc.name, arguments: JSON.stringify(fc.args) },
|
|
181
|
-
}
|
|
189
|
+
};
|
|
190
|
+
if (fc.thought_signature)
|
|
191
|
+
toolCall.thought_signature = fc.thought_signature;
|
|
192
|
+
toolCalls.push(toolCall);
|
|
182
193
|
}
|
|
183
194
|
}
|
|
184
195
|
const message = { role: 'assistant', content: textContent || null };
|
|
185
196
|
if (toolCalls.length > 0)
|
|
186
197
|
message.tool_calls = toolCalls;
|
|
198
|
+
const extractedSignatures = [];
|
|
199
|
+
for (const tc of toolCalls) {
|
|
200
|
+
if (tc.thought_signature && typeof tc.id === 'string') {
|
|
201
|
+
extractedSignatures.push({
|
|
202
|
+
toolCallId: tc.id,
|
|
203
|
+
signature: tc.thought_signature,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
187
207
|
const usage = googleResp.usageMetadata;
|
|
188
208
|
return {
|
|
189
209
|
id: `chatcmpl-${(0, crypto_1.randomUUID)()}`,
|
|
@@ -203,6 +223,7 @@ function fromGoogleResponse(googleResp, model) {
|
|
|
203
223
|
cache_creation_tokens: 0,
|
|
204
224
|
}
|
|
205
225
|
: undefined,
|
|
226
|
+
...(extractedSignatures.length > 0 ? { _extractedSignatures: extractedSignatures } : {}),
|
|
206
227
|
};
|
|
207
228
|
}
|
|
208
229
|
function mapFinishReason(candidate, hasToolCalls = false) {
|
|
@@ -236,12 +257,15 @@ function transformGoogleStreamChunk(chunk, model) {
|
|
|
236
257
|
for (const part of parts) {
|
|
237
258
|
if (part.functionCall) {
|
|
238
259
|
const fc = part.functionCall;
|
|
239
|
-
|
|
260
|
+
const toolCall = {
|
|
240
261
|
index: toolCalls.length,
|
|
241
262
|
id: `call_${(0, crypto_1.randomUUID)()}`,
|
|
242
263
|
type: 'function',
|
|
243
264
|
function: { name: fc.name, arguments: JSON.stringify(fc.args ?? {}) },
|
|
244
|
-
}
|
|
265
|
+
};
|
|
266
|
+
if (fc.thought_signature)
|
|
267
|
+
toolCall.thought_signature = fc.thought_signature;
|
|
268
|
+
toolCalls.push(toolCall);
|
|
245
269
|
}
|
|
246
270
|
}
|
|
247
271
|
let result = '';
|
|
@@ -22,7 +22,7 @@ function stripModelPrefix(model, endpointKey) {
|
|
|
22
22
|
let ProviderClient = ProviderClient_1 = class ProviderClient {
|
|
23
23
|
logger = new common_1.Logger(ProviderClient_1.name);
|
|
24
24
|
async forward(opts) {
|
|
25
|
-
const { provider, apiKey, model, body, stream, signal, extraHeaders, customEndpoint, authType, } = opts;
|
|
25
|
+
const { provider, apiKey, model, body, stream, signal, extraHeaders, customEndpoint, authType, signatureLookup, } = opts;
|
|
26
26
|
let endpoint;
|
|
27
27
|
let endpointKey;
|
|
28
28
|
if (customEndpoint) {
|
|
@@ -55,7 +55,7 @@ let ProviderClient = ProviderClient_1 = class ProviderClient {
|
|
|
55
55
|
if (stream)
|
|
56
56
|
url += '&alt=sse';
|
|
57
57
|
headers = endpoint.buildHeaders(apiKey, authType);
|
|
58
|
-
requestBody = (0, provider_client_converters_1.toGoogleRequest)(body, bareModel);
|
|
58
|
+
requestBody = (0, provider_client_converters_1.toGoogleRequest)(body, bareModel, signatureLookup);
|
|
59
59
|
}
|
|
60
60
|
else if (isAnthropic) {
|
|
61
61
|
url = `${endpoint.baseUrl}${endpoint.buildPath(bareModel)}`;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ProxyExceptionFilter = void 0;
|
|
13
|
+
const common_1 = require("@nestjs/common");
|
|
14
|
+
const config_1 = require("@nestjs/config");
|
|
15
|
+
const proxy_friendly_response_1 = require("./proxy-friendly-response");
|
|
16
|
+
const AUTH_ERROR_MESSAGES = {
|
|
17
|
+
'Authorization header required': 'Missing API key. Set your Manifest key (starts with mnfst_) as the Bearer token.',
|
|
18
|
+
'Empty token': 'Bearer token is empty — paste your Manifest API key into the authorization field.',
|
|
19
|
+
'Invalid API key format': 'That doesn\'t look like a Manifest key. They start with "mnfst_" — check your dashboard.',
|
|
20
|
+
'API key expired': 'This API key expired. Generate a new one from your Manifest dashboard',
|
|
21
|
+
'Invalid API key': "This API key wasn't recognized — it may have been rotated or deleted. Check your dashboard for the current key.",
|
|
22
|
+
};
|
|
23
|
+
const PASSTHROUGH_STATUSES = new Set([429]);
|
|
24
|
+
let ProxyExceptionFilter = class ProxyExceptionFilter {
|
|
25
|
+
config;
|
|
26
|
+
constructor(config) {
|
|
27
|
+
this.config = config;
|
|
28
|
+
}
|
|
29
|
+
catch(exception, host) {
|
|
30
|
+
const ctx = host.switchToHttp();
|
|
31
|
+
const req = ctx.getRequest();
|
|
32
|
+
const res = ctx.getResponse();
|
|
33
|
+
const status = exception.getStatus();
|
|
34
|
+
const message = exception.message;
|
|
35
|
+
if (PASSTHROUGH_STATUSES.has(status)) {
|
|
36
|
+
const response = exception.getResponse();
|
|
37
|
+
res
|
|
38
|
+
.status(status)
|
|
39
|
+
.json(typeof response === 'string'
|
|
40
|
+
? { error: { message: response, type: 'proxy_error' } }
|
|
41
|
+
: response);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const isStream = req.body?.stream === true;
|
|
45
|
+
const ingestionCtx = req
|
|
46
|
+
.ingestionContext;
|
|
47
|
+
const agentName = ingestionCtx?.agentName;
|
|
48
|
+
const friendly = AUTH_ERROR_MESSAGES[message];
|
|
49
|
+
if (friendly) {
|
|
50
|
+
const dashboardUrl = (0, proxy_friendly_response_1.getDashboardUrl)(this.config, agentName);
|
|
51
|
+
const content = message === 'API key expired'
|
|
52
|
+
? `${friendly}: ${dashboardUrl}`
|
|
53
|
+
: `${friendly}\n\nDashboard: ${dashboardUrl}`;
|
|
54
|
+
(0, proxy_friendly_response_1.sendFriendlyResponse)(res, content, isStream);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const content = status >= 500 ? 'Something broke on our end. Try again shortly.' : message;
|
|
58
|
+
(0, proxy_friendly_response_1.sendFriendlyResponse)(res, content, isStream);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
exports.ProxyExceptionFilter = ProxyExceptionFilter;
|
|
62
|
+
exports.ProxyExceptionFilter = ProxyExceptionFilter = __decorate([
|
|
63
|
+
(0, common_1.Injectable)(),
|
|
64
|
+
(0, common_1.Catch)(common_1.HttpException),
|
|
65
|
+
__metadata("design:paramtypes", [config_1.ConfigService])
|
|
66
|
+
], ProxyExceptionFilter);
|
|
67
|
+
//# sourceMappingURL=proxy-exception.filter.js.map
|
|
@@ -52,7 +52,7 @@ let ProxyFallbackService = ProxyFallbackService_1 = class ProxyFallbackService {
|
|
|
52
52
|
this.copilotToken = copilotToken;
|
|
53
53
|
this.pricingCache = pricingCache;
|
|
54
54
|
}
|
|
55
|
-
async tryFallbacks(agentId, userId, fallbackModels, body, stream, sessionKey, primaryModel, signal, primaryProvider, primaryAuthType) {
|
|
55
|
+
async tryFallbacks(agentId, userId, fallbackModels, body, stream, sessionKey, primaryModel, signal, primaryProvider, primaryAuthType, signatureLookup) {
|
|
56
56
|
const failures = [];
|
|
57
57
|
const failedAuthByProvider = new Map();
|
|
58
58
|
if (primaryProvider && primaryAuthType) {
|
|
@@ -101,6 +101,7 @@ let ProxyFallbackService = ProxyFallbackService_1 = class ProxyFallbackService {
|
|
|
101
101
|
authType,
|
|
102
102
|
resourceUrl: resolvedCredentials.resourceUrl,
|
|
103
103
|
providerRegion,
|
|
104
|
+
signatureLookup,
|
|
104
105
|
});
|
|
105
106
|
if (forward.response.ok) {
|
|
106
107
|
return { success: { forward, model, provider, fallbackIndex: i }, failures };
|
|
@@ -143,7 +144,7 @@ let ProxyFallbackService = ProxyFallbackService_1 = class ProxyFallbackService {
|
|
|
143
144
|
}
|
|
144
145
|
}
|
|
145
146
|
async forwardToProvider(opts) {
|
|
146
|
-
const { provider, body, stream, signal, authType, resourceUrl, providerRegion } = opts;
|
|
147
|
+
const { provider, body, stream, signal, authType, resourceUrl, providerRegion, signatureLookup, } = opts;
|
|
147
148
|
const extraHeaders = {};
|
|
148
149
|
if (provider === 'xai') {
|
|
149
150
|
extraHeaders['x-grok-conv-id'] = opts.sessionKey;
|
|
@@ -188,6 +189,7 @@ let ProxyFallbackService = ProxyFallbackService_1 = class ProxyFallbackService {
|
|
|
188
189
|
extraHeaders: hasExtraHeaders ? extraHeaders : undefined,
|
|
189
190
|
customEndpoint,
|
|
190
191
|
authType,
|
|
192
|
+
signatureLookup,
|
|
191
193
|
});
|
|
192
194
|
}
|
|
193
195
|
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getDashboardUrl = getDashboardUrl;
|
|
4
|
+
exports.buildFriendlyResponse = buildFriendlyResponse;
|
|
5
|
+
exports.sendFriendlyResponse = sendFriendlyResponse;
|
|
6
|
+
const crypto_1 = require("crypto");
|
|
7
|
+
function getDashboardUrl(config, agentName) {
|
|
8
|
+
const baseUrl = config.get('app.betterAuthUrl') ||
|
|
9
|
+
`http://localhost:${config.get('app.port', 3001)}`;
|
|
10
|
+
const path = agentName ? `/agents/${encodeURIComponent(agentName)}` : '/routing';
|
|
11
|
+
return `${baseUrl}${path}`;
|
|
12
|
+
}
|
|
13
|
+
function buildFriendlyResponse(content, stream, reason = 'friendly_error') {
|
|
14
|
+
const id = `chatcmpl-manifest-${(0, crypto_1.randomUUID)()}`;
|
|
15
|
+
const created = Math.floor(Date.now() / 1000);
|
|
16
|
+
const meta = {
|
|
17
|
+
tier: 'simple',
|
|
18
|
+
model: 'manifest',
|
|
19
|
+
provider: 'manifest',
|
|
20
|
+
confidence: 1,
|
|
21
|
+
reason,
|
|
22
|
+
};
|
|
23
|
+
if (stream) {
|
|
24
|
+
const chunk = {
|
|
25
|
+
id,
|
|
26
|
+
object: 'chat.completion.chunk',
|
|
27
|
+
created,
|
|
28
|
+
model: 'manifest',
|
|
29
|
+
choices: [{ index: 0, delta: { role: 'assistant', content }, finish_reason: 'stop' }],
|
|
30
|
+
};
|
|
31
|
+
const ssePayload = `data: ${JSON.stringify(chunk)}\n\ndata: [DONE]\n\n`;
|
|
32
|
+
const encoder = new TextEncoder();
|
|
33
|
+
const body = new ReadableStream({
|
|
34
|
+
start(controller) {
|
|
35
|
+
controller.enqueue(encoder.encode(ssePayload));
|
|
36
|
+
controller.close();
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
return {
|
|
40
|
+
forward: {
|
|
41
|
+
response: new Response(body, {
|
|
42
|
+
status: 200,
|
|
43
|
+
headers: { 'Content-Type': 'text/event-stream' },
|
|
44
|
+
}),
|
|
45
|
+
isGoogle: false,
|
|
46
|
+
isAnthropic: false,
|
|
47
|
+
isChatGpt: false,
|
|
48
|
+
},
|
|
49
|
+
meta,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const responseBody = {
|
|
53
|
+
id,
|
|
54
|
+
object: 'chat.completion',
|
|
55
|
+
created,
|
|
56
|
+
model: 'manifest',
|
|
57
|
+
choices: [
|
|
58
|
+
{
|
|
59
|
+
index: 0,
|
|
60
|
+
message: { role: 'assistant', content },
|
|
61
|
+
finish_reason: 'stop',
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
|
|
65
|
+
};
|
|
66
|
+
return {
|
|
67
|
+
forward: {
|
|
68
|
+
response: new Response(JSON.stringify(responseBody), {
|
|
69
|
+
status: 200,
|
|
70
|
+
headers: { 'Content-Type': 'application/json' },
|
|
71
|
+
}),
|
|
72
|
+
isGoogle: false,
|
|
73
|
+
isAnthropic: false,
|
|
74
|
+
isChatGpt: false,
|
|
75
|
+
},
|
|
76
|
+
meta,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function sendFriendlyResponse(res, content, stream) {
|
|
80
|
+
const id = `chatcmpl-manifest-${(0, crypto_1.randomUUID)()}`;
|
|
81
|
+
const created = Math.floor(Date.now() / 1000);
|
|
82
|
+
if (stream) {
|
|
83
|
+
const chunk = {
|
|
84
|
+
id,
|
|
85
|
+
object: 'chat.completion.chunk',
|
|
86
|
+
created,
|
|
87
|
+
model: 'manifest',
|
|
88
|
+
choices: [{ index: 0, delta: { role: 'assistant', content }, finish_reason: 'stop' }],
|
|
89
|
+
};
|
|
90
|
+
const ssePayload = `data: ${JSON.stringify(chunk)}\n\ndata: [DONE]\n\n`;
|
|
91
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
92
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
93
|
+
res.setHeader('Connection', 'keep-alive');
|
|
94
|
+
res.status(200).send(ssePayload);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
const responseBody = {
|
|
98
|
+
id,
|
|
99
|
+
object: 'chat.completion',
|
|
100
|
+
created,
|
|
101
|
+
model: 'manifest',
|
|
102
|
+
choices: [
|
|
103
|
+
{
|
|
104
|
+
index: 0,
|
|
105
|
+
message: { role: 'assistant', content },
|
|
106
|
+
finish_reason: 'stop',
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
|
|
110
|
+
};
|
|
111
|
+
res.setHeader('Content-Type', 'application/json');
|
|
112
|
+
res.status(200).json(responseBody);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=proxy-friendly-response.js.map
|
|
@@ -36,7 +36,7 @@ let ProxyRateLimiter = class ProxyRateLimiter {
|
|
|
36
36
|
entry = { count: 0, windowStart: now };
|
|
37
37
|
}
|
|
38
38
|
if (entry.count >= RATE_MAX_REQUESTS) {
|
|
39
|
-
throw new common_1.HttpException('
|
|
39
|
+
throw new common_1.HttpException('Too many requests — wait a few seconds and retry.', common_1.HttpStatus.TOO_MANY_REQUESTS);
|
|
40
40
|
}
|
|
41
41
|
entry.count++;
|
|
42
42
|
this.rates.set(userId, entry);
|
|
@@ -45,7 +45,7 @@ let ProxyRateLimiter = class ProxyRateLimiter {
|
|
|
45
45
|
acquireSlot(userId) {
|
|
46
46
|
const current = this.concurrency.get(userId) ?? 0;
|
|
47
47
|
if (current >= CONCURRENCY_MAX) {
|
|
48
|
-
throw new common_1.HttpException('Too many concurrent requests.
|
|
48
|
+
throw new common_1.HttpException('Too many concurrent requests. Give it a moment.', common_1.HttpStatus.TOO_MANY_REQUESTS);
|
|
49
49
|
}
|
|
50
50
|
this.concurrency.set(userId, current + 1);
|
|
51
51
|
}
|
|
@@ -107,10 +107,22 @@ function recordFallbackFailures(ctx, meta, failedFallbacks, recorder) {
|
|
|
107
107
|
}
|
|
108
108
|
return new Date(fallbackBaseTime + (failures.length + 1) * 100).toISOString();
|
|
109
109
|
}
|
|
110
|
-
async function handleStreamResponse(res, forward, meta, metaHeaders, providerClient) {
|
|
110
|
+
async function handleStreamResponse(res, forward, meta, metaHeaders, providerClient, signatureCache, sessionKey) {
|
|
111
111
|
(0, stream_writer_1.initSseHeaders)(res, metaHeaders);
|
|
112
112
|
if (forward.isGoogle) {
|
|
113
|
-
return (0, stream_writer_1.pipeStream)(forward.response.body, res, (chunk) =>
|
|
113
|
+
return (0, stream_writer_1.pipeStream)(forward.response.body, res, (chunk) => {
|
|
114
|
+
const result = providerClient.convertGoogleStreamChunk(chunk, meta.model);
|
|
115
|
+
if (signatureCache && sessionKey && result) {
|
|
116
|
+
const sigRe = /"thought_signature"\s*:\s*"([^"]+)"/g;
|
|
117
|
+
const idRe = /"id"\s*:\s*"([^"]+)"/g;
|
|
118
|
+
const ids = [...result.matchAll(idRe)].map((m) => m[1]);
|
|
119
|
+
const sigs = [...result.matchAll(sigRe)].map((m) => m[1]);
|
|
120
|
+
for (let i = 0; i < Math.min(ids.length, sigs.length); i++) {
|
|
121
|
+
signatureCache.store(sessionKey, ids[i], sigs[i]);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
125
|
+
});
|
|
114
126
|
}
|
|
115
127
|
if (forward.isAnthropic) {
|
|
116
128
|
return (0, stream_writer_1.pipeStream)(forward.response.body, res, providerClient.createAnthropicStreamTransformer(meta.model));
|
|
@@ -120,11 +132,19 @@ async function handleStreamResponse(res, forward, meta, metaHeaders, providerCli
|
|
|
120
132
|
}
|
|
121
133
|
return (0, stream_writer_1.pipeStream)(forward.response.body, res);
|
|
122
134
|
}
|
|
123
|
-
async function handleNonStreamResponse(res, forward, meta, metaHeaders, providerClient) {
|
|
135
|
+
async function handleNonStreamResponse(res, forward, meta, metaHeaders, providerClient, signatureCache, sessionKey) {
|
|
124
136
|
let responseBody;
|
|
125
137
|
if (forward.isGoogle) {
|
|
126
138
|
const googleData = (await forward.response.json());
|
|
127
139
|
responseBody = providerClient.convertGoogleResponse(googleData, meta.model);
|
|
140
|
+
if (signatureCache && sessionKey) {
|
|
141
|
+
const sigs = responseBody?._extractedSignatures;
|
|
142
|
+
if (sigs) {
|
|
143
|
+
for (const s of sigs)
|
|
144
|
+
signatureCache.store(sessionKey, s.toolCallId, s.signature);
|
|
145
|
+
delete responseBody._extractedSignatures;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
128
148
|
}
|
|
129
149
|
else if (forward.isAnthropic) {
|
|
130
150
|
const anthropicData = (await forward.response.json());
|
|
@@ -22,7 +22,10 @@ const proxy_service_1 = require("./proxy.service");
|
|
|
22
22
|
const proxy_rate_limiter_1 = require("./proxy-rate-limiter");
|
|
23
23
|
const provider_client_1 = require("./provider-client");
|
|
24
24
|
const proxy_message_recorder_1 = require("./proxy-message-recorder");
|
|
25
|
+
const thought_signature_cache_1 = require("./thought-signature-cache");
|
|
25
26
|
const proxy_response_handler_1 = require("./proxy-response-handler");
|
|
27
|
+
const proxy_exception_filter_1 = require("./proxy-exception.filter");
|
|
28
|
+
const proxy_friendly_response_1 = require("./proxy-friendly-response");
|
|
26
29
|
const MAX_SEEN_USERS = 10_000;
|
|
27
30
|
const SEEN_USER_TTL_MS = 24 * 60 * 60 * 1000;
|
|
28
31
|
let ProxyController = ProxyController_1 = class ProxyController {
|
|
@@ -30,13 +33,15 @@ let ProxyController = ProxyController_1 = class ProxyController {
|
|
|
30
33
|
rateLimiter;
|
|
31
34
|
providerClient;
|
|
32
35
|
recorder;
|
|
36
|
+
signatureCache;
|
|
33
37
|
logger = new common_1.Logger(ProxyController_1.name);
|
|
34
38
|
seenUsers = new Map();
|
|
35
|
-
constructor(proxyService, rateLimiter, providerClient, recorder) {
|
|
39
|
+
constructor(proxyService, rateLimiter, providerClient, recorder, signatureCache) {
|
|
36
40
|
this.proxyService = proxyService;
|
|
37
41
|
this.rateLimiter = rateLimiter;
|
|
38
42
|
this.providerClient = providerClient;
|
|
39
43
|
this.recorder = recorder;
|
|
44
|
+
this.signatureCache = signatureCache;
|
|
40
45
|
}
|
|
41
46
|
async chatCompletions(req, res) {
|
|
42
47
|
const { userId } = req.ingestionContext;
|
|
@@ -74,10 +79,10 @@ let ProxyController = ProxyController_1 = class ProxyController {
|
|
|
74
79
|
let streamUsage = null;
|
|
75
80
|
if (isStream && providerResponse.body) {
|
|
76
81
|
headersSent = true;
|
|
77
|
-
streamUsage = await (0, proxy_response_handler_1.handleStreamResponse)(res, forward, meta, metaHeaders, this.providerClient);
|
|
82
|
+
streamUsage = await (0, proxy_response_handler_1.handleStreamResponse)(res, forward, meta, metaHeaders, this.providerClient, this.signatureCache, sessionKey);
|
|
78
83
|
}
|
|
79
84
|
else {
|
|
80
|
-
streamUsage = await (0, proxy_response_handler_1.handleNonStreamResponse)(res, forward, meta, metaHeaders, this.providerClient);
|
|
85
|
+
streamUsage = await (0, proxy_response_handler_1.handleNonStreamResponse)(res, forward, meta, metaHeaders, this.providerClient, this.signatureCache, sessionKey);
|
|
81
86
|
}
|
|
82
87
|
(0, proxy_response_handler_1.recordSuccess)(req.ingestionContext, meta, streamUsage, fallbackSuccessTs, this.recorder, traceId, sessionKey, startTime);
|
|
83
88
|
}
|
|
@@ -98,10 +103,18 @@ let ProxyController = ProxyController_1 = class ProxyController {
|
|
|
98
103
|
res.end();
|
|
99
104
|
return;
|
|
100
105
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
106
|
+
if (status === 429) {
|
|
107
|
+
const response = err instanceof common_1.HttpException ? err.getResponse() : message;
|
|
108
|
+
res
|
|
109
|
+
.status(429)
|
|
110
|
+
.json(typeof response === 'string'
|
|
111
|
+
? { error: { message: response, type: 'proxy_error' } }
|
|
112
|
+
: response);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const isStream = req.body?.stream === true;
|
|
116
|
+
const clientMessage = status >= 500 ? 'Something broke on our end. Try again shortly.' : message;
|
|
117
|
+
(0, proxy_friendly_response_1.sendFriendlyResponse)(res, clientMessage, isStream);
|
|
105
118
|
}
|
|
106
119
|
finally {
|
|
107
120
|
if (slotAcquired)
|
|
@@ -150,10 +163,12 @@ exports.ProxyController = ProxyController = ProxyController_1 = __decorate([
|
|
|
150
163
|
(0, common_1.Controller)('v1'),
|
|
151
164
|
(0, public_decorator_1.Public)(),
|
|
152
165
|
(0, common_1.UseGuards)(agent_key_auth_guard_1.AgentKeyAuthGuard),
|
|
166
|
+
(0, common_1.UseFilters)(proxy_exception_filter_1.ProxyExceptionFilter),
|
|
153
167
|
(0, throttler_1.SkipThrottle)(),
|
|
154
168
|
__metadata("design:paramtypes", [proxy_service_1.ProxyService,
|
|
155
169
|
proxy_rate_limiter_1.ProxyRateLimiter,
|
|
156
170
|
provider_client_1.ProviderClient,
|
|
157
|
-
proxy_message_recorder_1.ProxyMessageRecorder
|
|
171
|
+
proxy_message_recorder_1.ProxyMessageRecorder,
|
|
172
|
+
thought_signature_cache_1.ThoughtSignatureCache])
|
|
158
173
|
], ProxyController);
|
|
159
174
|
//# sourceMappingURL=proxy.controller.js.map
|
|
@@ -27,6 +27,8 @@ const proxy_message_recorder_1 = require("./proxy-message-recorder");
|
|
|
27
27
|
const proxy_message_dedup_1 = require("./proxy-message-dedup");
|
|
28
28
|
const session_momentum_service_1 = require("./session-momentum.service");
|
|
29
29
|
const copilot_token_service_1 = require("./copilot-token.service");
|
|
30
|
+
const thought_signature_cache_1 = require("./thought-signature-cache");
|
|
31
|
+
const proxy_exception_filter_1 = require("./proxy-exception.filter");
|
|
30
32
|
let ProxyModule = class ProxyModule {
|
|
31
33
|
};
|
|
32
34
|
exports.ProxyModule = ProxyModule;
|
|
@@ -52,6 +54,8 @@ exports.ProxyModule = ProxyModule = __decorate([
|
|
|
52
54
|
proxy_message_dedup_1.ProxyMessageDedup,
|
|
53
55
|
session_momentum_service_1.SessionMomentumService,
|
|
54
56
|
copilot_token_service_1.CopilotTokenService,
|
|
57
|
+
thought_signature_cache_1.ThoughtSignatureCache,
|
|
58
|
+
proxy_exception_filter_1.ProxyExceptionFilter,
|
|
55
59
|
],
|
|
56
60
|
})
|
|
57
61
|
], ProxyModule);
|