@x12i/ai-gateway 10.3.0 → 10.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +92 -1
  2. package/dist/activity-manager.js +33 -0
  3. package/dist/gateway-config.js +15 -0
  4. package/dist/gateway-utils.d.ts +8 -1
  5. package/dist/gateway-utils.js +73 -1
  6. package/dist/gateway.js +18 -3
  7. package/dist/index.d.ts +4 -3
  8. package/dist/index.js +3 -2
  9. package/dist/instruction-errors.d.ts +21 -0
  10. package/dist/instruction-errors.js +36 -0
  11. package/dist/logger-factory.js +0 -2
  12. package/dist/openrouter-runtime-adapter/create-openrouter-runtime-adapter.d.ts +23 -0
  13. package/dist/openrouter-runtime-adapter/create-openrouter-runtime-adapter.js +31 -0
  14. package/dist/openrouter-runtime-adapter/create-openrouter-runtime-provider.d.ts +9 -0
  15. package/dist/openrouter-runtime-adapter/create-openrouter-runtime-provider.js +41 -0
  16. package/dist/openrouter-runtime-adapter/index.d.ts +8 -0
  17. package/dist/openrouter-runtime-adapter/index.js +8 -0
  18. package/dist/openrouter-runtime-adapter/map-gateway-request.d.ts +26 -0
  19. package/dist/openrouter-runtime-adapter/map-gateway-request.js +62 -0
  20. package/dist/openrouter-runtime-adapter/map-runtime-errors.d.ts +5 -0
  21. package/dist/openrouter-runtime-adapter/map-runtime-errors.js +60 -0
  22. package/dist/openrouter-runtime-adapter/map-runtime-response.d.ts +31 -0
  23. package/dist/openrouter-runtime-adapter/map-runtime-response.js +153 -0
  24. package/dist/openrouter-runtime-adapter/map-server-tools.d.ts +3 -0
  25. package/dist/openrouter-runtime-adapter/map-server-tools.js +143 -0
  26. package/dist/openrouter-runtime-adapter/map-trace.d.ts +5 -0
  27. package/dist/openrouter-runtime-adapter/map-trace.js +45 -0
  28. package/dist/openrouter-runtime-adapter/register-openrouter-runtime.d.ts +10 -0
  29. package/dist/openrouter-runtime-adapter/register-openrouter-runtime.js +14 -0
  30. package/dist/openrouter-runtime-adapter/should-use-openrouter-runtime.d.ts +14 -0
  31. package/dist/openrouter-runtime-adapter/should-use-openrouter-runtime.js +29 -0
  32. package/dist/openrouter-runtime-adapter/validate-server-tools.d.ts +30 -0
  33. package/dist/openrouter-runtime-adapter/validate-server-tools.js +151 -0
  34. package/dist/types.d.ts +234 -0
  35. package/dist-cjs/activity-manager.cjs +33 -0
  36. package/dist-cjs/gateway-config.cjs +15 -0
  37. package/dist-cjs/gateway-utils.cjs +73 -1
  38. package/dist-cjs/gateway-utils.d.ts +8 -1
  39. package/dist-cjs/gateway.cjs +18 -3
  40. package/dist-cjs/index.cjs +3 -2
  41. package/dist-cjs/index.d.ts +4 -3
  42. package/dist-cjs/instruction-errors.cjs +36 -0
  43. package/dist-cjs/instruction-errors.d.ts +21 -0
  44. package/dist-cjs/logger-factory.cjs +0 -2
  45. package/dist-cjs/openrouter-runtime-adapter/create-openrouter-runtime-adapter.cjs +31 -0
  46. package/dist-cjs/openrouter-runtime-adapter/create-openrouter-runtime-adapter.d.ts +23 -0
  47. package/dist-cjs/openrouter-runtime-adapter/create-openrouter-runtime-provider.cjs +41 -0
  48. package/dist-cjs/openrouter-runtime-adapter/create-openrouter-runtime-provider.d.ts +9 -0
  49. package/dist-cjs/openrouter-runtime-adapter/index.cjs +8 -0
  50. package/dist-cjs/openrouter-runtime-adapter/index.d.ts +8 -0
  51. package/dist-cjs/openrouter-runtime-adapter/map-gateway-request.cjs +62 -0
  52. package/dist-cjs/openrouter-runtime-adapter/map-gateway-request.d.ts +26 -0
  53. package/dist-cjs/openrouter-runtime-adapter/map-runtime-errors.cjs +60 -0
  54. package/dist-cjs/openrouter-runtime-adapter/map-runtime-errors.d.ts +5 -0
  55. package/dist-cjs/openrouter-runtime-adapter/map-runtime-response.cjs +153 -0
  56. package/dist-cjs/openrouter-runtime-adapter/map-runtime-response.d.ts +31 -0
  57. package/dist-cjs/openrouter-runtime-adapter/map-server-tools.cjs +143 -0
  58. package/dist-cjs/openrouter-runtime-adapter/map-server-tools.d.ts +3 -0
  59. package/dist-cjs/openrouter-runtime-adapter/map-trace.cjs +45 -0
  60. package/dist-cjs/openrouter-runtime-adapter/map-trace.d.ts +5 -0
  61. package/dist-cjs/openrouter-runtime-adapter/register-openrouter-runtime.cjs +14 -0
  62. package/dist-cjs/openrouter-runtime-adapter/register-openrouter-runtime.d.ts +10 -0
  63. package/dist-cjs/openrouter-runtime-adapter/should-use-openrouter-runtime.cjs +29 -0
  64. package/dist-cjs/openrouter-runtime-adapter/should-use-openrouter-runtime.d.ts +14 -0
  65. package/dist-cjs/openrouter-runtime-adapter/validate-server-tools.cjs +151 -0
  66. package/dist-cjs/openrouter-runtime-adapter/validate-server-tools.d.ts +30 -0
  67. package/dist-cjs/types.d.ts +234 -0
  68. package/package.json +7 -5
