@x12i/ai-gateway 9.0.9 → 9.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +897 -998
- package/dist/activity-manager.js +46 -6
- package/dist/config/activity-tracking-config.d.ts +2 -1
- package/dist/config/activity-tracking-config.js +3 -2
- package/dist/gateway-memory.d.ts +1 -2
- package/dist/gateway-memory.js +1 -15
- package/dist/gateway-meta.js +3 -0
- package/dist/gateway-utils.d.ts +24 -2
- package/dist/gateway-utils.js +122 -18
- package/dist/gateway-validation.d.ts +3 -3
- package/dist/gateway-validation.js +10 -1
- package/dist/gateway.d.ts +2 -2
- package/dist/gateway.js +30 -24
- package/dist/index.d.ts +2 -2
- package/dist/instruction-optimizer.js +3 -0
- package/dist/runtime-objects.d.ts +2 -13
- package/dist/troubleshooting-helper.d.ts +0 -3
- package/dist/troubleshooting-helper.js +99 -20
- package/dist/types.d.ts +49 -91
- package/dist-cjs/activity-manager.cjs +45 -5
- package/dist-cjs/config/activity-tracking-config.cjs +3 -2
- package/dist-cjs/config/activity-tracking-config.d.ts +2 -1
- package/dist-cjs/gateway-memory.cjs +1 -15
- package/dist-cjs/gateway-memory.d.ts +1 -2
- package/dist-cjs/gateway-meta.cjs +3 -0
- package/dist-cjs/gateway-utils.cjs +126 -18
- package/dist-cjs/gateway-utils.d.ts +24 -2
- package/dist-cjs/gateway-validation.cjs +10 -1
- package/dist-cjs/gateway-validation.d.ts +3 -3
- package/dist-cjs/gateway.cjs +29 -23
- package/dist-cjs/gateway.d.ts +2 -2
- package/dist-cjs/index.d.ts +2 -2
- package/dist-cjs/instruction-optimizer.cjs +3 -0
- package/dist-cjs/runtime-objects.d.ts +2 -13
- package/dist-cjs/troubleshooting-helper.cjs +99 -20
- package/dist-cjs/troubleshooting-helper.d.ts +0 -3
- package/dist-cjs/types.d.ts +49 -91
- package/package.json +2 -2
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 } from '@x12i/activix';
|
|
7
|
+
import { Activix, activixActivityIo, activixOuterTier, 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) {
|
|
@@ -161,6 +161,17 @@ function mergeGatewayActivityIdentity(request, aiRequestId, extras) {
|
|
|
161
161
|
merged.aiRequestId = aiRequestId;
|
|
162
162
|
merged.jobId = upstreamJobId;
|
|
163
163
|
merged.taskId = upstreamTaskId;
|
|
164
|
+
// gateway.invoke (AIInvokeRequest): request root is canonical for Activix runContext.
|
|
165
|
+
if ('actionType' in request && 'actionRef' in request) {
|
|
166
|
+
const inv = request;
|
|
167
|
+
if (inv.actionType) {
|
|
168
|
+
merged.actionType = inv.actionType;
|
|
169
|
+
}
|
|
170
|
+
const ref = typeof inv.actionRef === 'string' ? inv.actionRef.trim() : '';
|
|
171
|
+
if (ref) {
|
|
172
|
+
merged.actionRef = ref;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
164
175
|
return merged;
|
|
165
176
|
}
|
|
166
177
|
/**
|
|
@@ -259,12 +270,37 @@ export class ActivityManager {
|
|
|
259
270
|
}
|
|
260
271
|
}
|
|
261
272
|
});
|
|
262
|
-
this.initPromise = this.activix
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
273
|
+
this.initPromise = this.activix
|
|
274
|
+
.init()
|
|
275
|
+
.then(() => {
|
|
276
|
+
const ax = this.activix;
|
|
277
|
+
if (!ax) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const backend = ax.storageBackend;
|
|
281
|
+
const mongoDb = backend === 'database' ? resolveActivixLogsDatabaseName() : undefined;
|
|
282
|
+
const mongoUriConfigured = Boolean(resolveActivixMongoUriFromEnv());
|
|
283
|
+
this.logger.info('Activity tracking persistence backend ready', {
|
|
284
|
+
storageBackend: backend,
|
|
285
|
+
mongoDatabase: mongoDb,
|
|
286
|
+
mongoUriConfigured,
|
|
287
|
+
mainCollection: collectionName,
|
|
288
|
+
badRequestsCollection: badRequestsCollectionName,
|
|
289
|
+
skillExecutionsCollection: this.skillExecutionsCollectionName,
|
|
290
|
+
...(backend === 'local'
|
|
291
|
+
? {
|
|
292
|
+
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.'
|
|
293
|
+
}
|
|
294
|
+
: {
|
|
295
|
+
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).'
|
|
296
|
+
})
|
|
297
|
+
});
|
|
298
|
+
})
|
|
299
|
+
.catch((error) => {
|
|
300
|
+
// Init threw — disable tracker so requests are not blocked.
|
|
301
|
+
this.logger.warn('Activity tracking enabled but Activix init failed. Activity records will not be persisted.', {
|
|
266
302
|
error: error instanceof Error ? error.message : String(error),
|
|
267
|
-
hint: 'Set MONGO_URI and
|
|
303
|
+
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.'
|
|
268
304
|
});
|
|
269
305
|
this.activix = undefined;
|
|
270
306
|
});
|
|
@@ -343,6 +379,8 @@ export class ActivityManager {
|
|
|
343
379
|
startTime,
|
|
344
380
|
status: 'started',
|
|
345
381
|
activityType: 'gateway-invocation',
|
|
382
|
+
...(identity.actionType !== undefined && { actionType: identity.actionType }),
|
|
383
|
+
...(identity.actionRef !== undefined && identity.actionRef !== '' && { actionRef: identity.actionRef }),
|
|
346
384
|
// Activix v5+: correlation BSON field is `runContext` (same object as `request.identity`)
|
|
347
385
|
runContext: identity
|
|
348
386
|
// Removed root-level fields per v2.3.2:
|
|
@@ -557,6 +595,8 @@ export class ActivityManager {
|
|
|
557
595
|
taskTypeId: request.taskTypeId,
|
|
558
596
|
startTime,
|
|
559
597
|
status: 'started',
|
|
598
|
+
...(identity.actionType !== undefined && { actionType: identity.actionType }),
|
|
599
|
+
...(identity.actionRef !== undefined && identity.actionRef !== '' && { actionRef: identity.actionRef }),
|
|
560
600
|
runContext: identity,
|
|
561
601
|
...(instructionMetadata.key && { instructionKey: instructionMetadata.key }),
|
|
562
602
|
...(instructionMetadata.version && { instructionVersion: instructionMetadata.version }),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Centralized activity tracking configuration.
|
|
3
|
-
*
|
|
3
|
+
* Package-level Mongo collection names are fixed literals here (no env override).
|
|
4
|
+
* Main gateway rows: `ai-actions`; bad requests: `bad-requests` (see constants below).
|
|
4
5
|
*/
|
|
5
6
|
export interface ActivityTrackingConfig {
|
|
6
7
|
mongoUri: string;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Centralized activity tracking configuration.
|
|
3
|
-
*
|
|
3
|
+
* Package-level Mongo collection names are fixed literals here (no env override).
|
|
4
|
+
* Main gateway rows: `ai-actions`; bad requests: `bad-requests` (see constants below).
|
|
4
5
|
*/
|
|
5
|
-
const ACTIVITY_COLLECTION_NAME = 'ai-
|
|
6
|
+
const ACTIVITY_COLLECTION_NAME = 'ai-actions';
|
|
6
7
|
const BAD_REQUESTS_COLLECTION_NAME = 'bad-requests';
|
|
7
8
|
export function resolveActivityTrackingConfig() {
|
|
8
9
|
// Collection names are intentionally hardcoded at package level.
|
package/dist/gateway-memory.d.ts
CHANGED
|
@@ -10,7 +10,6 @@ type Request = ChatRequest | AIRequest;
|
|
|
10
10
|
* Merges existing workingMemory (from request or memory component) with request metadata
|
|
11
11
|
*
|
|
12
12
|
* Implements tiered token resolution:
|
|
13
|
-
* - Tier 1 (highest): templateTokens (handled in resolveTemplateParams, merged into shortTermMemory)
|
|
14
13
|
* - Tier 2: workingMemory (this method) - checks existing workingMemory first
|
|
15
14
|
* - Tier 3: derived from request fields or other memories (fallback)
|
|
16
15
|
*/
|
|
@@ -27,7 +26,7 @@ export declare function buildWorkingMemory(request: Request, existingWorkingMemo
|
|
|
27
26
|
}): unknown;
|
|
28
27
|
/**
|
|
29
28
|
* Resolves template parameters with smart fallback logic
|
|
30
|
-
* Priority:
|
|
29
|
+
* Priority: request.workingMemory -> memoryManager resolution -> buildWorkingMemory merge
|
|
31
30
|
*/
|
|
32
31
|
export declare function resolveTemplateParams(request: Request, config: GatewayConfig, logger: Logxer): Promise<{
|
|
33
32
|
workingMemory: unknown;
|
package/dist/gateway-memory.js
CHANGED
|
@@ -14,7 +14,6 @@ function isAIRequest(request) {
|
|
|
14
14
|
* Merges existing workingMemory (from request or memory component) with request metadata
|
|
15
15
|
*
|
|
16
16
|
* Implements tiered token resolution:
|
|
17
|
-
* - Tier 1 (highest): templateTokens (handled in resolveTemplateParams, merged into shortTermMemory)
|
|
18
17
|
* - Tier 2: workingMemory (this method) - checks existing workingMemory first
|
|
19
18
|
* - Tier 3: derived from request fields or other memories (fallback)
|
|
20
19
|
*/
|
|
@@ -34,7 +33,6 @@ export function buildWorkingMemory(request, existingWorkingMemory, otherMemories
|
|
|
34
33
|
}
|
|
35
34
|
/**
|
|
36
35
|
* Token Resolution with Tiered Fallback
|
|
37
|
-
* Tier 1: templateTokens (handled in resolveTemplateParams, merged into shortTermMemory)
|
|
38
36
|
* Tier 2: workingMemory (check existing workingMemory first)
|
|
39
37
|
* Tier 3: derive from request fields or other memories
|
|
40
38
|
*/
|
|
@@ -138,7 +136,7 @@ export function buildWorkingMemory(request, existingWorkingMemory, otherMemories
|
|
|
138
136
|
}
|
|
139
137
|
/**
|
|
140
138
|
* Resolves template parameters with smart fallback logic
|
|
141
|
-
* Priority:
|
|
139
|
+
* Priority: request.workingMemory -> memoryManager resolution -> buildWorkingMemory merge
|
|
142
140
|
*/
|
|
143
141
|
export async function resolveTemplateParams(request, config, logger) {
|
|
144
142
|
// Tier 1: Request args (highest priority)
|
|
@@ -189,19 +187,7 @@ export async function resolveTemplateParams(request, config, logger) {
|
|
|
189
187
|
// Build proper workingMemory structure (merge with request fields if needed)
|
|
190
188
|
// This implements tiered token resolution: tier 2 (workingMemory) and tier 3 (derive from request fields)
|
|
191
189
|
const finalWorkingMemory = buildWorkingMemory(request, workingMemory);
|
|
192
|
-
// Merge templateTokens (tier 1 - highest priority) into shortTermMemory AFTER memory resolution
|
|
193
|
-
// This ensures templateTokens override everything (workingMemory and other memories)
|
|
194
|
-
// Rendrix priority: shortTermMemory > workingMemory > experienceMemory > knowledgeMemory
|
|
195
|
-
if (request.templateTokens && Object.keys(request.templateTokens).length > 0) {
|
|
196
|
-
logger?.debug('Merged templateTokens into shortTermMemory (tier 1 - highest priority)', {
|
|
197
|
-
jobId: request.identity.jobId,
|
|
198
|
-
tokenKeys: Object.keys(request.templateTokens)
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
// Note: taskConfig removed - Rendrix 3.0.0+ no longer accepts it
|
|
202
|
-
// taskConfig is deprecated and no longer used
|
|
203
190
|
return {
|
|
204
191
|
workingMemory: finalWorkingMemory
|
|
205
|
-
// taskConfig removed - Rendrix 3.0.0+ no longer uses it
|
|
206
192
|
};
|
|
207
193
|
}
|
package/dist/gateway-meta.js
CHANGED
|
@@ -27,8 +27,11 @@ export async function testInstructions(instructions, testInput, expectedSchema,
|
|
|
27
27
|
const testRequest = {
|
|
28
28
|
aiRequestId,
|
|
29
29
|
agentId,
|
|
30
|
+
actionType: 'skill',
|
|
31
|
+
actionRef: 'gateway-meta/test-instructions',
|
|
30
32
|
instructions,
|
|
31
33
|
identity: runtimeIdentity,
|
|
34
|
+
prompt: '{{input}}',
|
|
32
35
|
workingMemory: { input: testInput },
|
|
33
36
|
config: {
|
|
34
37
|
model,
|
package/dist/gateway-utils.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Gateway Utilities Module
|
|
3
3
|
* Handles utility functions
|
|
4
4
|
*/
|
|
5
|
-
import type { ChatRequest, GatewayConfig } from './types.js';
|
|
5
|
+
import type { ChatRequest, GatewayConfig, ModelConfig } from './types.js';
|
|
6
6
|
import type { Logxer } from '@x12i/logxer';
|
|
7
7
|
/**
|
|
8
8
|
* Generates MD5 hash of a string
|
|
@@ -21,7 +21,7 @@ export declare function mergeConfig(request: ChatRequest & {
|
|
|
21
21
|
}, config: GatewayConfig, logger: Logxer): Promise<ChatRequest['config']>;
|
|
22
22
|
/**
|
|
23
23
|
* Maps provider/router usage objects to gateway token counts (`metadata.tokens`, Activix, trace attempts).
|
|
24
|
-
* Handles promptTokens/inputTokens, OpenAI-style snake_case, and missing total (sum prompt+completion).
|
|
24
|
+
* Handles promptTokens/inputTokens, OpenAI-style snake_case, Responses-style input/output tokens, and missing total (sum prompt+completion).
|
|
25
25
|
*/
|
|
26
26
|
export declare function normalizeRouterUsageTokens(usage: unknown): {
|
|
27
27
|
prompt: number;
|
|
@@ -30,6 +30,7 @@ export declare function normalizeRouterUsageTokens(usage: unknown): {
|
|
|
30
30
|
} | undefined;
|
|
31
31
|
/**
|
|
32
32
|
* Reads token usage from every stable location the router may populate (see docs/PROVIDERS_ROUTER_DIAGNOSTICS_TRACE_REQUIREMENTS.md).
|
|
33
|
+
* Prefers the raw/provider body (`rawResponse` / `raw`) when it carries non-zero usage before re-reading the outer envelope.
|
|
33
34
|
*/
|
|
34
35
|
export declare function extractTokenUsageFromRouterResponse(routerResponse: unknown): {
|
|
35
36
|
prompt: number;
|
|
@@ -42,3 +43,24 @@ export declare function extractTokenUsageFromRouterResponse(routerResponse: unkn
|
|
|
42
43
|
* Does not compute cost from tokens — adapters must populate normalized fields or raw usage.cost-style keys.
|
|
43
44
|
*/
|
|
44
45
|
export declare function extractCostUsdFromRouterResponse(routerResponse: unknown): number | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Stable routing facts for gateway response metadata (router metadata + merged config fallbacks).
|
|
48
|
+
* Matches trace-mode resolution; intended for every successful invoke(), not only diagnostics.trace.
|
|
49
|
+
*/
|
|
50
|
+
export declare function pickInvokeRoutingMetadataSlice(routerResponse: unknown, mergedConfig: unknown): Partial<{
|
|
51
|
+
provider: string;
|
|
52
|
+
modelUsed: string;
|
|
53
|
+
maxTokensRequested: number;
|
|
54
|
+
region: string;
|
|
55
|
+
}>;
|
|
56
|
+
/**
|
|
57
|
+
* Allowlisted generation profile from merged config for client introspection (no secrets, no arbitrary extras).
|
|
58
|
+
*/
|
|
59
|
+
export declare function pickEffectiveModelConfigForMetadata(mergedConfig: unknown): Partial<Pick<ModelConfig, 'model' | 'modelId' | 'provider' | 'temperature' | 'maxTokens' | 'topP'>> | undefined;
|
|
60
|
+
/** Default JSON string length cap for Activix `content.fullResponse` when diagnostics allow storing it. */
|
|
61
|
+
export declare const DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS = 512000;
|
|
62
|
+
/**
|
|
63
|
+
* Size-cap a provider/router payload before storing on an activity record.
|
|
64
|
+
* Non-serializable values become a small marker object instead of throwing.
|
|
65
|
+
*/
|
|
66
|
+
export declare function capActivityFullResponsePayload(payload: unknown, maxChars?: number): unknown;
|
package/dist/gateway-utils.js
CHANGED
|
@@ -183,53 +183,90 @@ function firstFiniteNumber(...vals) {
|
|
|
183
183
|
for (const v of vals) {
|
|
184
184
|
if (typeof v === 'number' && Number.isFinite(v))
|
|
185
185
|
return v;
|
|
186
|
+
if (typeof v === 'string' && v.trim() !== '') {
|
|
187
|
+
const n = Number(v);
|
|
188
|
+
if (Number.isFinite(n))
|
|
189
|
+
return n;
|
|
190
|
+
}
|
|
186
191
|
}
|
|
187
192
|
return undefined;
|
|
188
193
|
}
|
|
194
|
+
function isNonZeroTokenCount(n) {
|
|
195
|
+
return !!(n.prompt || n.completion || n.total);
|
|
196
|
+
}
|
|
189
197
|
/**
|
|
190
198
|
* Maps provider/router usage objects to gateway token counts (`metadata.tokens`, Activix, trace attempts).
|
|
191
|
-
* Handles promptTokens/inputTokens, OpenAI-style snake_case, and missing total (sum prompt+completion).
|
|
199
|
+
* Handles promptTokens/inputTokens, OpenAI-style snake_case, Responses-style input/output tokens, and missing total (sum prompt+completion).
|
|
192
200
|
*/
|
|
193
201
|
export function normalizeRouterUsageTokens(usage) {
|
|
194
202
|
if (usage == null || typeof usage !== 'object')
|
|
195
203
|
return undefined;
|
|
196
204
|
const u = usage;
|
|
197
|
-
const prompt = firstFiniteNumber(u.promptTokens, u.inputTokens, u.prompt, u.prompt_tokens) ?? 0;
|
|
198
|
-
const completion = firstFiniteNumber(u.completionTokens, u.outputTokens, u.completion, u.completion_tokens) ?? 0;
|
|
199
|
-
let total = firstFiniteNumber(u.totalTokens, u.total_tokens) ?? 0;
|
|
205
|
+
const prompt = firstFiniteNumber(u.promptTokens, u.inputTokens, u.input_tokens, u.prompt, u.prompt_tokens) ?? 0;
|
|
206
|
+
const completion = firstFiniteNumber(u.completionTokens, u.outputTokens, u.output_tokens, u.completion, u.completion_tokens) ?? 0;
|
|
207
|
+
let total = firstFiniteNumber(u.totalTokens, u.total_tokens, u.total) ?? 0;
|
|
200
208
|
if (!total && (prompt || completion))
|
|
201
209
|
total = prompt + completion;
|
|
202
210
|
return { prompt, completion, total };
|
|
203
211
|
}
|
|
204
212
|
/**
|
|
205
|
-
*
|
|
213
|
+
* Collect usage from one router/provider envelope (single object).
|
|
214
|
+
* When followRaw is true, also reads `(rawResponse ?? raw).usage` on that envelope.
|
|
206
215
|
*/
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
const r = routerResponse;
|
|
212
|
-
const meta = r.metadata != null && typeof r.metadata === 'object'
|
|
213
|
-
? r.metadata
|
|
216
|
+
function collectUsageBucketsFromRoot(root, followRaw) {
|
|
217
|
+
const meta = root.metadata != null && typeof root.metadata === 'object'
|
|
218
|
+
? root.metadata
|
|
214
219
|
: undefined;
|
|
215
|
-
const buckets = [
|
|
220
|
+
const buckets = [root.usage];
|
|
216
221
|
if (meta) {
|
|
217
222
|
buckets.push(meta.usage);
|
|
223
|
+
buckets.push(meta.tokens);
|
|
218
224
|
const nested = meta['ai-activities-response'];
|
|
219
225
|
if (nested != null && typeof nested === 'object') {
|
|
220
226
|
buckets.push(nested.usage);
|
|
221
227
|
}
|
|
222
228
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
229
|
+
if (followRaw) {
|
|
230
|
+
const raw = root.rawResponse ?? root.raw;
|
|
231
|
+
if (raw != null && typeof raw === 'object') {
|
|
232
|
+
buckets.push(raw.usage);
|
|
233
|
+
}
|
|
226
234
|
}
|
|
235
|
+
return buckets;
|
|
236
|
+
}
|
|
237
|
+
function firstNonZeroUsageFromBuckets(buckets) {
|
|
227
238
|
for (const b of buckets) {
|
|
228
239
|
const n = normalizeRouterUsageTokens(b);
|
|
229
|
-
if (n && (n
|
|
240
|
+
if (n && isNonZeroTokenCount(n))
|
|
230
241
|
return n;
|
|
231
242
|
}
|
|
232
|
-
return
|
|
243
|
+
return undefined;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Reads token usage from every stable location the router may populate (see docs/PROVIDERS_ROUTER_DIAGNOSTICS_TRACE_REQUIREMENTS.md).
|
|
247
|
+
* Prefers the raw/provider body (`rawResponse` / `raw`) when it carries non-zero usage before re-reading the outer envelope.
|
|
248
|
+
*/
|
|
249
|
+
export function extractTokenUsageFromRouterResponse(routerResponse) {
|
|
250
|
+
const zeros = { prompt: 0, completion: 0, total: 0 };
|
|
251
|
+
if (routerResponse == null || typeof routerResponse !== 'object') {
|
|
252
|
+
return zeros;
|
|
253
|
+
}
|
|
254
|
+
const r = routerResponse;
|
|
255
|
+
const raw = r.rawResponse ?? r.raw;
|
|
256
|
+
const inner = raw != null && typeof raw === 'object' ? raw : undefined;
|
|
257
|
+
const roots = inner != null && inner !== r
|
|
258
|
+
? [
|
|
259
|
+
{ root: inner, followRaw: false },
|
|
260
|
+
{ root: r, followRaw: true }
|
|
261
|
+
]
|
|
262
|
+
: [{ root: r, followRaw: true }];
|
|
263
|
+
for (const { root, followRaw } of roots) {
|
|
264
|
+
const buckets = collectUsageBucketsFromRoot(root, followRaw);
|
|
265
|
+
const found = firstNonZeroUsageFromBuckets(buckets);
|
|
266
|
+
if (found)
|
|
267
|
+
return found;
|
|
268
|
+
}
|
|
269
|
+
return zeros;
|
|
233
270
|
}
|
|
234
271
|
/**
|
|
235
272
|
* Best-effort USD cost from router/sync AIResponse shape: metadata.costUsd (preferred),
|
|
@@ -278,3 +315,70 @@ export function extractCostUsdFromRouterResponse(routerResponse) {
|
|
|
278
315
|
}
|
|
279
316
|
return undefined;
|
|
280
317
|
}
|
|
318
|
+
/**
|
|
319
|
+
* Stable routing facts for gateway response metadata (router metadata + merged config fallbacks).
|
|
320
|
+
* Matches trace-mode resolution; intended for every successful invoke(), not only diagnostics.trace.
|
|
321
|
+
*/
|
|
322
|
+
export function pickInvokeRoutingMetadataSlice(routerResponse, mergedConfig) {
|
|
323
|
+
const rr = routerResponse != null && typeof routerResponse === 'object' ? routerResponse : {};
|
|
324
|
+
const meta = rr.metadata != null && typeof rr.metadata === 'object' ? rr.metadata : {};
|
|
325
|
+
const cfg = mergedConfig != null && typeof mergedConfig === 'object' ? mergedConfig : {};
|
|
326
|
+
const provider = meta.provider || rr.provider || cfg.provider;
|
|
327
|
+
const modelUsed = meta.modelUsed || meta.model || rr.model || cfg.model;
|
|
328
|
+
const maxTokensRequested = typeof meta.maxTokensRequested === 'number'
|
|
329
|
+
? meta.maxTokensRequested
|
|
330
|
+
: typeof cfg.maxTokens === 'number'
|
|
331
|
+
? cfg.maxTokens
|
|
332
|
+
: undefined;
|
|
333
|
+
const region = typeof meta.region === 'string' ? meta.region : undefined;
|
|
334
|
+
const out = {};
|
|
335
|
+
if (provider !== undefined && provider !== null)
|
|
336
|
+
out.provider = provider;
|
|
337
|
+
if (modelUsed !== undefined && modelUsed !== null)
|
|
338
|
+
out.modelUsed = modelUsed;
|
|
339
|
+
if (maxTokensRequested !== undefined)
|
|
340
|
+
out.maxTokensRequested = maxTokensRequested;
|
|
341
|
+
if (region !== undefined)
|
|
342
|
+
out.region = region;
|
|
343
|
+
return out;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Allowlisted generation profile from merged config for client introspection (no secrets, no arbitrary extras).
|
|
347
|
+
*/
|
|
348
|
+
export function pickEffectiveModelConfigForMetadata(mergedConfig) {
|
|
349
|
+
if (mergedConfig == null || typeof mergedConfig !== 'object')
|
|
350
|
+
return undefined;
|
|
351
|
+
const c = mergedConfig;
|
|
352
|
+
const keys = ['model', 'modelId', 'provider', 'temperature', 'maxTokens', 'topP'];
|
|
353
|
+
const out = {};
|
|
354
|
+
for (const k of keys) {
|
|
355
|
+
const v = c[k];
|
|
356
|
+
if (v !== undefined)
|
|
357
|
+
out[k] = v;
|
|
358
|
+
}
|
|
359
|
+
return Object.keys(out).length ? out : undefined;
|
|
360
|
+
}
|
|
361
|
+
/** Default JSON string length cap for Activix `content.fullResponse` when diagnostics allow storing it. */
|
|
362
|
+
export const DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS = 512_000;
|
|
363
|
+
/**
|
|
364
|
+
* Size-cap a provider/router payload before storing on an activity record.
|
|
365
|
+
* Non-serializable values become a small marker object instead of throwing.
|
|
366
|
+
*/
|
|
367
|
+
export function capActivityFullResponsePayload(payload, maxChars = DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS) {
|
|
368
|
+
if (payload == null)
|
|
369
|
+
return payload;
|
|
370
|
+
let serialized;
|
|
371
|
+
try {
|
|
372
|
+
serialized = typeof payload === 'string' ? payload : JSON.stringify(payload);
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
return { _truncated: true, _reason: 'not_serializable' };
|
|
376
|
+
}
|
|
377
|
+
if (serialized.length <= maxChars)
|
|
378
|
+
return payload;
|
|
379
|
+
return {
|
|
380
|
+
_truncated: true,
|
|
381
|
+
_originalCharLength: serialized.length,
|
|
382
|
+
_preview: serialized.slice(0, maxChars)
|
|
383
|
+
};
|
|
384
|
+
}
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* Gateway Validation Module
|
|
3
3
|
* Basic validation for clean proxy implementation
|
|
4
4
|
*/
|
|
5
|
-
import type { ChatRequest,
|
|
5
|
+
import type { ChatRequest, AIInvokeRequest } from './types.js';
|
|
6
6
|
/**
|
|
7
7
|
* Validates ChatRequest has required fields
|
|
8
8
|
*/
|
|
9
9
|
export declare function validateChatRequest(request: ChatRequest): void;
|
|
10
10
|
/**
|
|
11
|
-
* Validates
|
|
11
|
+
* Validates AIInvokeRequest has required fields
|
|
12
12
|
*/
|
|
13
|
-
export declare function validateAIRequest(request:
|
|
13
|
+
export declare function validateAIRequest(request: AIInvokeRequest): void;
|
|
@@ -32,8 +32,9 @@ export function validateChatRequest(request) {
|
|
|
32
32
|
throw err;
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
+
const GATEWAY_ACTION_TYPES = ['skill', 'preSkill', 'postSkill'];
|
|
35
36
|
/**
|
|
36
|
-
* Validates
|
|
37
|
+
* Validates AIInvokeRequest has required fields
|
|
37
38
|
*/
|
|
38
39
|
export function validateAIRequest(request) {
|
|
39
40
|
if (!request.aiRequestId) {
|
|
@@ -43,6 +44,14 @@ export function validateAIRequest(request) {
|
|
|
43
44
|
throw new Error('agentId is required for AI requests');
|
|
44
45
|
}
|
|
45
46
|
validateMandatoryRuntimeIdentity(request);
|
|
47
|
+
if (!request.actionType ||
|
|
48
|
+
!GATEWAY_ACTION_TYPES.includes(request.actionType)) {
|
|
49
|
+
throw new Error(`actionType is required and must be one of: ${GATEWAY_ACTION_TYPES.join(', ')}`);
|
|
50
|
+
}
|
|
51
|
+
const ref = typeof request.actionRef === 'string' ? request.actionRef.trim() : '';
|
|
52
|
+
if (!ref) {
|
|
53
|
+
throw new Error('actionRef is required and must be a non-empty string');
|
|
54
|
+
}
|
|
46
55
|
// Reject input field - it has been removed
|
|
47
56
|
if ('input' in request && request.input !== undefined) {
|
|
48
57
|
const err = new Error(`The 'input' field has been removed. Use workingMemory.input instead for template rendering. Prompt templates should contain {{input}} which will be resolved from workingMemory.input.`);
|
package/dist/gateway.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Simplified AI Gateway - Clean proxy implementation
|
|
5
5
|
*/
|
|
6
6
|
import { LLMProviderRouter } from '@x12i/ai-providers-router';
|
|
7
|
-
import type { GatewayConfig, ChatRequest,
|
|
7
|
+
import type { GatewayConfig, ChatRequest, AIInvokeRequest, EnhancedLLMResponse } from './types.js';
|
|
8
8
|
import type { Logxer } from '@x12i/logxer';
|
|
9
9
|
import { ActivityManager } from './activity-manager.js';
|
|
10
10
|
/**
|
|
@@ -25,7 +25,7 @@ export declare class AIGateway {
|
|
|
25
25
|
/**
|
|
26
26
|
* Invoke AI request (with structured output support)
|
|
27
27
|
*/
|
|
28
|
-
invoke<TContent = unknown>(request:
|
|
28
|
+
invoke<TContent = unknown>(request: AIInvokeRequest): Promise<EnhancedLLMResponse<TContent>>;
|
|
29
29
|
/**
|
|
30
30
|
* Build simple messages from request (instructions and prompt as literal template text; no registry).
|
|
31
31
|
*/
|
package/dist/gateway.js
CHANGED
|
@@ -8,7 +8,7 @@ import { ensureGatewayRequestIdentity } from './activity-manager.js';
|
|
|
8
8
|
import { initializeGatewayComponents } from './gateway-config.js';
|
|
9
9
|
import { buildMessages } from './message-builder.js';
|
|
10
10
|
import { extractJsonFromFlexMd } from './flex-md-loader.js';
|
|
11
|
-
import { extractCostUsdFromRouterResponse, extractTokenUsageFromRouterResponse, mergeConfig } from './gateway-utils.js';
|
|
11
|
+
import { capActivityFullResponsePayload, DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, extractCostUsdFromRouterResponse, extractTokenUsageFromRouterResponse, mergeConfig, pickEffectiveModelConfigForMetadata, pickInvokeRoutingMetadataSlice } from './gateway-utils.js';
|
|
12
12
|
import { autoRegisterProviders } from './gateway-provider-auto-register.js';
|
|
13
13
|
import { setGatewayLastJobId, setGatewayRuntimeClients } from './runtime-objects.js';
|
|
14
14
|
import { gatewayLogDebug, withActivityIdentity } from './gateway-log-meta.js';
|
|
@@ -513,9 +513,19 @@ export class AIGateway {
|
|
|
513
513
|
}
|
|
514
514
|
contentType = 'structured';
|
|
515
515
|
parsingMethod = 'flex-md';
|
|
516
|
-
|
|
516
|
+
let tokens = extractTokenUsageFromRouterResponse(routerResponse);
|
|
517
|
+
if (!(tokens.prompt || tokens.completion || tokens.total)) {
|
|
518
|
+
const alt = routerResponse?.rawResponse ?? routerResponse?.raw;
|
|
519
|
+
if (alt != null && typeof alt === 'object' && alt !== routerResponse) {
|
|
520
|
+
const second = extractTokenUsageFromRouterResponse(alt);
|
|
521
|
+
if (second.prompt || second.completion || second.total)
|
|
522
|
+
tokens = second;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
517
525
|
const resolvedCostUsd = extractCostUsdFromRouterResponse(routerResponse);
|
|
518
526
|
const routerMetaForCost = routerResponse?.metadata || {};
|
|
527
|
+
const routingMetadataSlice = pickInvokeRoutingMetadataSlice(routerResponse, mergedConfig);
|
|
528
|
+
const effectiveModelConfig = pickEffectiveModelConfigForMetadata(mergedConfig);
|
|
519
529
|
const enhancedResponse = {
|
|
520
530
|
content: content,
|
|
521
531
|
parsedContent: parsedContent,
|
|
@@ -528,6 +538,8 @@ export class AIGateway {
|
|
|
528
538
|
agentType: 'ai',
|
|
529
539
|
contentType,
|
|
530
540
|
parsingMethod,
|
|
541
|
+
...routingMetadataSlice,
|
|
542
|
+
...(effectiveModelConfig !== undefined ? { effectiveModelConfig } : {}),
|
|
531
543
|
...(typeof resolvedCostUsd === 'number'
|
|
532
544
|
? {
|
|
533
545
|
costUsd: resolvedCostUsd,
|
|
@@ -537,38 +549,32 @@ export class AIGateway {
|
|
|
537
549
|
}
|
|
538
550
|
: {}),
|
|
539
551
|
...(traceEnabled
|
|
540
|
-
?
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
? meta.maxTokensRequested
|
|
547
|
-
: typeof mergedConfig?.maxTokens === 'number'
|
|
548
|
-
? mergedConfig.maxTokens
|
|
549
|
-
: undefined;
|
|
550
|
-
return {
|
|
551
|
-
provider,
|
|
552
|
-
region,
|
|
553
|
-
modelUsed,
|
|
554
|
-
maxTokensRequested,
|
|
555
|
-
requestIds: traceRequestIds,
|
|
556
|
-
retryCount: traceRetryCount,
|
|
557
|
-
fallbackCount: traceFallbackCount,
|
|
558
|
-
attempts: traceAttempts
|
|
559
|
-
};
|
|
560
|
-
})()
|
|
552
|
+
? {
|
|
553
|
+
requestIds: traceRequestIds,
|
|
554
|
+
retryCount: traceRetryCount,
|
|
555
|
+
fallbackCount: traceFallbackCount,
|
|
556
|
+
attempts: traceAttempts
|
|
557
|
+
}
|
|
561
558
|
: {})
|
|
562
559
|
}
|
|
563
560
|
};
|
|
564
561
|
// Track activity success if activity was started
|
|
565
562
|
if (activity) {
|
|
566
563
|
try {
|
|
564
|
+
const diag = request.diagnostics;
|
|
565
|
+
const includeFullProviderBlob = diag?.includeFullProviderResponseInActivity !== false;
|
|
566
|
+
const maxFullChars = typeof diag?.activityFullResponseMaxChars === 'number' && diag.activityFullResponseMaxChars > 0
|
|
567
|
+
? diag.activityFullResponseMaxChars
|
|
568
|
+
: DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS;
|
|
569
|
+
const rawFull = routerResponse.rawResponse || routerResponse;
|
|
570
|
+
const fullResponseForActivity = includeFullProviderBlob
|
|
571
|
+
? capActivityFullResponsePayload(rawFull, maxFullChars)
|
|
572
|
+
: undefined;
|
|
567
573
|
// Create activity response with proper structure for ActivityTracker
|
|
568
574
|
const activityResponse = {
|
|
569
575
|
content: {
|
|
570
576
|
rawContent: content, // Store the actual response content as rawContent
|
|
571
|
-
|
|
577
|
+
...(fullResponseForActivity !== undefined ? { fullResponse: fullResponseForActivity } : {})
|
|
572
578
|
},
|
|
573
579
|
parsed: parsedContent, // Include parsed content in activity record
|
|
574
580
|
metadata: enhancedResponse.metadata,
|
package/dist/index.d.ts
CHANGED
|
@@ -16,11 +16,11 @@ 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, AIRequest, EnhancedLLMResponse, InstructionMetadata, ValidationRule,
|
|
19
|
+
export type { GatewayConfig, ProviderModelRef, ModelConfig, RetryConfig, ChatRequest, AIInvokeRequest, AIRequest, GatewayActionType, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions } from './types.js';
|
|
20
20
|
export { mergeTemplateRenderOptions } from './template-render-merge.js';
|
|
21
21
|
export type { UsageTier } from './types.js';
|
|
22
22
|
export { Activix } from '@x12i/activix';
|
|
23
|
-
export type { ActivixRunContext, FindByRunContextCriteria } from '@x12i/activix';
|
|
23
|
+
export type { ActivixRunContext, FindByRunContextCriteria, GetJobActivitiesInput, GetJobActivitiesResult } from '@x12i/activix';
|
|
24
24
|
export { ActivityManager, ensureGatewayRequestIdentity } from './activity-manager.js';
|
|
25
25
|
export type { ActivityIdentity } from './types.js';
|
|
26
26
|
export { activityIdentityToLogMeta, withActivityIdentity, gatewayLogDebug } from './gateway-log-meta.js';
|
|
@@ -139,8 +139,11 @@ export async function optimizeInstructions(gateway, originalInstructions, option
|
|
|
139
139
|
const optimizationRequest = {
|
|
140
140
|
aiRequestId,
|
|
141
141
|
agentId,
|
|
142
|
+
actionType: 'skill',
|
|
143
|
+
actionRef: 'internal/instruction-optimizer',
|
|
142
144
|
instructions: INSTRUCTION_OPTIMIZER_INSTRUCTIONS + additionalContext,
|
|
143
145
|
identity,
|
|
146
|
+
prompt: '{{input}}',
|
|
144
147
|
workingMemory: { input: originalInstructions },
|
|
145
148
|
config: {
|
|
146
149
|
model,
|
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
import type { Logxer } from '@x12i/logxer';
|
|
2
|
-
import type { Activix } from '@x12i/activix';
|
|
3
|
-
export type ActivixQueryableClient
|
|
4
|
-
getJobActivities(input: {
|
|
5
|
-
jobId: string;
|
|
6
|
-
graphId?: string;
|
|
7
|
-
nodeId?: string;
|
|
8
|
-
limit?: number;
|
|
9
|
-
}): Promise<{
|
|
10
|
-
jobId: string;
|
|
11
|
-
graphRun?: unknown;
|
|
12
|
-
activities: unknown[];
|
|
13
|
-
}>;
|
|
14
|
-
};
|
|
2
|
+
import type { Activix, ActivixQueryableClient } from '@x12i/activix';
|
|
3
|
+
export type { ActivixQueryableClient } from '@x12i/activix';
|
|
15
4
|
export type LogxerQueryableClient = {
|
|
16
5
|
getJobLogs(input: {
|
|
17
6
|
jobId: string;
|