@x12i/ai-gateway 9.1.5 → 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 +6 -1
- package/dist/index.js +3 -1
- package/dist/memory-path-resolution.d.ts +33 -0
- package/dist/memory-path-resolution.js +172 -0
- package/dist/output-contract-normalizer.d.ts +21 -0
- package/dist/output-contract-normalizer.js +121 -0
- package/dist/template-parser.js +5 -3
- package/dist/types.d.ts +18 -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 +18 -1
- package/dist-cjs/index.d.ts +6 -1
- package/dist-cjs/memory-path-resolution.cjs +182 -0
- package/dist-cjs/memory-path-resolution.d.ts +33 -0
- package/dist-cjs/output-contract-normalizer.cjs +126 -0
- package/dist-cjs/output-contract-normalizer.d.ts +21 -0
- package/dist-cjs/template-parser.cjs +5 -3
- package/dist-cjs/types.d.ts +18 -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,9 +17,14 @@ 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';
|
|
26
|
+
export { GATEWAY_DUAL_MEMORY_ROOTS, buildMemoryResolutionRootFromWorkingMemory, coalesceMergedInputBucket, extractCallerInputsBag, mapSmartInputPathsInputsToInput, parseLooseJsonObject, prepareWorkingMemoryForTemplateRender, resolveGatewayMemoryPathValue } from './memory-path-resolution.js';
|
|
27
|
+
export type { GatewayDualMemoryRoot } from './memory-path-resolution.js';
|
|
23
28
|
export type { UsageTier } from './types.js';
|
|
24
29
|
export { Activix } from '@x12i/activix';
|
|
25
30
|
export type { ActivixRunContext, FindByRunContextCriteria, GetJobActivitiesInput, GetJobActivitiesResult } from '@x12i/activix';
|
package/dist/index.js
CHANGED
|
@@ -17,8 +17,10 @@ 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';
|
|
23
|
+
export { GATEWAY_DUAL_MEMORY_ROOTS, buildMemoryResolutionRootFromWorkingMemory, coalesceMergedInputBucket, extractCallerInputsBag, mapSmartInputPathsInputsToInput, parseLooseJsonObject, prepareWorkingMemoryForTemplateRender, resolveGatewayMemoryPathValue } from './memory-path-resolution.js';
|
|
22
24
|
// Usage tracking: UsageTracker class methods are available but consumption calculation is disabled
|
|
23
25
|
// (x-models was previously used for RPM/TPM tracking but is no longer integrated)
|
|
24
26
|
// Re-export activity tracking primitives (Activix)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dual memory roots `input` (merged MAIN payload) and `inputs` (caller / graph-entry bag).
|
|
3
|
+
* Aligned with @exellix/graph-engine ≥ 5.5.0 resolution rules for template / smart-input paths.
|
|
4
|
+
*/
|
|
5
|
+
export declare const GATEWAY_DUAL_MEMORY_ROOTS: readonly ["input", "inputs"];
|
|
6
|
+
export type GatewayDualMemoryRoot = (typeof GATEWAY_DUAL_MEMORY_ROOTS)[number];
|
|
7
|
+
/**
|
|
8
|
+
* Parse JSON object shapes from strings (matches graph-engine `parseLooseJsonObject`).
|
|
9
|
+
*/
|
|
10
|
+
export declare function parseLooseJsonObject(value: unknown): Record<string, unknown> | undefined;
|
|
11
|
+
/** Caller / graph-entry bag from `workingMemory.inputs`. */
|
|
12
|
+
export declare function extractCallerInputsBag(workingMemory: unknown): Record<string, unknown> | undefined;
|
|
13
|
+
/** Merged MAIN bucket from `workingMemory.input` (object or parsed JSON string). */
|
|
14
|
+
export declare function coalesceMergedInputBucket(workingMemory: unknown): unknown;
|
|
15
|
+
/**
|
|
16
|
+
* Resolve a dotted path against working memory with dual-root rules:
|
|
17
|
+
* - `inputs` / `inputs.*` → caller bag first, then merged `input`
|
|
18
|
+
* - `input` / `input.*` → merged `input` first, then caller `inputs`
|
|
19
|
+
* - other paths → direct lookup on working memory
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveGatewayMemoryPathValue(workingMemory: unknown, path: string): unknown;
|
|
22
|
+
/**
|
|
23
|
+
* Working-memory view for Rendrix / smart-input: preserves all keys but overlays
|
|
24
|
+
* `input` and `inputs` with dual-root merge views (does not rewrite authored paths).
|
|
25
|
+
*/
|
|
26
|
+
export declare function buildMemoryResolutionRootFromWorkingMemory(workingMemory: unknown): Record<string, unknown>;
|
|
27
|
+
/**
|
|
28
|
+
* When WM carries `input` and/or `inputs`, return a resolution root for template rendering.
|
|
29
|
+
* Otherwise returns the original reference unchanged.
|
|
30
|
+
*/
|
|
31
|
+
export declare function prepareWorkingMemoryForTemplateRender(workingMemory: unknown): unknown;
|
|
32
|
+
/** Optional migration: rewrite `inputs.*` smart-input / memory paths to `input.*`. */
|
|
33
|
+
export declare function mapSmartInputPathsInputsToInput(paths: string[]): string[];
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dual memory roots `input` (merged MAIN payload) and `inputs` (caller / graph-entry bag).
|
|
3
|
+
* Aligned with @exellix/graph-engine ≥ 5.5.0 resolution rules for template / smart-input paths.
|
|
4
|
+
*/
|
|
5
|
+
export const GATEWAY_DUAL_MEMORY_ROOTS = ['input', 'inputs'];
|
|
6
|
+
function isPlainObject(value) {
|
|
7
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Parse JSON object shapes from strings (matches graph-engine `parseLooseJsonObject`).
|
|
11
|
+
*/
|
|
12
|
+
export function parseLooseJsonObject(value) {
|
|
13
|
+
if (isPlainObject(value))
|
|
14
|
+
return value;
|
|
15
|
+
if (typeof value !== 'string')
|
|
16
|
+
return undefined;
|
|
17
|
+
const trimmed = value.trim();
|
|
18
|
+
if (!trimmed.startsWith('{'))
|
|
19
|
+
return undefined;
|
|
20
|
+
try {
|
|
21
|
+
const parsed = JSON.parse(trimmed);
|
|
22
|
+
return isPlainObject(parsed) ? parsed : undefined;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Caller / graph-entry bag from `workingMemory.inputs`. */
|
|
29
|
+
export function extractCallerInputsBag(workingMemory) {
|
|
30
|
+
if (!isPlainObject(workingMemory))
|
|
31
|
+
return undefined;
|
|
32
|
+
const inputs = workingMemory.inputs;
|
|
33
|
+
return isPlainObject(inputs) ? inputs : undefined;
|
|
34
|
+
}
|
|
35
|
+
/** Merged MAIN bucket from `workingMemory.input` (object or parsed JSON string). */
|
|
36
|
+
export function coalesceMergedInputBucket(workingMemory) {
|
|
37
|
+
if (!isPlainObject(workingMemory))
|
|
38
|
+
return undefined;
|
|
39
|
+
const raw = workingMemory.input;
|
|
40
|
+
if (raw === undefined || raw === null)
|
|
41
|
+
return undefined;
|
|
42
|
+
const parsed = parseLooseJsonObject(raw);
|
|
43
|
+
return parsed !== undefined ? parsed : raw;
|
|
44
|
+
}
|
|
45
|
+
function getValueAtPath(obj, path) {
|
|
46
|
+
if (obj == null)
|
|
47
|
+
return undefined;
|
|
48
|
+
if (path === '' || path === 'this' || path === '.')
|
|
49
|
+
return obj;
|
|
50
|
+
const parts = path.split('.');
|
|
51
|
+
let cur = obj;
|
|
52
|
+
for (const part of parts) {
|
|
53
|
+
if (cur == null || typeof cur !== 'object')
|
|
54
|
+
return undefined;
|
|
55
|
+
cur = cur[part];
|
|
56
|
+
}
|
|
57
|
+
return cur;
|
|
58
|
+
}
|
|
59
|
+
function asObjectBucket(value) {
|
|
60
|
+
const parsed = parseLooseJsonObject(value);
|
|
61
|
+
if (parsed)
|
|
62
|
+
return parsed;
|
|
63
|
+
return isPlainObject(value) ? value : undefined;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Shallow-deep merge for template lookup: primary wins per key; nested plain objects merge recursively.
|
|
67
|
+
*/
|
|
68
|
+
function mergeBucketViews(primary, fallback) {
|
|
69
|
+
const pObj = asObjectBucket(primary);
|
|
70
|
+
const fObj = asObjectBucket(fallback);
|
|
71
|
+
if (!pObj && !fObj) {
|
|
72
|
+
if (primary !== undefined && primary !== null)
|
|
73
|
+
return primary;
|
|
74
|
+
return fallback;
|
|
75
|
+
}
|
|
76
|
+
if (!pObj)
|
|
77
|
+
return { ...fObj };
|
|
78
|
+
if (!fObj)
|
|
79
|
+
return { ...pObj };
|
|
80
|
+
const keys = new Set([...Object.keys(pObj), ...Object.keys(fObj)]);
|
|
81
|
+
const view = {};
|
|
82
|
+
for (const key of keys) {
|
|
83
|
+
const pv = pObj[key];
|
|
84
|
+
const fv = fObj[key];
|
|
85
|
+
if (isPlainObject(pv) && isPlainObject(fv)) {
|
|
86
|
+
view[key] = mergeBucketViews(pv, fv);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
view[key] = pv !== undefined ? pv : fv;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return view;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Resolve a dotted path against working memory with dual-root rules:
|
|
96
|
+
* - `inputs` / `inputs.*` → caller bag first, then merged `input`
|
|
97
|
+
* - `input` / `input.*` → merged `input` first, then caller `inputs`
|
|
98
|
+
* - other paths → direct lookup on working memory
|
|
99
|
+
*/
|
|
100
|
+
export function resolveGatewayMemoryPathValue(workingMemory, path) {
|
|
101
|
+
const trimmed = path.trim();
|
|
102
|
+
if (!trimmed)
|
|
103
|
+
return undefined;
|
|
104
|
+
const dot = trimmed.indexOf('.');
|
|
105
|
+
const root = dot >= 0 ? trimmed.slice(0, dot) : trimmed;
|
|
106
|
+
const tail = dot >= 0 ? trimmed.slice(dot + 1) : '';
|
|
107
|
+
if (root === 'inputs') {
|
|
108
|
+
const primary = extractCallerInputsBag(workingMemory);
|
|
109
|
+
const fallback = coalesceMergedInputBucket(workingMemory);
|
|
110
|
+
if (!tail) {
|
|
111
|
+
if (primary && Object.keys(primary).length > 0)
|
|
112
|
+
return primary;
|
|
113
|
+
return fallback;
|
|
114
|
+
}
|
|
115
|
+
const fromPrimary = primary ? getValueAtPath(primary, tail) : undefined;
|
|
116
|
+
if (fromPrimary !== undefined)
|
|
117
|
+
return fromPrimary;
|
|
118
|
+
return fallback !== undefined ? getValueAtPath(fallback, tail) : undefined;
|
|
119
|
+
}
|
|
120
|
+
if (root === 'input') {
|
|
121
|
+
const primary = coalesceMergedInputBucket(workingMemory);
|
|
122
|
+
const fallback = extractCallerInputsBag(workingMemory);
|
|
123
|
+
if (!tail) {
|
|
124
|
+
if (primary !== undefined && primary !== null)
|
|
125
|
+
return primary;
|
|
126
|
+
return fallback;
|
|
127
|
+
}
|
|
128
|
+
const fromPrimary = primary !== undefined ? getValueAtPath(primary, tail) : undefined;
|
|
129
|
+
if (fromPrimary !== undefined)
|
|
130
|
+
return fromPrimary;
|
|
131
|
+
return fallback ? getValueAtPath(fallback, tail) : undefined;
|
|
132
|
+
}
|
|
133
|
+
return getValueAtPath(workingMemory, trimmed);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Working-memory view for Rendrix / smart-input: preserves all keys but overlays
|
|
137
|
+
* `input` and `inputs` with dual-root merge views (does not rewrite authored paths).
|
|
138
|
+
*/
|
|
139
|
+
export function buildMemoryResolutionRootFromWorkingMemory(workingMemory) {
|
|
140
|
+
if (!isPlainObject(workingMemory))
|
|
141
|
+
return {};
|
|
142
|
+
const inputsBag = extractCallerInputsBag(workingMemory);
|
|
143
|
+
const mergedInput = coalesceMergedInputBucket(workingMemory);
|
|
144
|
+
return {
|
|
145
|
+
...workingMemory,
|
|
146
|
+
input: mergeBucketViews(mergedInput, inputsBag),
|
|
147
|
+
inputs: mergeBucketViews(inputsBag, mergedInput)
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* When WM carries `input` and/or `inputs`, return a resolution root for template rendering.
|
|
152
|
+
* Otherwise returns the original reference unchanged.
|
|
153
|
+
*/
|
|
154
|
+
export function prepareWorkingMemoryForTemplateRender(workingMemory) {
|
|
155
|
+
if (!isPlainObject(workingMemory))
|
|
156
|
+
return workingMemory;
|
|
157
|
+
if (workingMemory.input === undefined && workingMemory.inputs === undefined) {
|
|
158
|
+
return workingMemory;
|
|
159
|
+
}
|
|
160
|
+
return buildMemoryResolutionRootFromWorkingMemory(workingMemory);
|
|
161
|
+
}
|
|
162
|
+
/** Optional migration: rewrite `inputs.*` smart-input / memory paths to `input.*`. */
|
|
163
|
+
export function mapSmartInputPathsInputsToInput(paths) {
|
|
164
|
+
return paths.map((p) => {
|
|
165
|
+
const t = p.trim();
|
|
166
|
+
if (t === 'inputs')
|
|
167
|
+
return 'input';
|
|
168
|
+
if (t.startsWith('inputs.'))
|
|
169
|
+
return `input.${t.slice('inputs.'.length)}`;
|
|
170
|
+
return p;
|
|
171
|
+
});
|
|
172
|
+
}
|
|
@@ -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
|
+
}
|