package/README.md CHANGED
@@ -13,7 +13,7 @@ Unified gateway for LLM provider routing, structured logging, optional Activix a
13
13
  | **Activix** | Optional Mongo-backed activity rows; billing written from gateway-computed slice on **`completeRecord`** (`outer.cost` + root fields). No Activix **`autoCost`** re-pricing. |
14
14
  | **Trace mode** | `diagnostics.mode === 'trace'` adds `metadata.attempts[]`, `metadata.usage`, and per-attempt **`costUsd`** / **`costStatus`**. |
15
15
 
16
- Pinned dependency versions are in `package.json` (currently **Activix ^9.0**, **ai-tools ^3.3**, **ai-profiles ^3.4**, **ai-providers-router ^4.9**).
16
+ Pinned dependency versions are in `package.json` (currently **Activix ^9.0**, **ai-tools ^3.3**, **ai-profiles ^3.4**, **ai-providers-router ^4.9**, **openrouter-runtime ^1.0**).
17
17
 
18
18
  ---
19
19
 
@@ -350,6 +350,97 @@ Live tests use `LIVE_TEST_PROVIDER` / `LIVE_TEST_MODEL` (default `openrouter` +
350
350
 
351
351
  ---
352
352
 
353
+ ## OpenRouter server tools
354
+
355
+ `ai-gateway` exposes OpenRouter server tools through `config.serverTools` (and `modelConfig.serverTools`). This is **additive** and only applies when the final provider is `openrouter`. Existing calls without `serverTools` behave the same.
356
+
357
+ OpenRouter execution uses `@x12i/openrouter-runtime` internally (enabled by default). Control via:
358
+
359
+ | Variable | Effect |
360
+ |----------|--------|
361
+ | `AI_GATEWAY_USE_OPENROUTER_RUNTIME=0` | Legacy router OpenRouter path |
362
+ | `AI_GATEWAY_USE_LEGACY_OPENROUTER=1` | Same as legacy (emergency fallback) |
363
+
364
+ Gateway constructor: `openrouterRuntime: { enabled, apiMode, includeRawInTrace }`, `defaultServerTools`.
365
+
366
+ ### Web search
367
+
368
+ ```typescript
369
+ await gateway.invoke({
370
+ ...base,
371
+ config: {
372
+ provider: 'openrouter',
373
+ model: 'openai/gpt-5.2',
374
+ maxTokens: 1200,
375
+ serverTools: {
376
+ webSearch: {
377
+ mode: 'required',
378
+ engine: 'auto',
379
+ maxResults: 5,
380
+ maxTotalResults: 15,
381
+ },
382
+ },
383
+ },
384
+ });
385
+ ```
386
+
387
+ ### Search + fetch
388
+
389
+ ```typescript
390
+ serverTools: {
391
+ webSearch: { mode: 'required' },
392
+ webFetch: {
393
+ mode: 'allowed',
394
+ engine: 'openrouter',
395
+ maxContentTokens: 50000,
396
+ },
397
+ }
398
+ ```
399
+
400
+ ### Datetime
401
+
402
+ ```typescript
403
+ serverTools: {
404
+ datetime: {
405
+ mode: 'allowed',
406
+ timezone: 'Asia/Jerusalem',
407
+ },
408
+ }
409
+ ```
410
+
411
+ ### Apply patch
412
+
413
+ Apply patch requires OpenRouter Responses API and returns patch proposals only. The gateway **never** writes files.
414
+
415
+ ```typescript
416
+ config: {
417
+ provider: 'openrouter',
418
+ model: 'openai/o4-mini',
419
+ maxTokens: 1200,
420
+ openrouter: { apiMode: 'responses' },
421
+ serverTools: {
422
+ applyPatch: {
423
+ mode: 'required',
424
+ behavior: 'return_only',
425
+ },
426
+ },
427
+ }
428
+ ```
429
+
430
+ ### Metadata
431
+
432
+ Tool usage and artifacts appear under:
433
+
434
+ - `response.metadata.serverTools`
435
+ - `response.metadata.citations`
436
+ - `response.metadata.generatedImages`
437
+ - `response.metadata.patchProposals`
438
+ - `response.metadata.openrouterRuntime`
439
+
440
+ Cost remains under `response.metadata.costUsd`, `response.metadata.costStatus`, and `response.metadata.tokens`.
441
+
442
+ ---
443
+
353
444
  ## Documentation index
354
445
 
355
446
  Full index: **[docs/README.md](./docs/README.md)**. Upstream gaps: **[docs/upstream-reports/](./docs/upstream-reports/README.md)**.
@@ -230,6 +230,39 @@ function pickActivixCompletionRoutingMetadata(response) {
230
230
  if (m.effectiveModelConfig != null && typeof m.effectiveModelConfig === 'object') {
231
231
  out.effectiveModelConfig = m.effectiveModelConfig;
232
232
  }
233
+ const citations = m.citations;
234
+ if (Array.isArray(citations) && citations.length) {
235
+ out.citations = citations
236
+ .filter((c) => c != null && typeof c === 'object')
237
+ .map((c) => {
238
+ const row = c;
239
+ return {
240
+ ...(typeof row.url === 'string' ? { url: row.url } : {}),
241
+ ...(typeof row.title === 'string' ? { title: row.title } : {}),
242
+ };
243
+ });
244
+ out.citationCount = citations.length;
245
+ }
246
+ const serverTools = m.serverTools;
247
+ const generatedImages = m.generatedImages;
248
+ const patchProposals = m.patchProposals;
249
+ const openrouterRuntime = m.openrouterRuntime;
250
+ if (serverTools != null ||
251
+ openrouterRuntime != null ||
252
+ (Array.isArray(generatedImages) && generatedImages.length) ||
253
+ (Array.isArray(patchProposals) && patchProposals.length)) {
254
+ out.openrouterRuntime = {
255
+ ...(openrouterRuntime != null && typeof openrouterRuntime === 'object'
256
+ ? {
257
+ apiMode: openrouterRuntime.apiMode,
258
+ serverTools,
259
+ }
260
+ : { serverTools }),
261
+ citationCount: Array.isArray(citations) ? citations.length : 0,
262
+ generatedImageCount: Array.isArray(generatedImages) ? generatedImages.length : 0,
263
+ patchProposalCount: Array.isArray(patchProposals) ? patchProposals.length : 0,
264
+ };
265
+ }
233
266
  }
234
267
  }
