manifest 5.31.1 → 5.32.0
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/database/migrations/1773600000000-AddProviderRegion.js +13 -0
- package/dist/backend/entities/user-provider.entity.js +5 -0
- package/dist/backend/routing/dto/routing.dto.js +6 -0
- package/dist/backend/routing/model-discovery/model-discovery.module.js +2 -1
- package/dist/backend/routing/model-discovery/model-discovery.service.js +26 -4
- package/dist/backend/routing/model-discovery/provider-model-fetcher.service.js +46 -1
- package/dist/backend/routing/proxy/provider-endpoints.js +7 -0
- package/dist/backend/routing/proxy/proxy.service.js +11 -5
- package/dist/backend/routing/qwen-region.js +51 -0
- package/dist/backend/routing/routing.controller.js +14 -1
- package/dist/backend/routing/routing.service.js +64 -7
- package/dist/index.js +1 -1
- package/dist/local-mode.js +1 -1
- package/dist/openclaw.plugin.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AddProviderRegion1773600000000 = void 0;
|
|
4
|
+
class AddProviderRegion1773600000000 {
|
|
5
|
+
async up(queryRunner) {
|
|
6
|
+
await queryRunner.query(`ALTER TABLE "user_providers" ADD COLUMN IF NOT EXISTS "region" varchar DEFAULT NULL`);
|
|
7
|
+
}
|
|
8
|
+
async down(queryRunner) {
|
|
9
|
+
await queryRunner.query(`ALTER TABLE "user_providers" DROP COLUMN IF EXISTS "region"`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
exports.AddProviderRegion1773600000000 = AddProviderRegion1773600000000;
|
|
13
|
+
//# sourceMappingURL=1773600000000-AddProviderRegion.js.map
|
|
@@ -20,6 +20,7 @@ let UserProvider = class UserProvider {
|
|
|
20
20
|
api_key_encrypted;
|
|
21
21
|
key_prefix;
|
|
22
22
|
auth_type;
|
|
23
|
+
region;
|
|
23
24
|
is_active;
|
|
24
25
|
connected_at;
|
|
25
26
|
updated_at;
|
|
@@ -55,6 +56,10 @@ __decorate([
|
|
|
55
56
|
(0, typeorm_1.Column)('varchar', { default: 'api_key' }),
|
|
56
57
|
__metadata("design:type", String)
|
|
57
58
|
], UserProvider.prototype, "auth_type", void 0);
|
|
59
|
+
__decorate([
|
|
60
|
+
(0, typeorm_1.Column)('varchar', { nullable: true, default: null }),
|
|
61
|
+
__metadata("design:type", Object)
|
|
62
|
+
], UserProvider.prototype, "region", void 0);
|
|
58
63
|
__decorate([
|
|
59
64
|
(0, typeorm_1.Column)('boolean', { default: true }),
|
|
60
65
|
__metadata("design:type", Boolean)
|
|
@@ -43,6 +43,7 @@ class ConnectProviderDto {
|
|
|
43
43
|
provider;
|
|
44
44
|
apiKey;
|
|
45
45
|
authType;
|
|
46
|
+
region;
|
|
46
47
|
}
|
|
47
48
|
exports.ConnectProviderDto = ConnectProviderDto;
|
|
48
49
|
__decorate([
|
|
@@ -60,6 +61,11 @@ __decorate([
|
|
|
60
61
|
(0, class_validator_1.IsIn)(manifest_shared_1.AUTH_TYPES),
|
|
61
62
|
__metadata("design:type", String)
|
|
62
63
|
], ConnectProviderDto.prototype, "authType", void 0);
|
|
64
|
+
__decorate([
|
|
65
|
+
(0, class_validator_1.IsOptional)(),
|
|
66
|
+
(0, class_validator_1.IsString)(),
|
|
67
|
+
__metadata("design:type", String)
|
|
68
|
+
], ConnectProviderDto.prototype, "region", void 0);
|
|
63
69
|
class AgentProviderParamDto {
|
|
64
70
|
agentName;
|
|
65
71
|
provider;
|
|
@@ -14,13 +14,14 @@ const custom_provider_entity_1 = require("../../entities/custom-provider.entity"
|
|
|
14
14
|
const model_prices_module_1 = require("../../model-prices/model-prices.module");
|
|
15
15
|
const provider_model_fetcher_service_1 = require("./provider-model-fetcher.service");
|
|
16
16
|
const model_discovery_service_1 = require("./model-discovery.service");
|
|
17
|
+
const copilot_token_service_1 = require("../proxy/copilot-token.service");
|
|
17
18
|
let ModelDiscoveryModule = class ModelDiscoveryModule {
|
|
18
19
|
};
|
|
19
20
|
exports.ModelDiscoveryModule = ModelDiscoveryModule;
|
|
20
21
|
exports.ModelDiscoveryModule = ModelDiscoveryModule = __decorate([
|
|
21
22
|
(0, common_1.Module)({
|
|
22
23
|
imports: [typeorm_1.TypeOrmModule.forFeature([user_provider_entity_1.UserProvider, custom_provider_entity_1.CustomProvider]), model_prices_module_1.ModelPricesModule],
|
|
23
|
-
providers: [provider_model_fetcher_service_1.ProviderModelFetcherService, model_discovery_service_1.ModelDiscoveryService],
|
|
24
|
+
providers: [provider_model_fetcher_service_1.ProviderModelFetcherService, model_discovery_service_1.ModelDiscoveryService, copilot_token_service_1.CopilotTokenService],
|
|
24
25
|
exports: [model_discovery_service_1.ModelDiscoveryService, provider_model_fetcher_service_1.ProviderModelFetcherService],
|
|
25
26
|
})
|
|
26
27
|
], ModelDiscoveryModule);
|
|
@@ -25,26 +25,35 @@ const crypto_util_1 = require("../../common/utils/crypto.util");
|
|
|
25
25
|
const quality_score_util_1 = require("../../database/quality-score.util");
|
|
26
26
|
const pricing_sync_service_1 = require("../../database/pricing-sync.service");
|
|
27
27
|
const openai_oauth_types_1 = require("../openai-oauth.types");
|
|
28
|
+
const qwen_region_1 = require("../qwen-region");
|
|
29
|
+
const copilot_token_service_1 = require("../proxy/copilot-token.service");
|
|
28
30
|
const model_fallback_1 = require("./model-fallback");
|
|
29
31
|
const customProviderKey = (id) => `custom:${id}`;
|
|
30
32
|
const customModelKey = (id, modelName) => `custom:${id}/${modelName}`;
|
|
33
|
+
function isQwenProvider(providerId) {
|
|
34
|
+
const lower = providerId.toLowerCase();
|
|
35
|
+
return lower === 'qwen' || lower === 'alibaba';
|
|
36
|
+
}
|
|
31
37
|
let ModelDiscoveryService = ModelDiscoveryService_1 = class ModelDiscoveryService {
|
|
32
38
|
providerRepo;
|
|
33
39
|
customProviderRepo;
|
|
34
40
|
fetcher;
|
|
35
41
|
pricingSync;
|
|
36
42
|
modelRegistry;
|
|
43
|
+
copilotTokenService;
|
|
37
44
|
logger = new common_1.Logger(ModelDiscoveryService_1.name);
|
|
38
|
-
constructor(providerRepo, customProviderRepo, fetcher, pricingSync, modelRegistry) {
|
|
45
|
+
constructor(providerRepo, customProviderRepo, fetcher, pricingSync, modelRegistry, copilotTokenService) {
|
|
39
46
|
this.providerRepo = providerRepo;
|
|
40
47
|
this.customProviderRepo = customProviderRepo;
|
|
41
48
|
this.fetcher = fetcher;
|
|
42
49
|
this.pricingSync = pricingSync;
|
|
43
50
|
this.modelRegistry = modelRegistry;
|
|
51
|
+
this.copilotTokenService = copilotTokenService;
|
|
44
52
|
}
|
|
45
53
|
async discoverModels(provider) {
|
|
46
54
|
let apiKey = '';
|
|
47
55
|
let endpointOverride;
|
|
56
|
+
const lowerProvider = provider.provider.toLowerCase();
|
|
48
57
|
if (provider.api_key_encrypted) {
|
|
49
58
|
try {
|
|
50
59
|
apiKey = (0, crypto_util_1.decrypt)(provider.api_key_encrypted, (0, crypto_util_1.getEncryptionSecret)());
|
|
@@ -55,7 +64,6 @@ let ModelDiscoveryService = ModelDiscoveryService_1 = class ModelDiscoveryServic
|
|
|
55
64
|
}
|
|
56
65
|
}
|
|
57
66
|
if (provider.auth_type === 'subscription' && apiKey) {
|
|
58
|
-
const lowerProvider = provider.provider.toLowerCase();
|
|
59
67
|
if (lowerProvider === 'openai' || lowerProvider === 'minimax') {
|
|
60
68
|
const blob = (0, openai_oauth_types_1.parseOAuthTokenBlob)(apiKey);
|
|
61
69
|
if (blob?.t) {
|
|
@@ -65,6 +73,18 @@ let ModelDiscoveryService = ModelDiscoveryService_1 = class ModelDiscoveryServic
|
|
|
65
73
|
}
|
|
66
74
|
}
|
|
67
75
|
}
|
|
76
|
+
else if (lowerProvider === 'copilot' && this.copilotTokenService) {
|
|
77
|
+
try {
|
|
78
|
+
apiKey = await this.copilotTokenService.getCopilotToken(apiKey);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
this.logger.warn('Copilot token exchange failed for model discovery — falling back to known models');
|
|
82
|
+
apiKey = '';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (isQwenProvider(provider.provider) && (0, qwen_region_1.isQwenResolvedRegion)(provider.region)) {
|
|
87
|
+
endpointOverride = (0, qwen_region_1.getQwenCompatibleBaseUrl)(provider.region);
|
|
68
88
|
}
|
|
69
89
|
let raw;
|
|
70
90
|
if (provider.auth_type === 'subscription' && !apiKey) {
|
|
@@ -78,7 +98,7 @@ let ModelDiscoveryService = ModelDiscoveryService_1 = class ModelDiscoveryServic
|
|
|
78
98
|
if (raw.length > 0 && this.modelRegistry) {
|
|
79
99
|
this.modelRegistry.registerModels(provider.provider, raw.map((m) => m.id));
|
|
80
100
|
}
|
|
81
|
-
if (raw.length === 0) {
|
|
101
|
+
if (raw.length === 0 && !isQwenProvider(provider.provider)) {
|
|
82
102
|
const confirmed = this.modelRegistry?.getConfirmedModels(provider.provider) ?? null;
|
|
83
103
|
raw = (0, model_fallback_1.buildFallbackModels)(this.pricingSync, provider.provider, confirmed);
|
|
84
104
|
if (raw.length > 0) {
|
|
@@ -224,8 +244,10 @@ exports.ModelDiscoveryService = ModelDiscoveryService = ModelDiscoveryService_1
|
|
|
224
244
|
__param(3, (0, common_1.Inject)(pricing_sync_service_1.PricingSyncService)),
|
|
225
245
|
__param(4, (0, common_1.Optional)()),
|
|
226
246
|
__param(4, (0, common_1.Inject)(provider_model_registry_service_1.ProviderModelRegistryService)),
|
|
247
|
+
__param(5, (0, common_1.Optional)()),
|
|
248
|
+
__param(5, (0, common_1.Inject)(copilot_token_service_1.CopilotTokenService)),
|
|
227
249
|
__metadata("design:paramtypes", [typeorm_2.Repository,
|
|
228
250
|
typeorm_2.Repository,
|
|
229
|
-
provider_model_fetcher_service_1.ProviderModelFetcherService, Object, Object])
|
|
251
|
+
provider_model_fetcher_service_1.ProviderModelFetcherService, Object, Object, Object])
|
|
230
252
|
], ModelDiscoveryService);
|
|
231
253
|
//# sourceMappingURL=model-discovery.service.js.map
|
|
@@ -11,6 +11,7 @@ exports.ProviderModelFetcherService = exports.PROVIDER_CONFIGS = void 0;
|
|
|
11
11
|
const common_1 = require("@nestjs/common");
|
|
12
12
|
const ollama_1 = require("../../common/constants/ollama");
|
|
13
13
|
const provider_base_url_1 = require("../provider-base-url");
|
|
14
|
+
const qwen_region_1 = require("../qwen-region");
|
|
14
15
|
const FETCH_TIMEOUT_MS = 5000;
|
|
15
16
|
const DEFAULT_CONTEXT_WINDOW = 128000;
|
|
16
17
|
const ANTHROPIC_DEFAULT_CONTEXT = 200000;
|
|
@@ -173,6 +174,30 @@ function parseOpenaiSubscription(body, provider) {
|
|
|
173
174
|
};
|
|
174
175
|
});
|
|
175
176
|
}
|
|
177
|
+
function parseCopilot(body, provider) {
|
|
178
|
+
const data = body?.data;
|
|
179
|
+
if (!Array.isArray(data))
|
|
180
|
+
return [];
|
|
181
|
+
return data
|
|
182
|
+
.filter((m) => {
|
|
183
|
+
const entry = m;
|
|
184
|
+
return typeof entry.id === 'string' && entry.id.length > 0;
|
|
185
|
+
})
|
|
186
|
+
.map((m) => {
|
|
187
|
+
const entry = m;
|
|
188
|
+
return {
|
|
189
|
+
id: `copilot/${entry.id}`,
|
|
190
|
+
displayName: entry.id,
|
|
191
|
+
provider,
|
|
192
|
+
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
193
|
+
inputPricePerToken: 0,
|
|
194
|
+
outputPricePerToken: 0,
|
|
195
|
+
capabilityReasoning: false,
|
|
196
|
+
capabilityCode: false,
|
|
197
|
+
qualityScore: 3,
|
|
198
|
+
};
|
|
199
|
+
});
|
|
200
|
+
}
|
|
176
201
|
exports.PROVIDER_CONFIGS = {
|
|
177
202
|
openai: {
|
|
178
203
|
endpoint: 'https://api.openai.com/v1/models',
|
|
@@ -223,7 +248,7 @@ exports.PROVIDER_CONFIGS = {
|
|
|
223
248
|
parse: parseAnthropic,
|
|
224
249
|
},
|
|
225
250
|
qwen: {
|
|
226
|
-
endpoint: '
|
|
251
|
+
endpoint: `${(0, qwen_region_1.getQwenCompatibleBaseUrl)('beijing')}/v1/models`,
|
|
227
252
|
buildHeaders: bearerHeaders,
|
|
228
253
|
parse: parseOpenAI,
|
|
229
254
|
},
|
|
@@ -264,6 +289,17 @@ exports.PROVIDER_CONFIGS = {
|
|
|
264
289
|
buildHeaders: () => ({}),
|
|
265
290
|
parse: parseOllama,
|
|
266
291
|
},
|
|
292
|
+
copilot: {
|
|
293
|
+
endpoint: 'https://api.githubcopilot.com/models',
|
|
294
|
+
buildHeaders: (key) => ({
|
|
295
|
+
Authorization: `Bearer ${key}`,
|
|
296
|
+
Accept: 'application/json',
|
|
297
|
+
'Editor-Version': 'vscode/1.100.0',
|
|
298
|
+
'Editor-Plugin-Version': 'copilot/1.300.0',
|
|
299
|
+
'Copilot-Integration-Id': 'vscode-chat',
|
|
300
|
+
}),
|
|
301
|
+
parse: parseCopilot,
|
|
302
|
+
},
|
|
267
303
|
};
|
|
268
304
|
let ProviderModelFetcherService = ProviderModelFetcherService_1 = class ProviderModelFetcherService {
|
|
269
305
|
logger = new common_1.Logger(ProviderModelFetcherService_1.name);
|
|
@@ -290,6 +326,15 @@ let ProviderModelFetcherService = ProviderModelFetcherService_1 = class Provider
|
|
|
290
326
|
this.logger.warn('Ignoring invalid MiniMax subscription endpoint override');
|
|
291
327
|
}
|
|
292
328
|
}
|
|
329
|
+
else if (endpointOverride && configKey === 'qwen') {
|
|
330
|
+
const qwenBaseUrl = (0, qwen_region_1.normalizeQwenCompatibleBaseUrl)(endpointOverride);
|
|
331
|
+
if (qwenBaseUrl) {
|
|
332
|
+
url = `${qwenBaseUrl}/v1/models`;
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
this.logger.warn('Ignoring invalid Qwen endpoint override');
|
|
336
|
+
}
|
|
337
|
+
}
|
|
293
338
|
const headers = config.buildHeaders(apiKey, authType);
|
|
294
339
|
try {
|
|
295
340
|
const controller = new AbortController();
|
|
@@ -7,6 +7,7 @@ exports.resolveEndpointKey = resolveEndpointKey;
|
|
|
7
7
|
const ollama_1 = require("../../common/constants/ollama");
|
|
8
8
|
const providers_1 = require("../../common/constants/providers");
|
|
9
9
|
const provider_base_url_1 = require("../provider-base-url");
|
|
10
|
+
const qwen_region_1 = require("../qwen-region");
|
|
10
11
|
const openaiHeaders = (apiKey) => ({
|
|
11
12
|
Authorization: `Bearer ${apiKey}`,
|
|
12
13
|
'Content-Type': 'application/json',
|
|
@@ -94,6 +95,12 @@ exports.PROVIDER_ENDPOINTS = {
|
|
|
94
95
|
buildPath: openaiPath,
|
|
95
96
|
format: 'openai',
|
|
96
97
|
},
|
|
98
|
+
qwen: {
|
|
99
|
+
baseUrl: (0, qwen_region_1.getQwenCompatibleBaseUrl)('beijing'),
|
|
100
|
+
buildHeaders: openaiHeaders,
|
|
101
|
+
buildPath: openaiPath,
|
|
102
|
+
format: 'openai',
|
|
103
|
+
},
|
|
97
104
|
zai: {
|
|
98
105
|
baseUrl: 'https://api.z.ai',
|
|
99
106
|
buildHeaders: openaiHeaders,
|
|
@@ -26,6 +26,7 @@ const limit_check_service_1 = require("../../notifications/services/limit-check.
|
|
|
26
26
|
const fallback_status_codes_1 = require("./fallback-status-codes");
|
|
27
27
|
const provider_aliases_1 = require("../provider-aliases");
|
|
28
28
|
const provider_base_url_1 = require("../provider-base-url");
|
|
29
|
+
const qwen_region_1 = require("../qwen-region");
|
|
29
30
|
const anthropic_model_id_1 = require("../../common/utils/anthropic-model-id");
|
|
30
31
|
const SCORING_EXCLUDED_ROLES = new Set(['system', 'developer']);
|
|
31
32
|
const SCORING_RECENT_MESSAGES = 10;
|
|
@@ -80,10 +81,11 @@ let ProxyService = ProxyService_1 = class ProxyService {
|
|
|
80
81
|
throw new common_1.BadRequestException(`No API key found for provider: ${resolved.provider}. Re-connect the provider with an API key.`);
|
|
81
82
|
}
|
|
82
83
|
const resolvedCredentials = await this.resolveApiKey(resolved.provider, apiKey, resolved.auth_type, agentId, userId);
|
|
84
|
+
const providerRegion = await this.routingService.getProviderRegion(agentId, resolved.provider, resolved.auth_type);
|
|
83
85
|
const primaryModel = this.normalizeProviderModel(resolved.provider, resolved.model);
|
|
84
86
|
this.logger.log(`Proxy: tier=${resolved.tier} model=${primaryModel} provider=${resolved.provider} auth_type=${resolved.auth_type} confidence=${resolved.confidence}`);
|
|
85
87
|
const stream = body.stream === true;
|
|
86
|
-
const forward = await this.tryForwardToProvider(resolved.provider, resolvedCredentials.apiKey, primaryModel, body, stream, sessionKey, signal, resolved.auth_type, resolvedCredentials.resourceUrl);
|
|
88
|
+
const forward = await this.tryForwardToProvider(resolved.provider, resolvedCredentials.apiKey, primaryModel, body, stream, sessionKey, signal, resolved.auth_type, resolvedCredentials.resourceUrl, providerRegion);
|
|
87
89
|
if (!forward.response.ok && (0, fallback_status_codes_1.shouldTriggerFallback)(forward.response.status)) {
|
|
88
90
|
const tiers = await this.routingService.getTiers(agentId);
|
|
89
91
|
const assignment = tiers.find((t) => t.tier === resolved.tier);
|
|
@@ -180,8 +182,9 @@ let ProxyService = ProxyService_1 = class ProxyService {
|
|
|
180
182
|
continue;
|
|
181
183
|
}
|
|
182
184
|
const resolvedCredentials = await this.resolveApiKey(provider, apiKey, authType, agentId, userId);
|
|
185
|
+
const providerRegion = await this.routingService.getProviderRegion(agentId, provider, authType);
|
|
183
186
|
this.logger.log(`Fallback ${i}: trying model=${model} provider=${provider} auth_type=${authType} (primary=${primaryModel})`);
|
|
184
|
-
const forward = await this.tryForwardToProvider(provider, resolvedCredentials.apiKey, model, body, stream, sessionKey, signal, authType, resolvedCredentials.resourceUrl);
|
|
187
|
+
const forward = await this.tryForwardToProvider(provider, resolvedCredentials.apiKey, model, body, stream, sessionKey, signal, authType, resolvedCredentials.resourceUrl, providerRegion);
|
|
185
188
|
if (forward.response.ok) {
|
|
186
189
|
return { success: { forward, model, provider, fallbackIndex: i }, failures };
|
|
187
190
|
}
|
|
@@ -214,9 +217,9 @@ let ProxyService = ProxyService_1 = class ProxyService {
|
|
|
214
217
|
}
|
|
215
218
|
return { apiKey };
|
|
216
219
|
}
|
|
217
|
-
async tryForwardToProvider(provider, apiKey, model, body, stream, sessionKey, signal, authType, resourceUrl) {
|
|
220
|
+
async tryForwardToProvider(provider, apiKey, model, body, stream, sessionKey, signal, authType, resourceUrl, providerRegion) {
|
|
218
221
|
try {
|
|
219
|
-
return await this.forwardToProvider(provider, apiKey, model, body, stream, sessionKey, signal, authType, resourceUrl);
|
|
222
|
+
return await this.forwardToProvider(provider, apiKey, model, body, stream, sessionKey, signal, authType, resourceUrl, providerRegion);
|
|
220
223
|
}
|
|
221
224
|
catch (error) {
|
|
222
225
|
if (signal?.aborted)
|
|
@@ -345,7 +348,7 @@ let ProxyService = ProxyService_1 = class ProxyService {
|
|
|
345
348
|
return undefined;
|
|
346
349
|
return error.cause;
|
|
347
350
|
}
|
|
348
|
-
async forwardToProvider(provider, apiKey, model, body, stream, sessionKey, signal, authType, resourceUrl) {
|
|
351
|
+
async forwardToProvider(provider, apiKey, model, body, stream, sessionKey, signal, authType, resourceUrl, providerRegion) {
|
|
349
352
|
const extraHeaders = {};
|
|
350
353
|
if (provider === 'xai') {
|
|
351
354
|
extraHeaders['x-grok-conv-id'] = sessionKey;
|
|
@@ -368,6 +371,9 @@ let ProxyService = ProxyService_1 = class ProxyService {
|
|
|
368
371
|
forwardModel = custom_provider_service_1.CustomProviderService.rawModelName(model);
|
|
369
372
|
}
|
|
370
373
|
}
|
|
374
|
+
else if ((0, provider_endpoints_1.resolveEndpointKey)(provider) === 'qwen' && (0, qwen_region_1.isQwenResolvedRegion)(providerRegion)) {
|
|
375
|
+
customEndpoint = (0, provider_endpoints_1.buildEndpointOverride)((0, qwen_region_1.getQwenCompatibleBaseUrl)(providerRegion), 'qwen');
|
|
376
|
+
}
|
|
371
377
|
else if (authType === 'subscription' && provider.toLowerCase() === 'minimax' && resourceUrl) {
|
|
372
378
|
const minimaxBaseUrl = (0, provider_base_url_1.normalizeMinimaxSubscriptionBaseUrl)(resourceUrl);
|
|
373
379
|
if (minimaxBaseUrl) {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isQwenResolvedRegion = isQwenResolvedRegion;
|
|
4
|
+
exports.isQwenRegion = isQwenRegion;
|
|
5
|
+
exports.getQwenCompatibleBaseUrl = getQwenCompatibleBaseUrl;
|
|
6
|
+
exports.normalizeQwenCompatibleBaseUrl = normalizeQwenCompatibleBaseUrl;
|
|
7
|
+
exports.detectQwenRegion = detectQwenRegion;
|
|
8
|
+
const provider_base_url_1 = require("./provider-base-url");
|
|
9
|
+
const QWEN_REGION_BASE_URLS = {
|
|
10
|
+
singapore: 'https://dashscope-intl.aliyuncs.com/compatible-mode',
|
|
11
|
+
us: 'https://dashscope-us.aliyuncs.com/compatible-mode',
|
|
12
|
+
beijing: 'https://dashscope.aliyuncs.com/compatible-mode',
|
|
13
|
+
};
|
|
14
|
+
const QWEN_ALLOWED_BASE_URLS = new Set(Object.values(QWEN_REGION_BASE_URLS));
|
|
15
|
+
const QWEN_REGION_DETECTION_ORDER = ['singapore', 'us', 'beijing'];
|
|
16
|
+
const QWEN_DETECTION_TIMEOUT_MS = 3000;
|
|
17
|
+
function buildModelsUrl(region) {
|
|
18
|
+
return `${QWEN_REGION_BASE_URLS[region]}/v1/models`;
|
|
19
|
+
}
|
|
20
|
+
function isQwenResolvedRegion(value) {
|
|
21
|
+
return value === 'singapore' || value === 'us' || value === 'beijing';
|
|
22
|
+
}
|
|
23
|
+
function isQwenRegion(value) {
|
|
24
|
+
return value === 'auto' || isQwenResolvedRegion(value);
|
|
25
|
+
}
|
|
26
|
+
function getQwenCompatibleBaseUrl(region) {
|
|
27
|
+
if (isQwenResolvedRegion(region))
|
|
28
|
+
return QWEN_REGION_BASE_URLS[region];
|
|
29
|
+
return QWEN_REGION_BASE_URLS.beijing;
|
|
30
|
+
}
|
|
31
|
+
function normalizeQwenCompatibleBaseUrl(baseUrl) {
|
|
32
|
+
const normalized = (0, provider_base_url_1.normalizeProviderBaseUrl)(baseUrl);
|
|
33
|
+
return QWEN_ALLOWED_BASE_URLS.has(normalized) ? normalized : null;
|
|
34
|
+
}
|
|
35
|
+
async function detectQwenRegion(apiKey, fetchImpl = fetch) {
|
|
36
|
+
const headers = { Authorization: `Bearer ${apiKey}` };
|
|
37
|
+
for (const region of QWEN_REGION_DETECTION_ORDER) {
|
|
38
|
+
try {
|
|
39
|
+
const response = await fetchImpl(buildModelsUrl(region), {
|
|
40
|
+
headers,
|
|
41
|
+
signal: AbortSignal.timeout(QWEN_DETECTION_TIMEOUT_MS),
|
|
42
|
+
});
|
|
43
|
+
if (response.ok)
|
|
44
|
+
return region;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=qwen-region.js.map
|
|
@@ -22,6 +22,7 @@ const model_discovery_service_1 = require("./model-discovery/model-discovery.ser
|
|
|
22
22
|
const ollama_sync_service_1 = require("../database/ollama-sync.service");
|
|
23
23
|
const copilot_device_auth_service_1 = require("./copilot-device-auth.service");
|
|
24
24
|
const routing_dto_1 = require("./dto/routing.dto");
|
|
25
|
+
const qwen_region_1 = require("./qwen-region");
|
|
25
26
|
let RoutingController = class RoutingController {
|
|
26
27
|
routingService;
|
|
27
28
|
customProviderService;
|
|
@@ -53,15 +54,26 @@ let RoutingController = class RoutingController {
|
|
|
53
54
|
is_active: p.is_active,
|
|
54
55
|
has_api_key: !!p.api_key_encrypted,
|
|
55
56
|
key_prefix: p.key_prefix ?? null,
|
|
57
|
+
region: p.region ?? null,
|
|
56
58
|
connected_at: p.connected_at,
|
|
57
59
|
}));
|
|
58
60
|
}
|
|
59
61
|
async upsertProvider(user, params, body) {
|
|
60
62
|
const agent = await this.resolveAgentService.resolve(user.id, params.agentName);
|
|
63
|
+
const lowerProvider = body.provider.toLowerCase();
|
|
64
|
+
const isQwenProvider = lowerProvider === 'qwen' || lowerProvider === 'alibaba';
|
|
65
|
+
if (body.region !== undefined) {
|
|
66
|
+
if (!isQwenProvider) {
|
|
67
|
+
throw new common_1.BadRequestException('region is only supported for Alibaba/Qwen providers');
|
|
68
|
+
}
|
|
69
|
+
if (!(0, qwen_region_1.isQwenRegion)(body.region)) {
|
|
70
|
+
throw new common_1.BadRequestException('region must be one of: auto, singapore, us, beijing');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
61
73
|
if (body.provider.toLowerCase() === 'ollama') {
|
|
62
74
|
await this.ollamaSync.sync();
|
|
63
75
|
}
|
|
64
|
-
const { provider: result, isNew } = await this.routingService.upsertProvider(agent.id, user.id, body.provider, body.apiKey, body.authType);
|
|
76
|
+
const { provider: result, isNew } = await this.routingService.upsertProvider(agent.id, user.id, body.provider, body.apiKey, body.authType, body.region);
|
|
65
77
|
try {
|
|
66
78
|
await this.discoveryService.discoverModels(result);
|
|
67
79
|
await this.routingService.recalculateTiers(agent.id);
|
|
@@ -73,6 +85,7 @@ let RoutingController = class RoutingController {
|
|
|
73
85
|
provider: result.provider,
|
|
74
86
|
auth_type: result.auth_type ?? 'api_key',
|
|
75
87
|
is_active: result.is_active,
|
|
88
|
+
region: result.region ?? null,
|
|
76
89
|
};
|
|
77
90
|
}
|
|
78
91
|
async deactivateAllProviders(user, params) {
|
|
@@ -28,6 +28,7 @@ const crypto_util_1 = require("../common/utils/crypto.util");
|
|
|
28
28
|
const provider_aliases_1 = require("./provider-aliases");
|
|
29
29
|
const manifest_shared_1 = require("manifest-shared");
|
|
30
30
|
const subscription_support_1 = require("./subscription-support");
|
|
31
|
+
const qwen_region_1 = require("./qwen-region");
|
|
31
32
|
let RoutingService = RoutingService_1 = class RoutingService {
|
|
32
33
|
providerRepo;
|
|
33
34
|
tierRepo;
|
|
@@ -57,18 +58,20 @@ let RoutingService = RoutingService_1 = class RoutingService {
|
|
|
57
58
|
this.routingCache.setProviders(agentId, providers);
|
|
58
59
|
return providers;
|
|
59
60
|
}
|
|
60
|
-
async upsertProvider(agentId, userId, provider, apiKey, authType) {
|
|
61
|
+
async upsertProvider(agentId, userId, provider, apiKey, authType, region) {
|
|
61
62
|
const effectiveAuthType = authType ?? 'api_key';
|
|
62
|
-
const apiKeyEncrypted = apiKey ? (0, crypto_util_1.encrypt)(apiKey, (0, crypto_util_1.getEncryptionSecret)()) : null;
|
|
63
|
-
const keyPrefix = apiKey ? apiKey.substring(0, 8) : null;
|
|
64
63
|
const existing = await this.providerRepo.findOne({
|
|
65
64
|
where: { agent_id: agentId, provider, auth_type: effectiveAuthType },
|
|
66
65
|
});
|
|
66
|
+
const resolvedRegion = await this.resolveProviderRegion(provider, effectiveAuthType, region, apiKey, existing);
|
|
67
|
+
const apiKeyEncrypted = apiKey ? (0, crypto_util_1.encrypt)(apiKey, (0, crypto_util_1.getEncryptionSecret)()) : null;
|
|
68
|
+
const keyPrefix = apiKey ? apiKey.substring(0, 8) : null;
|
|
67
69
|
if (existing) {
|
|
68
70
|
if (apiKeyEncrypted !== null) {
|
|
69
71
|
existing.api_key_encrypted = apiKeyEncrypted;
|
|
70
72
|
existing.key_prefix = keyPrefix;
|
|
71
73
|
}
|
|
74
|
+
existing.region = resolvedRegion;
|
|
72
75
|
existing.is_active = true;
|
|
73
76
|
existing.updated_at = new Date().toISOString();
|
|
74
77
|
await this.providerRepo.save(existing);
|
|
@@ -84,6 +87,7 @@ let RoutingService = RoutingService_1 = class RoutingService {
|
|
|
84
87
|
auth_type: effectiveAuthType,
|
|
85
88
|
api_key_encrypted: apiKeyEncrypted,
|
|
86
89
|
key_prefix: keyPrefix,
|
|
90
|
+
region: resolvedRegion,
|
|
87
91
|
is_active: true,
|
|
88
92
|
connected_at: new Date().toISOString(),
|
|
89
93
|
updated_at: new Date().toISOString(),
|
|
@@ -93,6 +97,47 @@ let RoutingService = RoutingService_1 = class RoutingService {
|
|
|
93
97
|
this.routingCache.invalidateAgent(agentId);
|
|
94
98
|
return { provider: record, isNew: true };
|
|
95
99
|
}
|
|
100
|
+
async resolveProviderRegion(provider, authType, requestedRegion, apiKey, existing) {
|
|
101
|
+
const lower = provider.toLowerCase();
|
|
102
|
+
const isQwenProvider = lower === 'qwen' || lower === 'alibaba';
|
|
103
|
+
if (!isQwenProvider || authType !== 'api_key')
|
|
104
|
+
return null;
|
|
105
|
+
if (requestedRegion === undefined) {
|
|
106
|
+
if (apiKey) {
|
|
107
|
+
return this.detectQwenRegionOrThrow(apiKey);
|
|
108
|
+
}
|
|
109
|
+
return (0, qwen_region_1.isQwenResolvedRegion)(existing?.region) ? existing.region : null;
|
|
110
|
+
}
|
|
111
|
+
if (!(0, qwen_region_1.isQwenRegion)(requestedRegion)) {
|
|
112
|
+
throw new common_1.BadRequestException('Qwen region must be one of: auto, singapore, us, beijing');
|
|
113
|
+
}
|
|
114
|
+
if (requestedRegion !== 'auto')
|
|
115
|
+
return requestedRegion;
|
|
116
|
+
const keyToProbe = await this.getQwenDetectionKey(apiKey, existing);
|
|
117
|
+
if (!keyToProbe) {
|
|
118
|
+
return (0, qwen_region_1.isQwenResolvedRegion)(existing?.region) ? existing.region : null;
|
|
119
|
+
}
|
|
120
|
+
return this.detectQwenRegionOrThrow(keyToProbe);
|
|
121
|
+
}
|
|
122
|
+
async getQwenDetectionKey(apiKey, existing) {
|
|
123
|
+
if (apiKey)
|
|
124
|
+
return apiKey;
|
|
125
|
+
if (!existing?.api_key_encrypted)
|
|
126
|
+
return null;
|
|
127
|
+
try {
|
|
128
|
+
return (0, crypto_util_1.decrypt)(existing.api_key_encrypted, (0, crypto_util_1.getEncryptionSecret)());
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
this.logger.warn('Failed to decrypt API key while auto-detecting Alibaba region');
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async detectQwenRegionOrThrow(apiKey) {
|
|
136
|
+
const detected = await (0, qwen_region_1.detectQwenRegion)(apiKey);
|
|
137
|
+
if (detected)
|
|
138
|
+
return detected;
|
|
139
|
+
throw new common_1.BadRequestException('Could not auto-detect Alibaba region from this API key. Verify the key and try again.');
|
|
140
|
+
}
|
|
96
141
|
async registerSubscriptionProvider(agentId, userId, provider) {
|
|
97
142
|
if (!(0, subscription_support_1.isSupportedSubscriptionProvider)(provider)) {
|
|
98
143
|
this.logger.debug(`Ignoring unsupported subscription provider registration for ${provider}`);
|
|
@@ -423,6 +468,13 @@ let RoutingService = RoutingService_1 = class RoutingService {
|
|
|
423
468
|
const withKey = matches.find((r) => r.api_key_encrypted);
|
|
424
469
|
return withKey?.auth_type ?? matches[0]?.auth_type ?? 'api_key';
|
|
425
470
|
}
|
|
471
|
+
async getProviderRegion(agentId, provider, authType) {
|
|
472
|
+
const names = (0, provider_aliases_1.expandProviderNames)([provider]);
|
|
473
|
+
const records = await this.getProviders(agentId);
|
|
474
|
+
const matches = records.filter((r) => r.is_active && names.has(r.provider.toLowerCase()));
|
|
475
|
+
const match = authType ? matches.find((r) => r.auth_type === authType) : matches[0];
|
|
476
|
+
return match?.region ?? null;
|
|
477
|
+
}
|
|
426
478
|
async findProviderForModel(agentId, model) {
|
|
427
479
|
const providers = await this.getProviders(agentId);
|
|
428
480
|
for (const p of providers) {
|
|
@@ -451,10 +503,16 @@ let RoutingService = RoutingService_1 = class RoutingService {
|
|
|
451
503
|
const discovered = await this.discoveryService.getModelForAgent(agentId, model);
|
|
452
504
|
if (discovered)
|
|
453
505
|
return true;
|
|
506
|
+
const pricing = this.pricingCache.getByModel(model);
|
|
507
|
+
const inferredPrefix = (0, provider_aliases_1.inferProviderFromModelName)(model);
|
|
508
|
+
const pricingNames = pricing ? (0, provider_aliases_1.expandProviderNames)([pricing.provider]) : null;
|
|
509
|
+
const inferredNames = inferredPrefix ? (0, provider_aliases_1.expandProviderNames)([inferredPrefix]) : null;
|
|
510
|
+
if (pricingNames?.has('qwen') || inferredNames?.has('qwen')) {
|
|
511
|
+
return false;
|
|
512
|
+
}
|
|
454
513
|
const records = (await this.providerRepo.find({
|
|
455
514
|
where: { agent_id: agentId, is_active: true },
|
|
456
515
|
})).filter(subscription_support_1.isManifestUsableProvider);
|
|
457
|
-
const pricing = this.pricingCache.getByModel(model);
|
|
458
516
|
if (pricing) {
|
|
459
517
|
const names = (0, provider_aliases_1.expandProviderNames)([pricing.provider]);
|
|
460
518
|
if (records.find((r) => names.has(r.provider.toLowerCase())))
|
|
@@ -466,9 +524,8 @@ let RoutingService = RoutingService_1 = class RoutingService {
|
|
|
466
524
|
return true;
|
|
467
525
|
}
|
|
468
526
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
const prefixNames = (0, provider_aliases_1.expandProviderNames)([prefix]);
|
|
527
|
+
if (inferredPrefix) {
|
|
528
|
+
const prefixNames = (0, provider_aliases_1.expandProviderNames)([inferredPrefix]);
|
|
472
529
|
if (records.find((r) => prefixNames.has(r.provider.toLowerCase())))
|
|
473
530
|
return true;
|
|
474
531
|
}
|
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@ To resolve the conflict:`,(0,ni.getConflictResolutionRecipe)(u,r))),c=a):ZM.diag
|
|
|
16
16
|
openclaw config set plugins.entries.manifest.config.endpoint https://app.manifest.build/otlp`:`Invalid apiKey format. Keys must start with '${Ie}'. Fix it via:
|
|
17
17
|
openclaw config set manifest.apiKey ${Ie}YOUR_KEY`:`Missing apiKey. Set it via:
|
|
18
18
|
openclaw config set manifest.apiKey ${Ie}YOUR_KEY
|
|
19
|
-
or export MANIFEST_API_KEY=${Ie}YOUR_KEY`}var oa=ve(cM()),ia=ve(Ai()),BI=ve(II()),GI=ve(wI()),HI=ve(mo());m();var tn=null,rn=null,Lc=null,Ic=null;function kI(o,r){let i=new HI.Resource({"service.name":dr.SERVICE_NAME,"service.version":"5.31.1","manifest.plugin":"true"}),c=o.apiKey?{Authorization:`Bearer ${o.apiKey}`}:{},a=new BI.OTLPTraceExporter({url:`${o.endpoint}/v1/traces`,headers:c});tn=new oa.BasicTracerProvider({resource:i,spanProcessors:[new oa.BatchSpanProcessor(a,{scheduledDelayMillis:5e3,maxQueueSize:2048,maxExportBatchSize:512})]}),tn.register(),r.debug(`[manifest] Trace exporter -> ${o.endpoint}/v1/traces`);let u=new GI.OTLPMetricExporter({url:`${o.endpoint}/v1/metrics`,headers:c}),t=o.devMode||o.mode==="local"?Cc.METRICS_INTERVAL_MS:dr.METRICS_INTERVAL_MS;return rn=new ia.MeterProvider({resource:i,readers:[new ia.PeriodicExportingMetricReader({exporter:u,exportIntervalMillis:t})]}),gt.setGlobalMeterProvider(rn),r.debug(`[manifest] Metrics exporter -> ${o.endpoint}/v1/metrics (interval=${t}ms)`),Lc=Ge.getTracer("manifest-plugin","5.31.1"),Ic=gt.getMeter("manifest-plugin","5.31.1"),{tracer:Lc,meter:Ic}}async function YI(o){o.info("[manifest] Shutting down telemetry..."),tn&&(await tn.shutdown(),tn=null),rn&&(await rn.shutdown(),rn=null),Lc=null,Ic=null,o.info("[manifest] Telemetry shut down")}m();var L5=5,KI=30*60*1e3,I5=3e3,x5=5*60*1e3,C5=new Set(["system","developer"]),b5=10,nn=new Map,FI=!1;function U5(){if(FI)return;FI=!0;let o=setInterval(()=>{let r=Date.now();for(let[i,c]of nn)r-c.lastUpdated>KI&&nn.delete(i)},x5);typeof o=="object"&&"unref"in o&&o.unref()}async function jI(o,r,i,c){U5();let u=`${o.endpoint.replace(/\/otlp(\/v1)?\/?$/,"")}/api/v1/routing/resolve`;try{if(!r||!Array.isArray(r)||r.length===0)return c.debug("[manifest] Routing resolve: no messages, skipping"),null;let t=r.filter(C=>C&&typeof C=="object"&&"role"in C&&!C5.has(C.role)).slice(-b5).map(C=>({role:C.role,content:C.content}));if(t.length===0)return c.debug("[manifest] Routing resolve: no scorable messages, skipping"),null;let e=nn.get(i),n=e&&Date.now()-e.lastUpdated<KI?e.tiers:void 0,s={messages:t};n&&(s.recentTiers=n);let l={"Content-Type":"application/json"};o.apiKey&&(l.Authorization=`Bearer ${o.apiKey}`);let E=await fetch(u,{method:"POST",headers:l,body:JSON.stringify(s),signal:AbortSignal.timeout(I5)});if(!E.ok)return c.debug(`[manifest] Routing resolve returned ${E.status}`),null;let A=await E.json();if(!A.model)return c.debug(`[manifest] Routing resolve: no model for tier=${A.tier}`),null;let g=nn.get(i);return g?(g.tiers=[A.tier,...g.tiers].slice(0,L5),g.lastUpdated=Date.now()):nn.set(i,{tiers:[A.tier],lastUpdated:Date.now()}),c.debug(`[manifest] Routing resolved: tier=${A.tier} model=${A.model} provider=${A.provider}`),{tier:A.tier,model:A.model,provider:A.provider??"unknown",reason:A.reason??"",auth_type:A.auth_type??"api_key"}}catch(t){let e=t instanceof Error?t.message:String(t);return c.debug(`[manifest] Routing resolve failed (${e})`),null}}function qI(o,r,i){if(typeof o.registerProvider!="function"){i.debug("[manifest] registerProvider not available, skipping provider registration");return}let c=r.endpoint.replace(/\/otlp(\/v1)?\/?$/,"");try{o.registerProvider({id:"manifest",name:"Manifest Router",label:"Manifest Router",api:"openai-completions",baseUrl:c,apiKey:r.apiKey,models:["auto"]}),i.info("[manifest] Registered as OpenAI-compatible provider (proxy mode)")}catch(a){let u=a instanceof Error?a.message:String(a);i.debug(`[manifest] registerProvider failed (${u})`)}}var Er=new Map,WI,zI,JI,XI,D5,$I,QI,ZI,ex;function tx(o){WI=o.createCounter(Oe.LLM_REQUESTS,{description:"Total LLM inference requests"}),zI=o.createCounter(Oe.LLM_TOKENS_INPUT,{description:"Total input tokens sent to LLM"}),JI=o.createCounter(Oe.LLM_TOKENS_OUTPUT,{description:"Total output tokens from LLM"}),XI=o.createCounter(Oe.LLM_TOKENS_CACHE_READ,{description:"Total cache-read tokens"}),D5=o.createHistogram(Oe.LLM_DURATION,{description:"LLM request duration in ms",unit:"ms"}),$I=o.createCounter(Oe.TOOL_CALLS,{description:"Total tool invocations"}),QI=o.createCounter(Oe.TOOL_ERRORS,{description:"Total tool errors"}),ZI=o.createHistogram(Oe.TOOL_DURATION,{description:"Tool execution duration in ms",unit:"ms"}),ex=o.createCounter(Oe.MESSAGES_RECEIVED,{description:"Total messages received from users"})}function aa(o,r,i){typeof o.on=="function"?o.on(r,i):typeof o.registerHook=="function"&&o.registerHook(r,i)}function rx(o,r,i,c){aa(o,"message_received",a=>{let u=a.sessionKey||a.session?.key||`agent:${a.agent||"main"}:main`,t=a.channel||"unknown",e=r.startSpan(sn.REQUEST,{kind:we.SERVER,attributes:{[b.SESSION_KEY]:u,[b.CHANNEL]:t}});Er.set(u,{root:e}),ex.add(1,{[b.CHANNEL]:t}),c.debug(`[manifest] Root span started for session=${u}`)}),aa(o,"before_agent_start",a=>{let u=a.sessionKey||a.session?.key||`agent:${a.agent||"main"}:main`,t=a.agent||"main",e=Er.get(u),n=e?.root?Ge.setSpan(Be.active(),e.root):Be.active(),s=r.startSpan(sn.AGENT_TURN,{kind:we.INTERNAL,attributes:{[b.AGENT_NAME]:t,[b.SESSION_KEY]:u}},n);e?e.turn=s:Er.set(u,{root:s,turn:s}),c.debug(`[manifest] Agent turn started: agent=${t}, session=${u}`)}),aa(o,"tool_result_persist",a=>{let u=a.toolName||a.tool||"unknown",t=a.durationMs||0,e=a.error==null,n=a.sessionKey||"unknown",s=Er.get(n),l=s?.turn?Ge.setSpan(Be.active(),s.turn):Be.active(),E=r.startSpan(`${sn.TOOL_PREFIX}${u}`,{kind:we.INTERNAL,attributes:{[b.TOOL_NAME]:u,[b.TOOL_SUCCESS]:String(e),[b.SESSION_KEY]:n}},l);e||(E.setStatus({code:rt.ERROR,message:a.error?.message||"Tool execution failed"}),QI.add(1,{[b.TOOL_NAME]:u})),E.end(),$I.add(1,{[b.TOOL_NAME]:u}),ZI.record(t,{[b.TOOL_NAME]:u})}),aa(o,"agent_end",async a=>{let u=a.sessionKey||a.session?.key||`agent:${a.agent||"main"}:main`,t=a.messages||[],e=[...t].reverse().find(k=>k.role==="assistant"&&k.usage),n=e?.model||a.model||"unknown",s=e?.provider||a.provider||"unknown",l=e?.usage||a.usage||{},E=l.input||l.inputTokens||l.prompt_tokens||l.promptTokens||0,A=l.output||l.outputTokens||l.completion_tokens||l.completionTokens||0,g=l.prompt_tokens_details||{},C=l.cacheRead||l.cacheReadTokens||l.cache_read_tokens||g.cached_tokens||0,on=l.cacheWrite||l.cacheWriteTokens||l.cache_creation_tokens||0,Le=n,Sr=s,Qe=null,mt=null;if(Le==="auto"){let k=await jI(i,t,u,c);k&&(Le=k.model,Sr=k.provider,Qe=k.tier,mt=k.reason||null)}let ht=[...t].reverse().find(k=>k?.role==="user");(ht?typeof ht.content=="string"?ht.content.includes("HEARTBEAT_OK"):Array.isArray(ht.content)?ht.content.some(k=>k.type==="text"&&typeof k.text=="string"&&k.text.includes("HEARTBEAT_OK")):!1:!1)&&(mt="heartbeat",Qe="simple");let Ae=Er.get(u);if(Ae?.turn){if(Ae.turn.setAttributes({[b.MODEL]:Le,[b.PROVIDER]:Sr,[b.INPUT_TOKENS]:E,[b.OUTPUT_TOKENS]:A,[b.CACHE_READ_TOKENS]:C,[b.CACHE_WRITE_TOKENS]:on}),Qe&&Ae.turn.setAttribute(b.ROUTING_TIER,Qe),mt&&Ae.turn.setAttribute(b.ROUTING_REASON,mt),a.success===!1||a.error!=null){let k=a.error?.message||a.errorMessage||"Agent turn failed";Ae.turn.setStatus({code:rt.ERROR,message:typeof k=="string"?k.slice(0,500):String(k)})}Ae.turn.end()}Ae?.root&&Ae.root!==Ae.turn&&Ae.root.end(),Er.delete(u);let an={[b.MODEL]:Le,[b.PROVIDER]:Sr};WI.add(1,an),zI.add(E,an),JI.add(A,an),C>0&&XI.add(C,an),c.debug(`[manifest] agent_end tokens: in=${E}, out=${A}, cache=${C}`),c.debug(`[manifest] Trace completed for session=${u}`)}),c.debug("[manifest] All hooks registered")}async function Ot(o){let r=o.endpoint.replace(/\/otlp(\/v1)?\/?$/,""),i={endpointReachable:!1,authValid:!1,agentName:null,error:null};try{let c=await fetch(`${r}/api/v1/health`,{signal:AbortSignal.timeout(5e3)});if(!c.ok)return i.error=`Health endpoint returned ${c.status}`,i;i.endpointReachable=!0}catch(c){let a=c instanceof Error?c.message:String(c);return i.error=`Cannot reach endpoint: ${a}`,i}try{let c=o.apiKey?{Authorization:`Bearer ${o.apiKey}`}:{},a=await fetch(`${r}/api/v1/agent/usage?range=24h`,{headers:c,signal:AbortSignal.timeout(5e3)});if(a.status===401||a.status===403)return i.error="API key rejected \u2014 check your mnfst_ key is correct",i;if(!a.ok)return i.error=`Usage endpoint returned ${a.status}`,i;i.authValid=!0;let u=await a.json();u&&typeof u.agentName=="string"&&(i.agentName=u.agentName)}catch(c){let a=c instanceof Error?c.message:String(c);return i.error=`Auth check failed: ${a}`,i}return i}var ua={today:"24h",week:"7d",month:"30d"};async function sa(o,r,i,c){let a=`${o}${r}`;try{let u=i?{Authorization:`Bearer ${i}`}:{},t=await fetch(a,{headers:u});if(!t.ok)return{content:[{type:"text",text:JSON.stringify({error:`API returned ${t.status}`})}]};let e=await t.json();return{content:[{type:"text",text:JSON.stringify(e)}]}}catch(u){let t=u instanceof Error?u.message:String(u);return c.error(`[manifest] API call failed: ${t}`),{content:[{type:"text",text:JSON.stringify({error:t})}]}}}function nx(o){try{let r=JSON.parse(o.content[0].text);return r.error?{error:r.error}:{result:r}}catch{return{result:o.content[0].text}}}function ox(o,r,i){let c=r.endpoint.replace(/\/otlp(\/v1)?\/?$/,"");o.registerTool({name:"manifest_usage",description:"Get token consumption for this agent: total, input, output, cache-read tokens, and action count. Use when the user asks about token usage or consumption.",parameters:{type:"object",properties:{period:{type:"string",enum:["today","week","month"],default:"today",description:"Time period"}}},async handler(a){let u=ua[a.period||"today"]||"24h";return nx(await sa(c,`/api/v1/agent/usage?range=${u}`,r.apiKey,i))},async execute(a,u){let t=ua[u.period||"today"]||"24h";return sa(c,`/api/v1/agent/usage?range=${t}`,r.apiKey,i)}},{optional:!0}),o.registerTool({name:"manifest_costs",description:"Get cost breakdown for this agent in USD, grouped by model. Use when the user asks about costs, spending, or money burned.",parameters:{type:"object",properties:{period:{type:"string",enum:["today","week","month"],default:"week",description:"Time period"}}},async handler(a){let u=ua[a.period||"week"]||"7d";return nx(await sa(c,`/api/v1/agent/costs?range=${u}`,r.apiKey,i))},async execute(a,u){let t=ua[u.period||"week"]||"7d";return sa(c,`/api/v1/agent/costs?range=${t}`,r.apiKey,i)}},{optional:!0}),o.registerTool({name:"manifest_health",description:"Check whether Manifest observability is connected and working. Use when the user asks if monitoring is set up or wants a connectivity test.",parameters:{type:"object",properties:{}},async handler(){let a=await Ot(r);return a.error?{error:a.error}:{result:{endpointReachable:a.endpointReachable,authValid:a.authValid,agentName:a.agentName,status:"ok"}}},async execute(){let a=await Ot(r);return a.error?{content:[{type:"text",text:JSON.stringify({error:a.error})}]}:{content:[{type:"text",text:JSON.stringify({endpointReachable:a.endpointReachable,authValid:a.authValid,agentName:a.agentName,status:"ok"})}]}}},{optional:!0}),i.debug("[manifest] Registered agent tools: manifest_usage, manifest_costs, manifest_health")}function ix(o,r,i){if(typeof o.registerCommand!="function"){i.debug("[manifest] registerCommand not available, skipping /manifest command");return}let c=async()=>{try{let a=await Ot(r),u=[`Mode: ${r.mode}`,`Dev mode: ${r.devMode?"yes":"no"}`,`Endpoint reachable: ${a.endpointReachable?"yes":"no"}`,`Auth valid: ${a.authValid?"yes":"no"}`];return a.agentName&&u.push(`Agent: ${a.agentName}`),a.error&&u.push(`Error: ${a.error}`),u.join(`
|
|
19
|
+
or export MANIFEST_API_KEY=${Ie}YOUR_KEY`}var oa=ve(cM()),ia=ve(Ai()),BI=ve(II()),GI=ve(wI()),HI=ve(mo());m();var tn=null,rn=null,Lc=null,Ic=null;function kI(o,r){let i=new HI.Resource({"service.name":dr.SERVICE_NAME,"service.version":"5.32.0","manifest.plugin":"true"}),c=o.apiKey?{Authorization:`Bearer ${o.apiKey}`}:{},a=new BI.OTLPTraceExporter({url:`${o.endpoint}/v1/traces`,headers:c});tn=new oa.BasicTracerProvider({resource:i,spanProcessors:[new oa.BatchSpanProcessor(a,{scheduledDelayMillis:5e3,maxQueueSize:2048,maxExportBatchSize:512})]}),tn.register(),r.debug(`[manifest] Trace exporter -> ${o.endpoint}/v1/traces`);let u=new GI.OTLPMetricExporter({url:`${o.endpoint}/v1/metrics`,headers:c}),t=o.devMode||o.mode==="local"?Cc.METRICS_INTERVAL_MS:dr.METRICS_INTERVAL_MS;return rn=new ia.MeterProvider({resource:i,readers:[new ia.PeriodicExportingMetricReader({exporter:u,exportIntervalMillis:t})]}),gt.setGlobalMeterProvider(rn),r.debug(`[manifest] Metrics exporter -> ${o.endpoint}/v1/metrics (interval=${t}ms)`),Lc=Ge.getTracer("manifest-plugin","5.32.0"),Ic=gt.getMeter("manifest-plugin","5.32.0"),{tracer:Lc,meter:Ic}}async function YI(o){o.info("[manifest] Shutting down telemetry..."),tn&&(await tn.shutdown(),tn=null),rn&&(await rn.shutdown(),rn=null),Lc=null,Ic=null,o.info("[manifest] Telemetry shut down")}m();var L5=5,KI=30*60*1e3,I5=3e3,x5=5*60*1e3,C5=new Set(["system","developer"]),b5=10,nn=new Map,FI=!1;function U5(){if(FI)return;FI=!0;let o=setInterval(()=>{let r=Date.now();for(let[i,c]of nn)r-c.lastUpdated>KI&&nn.delete(i)},x5);typeof o=="object"&&"unref"in o&&o.unref()}async function jI(o,r,i,c){U5();let u=`${o.endpoint.replace(/\/otlp(\/v1)?\/?$/,"")}/api/v1/routing/resolve`;try{if(!r||!Array.isArray(r)||r.length===0)return c.debug("[manifest] Routing resolve: no messages, skipping"),null;let t=r.filter(C=>C&&typeof C=="object"&&"role"in C&&!C5.has(C.role)).slice(-b5).map(C=>({role:C.role,content:C.content}));if(t.length===0)return c.debug("[manifest] Routing resolve: no scorable messages, skipping"),null;let e=nn.get(i),n=e&&Date.now()-e.lastUpdated<KI?e.tiers:void 0,s={messages:t};n&&(s.recentTiers=n);let l={"Content-Type":"application/json"};o.apiKey&&(l.Authorization=`Bearer ${o.apiKey}`);let E=await fetch(u,{method:"POST",headers:l,body:JSON.stringify(s),signal:AbortSignal.timeout(I5)});if(!E.ok)return c.debug(`[manifest] Routing resolve returned ${E.status}`),null;let A=await E.json();if(!A.model)return c.debug(`[manifest] Routing resolve: no model for tier=${A.tier}`),null;let g=nn.get(i);return g?(g.tiers=[A.tier,...g.tiers].slice(0,L5),g.lastUpdated=Date.now()):nn.set(i,{tiers:[A.tier],lastUpdated:Date.now()}),c.debug(`[manifest] Routing resolved: tier=${A.tier} model=${A.model} provider=${A.provider}`),{tier:A.tier,model:A.model,provider:A.provider??"unknown",reason:A.reason??"",auth_type:A.auth_type??"api_key"}}catch(t){let e=t instanceof Error?t.message:String(t);return c.debug(`[manifest] Routing resolve failed (${e})`),null}}function qI(o,r,i){if(typeof o.registerProvider!="function"){i.debug("[manifest] registerProvider not available, skipping provider registration");return}let c=r.endpoint.replace(/\/otlp(\/v1)?\/?$/,"");try{o.registerProvider({id:"manifest",name:"Manifest Router",label:"Manifest Router",api:"openai-completions",baseUrl:c,apiKey:r.apiKey,models:["auto"]}),i.info("[manifest] Registered as OpenAI-compatible provider (proxy mode)")}catch(a){let u=a instanceof Error?a.message:String(a);i.debug(`[manifest] registerProvider failed (${u})`)}}var Er=new Map,WI,zI,JI,XI,D5,$I,QI,ZI,ex;function tx(o){WI=o.createCounter(Oe.LLM_REQUESTS,{description:"Total LLM inference requests"}),zI=o.createCounter(Oe.LLM_TOKENS_INPUT,{description:"Total input tokens sent to LLM"}),JI=o.createCounter(Oe.LLM_TOKENS_OUTPUT,{description:"Total output tokens from LLM"}),XI=o.createCounter(Oe.LLM_TOKENS_CACHE_READ,{description:"Total cache-read tokens"}),D5=o.createHistogram(Oe.LLM_DURATION,{description:"LLM request duration in ms",unit:"ms"}),$I=o.createCounter(Oe.TOOL_CALLS,{description:"Total tool invocations"}),QI=o.createCounter(Oe.TOOL_ERRORS,{description:"Total tool errors"}),ZI=o.createHistogram(Oe.TOOL_DURATION,{description:"Tool execution duration in ms",unit:"ms"}),ex=o.createCounter(Oe.MESSAGES_RECEIVED,{description:"Total messages received from users"})}function aa(o,r,i){typeof o.on=="function"?o.on(r,i):typeof o.registerHook=="function"&&o.registerHook(r,i)}function rx(o,r,i,c){aa(o,"message_received",a=>{let u=a.sessionKey||a.session?.key||`agent:${a.agent||"main"}:main`,t=a.channel||"unknown",e=r.startSpan(sn.REQUEST,{kind:we.SERVER,attributes:{[b.SESSION_KEY]:u,[b.CHANNEL]:t}});Er.set(u,{root:e}),ex.add(1,{[b.CHANNEL]:t}),c.debug(`[manifest] Root span started for session=${u}`)}),aa(o,"before_agent_start",a=>{let u=a.sessionKey||a.session?.key||`agent:${a.agent||"main"}:main`,t=a.agent||"main",e=Er.get(u),n=e?.root?Ge.setSpan(Be.active(),e.root):Be.active(),s=r.startSpan(sn.AGENT_TURN,{kind:we.INTERNAL,attributes:{[b.AGENT_NAME]:t,[b.SESSION_KEY]:u}},n);e?e.turn=s:Er.set(u,{root:s,turn:s}),c.debug(`[manifest] Agent turn started: agent=${t}, session=${u}`)}),aa(o,"tool_result_persist",a=>{let u=a.toolName||a.tool||"unknown",t=a.durationMs||0,e=a.error==null,n=a.sessionKey||"unknown",s=Er.get(n),l=s?.turn?Ge.setSpan(Be.active(),s.turn):Be.active(),E=r.startSpan(`${sn.TOOL_PREFIX}${u}`,{kind:we.INTERNAL,attributes:{[b.TOOL_NAME]:u,[b.TOOL_SUCCESS]:String(e),[b.SESSION_KEY]:n}},l);e||(E.setStatus({code:rt.ERROR,message:a.error?.message||"Tool execution failed"}),QI.add(1,{[b.TOOL_NAME]:u})),E.end(),$I.add(1,{[b.TOOL_NAME]:u}),ZI.record(t,{[b.TOOL_NAME]:u})}),aa(o,"agent_end",async a=>{let u=a.sessionKey||a.session?.key||`agent:${a.agent||"main"}:main`,t=a.messages||[],e=[...t].reverse().find(k=>k.role==="assistant"&&k.usage),n=e?.model||a.model||"unknown",s=e?.provider||a.provider||"unknown",l=e?.usage||a.usage||{},E=l.input||l.inputTokens||l.prompt_tokens||l.promptTokens||0,A=l.output||l.outputTokens||l.completion_tokens||l.completionTokens||0,g=l.prompt_tokens_details||{},C=l.cacheRead||l.cacheReadTokens||l.cache_read_tokens||g.cached_tokens||0,on=l.cacheWrite||l.cacheWriteTokens||l.cache_creation_tokens||0,Le=n,Sr=s,Qe=null,mt=null;if(Le==="auto"){let k=await jI(i,t,u,c);k&&(Le=k.model,Sr=k.provider,Qe=k.tier,mt=k.reason||null)}let ht=[...t].reverse().find(k=>k?.role==="user");(ht?typeof ht.content=="string"?ht.content.includes("HEARTBEAT_OK"):Array.isArray(ht.content)?ht.content.some(k=>k.type==="text"&&typeof k.text=="string"&&k.text.includes("HEARTBEAT_OK")):!1:!1)&&(mt="heartbeat",Qe="simple");let Ae=Er.get(u);if(Ae?.turn){if(Ae.turn.setAttributes({[b.MODEL]:Le,[b.PROVIDER]:Sr,[b.INPUT_TOKENS]:E,[b.OUTPUT_TOKENS]:A,[b.CACHE_READ_TOKENS]:C,[b.CACHE_WRITE_TOKENS]:on}),Qe&&Ae.turn.setAttribute(b.ROUTING_TIER,Qe),mt&&Ae.turn.setAttribute(b.ROUTING_REASON,mt),a.success===!1||a.error!=null){let k=a.error?.message||a.errorMessage||"Agent turn failed";Ae.turn.setStatus({code:rt.ERROR,message:typeof k=="string"?k.slice(0,500):String(k)})}Ae.turn.end()}Ae?.root&&Ae.root!==Ae.turn&&Ae.root.end(),Er.delete(u);let an={[b.MODEL]:Le,[b.PROVIDER]:Sr};WI.add(1,an),zI.add(E,an),JI.add(A,an),C>0&&XI.add(C,an),c.debug(`[manifest] agent_end tokens: in=${E}, out=${A}, cache=${C}`),c.debug(`[manifest] Trace completed for session=${u}`)}),c.debug("[manifest] All hooks registered")}async function Ot(o){let r=o.endpoint.replace(/\/otlp(\/v1)?\/?$/,""),i={endpointReachable:!1,authValid:!1,agentName:null,error:null};try{let c=await fetch(`${r}/api/v1/health`,{signal:AbortSignal.timeout(5e3)});if(!c.ok)return i.error=`Health endpoint returned ${c.status}`,i;i.endpointReachable=!0}catch(c){let a=c instanceof Error?c.message:String(c);return i.error=`Cannot reach endpoint: ${a}`,i}try{let c=o.apiKey?{Authorization:`Bearer ${o.apiKey}`}:{},a=await fetch(`${r}/api/v1/agent/usage?range=24h`,{headers:c,signal:AbortSignal.timeout(5e3)});if(a.status===401||a.status===403)return i.error="API key rejected \u2014 check your mnfst_ key is correct",i;if(!a.ok)return i.error=`Usage endpoint returned ${a.status}`,i;i.authValid=!0;let u=await a.json();u&&typeof u.agentName=="string"&&(i.agentName=u.agentName)}catch(c){let a=c instanceof Error?c.message:String(c);return i.error=`Auth check failed: ${a}`,i}return i}var ua={today:"24h",week:"7d",month:"30d"};async function sa(o,r,i,c){let a=`${o}${r}`;try{let u=i?{Authorization:`Bearer ${i}`}:{},t=await fetch(a,{headers:u});if(!t.ok)return{content:[{type:"text",text:JSON.stringify({error:`API returned ${t.status}`})}]};let e=await t.json();return{content:[{type:"text",text:JSON.stringify(e)}]}}catch(u){let t=u instanceof Error?u.message:String(u);return c.error(`[manifest] API call failed: ${t}`),{content:[{type:"text",text:JSON.stringify({error:t})}]}}}function nx(o){try{let r=JSON.parse(o.content[0].text);return r.error?{error:r.error}:{result:r}}catch{return{result:o.content[0].text}}}function ox(o,r,i){let c=r.endpoint.replace(/\/otlp(\/v1)?\/?$/,"");o.registerTool({name:"manifest_usage",description:"Get token consumption for this agent: total, input, output, cache-read tokens, and action count. Use when the user asks about token usage or consumption.",parameters:{type:"object",properties:{period:{type:"string",enum:["today","week","month"],default:"today",description:"Time period"}}},async handler(a){let u=ua[a.period||"today"]||"24h";return nx(await sa(c,`/api/v1/agent/usage?range=${u}`,r.apiKey,i))},async execute(a,u){let t=ua[u.period||"today"]||"24h";return sa(c,`/api/v1/agent/usage?range=${t}`,r.apiKey,i)}},{optional:!0}),o.registerTool({name:"manifest_costs",description:"Get cost breakdown for this agent in USD, grouped by model. Use when the user asks about costs, spending, or money burned.",parameters:{type:"object",properties:{period:{type:"string",enum:["today","week","month"],default:"week",description:"Time period"}}},async handler(a){let u=ua[a.period||"week"]||"7d";return nx(await sa(c,`/api/v1/agent/costs?range=${u}`,r.apiKey,i))},async execute(a,u){let t=ua[u.period||"week"]||"7d";return sa(c,`/api/v1/agent/costs?range=${t}`,r.apiKey,i)}},{optional:!0}),o.registerTool({name:"manifest_health",description:"Check whether Manifest observability is connected and working. Use when the user asks if monitoring is set up or wants a connectivity test.",parameters:{type:"object",properties:{}},async handler(){let a=await Ot(r);return a.error?{error:a.error}:{result:{endpointReachable:a.endpointReachable,authValid:a.authValid,agentName:a.agentName,status:"ok"}}},async execute(){let a=await Ot(r);return a.error?{content:[{type:"text",text:JSON.stringify({error:a.error})}]}:{content:[{type:"text",text:JSON.stringify({endpointReachable:a.endpointReachable,authValid:a.authValid,agentName:a.agentName,status:"ok"})}]}}},{optional:!0}),i.debug("[manifest] Registered agent tools: manifest_usage, manifest_costs, manifest_health")}function ix(o,r,i){if(typeof o.registerCommand!="function"){i.debug("[manifest] registerCommand not available, skipping /manifest command");return}let c=async()=>{try{let a=await Ot(r),u=[`Mode: ${r.mode}`,`Dev mode: ${r.devMode?"yes":"no"}`,`Endpoint reachable: ${a.endpointReachable?"yes":"no"}`,`Auth valid: ${a.authValid?"yes":"no"}`];return a.agentName&&u.push(`Agent: ${a.agentName}`),a.error&&u.push(`Error: ${a.error}`),u.join(`
|
|
20
20
|
`)}catch(a){return`Manifest status check failed: ${a instanceof Error?a.message:String(a)}`}};o.registerCommand({name:"manifest",description:"Show Manifest plugin status and connection info",async handler(){return c()},async execute(){let a=await c();return{text:a,content:[{type:"text",text:a}]}}}),i.debug("[manifest] Registered /manifest command")}var Tr=require("./local-mode"),ca=require("./subscription");module.exports={id:"manifest",name:"Manifest \u2014 Agent Observability",register(o){let r=o.logger||{info:(...A)=>console.log(...A),debug:()=>{},error:(...A)=>console.error(...A),warn:(...A)=>console.warn(...A)},{config:i,_deprecatedDevMode:c}=bc(o.pluginConfig);if(c&&r.warn?.(`[manifest] mode: "dev" is deprecated. Use mode: "cloud" with devMode: true instead.
|
|
21
21
|
openclaw config set plugins.entries.manifest.config.mode cloud
|
|
22
22
|
openclaw config set plugins.entries.manifest.config.devMode true`),i.mode==="local"){(0,Tr.registerLocalMode)(o,i,r);return}let a=Uc(i);if(a){!i.devMode&&i.mode==="cloud"&&!i.apiKey?r.info(`[manifest] Cloud mode requires an API key:
|