@x12i/ai-gateway 9.1.6 → 9.2.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.
- package/dist/activity-manager.d.ts +1 -0
- package/dist/activity-manager.js +7 -0
- package/dist/flex-md-loader.d.ts +5 -0
- package/dist/flex-md-loader.js +16 -0
- package/dist/gateway-utils.d.ts +29 -0
- package/dist/gateway-utils.js +44 -0
- package/dist/gateway.js +21 -13
- package/dist/index.d.ts +4 -1
- package/dist/index.js +2 -1
- package/dist/output-contract-normalizer.d.ts +21 -0
- package/dist/output-contract-normalizer.js +121 -0
- package/dist/types.d.ts +14 -0
- package/dist-cjs/activity-manager.cjs +7 -0
- package/dist-cjs/activity-manager.d.ts +1 -0
- package/dist-cjs/flex-md-loader.cjs +17 -0
- package/dist-cjs/flex-md-loader.d.ts +5 -0
- package/dist-cjs/gateway-utils.cjs +47 -0
- package/dist-cjs/gateway-utils.d.ts +29 -0
- package/dist-cjs/gateway.cjs +20 -12
- package/dist-cjs/index.cjs +9 -2
- package/dist-cjs/index.d.ts +4 -1
- package/dist-cjs/output-contract-normalizer.cjs +126 -0
- package/dist-cjs/output-contract-normalizer.d.ts +21 -0
- package/dist-cjs/types.d.ts +14 -0
- package/package.json +2 -2
package/dist/activity-manager.js
CHANGED
|
@@ -155,6 +155,12 @@ function pickActivixCompletionRoutingMetadata(response) {
|
|
|
155
155
|
if (m.effectiveModelConfig != null && typeof m.effectiveModelConfig === 'object') {
|
|
156
156
|
out.effectiveModelConfig = m.effectiveModelConfig;
|
|
157
157
|
}
|
|
158
|
+
if (typeof m.cost === 'number' && Number.isFinite(m.cost))
|
|
159
|
+
out.cost = m.cost;
|
|
160
|
+
if (typeof m.costUsd === 'number' && Number.isFinite(m.costUsd))
|
|
161
|
+
out.costUsd = m.costUsd;
|
|
162
|
+
if (m.costStatus === 'priced' || m.costStatus === 'unpriced')
|
|
163
|
+
out.costStatus = m.costStatus;
|
|
158
164
|
return out;
|
|
159
165
|
}
|
|
160
166
|
function mergeGatewayActivityIdentity(request, aiRequestId, extras) {
|
|
@@ -844,6 +850,7 @@ export class ActivityManager {
|
|
|
844
850
|
}
|
|
845
851
|
await this.activix.completeRecord(activity.activityId, {
|
|
846
852
|
cost: details.cost,
|
|
853
|
+
...(details.costStatus ? { costStatus: details.costStatus } : {}),
|
|
847
854
|
response: details.response,
|
|
848
855
|
outer: {
|
|
849
856
|
output: details.response,
|
package/dist/flex-md-loader.d.ts
CHANGED
|
@@ -43,6 +43,11 @@ export declare function extractJsonFromFlexMd(content: string, logger?: Logxer):
|
|
|
43
43
|
json: any;
|
|
44
44
|
method: string;
|
|
45
45
|
} | null>;
|
|
46
|
+
/**
|
|
47
|
+
* Section-based markdown → camelCase field map (e.g. `### Short Answer` → `shortAnswer`).
|
|
48
|
+
* Used when output contracts need structured `parsed` fields but flex-md returned only `rawText`.
|
|
49
|
+
*/
|
|
50
|
+
export declare function parseMarkdownSectionsFromContent(content: string, logger?: Logxer): Record<string, unknown>;
|
|
46
51
|
/**
|
|
47
52
|
* Check if flex-md module is available
|
|
48
53
|
*/
|
package/dist/flex-md-loader.js
CHANGED
|
@@ -503,6 +503,22 @@ function fallbackMarkdownParser(content, logger) {
|
|
|
503
503
|
method: 'fallback-raw-text'
|
|
504
504
|
};
|
|
505
505
|
}
|
|
506
|
+
/**
|
|
507
|
+
* Section-based markdown → camelCase field map (e.g. `### Short Answer` → `shortAnswer`).
|
|
508
|
+
* Used when output contracts need structured `parsed` fields but flex-md returned only `rawText`.
|
|
509
|
+
*/
|
|
510
|
+
export function parseMarkdownSectionsFromContent(content, logger) {
|
|
511
|
+
const parsed = fallbackMarkdownParser(content, logger);
|
|
512
|
+
const json = parsed.json;
|
|
513
|
+
if (json != null && typeof json === 'object' && !Array.isArray(json)) {
|
|
514
|
+
const keys = Object.keys(json);
|
|
515
|
+
if (keys.length === 1 && keys[0] === 'rawText') {
|
|
516
|
+
return {};
|
|
517
|
+
}
|
|
518
|
+
return json;
|
|
519
|
+
}
|
|
520
|
+
return {};
|
|
521
|
+
}
|
|
506
522
|
/**
|
|
507
523
|
* Fallback JSON extraction when flex-md is not available
|
|
508
524
|
*/
|
package/dist/gateway-utils.d.ts
CHANGED
|
@@ -43,6 +43,35 @@ export declare function extractTokenUsageFromRouterResponse(routerResponse: unkn
|
|
|
43
43
|
* Does not compute cost from tokens — adapters must populate normalized fields or raw usage.cost-style keys.
|
|
44
44
|
*/
|
|
45
45
|
export declare function extractCostUsdFromRouterResponse(routerResponse: unknown): number | undefined;
|
|
46
|
+
/** Activity billing state when token usage is recorded (Run Analysis G8). */
|
|
47
|
+
export type ActivityCostStatus = 'priced' | 'unpriced';
|
|
48
|
+
export type ResolvedActivityCost = {
|
|
49
|
+
cost?: number;
|
|
50
|
+
costStatus?: ActivityCostStatus;
|
|
51
|
+
};
|
|
52
|
+
export declare function hasNonZeroTokenUsage(tokens: {
|
|
53
|
+
prompt: number;
|
|
54
|
+
completion: number;
|
|
55
|
+
total: number;
|
|
56
|
+
}): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Gateway fallback when the router does not set `metadata.costStatus`.
|
|
59
|
+
* Prefer {@link resolveCostCompletionForActivity} at invoke boundaries.
|
|
60
|
+
*/
|
|
61
|
+
export declare function resolveActivityCostCompletion(tokens: {
|
|
62
|
+
prompt: number;
|
|
63
|
+
completion: number;
|
|
64
|
+
total: number;
|
|
65
|
+
}, costUsd: number | undefined): ResolvedActivityCost;
|
|
66
|
+
/**
|
|
67
|
+
* Activity cost slice for Activix: router `metadata.costStatus` / cost wins when present;
|
|
68
|
+
* otherwise gateway applies the G8 fallback (usage + no price → `unpriced`).
|
|
69
|
+
*/
|
|
70
|
+
export declare function resolveCostCompletionForActivity(routerResponse: unknown, tokens: {
|
|
71
|
+
prompt: number;
|
|
72
|
+
completion: number;
|
|
73
|
+
total: number;
|
|
74
|
+
}): ResolvedActivityCost;
|
|
46
75
|
/**
|
|
47
76
|
* Stable routing facts for gateway response metadata (router metadata + merged config fallbacks).
|
|
48
77
|
* Matches trace-mode resolution; intended for every successful invoke(), not only diagnostics.trace.
|
package/dist/gateway-utils.js
CHANGED
|
@@ -315,6 +315,50 @@ export function extractCostUsdFromRouterResponse(routerResponse) {
|
|
|
315
315
|
}
|
|
316
316
|
return undefined;
|
|
317
317
|
}
|
|
318
|
+
export function hasNonZeroTokenUsage(tokens) {
|
|
319
|
+
return !!(tokens.prompt || tokens.completion || tokens.total);
|
|
320
|
+
}
|
|
321
|
+
function pickRouterCostStatus(routerResponse) {
|
|
322
|
+
if (routerResponse == null || typeof routerResponse !== 'object')
|
|
323
|
+
return undefined;
|
|
324
|
+
const r = routerResponse;
|
|
325
|
+
const meta = r.metadata != null && typeof r.metadata === 'object'
|
|
326
|
+
? r.metadata
|
|
327
|
+
: undefined;
|
|
328
|
+
const status = meta?.costStatus ?? r.costStatus;
|
|
329
|
+
return status === 'priced' || status === 'unpriced' ? status : undefined;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Gateway fallback when the router does not set `metadata.costStatus`.
|
|
333
|
+
* Prefer {@link resolveCostCompletionForActivity} at invoke boundaries.
|
|
334
|
+
*/
|
|
335
|
+
export function resolveActivityCostCompletion(tokens, costUsd) {
|
|
336
|
+
if (typeof costUsd === 'number' && Number.isFinite(costUsd)) {
|
|
337
|
+
return { cost: costUsd, costStatus: 'priced' };
|
|
338
|
+
}
|
|
339
|
+
if (hasNonZeroTokenUsage(tokens)) {
|
|
340
|
+
return { costStatus: 'unpriced' };
|
|
341
|
+
}
|
|
342
|
+
return {};
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Activity cost slice for Activix: router `metadata.costStatus` / cost wins when present;
|
|
346
|
+
* otherwise gateway applies the G8 fallback (usage + no price → `unpriced`).
|
|
347
|
+
*/
|
|
348
|
+
export function resolveCostCompletionForActivity(routerResponse, tokens) {
|
|
349
|
+
const routerStatus = pickRouterCostStatus(routerResponse);
|
|
350
|
+
const costUsd = extractCostUsdFromRouterResponse(routerResponse);
|
|
351
|
+
if (routerStatus === 'priced') {
|
|
352
|
+
return {
|
|
353
|
+
...(typeof costUsd === 'number' && Number.isFinite(costUsd) ? { cost: costUsd } : {}),
|
|
354
|
+
costStatus: 'priced'
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
if (routerStatus === 'unpriced') {
|
|
358
|
+
return { costStatus: 'unpriced' };
|
|
359
|
+
}
|
|
360
|
+
return resolveActivityCostCompletion(tokens, costUsd);
|
|
361
|
+
}
|
|
318
362
|
/**
|
|
319
363
|
* Stable routing facts for gateway response metadata (router metadata + merged config fallbacks).
|
|
320
364
|
* Matches trace-mode resolution; intended for every successful invoke(), not only diagnostics.trace.
|
package/dist/gateway.js
CHANGED
|
@@ -8,7 +8,8 @@ import { ensureGatewayRequestIdentity } from './activity-manager.js';
|
|
|
8
8
|
import { initializeGatewayComponents } from './gateway-config.js';
|
|
9
9
|
import { buildMessages } from './message-builder.js';
|
|
10
10
|
import { extractJsonFromFlexMd } from './flex-md-loader.js';
|
|
11
|
-
import {
|
|
11
|
+
import { enrichParsedContentForOutputContract, resolveOutputContractFieldKeys } from './output-contract-normalizer.js';
|
|
12
|
+
import { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, capActivityFullResponsePayload, DEFAULT_ACTIVITY_FULL_RESPONSE_MAX_CHARS, extractCostUsdFromRouterResponse, extractTokenUsageFromRouterResponse, mergeConfig, pickEffectiveModelConfigForMetadata, pickInvokeRoutingMetadataSlice, pickTraceMergedRouterConfig, resolveCostCompletionForActivity, tryExtractRouterLikePayloadFromErrorChain } from './gateway-utils.js';
|
|
12
13
|
import { autoRegisterProviders } from './gateway-provider-auto-register.js';
|
|
13
14
|
import { setGatewayLastJobId, setGatewayRuntimeClients } from './runtime-objects.js';
|
|
14
15
|
import { gatewayLogDebug, withActivityIdentity } from './gateway-log-meta.js';
|
|
@@ -108,8 +109,9 @@ export class AIGateway {
|
|
|
108
109
|
},
|
|
109
110
|
mode: 'sync'
|
|
110
111
|
});
|
|
111
|
-
const costUsdChat = extractCostUsdFromRouterResponse(response);
|
|
112
112
|
const metaChat = response?.metadata || {};
|
|
113
|
+
const tokensChat = extractTokenUsageFromRouterResponse(response);
|
|
114
|
+
const costCompletionChat = resolveCostCompletionForActivity(response, tokensChat);
|
|
113
115
|
// Create enhanced response
|
|
114
116
|
const enhancedResponse = {
|
|
115
117
|
content: response.content || '',
|
|
@@ -117,22 +119,25 @@ export class AIGateway {
|
|
|
117
119
|
aiRequestId: request.aiRequestId,
|
|
118
120
|
identity: request.identity,
|
|
119
121
|
latencyMs: Date.now() - startTime,
|
|
120
|
-
tokens:
|
|
122
|
+
tokens: tokensChat,
|
|
121
123
|
taskTypeId,
|
|
122
124
|
agentType: 'chat',
|
|
123
|
-
...(
|
|
125
|
+
...(costCompletionChat.costStatus === 'priced'
|
|
124
126
|
? {
|
|
125
|
-
costUsd:
|
|
126
|
-
...(typeof metaChat.cost === 'number'
|
|
127
|
+
costUsd: costCompletionChat.cost,
|
|
128
|
+
...(typeof metaChat.cost === 'number'
|
|
129
|
+
? { cost: metaChat.cost }
|
|
130
|
+
: { cost: costCompletionChat.cost })
|
|
127
131
|
}
|
|
128
|
-
: {})
|
|
132
|
+
: {}),
|
|
133
|
+
...(costCompletionChat.costStatus ? { costStatus: costCompletionChat.costStatus } : {})
|
|
129
134
|
}
|
|
130
135
|
};
|
|
131
136
|
// Track activity success if activity was started
|
|
132
137
|
if (activity) {
|
|
133
138
|
try {
|
|
134
139
|
await this.activityManager.logSuccess(activity, {
|
|
135
|
-
...
|
|
140
|
+
...costCompletionChat,
|
|
136
141
|
response: enhancedResponse,
|
|
137
142
|
endTime: Date.now(),
|
|
138
143
|
duration: Date.now() - startTime
|
|
@@ -523,6 +528,8 @@ export class AIGateway {
|
|
|
523
528
|
}
|
|
524
529
|
contentType = 'structured';
|
|
525
530
|
parsingMethod = 'flex-md';
|
|
531
|
+
const outputContractKeys = resolveOutputContractFieldKeys(request);
|
|
532
|
+
parsedContent = await enrichParsedContentForOutputContract(parsedContent, content, outputContractKeys, this.logger);
|
|
526
533
|
let tokens = extractTokenUsageFromRouterResponse(routerResponse);
|
|
527
534
|
if (!(tokens.prompt || tokens.completion || tokens.total)) {
|
|
528
535
|
const alt = routerResponse?.rawResponse ?? routerResponse?.raw;
|
|
@@ -532,7 +539,7 @@ export class AIGateway {
|
|
|
532
539
|
tokens = second;
|
|
533
540
|
}
|
|
534
541
|
}
|
|
535
|
-
const
|
|
542
|
+
const costCompletion = resolveCostCompletionForActivity(routerResponse, tokens);
|
|
536
543
|
const routerMetaForCost = routerResponse?.metadata || {};
|
|
537
544
|
const routingMetadataSlice = pickInvokeRoutingMetadataSlice(routerResponse, mergedConfig);
|
|
538
545
|
const effectiveModelConfig = pickEffectiveModelConfigForMetadata(mergedConfig);
|
|
@@ -551,14 +558,15 @@ export class AIGateway {
|
|
|
551
558
|
parsingMethod,
|
|
552
559
|
...routingMetadataSlice,
|
|
553
560
|
...(effectiveModelConfig !== undefined ? { effectiveModelConfig } : {}),
|
|
554
|
-
...(
|
|
561
|
+
...(costCompletion.costStatus === 'priced'
|
|
555
562
|
? {
|
|
556
|
-
costUsd:
|
|
563
|
+
costUsd: costCompletion.cost,
|
|
557
564
|
...(typeof routerMetaForCost.cost === 'number'
|
|
558
565
|
? { cost: routerMetaForCost.cost }
|
|
559
|
-
: { cost:
|
|
566
|
+
: { cost: costCompletion.cost })
|
|
560
567
|
}
|
|
561
568
|
: {}),
|
|
569
|
+
...(costCompletion.costStatus ? { costStatus: costCompletion.costStatus } : {}),
|
|
562
570
|
...(traceEnabled
|
|
563
571
|
? {
|
|
564
572
|
requestIds: traceRequestIds,
|
|
@@ -597,7 +605,7 @@ export class AIGateway {
|
|
|
597
605
|
usage: tokens
|
|
598
606
|
};
|
|
599
607
|
await this.activityManager.logSuccess(activity, {
|
|
600
|
-
...
|
|
608
|
+
...costCompletion,
|
|
601
609
|
response: activityResponse,
|
|
602
610
|
endTime: Date.now(),
|
|
603
611
|
duration: Date.now() - startTime
|
package/dist/index.d.ts
CHANGED
|
@@ -17,7 +17,10 @@ export { AIGateway } from './gateway.js';
|
|
|
17
17
|
export { InstructionNotFoundError, InstructionBackendError } from './instruction-errors.js';
|
|
18
18
|
export { autoRegisterProviders } from './gateway-provider-auto-register.js';
|
|
19
19
|
export type { GatewayConfig, ProviderModelRef, ModelConfig, RetryConfig, ChatRequest, AIInvokeRequest, AIRequest, GatewayActionType, GatewayInvokeRejectionMetadata, GatewayTraceRequestIds, GatewayTraceMergedConfig, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions, SmartInputConfig, SmartInputRenderOptions } from './types.js';
|
|
20
|
-
export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, pickRequestIdsFromRouterLike } from './gateway-utils.js';
|
|
20
|
+
export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, hasNonZeroTokenUsage } from './gateway-utils.js';
|
|
21
|
+
export type { ActivityCostStatus, ResolvedActivityCost } from './gateway-utils.js';
|
|
22
|
+
export { contractSpecToFieldKeys, enrichParsedContentForOutputContract, resolveOutputContractFieldKeys } from './output-contract-normalizer.js';
|
|
23
|
+
export type { OutputContractSpec } from './output-contract-normalizer.js';
|
|
21
24
|
export { mergeGatewayAndRequestTemplateRenderOptions, mergeTemplateRenderOptions } from './template-render-merge.js';
|
|
22
25
|
export type { GatewayTemplateRenderRequestSlice } from './template-render-merge.js';
|
|
23
26
|
export { GATEWAY_DUAL_MEMORY_ROOTS, buildMemoryResolutionRootFromWorkingMemory, coalesceMergedInputBucket, extractCallerInputsBag, mapSmartInputPathsInputsToInput, parseLooseJsonObject, prepareWorkingMemoryForTemplateRender, resolveGatewayMemoryPathValue } from './memory-path-resolution.js';
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,8 @@ export * from '@x12i/ai-providers-router';
|
|
|
17
17
|
export { AIGateway } from './gateway.js';
|
|
18
18
|
export { InstructionNotFoundError, InstructionBackendError } from './instruction-errors.js';
|
|
19
19
|
export { autoRegisterProviders } from './gateway-provider-auto-register.js';
|
|
20
|
-
export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, pickRequestIdsFromRouterLike } from './gateway-utils.js';
|
|
20
|
+
export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, hasNonZeroTokenUsage } from './gateway-utils.js';
|
|
21
|
+
export { contractSpecToFieldKeys, enrichParsedContentForOutputContract, resolveOutputContractFieldKeys } from './output-contract-normalizer.js';
|
|
21
22
|
export { mergeGatewayAndRequestTemplateRenderOptions, mergeTemplateRenderOptions } from './template-render-merge.js';
|
|
22
23
|
export { GATEWAY_DUAL_MEMORY_ROOTS, buildMemoryResolutionRootFromWorkingMemory, coalesceMergedInputBucket, extractCallerInputsBag, mapSmartInputPathsInputsToInput, parseLooseJsonObject, prepareWorkingMemoryForTemplateRender, resolveGatewayMemoryPathValue } from './memory-path-resolution.js';
|
|
23
24
|
// Usage tracking: UsageTracker class methods are available but consumption calculation is disabled
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalizes invoke responses into `output.parsed` when an explicit output contract is forwarded.
|
|
3
|
+
* Does not infer contracts from other request fields — that is upstream (graph-engine / ai-tasks).
|
|
4
|
+
*/
|
|
5
|
+
import type { Logxer } from '@x12i/logxer';
|
|
6
|
+
/** Explicit contract: field names or JSON-schema-style `properties` only. */
|
|
7
|
+
export type OutputContractSpec = string[] | {
|
|
8
|
+
properties: Record<string, unknown>;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Maps an explicit contract spec to field keys. No inference from descriptions or other request fields.
|
|
12
|
+
*/
|
|
13
|
+
export declare function contractSpecToFieldKeys(contract: unknown): string[];
|
|
14
|
+
/**
|
|
15
|
+
* Resolves field keys only from explicit `outputContract` on the request or graph-forwarded inputs.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveOutputContractFieldKeys(request: unknown): string[] | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Fills missing contract keys from markdown sections after flex-md. Only runs when explicit contract keys were supplied.
|
|
20
|
+
*/
|
|
21
|
+
export declare function enrichParsedContentForOutputContract(parsed: unknown, rawContent: string, contractKeys: string[] | undefined, logger?: Logxer): Promise<Record<string, unknown>>;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalizes invoke responses into `output.parsed` when an explicit output contract is forwarded.
|
|
3
|
+
* Does not infer contracts from other request fields — that is upstream (graph-engine / ai-tasks).
|
|
4
|
+
*/
|
|
5
|
+
import { parseMarkdownSectionsFromContent } from './flex-md-loader.js';
|
|
6
|
+
import { coalesceMergedInputBucket, extractCallerInputsBag, parseLooseJsonObject } from './memory-path-resolution.js';
|
|
7
|
+
function isPlainObject(value) {
|
|
8
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
9
|
+
}
|
|
10
|
+
function hasMeaningfulContractValue(value) {
|
|
11
|
+
if (value === undefined || value === null)
|
|
12
|
+
return false;
|
|
13
|
+
if (typeof value === 'string')
|
|
14
|
+
return value.trim().length > 0;
|
|
15
|
+
if (Array.isArray(value))
|
|
16
|
+
return value.length > 0;
|
|
17
|
+
if (typeof value === 'object')
|
|
18
|
+
return Object.keys(value).length > 0;
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Maps an explicit contract spec to field keys. No inference from descriptions or other request fields.
|
|
23
|
+
*/
|
|
24
|
+
export function contractSpecToFieldKeys(contract) {
|
|
25
|
+
if (contract == null)
|
|
26
|
+
return [];
|
|
27
|
+
if (Array.isArray(contract)) {
|
|
28
|
+
return contract.filter((k) => typeof k === 'string' && k.trim().length > 0);
|
|
29
|
+
}
|
|
30
|
+
if (!isPlainObject(contract))
|
|
31
|
+
return [];
|
|
32
|
+
const properties = contract.properties;
|
|
33
|
+
if (isPlainObject(properties)) {
|
|
34
|
+
return Object.keys(properties);
|
|
35
|
+
}
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
/** Graph-engine path: `workingMemory.inputs.outputContract` or merged `input.outputContract`. */
|
|
39
|
+
function readExplicitOutputContractFromWorkingMemory(workingMemory) {
|
|
40
|
+
if (!isPlainObject(workingMemory))
|
|
41
|
+
return undefined;
|
|
42
|
+
const inputs = extractCallerInputsBag(workingMemory);
|
|
43
|
+
if (inputs?.outputContract !== undefined)
|
|
44
|
+
return inputs.outputContract;
|
|
45
|
+
const input = coalesceMergedInputBucket(workingMemory);
|
|
46
|
+
if (isPlainObject(input) && input.outputContract !== undefined) {
|
|
47
|
+
return input.outputContract;
|
|
48
|
+
}
|
|
49
|
+
const loose = parseLooseJsonObject(workingMemory.input);
|
|
50
|
+
if (loose?.outputContract !== undefined)
|
|
51
|
+
return loose.outputContract;
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Resolves field keys only from explicit `outputContract` on the request or graph-forwarded inputs.
|
|
56
|
+
*/
|
|
57
|
+
export function resolveOutputContractFieldKeys(request) {
|
|
58
|
+
if (request == null || typeof request !== 'object')
|
|
59
|
+
return undefined;
|
|
60
|
+
const r = request;
|
|
61
|
+
const candidates = [
|
|
62
|
+
r.outputContract,
|
|
63
|
+
readExplicitOutputContractFromWorkingMemory(r.workingMemory)
|
|
64
|
+
];
|
|
65
|
+
for (const candidate of candidates) {
|
|
66
|
+
const keys = contractSpecToFieldKeys(candidate);
|
|
67
|
+
if (keys.length > 0)
|
|
68
|
+
return keys;
|
|
69
|
+
}
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
function asParsedRecord(parsed) {
|
|
73
|
+
if (!isPlainObject(parsed))
|
|
74
|
+
return {};
|
|
75
|
+
return { ...parsed };
|
|
76
|
+
}
|
|
77
|
+
function pickAliasValue(source, key) {
|
|
78
|
+
if (hasMeaningfulContractValue(source[key]))
|
|
79
|
+
return source[key];
|
|
80
|
+
const spaced = key.replace(/([A-Z])/g, ' $1').replace(/^./, (c) => c.toUpperCase());
|
|
81
|
+
const title = spaced.charAt(0).toUpperCase() + spaced.slice(1);
|
|
82
|
+
const aliases = [
|
|
83
|
+
key,
|
|
84
|
+
key.toLowerCase(),
|
|
85
|
+
title,
|
|
86
|
+
title.replace(/\s+/g, ' '),
|
|
87
|
+
key.replace(/([A-Z])/g, '_$1').toLowerCase()
|
|
88
|
+
];
|
|
89
|
+
for (const alias of aliases) {
|
|
90
|
+
if (hasMeaningfulContractValue(source[alias]))
|
|
91
|
+
return source[alias];
|
|
92
|
+
}
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Fills missing contract keys from markdown sections after flex-md. Only runs when explicit contract keys were supplied.
|
|
97
|
+
*/
|
|
98
|
+
export async function enrichParsedContentForOutputContract(parsed, rawContent, contractKeys, logger) {
|
|
99
|
+
const base = asParsedRecord(parsed);
|
|
100
|
+
if (!contractKeys?.length)
|
|
101
|
+
return base;
|
|
102
|
+
const missing = contractKeys.filter((k) => !hasMeaningfulContractValue(base[k]));
|
|
103
|
+
if (missing.length === 0)
|
|
104
|
+
return base;
|
|
105
|
+
const content = typeof rawContent === 'string' && rawContent.trim().length > 0
|
|
106
|
+
? rawContent
|
|
107
|
+
: typeof base.rawText === 'string'
|
|
108
|
+
? base.rawText
|
|
109
|
+
: '';
|
|
110
|
+
if (!content.trim())
|
|
111
|
+
return base;
|
|
112
|
+
const fromMarkdown = parseMarkdownSectionsFromContent(content, logger);
|
|
113
|
+
const merged = { ...base };
|
|
114
|
+
for (const key of missing) {
|
|
115
|
+
const value = pickAliasValue(fromMarkdown, key);
|
|
116
|
+
if (hasMeaningfulContractValue(value)) {
|
|
117
|
+
merged[key] = value;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return merged;
|
|
121
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -701,6 +701,14 @@ interface BaseLLMRequest extends Omit<LLMRequest, 'messages' | 'input' | 'reques
|
|
|
701
701
|
* attach heavy diagnostic objects or raw provider payloads.
|
|
702
702
|
*/
|
|
703
703
|
diagnostics?: DiagnosticsOptions;
|
|
704
|
+
/**
|
|
705
|
+
* Explicit output contract from graph-engine / ai-tasks: field names or `{ properties }`.
|
|
706
|
+
* Also accepted on `workingMemory.inputs.outputContract`. When absent, the gateway does not
|
|
707
|
+
* infer contract fields from other request shapes (`expectedSchema`, config, etc.).
|
|
708
|
+
*/
|
|
709
|
+
outputContract?: string[] | {
|
|
710
|
+
properties: Record<string, unknown>;
|
|
711
|
+
};
|
|
704
712
|
}
|
|
705
713
|
/**
|
|
706
714
|
* Chat request for conversational use cases
|
|
@@ -932,6 +940,12 @@ export interface EnhancedLLMResponse<TContent = unknown> extends Omit<AIResponse
|
|
|
932
940
|
* Cost in USD (if available)
|
|
933
941
|
*/
|
|
934
942
|
cost?: number;
|
|
943
|
+
/**
|
|
944
|
+
* Billing state for Run Analysis / Activix when usage is recorded.
|
|
945
|
+
* - `priced`: {@link cost} / {@link costUsd} is a finite number from the router
|
|
946
|
+
* - `unpriced`: usage exists but no price table / adapter cost was returned
|
|
947
|
+
*/
|
|
948
|
+
costStatus?: 'priced' | 'unpriced';
|
|
935
949
|
/**
|
|
936
950
|
* Cost in USD (preferred stable key when the router exposes it).
|
|
937
951
|
* When both are present, costUsd should mirror cost.
|
|
@@ -159,6 +159,12 @@ function pickActivixCompletionRoutingMetadata(response) {
|
|
|
159
159
|
if (m.effectiveModelConfig != null && typeof m.effectiveModelConfig === 'object') {
|
|
160
160
|
out.effectiveModelConfig = m.effectiveModelConfig;
|
|
161
161
|
}
|
|
162
|
+
if (typeof m.cost === 'number' && Number.isFinite(m.cost))
|
|
163
|
+
out.cost = m.cost;
|
|
164
|
+
if (typeof m.costUsd === 'number' && Number.isFinite(m.costUsd))
|
|
165
|
+
out.costUsd = m.costUsd;
|
|
166
|
+
if (m.costStatus === 'priced' || m.costStatus === 'unpriced')
|
|
167
|
+
out.costStatus = m.costStatus;
|
|
162
168
|
return out;
|
|
163
169
|
}
|
|
164
170
|
function mergeGatewayActivityIdentity(request, aiRequestId, extras) {
|
|
@@ -848,6 +854,7 @@ class ActivityManager {
|
|
|
848
854
|
}
|
|
849
855
|
await this.activix.completeRecord(activity.activityId, {
|
|
850
856
|
cost: details.cost,
|
|
857
|
+
...(details.costStatus ? { costStatus: details.costStatus } : {}),
|
|
851
858
|
response: details.response,
|
|
852
859
|
outer: {
|
|
853
860
|
output: details.response,
|
|
@@ -50,6 +50,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
50
50
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
51
51
|
exports.loadFlexMd = loadFlexMd;
|
|
52
52
|
exports.extractJsonFromFlexMd = extractJsonFromFlexMd;
|
|
53
|
+
exports.parseMarkdownSectionsFromContent = parseMarkdownSectionsFromContent;
|
|
53
54
|
exports.isFlexMdAvailable = isFlexMdAvailable;
|
|
54
55
|
exports.getModelMaxTokensFromFlexMd = getModelMaxTokensFromFlexMd;
|
|
55
56
|
exports.getFlexMdModule = getFlexMdModule;
|
|
@@ -549,6 +550,22 @@ function fallbackMarkdownParser(content, logger) {
|
|
|
549
550
|
method: 'fallback-raw-text'
|
|
550
551
|
};
|
|
551
552
|
}
|
|
553
|
+
/**
|
|
554
|
+
* Section-based markdown → camelCase field map (e.g. `### Short Answer` → `shortAnswer`).
|
|
555
|
+
* Used when output contracts need structured `parsed` fields but flex-md returned only `rawText`.
|
|
556
|
+
*/
|
|
557
|
+
function parseMarkdownSectionsFromContent(content, logger) {
|
|
558
|
+
const parsed = fallbackMarkdownParser(content, logger);
|
|
559
|
+
const json = parsed.json;
|
|
560
|
+
if (json != null && typeof json === 'object' && !Array.isArray(json)) {
|
|
561
|
+
const keys = Object.keys(json);
|
|
562
|
+
if (keys.length === 1 && keys[0] === 'rawText') {
|
|
563
|
+
return {};
|
|
564
|
+
}
|
|
565
|
+
return json;
|
|
566
|
+
}
|
|
567
|
+
return {};
|
|
568
|
+
}
|
|
552
569
|
/**
|
|
553
570
|
* Fallback JSON extraction when flex-md is not available
|
|
554
571
|
*/
|
|
@@ -43,6 +43,11 @@ export declare function extractJsonFromFlexMd(content: string, logger?: Logxer):
|
|
|
43
43
|
json: any;
|
|
44
44
|
method: string;
|
|
45
45
|
} | null>;
|
|
46
|
+
/**
|
|
47
|
+
* Section-based markdown → camelCase field map (e.g. `### Short Answer` → `shortAnswer`).
|
|
48
|
+
* Used when output contracts need structured `parsed` fields but flex-md returned only `rawText`.
|
|
49
|
+
*/
|
|
50
|
+
export declare function parseMarkdownSectionsFromContent(content: string, logger?: Logxer): Record<string, unknown>;
|
|
46
51
|
/**
|
|
47
52
|
* Check if flex-md module is available
|
|
48
53
|
*/
|
|
@@ -44,6 +44,9 @@ exports.mergeConfig = mergeConfig;
|
|
|
44
44
|
exports.normalizeRouterUsageTokens = normalizeRouterUsageTokens;
|
|
45
45
|
exports.extractTokenUsageFromRouterResponse = extractTokenUsageFromRouterResponse;
|
|
46
46
|
exports.extractCostUsdFromRouterResponse = extractCostUsdFromRouterResponse;
|
|
47
|
+
exports.hasNonZeroTokenUsage = hasNonZeroTokenUsage;
|
|
48
|
+
exports.resolveActivityCostCompletion = resolveActivityCostCompletion;
|
|
49
|
+
exports.resolveCostCompletionForActivity = resolveCostCompletionForActivity;
|
|
47
50
|
exports.pickInvokeRoutingMetadataSlice = pickInvokeRoutingMetadataSlice;
|
|
48
51
|
exports.pickEffectiveModelConfigForMetadata = pickEffectiveModelConfigForMetadata;
|
|
49
52
|
exports.pickTraceMergedRouterConfig = pickTraceMergedRouterConfig;
|
|
@@ -366,6 +369,50 @@ function extractCostUsdFromRouterResponse(routerResponse) {
|
|
|
366
369
|
}
|
|
367
370
|
return undefined;
|
|
368
371
|
}
|
|
372
|
+
function hasNonZeroTokenUsage(tokens) {
|
|
373
|
+
return !!(tokens.prompt || tokens.completion || tokens.total);
|
|
374
|
+
}
|
|
375
|
+
function pickRouterCostStatus(routerResponse) {
|
|
376
|
+
if (routerResponse == null || typeof routerResponse !== 'object')
|
|
377
|
+
return undefined;
|
|
378
|
+
const r = routerResponse;
|
|
379
|
+
const meta = r.metadata != null && typeof r.metadata === 'object'
|
|
380
|
+
? r.metadata
|
|
381
|
+
: undefined;
|
|
382
|
+
const status = meta?.costStatus ?? r.costStatus;
|
|
383
|
+
return status === 'priced' || status === 'unpriced' ? status : undefined;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Gateway fallback when the router does not set `metadata.costStatus`.
|
|
387
|
+
* Prefer {@link resolveCostCompletionForActivity} at invoke boundaries.
|
|
388
|
+
*/
|
|
389
|
+
function resolveActivityCostCompletion(tokens, costUsd) {
|
|
390
|
+
if (typeof costUsd === 'number' && Number.isFinite(costUsd)) {
|
|
391
|
+
return { cost: costUsd, costStatus: 'priced' };
|
|
392
|
+
}
|
|
393
|
+
if (hasNonZeroTokenUsage(tokens)) {
|
|
394
|
+
return { costStatus: 'unpriced' };
|
|
395
|
+
}
|
|
396
|
+
return {};
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Activity cost slice for Activix: router `metadata.costStatus` / cost wins when present;
|
|
400
|
+
* otherwise gateway applies the G8 fallback (usage + no price → `unpriced`).
|
|
401
|
+
*/
|
|
402
|
+
function resolveCostCompletionForActivity(routerResponse, tokens) {
|
|
403
|
+
const routerStatus = pickRouterCostStatus(routerResponse);
|
|
404
|
+
const costUsd = extractCostUsdFromRouterResponse(routerResponse);
|
|
405
|
+
if (routerStatus === 'priced') {
|
|
406
|
+
return {
|
|
407
|
+
...(typeof costUsd === 'number' && Number.isFinite(costUsd) ? { cost: costUsd } : {}),
|
|
408
|
+
costStatus: 'priced'
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
if (routerStatus === 'unpriced') {
|
|
412
|
+
return { costStatus: 'unpriced' };
|
|
413
|
+
}
|
|
414
|
+
return resolveActivityCostCompletion(tokens, costUsd);
|
|
415
|
+
}
|
|
369
416
|
/**
|
|
370
417
|
* Stable routing facts for gateway response metadata (router metadata + merged config fallbacks).
|
|
371
418
|
* Matches trace-mode resolution; intended for every successful invoke(), not only diagnostics.trace.
|
|
@@ -43,6 +43,35 @@ export declare function extractTokenUsageFromRouterResponse(routerResponse: unkn
|
|
|
43
43
|
* Does not compute cost from tokens — adapters must populate normalized fields or raw usage.cost-style keys.
|
|
44
44
|
*/
|
|
45
45
|
export declare function extractCostUsdFromRouterResponse(routerResponse: unknown): number | undefined;
|
|
46
|
+
/** Activity billing state when token usage is recorded (Run Analysis G8). */
|
|
47
|
+
export type ActivityCostStatus = 'priced' | 'unpriced';
|
|
48
|
+
export type ResolvedActivityCost = {
|
|
49
|
+
cost?: number;
|
|
50
|
+
costStatus?: ActivityCostStatus;
|
|
51
|
+
};
|
|
52
|
+
export declare function hasNonZeroTokenUsage(tokens: {
|
|
53
|
+
prompt: number;
|
|
54
|
+
completion: number;
|
|
55
|
+
total: number;
|
|
56
|
+
}): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Gateway fallback when the router does not set `metadata.costStatus`.
|
|
59
|
+
* Prefer {@link resolveCostCompletionForActivity} at invoke boundaries.
|
|
60
|
+
*/
|
|
61
|
+
export declare function resolveActivityCostCompletion(tokens: {
|
|
62
|
+
prompt: number;
|
|
63
|
+
completion: number;
|
|
64
|
+
total: number;
|
|
65
|
+
}, costUsd: number | undefined): ResolvedActivityCost;
|
|
66
|
+
/**
|
|
67
|
+
* Activity cost slice for Activix: router `metadata.costStatus` / cost wins when present;
|
|
68
|
+
* otherwise gateway applies the G8 fallback (usage + no price → `unpriced`).
|
|
69
|
+
*/
|
|
70
|
+
export declare function resolveCostCompletionForActivity(routerResponse: unknown, tokens: {
|
|
71
|
+
prompt: number;
|
|
72
|
+
completion: number;
|
|
73
|
+
total: number;
|
|
74
|
+
}): ResolvedActivityCost;
|
|
46
75
|
/**
|
|
47
76
|
* Stable routing facts for gateway response metadata (router metadata + merged config fallbacks).
|
|
48
77
|
* Matches trace-mode resolution; intended for every successful invoke(), not only diagnostics.trace.
|
package/dist-cjs/gateway.cjs
CHANGED
|
@@ -11,6 +11,7 @@ const activity_manager_js_1 = require("./activity-manager.cjs");
|
|
|
11
11
|
const gateway_config_js_1 = require("./gateway-config.cjs");
|
|
12
12
|
const message_builder_js_1 = require("./message-builder.cjs");
|
|
13
13
|
const flex_md_loader_js_1 = require("./flex-md-loader.cjs");
|
|
14
|
+
const output_contract_normalizer_js_1 = require("./output-contract-normalizer.cjs");
|
|
14
15
|
const gateway_utils_js_1 = require("./gateway-utils.cjs");
|
|
15
16
|
const gateway_provider_auto_register_js_1 = require("./gateway-provider-auto-register.cjs");
|
|
16
17
|
const runtime_objects_js_1 = require("./runtime-objects.cjs");
|
|
@@ -111,8 +112,9 @@ class AIGateway {
|
|
|
111
112
|
},
|
|
112
113
|
mode: 'sync'
|
|
113
114
|
});
|
|
114
|
-
const costUsdChat = (0, gateway_utils_js_1.extractCostUsdFromRouterResponse)(response);
|
|
115
115
|
const metaChat = response?.metadata || {};
|
|
116
|
+
const tokensChat = (0, gateway_utils_js_1.extractTokenUsageFromRouterResponse)(response);
|
|
117
|
+
const costCompletionChat = (0, gateway_utils_js_1.resolveCostCompletionForActivity)(response, tokensChat);
|
|
116
118
|
// Create enhanced response
|
|
117
119
|
const enhancedResponse = {
|
|
118
120
|
content: response.content || '',
|
|
@@ -120,22 +122,25 @@ class AIGateway {
|
|
|
120
122
|
aiRequestId: request.aiRequestId,
|
|
121
123
|
identity: request.identity,
|
|
122
124
|
latencyMs: Date.now() - startTime,
|
|
123
|
-
tokens:
|
|
125
|
+
tokens: tokensChat,
|
|
124
126
|
taskTypeId,
|
|
125
127
|
agentType: 'chat',
|
|
126
|
-
...(
|
|
128
|
+
...(costCompletionChat.costStatus === 'priced'
|
|
127
129
|
? {
|
|
128
|
-
costUsd:
|
|
129
|
-
...(typeof metaChat.cost === 'number'
|
|
130
|
+
costUsd: costCompletionChat.cost,
|
|
131
|
+
...(typeof metaChat.cost === 'number'
|
|
132
|
+
? { cost: metaChat.cost }
|
|
133
|
+
: { cost: costCompletionChat.cost })
|
|
130
134
|
}
|
|
131
|
-
: {})
|
|
135
|
+
: {}),
|
|
136
|
+
...(costCompletionChat.costStatus ? { costStatus: costCompletionChat.costStatus } : {})
|
|
132
137
|
}
|
|
133
138
|
};
|
|
134
139
|
// Track activity success if activity was started
|
|
135
140
|
if (activity) {
|
|
136
141
|
try {
|
|
137
142
|
await this.activityManager.logSuccess(activity, {
|
|
138
|
-
...
|
|
143
|
+
...costCompletionChat,
|
|
139
144
|
response: enhancedResponse,
|
|
140
145
|
endTime: Date.now(),
|
|
141
146
|
duration: Date.now() - startTime
|
|
@@ -526,6 +531,8 @@ class AIGateway {
|
|
|
526
531
|
}
|
|
527
532
|
contentType = 'structured';
|
|
528
533
|
parsingMethod = 'flex-md';
|
|
534
|
+
const outputContractKeys = (0, output_contract_normalizer_js_1.resolveOutputContractFieldKeys)(request);
|
|
535
|
+
parsedContent = await (0, output_contract_normalizer_js_1.enrichParsedContentForOutputContract)(parsedContent, content, outputContractKeys, this.logger);
|
|
529
536
|
let tokens = (0, gateway_utils_js_1.extractTokenUsageFromRouterResponse)(routerResponse);
|
|
530
537
|
if (!(tokens.prompt || tokens.completion || tokens.total)) {
|
|
531
538
|
const alt = routerResponse?.rawResponse ?? routerResponse?.raw;
|
|
@@ -535,7 +542,7 @@ class AIGateway {
|
|
|
535
542
|
tokens = second;
|
|
536
543
|
}
|
|
537
544
|
}
|
|
538
|
-
const
|
|
545
|
+
const costCompletion = (0, gateway_utils_js_1.resolveCostCompletionForActivity)(routerResponse, tokens);
|
|
539
546
|
const routerMetaForCost = routerResponse?.metadata || {};
|
|
540
547
|
const routingMetadataSlice = (0, gateway_utils_js_1.pickInvokeRoutingMetadataSlice)(routerResponse, mergedConfig);
|
|
541
548
|
const effectiveModelConfig = (0, gateway_utils_js_1.pickEffectiveModelConfigForMetadata)(mergedConfig);
|
|
@@ -554,14 +561,15 @@ class AIGateway {
|
|
|
554
561
|
parsingMethod,
|
|
555
562
|
...routingMetadataSlice,
|
|
556
563
|
...(effectiveModelConfig !== undefined ? { effectiveModelConfig } : {}),
|
|
557
|
-
...(
|
|
564
|
+
...(costCompletion.costStatus === 'priced'
|
|
558
565
|
? {
|
|
559
|
-
costUsd:
|
|
566
|
+
costUsd: costCompletion.cost,
|
|
560
567
|
...(typeof routerMetaForCost.cost === 'number'
|
|
561
568
|
? { cost: routerMetaForCost.cost }
|
|
562
|
-
: { cost:
|
|
569
|
+
: { cost: costCompletion.cost })
|
|
563
570
|
}
|
|
564
571
|
: {}),
|
|
572
|
+
...(costCompletion.costStatus ? { costStatus: costCompletion.costStatus } : {}),
|
|
565
573
|
...(traceEnabled
|
|
566
574
|
? {
|
|
567
575
|
requestIds: traceRequestIds,
|
|
@@ -600,7 +608,7 @@ class AIGateway {
|
|
|
600
608
|
usage: tokens
|
|
601
609
|
};
|
|
602
610
|
await this.activityManager.logSuccess(activity, {
|
|
603
|
-
...
|
|
611
|
+
...costCompletion,
|
|
604
612
|
response: activityResponse,
|
|
605
613
|
endTime: Date.now(),
|
|
606
614
|
duration: Date.now() - startTime
|
package/dist-cjs/index.cjs
CHANGED
|
@@ -21,8 +21,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
21
21
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
22
22
|
};
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
-
exports.
|
|
25
|
-
exports.resetObjectTypesLibrary = exports.getObjectTypesLibrary = exports.initializeObjectTypesLibrary = void 0;
|
|
24
|
+
exports.createValidationTestCases = exports.createTestAIRequest = exports.supportsJSONMode = exports.diagnoseResponse = exports.diagnoseRequest = exports.validateResponse = exports.extractJSON = exports.validateJSON = exports.validateAIRequest = exports.DEFAULT_RATE_LIMIT_ENABLED = exports.DEFAULT_RATE_LIMIT_MIN_INTERVAL_MS = exports.GatewayRateLimiter = exports.runtimeObjects = exports.DebugLogAbstract = exports.createLogxer = exports.gatewayLogDebug = exports.withActivityIdentity = exports.activityIdentityToLogMeta = exports.ensureGatewayRequestIdentity = exports.ActivityManager = exports.Activix = exports.resolveGatewayMemoryPathValue = exports.prepareWorkingMemoryForTemplateRender = exports.parseLooseJsonObject = exports.mapSmartInputPathsInputsToInput = exports.extractCallerInputsBag = exports.coalesceMergedInputBucket = exports.buildMemoryResolutionRootFromWorkingMemory = exports.GATEWAY_DUAL_MEMORY_ROOTS = exports.mergeTemplateRenderOptions = exports.mergeGatewayAndRequestTemplateRenderOptions = exports.resolveOutputContractFieldKeys = exports.enrichParsedContentForOutputContract = exports.contractSpecToFieldKeys = exports.hasNonZeroTokenUsage = exports.resolveCostCompletionForActivity = exports.resolveActivityCostCompletion = exports.pickRequestIdsFromRouterLike = exports.tryExtractRouterLikePayloadFromErrorChain = exports.buildInvokeRejectionMetadata = exports.attachGatewayInvokeRejectionMetadata = exports.autoRegisterProviders = exports.InstructionBackendError = exports.InstructionNotFoundError = exports.AIGateway = exports.FallbackExhaustedError = exports.ProviderNotFoundError = exports.createRouterFromConfig = exports.createRouter = exports.LLMProviderRouter = void 0;
|
|
25
|
+
exports.resetObjectTypesLibrary = exports.getObjectTypesLibrary = exports.initializeObjectTypesLibrary = exports.getObjectTypesForAgent = exports.getObjectType = exports.OBJECT_TYPES_LIBRARY = exports.assertValidAIRequest = exports.formatDiagnostic = exports.runValidationTests = void 0;
|
|
26
26
|
// Re-export router class and types (base functionality)
|
|
27
27
|
var ai_providers_router_1 = require("@x12i/ai-providers-router");
|
|
28
28
|
Object.defineProperty(exports, "LLMProviderRouter", { enumerable: true, get: function () { return ai_providers_router_1.LLMProviderRouter; } });
|
|
@@ -49,6 +49,13 @@ Object.defineProperty(exports, "attachGatewayInvokeRejectionMetadata", { enumera
|
|
|
49
49
|
Object.defineProperty(exports, "buildInvokeRejectionMetadata", { enumerable: true, get: function () { return gateway_utils_js_1.buildInvokeRejectionMetadata; } });
|
|
50
50
|
Object.defineProperty(exports, "tryExtractRouterLikePayloadFromErrorChain", { enumerable: true, get: function () { return gateway_utils_js_1.tryExtractRouterLikePayloadFromErrorChain; } });
|
|
51
51
|
Object.defineProperty(exports, "pickRequestIdsFromRouterLike", { enumerable: true, get: function () { return gateway_utils_js_1.pickRequestIdsFromRouterLike; } });
|
|
52
|
+
Object.defineProperty(exports, "resolveActivityCostCompletion", { enumerable: true, get: function () { return gateway_utils_js_1.resolveActivityCostCompletion; } });
|
|
53
|
+
Object.defineProperty(exports, "resolveCostCompletionForActivity", { enumerable: true, get: function () { return gateway_utils_js_1.resolveCostCompletionForActivity; } });
|
|
54
|
+
Object.defineProperty(exports, "hasNonZeroTokenUsage", { enumerable: true, get: function () { return gateway_utils_js_1.hasNonZeroTokenUsage; } });
|
|
55
|
+
var output_contract_normalizer_js_1 = require("./output-contract-normalizer.cjs");
|
|
56
|
+
Object.defineProperty(exports, "contractSpecToFieldKeys", { enumerable: true, get: function () { return output_contract_normalizer_js_1.contractSpecToFieldKeys; } });
|
|
57
|
+
Object.defineProperty(exports, "enrichParsedContentForOutputContract", { enumerable: true, get: function () { return output_contract_normalizer_js_1.enrichParsedContentForOutputContract; } });
|
|
58
|
+
Object.defineProperty(exports, "resolveOutputContractFieldKeys", { enumerable: true, get: function () { return output_contract_normalizer_js_1.resolveOutputContractFieldKeys; } });
|
|
52
59
|
var template_render_merge_js_1 = require("./template-render-merge.cjs");
|
|
53
60
|
Object.defineProperty(exports, "mergeGatewayAndRequestTemplateRenderOptions", { enumerable: true, get: function () { return template_render_merge_js_1.mergeGatewayAndRequestTemplateRenderOptions; } });
|
|
54
61
|
Object.defineProperty(exports, "mergeTemplateRenderOptions", { enumerable: true, get: function () { return template_render_merge_js_1.mergeTemplateRenderOptions; } });
|
package/dist-cjs/index.d.ts
CHANGED
|
@@ -17,7 +17,10 @@ export { AIGateway } from './gateway.js';
|
|
|
17
17
|
export { InstructionNotFoundError, InstructionBackendError } from './instruction-errors.js';
|
|
18
18
|
export { autoRegisterProviders } from './gateway-provider-auto-register.js';
|
|
19
19
|
export type { GatewayConfig, ProviderModelRef, ModelConfig, RetryConfig, ChatRequest, AIInvokeRequest, AIRequest, GatewayActionType, GatewayInvokeRejectionMetadata, GatewayTraceRequestIds, GatewayTraceMergedConfig, EnhancedLLMResponse, InstructionMetadata, ValidationRule, TemplateRenderOptions, SmartInputConfig, SmartInputRenderOptions } from './types.js';
|
|
20
|
-
export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, pickRequestIdsFromRouterLike } from './gateway-utils.js';
|
|
20
|
+
export { attachGatewayInvokeRejectionMetadata, buildInvokeRejectionMetadata, tryExtractRouterLikePayloadFromErrorChain, pickRequestIdsFromRouterLike, resolveActivityCostCompletion, resolveCostCompletionForActivity, hasNonZeroTokenUsage } from './gateway-utils.js';
|
|
21
|
+
export type { ActivityCostStatus, ResolvedActivityCost } from './gateway-utils.js';
|
|
22
|
+
export { contractSpecToFieldKeys, enrichParsedContentForOutputContract, resolveOutputContractFieldKeys } from './output-contract-normalizer.js';
|
|
23
|
+
export type { OutputContractSpec } from './output-contract-normalizer.js';
|
|
21
24
|
export { mergeGatewayAndRequestTemplateRenderOptions, mergeTemplateRenderOptions } from './template-render-merge.js';
|
|
22
25
|
export type { GatewayTemplateRenderRequestSlice } from './template-render-merge.js';
|
|
23
26
|
export { GATEWAY_DUAL_MEMORY_ROOTS, buildMemoryResolutionRootFromWorkingMemory, coalesceMergedInputBucket, extractCallerInputsBag, mapSmartInputPathsInputsToInput, parseLooseJsonObject, prepareWorkingMemoryForTemplateRender, resolveGatewayMemoryPathValue } from './memory-path-resolution.js';
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Normalizes invoke responses into `output.parsed` when an explicit output contract is forwarded.
|
|
4
|
+
* Does not infer contracts from other request fields — that is upstream (graph-engine / ai-tasks).
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.contractSpecToFieldKeys = contractSpecToFieldKeys;
|
|
8
|
+
exports.resolveOutputContractFieldKeys = resolveOutputContractFieldKeys;
|
|
9
|
+
exports.enrichParsedContentForOutputContract = enrichParsedContentForOutputContract;
|
|
10
|
+
const flex_md_loader_js_1 = require("./flex-md-loader.cjs");
|
|
11
|
+
const memory_path_resolution_js_1 = require("./memory-path-resolution.cjs");
|
|
12
|
+
function isPlainObject(value) {
|
|
13
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
14
|
+
}
|
|
15
|
+
function hasMeaningfulContractValue(value) {
|
|
16
|
+
if (value === undefined || value === null)
|
|
17
|
+
return false;
|
|
18
|
+
if (typeof value === 'string')
|
|
19
|
+
return value.trim().length > 0;
|
|
20
|
+
if (Array.isArray(value))
|
|
21
|
+
return value.length > 0;
|
|
22
|
+
if (typeof value === 'object')
|
|
23
|
+
return Object.keys(value).length > 0;
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Maps an explicit contract spec to field keys. No inference from descriptions or other request fields.
|
|
28
|
+
*/
|
|
29
|
+
function contractSpecToFieldKeys(contract) {
|
|
30
|
+
if (contract == null)
|
|
31
|
+
return [];
|
|
32
|
+
if (Array.isArray(contract)) {
|
|
33
|
+
return contract.filter((k) => typeof k === 'string' && k.trim().length > 0);
|
|
34
|
+
}
|
|
35
|
+
if (!isPlainObject(contract))
|
|
36
|
+
return [];
|
|
37
|
+
const properties = contract.properties;
|
|
38
|
+
if (isPlainObject(properties)) {
|
|
39
|
+
return Object.keys(properties);
|
|
40
|
+
}
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
/** Graph-engine path: `workingMemory.inputs.outputContract` or merged `input.outputContract`. */
|
|
44
|
+
function readExplicitOutputContractFromWorkingMemory(workingMemory) {
|
|
45
|
+
if (!isPlainObject(workingMemory))
|
|
46
|
+
return undefined;
|
|
47
|
+
const inputs = (0, memory_path_resolution_js_1.extractCallerInputsBag)(workingMemory);
|
|
48
|
+
if (inputs?.outputContract !== undefined)
|
|
49
|
+
return inputs.outputContract;
|
|
50
|
+
const input = (0, memory_path_resolution_js_1.coalesceMergedInputBucket)(workingMemory);
|
|
51
|
+
if (isPlainObject(input) && input.outputContract !== undefined) {
|
|
52
|
+
return input.outputContract;
|
|
53
|
+
}
|
|
54
|
+
const loose = (0, memory_path_resolution_js_1.parseLooseJsonObject)(workingMemory.input);
|
|
55
|
+
if (loose?.outputContract !== undefined)
|
|
56
|
+
return loose.outputContract;
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Resolves field keys only from explicit `outputContract` on the request or graph-forwarded inputs.
|
|
61
|
+
*/
|
|
62
|
+
function resolveOutputContractFieldKeys(request) {
|
|
63
|
+
if (request == null || typeof request !== 'object')
|
|
64
|
+
return undefined;
|
|
65
|
+
const r = request;
|
|
66
|
+
const candidates = [
|
|
67
|
+
r.outputContract,
|
|
68
|
+
readExplicitOutputContractFromWorkingMemory(r.workingMemory)
|
|
69
|
+
];
|
|
70
|
+
for (const candidate of candidates) {
|
|
71
|
+
const keys = contractSpecToFieldKeys(candidate);
|
|
72
|
+
if (keys.length > 0)
|
|
73
|
+
return keys;
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
function asParsedRecord(parsed) {
|
|
78
|
+
if (!isPlainObject(parsed))
|
|
79
|
+
return {};
|
|
80
|
+
return { ...parsed };
|
|
81
|
+
}
|
|
82
|
+
function pickAliasValue(source, key) {
|
|
83
|
+
if (hasMeaningfulContractValue(source[key]))
|
|
84
|
+
return source[key];
|
|
85
|
+
const spaced = key.replace(/([A-Z])/g, ' $1').replace(/^./, (c) => c.toUpperCase());
|
|
86
|
+
const title = spaced.charAt(0).toUpperCase() + spaced.slice(1);
|
|
87
|
+
const aliases = [
|
|
88
|
+
key,
|
|
89
|
+
key.toLowerCase(),
|
|
90
|
+
title,
|
|
91
|
+
title.replace(/\s+/g, ' '),
|
|
92
|
+
key.replace(/([A-Z])/g, '_$1').toLowerCase()
|
|
93
|
+
];
|
|
94
|
+
for (const alias of aliases) {
|
|
95
|
+
if (hasMeaningfulContractValue(source[alias]))
|
|
96
|
+
return source[alias];
|
|
97
|
+
}
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Fills missing contract keys from markdown sections after flex-md. Only runs when explicit contract keys were supplied.
|
|
102
|
+
*/
|
|
103
|
+
async function enrichParsedContentForOutputContract(parsed, rawContent, contractKeys, logger) {
|
|
104
|
+
const base = asParsedRecord(parsed);
|
|
105
|
+
if (!contractKeys?.length)
|
|
106
|
+
return base;
|
|
107
|
+
const missing = contractKeys.filter((k) => !hasMeaningfulContractValue(base[k]));
|
|
108
|
+
if (missing.length === 0)
|
|
109
|
+
return base;
|
|
110
|
+
const content = typeof rawContent === 'string' && rawContent.trim().length > 0
|
|
111
|
+
? rawContent
|
|
112
|
+
: typeof base.rawText === 'string'
|
|
113
|
+
? base.rawText
|
|
114
|
+
: '';
|
|
115
|
+
if (!content.trim())
|
|
116
|
+
return base;
|
|
117
|
+
const fromMarkdown = (0, flex_md_loader_js_1.parseMarkdownSectionsFromContent)(content, logger);
|
|
118
|
+
const merged = { ...base };
|
|
119
|
+
for (const key of missing) {
|
|
120
|
+
const value = pickAliasValue(fromMarkdown, key);
|
|
121
|
+
if (hasMeaningfulContractValue(value)) {
|
|
122
|
+
merged[key] = value;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return merged;
|
|
126
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalizes invoke responses into `output.parsed` when an explicit output contract is forwarded.
|
|
3
|
+
* Does not infer contracts from other request fields — that is upstream (graph-engine / ai-tasks).
|
|
4
|
+
*/
|
|
5
|
+
import type { Logxer } from '@x12i/logxer';
|
|
6
|
+
/** Explicit contract: field names or JSON-schema-style `properties` only. */
|
|
7
|
+
export type OutputContractSpec = string[] | {
|
|
8
|
+
properties: Record<string, unknown>;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Maps an explicit contract spec to field keys. No inference from descriptions or other request fields.
|
|
12
|
+
*/
|
|
13
|
+
export declare function contractSpecToFieldKeys(contract: unknown): string[];
|
|
14
|
+
/**
|
|
15
|
+
* Resolves field keys only from explicit `outputContract` on the request or graph-forwarded inputs.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveOutputContractFieldKeys(request: unknown): string[] | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Fills missing contract keys from markdown sections after flex-md. Only runs when explicit contract keys were supplied.
|
|
20
|
+
*/
|
|
21
|
+
export declare function enrichParsedContentForOutputContract(parsed: unknown, rawContent: string, contractKeys: string[] | undefined, logger?: Logxer): Promise<Record<string, unknown>>;
|
package/dist-cjs/types.d.ts
CHANGED
|
@@ -701,6 +701,14 @@ interface BaseLLMRequest extends Omit<LLMRequest, 'messages' | 'input' | 'reques
|
|
|
701
701
|
* attach heavy diagnostic objects or raw provider payloads.
|
|
702
702
|
*/
|
|
703
703
|
diagnostics?: DiagnosticsOptions;
|
|
704
|
+
/**
|
|
705
|
+
* Explicit output contract from graph-engine / ai-tasks: field names or `{ properties }`.
|
|
706
|
+
* Also accepted on `workingMemory.inputs.outputContract`. When absent, the gateway does not
|
|
707
|
+
* infer contract fields from other request shapes (`expectedSchema`, config, etc.).
|
|
708
|
+
*/
|
|
709
|
+
outputContract?: string[] | {
|
|
710
|
+
properties: Record<string, unknown>;
|
|
711
|
+
};
|
|
704
712
|
}
|
|
705
713
|
/**
|
|
706
714
|
* Chat request for conversational use cases
|
|
@@ -932,6 +940,12 @@ export interface EnhancedLLMResponse<TContent = unknown> extends Omit<AIResponse
|
|
|
932
940
|
* Cost in USD (if available)
|
|
933
941
|
*/
|
|
934
942
|
cost?: number;
|
|
943
|
+
/**
|
|
944
|
+
* Billing state for Run Analysis / Activix when usage is recorded.
|
|
945
|
+
* - `priced`: {@link cost} / {@link costUsd} is a finite number from the router
|
|
946
|
+
* - `unpriced`: usage exists but no price table / adapter cost was returned
|
|
947
|
+
*/
|
|
948
|
+
costStatus?: 'priced' | 'unpriced';
|
|
935
949
|
/**
|
|
936
950
|
* Cost in USD (preferred stable key when the router exposes it).
|
|
937
951
|
* When both are present, costUsd should mirror cost.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@x12i/ai-gateway",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.2.0",
|
|
4
4
|
"description": "AI Gateway - Unified interface for LLM provider routing and management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"author": "x12i",
|
|
61
61
|
"license": "mit",
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@x12i/ai-providers-router": "^4.
|
|
63
|
+
"@x12i/ai-providers-router": "^4.8.0",
|
|
64
64
|
"@x12i/rendrix": "^4.3.0",
|
|
65
65
|
"@aws-sdk/s3-request-presigner": "^3.953.0",
|
|
66
66
|
"@x12i/env": "^4.0.1",
|