235
268
  return out;
@@ -11,6 +11,7 @@ import { createGatewayLogger } from './logger-factory.js';
11
11
  import { ActivityManager } from './activity-manager.js';
12
12
  import { UsageTracker } from './usage-tracker.js';
13
13
  import { mergeTemplateRenderOptions } from './template-render-merge.js';
14
+ import { registerOpenRouterRuntime, shouldUseOpenRouterRuntime, } from './openrouter-runtime-adapter/index.js';
14
15
  /** Resolve current module directory across ESM/CJS builds. */
15
16
  function getModuleDir() {
16
17
  if (typeof __dirname !== 'undefined') {
@@ -167,6 +168,20 @@ export function initializeGatewayComponents(config) {
167
168
  }
168
169
  const router = new LLMProviderRouter(routerConfig);
169
170
  setupRequestInterceptor(router, logger);
171
+ if (openRouterKey && shouldUseOpenRouterRuntime(config)) {
172
+ registerOpenRouterRuntime(router, {
173
+ apiKey: openRouterKey,
174
+ logger,
175
+ runtimeConfig: config.openrouterRuntime,
176
+ });
177
+ }
178
+ else if (openRouterKey) {
179
+ logger.debug('OpenRouter legacy provider path enabled (runtime disabled)', {
180
+ AI_GATEWAY_USE_OPENROUTER_RUNTIME: process.env.AI_GATEWAY_USE_OPENROUTER_RUNTIME,
181
+ AI_GATEWAY_USE_LEGACY_OPENROUTER: process.env.AI_GATEWAY_USE_LEGACY_OPENROUTER,
182
+ openrouterRuntimeEnabled: config.openrouterRuntime?.enabled,
183
+ });
184
+ }
170
185
  const usageTracker = new UsageTracker({
171
186
  enableUsageTracking: config.enableUsageTracking ?? true,
172
187
  usageTier: config.usageTier,
@@ -5,6 +5,7 @@
5
5
  import type { AIInvokeRequest, ChatRequest, GatewayConfig, GatewayFallbackAttempt, GatewayInvokeRejectionMetadata, GatewayTraceAttempt, GatewayTraceMergedConfig, GatewayTraceRequestIds, GatewayTraceUsageSummary, ModelConfig } from './types.js';
6
6
  import type { Logxer } from '@x12i/logxer';
7
7
  import { type AiCostResult, type AiModelsCatalogClient, type CostCalculator, type OpenRouterRoutingConfig } from '@x12i/ai-tools';
8
+ import { pickOpenRouterRuntimeMetadataSlice, enrichTraceOpenRouterRuntimeMetadata } from './openrouter-runtime-adapter/index.js';
8
9
  /**
9
10
  * Generates MD5 hash of a string
10
11
  */
@@ -132,7 +133,13 @@ export declare function buildTraceUsageSummary(tokens: {
132
133
  prompt: number;
133
134
  completion: number;
134
135
  total: number;
135
- }, billing: ResolvedActivityCost, maxTokensRequested?: number): GatewayTraceUsageSummary | undefined;
136
+ }, billing: ResolvedActivityCost, maxTokensRequested?: number, openrouterExtras?: Pick<GatewayTraceUsageSummary, 'serverTools' | 'citations'>): GatewayTraceUsageSummary | undefined;
137
+ /**
138
+ * Additive OpenRouter runtime metadata for gateway responses.
139
+ */
140
+ export declare function pickEnhancedOpenRouterMetadata(routerResponse: unknown, traceEnabled?: boolean): ReturnType<typeof pickOpenRouterRuntimeMetadataSlice> & {
141
+ openrouterRuntime?: ReturnType<typeof enrichTraceOpenRouterRuntimeMetadata>;
142
+ };
136
143
  /**
137
144
  * Apply resolved billing to trace attempts: final successful attempt gets aggregate billing;
138
145
  * other successful attempts without router cost get per-attempt catalog pricing when enabled.
@@ -9,6 +9,7 @@ import { extractHttpStatusCode } from './gateway-retry.js';
9
9
  import { gatewayLogDebug, withActivityIdentity } from './gateway-log-meta.js';
10
10
  import { MaxTokensRequiredError, ModelRequiredError, } from './instruction-errors.js';
11
11
  import { normalizeInvokeModelAtIngress } from './invoke-model-ingress.js';
12
+ import { applyOnlineVariantMigration, applyPostRoutingServerToolsPolicy, mergeOpenRouterConfig, mergeServerToolsConfig, normalizeApplyPatchDefaults, resolveOpenRouterRuntimeDefaults, pickOpenRouterRuntimeMetadataSlice, enrichTraceOpenRouterRuntimeMetadata, } from './openrouter-runtime-adapter/index.js';
12
13
  import { DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, GATEWAY_DEFAULT_FREQUENCY_PENALTY, GATEWAY_DEFAULT_PRESENCE_PENALTY, GATEWAY_DEFAULT_TEMPERATURE, GATEWAY_DEFAULT_TOP_P } from './gateway-defaults.js';
13
14
  function getPreParsedInstructions(instructions) {
14
15
  return instructions ?? '';
@@ -115,6 +116,31 @@ export async function mergeConfig(request, config, logger, mergeOptions) {
115
116
  merged.model = ingress.model;
116
117
  originalProvider = merged.provider;
117
118
  originalModel = merged.model;
119
+ const runtimeDefaults = resolveOpenRouterRuntimeDefaults(config);
120
+ let mergedServerTools = normalizeApplyPatchDefaults(mergeServerToolsConfig(config.defaultServerTools, request.config?.serverTools, request.modelConfig?.serverTools));
121
+ const mergedOpenRouter = mergeOpenRouterConfig({ apiMode: runtimeDefaults.apiMode, includeRawInTrace: runtimeDefaults.includeRawInTrace }, request.config?.openrouter, request.modelConfig?.openrouter);
122
+ const onlineMigration = applyOnlineVariantMigration(merged.model, mergedServerTools);
123
+ if (onlineMigration.model !== merged.model) {
124
+ merged.model = onlineMigration.model;
125
+ originalModel = merged.model;
126
+ mergedServerTools = onlineMigration.serverTools;
127
+ logger.warn('OpenRouter :online model variant migrated', {
128
+ jobId: request.identity.jobId,
129
+ code: onlineMigration.warning?.code,
130
+ message: onlineMigration.warning?.message,
131
+ });
132
+ const warnings = request._openrouterWarnings;
133
+ if (onlineMigration.warning) {
134
+ request._openrouterWarnings = [
135
+ ...(warnings ?? []),
136
+ onlineMigration.warning,
137
+ ];
138
+ }
139
+ }
140
+ if (mergedServerTools)
141
+ merged.serverTools = mergedServerTools;
142
+ if (mergedOpenRouter)
143
+ merged.openrouter = mergedOpenRouter;
118
144
  if (resolveModels && mergeOptions?.catalog) {
119
145
  try {
120
146
  const resolved = await resolveInvokeModel({ provider: merged.provider, model: merged.model }, {
@@ -175,6 +201,34 @@ export async function mergeConfig(request, config, logger, mergeOptions) {
175
201
  merged.providerProxy = resolved.router.providerProxy;
176
202
  }
177
203
  }
204
+ const serverToolsPolicy = applyPostRoutingServerToolsPolicy({
205
+ provider: merged.provider,
206
+ serverTools: merged.serverTools,
207
+ openrouter: merged.openrouter,
208
+ openRouterApiKey: mergeOptions?.openRouterApiKey,
209
+ });
210
+ if (serverToolsPolicy.forceOpenRouter) {
211
+ merged.provider = 'openrouter';
212
+ logger.verbose('Required OpenRouter server tool forced provider=openrouter', {
213
+ jobId: request.identity.jobId,
214
+ });
215
+ }
216
+ if (serverToolsPolicy.serverTools) {
217
+ merged.serverTools = serverToolsPolicy.serverTools;
218
+ }
219
+ else {
220
+ delete merged.serverTools;
221
+ }
222
+ if (serverToolsPolicy.warnings.length) {
223
+ const existing = request._openrouterWarnings;
224
+ request._openrouterWarnings = [
225
+ ...(existing ?? []),
226
+ ...serverToolsPolicy.warnings,
227
+ ];
228
+ for (const w of serverToolsPolicy.warnings) {
229
+ logger.warn(w.message, { jobId: request.identity.jobId, code: w.code });
230
+ }
231
+ }
178
232
  if (!merged.model) {
179
233
  throw new ModelRequiredError();
180
234
  }
@@ -571,7 +625,7 @@ function buildTraceAttemptPricingRecord(attempt, mergedConfig) {
571
625
  /**
572
626
  * Trace-mode summary: final token usage + resolved billing (after catalog pricing when applicable).
573
627
  */
574
- export function buildTraceUsageSummary(tokens, billing, maxTokensRequested) {
628
+ export function buildTraceUsageSummary(tokens, billing, maxTokensRequested, openrouterExtras) {
575
629
  if (!hasNonZeroTokenUsage(tokens) && !billing.costStatus) {
576
630
  return undefined;
577
631
  }
@@ -589,8 +643,26 @@ export function buildTraceUsageSummary(tokens, billing, maxTokensRequested) {
589
643
  if (billing.costBreakdown) {
590
644
  summary.costBreakdown = billing.costBreakdown;
591
645
  }
646
+ if (openrouterExtras?.serverTools)
647
+ summary.serverTools = openrouterExtras.serverTools;
648
+ if (openrouterExtras?.citations?.length)
649
+ summary.citations = openrouterExtras.citations;
592
650
  return summary;
593
651
  }
652
+ /**
653
+ * Additive OpenRouter runtime metadata for gateway responses.
654
+ */
655
+ export function pickEnhancedOpenRouterMetadata(routerResponse, traceEnabled = false) {
656
+ const slice = pickOpenRouterRuntimeMetadataSlice(routerResponse);
657
+ if (!slice.openrouterRuntime && !slice.serverTools && !slice.citations?.length) {
658
+ return slice;
659
+ }
660
+ const openrouterRuntime = enrichTraceOpenRouterRuntimeMetadata(slice.openrouterRuntime, slice, traceEnabled);
661
+ return {
662
+ ...slice,
663
+ ...(openrouterRuntime ? { openrouterRuntime } : {}),
664
+ };
665
+ }
594
666
  /**
595
667
  * Apply resolved billing to trace attempts: final successful attempt gets aggregate billing;
596
668
  * other successful attempts without router cost get per-attempt catalog pricing when enabled.
package/dist/gateway.js CHANGED
@@ -11,7 +11,8 @@ import { resolveRetryConfig } from './gateway-defaults.js';
11
11
  import { buildMessages } from './message-builder.js';
12
12
  import { extractJsonFromFlexMd } from './flex-md-loader.js';
13
13
  import { enrichParsedContentForOutputContract, resolveOutputContractFieldKeys } from './output-contract-normalizer.js';
14
- import { attachGatewayInvokeRejectionMetadata, buildGatewayFallbackAttemptsFromTrace, buildInvokeRejectionMetadata, capActivityFullResponsePayload, formatFallbackExhaustionMessage, logResolvedModelRouting, mapGatewayFallbackAttemptsToRouter, DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, extractCostUsdFromRouterResponse, extractTokenUsageFromRouterResponse, mergeConfig, pickEffectiveModelConfigForMetadata, pickInvokeRoutingMetadataSlice, pickTraceMergedRouterConfig, resolveCostCompletionWithAiTools, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, tryExtractRouterLikePayloadFromErrorChain } from './gateway-utils.js';
14
+ import { attachGatewayInvokeRejectionMetadata, buildGatewayFallbackAttemptsFromTrace, buildInvokeRejectionMetadata, capActivityFullResponsePayload, formatFallbackExhaustionMessage, logResolvedModelRouting, mapGatewayFallbackAttemptsToRouter, DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, extractCostUsdFromRouterResponse, extractTokenUsageFromRouterResponse, mergeConfig, pickEffectiveModelConfigForMetadata, pickInvokeRoutingMetadataSlice, pickTraceMergedRouterConfig, pickEnhancedOpenRouterMetadata, resolveCostCompletionWithAiTools, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, tryExtractRouterLikePayloadFromErrorChain } from './gateway-utils.js';
15
+ import { buildTraceAttemptOpenRouterRuntimeSlice } from './openrouter-runtime-adapter/index.js';
15
16
  import { getAiToolsClient } from './ai-tools-client.js';
16
17
  import { autoRegisterProviders } from './gateway-provider-auto-register.js';
17
18
  import { setGatewayLastJobId, setGatewayRuntimeClients } from './runtime-objects.js';
@@ -140,9 +141,12 @@ export class AIGateway {
140
141
  calculator: aiTools?.calculator ?? null,
141
142
  calculateCost: this.config.aiTools?.calculateCost
142
143
  });
144
+ const routingMetadataSliceChat = pickInvokeRoutingMetadataSlice(response, mergedConfig);
145
+ const effectiveModelConfigChat = pickEffectiveModelConfigForMetadata(mergedConfig);
146
+ const openrouterMetadataChat = pickEnhancedOpenRouterMetadata(response, false);
143
147
  // Create enhanced response
144
148
  const enhancedResponse = {
145
- content: response.content || '',
149
+ content: response.content || response.outputText || '',
146
150
  metadata: {
147
151
  aiRequestId: request.aiRequestId,
148
152
  identity: request.identity,
@@ -150,6 +154,9 @@ export class AIGateway {
150
154
  tokens: tokensChat,
151
155
  taskTypeId,
152
156
  agentType: 'chat',
157
+ ...routingMetadataSliceChat,
158
+ ...(effectiveModelConfigChat !== undefined ? { effectiveModelConfig: effectiveModelConfigChat } : {}),
159
+ ...openrouterMetadataChat,
153
160
  ...(costCompletionChat.costStatus === 'priced'
154
161
  ? {
155
162
  costUsd: costCompletionChat.cost,
@@ -466,6 +473,9 @@ export class AIGateway {
466
473
  const rawStr = typeof raw === 'string' ? raw : safeJsonStringify(raw);
467
474
  a.rawProviderPayload = capString(rawStr, 4000);
468
475
  }
476
+ const runtimeSlice = buildTraceAttemptOpenRouterRuntimeSlice(pickEnhancedOpenRouterMetadata(respAny, true));
477
+ if (runtimeSlice)
478
+ a.openrouterRuntime = runtimeSlice;
469
479
  }
470
480
  else if (tryErr) {
471
481
  a.error = { name: tryErr.name || 'Error', message: capErrorMessage(tryErr.message || String(tryErr)) };
@@ -617,6 +627,7 @@ export class AIGateway {
617
627
  const routerMetaForCost = routerResponse?.metadata || {};
618
628
  const routingMetadataSlice = pickInvokeRoutingMetadataSlice(routerResponse, mergedConfig);
619
629
  const effectiveModelConfig = pickEffectiveModelConfigForMetadata(mergedConfig);
630
+ const openrouterMetadata = pickEnhancedOpenRouterMetadata(routerResponse, traceEnabled);
620
631
  const traceMergedRouterSnapshot = traceEnabled ? pickTraceMergedRouterConfig(mergedConfig) : undefined;
621
632
  if (traceEnabled && traceAttempts) {
622
633
  await enrichTraceAttemptsWithBilling(traceAttempts, costCompletion, {
@@ -626,7 +637,10 @@ export class AIGateway {
626
637
  });
627
638
  }
628
639
  const traceUsageSummary = traceEnabled
629
- ? buildTraceUsageSummary(tokens, costCompletion, routingMetadataSlice.maxTokensRequested)
640
+ ? buildTraceUsageSummary(tokens, costCompletion, routingMetadataSlice.maxTokensRequested, {
641
+ serverTools: openrouterMetadata.serverTools,
642
+ citations: openrouterMetadata.citations,
643
+ })
630
644
  : undefined;
631
645
  const enhancedResponse = {
632
646
  content: content,
@@ -642,6 +656,7 @@ export class AIGateway {
642
656
  parsingMethod,
643
657
  ...routingMetadataSlice,
644
658
  ...(effectiveModelConfig !== undefined ? { effectiveModelConfig } : {}),
659
+ ...openrouterMetadata,
645
660
  ...(costCompletion.costStatus === 'priced'
646
661
  ? {
647
662
  costUsd: costCompletion.cost,
package/dist/index.d.ts CHANGED
@@ -14,11 +14,12 @@ export type { RequestInterceptor, ResponseInterceptor } from '@x12i/ai-providers
14
14
  export type { UsageTracker } from '@x12i/ai-providers-router';
15
15
  export * from '@x12i/ai-providers-router';
16
16
  export { AIGateway } from './gateway.js';
17
- export { InstructionNotFoundError, InstructionBackendError, ModelRequiredError, MaxTokensRequiredError, GatewayAliasModelRejectedError, } from './instruction-errors.js';
17
+ export { InstructionNotFoundError, InstructionBackendError, ModelRequiredError, MaxTokensRequiredError, GatewayAliasModelRejectedError, GatewayValidationError, ProviderConfigError, ProviderInvokeError, GatewayPolicyViolationError, } from './instruction-errors.js';
18
18
  export { normalizeInvokeModelAtIngress } from './invoke-model-ingress.js';
19
19
  export { autoRegisterProviders } from './gateway-provider-auto-register.js';
20
- export type { GatewayConfig, ProviderModelRef, ModelConfig, RetryConfig, ChatRequest, AIInvokeRequest, AIRequest, GatewayActionType, GatewayInvokeRejectionMetadata, GatewayFallbackAttempt, GatewayTraceRequestIds, GatewayTraceAttempt, GatewayTraceUsageSummary, GatewayTraceMergedConfig, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions, SmartInputConfig, SmartInputRenderOptions } from './types.js';
21
- export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, tryExtractFallbackAttemptsFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, catalogPricingSucceeded, extractUsageExtrasFromRouterResponse, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, hasNonZeroTokenUsage, MODEL_PROFILE_UNROUTABLE, ModelProfileUnroutableError, ModelProfileInputRejectedError, buildGatewayFallbackAttemptsFromTrace, formatFallbackExhaustionMessage, logResolvedModelRouting, mapGatewayFallbackAttemptsToRouter } from './gateway-utils.js';
20
+ export { registerOpenRouterRuntime, shouldUseOpenRouterRuntime, mapGatewayServerTools, } from './openrouter-runtime-adapter/index.js';
21
+ export type { GatewayConfig, ProviderModelRef, ModelConfig, GatewayModelConfig, GatewayServerToolsConfig, GatewayOpenRouterConfig, GatewayOpenRouterRuntimeConfig, GatewayServerToolUsageMap, GatewayCitation, GatewayGeneratedImage, GatewayPatchProposal, GatewayOpenRouterRuntimeMetadata, RetryConfig, ChatRequest, AIInvokeRequest, AIRequest, GatewayActionType, GatewayInvokeRejectionMetadata, GatewayFallbackAttempt, GatewayTraceRequestIds, GatewayTraceAttempt, GatewayTraceUsageSummary, GatewayTraceMergedConfig, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions, SmartInputConfig, SmartInputRenderOptions } from './types.js';
22
+ export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, tryExtractFallbackAttemptsFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, catalogPricingSucceeded, extractUsageExtrasFromRouterResponse, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, pickEnhancedOpenRouterMetadata, hasNonZeroTokenUsage, MODEL_PROFILE_UNROUTABLE, ModelProfileUnroutableError, ModelProfileInputRejectedError, buildGatewayFallbackAttemptsFromTrace, formatFallbackExhaustionMessage, logResolvedModelRouting, mapGatewayFallbackAttemptsToRouter } from './gateway-utils.js';
22
23
  export { getGatewayOperationalMode, isProdGatewayMode, parseModelProviderSpec } from './gateway-mode.js';
23
24
  export type { GatewayOperationalMode } from './gateway-mode.js';
24
25
  export { DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, GATEWAY_DEFAULT_FREQUENCY_PENALTY, GATEWAY_DEFAULT_PRESENCE_PENALTY, GATEWAY_DEFAULT_RETRY, GATEWAY_DEFAULT_TEMPERATURE, GATEWAY_DEFAULT_TOP_P, resolveRetryConfig } from './gateway-defaults.js';
package/dist/index.js CHANGED
@@ -15,10 +15,11 @@ export { ProviderNotFoundError, FallbackExhaustedError } from '@x12i/ai-provider
15
15
  export * from '@x12i/ai-providers-router';
16
16
  // Export enhanced gateway
17
17
  export { AIGateway } from './gateway.js';
18
- export { InstructionNotFoundError, InstructionBackendError, ModelRequiredError, MaxTokensRequiredError, GatewayAliasModelRejectedError, } from './instruction-errors.js';
18
+ export { InstructionNotFoundError, InstructionBackendError, ModelRequiredError, MaxTokensRequiredError, GatewayAliasModelRejectedError, GatewayValidationError, ProviderConfigError, ProviderInvokeError, GatewayPolicyViolationError, } from './instruction-errors.js';
19
19
  export { normalizeInvokeModelAtIngress } from './invoke-model-ingress.js';
20
20
  export { autoRegisterProviders } from './gateway-provider-auto-register.js';
21
- export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, tryExtractFallbackAttemptsFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, catalogPricingSucceeded, extractUsageExtrasFromRouterResponse, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, hasNonZeroTokenUsage, MODEL_PROFILE_UNROUTABLE, ModelProfileUnroutableError, ModelProfileInputRejectedError, buildGatewayFallbackAttemptsFromTrace, formatFallbackExhaustionMessage, logResolvedModelRouting, mapGatewayFallbackAttemptsToRouter } from './gateway-utils.js';
21
+ export { registerOpenRouterRuntime, shouldUseOpenRouterRuntime, mapGatewayServerTools, } from './openrouter-runtime-adapter/index.js';
22
+ export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, tryExtractFallbackAttemptsFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, resolveCostCompletionWithAiTools, buildGatewayPricingRecord, mapAiCostResultToResolvedActivityCost, catalogPricingSucceeded, extractUsageExtrasFromRouterResponse, buildTraceUsageSummary, enrichTraceAttemptsWithBilling, pickEnhancedOpenRouterMetadata, hasNonZeroTokenUsage, MODEL_PROFILE_UNROUTABLE, ModelProfileUnroutableError, ModelProfileInputRejectedError, buildGatewayFallbackAttemptsFromTrace, formatFallbackExhaustionMessage, logResolvedModelRouting, mapGatewayFallbackAttemptsToRouter } from './gateway-utils.js';
22
23
  export { getGatewayOperationalMode, isProdGatewayMode, parseModelProviderSpec } from './gateway-mode.js';
23
24
  export { DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, GATEWAY_DEFAULT_FREQUENCY_PENALTY, GATEWAY_DEFAULT_PRESENCE_PENALTY, GATEWAY_DEFAULT_RETRY, GATEWAY_DEFAULT_TEMPERATURE, GATEWAY_DEFAULT_TOP_P, resolveRetryConfig } from './gateway-defaults.js';
24
25
  export { contractSpecToFieldKeys, enrichParsedContentForOutputContract, resolveOutputContractFieldKeys } from './output-contract-normalizer.js';
@@ -14,6 +14,27 @@ export declare class GatewayAliasModelRejectedError extends Error {
14
14
  readonly code = "GATEWAY_ALIAS_MODEL_REJECTED";
15
15
  constructor(aliasModel: string, message?: string);
16
16
  }
17
+ export declare class GatewayValidationError extends Error {
18
+ readonly code: string;
19
+ constructor(code: string, message: string);
20
+ }
21
+ export declare class ProviderConfigError extends Error {
22
+ readonly code: string;
23
+ constructor(code: string, message: string);
24
+ }
25
+ export declare class ProviderInvokeError extends Error {
26
+ readonly code: string;
27
+ readonly status?: number;
28
+ readonly retryable?: boolean;
29
+ constructor(code: string, message: string, options?: {
30
+ status?: number;
31
+ retryable?: boolean;
32
+ });
33
+ }
34
+ export declare class GatewayPolicyViolationError extends Error {
35
+ readonly code: string;
36
+ constructor(code: string, message: string);
37
+ }
17
38
  export declare class InstructionNotFoundError extends Error {
18
39
  key: string;
19
40
  backend: string;
@@ -25,6 +25,42 @@ export class GatewayAliasModelRejectedError extends Error {
25
25
  this.name = 'GatewayAliasModelRejectedError';
26
26
  }
27
27
  }
28
+ export class GatewayValidationError extends Error {
29
+ code;
30
+ constructor(code, message) {
31
+ super(message);
32
+ this.name = 'GatewayValidationError';
33
+ this.code = code;
34
+ }
35
+ }
36
+ export class ProviderConfigError extends Error {
37
+ code;
38
+ constructor(code, message) {
39
+ super(message);
40
+ this.name = 'ProviderConfigError';
41
+ this.code = code;
42
+ }
43
+ }
44
+ export class ProviderInvokeError extends Error {
45
+ code;
46
+ status;
47
+ retryable;
48
+ constructor(code, message, options) {
49
+ super(message);
50
+ this.name = 'ProviderInvokeError';
51
+ this.code = code;
52
+ this.status = options?.status;
53
+ this.retryable = options?.retryable;
54
+ }
55
+ }
56
+ export class GatewayPolicyViolationError extends Error {
57
+ code;
58
+ constructor(code, message) {
59
+ super(message);
60
+ this.name = 'GatewayPolicyViolationError';
61
+ this.code = code;
62
+ }
63
+ }
28
64
  export class InstructionNotFoundError extends Error {
29
65
  key;
30
66
  backend;
@@ -59,8 +59,6 @@ function inheritLoggingConfigFromHost(hostLogger) {
59
59
  pick('logFilePath');
60
60
  pick('logFormat');
61
61
  pick('showFullTimestamp');
62
- pick('enableUnifiedLogger');
63
- pick('unifiedLogger');
64
62
  pick('consolePackagesShow');
65
63
  pick('consolePackagesHide');
66
64
  pick('transports');
@@ -0,0 +1,23 @@
1
+ import type { ProviderSDKCallSpec } from '@x12i/ai-provider-interface';
2
+ import type { AIResponse } from '@x12i/ai-providers-router';
3
+ export declare function createOpenRouterRuntimeAdapter(): {
4
+ provider: string;
5
+ buildCallSpec(input: {
6
+ requestId: string;
7
+ mode: "sync" | "stream";
8
+ request: Record<string, unknown>;
9
+ exec?: {
10
+ timeoutMs?: number;
11
+ retries?: number;
12
+ idempotencyKey?: string;
13
+ signal?: AbortSignal;
14
+ };
15
+ }): ProviderSDKCallSpec;
16
+ parseResponse(input: {
17
+ requestId: string;
18
+ request: unknown;
19
+ execResult: {
20
+ rawResponse: unknown;
21
+ };
22
+ }): AIResponse;
23
+ };
@@ -0,0 +1,31 @@
1
+ import { mapGatewayRequestToRuntimeRequestFromRouter, OPENROUTER_RUNTIME_OPERATION, } from './map-gateway-request.js';
2
+ import { parseRuntimeResponseToAIResponse } from './map-runtime-response.js';
3
+ export function createOpenRouterRuntimeAdapter() {
4
+ return {
5
+ provider: 'openrouter',
6
+ buildCallSpec(input) {
7
+ const runtimeRequest = mapGatewayRequestToRuntimeRequestFromRouter({
8
+ request: input.request,
9
+ exec: input.exec,
10
+ });
11
+ return {
12
+ requestId: input.requestId,
13
+ provider: 'openrouter',
14
+ mode: input.mode === 'stream' ? 'sync' : input.mode,
15
+ operation: OPENROUTER_RUNTIME_OPERATION,
16
+ args: runtimeRequest,
17
+ exec: input.exec
18
+ ? {
19
+ timeoutMs: input.exec.timeoutMs,
20
+ retries: input.exec.retries,
21
+ idempotencyKey: input.exec.idempotencyKey,
22
+ signal: input.exec.signal,
23
+ }
24
+ : undefined,
25
+ };
26
+ },
27
+ parseResponse(input) {
28
+ return parseRuntimeResponseToAIResponse(input);
29
+ },
30
+ };
31
+ }
@@ -0,0 +1,9 @@
1
+ import { type OpenRouterRuntime } from '@x12i/openrouter-runtime';
2
+ import type { ProviderModule } from '@x12i/ai-provider-interface';
3
+ import type { Logxer } from '@x12i/logxer';
4
+ export type OpenRouterRuntimeProviderOptions = {
5
+ apiKey: string;
6
+ logger?: Logxer;
7
+ runtime?: OpenRouterRuntime;
8
+ };
9
+ export declare function createOpenRouterRuntimeProvider(options: OpenRouterRuntimeProviderOptions): ProviderModule;
@@ -0,0 +1,41 @@
1
+ import { createOpenRouterRuntime, RuntimeConfigError, OpenRouterHttpError, } from '@x12i/openrouter-runtime';
2
+ import { OPENROUTER_RUNTIME_OPERATION } from './map-gateway-request.js';
3
+ import { throwMappedOpenRouterHttpError, throwMappedRuntimeConfigError, throwMappedRuntimeResponseErrors, } from './map-runtime-errors.js';
4
+ export function createOpenRouterRuntimeProvider(options) {
5
+ const runtime = options.runtime ??
6
+ createOpenRouterRuntime({
7
+ apiKey: options.apiKey,
8
+ defaults: {
9
+ retry: { enabled: false },
10
+ onPolicyViolation: 'return_error',
11
+ },
12
+ });
13
+ return {
14
+ name: 'openrouter',
15
+ capabilities: {
16
+ modes: { sync: true, stream: false, batch: false },
17
+ operations: [OPENROUTER_RUNTIME_OPERATION],
18
+ maxConcurrency: 10,
19
+ },
20
+ async execute(spec) {
21
+ if (spec.operation !== OPENROUTER_RUNTIME_OPERATION) {
22
+ throw new Error(`Unsupported OpenRouter runtime operation: ${spec.operation}`);
23
+ }
24
+ const runtimeRequest = spec.args;
25
+ try {
26
+ const response = await runtime.run(runtimeRequest);
27
+ if (response.status !== 'completed') {
28
+ throwMappedRuntimeResponseErrors(response);
29
+ }
30
+ return { rawResponse: response };
31
+ }
32
+ catch (err) {
33
+ if (err instanceof RuntimeConfigError)
34
+ throwMappedRuntimeConfigError(err);
35
+ if (err instanceof OpenRouterHttpError)
36
+ throwMappedOpenRouterHttpError(err);
37
+ throw err;
38
+ }
39
+ },
40
+ };
41
+ }
@@ -0,0 +1,8 @@
1
+ export { registerOpenRouterRuntime, shouldUseOpenRouterRuntime, resolveOpenRouterRuntimeDefaults, } from './register-openrouter-runtime.js';
2
+ export { createOpenRouterRuntimeProvider } from './create-openrouter-runtime-provider.js';
3
+ export { createOpenRouterRuntimeAdapter } from './create-openrouter-runtime-adapter.js';
4
+ export { mapGatewayServerTools } from './map-server-tools.js';
5
+ export { mapGatewayRequestToRuntimeRequest } from './map-gateway-request.js';
6
+ export { pickOpenRouterRuntimeMetadataSlice, extractOpenRouterRuntimeRouterMetadata, parseRuntimeResponseToAIResponse, } from './map-runtime-response.js';
7
+ export { applyOnlineVariantMigration, applyPostRoutingServerToolsPolicy, mergeOpenRouterConfig, mergeServerToolsConfig, normalizeApplyPatchDefaults, validateApplyPatchConfig, hasAnyActiveServerTool, hasRequiredServerTool, } from './validate-server-tools.js';
8
+ export { buildTraceAttemptOpenRouterRuntimeSlice, enrichTraceOpenRouterRuntimeMetadata, redactRawOpenRouterPayload, } from './map-trace.js';
@@ -0,0 +1,8 @@
1
+ export { registerOpenRouterRuntime, shouldUseOpenRouterRuntime, resolveOpenRouterRuntimeDefaults, } from './register-openrouter-runtime.js';
2
+ export { createOpenRouterRuntimeProvider } from './create-openrouter-runtime-provider.js';
3
+ export { createOpenRouterRuntimeAdapter } from './create-openrouter-runtime-adapter.js';
4
+ export { mapGatewayServerTools } from './map-server-tools.js';
5
+ export { mapGatewayRequestToRuntimeRequest } from './map-gateway-request.js';
6
+ export { pickOpenRouterRuntimeMetadataSlice, extractOpenRouterRuntimeRouterMetadata, parseRuntimeResponseToAIResponse, } from './map-runtime-response.js';
7
+ export { applyOnlineVariantMigration, applyPostRoutingServerToolsPolicy, mergeOpenRouterConfig, mergeServerToolsConfig, normalizeApplyPatchDefaults, validateApplyPatchConfig, hasAnyActiveServerTool, hasRequiredServerTool, } from './validate-server-tools.js';
8
+ export { buildTraceAttemptOpenRouterRuntimeSlice, enrichTraceOpenRouterRuntimeMetadata, redactRawOpenRouterPayload, } from './map-trace.js';