@x12i/ai-gateway 9.3.5 → 9.5.2
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/README.md +151 -4147
- package/dist/activity-manager.d.ts +9 -1
- package/dist/activity-manager.js +85 -81
- package/dist/ai-tools-client.js +4 -12
- package/dist/gateway-config.d.ts +3 -0
- package/dist/gateway-config.js +19 -1
- package/dist/gateway-utils.d.ts +34 -2
- package/dist/gateway-utils.js +204 -35
- package/dist/gateway.d.ts +2 -0
- package/dist/gateway.js +69 -2
- package/dist/index.d.ts +5 -3
- package/dist/index.js +4 -19
- package/dist/optimixer-manager.d.ts +33 -0
- package/dist/optimixer-manager.js +128 -0
- package/dist/token-estimate.d.ts +12 -0
- package/dist/token-estimate.js +30 -0
- package/dist/types.d.ts +50 -2
- package/dist-cjs/activity-manager.cjs +85 -81
- package/dist-cjs/activity-manager.d.ts +9 -1
- package/dist-cjs/ai-tools-client.cjs +4 -12
- package/dist-cjs/gateway-config.cjs +19 -1
- package/dist-cjs/gateway-config.d.ts +3 -0
- package/dist-cjs/gateway-utils.cjs +204 -35
- package/dist-cjs/gateway-utils.d.ts +34 -2
- package/dist-cjs/gateway.cjs +69 -2
- package/dist-cjs/gateway.d.ts +2 -0
- package/dist-cjs/index.cjs +4 -19
- package/dist-cjs/index.d.ts +5 -3
- package/dist-cjs/optimixer-manager.cjs +128 -0
- package/dist-cjs/optimixer-manager.d.ts +33 -0
- package/dist-cjs/token-estimate.cjs +30 -0
- package/dist-cjs/token-estimate.d.ts +12 -0
- package/dist-cjs/types.d.ts +50 -2
- package/package.json +10 -36
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight token-size estimates for Optimixer predict inputs.
|
|
3
|
+
* Uses a chars/4 heuristic (no tiktoken dependency).
|
|
4
|
+
*/
|
|
5
|
+
export declare function estimateTextTokens(text: string): number;
|
|
6
|
+
export declare function estimateMessagesTokenSizes(messages: Array<{
|
|
7
|
+
role?: string;
|
|
8
|
+
content?: unknown;
|
|
9
|
+
}>): {
|
|
10
|
+
inputSize: number;
|
|
11
|
+
contextSize: number;
|
|
12
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight token-size estimates for Optimixer predict inputs.
|
|
3
|
+
* Uses a chars/4 heuristic (no tiktoken dependency).
|
|
4
|
+
*/
|
|
5
|
+
export function estimateTextTokens(text) {
|
|
6
|
+
const trimmed = text.trim();
|
|
7
|
+
if (!trimmed)
|
|
8
|
+
return 0;
|
|
9
|
+
return Math.max(1, Math.ceil(trimmed.length / 4));
|
|
10
|
+
}
|
|
11
|
+
export function estimateMessagesTokenSizes(messages) {
|
|
12
|
+
let inputSize = 0;
|
|
13
|
+
let contextSize = 0;
|
|
14
|
+
for (const message of messages) {
|
|
15
|
+
const role = typeof message.role === 'string' ? message.role.toLowerCase() : '';
|
|
16
|
+
const content = typeof message.content === 'string'
|
|
17
|
+
? message.content
|
|
18
|
+
: message.content != null
|
|
19
|
+
? JSON.stringify(message.content)
|
|
20
|
+
: '';
|
|
21
|
+
const tokens = estimateTextTokens(content);
|
|
22
|
+
if (role === 'system') {
|
|
23
|
+
contextSize += tokens;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
inputSize += tokens;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return { inputSize, contextSize };
|
|
30
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -73,6 +73,17 @@ export type GatewayTraceAttempt = {
|
|
|
73
73
|
};
|
|
74
74
|
modelUsed?: string;
|
|
75
75
|
costUsd?: number;
|
|
76
|
+
/** Billing state for this attempt (trace mode; mirrors top-level {@link EnhancedLLMResponse.metadata.costStatus}). */
|
|
77
|
+
costStatus?: 'priced' | 'unpriced';
|
|
78
|
+
costBreakdown?: {
|
|
79
|
+
promptCostUsd: number;
|
|
80
|
+
completionCostUsd: number;
|
|
81
|
+
cachingCostUsd?: number;
|
|
82
|
+
reasoningCostUsd?: number;
|
|
83
|
+
audioCostUsd?: number;
|
|
84
|
+
imageCostUsd?: number;
|
|
85
|
+
requestFlatCostUsd?: number;
|
|
86
|
+
};
|
|
76
87
|
ok: boolean;
|
|
77
88
|
error?: {
|
|
78
89
|
name: string;
|
|
@@ -88,6 +99,22 @@ export type GatewayTraceAttempt = {
|
|
|
88
99
|
* Allowlisted merged router/generation config returned in {@link EnhancedLLMResponse.metadata}
|
|
89
100
|
* when `diagnostics.mode === 'trace'`. Omits arbitrary extras and secrets.
|
|
90
101
|
*/
|
|
102
|
+
/**
|
|
103
|
+
* Consolidated usage + billing summary on {@link EnhancedLLMResponse.metadata} when
|
|
104
|
+
* `diagnostics.mode === 'trace'` (single object for orchestrators / Run Analysis).
|
|
105
|
+
*/
|
|
106
|
+
export type GatewayTraceUsageSummary = {
|
|
107
|
+
tokens: {
|
|
108
|
+
prompt: number;
|
|
109
|
+
completion: number;
|
|
110
|
+
total: number;
|
|
111
|
+
};
|
|
112
|
+
maxTokensRequested?: number;
|
|
113
|
+
costUsd?: number;
|
|
114
|
+
cost?: number;
|
|
115
|
+
costStatus?: 'priced' | 'unpriced';
|
|
116
|
+
costBreakdown?: GatewayTraceAttempt['costBreakdown'];
|
|
117
|
+
};
|
|
91
118
|
export type GatewayTraceMergedConfig = Partial<Pick<ModelConfig, 'model' | 'modelId' | 'provider' | 'temperature' | 'maxTokens' | 'topP' | 'frequencyPenalty' | 'presencePenalty' | 'stop'>>;
|
|
92
119
|
/**
|
|
93
120
|
* Normalized observability payload attached to thrown errors from {@link AIGateway.invoke}
|
|
@@ -348,19 +375,35 @@ export interface GatewayConfig extends Omit<RouterConfig, 'defaultEngine' | 'log
|
|
|
348
375
|
mode?: 'dev' | 'debug' | 'prod';
|
|
349
376
|
/**
|
|
350
377
|
* @x12i/ai-tools integration: catalog model resolution (request) and cost calculation (response).
|
|
378
|
+
* Pricing catalogs load from open-assets JSON (remote with bundled fallback).
|
|
351
379
|
*/
|
|
352
380
|
aiTools?: {
|
|
353
381
|
/** @default true */
|
|
354
382
|
enabled?: boolean;
|
|
355
|
-
/**
|
|
356
|
-
catalox?: import('@x12i/catalox').Catalox;
|
|
383
|
+
/** In-memory catalog cache TTL (ms). Default in ai-tools is 24h. */
|
|
357
384
|
cacheTtlMs?: number;
|
|
385
|
+
/** Use bundled catalog JSON only (offline / tests). */
|
|
386
|
+
bundledOnly?: boolean;
|
|
358
387
|
/** @default true */
|
|
359
388
|
resolveModels?: boolean;
|
|
360
389
|
/** @default true */
|
|
361
390
|
calculateCost?: boolean;
|
|
362
391
|
costIncludeBreakdown?: boolean;
|
|
363
392
|
};
|
|
393
|
+
/**
|
|
394
|
+
* Adaptive `max_tokens` via @x12i/optimixer (embedded Activix mode).
|
|
395
|
+
* When enabled, the gateway predicts completion budget before each LLM call unless
|
|
396
|
+
* the caller explicitly sets `maxTokens` on the request / modelConfig / gateway config.
|
|
397
|
+
*/
|
|
398
|
+
optimixer?: {
|
|
399
|
+
/** @default false */
|
|
400
|
+
enabled?: boolean;
|
|
401
|
+
acceptableRisk?: 'very-low' | 'low' | 'medium' | 'high' | number;
|
|
402
|
+
/** Cap predicted max tokens with flex-md model limit when available. @default true */
|
|
403
|
+
useFlexMdCeiling?: boolean;
|
|
404
|
+
/** Passed to Optimixer warmup on create. */
|
|
405
|
+
warmupLimit?: number;
|
|
406
|
+
};
|
|
364
407
|
/**
|
|
365
408
|
* InstructionsBlocks overrides
|
|
366
409
|
* Key: block name, Value: block content
|
|
@@ -1009,6 +1052,11 @@ export interface EnhancedLLMResponse<TContent = unknown> extends Omit<AIResponse
|
|
|
1009
1052
|
* Ordered, authoritative attempts across retries and fallbacks (trace mode).
|
|
1010
1053
|
*/
|
|
1011
1054
|
attempts?: GatewayTraceAttempt[];
|
|
1055
|
+
/**
|
|
1056
|
+
* Final usage + billing for the invocation (trace mode). Mirrors successful-attempt
|
|
1057
|
+
* tokens and {@link costUsd} / {@link costStatus} after router passthrough + catalog pricing.
|
|
1058
|
+
*/
|
|
1059
|
+
usage?: GatewayTraceUsageSummary;
|
|
1012
1060
|
/**
|
|
1013
1061
|
* Merged gateway/router generation config actually used for the invocation (after
|
|
1014
1062
|
* {@link mergeConfig}: modelConfig / request.config / defaults / flex-md maxTokens).
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Manages activity tracking for LLM requests.
|
|
5
5
|
* Wraps the ActivityTracker and provides convenience methods.
|
|
6
6
|
*/
|
|
7
|
-
import { Activix, activixActivityIo, activixOuterTier, resolveActivixLogsDatabaseName, resolveActivixMongoUriFromEnv } from '@x12i/activix';
|
|
7
|
+
import { Activix, activixActivityIo, activixOuterTier, normalizeToActivixCostShape, resolveActivixLogsDatabaseName, resolveActivixMongoUriFromEnv } from '@x12i/activix';
|
|
8
8
|
import { resolveActivityTrackingConfig } from './config/activity-tracking-config.js';
|
|
9
9
|
import { gatewayLogDebug, withActivityIdentity } from './gateway-log-meta.js';
|
|
10
10
|
function readAiRequestIdFromRequest(request) {
|
|
@@ -165,16 +165,11 @@ function pickActivixUsageTokens(response) {
|
|
|
165
165
|
};
|
|
166
166
|
}
|
|
167
167
|
/**
|
|
168
|
-
* Activix
|
|
168
|
+
* Activix 7.x `outer.cost` from gateway billing + routing (Run Analysis G8).
|
|
169
|
+
* Uses Activix {@link normalizeToActivixCostShape} so the shape matches package validators.
|
|
169
170
|
*/
|
|
170
171
|
function buildActivixOuterCost(routingMeta, billing, response) {
|
|
171
|
-
const usd = typeof billing.cost === 'number' && Number.isFinite(billing.cost)
|
|
172
|
-
? billing.cost
|
|
173
|
-
: typeof routingMeta.costUsd === 'number' && Number.isFinite(routingMeta.costUsd)
|
|
174
|
-
? routingMeta.costUsd
|
|
175
|
-
: typeof routingMeta.cost === 'number' && Number.isFinite(routingMeta.cost)
|
|
176
|
-
? routingMeta.cost
|
|
177
|
-
: undefined;
|
|
172
|
+
const usd = typeof billing.cost === 'number' && Number.isFinite(billing.cost) ? billing.cost : undefined;
|
|
178
173
|
const tokens = pickActivixUsageTokens(response);
|
|
179
174
|
const provider = typeof routingMeta.provider === 'string' ? routingMeta.provider : undefined;
|
|
180
175
|
const model = typeof routingMeta.modelUsed === 'string'
|
|
@@ -189,20 +184,34 @@ function buildActivixOuterCost(routingMeta, billing, response) {
|
|
|
189
184
|
if (billing.costBreakdown != null && typeof billing.costBreakdown === 'object') {
|
|
190
185
|
details.costBreakdown = billing.costBreakdown;
|
|
191
186
|
}
|
|
192
|
-
const
|
|
193
|
-
if (usd === undefined && !tokens && !provider && !model && !hasDetails) {
|
|
194
|
-
return undefined;
|
|
195
|
-
}
|
|
196
|
-
return {
|
|
187
|
+
const candidate = {
|
|
197
188
|
...(usd !== undefined ? { usd, unit: 'USD' } : {}),
|
|
198
189
|
...(tokens ? { tokens } : {}),
|
|
199
190
|
...(provider ? { provider } : {}),
|
|
200
191
|
...(model ? { model } : {}),
|
|
201
|
-
...(
|
|
192
|
+
...(Object.keys(details).length > 0 ? { details } : {})
|
|
202
193
|
};
|
|
194
|
+
return normalizeToActivixCostShape(candidate, 'gateway.outer.cost') ?? undefined;
|
|
203
195
|
}
|
|
204
|
-
/**
|
|
205
|
-
function
|
|
196
|
+
/** Run-level record metadata (Activix 7.x top-level `metadata`, sibling to `outer`). */
|
|
197
|
+
function buildActivixRecordMetadata(response, billing) {
|
|
198
|
+
const out = {
|
|
199
|
+
...pickActivixCompletionRoutingMetadata(response)
|
|
200
|
+
};
|
|
201
|
+
if (billing.costStatus === 'priced' || billing.costStatus === 'unpriced') {
|
|
202
|
+
out.costStatus = billing.costStatus;
|
|
203
|
+
}
|
|
204
|
+
if (typeof billing.cost === 'number' && Number.isFinite(billing.cost)) {
|
|
205
|
+
out.cost = billing.cost;
|
|
206
|
+
out.costUsd = billing.cost;
|
|
207
|
+
}
|
|
208
|
+
if (billing.costBreakdown != null && typeof billing.costBreakdown === 'object') {
|
|
209
|
+
out.costBreakdown = billing.costBreakdown;
|
|
210
|
+
}
|
|
211
|
+
return out;
|
|
212
|
+
}
|
|
213
|
+
/** Routing / generation facts for Activix `outer.metadata` on completion (no billing — see root + `outer.cost`). */
|
|
214
|
+
function pickActivixCompletionRoutingMetadata(response) {
|
|
206
215
|
const out = {};
|
|
207
216
|
if (response != null && typeof response === 'object') {
|
|
208
217
|
const meta = response.metadata;
|
|
@@ -221,30 +230,6 @@ function pickActivixCompletionRoutingMetadata(response, billing) {
|
|
|
221
230
|
if (m.effectiveModelConfig != null && typeof m.effectiveModelConfig === 'object') {
|
|
222
231
|
out.effectiveModelConfig = m.effectiveModelConfig;
|
|
223
232
|
}
|
|
224
|
-
if (typeof m.cost === 'number' && Number.isFinite(m.cost))
|
|
225
|
-
out.cost = m.cost;
|
|
226
|
-
if (typeof m.costUsd === 'number' && Number.isFinite(m.costUsd))
|
|
227
|
-
out.costUsd = m.costUsd;
|
|
228
|
-
if (m.costStatus === 'priced' || m.costStatus === 'unpriced')
|
|
229
|
-
out.costStatus = m.costStatus;
|
|
230
|
-
if (m.costBreakdown != null && typeof m.costBreakdown === 'object') {
|
|
231
|
-
out.costBreakdown = m.costBreakdown;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
if (billing) {
|
|
236
|
-
if ((out.costStatus !== 'priced' && out.costStatus !== 'unpriced') &&
|
|
237
|
-
(billing.costStatus === 'priced' || billing.costStatus === 'unpriced')) {
|
|
238
|
-
out.costStatus = billing.costStatus;
|
|
239
|
-
}
|
|
240
|
-
if (typeof billing.cost === 'number' && Number.isFinite(billing.cost)) {
|
|
241
|
-
if (out.cost === undefined)
|
|
242
|
-
out.cost = billing.cost;
|
|
243
|
-
if (out.costUsd === undefined)
|
|
244
|
-
out.costUsd = billing.cost;
|
|
245
|
-
}
|
|
246
|
-
if (out.costBreakdown === undefined && billing.costBreakdown != null) {
|
|
247
|
-
out.costBreakdown = billing.costBreakdown;
|
|
248
233
|
}
|
|
249
234
|
}
|
|
250
235
|
return out;
|
|
@@ -345,8 +330,7 @@ export class ActivityManager {
|
|
|
345
330
|
failed: 'failed',
|
|
346
331
|
timeout: 'timeout'
|
|
347
332
|
};
|
|
348
|
-
|
|
349
|
-
// Keep mode explicit for operational clarity (matches integration checklist expectations).
|
|
333
|
+
const activixOptions = {
|
|
350
334
|
storageMode: 'automatic',
|
|
351
335
|
collections: [
|
|
352
336
|
{
|
|
@@ -385,41 +369,31 @@ export class ActivityManager {
|
|
|
385
369
|
exponentialBackoff: false
|
|
386
370
|
}
|
|
387
371
|
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
.
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
?
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
note: 'MongoDB stores one document per activity; the ai-actions collection is created on first insert (empty collections may be hidden in some tools until then).'
|
|
412
|
-
})
|
|
413
|
-
});
|
|
414
|
-
})
|
|
415
|
-
.catch((error) => {
|
|
416
|
-
// Init threw — disable tracker so requests are not blocked.
|
|
417
|
-
this.logger.warn('Activity tracking enabled but Activix init failed. Activity records will not be persisted.', {
|
|
418
|
-
error: error instanceof Error ? error.message : String(error),
|
|
419
|
-
hint: 'Set MONGO_URI or MONGO_LOGS_URI and a database name (MONGO_LOGS_DB, MONGO_DB, MONGO_AI_LOGS_DB, or ACTIVIX_DB_NAME). See README: Activity tracking / persistence troubleshooting.'
|
|
372
|
+
};
|
|
373
|
+
if (config.autoCost !== undefined && config.autoCost !== false) {
|
|
374
|
+
activixOptions.autoCost =
|
|
375
|
+
config.autoCost === true
|
|
376
|
+
? { enabled: true, overwriteOuterCost: false }
|
|
377
|
+
: { enabled: true, overwriteOuterCost: false, ...config.autoCost };
|
|
378
|
+
}
|
|
379
|
+
if (config.customTracker) {
|
|
380
|
+
this.activix = config.customTracker;
|
|
381
|
+
this.initPromise = Promise.resolve().then(() => this.logActivixBackendReady(collectionName, badRequestsCollectionName));
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
this.initPromise = Activix.create(activixOptions)
|
|
385
|
+
.then((ax) => {
|
|
386
|
+
this.activix = ax;
|
|
387
|
+
this.logActivixBackendReady(collectionName, badRequestsCollectionName);
|
|
388
|
+
})
|
|
389
|
+
.catch((error) => {
|
|
390
|
+
this.logger.warn('Activity tracking enabled but Activix init failed. Activity records will not be persisted.', {
|
|
391
|
+
error: error instanceof Error ? error.message : String(error),
|
|
392
|
+
hint: 'Set MONGO_URI or MONGO_LOGS_URI and a database name (MONGO_LOGS_DB, MONGO_DB, MONGO_AI_LOGS_DB, or ACTIVIX_DB_NAME). See README: Activity tracking / persistence troubleshooting.'
|
|
393
|
+
});
|
|
394
|
+
this.activix = undefined;
|
|
420
395
|
});
|
|
421
|
-
|
|
422
|
-
});
|
|
396
|
+
}
|
|
423
397
|
this.logger.debug('Activity tracking enabled with Activix', {
|
|
424
398
|
collection: collectionName,
|
|
425
399
|
badRequestsCollection: badRequestsCollectionName,
|
|
@@ -939,8 +913,9 @@ export class ActivityManager {
|
|
|
939
913
|
costStatus: details.costStatus,
|
|
940
914
|
costBreakdown: details.costBreakdown
|
|
941
915
|
};
|
|
942
|
-
const outerMetadata = pickActivixCompletionRoutingMetadata(details.response
|
|
916
|
+
const outerMetadata = pickActivixCompletionRoutingMetadata(details.response);
|
|
943
917
|
const outerCost = buildActivixOuterCost(outerMetadata, billingSlice, details.response);
|
|
918
|
+
const recordMetadata = buildActivixRecordMetadata(details.response, billingSlice);
|
|
944
919
|
await this.activix.completeRecord(activity.activityId, {
|
|
945
920
|
cost: details.cost,
|
|
946
921
|
...(typeof details.cost === 'number' && Number.isFinite(details.cost)
|
|
@@ -948,13 +923,12 @@ export class ActivityManager {
|
|
|
948
923
|
: {}),
|
|
949
924
|
...(details.costStatus ? { costStatus: details.costStatus } : {}),
|
|
950
925
|
response: details.response,
|
|
926
|
+
...(Object.keys(recordMetadata).length > 0 ? { metadata: recordMetadata } : {}),
|
|
951
927
|
outer: {
|
|
952
928
|
output: details.response,
|
|
953
929
|
metadata: outerMetadata,
|
|
954
930
|
...(outerCost ? { cost: outerCost } : {})
|
|
955
|
-
}
|
|
956
|
-
endTime: details.endTime,
|
|
957
|
-
duration: details.duration
|
|
931
|
+
}
|
|
958
932
|
}, { collection });
|
|
959
933
|
this.logger.debug('Activix.completeRecord completed', {
|
|
960
934
|
aiRequestId: activity.aiRequestId,
|
|
@@ -1224,6 +1198,36 @@ export class ActivityManager {
|
|
|
1224
1198
|
getTracker() {
|
|
1225
1199
|
return this.activix;
|
|
1226
1200
|
}
|
|
1201
|
+
/** Await Activix init (no-op when tracking is disabled). */
|
|
1202
|
+
async getReadyTracker() {
|
|
1203
|
+
if (this.initPromise) {
|
|
1204
|
+
await this.initPromise;
|
|
1205
|
+
}
|
|
1206
|
+
return this.activix;
|
|
1207
|
+
}
|
|
1208
|
+
logActivixBackendReady(collectionName, badRequestsCollectionName) {
|
|
1209
|
+
const ax = this.activix;
|
|
1210
|
+
if (!ax)
|
|
1211
|
+
return;
|
|
1212
|
+
const backend = ax.storageBackend;
|
|
1213
|
+
const mongoDb = backend === 'database' ? resolveActivixLogsDatabaseName() : undefined;
|
|
1214
|
+
const mongoUriConfigured = Boolean(resolveActivixMongoUriFromEnv());
|
|
1215
|
+
this.logger.info('Activity tracking persistence backend ready', {
|
|
1216
|
+
storageBackend: backend,
|
|
1217
|
+
mongoDatabase: mongoDb,
|
|
1218
|
+
mongoUriConfigured,
|
|
1219
|
+
mainCollection: collectionName,
|
|
1220
|
+
badRequestsCollection: badRequestsCollectionName,
|
|
1221
|
+
skillExecutionsCollection: this.skillExecutionsCollectionName,
|
|
1222
|
+
...(backend === 'local'
|
|
1223
|
+
? {
|
|
1224
|
+
note: 'Activix is using local playground storage, not MongoDB. The ai-actions collection will not appear in Mongo until URI is set (MONGO_URI or MONGO_LOGS_URI), Activix can ping the database, and at least one activity is written.'
|
|
1225
|
+
}
|
|
1226
|
+
: {
|
|
1227
|
+
note: 'MongoDB stores one document per activity; the ai-actions collection is created on first insert (empty collections may be hidden in some tools until then).'
|
|
1228
|
+
})
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1227
1231
|
/**
|
|
1228
1232
|
* Get status of activity tracker
|
|
1229
1233
|
*/
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Manages activity tracking for LLM requests.
|
|
5
5
|
* Wraps the ActivityTracker and provides convenience methods.
|
|
6
6
|
*/
|
|
7
|
-
import { Activix } from '@x12i/activix';
|
|
7
|
+
import { Activix, type ActivixAutoCostOptions } from '@x12i/activix';
|
|
8
8
|
import type { Logxer } from '@x12i/logxer';
|
|
9
9
|
import type { ActivityIdentity, ChatRequest, AIRequest, FailureType, LLMResponseFailureSubtype, ResponseParsingFailureSubtype } from './types.js';
|
|
10
10
|
type Request = ChatRequest | AIRequest;
|
|
@@ -31,6 +31,11 @@ export interface ActivityManagerConfig {
|
|
|
31
31
|
enableActivityTracking: boolean;
|
|
32
32
|
customTracker?: Activix;
|
|
33
33
|
logger: Logxer;
|
|
34
|
+
/**
|
|
35
|
+
* Activix 7.2+ {@link ActivixAutoCostOptions}: fill `outer.cost` via @x12i/ai-tools when the gateway
|
|
36
|
+
* did not supply a valid cost. Ignored when `customTracker` is provided.
|
|
37
|
+
*/
|
|
38
|
+
autoCost?: boolean | ActivixAutoCostOptions;
|
|
34
39
|
}
|
|
35
40
|
/**
|
|
36
41
|
* Manages activity tracking lifecycle
|
|
@@ -193,6 +198,9 @@ export declare class ActivityManager {
|
|
|
193
198
|
* @returns Activix instance or undefined if not enabled
|
|
194
199
|
*/
|
|
195
200
|
getTracker(): Activix | undefined;
|
|
201
|
+
/** Await Activix init (no-op when tracking is disabled). */
|
|
202
|
+
getReadyTracker(): Promise<Activix | undefined>;
|
|
203
|
+
private logActivixBackendReady;
|
|
196
204
|
/**
|
|
197
205
|
* Get status of activity tracker
|
|
198
206
|
*/
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Lazy @x12i/ai-tools catalog + cost calculator bootstrap.
|
|
3
3
|
*/
|
|
4
|
-
import { AiModelsCatalogClient, CostCalculator
|
|
4
|
+
import { AiModelsCatalogClient, CostCalculator } from '@x12i/ai-tools';
|
|
5
5
|
import { gatewayLogDebug, withActivityIdentity } from './gateway-log-meta.js';
|
|
6
6
|
let sharedClientPromise = null;
|
|
7
7
|
let sharedConfigKey;
|
|
8
8
|
let bootstrapFailedLogged = false;
|
|
9
9
|
function configKey(config) {
|
|
10
|
-
|
|
11
|
-
return `${injected}:${config.aiTools?.cacheTtlMs ?? ''}:${config.aiTools?.costIncludeBreakdown ?? ''}`;
|
|
10
|
+
return `${config.aiTools?.cacheTtlMs ?? ''}:${config.aiTools?.costIncludeBreakdown ?? ''}:${config.aiTools?.bundledOnly ?? ''}`;
|
|
12
11
|
}
|
|
13
12
|
/**
|
|
14
13
|
* Returns catalog + calculator, or null when disabled or bootstrap fails.
|
|
@@ -35,16 +34,9 @@ export function resetAiToolsClientForTests() {
|
|
|
35
34
|
}
|
|
36
35
|
async function bootstrapAiTools(config, logger) {
|
|
37
36
|
try {
|
|
38
|
-
let catalox = config.aiTools?.catalox;
|
|
39
|
-
if (!catalox) {
|
|
40
|
-
const { createCataloxFromEnv } = await import('@x12i/catalox/firebase');
|
|
41
|
-
const bootstrapped = createCataloxFromEnv();
|
|
42
|
-
catalox = bootstrapped.catalox;
|
|
43
|
-
}
|
|
44
|
-
await ensureAiModelsCatalog(catalox);
|
|
45
37
|
const catalog = new AiModelsCatalogClient({
|
|
46
|
-
|
|
47
|
-
|
|
38
|
+
cacheTtlMs: config.aiTools?.cacheTtlMs,
|
|
39
|
+
...(config.aiTools?.bundledOnly ? { bundledOnly: true } : {})
|
|
48
40
|
});
|
|
49
41
|
const calculator = new CostCalculator(catalog, {
|
|
50
42
|
includeBreakdown: config.aiTools?.costIncludeBreakdown === true
|
|
@@ -48,6 +48,7 @@ function getDefaultsDir() {
|
|
|
48
48
|
import { LLMProviderRouter } from '@x12i/ai-providers-router';
|
|
49
49
|
import { createGatewayLogger } from './logger-factory.js';
|
|
50
50
|
import { ActivityManager } from './activity-manager.js';
|
|
51
|
+
import { OptimixerManager } from './optimixer-manager.js';
|
|
51
52
|
import { UsageTracker } from './usage-tracker.js';
|
|
52
53
|
import { mergeTemplateRenderOptions } from './template-render-merge.js';
|
|
53
54
|
import { GatewayRateLimiter } from './gateway-rate-limiter.js';
|
|
@@ -265,7 +266,23 @@ export function initializeGatewayComponents(config) {
|
|
|
265
266
|
const activityManager = new ActivityManager({
|
|
266
267
|
enableActivityTracking: config.enableActivityTracking ?? true,
|
|
267
268
|
customTracker: config.activityTracker,
|
|
268
|
-
logger
|
|
269
|
+
logger,
|
|
270
|
+
...(config.activityTracker
|
|
271
|
+
? {}
|
|
272
|
+
: {
|
|
273
|
+
autoCost: config.aiTools?.enabled === false || config.aiTools?.calculateCost === false
|
|
274
|
+
? false
|
|
275
|
+
: {
|
|
276
|
+
enabled: true,
|
|
277
|
+
overwriteOuterCost: false,
|
|
278
|
+
...(config.aiTools?.bundledOnly ? { bundledOnly: true } : {})
|
|
279
|
+
}
|
|
280
|
+
})
|
|
281
|
+
});
|
|
282
|
+
const optimixerManager = new OptimixerManager({
|
|
283
|
+
optimixer: config.optimixer,
|
|
284
|
+
logger,
|
|
285
|
+
getActivix: () => activityManager.getReadyTracker()
|
|
269
286
|
});
|
|
270
287
|
const templateRendering = mergeTemplateRenderOptions(defaultTemplateRendering, config.templateRendering);
|
|
271
288
|
const instructionsBlockOverrides = {
|
|
@@ -282,6 +299,7 @@ export function initializeGatewayComponents(config) {
|
|
|
282
299
|
logger,
|
|
283
300
|
router,
|
|
284
301
|
activityManager,
|
|
302
|
+
optimixerManager,
|
|
285
303
|
usageTracker,
|
|
286
304
|
messageBuilderConfig,
|
|
287
305
|
defaultModelConfig
|
|
@@ -6,6 +6,7 @@ import type { GatewayConfig } from './types.js';
|
|
|
6
6
|
import type { Logxer } from '@x12i/logxer';
|
|
7
7
|
import { LLMProviderRouter } from '@x12i/ai-providers-router';
|
|
8
8
|
import { ActivityManager } from './activity-manager.js';
|
|
9
|
+
import { OptimixerManager } from './optimixer-manager.js';
|
|
9
10
|
import { UsageTracker } from './usage-tracker.js';
|
|
10
11
|
import type { MessageBuilderConfig } from './message-builder.js';
|
|
11
12
|
import type { TemplateRenderOptions } from '@x12i/rendrix';
|
|
@@ -16,6 +17,7 @@ export interface GatewayConfigContext {
|
|
|
16
17
|
logger: Logxer;
|
|
17
18
|
router: LLMProviderRouter;
|
|
18
19
|
activityManager: ActivityManager;
|
|
20
|
+
optimixerManager: OptimixerManager;
|
|
19
21
|
usageTracker: UsageTracker;
|
|
20
22
|
messageBuilderConfig: MessageBuilderConfig;
|
|
21
23
|
}
|
|
@@ -45,6 +47,7 @@ export declare function initializeGatewayComponents(config: GatewayConfig): {
|
|
|
45
47
|
logger: Logxer;
|
|
46
48
|
router: LLMProviderRouter;
|
|
47
49
|
activityManager: ActivityManager;
|
|
50
|
+
optimixerManager: OptimixerManager;
|
|
48
51
|
usageTracker: UsageTracker;
|
|
49
52
|
messageBuilderConfig: MessageBuilderConfig;
|
|
50
53
|
defaultModelConfig: Record<string, unknown>;
|