@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 +15 -0
- package/dist/router/Router.js +175 -21
- package/dist/router/RouterTypes.d.ts +57 -2
- package/dist/router/RouterWrapper.js +4 -214
- package/package.json +2 -2
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
|
|
package/dist/router/Router.js
CHANGED
|
@@ -90,28 +90,182 @@ export class AIRouter {
|
|
|
90
90
|
*/
|
|
91
91
|
async runSync(input) {
|
|
92
92
|
const requestId = input.requestId ?? newId();
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
//
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
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?:
|
|
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?:
|
|
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 (
|
|
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?.
|
|
273
|
-
output: result.usage?.
|
|
240
|
+
input: result.usage?.promptTokens || 0,
|
|
241
|
+
output: result.usage?.completionTokens || 0,
|
|
274
242
|
total: result.usage?.totalTokens || 0,
|
|
275
243
|
},
|
|
276
|
-
cost: result.
|
|
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.
|
|
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.
|
|
48
|
+
"@x12i/logxer": "^4.3.6",
|
|
49
49
|
"ai-io-normalizer": "^6.0.3"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|