@x12i/ai-gateway 9.3.5 → 9.4.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/README.md +151 -4147
- package/dist/activity-manager.d.ts +6 -1
- package/dist/activity-manager.js +39 -48
- package/dist/ai-tools-client.js +4 -12
- package/dist/gateway-config.js +12 -1
- package/dist/gateway-utils.d.ts +22 -2
- package/dist/gateway-utils.js +148 -27
- package/dist/gateway.js +12 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -19
- package/dist/types.d.ts +36 -2
- package/dist-cjs/activity-manager.cjs +39 -48
- package/dist-cjs/activity-manager.d.ts +6 -1
- package/dist-cjs/ai-tools-client.cjs +4 -12
- package/dist-cjs/gateway-config.cjs +12 -1
- package/dist-cjs/gateway-utils.cjs +148 -27
- package/dist-cjs/gateway-utils.d.ts +22 -2
- package/dist-cjs/gateway.cjs +12 -1
- package/dist-cjs/index.cjs +3 -19
- package/dist-cjs/index.d.ts +4 -3
- package/dist-cjs/types.d.ts +36 -2
- package/package.json +9 -36
|
@@ -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
|
package/dist/activity-manager.js
CHANGED
|
@@ -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 } : {})
|
|
193
|
+
};
|
|
194
|
+
return normalizeToActivixCostShape(candidate, 'gateway.outer.cost') ?? undefined;
|
|
195
|
+
}
|
|
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)
|
|
202
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;
|
|
203
212
|
}
|
|
204
|
-
/** Routing / generation facts for Activix `outer.metadata` on completion (
|
|
205
|
-
function pickActivixCompletionRoutingMetadata(response
|
|
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,7 +369,14 @@ export class ActivityManager {
|
|
|
385
369
|
exponentialBackoff: false
|
|
386
370
|
}
|
|
387
371
|
}
|
|
388
|
-
}
|
|
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
|
+
this.activix = config.customTracker ?? new Activix(activixOptions);
|
|
389
380
|
this.initPromise = this.activix
|
|
390
381
|
.init()
|
|
391
382
|
.then(() => {
|
|
@@ -939,8 +930,9 @@ export class ActivityManager {
|
|
|
939
930
|
costStatus: details.costStatus,
|
|
940
931
|
costBreakdown: details.costBreakdown
|
|
941
932
|
};
|
|
942
|
-
const outerMetadata = pickActivixCompletionRoutingMetadata(details.response
|
|
933
|
+
const outerMetadata = pickActivixCompletionRoutingMetadata(details.response);
|
|
943
934
|
const outerCost = buildActivixOuterCost(outerMetadata, billingSlice, details.response);
|
|
935
|
+
const recordMetadata = buildActivixRecordMetadata(details.response, billingSlice);
|
|
944
936
|
await this.activix.completeRecord(activity.activityId, {
|
|
945
937
|
cost: details.cost,
|
|
946
938
|
...(typeof details.cost === 'number' && Number.isFinite(details.cost)
|
|
@@ -948,13 +940,12 @@ export class ActivityManager {
|
|
|
948
940
|
: {}),
|
|
949
941
|
...(details.costStatus ? { costStatus: details.costStatus } : {}),
|
|
950
942
|
response: details.response,
|
|
943
|
+
...(Object.keys(recordMetadata).length > 0 ? { metadata: recordMetadata } : {}),
|
|
951
944
|
outer: {
|
|
952
945
|
output: details.response,
|
|
953
946
|
metadata: outerMetadata,
|
|
954
947
|
...(outerCost ? { cost: outerCost } : {})
|
|
955
|
-
}
|
|
956
|
-
endTime: details.endTime,
|
|
957
|
-
duration: details.duration
|
|
948
|
+
}
|
|
958
949
|
}, { collection });
|
|
959
950
|
this.logger.debug('Activix.completeRecord completed', {
|
|
960
951
|
aiRequestId: activity.aiRequestId,
|
package/dist/ai-tools-client.js
CHANGED
|
@@ -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
|
package/dist/gateway-config.js
CHANGED
|
@@ -265,7 +265,18 @@ export function initializeGatewayComponents(config) {
|
|
|
265
265
|
const activityManager = new ActivityManager({
|
|
266
266
|
enableActivityTracking: config.enableActivityTracking ?? true,
|
|
267
267
|
customTracker: config.activityTracker,
|
|
268
|
-
logger
|
|
268
|
+
logger,
|
|
269
|
+
...(config.activityTracker
|
|
270
|
+
? {}
|
|
271
|
+
: {
|
|
272
|
+
autoCost: config.aiTools?.enabled === false || config.aiTools?.calculateCost === false
|
|
273
|
+
? false
|
|
274
|
+
: {
|
|
275
|
+
enabled: true,
|
|
276
|
+
overwriteOuterCost: false,
|
|
277
|
+
...(config.aiTools?.bundledOnly ? { bundledOnly: true } : {})
|
|
278
|
+
}
|
|
279
|
+
})
|
|
269
280
|
});
|
|
270
281
|
const templateRendering = mergeTemplateRenderOptions(defaultTemplateRendering, config.templateRendering);
|
|
271
282
|
const instructionsBlockOverrides = {
|
package/dist/gateway-utils.d.ts
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* Gateway Utilities Module
|
|
3
3
|
* Handles utility functions
|
|
4
4
|
*/
|
|
5
|
-
import type { AIInvokeRequest, ChatRequest, GatewayConfig, GatewayInvokeRejectionMetadata, GatewayTraceMergedConfig, GatewayTraceRequestIds, ModelConfig } from './types.js';
|
|
5
|
+
import type { AIInvokeRequest, ChatRequest, GatewayConfig, GatewayInvokeRejectionMetadata, GatewayTraceAttempt, GatewayTraceMergedConfig, GatewayTraceRequestIds, GatewayTraceUsageSummary, ModelConfig } from './types.js';
|
|
6
6
|
import type { Logxer } from '@x12i/logxer';
|
|
7
|
-
import { type AiModelsCatalogClient, type CostCalculator } from '@x12i/ai-tools';
|
|
7
|
+
import { type AiCostResult, type AiModelsCatalogClient, type CostCalculator } from '@x12i/ai-tools';
|
|
8
8
|
/**
|
|
9
9
|
* Generates MD5 hash of a string
|
|
10
10
|
*/
|
|
@@ -91,6 +91,13 @@ export type ResolveCostCompletionOptions = {
|
|
|
91
91
|
calculator?: CostCalculator | null;
|
|
92
92
|
calculateCost?: boolean;
|
|
93
93
|
};
|
|
94
|
+
/** Record shape for {@link CostCalculator.calculateFromRecord} (router + merged config + usage). */
|
|
95
|
+
export declare function buildGatewayPricingRecord(routerResponse: unknown, tokens: {
|
|
96
|
+
prompt: number;
|
|
97
|
+
completion: number;
|
|
98
|
+
total: number;
|
|
99
|
+
}, mergedConfig?: unknown): Record<string, unknown>;
|
|
100
|
+
export declare function mapAiCostResultToResolvedActivityCost(base: ResolvedActivityCost, result: AiCostResult): ResolvedActivityCost;
|
|
94
101
|
/**
|
|
95
102
|
* Router cost passthrough, then optional @x12i/ai-tools catalog pricing when still unpriced.
|
|
96
103
|
*/
|
|
@@ -99,6 +106,19 @@ export declare function resolveCostCompletionWithAiTools(routerResponse: unknown
|
|
|
99
106
|
completion: number;
|
|
100
107
|
total: number;
|
|
101
108
|
}, options?: ResolveCostCompletionOptions): Promise<ResolvedActivityCost>;
|
|
109
|
+
/**
|
|
110
|
+
* Trace-mode summary: final token usage + resolved billing (after catalog pricing when applicable).
|
|
111
|
+
*/
|
|
112
|
+
export declare function buildTraceUsageSummary(tokens: {
|
|
113
|
+
prompt: number;
|
|
114
|
+
completion: number;
|
|
115
|
+
total: number;
|
|
116
|
+
}, billing: ResolvedActivityCost, maxTokensRequested?: number): GatewayTraceUsageSummary | undefined;
|
|
117
|
+
/**
|
|
118
|
+
* Apply resolved billing to trace attempts: final successful attempt gets aggregate billing;
|
|
119
|
+
* other successful attempts without router cost get per-attempt catalog pricing when enabled.
|
|
120
|
+
*/
|
|
121
|
+
export declare function enrichTraceAttemptsWithBilling(attempts: GatewayTraceAttempt[], finalBilling: ResolvedActivityCost, options?: ResolveCostCompletionOptions): Promise<void>;
|
|
102
122
|
/**
|
|
103
123
|
* Stable routing facts for gateway response metadata (router metadata + merged config fallbacks).
|
|
104
124
|
* Matches trace-mode resolution; intended for every successful invoke(), not only diagnostics.trace.
|
package/dist/gateway-utils.js
CHANGED
|
@@ -434,6 +434,50 @@ export function resolveCostCompletionForActivity(routerResponse, tokens) {
|
|
|
434
434
|
}
|
|
435
435
|
return resolveActivityCostCompletion(tokens, costUsd);
|
|
436
436
|
}
|
|
437
|
+
/** Record shape for {@link CostCalculator.calculateFromRecord} (router + merged config + usage). */
|
|
438
|
+
export function buildGatewayPricingRecord(routerResponse, tokens, mergedConfig) {
|
|
439
|
+
const base = routerResponse != null && typeof routerResponse === 'object'
|
|
440
|
+
? { ...routerResponse }
|
|
441
|
+
: {};
|
|
442
|
+
const meta = base.metadata != null && typeof base.metadata === 'object'
|
|
443
|
+
? { ...base.metadata }
|
|
444
|
+
: {};
|
|
445
|
+
const routing = pickInvokeRoutingMetadataSlice(routerResponse, mergedConfig);
|
|
446
|
+
return {
|
|
447
|
+
...base,
|
|
448
|
+
usage: {
|
|
449
|
+
promptTokens: tokens.prompt,
|
|
450
|
+
completionTokens: tokens.completion,
|
|
451
|
+
totalTokens: tokens.total
|
|
452
|
+
},
|
|
453
|
+
tokens,
|
|
454
|
+
metadata: {
|
|
455
|
+
...meta,
|
|
456
|
+
tokens,
|
|
457
|
+
...(routing.provider ? { provider: routing.provider } : {}),
|
|
458
|
+
...(routing.modelUsed
|
|
459
|
+
? { modelUsed: routing.modelUsed, model: routing.modelUsed }
|
|
460
|
+
: {})
|
|
461
|
+
},
|
|
462
|
+
...(mergedConfig != null ? { config: mergedConfig } : {})
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
export function mapAiCostResultToResolvedActivityCost(base, result) {
|
|
466
|
+
if (result.unknownModel) {
|
|
467
|
+
return base.costStatus ? base : { ...base, costStatus: 'unpriced' };
|
|
468
|
+
}
|
|
469
|
+
if (typeof result.cost !== 'number' || !Number.isFinite(result.cost)) {
|
|
470
|
+
return base;
|
|
471
|
+
}
|
|
472
|
+
if (!result.isAuthoritative && result.source === 'estimate-fallback') {
|
|
473
|
+
return base.costStatus ? base : { ...base, costStatus: 'unpriced' };
|
|
474
|
+
}
|
|
475
|
+
return {
|
|
476
|
+
cost: result.cost,
|
|
477
|
+
costStatus: 'priced',
|
|
478
|
+
...(result.breakdown ? { costBreakdown: result.breakdown } : {})
|
|
479
|
+
};
|
|
480
|
+
}
|
|
437
481
|
/**
|
|
438
482
|
* Router cost passthrough, then optional @x12i/ai-tools catalog pricing when still unpriced.
|
|
439
483
|
*/
|
|
@@ -452,37 +496,114 @@ export async function resolveCostCompletionWithAiTools(routerResponse, tokens, o
|
|
|
452
496
|
if (!hasNonZeroTokenUsage(tokens)) {
|
|
453
497
|
return base;
|
|
454
498
|
}
|
|
455
|
-
const routing = pickInvokeRoutingMetadataSlice(routerResponse, options.mergedConfig);
|
|
456
|
-
const cfg = options.mergedConfig != null && typeof options.mergedConfig === 'object'
|
|
457
|
-
? options.mergedConfig
|
|
458
|
-
: {};
|
|
459
|
-
const provider = routing.provider ?? cfg.provider;
|
|
460
|
-
const modelUsed = routing.modelUsed ?? cfg.model;
|
|
461
|
-
if (!provider || !modelUsed) {
|
|
462
|
-
return base;
|
|
463
|
-
}
|
|
464
499
|
try {
|
|
465
|
-
const
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
completion: tokens.completion,
|
|
469
|
-
total: tokens.total
|
|
470
|
-
},
|
|
471
|
-
provider,
|
|
472
|
-
modelUsed
|
|
473
|
-
});
|
|
474
|
-
if (typeof result.cost === 'number' && Number.isFinite(result.cost)) {
|
|
475
|
-
return {
|
|
476
|
-
cost: result.cost,
|
|
477
|
-
costStatus: 'priced',
|
|
478
|
-
...(result.breakdown ? { costBreakdown: result.breakdown } : {})
|
|
479
|
-
};
|
|
480
|
-
}
|
|
500
|
+
const record = buildGatewayPricingRecord(routerResponse, tokens, options.mergedConfig);
|
|
501
|
+
const result = await options.calculator.calculateFromRecord(record);
|
|
502
|
+
return mapAiCostResultToResolvedActivityCost(base, result);
|
|
481
503
|
}
|
|
482
504
|
catch {
|
|
483
|
-
|
|
505
|
+
const routing = pickInvokeRoutingMetadataSlice(routerResponse, options.mergedConfig);
|
|
506
|
+
const cfg = options.mergedConfig != null && typeof options.mergedConfig === 'object'
|
|
507
|
+
? options.mergedConfig
|
|
508
|
+
: {};
|
|
509
|
+
const provider = routing.provider ?? cfg.provider;
|
|
510
|
+
const modelUsed = routing.modelUsed ?? cfg.model;
|
|
511
|
+
if (!provider || !modelUsed) {
|
|
512
|
+
return base;
|
|
513
|
+
}
|
|
514
|
+
try {
|
|
515
|
+
const result = await options.calculator.calculate({
|
|
516
|
+
tokens: {
|
|
517
|
+
prompt: tokens.prompt,
|
|
518
|
+
completion: tokens.completion,
|
|
519
|
+
total: tokens.total
|
|
520
|
+
},
|
|
521
|
+
provider,
|
|
522
|
+
usedModel: modelUsed
|
|
523
|
+
});
|
|
524
|
+
return mapAiCostResultToResolvedActivityCost(base, result);
|
|
525
|
+
}
|
|
526
|
+
catch {
|
|
527
|
+
return base;
|
|
528
|
+
}
|
|
484
529
|
}
|
|
485
|
-
|
|
530
|
+
}
|
|
531
|
+
function applyBillingToTraceAttempt(attempt, billing) {
|
|
532
|
+
if (billing.costStatus === 'priced' || billing.costStatus === 'unpriced') {
|
|
533
|
+
attempt.costStatus = billing.costStatus;
|
|
534
|
+
}
|
|
535
|
+
if (typeof billing.cost === 'number' && Number.isFinite(billing.cost)) {
|
|
536
|
+
attempt.costUsd = billing.cost;
|
|
537
|
+
}
|
|
538
|
+
if (billing.costBreakdown) {
|
|
539
|
+
attempt.costBreakdown = billing.costBreakdown;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
function buildTraceAttemptPricingRecord(attempt, mergedConfig) {
|
|
543
|
+
const tokens = attempt.usage?.tokens ?? { prompt: 0, completion: 0, total: 0 };
|
|
544
|
+
return buildGatewayPricingRecord({
|
|
545
|
+
metadata: {
|
|
546
|
+
provider: attempt.routing.provider,
|
|
547
|
+
modelUsed: attempt.modelUsed,
|
|
548
|
+
region: attempt.routing.region,
|
|
549
|
+
tokens
|
|
550
|
+
}
|
|
551
|
+
}, tokens, mergedConfig);
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Trace-mode summary: final token usage + resolved billing (after catalog pricing when applicable).
|
|
555
|
+
*/
|
|
556
|
+
export function buildTraceUsageSummary(tokens, billing, maxTokensRequested) {
|
|
557
|
+
if (!hasNonZeroTokenUsage(tokens) && !billing.costStatus) {
|
|
558
|
+
return undefined;
|
|
559
|
+
}
|
|
560
|
+
const summary = { tokens };
|
|
561
|
+
if (maxTokensRequested !== undefined) {
|
|
562
|
+
summary.maxTokensRequested = maxTokensRequested;
|
|
563
|
+
}
|
|
564
|
+
if (billing.costStatus === 'priced' && typeof billing.cost === 'number') {
|
|
565
|
+
summary.costUsd = billing.cost;
|
|
566
|
+
summary.cost = billing.cost;
|
|
567
|
+
}
|
|
568
|
+
if (billing.costStatus) {
|
|
569
|
+
summary.costStatus = billing.costStatus;
|
|
570
|
+
}
|
|
571
|
+
if (billing.costBreakdown) {
|
|
572
|
+
summary.costBreakdown = billing.costBreakdown;
|
|
573
|
+
}
|
|
574
|
+
return summary;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Apply resolved billing to trace attempts: final successful attempt gets aggregate billing;
|
|
578
|
+
* other successful attempts without router cost get per-attempt catalog pricing when enabled.
|
|
579
|
+
*/
|
|
580
|
+
export async function enrichTraceAttemptsWithBilling(attempts, finalBilling, options) {
|
|
581
|
+
if (!attempts.length)
|
|
582
|
+
return;
|
|
583
|
+
let lastOkIdx = -1;
|
|
584
|
+
for (let i = attempts.length - 1; i >= 0; i--) {
|
|
585
|
+
if (attempts[i].ok) {
|
|
586
|
+
lastOkIdx = i;
|
|
587
|
+
break;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
if (lastOkIdx >= 0) {
|
|
591
|
+
applyBillingToTraceAttempt(attempts[lastOkIdx], finalBilling);
|
|
592
|
+
}
|
|
593
|
+
if (options?.calculateCost === false || !options?.calculator) {
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
await Promise.all(attempts.map(async (attempt, idx) => {
|
|
597
|
+
if (!attempt.ok || idx === lastOkIdx)
|
|
598
|
+
return;
|
|
599
|
+
const tokens = attempt.usage?.tokens;
|
|
600
|
+
if (!tokens || !hasNonZeroTokenUsage(tokens))
|
|
601
|
+
return;
|
|
602
|
+
if (attempt.costStatus === 'priced' && typeof attempt.costUsd === 'number')
|
|
603
|
+
return;
|
|
604
|
+
const slice = await resolveCostCompletionWithAiTools(buildTraceAttemptPricingRecord(attempt, options.mergedConfig), tokens, options);
|
|
605
|
+
applyBillingToTraceAttempt(attempt, slice);
|
|
606
|
+
}));
|
|
486
607
|
}
|
|
487
608
|
/**
|
|
488
609
|
* Stable routing facts for gateway response metadata (router metadata + merged config fallbacks).
|
package/dist/gateway.js
CHANGED
|
@@ -9,7 +9,7 @@ import { initializeGatewayComponents } from './gateway-config.js';
|
|
|
9
9
|
import { buildMessages } from './message-builder.js';
|
|
10
10
|
import { extractJsonFromFlexMd } from './flex-md-loader.js';
|
|
11
11
|
import { enrichParsedContentForOutputContract, resolveOutputContractFieldKeys } from './output-contract-normalizer.js';
|
|
12
|
-
import { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, capActivityFullResponsePayload, DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, extractCostUsdFromRouterResponse, extractTokenUsageFromRouterResponse, mergeConfig, pickEffectiveModelConfigForMetadata, pickInvokeRoutingMetadataSlice, pickTraceMergedRouterConfig, resolveCostCompletionWithAiTools, tryExtractRouterLikePayloadFromErrorChain } from './gateway-utils.js';
|
|
12
|
+
import { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, capActivityFullResponsePayload, DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, extractCostUsdFromRouterResponse, extractTokenUsageFromRouterResponse, mergeConfig, pickEffectiveModelConfigForMetadata, pickInvokeRoutingMetadataSlice, pickTraceMergedRouterConfig, resolveCostCompletionWithAiTools, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, tryExtractRouterLikePayloadFromErrorChain } from './gateway-utils.js';
|
|
13
13
|
import { getAiToolsClient } from './ai-tools-client.js';
|
|
14
14
|
import { autoRegisterProviders } from './gateway-provider-auto-register.js';
|
|
15
15
|
import { setGatewayLastJobId, setGatewayRuntimeClients } from './runtime-objects.js';
|
|
@@ -567,6 +567,16 @@ export class AIGateway {
|
|
|
567
567
|
const routingMetadataSlice = pickInvokeRoutingMetadataSlice(routerResponse, mergedConfig);
|
|
568
568
|
const effectiveModelConfig = pickEffectiveModelConfigForMetadata(mergedConfig);
|
|
569
569
|
const traceMergedRouterSnapshot = traceEnabled ? pickTraceMergedRouterConfig(mergedConfig) : undefined;
|
|
570
|
+
if (traceEnabled && traceAttempts) {
|
|
571
|
+
await enrichTraceAttemptsWithBilling(traceAttempts, costCompletion, {
|
|
572
|
+
mergedConfig,
|
|
573
|
+
calculator: aiTools?.calculator ?? null,
|
|
574
|
+
calculateCost: this.config.aiTools?.calculateCost
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
const traceUsageSummary = traceEnabled
|
|
578
|
+
? buildTraceUsageSummary(tokens, costCompletion, routingMetadataSlice.maxTokensRequested)
|
|
579
|
+
: undefined;
|
|
570
580
|
const enhancedResponse = {
|
|
571
581
|
content: content,
|
|
572
582
|
parsedContent: parsedContent,
|
|
@@ -597,6 +607,7 @@ export class AIGateway {
|
|
|
597
607
|
retryCount: traceRetryCount,
|
|
598
608
|
fallbackCount: traceFallbackCount,
|
|
599
609
|
attempts: traceAttempts,
|
|
610
|
+
...(traceUsageSummary !== undefined ? { usage: traceUsageSummary } : {}),
|
|
600
611
|
...(traceMergedRouterSnapshot !== undefined
|
|
601
612
|
? { mergedRouterConfig: traceMergedRouterSnapshot }
|
|
602
613
|
: {})
|
package/dist/index.d.ts
CHANGED
|
@@ -16,8 +16,8 @@ export * from '@x12i/ai-providers-router';
|
|
|
16
16
|
export { AIGateway } from './gateway.js';
|
|
17
17
|
export { InstructionNotFoundError, InstructionBackendError } from './instruction-errors.js';
|
|
18
18
|
export { autoRegisterProviders } from './gateway-provider-auto-register.js';
|
|
19
|
-
export type { GatewayConfig, ProviderModelRef, ModelConfig, RetryConfig, ChatRequest, AIInvokeRequest, AIRequest, GatewayActionType, GatewayInvokeRejectionMetadata, GatewayTraceRequestIds, GatewayTraceMergedConfig, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions, SmartInputConfig, SmartInputRenderOptions } from './types.js';
|
|
20
|
-
export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, hasNonZeroTokenUsage } from './gateway-utils.js';
|
|
19
|
+
export type { GatewayConfig, ProviderModelRef, ModelConfig, RetryConfig, ChatRequest, AIInvokeRequest, AIRequest, GatewayActionType, GatewayInvokeRejectionMetadata, GatewayTraceRequestIds, GatewayTraceAttempt, GatewayTraceUsageSummary, GatewayTraceMergedConfig, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions, SmartInputConfig, SmartInputRenderOptions } from './types.js';
|
|
20
|
+
export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, hasNonZeroTokenUsage } from './gateway-utils.js';
|
|
21
21
|
export { getGatewayOperationalMode, isProdGatewayMode, resolveGatewayDefaultModel, parseModelProviderSpec, CODE_DEFAULT_MODEL } from './gateway-mode.js';
|
|
22
22
|
export type { GatewayOperationalMode, GatewayDefaultModelSource, DefaultModelSubstitutionReason, ResolvedGatewayDefault } from './gateway-mode.js';
|
|
23
23
|
export type { ActivityCostStatus, ResolvedActivityCost } from './gateway-utils.js';
|
|
@@ -29,7 +29,8 @@ export { GATEWAY_DUAL_MEMORY_ROOTS, buildMemoryResolutionRootFromWorkingMemory,
|
|
|
29
29
|
export type { GatewayDualMemoryRoot } from './memory-path-resolution.js';
|
|
30
30
|
export type { UsageTier } from './types.js';
|
|
31
31
|
export { Activix } from '@x12i/activix';
|
|
32
|
-
export type { ActivixRunContext, FindByRunContextCriteria, GetJobActivitiesInput, GetJobActivitiesResult } from '@x12i/activix';
|
|
32
|
+
export type { ActivixRunContext, ActivixAutoCostOptions, ActivixCostShape, FindByRunContextCriteria, GetJobActivitiesInput, GetJobActivitiesResult } from '@x12i/activix';
|
|
33
|
+
export { normalizeToActivixCostShape } from '@x12i/activix';
|
|
33
34
|
export { ActivityManager, ensureGatewayRequestIdentity } from './activity-manager.js';
|
|
34
35
|
export type { ActivityIdentity } from './types.js';
|
|
35
36
|
export { activityIdentityToLogMeta, withActivityIdentity, gatewayLogDebug } from './gateway-log-meta.js';
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,7 @@ export * from '@x12i/ai-providers-router';
|
|
|
17
17
|
export { AIGateway } from './gateway.js';
|
|
18
18
|
export { InstructionNotFoundError, InstructionBackendError } from './instruction-errors.js';
|
|
19
19
|
export { autoRegisterProviders } from './gateway-provider-auto-register.js';
|
|
20
|
-
export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, hasNonZeroTokenUsage } from './gateway-utils.js';
|
|
20
|
+
export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, hasNonZeroTokenUsage } from './gateway-utils.js';
|
|
21
21
|
export { getGatewayOperationalMode, isProdGatewayMode, resolveGatewayDefaultModel, parseModelProviderSpec, CODE_DEFAULT_MODEL } from './gateway-mode.js';
|
|
22
22
|
export { contractSpecToFieldKeys, enrichParsedContentForOutputContract, resolveOutputContractFieldKeys } from './output-contract-normalizer.js';
|
|
23
23
|
export { mergeGatewayAndRequestTemplateRenderOptions, mergeTemplateRenderOptions } from './template-render-merge.js';
|
|
@@ -26,6 +26,7 @@ export { GATEWAY_DUAL_MEMORY_ROOTS, buildMemoryResolutionRootFromWorkingMemory,
|
|
|
26
26
|
// (x-models was previously used for RPM/TPM tracking but is no longer integrated)
|
|
27
27
|
// Re-export activity tracking primitives (Activix)
|
|
28
28
|
export { Activix } from '@x12i/activix';
|
|
29
|
+
export { normalizeToActivixCostShape } from '@x12i/activix';
|
|
29
30
|
export { ActivityManager, ensureGatewayRequestIdentity } from './activity-manager.js';
|
|
30
31
|
export { activityIdentityToLogMeta, withActivityIdentity, gatewayLogDebug } from './gateway-log-meta.js';
|
|
31
32
|
// Re-export logging (@x12i/logxer)
|
|
@@ -39,22 +40,5 @@ export { DEFAULT_RATE_LIMIT_MIN_INTERVAL_MS, DEFAULT_RATE_LIMIT_ENABLED } from '
|
|
|
39
40
|
export { validateAIRequest, validateJSON, extractJSON, validateResponse, diagnoseRequest, diagnoseResponse, supportsJSONMode, createTestAIRequest, createValidationTestCases, runValidationTests, formatDiagnostic, assertValidAIRequest } from './troubleshooting-helper.js';
|
|
40
41
|
// Export object types library
|
|
41
42
|
export { OBJECT_TYPES_LIBRARY, getObjectType, getObjectTypesForAgent } from './object-types-library.js';
|
|
42
|
-
//
|
|
43
|
+
// Object-types library stubs (optional @x12i/outputs-library integration; see object-types-library-integration.ts)
|
|
43
44
|
export { initializeObjectTypesLibrary, getObjectTypesLibrary, resetObjectTypesLibrary } from './object-types-library-integration.js';
|
|
44
|
-
// Re-export outputs library types and utilities for convenience
|
|
45
|
-
// Note: Since we use dynamic imports for the outputs library, these types may not be available
|
|
46
|
-
// at compile time if the package isn't installed. Users can import directly from
|
|
47
|
-
// @x12i/outputs-library if they need these types or utilities.
|
|
48
|
-
//
|
|
49
|
-
// Recommended: Import types and utilities directly from @x12i/outputs-library:
|
|
50
|
-
// import type { ClassificationOutput } from '@x12i/outputs-library/types';
|
|
51
|
-
// import { ResponseParser } from '@x12i/outputs-library/parsers';
|
|
52
|
-
// import type { ObjectTypesLibrary, FlexMdSupport } from '@x12i/outputs-library';
|
|
53
|
-
//
|
|
54
|
-
// The gateway integrates with the outputs library internally via dynamic imports,
|
|
55
|
-
// so these re-exports are optional and mainly for convenience.
|
|
56
|
-
//
|
|
57
|
-
// For outputs-library v3.3.1+ with flex-md support:
|
|
58
|
-
// - ObjectTypesLibrary class with flex-md methods (getFlexMdTemplate, getFlexMdFormatSpec, etc.)
|
|
59
|
-
// - FlexMdSupport type for object type definitions
|
|
60
|
-
// - All flex-md methods are available on the library instance returned by getObjectTypesLibrary()
|
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,13 +375,15 @@ 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 */
|
|
@@ -1009,6 +1038,11 @@ export interface EnhancedLLMResponse<TContent = unknown> extends Omit<AIResponse
|
|
|
1009
1038
|
* Ordered, authoritative attempts across retries and fallbacks (trace mode).
|
|
1010
1039
|
*/
|
|
1011
1040
|
attempts?: GatewayTraceAttempt[];
|
|
1041
|
+
/**
|
|
1042
|
+
* Final usage + billing for the invocation (trace mode). Mirrors successful-attempt
|
|
1043
|
+
* tokens and {@link costUsd} / {@link costStatus} after router passthrough + catalog pricing.
|
|
1044
|
+
*/
|
|
1045
|
+
usage?: GatewayTraceUsageSummary;
|
|
1012
1046
|
/**
|
|
1013
1047
|
* Merged gateway/router generation config actually used for the invocation (after
|
|
1014
1048
|
* {@link mergeConfig}: modelConfig / request.config / defaults / flex-md maxTokens).
|