@x12i/ai-gateway 9.3.0 β 9.3.5
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 +60 -13
- package/dist/activity-manager.d.ts +1 -0
- package/dist/activity-manager.js +123 -26
- package/dist/gateway.js +5 -1
- package/dist-cjs/activity-manager.cjs +123 -26
- package/dist-cjs/activity-manager.d.ts +1 -0
- package/dist-cjs/gateway.cjs +5 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -63,6 +63,7 @@ npm install @x12i/ai-gateway
|
|
|
63
63
|
**π Documentation**: After installation, documentation is available in:
|
|
64
64
|
- `node_modules/@x12i/ai-gateway/CONTENT_RESOLVER_UPSTREAM_GUIDE.md` - **Content resolver (nx-content)**: config, keys, local/git, upstream checklist
|
|
65
65
|
- `node_modules/@x12i/ai-gateway/docs/IDENTITY_OBJECT_CONTRACT.md` - **Identity contract** for Activix (`sessionId` + `instance`)
|
|
66
|
+
- `node_modules/@x12i/ai-gateway/docs/AI_GATEWAY_INVOKE_EXECUTION_METADATA.md` - **Invoke metadata**, cost/billing (G8), output contract (G6), Activix completion fields
|
|
66
67
|
- `node_modules/@x12i/ai-gateway/docs/LOGGER_INITIALIZATION.md` - **Required reading**: How to properly initialize logger
|
|
67
68
|
- `node_modules/@x12i/ai-gateway/TROUBLESHOOTING.md` - Troubleshooting guide
|
|
68
69
|
- `node_modules/@x12i/ai-gateway/TROUBLESHOOTING_TOOLBOX.md` - Diagnostic tools
|
|
@@ -309,7 +310,7 @@ The gateway reads **Mongo connection** settings from the environment, but **coll
|
|
|
309
310
|
`ActivityManager` drives **`@x12i/activix` v7** with a **two-phase** API:
|
|
310
311
|
|
|
311
312
|
1. **`startRecord`** β Inserts a new document with **`status: 'started'`**, **`startTime`**, **`runContext`** (same object as **`request.identity`**), root **`request`** / **`config`** snapshots, gateway metadata (e.g. **`activityType`**, **`aiRequestId`**), and the initial **`outer`** fragment (see below). Activix returns **`activityId`** (prefix **`act-`**, configured as the collection **`primaryKey`**); that id is used for all later updates β **not** `jobId`.
|
|
312
|
-
2. **`completeRecord`** or **`failRecord`** β Patches the **same** document by **`activityId`**.
|
|
313
|
+
2. **`completeRecord`** or **`failRecord`** β Patches the **same** document by **`activityId`**. On success, adds **`response`**, **`endTime`**, **`duration`**, root **`cost`** / **`costUsd`** / **`costStatus`**, sets **`outer.output`** to the completion payload, merges billing into **`outer.metadata`**, and when priced or unpriced with usage, sets Activix **`outer.cost`** (`usd`, `tokens`, `provider`, `model`, optional `details`). Failure adds error details (and may attach **`outer.output`** for certain failure modes such as response parsing).
|
|
313
314
|
|
|
314
315
|
**How a document is shaped (reading `ai-actions` in Mongo)**
|
|
315
316
|
|
|
@@ -317,7 +318,7 @@ The gateway reads **Mongo connection** settings from the environment, but **coll
|
|
|
317
318
|
- **Root-level copies** of common identity fields may appear beside **`runContext`** for convenient indexing; treat **`runContext`** as the full envelope when in doubt.
|
|
318
319
|
- **`request`**: Structured snapshot only β **`raw`** / **`parsed`** instructions, context, prompt; **`messages`**; **`workingMemory`** (template/user payload). There is **no** separate legacy **`input`** field on this object; use **`workingMemory`**.
|
|
319
320
|
- **`config`**: `model`, `provider`, `temperature`, `maxTokens`, **`rawConfig`** (exact router config).
|
|
320
|
-
- **`outer`**: Activix v7 **validated I/O** at the document root. At **start**, **`outer.input`** contains **`activityType`** and the same **`request`** snapshot as root **`request`** when a body exists (`{ activityType, request }`). At **success**, **`outer.output`** matches the **`response`** object written on completion. Root **`request`** / **`response`** support querying and older tooling; **`outer`** satisfies Activixβs envelope β so the same logical request snapshot can appear both at **`request`** and under **`outer.input.request`** by design. Large provider blobs (**`response.content.fullResponse`**) and size limits are described in [Activities outer duplication & payload controls](./docs/ACTIVITIES_OUTER_DUPLICATION.md).
|
|
321
|
+
- **`outer`**: Activix v7 **validated I/O** at the document root. At **start**, **`outer.input`** contains **`activityType`** and the same **`request`** snapshot as root **`request`** when a body exists (`{ activityType, request }`). At **success**, **`outer.output`** matches the **`response`** object written on completion; **`outer.metadata`** mirrors routing and billing from **`response.metadata`** (`modelUsed`, `provider`, `cost`, `costUsd`, `costStatus`, optional `costBreakdown`); **`outer.cost`** holds the canonical Activix cost object when usage or price is known (see [Cost reporting](#cost-reporting-invoke-response--activix-run-analysis-g8) below). Root **`request`** / **`response`** support querying and older tooling; **`outer`** satisfies Activixβs envelope β so the same logical request snapshot can appear both at **`request`** and under **`outer.input.request`** by design. Large provider blobs (**`response.content.fullResponse`**) and size limits are described in [Activities outer duplication & payload controls](./docs/ACTIVITIES_OUTER_DUPLICATION.md).
|
|
321
322
|
|
|
322
323
|
**Environment variable priority (Activix / Mongo β implemented in `@x12i/activix`, not in `activity-tracking-config.ts`):**
|
|
323
324
|
- **Mongo URI**: `MONGO_LOGS_URI` if set, otherwise **`MONGO_URI`**. If neither is set, Activix cannot use the database.
|
|
@@ -401,6 +402,24 @@ const gateway = new AIGateway({
|
|
|
401
402
|
});
|
|
402
403
|
```
|
|
403
404
|
|
|
405
|
+
#### Cost reporting (invoke response + Activix, Run Analysis G8)
|
|
406
|
+
|
|
407
|
+
Billing is resolved once per successful **`invoke()`** / **`invokeChat()`** via **`resolveCostCompletionWithAiTools`** (see [`docs/AI_GATEWAY_INVOKE_EXECUTION_METADATA.md`](./docs/AI_GATEWAY_INVOKE_EXECUTION_METADATA.md)):
|
|
408
|
+
|
|
409
|
+
| Layer | Fields |
|
|
410
|
+
|--------|--------|
|
|
411
|
+
| **Router** (`@x12i/ai-providers-router`) | Preferred source: **`metadata.costStatus`** (`priced` \| `unpriced`), **`metadata.costUsd`** / **`metadata.cost`** when priced |
|
|
412
|
+
| **Gateway response** | Same slice on **`response.metadata`**: **`costStatus`**, **`costUsd`**, **`cost`**, optional **`costBreakdown`** (when **`aiTools.calculateCost`** and catalog pricing apply and the router left cost unpriced) |
|
|
413
|
+
| **Activix activity (on `logSuccess`)** | Root **`cost`**, **`costUsd`**, **`costStatus`**; **`outer.metadata`** mirror; **`outer.cost`** (`usd`, `tokens` with `input`/`output`/`total`, `provider`, `model`, `details.costStatus`, optional `details.costBreakdown`) |
|
|
414
|
+
|
|
415
|
+
**`costStatus` semantics:**
|
|
416
|
+
|
|
417
|
+
- **`priced`** β **`costUsd`** / **`cost`** is a finite USD amount for this call (from the router or from **`@x12i/ai-tools`** catalog **`CostCalculator`** when the router did not price).
|
|
418
|
+
- **`unpriced`** β Token usage was recorded but no authoritative USD price was available (explicit router **`unpriced`** is never overridden by catalog).
|
|
419
|
+
- Omitted β No non-zero token usage (no billing signal).
|
|
420
|
+
|
|
421
|
+
Requires **`enableActivityTracking: true`** (default when Mongo/env is configured) for Activix persistence; invoke metadata is always set on the gateway response regardless.
|
|
422
|
+
|
|
404
423
|
**Tests before release:**
|
|
405
424
|
|
|
406
425
|
```bash
|
|
@@ -472,7 +491,7 @@ When the gateway constructs Activix internally, each collection uses **`primaryK
|
|
|
472
491
|
- **Config data**: Stored in **`config`** (model, provider, temperature, maxTokens, **`rawConfig`**)
|
|
473
492
|
- **Response data**: Stored in **`response`** on completion (content, metadata, optional **`fullResponse`** per diagnostics)
|
|
474
493
|
- **Activix I/O**: Root **`outer`** β **`outer.input`** at start, **`outer.output`** on success (and some failure paths)
|
|
475
|
-
- **Cost**:
|
|
494
|
+
- **Cost / billing**: On success, root **`cost`**, **`costUsd`**, **`costStatus`**, plus **`outer.metadata`** and **`outer.cost`** (same values as **`response.metadata`** from the invoke path β router passthrough or catalog pricing via **`@x12i/ai-tools`**)
|
|
476
495
|
|
|
477
496
|
**Best Practices for Type IDs:**
|
|
478
497
|
- **`jobTypeId`**: Use MD5 hash of your job type string (e.g., `MD5('data-processing-job')`) for consistent job-level aggregation
|
|
@@ -1119,7 +1138,7 @@ The gateway uses **`@x12i/activix` v7** (xronox-activitix) for full lifecycle lo
|
|
|
1119
1138
|
- Sends **`runContext`**, **`request`**, **`config`**, **`startTime`**, **`status: 'started'`**, plus Activix **`outer.input`** (wraps **`activityType`** and the same **`request`** snapshot when present β see section 2).
|
|
1120
1139
|
- Returns **`activityId`** (and record payload) for phase 2.
|
|
1121
1140
|
- **Phase 2 (complete / fail)**: Updates the SAME document by **`activityId`**
|
|
1122
|
-
- Success: **`response`**, **`cost`**, **`endTime`**, **`duration`**, **`status`**,
|
|
1141
|
+
- Success: **`response`**, root **`cost`** / **`costUsd`** / **`costStatus`**, **`endTime`**, **`duration`**, **`status`**, **`outer.output`** (completion payload), **`outer.metadata`** (routing + billing mirror), and **`outer.cost`** when usage or price is known (see [Cost reporting](#cost-reporting-invoke-response--activix-run-analysis-g8)).
|
|
1123
1142
|
- Failure: error payload and timing; optional **`response`** / **`outer.output`** only for specific failure kinds.
|
|
1124
1143
|
|
|
1125
1144
|
4. **Structured fields vs Activix `outer` (v2.6.0+):**
|
|
@@ -1264,8 +1283,22 @@ Example shape for a completed row in **`ai-actions`** (`activityType: 'gateway-i
|
|
|
1264
1283
|
// completeRecord: outer.output β same object as root `response` on success
|
|
1265
1284
|
outer: {
|
|
1266
1285
|
input: { activityType: 'gateway-invocation', request: { /* same snapshot as root request */ } },
|
|
1267
|
-
output: { /* success:
|
|
1268
|
-
metadata: {
|
|
1286
|
+
output: { /* success: gateway activity response (content, parsed, metadata, usage) */ },
|
|
1287
|
+
metadata: {
|
|
1288
|
+
modelUsed: 'openai/gpt-5-nano-2025-08-07',
|
|
1289
|
+
provider: 'openrouter',
|
|
1290
|
+
cost: 0.0000348,
|
|
1291
|
+
costUsd: 0.0000348,
|
|
1292
|
+
costStatus: 'priced'
|
|
1293
|
+
},
|
|
1294
|
+
cost: {
|
|
1295
|
+
usd: 0.0000348,
|
|
1296
|
+
unit: 'USD',
|
|
1297
|
+
tokens: { input: 16, output: 85, total: 101 },
|
|
1298
|
+
provider: 'openrouter',
|
|
1299
|
+
model: 'openai/gpt-5-nano-2025-08-07',
|
|
1300
|
+
details: { costStatus: 'priced' /* optional costBreakdown when aiTools.costIncludeBreakdown */ }
|
|
1301
|
+
}
|
|
1269
1302
|
},
|
|
1270
1303
|
// inner: optional step array for multi-step flows (see @x12i/activix docs)
|
|
1271
1304
|
|
|
@@ -1306,8 +1339,10 @@ Example shape for a completed row in **`ai-actions`** (`activityType: 'gateway-i
|
|
|
1306
1339
|
metadata: {...}
|
|
1307
1340
|
},
|
|
1308
1341
|
|
|
1309
|
-
//
|
|
1310
|
-
cost: 0.
|
|
1342
|
+
// Billing (from logSuccess β mirrors response.metadata from invoke)
|
|
1343
|
+
cost: 0.0000348,
|
|
1344
|
+
costUsd: 0.0000348,
|
|
1345
|
+
costStatus: 'priced',
|
|
1311
1346
|
|
|
1312
1347
|
// Metadata
|
|
1313
1348
|
createdAt: Date,
|
|
@@ -1319,7 +1354,7 @@ Example shape for a completed row in **`ai-actions`** (`activityType: 'gateway-i
|
|
|
1319
1354
|
- β
Each activity = separate Mongo document (**`_id`**) with stable **`activityId`** (`act-β¦`) for Activix APIs
|
|
1320
1355
|
- β
**`aiRequestId`** = per-request correlation (required on invoke)
|
|
1321
1356
|
- β
**`runContext.jobId`** / **`runContext.taskId`** = upstream identity (required on invoke since v9+)
|
|
1322
|
-
- β
Request/config sent at **start**; response/timing/cost at **complete**
|
|
1357
|
+
- β
Request/config sent at **start**; response/timing/billing (`cost`, `costUsd`, `costStatus`, `outer.cost`) at **complete**
|
|
1323
1358
|
- β
Updates target **`activityId`** from **`startRecord`**, not **`jobId`**
|
|
1324
1359
|
|
|
1325
1360
|
#### Retry Tracking (@x12i/activix v7)
|
|
@@ -1455,8 +1490,16 @@ const response = await gateway.invoke({
|
|
|
1455
1490
|
cacheTotalTokens?: number
|
|
1456
1491
|
},
|
|
1457
1492
|
model?: string, // Model ID used (e.g., 'gpt-4o', 'claude-sonnet-4')
|
|
1493
|
+
modelUsed?: string, // Resolved/served model id (when distinct from request model)
|
|
1458
1494
|
provider?: string, // Provider used (e.g., 'openai', 'anthropic')
|
|
1459
|
-
|
|
1495
|
+
costStatus?: 'priced' | 'unpriced', // Billing state (Run Analysis G8)
|
|
1496
|
+
costUsd?: number, // USD when costStatus === 'priced' (preferred field)
|
|
1497
|
+
cost?: number, // USD mirror of costUsd when priced
|
|
1498
|
+
costBreakdown?: { // Optional when aiTools catalog pricing runs (calculateCost + breakdown)
|
|
1499
|
+
promptCostUsd?: number;
|
|
1500
|
+
completionCostUsd?: number;
|
|
1501
|
+
// ...other breakdown keys from @x12i/ai-tools
|
|
1502
|
+
},
|
|
1460
1503
|
|
|
1461
1504
|
// ============================================
|
|
1462
1505
|
// Inference Output Parsing (if inferenceType provided)
|
|
@@ -1503,8 +1546,10 @@ const response = await gateway.invoke({
|
|
|
1503
1546
|
- `metadata.jobId` - Job ID for correlation
|
|
1504
1547
|
- `metadata.latencyMs` - Request duration in milliseconds
|
|
1505
1548
|
- `metadata.tokens` - Token breakdown (prompt, completion, total, cache tokens)
|
|
1506
|
-
- `metadata.
|
|
1507
|
-
- `metadata.
|
|
1549
|
+
- `metadata.costStatus` - `priced` | `unpriced` (see [Cost reporting](#cost-reporting-invoke-response--activix-run-analysis-g8))
|
|
1550
|
+
- `metadata.costUsd` / `metadata.cost` - USD when priced
|
|
1551
|
+
- `metadata.costBreakdown` - Optional catalog breakdown when `aiTools.calculateCost` applies
|
|
1552
|
+
- `metadata.model` / `metadata.modelUsed` - Model id used
|
|
1508
1553
|
- `metadata.provider` - Provider used
|
|
1509
1554
|
|
|
1510
1555
|
#### Example: Full Response
|
|
@@ -1554,8 +1599,10 @@ const response = await gateway.invoke({
|
|
|
1554
1599
|
completion: 50,
|
|
1555
1600
|
total: 150
|
|
1556
1601
|
},
|
|
1557
|
-
|
|
1602
|
+
modelUsed: 'gpt-5-mini',
|
|
1558
1603
|
provider: 'openai',
|
|
1604
|
+
costStatus: 'priced',
|
|
1605
|
+
costUsd: 0.002,
|
|
1559
1606
|
cost: 0.002,
|
|
1560
1607
|
|
|
1561
1608
|
// Inference output (parsed)
|
|
@@ -121,6 +121,7 @@ export declare class ActivityManager {
|
|
|
121
121
|
logSuccess(activity: ActivityMetadata | undefined, details: {
|
|
122
122
|
cost?: number;
|
|
123
123
|
costStatus?: 'priced' | 'unpriced';
|
|
124
|
+
costBreakdown?: Record<string, unknown>;
|
|
124
125
|
response: any;
|
|
125
126
|
endTime: number;
|
|
126
127
|
duration: number;
|
package/dist/activity-manager.js
CHANGED
|
@@ -133,34 +133,120 @@ function logUpstreamIdentityWarnings(logger, incomingIdentity, merged) {
|
|
|
133
133
|
}));
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
|
-
/**
|
|
137
|
-
function
|
|
136
|
+
/** Token counts for Activix `outer.cost.tokens` (maps gateway prompt/completion β input/output). */
|
|
137
|
+
function pickActivixUsageTokens(response) {
|
|
138
138
|
if (response == null || typeof response !== 'object')
|
|
139
|
-
return
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
139
|
+
return undefined;
|
|
140
|
+
const r = response;
|
|
141
|
+
const raw = (r.usage != null && typeof r.usage === 'object' ? r.usage : undefined) ??
|
|
142
|
+
(r.metadata != null && typeof r.metadata === 'object'
|
|
143
|
+
? r.metadata.tokens
|
|
144
|
+
: undefined);
|
|
145
|
+
if (raw == null || typeof raw !== 'object')
|
|
146
|
+
return undefined;
|
|
147
|
+
const t = raw;
|
|
148
|
+
const input = typeof t.prompt === 'number'
|
|
149
|
+
? t.prompt
|
|
150
|
+
: typeof t.input === 'number'
|
|
151
|
+
? t.input
|
|
152
|
+
: undefined;
|
|
153
|
+
const output = typeof t.completion === 'number'
|
|
154
|
+
? t.completion
|
|
155
|
+
: typeof t.output === 'number'
|
|
156
|
+
? t.output
|
|
157
|
+
: undefined;
|
|
158
|
+
const total = typeof t.total === 'number' ? t.total : undefined;
|
|
159
|
+
if (input === undefined && output === undefined && total === undefined)
|
|
160
|
+
return undefined;
|
|
161
|
+
return {
|
|
162
|
+
...(input !== undefined ? { input } : {}),
|
|
163
|
+
...(output !== undefined ? { output } : {}),
|
|
164
|
+
...(total !== undefined ? { total } : {})
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Activix v6+ `outer.cost` from gateway billing + routing metadata (Run Analysis G8).
|
|
169
|
+
*/
|
|
170
|
+
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;
|
|
178
|
+
const tokens = pickActivixUsageTokens(response);
|
|
179
|
+
const provider = typeof routingMeta.provider === 'string' ? routingMeta.provider : undefined;
|
|
180
|
+
const model = typeof routingMeta.modelUsed === 'string'
|
|
181
|
+
? routingMeta.modelUsed
|
|
182
|
+
: typeof routingMeta.model === 'string'
|
|
183
|
+
? routingMeta.model
|
|
184
|
+
: undefined;
|
|
185
|
+
const details = {};
|
|
186
|
+
if (billing.costStatus === 'priced' || billing.costStatus === 'unpriced') {
|
|
187
|
+
details.costStatus = billing.costStatus;
|
|
188
|
+
}
|
|
189
|
+
if (billing.costBreakdown != null && typeof billing.costBreakdown === 'object') {
|
|
190
|
+
details.costBreakdown = billing.costBreakdown;
|
|
191
|
+
}
|
|
192
|
+
const hasDetails = Object.keys(details).length > 0;
|
|
193
|
+
if (usd === undefined && !tokens && !provider && !model && !hasDetails) {
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
...(usd !== undefined ? { usd, unit: 'USD' } : {}),
|
|
198
|
+
...(tokens ? { tokens } : {}),
|
|
199
|
+
...(provider ? { provider } : {}),
|
|
200
|
+
...(model ? { model } : {}),
|
|
201
|
+
...(hasDetails ? { details } : {})
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/** Routing / generation facts for Activix `outer.metadata` on completion (includes billing mirror). */
|
|
205
|
+
function pickActivixCompletionRoutingMetadata(response, billing) {
|
|
144
206
|
const out = {};
|
|
145
|
-
if (typeof
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
207
|
+
if (response != null && typeof response === 'object') {
|
|
208
|
+
const meta = response.metadata;
|
|
209
|
+
if (meta != null && typeof meta === 'object') {
|
|
210
|
+
const m = meta;
|
|
211
|
+
if (typeof m.modelUsed === 'string')
|
|
212
|
+
out.modelUsed = m.modelUsed;
|
|
213
|
+
if (typeof m.model === 'string')
|
|
214
|
+
out.model = m.model;
|
|
215
|
+
if (typeof m.provider === 'string')
|
|
216
|
+
out.provider = m.provider;
|
|
217
|
+
if (typeof m.maxTokensRequested === 'number')
|
|
218
|
+
out.maxTokensRequested = m.maxTokensRequested;
|
|
219
|
+
if (typeof m.region === 'string')
|
|
220
|
+
out.region = m.region;
|
|
221
|
+
if (m.effectiveModelConfig != null && typeof m.effectiveModelConfig === 'object') {
|
|
222
|
+
out.effectiveModelConfig = m.effectiveModelConfig;
|
|
223
|
+
}
|
|
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
|
+
}
|
|
157
249
|
}
|
|
158
|
-
if (typeof m.cost === 'number' && Number.isFinite(m.cost))
|
|
159
|
-
out.cost = m.cost;
|
|
160
|
-
if (typeof m.costUsd === 'number' && Number.isFinite(m.costUsd))
|
|
161
|
-
out.costUsd = m.costUsd;
|
|
162
|
-
if (m.costStatus === 'priced' || m.costStatus === 'unpriced')
|
|
163
|
-
out.costStatus = m.costStatus;
|
|
164
250
|
return out;
|
|
165
251
|
}
|
|
166
252
|
function mergeGatewayActivityIdentity(request, aiRequestId, extras) {
|
|
@@ -848,13 +934,24 @@ export class ActivityManager {
|
|
|
848
934
|
});
|
|
849
935
|
return;
|
|
850
936
|
}
|
|
937
|
+
const billingSlice = {
|
|
938
|
+
cost: details.cost,
|
|
939
|
+
costStatus: details.costStatus,
|
|
940
|
+
costBreakdown: details.costBreakdown
|
|
941
|
+
};
|
|
942
|
+
const outerMetadata = pickActivixCompletionRoutingMetadata(details.response, billingSlice);
|
|
943
|
+
const outerCost = buildActivixOuterCost(outerMetadata, billingSlice, details.response);
|
|
851
944
|
await this.activix.completeRecord(activity.activityId, {
|
|
852
945
|
cost: details.cost,
|
|
946
|
+
...(typeof details.cost === 'number' && Number.isFinite(details.cost)
|
|
947
|
+
? { costUsd: details.cost }
|
|
948
|
+
: {}),
|
|
853
949
|
...(details.costStatus ? { costStatus: details.costStatus } : {}),
|
|
854
950
|
response: details.response,
|
|
855
951
|
outer: {
|
|
856
952
|
output: details.response,
|
|
857
|
-
metadata:
|
|
953
|
+
metadata: outerMetadata,
|
|
954
|
+
...(outerCost ? { cost: outerCost } : {})
|
|
858
955
|
},
|
|
859
956
|
endTime: details.endTime,
|
|
860
957
|
duration: details.duration
|
package/dist/gateway.js
CHANGED
|
@@ -142,7 +142,10 @@ export class AIGateway {
|
|
|
142
142
|
: { cost: costCompletionChat.cost })
|
|
143
143
|
}
|
|
144
144
|
: {}),
|
|
145
|
-
...(costCompletionChat.costStatus ? { costStatus: costCompletionChat.costStatus } : {})
|
|
145
|
+
...(costCompletionChat.costStatus ? { costStatus: costCompletionChat.costStatus } : {}),
|
|
146
|
+
...(costCompletionChat.costBreakdown
|
|
147
|
+
? { costBreakdown: costCompletionChat.costBreakdown }
|
|
148
|
+
: {})
|
|
146
149
|
}
|
|
147
150
|
};
|
|
148
151
|
// Track activity success if activity was started
|
|
@@ -587,6 +590,7 @@ export class AIGateway {
|
|
|
587
590
|
}
|
|
588
591
|
: {}),
|
|
589
592
|
...(costCompletion.costStatus ? { costStatus: costCompletion.costStatus } : {}),
|
|
593
|
+
...(costCompletion.costBreakdown ? { costBreakdown: costCompletion.costBreakdown } : {}),
|
|
590
594
|
...(traceEnabled
|
|
591
595
|
? {
|
|
592
596
|
requestIds: traceRequestIds,
|
|
@@ -133,34 +133,120 @@ function logUpstreamIdentityWarnings(logger, incomingIdentity, merged) {
|
|
|
133
133
|
}));
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
|
-
/**
|
|
137
|
-
function
|
|
136
|
+
/** Token counts for Activix `outer.cost.tokens` (maps gateway prompt/completion β input/output). */
|
|
137
|
+
function pickActivixUsageTokens(response) {
|
|
138
138
|
if (response == null || typeof response !== 'object')
|
|
139
|
-
return
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
139
|
+
return undefined;
|
|
140
|
+
const r = response;
|
|
141
|
+
const raw = (r.usage != null && typeof r.usage === 'object' ? r.usage : undefined) ??
|
|
142
|
+
(r.metadata != null && typeof r.metadata === 'object'
|
|
143
|
+
? r.metadata.tokens
|
|
144
|
+
: undefined);
|
|
145
|
+
if (raw == null || typeof raw !== 'object')
|
|
146
|
+
return undefined;
|
|
147
|
+
const t = raw;
|
|
148
|
+
const input = typeof t.prompt === 'number'
|
|
149
|
+
? t.prompt
|
|
150
|
+
: typeof t.input === 'number'
|
|
151
|
+
? t.input
|
|
152
|
+
: undefined;
|
|
153
|
+
const output = typeof t.completion === 'number'
|
|
154
|
+
? t.completion
|
|
155
|
+
: typeof t.output === 'number'
|
|
156
|
+
? t.output
|
|
157
|
+
: undefined;
|
|
158
|
+
const total = typeof t.total === 'number' ? t.total : undefined;
|
|
159
|
+
if (input === undefined && output === undefined && total === undefined)
|
|
160
|
+
return undefined;
|
|
161
|
+
return {
|
|
162
|
+
...(input !== undefined ? { input } : {}),
|
|
163
|
+
...(output !== undefined ? { output } : {}),
|
|
164
|
+
...(total !== undefined ? { total } : {})
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Activix v6+ `outer.cost` from gateway billing + routing metadata (Run Analysis G8).
|
|
169
|
+
*/
|
|
170
|
+
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;
|
|
178
|
+
const tokens = pickActivixUsageTokens(response);
|
|
179
|
+
const provider = typeof routingMeta.provider === 'string' ? routingMeta.provider : undefined;
|
|
180
|
+
const model = typeof routingMeta.modelUsed === 'string'
|
|
181
|
+
? routingMeta.modelUsed
|
|
182
|
+
: typeof routingMeta.model === 'string'
|
|
183
|
+
? routingMeta.model
|
|
184
|
+
: undefined;
|
|
185
|
+
const details = {};
|
|
186
|
+
if (billing.costStatus === 'priced' || billing.costStatus === 'unpriced') {
|
|
187
|
+
details.costStatus = billing.costStatus;
|
|
188
|
+
}
|
|
189
|
+
if (billing.costBreakdown != null && typeof billing.costBreakdown === 'object') {
|
|
190
|
+
details.costBreakdown = billing.costBreakdown;
|
|
191
|
+
}
|
|
192
|
+
const hasDetails = Object.keys(details).length > 0;
|
|
193
|
+
if (usd === undefined && !tokens && !provider && !model && !hasDetails) {
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
...(usd !== undefined ? { usd, unit: 'USD' } : {}),
|
|
198
|
+
...(tokens ? { tokens } : {}),
|
|
199
|
+
...(provider ? { provider } : {}),
|
|
200
|
+
...(model ? { model } : {}),
|
|
201
|
+
...(hasDetails ? { details } : {})
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/** Routing / generation facts for Activix `outer.metadata` on completion (includes billing mirror). */
|
|
205
|
+
function pickActivixCompletionRoutingMetadata(response, billing) {
|
|
144
206
|
const out = {};
|
|
145
|
-
if (typeof
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
207
|
+
if (response != null && typeof response === 'object') {
|
|
208
|
+
const meta = response.metadata;
|
|
209
|
+
if (meta != null && typeof meta === 'object') {
|
|
210
|
+
const m = meta;
|
|
211
|
+
if (typeof m.modelUsed === 'string')
|
|
212
|
+
out.modelUsed = m.modelUsed;
|
|
213
|
+
if (typeof m.model === 'string')
|
|
214
|
+
out.model = m.model;
|
|
215
|
+
if (typeof m.provider === 'string')
|
|
216
|
+
out.provider = m.provider;
|
|
217
|
+
if (typeof m.maxTokensRequested === 'number')
|
|
218
|
+
out.maxTokensRequested = m.maxTokensRequested;
|
|
219
|
+
if (typeof m.region === 'string')
|
|
220
|
+
out.region = m.region;
|
|
221
|
+
if (m.effectiveModelConfig != null && typeof m.effectiveModelConfig === 'object') {
|
|
222
|
+
out.effectiveModelConfig = m.effectiveModelConfig;
|
|
223
|
+
}
|
|
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
|
+
}
|
|
157
249
|
}
|
|
158
|
-
if (typeof m.cost === 'number' && Number.isFinite(m.cost))
|
|
159
|
-
out.cost = m.cost;
|
|
160
|
-
if (typeof m.costUsd === 'number' && Number.isFinite(m.costUsd))
|
|
161
|
-
out.costUsd = m.costUsd;
|
|
162
|
-
if (m.costStatus === 'priced' || m.costStatus === 'unpriced')
|
|
163
|
-
out.costStatus = m.costStatus;
|
|
164
250
|
return out;
|
|
165
251
|
}
|
|
166
252
|
function mergeGatewayActivityIdentity(request, aiRequestId, extras) {
|
|
@@ -848,13 +934,24 @@ export class ActivityManager {
|
|
|
848
934
|
});
|
|
849
935
|
return;
|
|
850
936
|
}
|
|
937
|
+
const billingSlice = {
|
|
938
|
+
cost: details.cost,
|
|
939
|
+
costStatus: details.costStatus,
|
|
940
|
+
costBreakdown: details.costBreakdown
|
|
941
|
+
};
|
|
942
|
+
const outerMetadata = pickActivixCompletionRoutingMetadata(details.response, billingSlice);
|
|
943
|
+
const outerCost = buildActivixOuterCost(outerMetadata, billingSlice, details.response);
|
|
851
944
|
await this.activix.completeRecord(activity.activityId, {
|
|
852
945
|
cost: details.cost,
|
|
946
|
+
...(typeof details.cost === 'number' && Number.isFinite(details.cost)
|
|
947
|
+
? { costUsd: details.cost }
|
|
948
|
+
: {}),
|
|
853
949
|
...(details.costStatus ? { costStatus: details.costStatus } : {}),
|
|
854
950
|
response: details.response,
|
|
855
951
|
outer: {
|
|
856
952
|
output: details.response,
|
|
857
|
-
metadata:
|
|
953
|
+
metadata: outerMetadata,
|
|
954
|
+
...(outerCost ? { cost: outerCost } : {})
|
|
858
955
|
},
|
|
859
956
|
endTime: details.endTime,
|
|
860
957
|
duration: details.duration
|
|
@@ -121,6 +121,7 @@ export declare class ActivityManager {
|
|
|
121
121
|
logSuccess(activity: ActivityMetadata | undefined, details: {
|
|
122
122
|
cost?: number;
|
|
123
123
|
costStatus?: 'priced' | 'unpriced';
|
|
124
|
+
costBreakdown?: Record<string, unknown>;
|
|
124
125
|
response: any;
|
|
125
126
|
endTime: number;
|
|
126
127
|
duration: number;
|
package/dist-cjs/gateway.cjs
CHANGED
|
@@ -142,7 +142,10 @@ export class AIGateway {
|
|
|
142
142
|
: { cost: costCompletionChat.cost })
|
|
143
143
|
}
|
|
144
144
|
: {}),
|
|
145
|
-
...(costCompletionChat.costStatus ? { costStatus: costCompletionChat.costStatus } : {})
|
|
145
|
+
...(costCompletionChat.costStatus ? { costStatus: costCompletionChat.costStatus } : {}),
|
|
146
|
+
...(costCompletionChat.costBreakdown
|
|
147
|
+
? { costBreakdown: costCompletionChat.costBreakdown }
|
|
148
|
+
: {})
|
|
146
149
|
}
|
|
147
150
|
};
|
|
148
151
|
// Track activity success if activity was started
|
|
@@ -587,6 +590,7 @@ export class AIGateway {
|
|
|
587
590
|
}
|
|
588
591
|
: {}),
|
|
589
592
|
...(costCompletion.costStatus ? { costStatus: costCompletion.costStatus } : {}),
|
|
593
|
+
...(costCompletion.costBreakdown ? { costBreakdown: costCompletion.costBreakdown } : {}),
|
|
590
594
|
...(traceEnabled
|
|
591
595
|
? {
|
|
592
596
|
requestIds: traceRequestIds,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@x12i/ai-gateway",
|
|
3
|
-
"version": "9.3.
|
|
3
|
+
"version": "9.3.5",
|
|
4
4
|
"description": "AI Gateway - Unified interface for LLM provider routing and management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"license": "mit",
|
|
66
66
|
"dependencies": {
|
|
67
67
|
"@aws-sdk/s3-request-presigner": "^3.953.0",
|
|
68
|
-
"@x12i/activix": "^7.1.
|
|
68
|
+
"@x12i/activix": "^7.1.2",
|
|
69
69
|
"@x12i/ai-providers-router": "^4.8.0",
|
|
70
70
|
"@x12i/ai-tools": "^1.0.3",
|
|
71
71
|
"@x12i/catalox": "^4.2.0",
|