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.
Files changed (49) hide show
  1. package/dist/backend/auth/auth.instance.js +3 -2
  2. package/dist/backend/database/models-dev-sync.service.js +1 -1
  3. package/dist/backend/database/pricing-sync.service.js +1 -1
  4. package/dist/backend/model-discovery/model-discovery.service.js +9 -3
  5. package/dist/backend/model-discovery/provider-model-fetcher.service.js +23 -10
  6. package/dist/backend/routing/proxy/google-adapter.js +37 -13
  7. package/dist/backend/routing/proxy/provider-client.js +2 -2
  8. package/dist/backend/routing/proxy/proxy-exception.filter.js +67 -0
  9. package/dist/backend/routing/proxy/proxy-fallback.service.js +4 -2
  10. package/dist/backend/routing/proxy/proxy-friendly-response.js +115 -0
  11. package/dist/backend/routing/proxy/proxy-rate-limiter.js +2 -2
  12. package/dist/backend/routing/proxy/proxy-response-handler.js +23 -3
  13. package/dist/backend/routing/proxy/proxy.controller.js +23 -8
  14. package/dist/backend/routing/proxy/proxy.module.js +4 -0
  15. package/dist/backend/routing/proxy/proxy.service.js +23 -86
  16. package/dist/backend/routing/proxy/thought-signature-cache.js +45 -0
  17. package/dist/backend/routing/routing-core/tier-auto-assign.service.js +9 -4
  18. package/dist/openclaw.plugin.json +1 -1
  19. package/openclaw.plugin.json +1 -1
  20. package/package.json +1 -1
  21. package/public/assets/{Account-B_oSTrdf.js → Account-y_dgXY3t.js} +1 -1
  22. package/public/assets/{AuthBadge-CN6tvI2S.js → AuthBadge-zJLfTd7g.js} +1 -1
  23. package/public/assets/{CopyButton-BrMDM22y.js → CopyButton-CZjeoxZT.js} +1 -1
  24. package/public/assets/{Help-BCxDh7o6.js → Help-Bfmf4yw8.js} +1 -1
  25. package/public/assets/{Limits-HwYePaDb.js → Limits-B9jYN_38.js} +1 -1
  26. package/public/assets/{Login-BWgCdMOB.js → Login-U_w8MjSD.js} +1 -1
  27. package/public/assets/{MessageLog-TkfamA_u.js → MessageLog-BWdqhwMK.js} +1 -1
  28. package/public/assets/{ModelPrices-Bg-zVU9G.js → ModelPrices-BKoXr-BI.js} +1 -1
  29. package/public/assets/{Overview-ByF5CHrf.js → Overview-CbTkR8Az.js} +1 -1
  30. package/public/assets/{Pagination-CQk_Ps2L.js → Pagination-Ctujg2Tx.js} +1 -1
  31. package/public/assets/Register-27bT_1Vp.js +1 -0
  32. package/public/assets/{ResetPassword-BuNNDKRS.js → ResetPassword-ocNrLA-e.js} +1 -1
  33. package/public/assets/{Routing-95JQekwl.js → Routing-CXK8HKdf.js} +1 -1
  34. package/public/assets/Settings-D0Si7pTy.js +1 -0
  35. package/public/assets/{SetupStepAddProvider-CYgcYYLH.js → SetupStepAddProvider-CJemlPav.js} +1 -1
  36. package/public/assets/{SocialButtons-tQY9m0lz.js → SocialButtons-BkFlgg7b.js} +1 -1
  37. package/public/assets/{auth-B7LxODhZ.js → auth-DmX5tAfx.js} +1 -1
  38. package/public/assets/index-8qMFCIBN.css +1 -0
  39. package/public/assets/{index--P4PCJDm.js → index-DJfxuzym.js} +2 -2
  40. package/public/assets/{model-display-DmgfqEg8.js → model-display-CXVPJTCl.js} +1 -1
  41. package/public/assets/{overview-D5Ohx_UN.js → overview-M26L7EnR.js} +1 -1
  42. package/public/assets/{routing-Bf9SE33r.js → routing-C7oZEARV.js} +1 -1
  43. package/public/assets/{routing-utils-U3G1-aWH.js → routing-utils-BSvsQ0Op.js} +1 -1
  44. package/public/assets/vendor-pl6Q4jbW.js +1 -0
  45. package/public/index.html +4 -4
  46. package/public/assets/Register-BDPUwPD6.js +0 -1
  47. package/public/assets/Settings-DPVOsBPN.js +0 -1
  48. package/public/assets/index-BsqwVwB3.css +0 -1
  49. 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.every((m) => m === 'text');
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.every((m) => m === 'text');
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
- provider.cached_models = enriched;
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 ${enriched.length} models for provider ${provider.provider} (agent ${provider.agent_id})`);
130
- return enriched;
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 parseOpenAIChatOnly(body, provider) {
55
- const filtered = parseOpenAI(body, provider).filter((m) => !OPENAI_NON_CHAT_RE.test(m.id) && !OPENAI_RESPONSES_ONLY_RE.test(m.id));
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
- const MISTRAL_NON_CHAT_RE = /(?:^mistral-ocr|embed)/i;
65
- function parseMistralChatOnly(body, provider) {
66
- return parseOpenAI(body, provider).filter((m) => !MISTRAL_NON_CHAT_RE.test(m.id));
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: parseOpenAIChatOnly,
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: parseMistralChatOnly,
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
- parts.push({
78
- functionCall: {
79
- name: tc.function.name,
80
- args: safeParseArgs(tc.function.arguments),
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
- toolCalls.push({
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
- toolCalls.push({
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('Rate limit exceeded. Try again later.', common_1.HttpStatus.TOO_MANY_REQUESTS);
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. Try again later.', common_1.HttpStatus.TOO_MANY_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) => providerClient.convertGoogleStreamChunk(chunk, meta.model));
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
- const clientMessage = status >= 500 ? 'Internal proxy error' : message;
102
- res.status(status).json({
103
- error: { message: clientMessage, type: 'proxy_error' },
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);