@x12i/ai-providers-router 4.7.0 → 4.7.6

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 CHANGED
@@ -308,6 +308,21 @@ Router uses its own request/response types:
308
308
  * `AIStreamEvent` (streaming output) - includes reasoning streaming events
309
309
  * `AIBatchResponse` (batch output)
310
310
 
311
+ ### Authoritative trace diagnostics (stable contract)
312
+
313
+ For downstream orchestration, `AIResponse` includes stable, provider-agnostic diagnostics:
314
+
315
+ - `response.usage?: { promptTokens; completionTokens; totalTokens }`
316
+ - `response.metadata` (keys when known):
317
+ - `metadata.provider`: final provider used for the successful call (or last attempt)
318
+ - `metadata.modelUsed`: the actual model that served the response
319
+ - `metadata.maxTokensRequested`: final effective generation cap applied (if determinable)
320
+ - `metadata.costUsd`: normalized USD cost (if computable)
321
+ - `metadata.requestIds`: `{ routerRequestId, providerRequestId?, openrouterRequestId? }`
322
+ - `metadata.timing`: `{ startedAt, endedAt, durationMs }` (provider-call timing)
323
+ - `metadata.latencyMs`: alias for `metadata.timing.durationMs`
324
+ - `metadata.attempts[]`: ordered attempts across retries + fallbacks (authoritative execution trace)
325
+
311
326
  ```ts
312
327
  import type { AIRouterRequest, AIResponse } from '@x12i/ai-providers-router';
313
328
 
@@ -90,28 +90,182 @@ export class AIRouter {
90
90
  */
91
91
  async runSync(input) {
92
92
  const requestId = input.requestId ?? newId();
93
- const providerName = this.resolveProviderName(input);
94
- const provider = this.providers.get(providerName);
95
- const adapter = this.adapters.get(providerName);
96
- // Check capabilities
97
- if (!provider.capabilities.modes.sync) {
98
- throw new Error(`Provider '${providerName}' does not support sync mode`);
93
+ const primaryProviderName = this.resolveProviderName(input);
94
+ const fallbackProvidersRaw = input.request?.config?.fallbackProviders;
95
+ const fallbackProviders = Array.isArray(fallbackProvidersRaw)
96
+ ? fallbackProvidersRaw.filter((p) => typeof p === 'string' && p.trim().length > 0).map((p) => p.trim())
97
+ : [];
98
+ // Candidates are ordered: primary, then fallbacks.
99
+ const providerCandidates = [primaryProviderName, ...fallbackProviders.filter((p) => p !== primaryProviderName)];
100
+ const maxRetries = Math.max(0, Math.min(10, Number(input.exec?.retries ?? 0) || 0));
101
+ const attempts = [];
102
+ const normalizeUsage = (usage) => {
103
+ if (!usage || typeof usage !== 'object')
104
+ return undefined;
105
+ const promptTokens = (typeof usage.promptTokens === 'number' ? usage.promptTokens : undefined) ??
106
+ (typeof usage.inputTokens === 'number' ? usage.inputTokens : undefined) ??
107
+ (typeof usage.prompt_tokens === 'number' ? usage.prompt_tokens : undefined) ??
108
+ (typeof usage.input_tokens === 'number' ? usage.input_tokens : undefined);
109
+ const completionTokens = (typeof usage.completionTokens === 'number' ? usage.completionTokens : undefined) ??
110
+ (typeof usage.outputTokens === 'number' ? usage.outputTokens : undefined) ??
111
+ (typeof usage.completion_tokens === 'number' ? usage.completion_tokens : undefined) ??
112
+ (typeof usage.output_tokens === 'number' ? usage.output_tokens : undefined);
113
+ const totalTokens = (typeof usage.totalTokens === 'number' ? usage.totalTokens : undefined) ??
114
+ (typeof usage.total_tokens === 'number' ? usage.total_tokens : undefined);
115
+ if (promptTokens === undefined && completionTokens === undefined && totalTokens === undefined)
116
+ return undefined;
117
+ const p = promptTokens ?? 0;
118
+ const c = completionTokens ?? 0;
119
+ return { promptTokens: p, completionTokens: c, totalTokens: totalTokens ?? p + c };
120
+ };
121
+ const extractMaxTokensRequested = (spec) => {
122
+ const args = spec?.args;
123
+ if (!args || typeof args !== 'object')
124
+ return undefined;
125
+ const v = (typeof args.max_output_tokens === 'number' ? args.max_output_tokens : undefined) ??
126
+ (typeof args.max_tokens === 'number' ? args.max_tokens : undefined) ??
127
+ (typeof args.maxTokens === 'number' ? args.maxTokens : undefined);
128
+ return typeof v === 'number' && Number.isFinite(v) ? v : undefined;
129
+ };
130
+ const summarizeError = (err) => {
131
+ const e = err instanceof Error ? err : new Error(String(err));
132
+ const name = typeof e.name === 'string' ? e.name : 'Error';
133
+ const message = typeof e.message === 'string' ? e.message : String(err);
134
+ // Size cap to avoid shipping huge provider payloads.
135
+ const capped = message.length > 500 ? message.slice(0, 500) : message;
136
+ return { name, message: capped };
137
+ };
138
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
139
+ const isRetryableError = (err) => {
140
+ const msg = err instanceof Error ? err.message : String(err);
141
+ const name = err instanceof Error ? err.name : '';
142
+ const combined = `${name} ${msg}`.toLowerCase();
143
+ return (combined.includes('timeout') ||
144
+ combined.includes('timed out') ||
145
+ combined.includes('rate limit') ||
146
+ combined.includes('429') ||
147
+ combined.includes('econnreset') ||
148
+ combined.includes('etimedout') ||
149
+ combined.includes('503') ||
150
+ combined.includes('502') ||
151
+ combined.includes('504') ||
152
+ combined.includes('temporarily unavailable'));
153
+ };
154
+ const backoffMs = (retryIndex) => {
155
+ const base = 250 * Math.pow(2, Math.max(0, retryIndex));
156
+ const jitter = Math.floor(Math.random() * 100);
157
+ return Math.min(5000, base + jitter);
158
+ };
159
+ let lastError = undefined;
160
+ for (let fallbackIndex = 0; fallbackIndex < providerCandidates.length; fallbackIndex++) {
161
+ const providerName = providerCandidates[fallbackIndex];
162
+ const provider = this.providers.get(providerName);
163
+ const adapter = this.adapters.get(providerName);
164
+ // Check capabilities
165
+ if (!provider.capabilities.modes.sync) {
166
+ throw new Error(`Provider '${providerName}' does not support sync mode`);
167
+ }
168
+ // Build call spec (once per provider candidate; retries reuse the same spec)
169
+ const spec = await adapter.buildCallSpec({
170
+ requestId,
171
+ mode: 'sync',
172
+ request: input.request,
173
+ exec: input.exec,
174
+ });
175
+ const maxTokensRequested = extractMaxTokensRequested(spec);
176
+ const requestedModel = spec?.args?.model ?? input.request?.config?.model ?? input.request?.model;
177
+ for (let retryIndex = 0; retryIndex <= maxRetries; retryIndex++) {
178
+ const startedAt = Date.now();
179
+ try {
180
+ const execResult = await provider.execute(spec);
181
+ const endedAt = Date.now();
182
+ const parsed = adapter.parseResponse({
183
+ requestId,
184
+ request: { ...input.request, _callSpec: spec }, // Include call spec for adapter access
185
+ execResult,
186
+ });
187
+ const usage = normalizeUsage(parsed.usage);
188
+ const modelUsed = parsed.metadata?.modelUsed ||
189
+ parsed.metadata?.model ||
190
+ parsed.rawResponse?.model ||
191
+ (typeof requestedModel === 'string' ? requestedModel : undefined);
192
+ const providerRequestId = parsed.metadata?.providerRequestId ||
193
+ parsed.metadata?.id ||
194
+ parsed.rawResponse?.id;
195
+ const openrouterRequestId = providerName === 'openrouter' ? providerRequestId : undefined;
196
+ const timing = { startedAt, endedAt, durationMs: endedAt - startedAt };
197
+ const attempt = {
198
+ ok: true,
199
+ routing: {
200
+ provider: providerName,
201
+ retryIndex,
202
+ fallbackIndex,
203
+ requestIds: {
204
+ routerRequestId: requestId,
205
+ providerRequestId: providerRequestId ? String(providerRequestId) : undefined,
206
+ openrouterRequestId: openrouterRequestId ? String(openrouterRequestId) : undefined,
207
+ },
208
+ },
209
+ timing,
210
+ usage,
211
+ maxTokensRequested,
212
+ modelUsed,
213
+ costUsd: typeof parsed.metadata?.costUsd === 'number' ? parsed.metadata.costUsd : undefined,
214
+ };
215
+ attempts.push(attempt);
216
+ const mergedMetadata = {
217
+ ...(parsed.metadata || {}),
218
+ provider: providerName,
219
+ modelUsed,
220
+ maxTokensRequested,
221
+ costUsd: attempt.costUsd,
222
+ requestIds: {
223
+ ...parsed.metadata?.requestIds,
224
+ routerRequestId: requestId,
225
+ providerRequestId: providerRequestId ? String(providerRequestId) : undefined,
226
+ openrouterRequestId: openrouterRequestId ? String(openrouterRequestId) : undefined,
227
+ },
228
+ timing,
229
+ latencyMs: timing.durationMs,
230
+ attempts,
231
+ };
232
+ return {
233
+ ...parsed,
234
+ requestId,
235
+ provider: parsed.provider || providerName,
236
+ usage,
237
+ metadata: mergedMetadata,
238
+ };
239
+ }
240
+ catch (err) {
241
+ const endedAt = Date.now();
242
+ lastError = err;
243
+ attempts.push({
244
+ ok: false,
245
+ routing: {
246
+ provider: providerName,
247
+ retryIndex,
248
+ fallbackIndex,
249
+ requestIds: { routerRequestId: requestId },
250
+ },
251
+ timing: { startedAt, endedAt, durationMs: endedAt - startedAt },
252
+ maxTokensRequested,
253
+ modelUsed: typeof requestedModel === 'string' ? requestedModel : undefined,
254
+ error: summarizeError(err),
255
+ });
256
+ const shouldRetry = retryIndex < maxRetries && isRetryableError(err);
257
+ if (shouldRetry) {
258
+ await sleep(backoffMs(retryIndex));
259
+ continue;
260
+ }
261
+ break;
262
+ }
263
+ }
99
264
  }
100
- // Build call spec
101
- const spec = await adapter.buildCallSpec({
102
- requestId,
103
- mode: 'sync',
104
- request: input.request,
105
- exec: input.exec,
106
- });
107
- // Execute
108
- const execResult = await provider.execute(spec);
109
- // Parse response
110
- return adapter.parseResponse({
111
- requestId,
112
- request: { ...input.request, _callSpec: spec }, // Include call spec for adapter access
113
- execResult,
114
- });
265
+ // Fallback chain exhausted
266
+ const error = lastError instanceof Error ? lastError : new Error(String(lastError ?? 'Unknown error'));
267
+ error.attempts = attempts;
268
+ throw error;
115
269
  }
116
270
  /**
117
271
  * Execute a streaming request
@@ -3,6 +3,61 @@
3
3
  * These are the public API of the router
4
4
  */
5
5
  export type RouterMode = 'sync' | 'stream' | 'batch';
6
+ export type NormalizedUsage = {
7
+ promptTokens: number;
8
+ completionTokens: number;
9
+ totalTokens: number;
10
+ };
11
+ export type AttemptTiming = {
12
+ startedAt: number;
13
+ endedAt: number;
14
+ durationMs: number;
15
+ };
16
+ export type AttemptErrorSummary = {
17
+ name: string;
18
+ message: string;
19
+ };
20
+ export type AttemptRouting = {
21
+ provider: string;
22
+ region?: string;
23
+ requestIds?: Record<string, string | undefined>;
24
+ retryIndex: number;
25
+ fallbackIndex: number;
26
+ };
27
+ export type AttemptTrace = {
28
+ timing?: AttemptTiming;
29
+ routing: AttemptRouting;
30
+ usage?: NormalizedUsage;
31
+ maxTokensRequested?: number;
32
+ modelUsed?: string;
33
+ costUsd?: number;
34
+ ok: boolean;
35
+ error?: AttemptErrorSummary;
36
+ };
37
+ export type DiagnosticsMetadata = {
38
+ /** Final provider used for the successful call (or last attempt). */
39
+ provider?: string;
40
+ region?: string;
41
+ /** The actual model that served the response (may differ after normalization/routing). */
42
+ modelUsed?: string;
43
+ costUsd?: number;
44
+ maxTokensRequested?: number;
45
+ /** Stable correlation ids across router/provider proxies. */
46
+ requestIds?: Record<string, string | undefined>;
47
+ /** Provider-call timing for the successful call (or last attempt). */
48
+ timing?: AttemptTiming;
49
+ /** Provider-call latency in ms (alias for timing.durationMs). */
50
+ latencyMs?: number;
51
+ /** Ordered attempt trace across retries + fallbacks. */
52
+ attempts?: AttemptTrace[];
53
+ /** Optional normalization details (size-capped by caller). */
54
+ normalization?: {
55
+ normalizedConfig?: Record<string, unknown>;
56
+ warnings?: string[];
57
+ };
58
+ /** Allow additional vendor/provider specific keys. */
59
+ [key: string]: unknown;
60
+ };
6
61
  /**
7
62
  * Router configuration
8
63
  */
@@ -56,7 +111,7 @@ export interface AIResponse {
56
111
  provider: string;
57
112
  rawResponse: unknown;
58
113
  outputText?: string;
59
- usage?: any;
114
+ usage?: NormalizedUsage;
60
115
  reasoning: {
61
116
  requested: {
62
117
  effort: string;
@@ -85,7 +140,7 @@ export interface AIResponse {
85
140
  };
86
141
  warnings?: any[];
87
142
  };
88
- metadata?: any;
143
+ metadata?: DiagnosticsMetadata;
89
144
  }
90
145
  /**
91
146
  * Router stream events
@@ -224,43 +224,11 @@ export class LLMProviderRouter {
224
224
  duration: invokeDuration,
225
225
  usage: result.usage,
226
226
  });
227
- // #region agent log
228
- const logDataC = { location: 'RouterWrapper.ts:224', message: 'Before response interceptors', data: { hasMetadata: !!result.metadata, metadataKeys: result.metadata ? Object.keys(result.metadata) : [], hasAiActivitiesResponse: !!result.metadata?.['ai-activities-response'], hasAiActivitiesRequest: !!result.metadata?.['ai-activities-request'], interceptorsCount: this.responseInterceptors.length }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'run1', hypothesisId: 'C' };
229
- console.log('[DEBUG]', logDataC);
230
- fetch('http://127.0.0.1:7243/ingest/0d4596d8-b8fa-4ca1-a4f4-2e6f23baa429', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(logDataC) }).catch(() => { });
231
- // #endregion
232
227
  // Apply response interceptors
233
228
  let processedResponse = result;
234
- for (let i = 0; i < this.responseInterceptors.length; i++) {
235
- const interceptor = this.responseInterceptors[i];
236
- const beforeInterceptor = {
237
- hasMetadata: !!processedResponse.metadata,
238
- metadataKeys: processedResponse.metadata ? Object.keys(processedResponse.metadata) : [],
239
- hasAiActivitiesResponse: !!processedResponse.metadata?.['ai-activities-response'],
240
- hasAiActivitiesRequest: !!processedResponse.metadata?.['ai-activities-request'],
241
- };
229
+ for (const interceptor of this.responseInterceptors) {
242
230
  processedResponse = await interceptor(processedResponse, result.provider);
243
- const afterInterceptor = {
244
- hasMetadata: !!processedResponse.metadata,
245
- metadataKeys: processedResponse.metadata ? Object.keys(processedResponse.metadata) : [],
246
- hasAiActivitiesResponse: !!processedResponse.metadata?.['ai-activities-response'],
247
- hasAiActivitiesRequest: !!processedResponse.metadata?.['ai-activities-request'],
248
- };
249
- // #region agent log
250
- const interceptorLog = { location: `RouterWrapper.ts:231:interceptor_${i}`, message: `Interceptor ${i} execution`, data: { before: beforeInterceptor, after: afterInterceptor, changed: JSON.stringify(beforeInterceptor) !== JSON.stringify(afterInterceptor) }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'run1', hypothesisId: `INTERCEPTOR_${i}` };
251
- console.log(`[DEBUG RouterWrapper] Interceptor ${i} - Before:`, JSON.stringify(beforeInterceptor, null, 2));
252
- console.log(`[DEBUG RouterWrapper] Interceptor ${i} - After:`, JSON.stringify(afterInterceptor, null, 2));
253
- if (JSON.stringify(beforeInterceptor) !== JSON.stringify(afterInterceptor)) {
254
- console.warn(`[WARNING RouterWrapper] Interceptor ${i} modified ai-activities metadata!`);
255
- }
256
- fetch('http://127.0.0.1:7243/ingest/0d4596d8-b8fa-4ca1-a4f4-2e6f23baa429', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(interceptorLog) }).catch(() => { });
257
- // #endregion
258
231
  }
259
- // #region agent log
260
- const logDataD = { location: 'RouterWrapper.ts:230', message: 'After response interceptors', data: { hasMetadata: !!processedResponse.metadata, metadataKeys: processedResponse.metadata ? Object.keys(processedResponse.metadata) : [], hasAiActivitiesResponse: !!processedResponse.metadata?.['ai-activities-response'], hasAiActivitiesRequest: !!processedResponse.metadata?.['ai-activities-request'], responseChanged: processedResponse !== result }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'run1', hypothesisId: 'D' };
261
- console.log('[DEBUG]', logDataD);
262
- fetch('http://127.0.0.1:7243/ingest/0d4596d8-b8fa-4ca1-a4f4-2e6f23baa429', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(logDataD) }).catch(() => { });
263
- // #endregion
264
232
  // Record usage
265
233
  if (this.config.usageTracker) {
266
234
  const duration = Date.now() - startTime;
@@ -269,192 +237,14 @@ export class LLMProviderRouter {
269
237
  timestamp: Date.now(),
270
238
  duration,
271
239
  tokens: {
272
- input: result.usage?.inputTokens || 0,
273
- output: result.usage?.outputTokens || 0,
240
+ input: result.usage?.promptTokens || 0,
241
+ output: result.usage?.completionTokens || 0,
274
242
  total: result.usage?.totalTokens || 0,
275
243
  },
276
- cost: result.usage?.cost,
244
+ cost: result.metadata?.costUsd,
277
245
  success: true,
278
246
  });
279
247
  }
280
- // #region agent log
281
- const logDataE = { location: 'RouterWrapper.ts:246', message: 'Returning final response from invoke', data: { hasMetadata: !!processedResponse.metadata, metadataKeys: processedResponse.metadata ? Object.keys(processedResponse.metadata) : [], hasAiActivitiesResponse: !!processedResponse.metadata?.['ai-activities-response'], hasAiActivitiesRequest: !!processedResponse.metadata?.['ai-activities-request'], finalResponseKeys: Object.keys(processedResponse) }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'run1', hypothesisId: 'E' };
282
- console.log('[DEBUG RouterWrapper] Final response metadata keys:', processedResponse.metadata ? Object.keys(processedResponse.metadata) : 'NO METADATA');
283
- console.log('[DEBUG RouterWrapper] ai-activities keys in final response:', processedResponse.metadata ? Object.keys(processedResponse.metadata).filter(k => k.startsWith('ai-activities')) : []);
284
- console.log('[DEBUG RouterWrapper] Full log data:', JSON.stringify(logDataE, null, 2));
285
- fetch('http://127.0.0.1:7243/ingest/0d4596d8-b8fa-4ca1-a4f4-2e6f23baa429', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(logDataE) }).catch(() => { });
286
- // Test serialization to verify metadata survives JSON.stringify/parse
287
- let serializationTest = null;
288
- let serializationError = null;
289
- try {
290
- const serialized = JSON.stringify(processedResponse);
291
- const deserialized = JSON.parse(serialized);
292
- serializationTest = {
293
- success: true,
294
- hasMetadataAfterSerialize: !!deserialized.metadata,
295
- metadataKeysAfterSerialize: deserialized.metadata ? Object.keys(deserialized.metadata) : [],
296
- hasAiActivitiesResponseAfterSerialize: !!deserialized.metadata?.['ai-activities-response'],
297
- hasAiActivitiesRequestAfterSerialize: !!deserialized.metadata?.['ai-activities-request'],
298
- aiActivitiesKeysAfterSerialize: deserialized.metadata ? Object.keys(deserialized.metadata).filter((k) => k.startsWith('ai-activities')) : [],
299
- };
300
- }
301
- catch (err) {
302
- serializationError = err.message || String(err);
303
- serializationTest = { success: false, error: serializationError };
304
- }
305
- const logDataF = { location: 'RouterWrapper.ts:280', message: 'Serialization test of final response', data: serializationTest, timestamp: Date.now(), sessionId: 'debug-session', runId: 'run1', hypothesisId: 'F' };
306
- console.log('[DEBUG RouterWrapper] Serialization test:', JSON.stringify(serializationTest, null, 2));
307
- fetch('http://127.0.0.1:7243/ingest/0d4596d8-b8fa-4ca1-a4f4-2e6f23baa429', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(logDataF) }).catch(() => { });
308
- // Detailed inspection of ai-activities data structure
309
- const aiActivitiesInspection = {
310
- hasAiActivitiesResponse: !!processedResponse.metadata?.['ai-activities-response'],
311
- hasAiActivitiesRequest: !!processedResponse.metadata?.['ai-activities-request'],
312
- aiActivitiesResponseType: typeof processedResponse.metadata?.['ai-activities-response'],
313
- aiActivitiesRequestType: typeof processedResponse.metadata?.['ai-activities-request'],
314
- };
315
- if (processedResponse.metadata?.['ai-activities-response']) {
316
- const resp = processedResponse.metadata['ai-activities-response'];
317
- aiActivitiesInspection.aiActivitiesResponseKeys = Object.keys(resp);
318
- aiActivitiesInspection.hasRequestId = !!resp.requestId;
319
- aiActivitiesInspection.hasProvider = !!resp.provider;
320
- aiActivitiesInspection.hasRawResponse = !!resp.rawResponse;
321
- aiActivitiesInspection.hasOutputText = !!resp.outputText;
322
- aiActivitiesInspection.hasUsage = !!resp.usage;
323
- aiActivitiesInspection.hasReasoning = !!resp.reasoning;
324
- aiActivitiesInspection.outputTextLength = resp.outputText?.length || 0;
325
- aiActivitiesInspection.outputTextPreview = resp.outputText?.substring(0, 100) || null;
326
- }
327
- if (processedResponse.metadata?.['ai-activities-request']) {
328
- const req = processedResponse.metadata['ai-activities-request'];
329
- aiActivitiesInspection.aiActivitiesRequestKeys = Object.keys(req);
330
- aiActivitiesInspection.requestHasInstructions = !!req.instructions;
331
- aiActivitiesInspection.requestHasInputData = !!req.inputData;
332
- aiActivitiesInspection.requestHasConfig = !!req.config;
333
- aiActivitiesInspection.requestHasCallSpec = !!req._callSpec;
334
- }
335
- // Test if we can serialize just the ai-activities data
336
- let aiActivitiesSerializationTest = null;
337
- try {
338
- const aiActivitiesOnly = {
339
- 'ai-activities-response': processedResponse.metadata?.['ai-activities-response'],
340
- 'ai-activities-request': processedResponse.metadata?.['ai-activities-request'],
341
- 'ai-activities-raw-response': processedResponse.metadata?.['ai-activities-raw-response'],
342
- 'ai-activities-original-response': processedResponse.metadata?.['ai-activities-original-response'],
343
- 'ai-activities-exec-meta': processedResponse.metadata?.['ai-activities-exec-meta'],
344
- };
345
- const serialized = JSON.stringify(aiActivitiesOnly);
346
- const deserialized = JSON.parse(serialized);
347
- aiActivitiesSerializationTest = {
348
- success: true,
349
- serializedSize: serialized.length,
350
- hasResponseAfterSerialize: !!deserialized['ai-activities-response'],
351
- hasRequestAfterSerialize: !!deserialized['ai-activities-request'],
352
- responseKeysAfterSerialize: deserialized['ai-activities-response'] ? Object.keys(deserialized['ai-activities-response']) : [],
353
- };
354
- }
355
- catch (err) {
356
- aiActivitiesSerializationTest = { success: false, error: err.message || String(err) };
357
- }
358
- const logDataG = { location: 'RouterWrapper.ts:320', message: 'Detailed ai-activities inspection', data: { inspection: aiActivitiesInspection, serializationTest: aiActivitiesSerializationTest }, timestamp: Date.now(), sessionId: 'debug-session', runId: 'run1', hypothesisId: 'G' };
359
- console.log('[DEBUG RouterWrapper] ai-activities inspection:', JSON.stringify(aiActivitiesInspection, null, 2));
360
- console.log('[DEBUG RouterWrapper] ai-activities serialization test:', JSON.stringify(aiActivitiesSerializationTest, null, 2));
361
- fetch('http://127.0.0.1:7243/ingest/0d4596d8-b8fa-4ca1-a4f4-2e6f23baa429', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(logDataG) }).catch(() => { });
362
- // Summary: Print complete response structure for downstream system debugging
363
- console.log('\n=== ROUTER RESPONSE SUMMARY (for downstream system) ===');
364
- console.log('Response has metadata:', !!processedResponse.metadata);
365
- console.log('Metadata keys:', processedResponse.metadata ? Object.keys(processedResponse.metadata) : 'NONE');
366
- console.log('ai-activities keys:', processedResponse.metadata ? Object.keys(processedResponse.metadata).filter(k => k.startsWith('ai-activities')) : []);
367
- console.log('Access path: response.metadata["ai-activities-response"]');
368
- console.log('Access path: response.metadata["ai-activities-request"]');
369
- if (processedResponse.metadata?.['ai-activities-response']) {
370
- const resp = processedResponse.metadata['ai-activities-response'];
371
- console.log('ai-activities-response structure:', {
372
- hasRequestId: !!resp.requestId,
373
- hasProvider: !!resp.provider,
374
- hasRawResponse: !!resp.rawResponse,
375
- hasOutputText: !!resp.outputText,
376
- hasUsage: !!resp.usage,
377
- hasReasoning: !!resp.reasoning,
378
- outputTextPreview: resp.outputText?.substring(0, 50) || 'NONE',
379
- });
380
- }
381
- console.log('=== END ROUTER RESPONSE SUMMARY ===\n');
382
- // Final verification: Test accessing ai-activities data as downstream system would
383
- try {
384
- const testAccess = {
385
- 'ai-activities-response': processedResponse.metadata?.['ai-activities-response'],
386
- 'ai-activities-request': processedResponse.metadata?.['ai-activities-request'],
387
- 'ai-activities-raw-response': processedResponse.metadata?.['ai-activities-raw-response'],
388
- 'ai-activities-original-response': processedResponse.metadata?.['ai-activities-original-response'],
389
- 'ai-activities-exec-meta': processedResponse.metadata?.['ai-activities-exec-meta'],
390
- };
391
- const canAccess = {
392
- responseExists: !!testAccess['ai-activities-response'],
393
- requestExists: !!testAccess['ai-activities-request'],
394
- responseHasData: testAccess['ai-activities-response'] ? Object.keys(testAccess['ai-activities-response']).length > 0 : false,
395
- requestHasData: testAccess['ai-activities-request'] ? Object.keys(testAccess['ai-activities-request']).length > 0 : false,
396
- };
397
- console.log('[VERIFICATION] Downstream access test:', JSON.stringify(canAccess, null, 2));
398
- console.log('[VERIFICATION] Example access code:');
399
- console.log(' const response = await router.invoke(request);');
400
- console.log(' const activitiesResponse = response.metadata["ai-activities-response"];');
401
- console.log(' const activitiesRequest = response.metadata["ai-activities-request"];');
402
- console.log(' // Save to database: activitiesResponse, activitiesRequest');
403
- if (!canAccess.responseExists || !canAccess.requestExists) {
404
- console.error('[ERROR] ai-activities data is missing! This will cause database saves to fail.');
405
- }
406
- }
407
- catch (err) {
408
- console.error('[ERROR] Failed to verify ai-activities access:', err.message);
409
- }
410
- // Final safeguard: Ensure metadata is never lost
411
- if (!processedResponse.metadata) {
412
- console.error('[CRITICAL ERROR] Response has no metadata! Creating empty metadata object.');
413
- processedResponse.metadata = {};
414
- }
415
- // Final safeguard: Ensure ai-activities data exists, log warning if missing
416
- if (!processedResponse.metadata['ai-activities-response'] || !processedResponse.metadata['ai-activities-request']) {
417
- console.warn('[WARNING] ai-activities data is missing from final response!');
418
- console.warn('[WARNING] This will cause database saves to fail.');
419
- console.warn('[WARNING] Metadata keys present:', Object.keys(processedResponse.metadata));
420
- }
421
- else {
422
- console.log('[SUCCESS] ai-activities data confirmed present in final response');
423
- }
424
- // Final log: Complete response structure snapshot
425
- const finalSnapshot = {
426
- timestamp: new Date().toISOString(),
427
- hasMetadata: !!processedResponse.metadata,
428
- metadataKeys: processedResponse.metadata ? Object.keys(processedResponse.metadata) : [],
429
- aiActivitiesKeys: processedResponse.metadata ? Object.keys(processedResponse.metadata).filter(k => k.startsWith('ai-activities')) : [],
430
- hasAiActivitiesResponse: !!processedResponse.metadata?.['ai-activities-response'],
431
- hasAiActivitiesRequest: !!processedResponse.metadata?.['ai-activities-request'],
432
- responseKeys: Object.keys(processedResponse),
433
- };
434
- console.log('[FINAL SNAPSHOT] Response structure before return:', JSON.stringify(finalSnapshot, null, 2));
435
- // Add helper property for easier access (non-enumerable to avoid breaking serialization)
436
- // This makes it easier for downstream systems to find the data
437
- if (processedResponse.metadata?.['ai-activities-response'] && processedResponse.metadata?.['ai-activities-request']) {
438
- try {
439
- Object.defineProperty(processedResponse, '_aiActivities', {
440
- value: {
441
- response: processedResponse.metadata['ai-activities-response'],
442
- request: processedResponse.metadata['ai-activities-request'],
443
- rawResponse: processedResponse.metadata['ai-activities-raw-response'],
444
- originalResponse: processedResponse.metadata['ai-activities-original-response'],
445
- execMeta: processedResponse.metadata['ai-activities-exec-meta'],
446
- },
447
- enumerable: false, // Don't include in JSON.stringify
448
- writable: false,
449
- configurable: true,
450
- });
451
- console.log('[HELPER] Added _aiActivities helper property (access via response._aiActivities)');
452
- }
453
- catch (err) {
454
- console.warn('[WARNING] Could not add _aiActivities helper property:', err.message);
455
- }
456
- }
457
- // #endregion
458
248
  return processedResponse;
459
249
  }
460
250
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@x12i/ai-providers-router",
3
- "version": "4.7.0",
3
+ "version": "4.7.6",
4
4
  "description": "Unified router for all LLM provider implementations",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -45,7 +45,7 @@
45
45
  "@x12i/ai-provider-grok": "^3.2.0",
46
46
  "@x12i/ai-provider-interface": "^3.2.1",
47
47
  "@x12i/ai-provider-openai": "^3.2.1",
48
- "@x12i/logxer": "^4.3.5",
48
+ "@x12i/logxer": "^4.3.6",
49
49
  "ai-io-normalizer": "^6.0.3"
50
50
  },
51
51
  "devDependencies": {