@jsonstudio/llms 0.6.3405 → 0.6.3539
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/conversion/codecs/anthropic-openai-codec.d.ts +12 -3
- package/dist/conversion/codecs/anthropic-openai-codec.js +32 -92
- package/dist/conversion/codecs/gemini-openai-codec.d.ts +6 -5
- package/dist/conversion/codecs/gemini-openai-codec.js +48 -685
- package/dist/conversion/codecs/openai-openai-codec.d.ts +1 -1
- package/dist/conversion/codecs/openai-openai-codec.js +34 -100
- package/dist/conversion/codecs/responses-openai-codec.d.ts +1 -1
- package/dist/conversion/codecs/responses-openai-codec.js +47 -159
- package/dist/conversion/compat/actions/anthropic-claude-code-system-prompt.d.ts +2 -6
- package/dist/conversion/compat/actions/anthropic-claude-code-system-prompt.js +29 -245
- package/dist/conversion/compat/actions/anthropic-claude-code-user-id.d.ts +3 -0
- package/dist/conversion/compat/actions/anthropic-claude-code-user-id.js +30 -0
- package/dist/conversion/compat/actions/antigravity-thought-signature-prepare.js +21 -232
- package/dist/conversion/compat/actions/deepseek-web-request.js +41 -276
- package/dist/conversion/compat/actions/deepseek-web-response.js +64 -859
- package/dist/conversion/compat/actions/gemini-cli-request.d.ts +1 -1
- package/dist/conversion/compat/actions/gemini-cli-request.js +20 -613
- package/dist/conversion/compat/actions/gemini-web-search.d.ts +1 -15
- package/dist/conversion/compat/actions/gemini-web-search.js +22 -69
- package/dist/conversion/compat/actions/glm-tool-extraction.d.ts +3 -2
- package/dist/conversion/compat/actions/glm-tool-extraction.js +28 -257
- package/dist/conversion/compat/actions/iflow-tool-text-fallback.d.ts +0 -8
- package/dist/conversion/compat/actions/iflow-tool-text-fallback.js +24 -206
- package/dist/conversion/compat/actions/qwen-transform.d.ts +3 -2
- package/dist/conversion/compat/actions/qwen-transform.js +30 -271
- package/dist/conversion/compat/actions/tool-text-request-guidance.js +3 -173
- package/dist/conversion/compat/actions/universal-shape-filter.d.ts +6 -23
- package/dist/conversion/compat/actions/universal-shape-filter.js +4 -383
- package/dist/conversion/hub/pipeline/compat/native-adapter-context.js +1 -0
- package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.d.ts +1 -2
- package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.js +50 -104
- package/dist/conversion/pipeline/codecs/v2/openai-openai-pipeline.js +12 -10
- package/dist/conversion/pipeline/codecs/v2/responses-openai-pipeline.d.ts +0 -2
- package/dist/conversion/pipeline/codecs/v2/responses-openai-pipeline.js +46 -67
- package/dist/conversion/pipeline/codecs/v2/shared/openai-chat-helpers.js +15 -40
- package/dist/conversion/responses/responses-openai-bridge/response-payload.js +47 -348
- package/dist/conversion/responses/responses-openai-bridge.js +129 -611
- package/dist/conversion/shared/chat-output-normalizer.js +6 -0
- package/dist/conversion/shared/chat-request-filters.js +1 -1
- package/dist/conversion/shared/output-content-normalizer.js +10 -0
- package/dist/conversion/shared/responses-conversation-store.js +22 -135
- package/dist/conversion/shared/responses-output-builder.d.ts +0 -2
- package/dist/conversion/shared/responses-output-builder.js +28 -318
- package/dist/conversion/shared/responses-response-utils.js +35 -86
- package/dist/conversion/shared/streaming-text-extractor.d.ts +1 -2
- package/dist/conversion/shared/streaming-text-extractor.js +13 -14
- package/dist/conversion/shared/tool-call-id-manager.js +18 -21
- package/dist/native/router_hotpath_napi.node +0 -0
- package/dist/router/virtual-router/bootstrap/routing-config.d.ts +2 -1
- package/dist/router/virtual-router/bootstrap/routing-config.js +57 -4
- package/dist/router/virtual-router/engine-legacy.d.ts +3 -3
- package/dist/router/virtual-router/engine-legacy.js +15 -7
- package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.d.ts +16 -0
- package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.js +434 -46
- package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.d.ts +83 -0
- package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js +295 -0
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-outbound-semantics.d.ts +1 -0
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.d.ts +7 -0
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.js +8 -1
- package/dist/router/virtual-router/engine-selection/native-router-hotpath-loader.js +383 -298
- package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.d.ts +20 -0
- package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.js +201 -0
- package/dist/router/virtual-router/engine-selection/native-virtual-router-routing-instructions-semantics.d.ts +1 -0
- package/dist/router/virtual-router/engine-selection/native-virtual-router-routing-instructions-semantics.js +37 -0
- package/dist/router/virtual-router/engine-selection/tier-load-balancing.d.ts +16 -0
- package/dist/router/virtual-router/engine-selection/tier-load-balancing.js +120 -0
- package/dist/router/virtual-router/engine-selection/tier-selection-quota-integration.d.ts +2 -0
- package/dist/router/virtual-router/engine-selection/tier-selection-quota-integration.js +44 -66
- package/dist/router/virtual-router/engine-selection/tier-selection-select.js +53 -84
- package/dist/router/virtual-router/engine.js +0 -38
- package/dist/router/virtual-router/features.js +44 -3
- package/dist/router/virtual-router/routing-instructions/parse.d.ts +0 -12
- package/dist/router/virtual-router/routing-instructions/parse.js +9 -389
- package/dist/router/virtual-router/stop-message-state-sync.d.ts +3 -6
- package/dist/router/virtual-router/stop-message-state-sync.js +50 -21
- package/dist/router/virtual-router/types.d.ts +16 -0
- package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +1 -0
- package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.js +26 -0
- package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +12 -2
- package/package.json +1 -1
- package/dist/router/virtual-router/engine-legacy/route-finalize.d.ts +0 -9
- package/dist/router/virtual-router/engine-legacy/route-finalize.js +0 -84
- package/dist/router/virtual-router/engine-legacy/route-selection.d.ts +0 -17
- package/dist/router/virtual-router/engine-legacy/route-selection.js +0 -205
- package/dist/router/virtual-router/engine-legacy/route-state-allowlist.d.ts +0 -3
- package/dist/router/virtual-router/engine-legacy/route-state-allowlist.js +0 -36
- package/dist/router/virtual-router/engine-legacy/route-state.d.ts +0 -12
- package/dist/router/virtual-router/engine-legacy/route-state.js +0 -386
- package/dist/router/virtual-router/engine-legacy/routing.d.ts +0 -8
- package/dist/router/virtual-router/engine-legacy/routing.js +0 -8
|
@@ -28,6 +28,7 @@ export declare function mapChatToolsToBridgeWithNative(rawTools: unknown, option
|
|
|
28
28
|
}): Array<Record<string, unknown>>;
|
|
29
29
|
export declare function collectToolCallsFromResponsesWithNative(response: Record<string, unknown>): Array<Record<string, unknown>>;
|
|
30
30
|
export declare function resolveFinishReasonWithNative(response: Record<string, unknown>, toolCalls: Array<Record<string, unknown>>): string;
|
|
31
|
+
export declare function buildChatResponseFromResponsesWithNative(payload: unknown): Record<string, unknown> | null;
|
|
31
32
|
export declare function hasValidThoughtSignatureWithNative(block: unknown, options?: Record<string, unknown>): boolean;
|
|
32
33
|
export declare function sanitizeThinkingBlockWithNative(block: unknown): Record<string, unknown>;
|
|
33
34
|
export declare function filterInvalidThinkingBlocksWithNative(messages: unknown, options?: Record<string, unknown>): unknown[];
|
|
@@ -146,6 +147,16 @@ export declare function extractStreamingToolCallsWithNative(input: {
|
|
|
146
147
|
idCounter: number;
|
|
147
148
|
toolCalls: Array<Record<string, unknown>>;
|
|
148
149
|
};
|
|
150
|
+
export declare function createStreamingToolExtractorStateWithNative(idPrefix?: string): Record<string, unknown>;
|
|
151
|
+
export declare function resetStreamingToolExtractorStateWithNative(state: Record<string, unknown>): Record<string, unknown>;
|
|
152
|
+
export declare function feedStreamingToolExtractorWithNative(input: {
|
|
153
|
+
state: Record<string, unknown>;
|
|
154
|
+
text: string;
|
|
155
|
+
nowMs?: number;
|
|
156
|
+
}): {
|
|
157
|
+
state: Record<string, unknown>;
|
|
158
|
+
toolCalls: Array<Record<string, unknown>>;
|
|
159
|
+
};
|
|
149
160
|
export declare function isCompactionRequestWithNative(payload: unknown): boolean;
|
|
150
161
|
export declare function encodeMetadataPassthroughWithNative(parameters: unknown, prefix: string, keys: readonly string[]): Record<string, string> | undefined;
|
|
151
162
|
export declare function extractMetadataPassthroughWithNative(metadataField: unknown, prefix: string, keys: readonly string[]): {
|
|
@@ -194,6 +205,15 @@ export declare function prepareGeminiToolsForBridgeWithNative(rawTools: unknown,
|
|
|
194
205
|
export declare function buildGeminiToolsFromBridgeWithNative(defs: unknown, mode?: 'antigravity' | 'default'): Array<Record<string, unknown>> | undefined;
|
|
195
206
|
export declare function pickResponsesPersistedFieldsWithNative(payload: unknown): Record<string, unknown>;
|
|
196
207
|
export declare function convertResponsesOutputToInputItemsWithNative(response: unknown): Array<Record<string, unknown>>;
|
|
208
|
+
export declare function prepareResponsesConversationEntryWithNative(payload: unknown, context: unknown): {
|
|
209
|
+
basePayload: Record<string, unknown>;
|
|
210
|
+
input: Array<Record<string, unknown>>;
|
|
211
|
+
tools?: Array<Record<string, unknown>>;
|
|
212
|
+
};
|
|
213
|
+
export declare function resumeResponsesConversationPayloadWithNative(entry: unknown, responseId: string, submitPayload: unknown, requestId?: string): {
|
|
214
|
+
payload: Record<string, unknown>;
|
|
215
|
+
meta: Record<string, unknown>;
|
|
216
|
+
};
|
|
197
217
|
export declare function enforceChatBudgetWithNative(chat: unknown, allowedBytes: number, systemTextLimit: number): unknown;
|
|
198
218
|
export declare function resolveBudgetForModelWithNative(modelId: string, fallback: {
|
|
199
219
|
maxBytes: number;
|
|
@@ -111,6 +111,24 @@ function parseToolDefinitionOutput(raw) {
|
|
|
111
111
|
const parsed = parseRecord(raw);
|
|
112
112
|
return parsed;
|
|
113
113
|
}
|
|
114
|
+
function parseResponsesConversationResumeResult(raw) {
|
|
115
|
+
const parsed = parseRecord(raw);
|
|
116
|
+
if (!parsed) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const payload = parsed.payload;
|
|
120
|
+
const meta = parsed.meta;
|
|
121
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
if (!meta || typeof meta !== 'object' || Array.isArray(meta)) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
payload: payload,
|
|
129
|
+
meta: meta
|
|
130
|
+
};
|
|
131
|
+
}
|
|
114
132
|
function parseToolDefinitionArray(raw) {
|
|
115
133
|
const parsed = parseArray(raw);
|
|
116
134
|
if (!parsed)
|
|
@@ -568,6 +586,32 @@ export function resolveFinishReasonWithNative(response, toolCalls) {
|
|
|
568
586
|
return fail(reason);
|
|
569
587
|
}
|
|
570
588
|
}
|
|
589
|
+
export function buildChatResponseFromResponsesWithNative(payload) {
|
|
590
|
+
const capability = 'buildChatResponseFromResponsesJson';
|
|
591
|
+
const fail = (reason) => failNativeRequired(capability, reason);
|
|
592
|
+
if (isNativeDisabledByEnv()) {
|
|
593
|
+
return fail('native disabled');
|
|
594
|
+
}
|
|
595
|
+
const fn = readNativeFunction(capability);
|
|
596
|
+
if (!fn) {
|
|
597
|
+
return fail();
|
|
598
|
+
}
|
|
599
|
+
const payloadJson = safeStringify(payload ?? null);
|
|
600
|
+
if (!payloadJson) {
|
|
601
|
+
return fail('json stringify failed');
|
|
602
|
+
}
|
|
603
|
+
try {
|
|
604
|
+
const raw = fn(payloadJson);
|
|
605
|
+
if (typeof raw !== 'string' || !raw) {
|
|
606
|
+
return fail('empty result');
|
|
607
|
+
}
|
|
608
|
+
return parseRecord(raw) ?? fail('invalid payload');
|
|
609
|
+
}
|
|
610
|
+
catch (error) {
|
|
611
|
+
const reason = error instanceof Error ? error.message : String(error ?? 'unknown');
|
|
612
|
+
return fail(reason);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
571
615
|
export function hasValidThoughtSignatureWithNative(block, options) {
|
|
572
616
|
const capability = 'hasValidThoughtSignatureJson';
|
|
573
617
|
const fail = (reason) => failNativeRequired(capability, reason);
|
|
@@ -1414,6 +1458,92 @@ export function extractStreamingToolCallsWithNative(input) {
|
|
|
1414
1458
|
return fail(reason);
|
|
1415
1459
|
}
|
|
1416
1460
|
}
|
|
1461
|
+
export function createStreamingToolExtractorStateWithNative(idPrefix) {
|
|
1462
|
+
const capability = 'createStreamingToolExtractorStateJson';
|
|
1463
|
+
const fail = (reason) => failNativeRequired(capability, reason);
|
|
1464
|
+
if (isNativeDisabledByEnv()) {
|
|
1465
|
+
return fail('native disabled');
|
|
1466
|
+
}
|
|
1467
|
+
const fn = readNativeFunction(capability);
|
|
1468
|
+
if (!fn) {
|
|
1469
|
+
return fail();
|
|
1470
|
+
}
|
|
1471
|
+
const payloadJson = safeStringify(idPrefix ? { idPrefix } : {});
|
|
1472
|
+
if (!payloadJson) {
|
|
1473
|
+
return fail('json stringify failed');
|
|
1474
|
+
}
|
|
1475
|
+
try {
|
|
1476
|
+
const raw = fn(payloadJson);
|
|
1477
|
+
if (typeof raw !== 'string' || !raw) {
|
|
1478
|
+
return fail('empty result');
|
|
1479
|
+
}
|
|
1480
|
+
return parseRecord(raw) ?? fail('invalid payload');
|
|
1481
|
+
}
|
|
1482
|
+
catch (error) {
|
|
1483
|
+
const reason = error instanceof Error ? error.message : String(error ?? 'unknown');
|
|
1484
|
+
return fail(reason);
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
export function resetStreamingToolExtractorStateWithNative(state) {
|
|
1488
|
+
const capability = 'resetStreamingToolExtractorStateJson';
|
|
1489
|
+
const fail = (reason) => failNativeRequired(capability, reason);
|
|
1490
|
+
if (isNativeDisabledByEnv()) {
|
|
1491
|
+
return fail('native disabled');
|
|
1492
|
+
}
|
|
1493
|
+
const fn = readNativeFunction(capability);
|
|
1494
|
+
if (!fn) {
|
|
1495
|
+
return fail();
|
|
1496
|
+
}
|
|
1497
|
+
const payloadJson = safeStringify(state ?? {});
|
|
1498
|
+
if (!payloadJson) {
|
|
1499
|
+
return fail('json stringify failed');
|
|
1500
|
+
}
|
|
1501
|
+
try {
|
|
1502
|
+
const raw = fn(payloadJson);
|
|
1503
|
+
if (typeof raw !== 'string' || !raw) {
|
|
1504
|
+
return fail('empty result');
|
|
1505
|
+
}
|
|
1506
|
+
return parseRecord(raw) ?? fail('invalid payload');
|
|
1507
|
+
}
|
|
1508
|
+
catch (error) {
|
|
1509
|
+
const reason = error instanceof Error ? error.message : String(error ?? 'unknown');
|
|
1510
|
+
return fail(reason);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
export function feedStreamingToolExtractorWithNative(input) {
|
|
1514
|
+
const capability = 'feedStreamingToolExtractorJson';
|
|
1515
|
+
const fail = (reason) => failNativeRequired(capability, reason);
|
|
1516
|
+
if (isNativeDisabledByEnv()) {
|
|
1517
|
+
return fail('native disabled');
|
|
1518
|
+
}
|
|
1519
|
+
const fn = readNativeFunction(capability);
|
|
1520
|
+
if (!fn) {
|
|
1521
|
+
return fail();
|
|
1522
|
+
}
|
|
1523
|
+
const payloadJson = safeStringify(input ?? {});
|
|
1524
|
+
if (!payloadJson) {
|
|
1525
|
+
return fail('json stringify failed');
|
|
1526
|
+
}
|
|
1527
|
+
try {
|
|
1528
|
+
const raw = fn(payloadJson);
|
|
1529
|
+
if (typeof raw !== 'string' || !raw) {
|
|
1530
|
+
return fail('empty result');
|
|
1531
|
+
}
|
|
1532
|
+
const parsed = parseRecord(raw);
|
|
1533
|
+
if (!parsed || !parsed.state || typeof parsed.state !== 'object' || Array.isArray(parsed.state)) {
|
|
1534
|
+
return fail('invalid payload');
|
|
1535
|
+
}
|
|
1536
|
+
const toolCalls = Array.isArray(parsed.toolCalls)
|
|
1537
|
+
? parsed.toolCalls.filter((entry) => entry && typeof entry === 'object' && !Array.isArray(entry))
|
|
1538
|
+
.map((entry) => entry)
|
|
1539
|
+
: [];
|
|
1540
|
+
return { state: parsed.state, toolCalls };
|
|
1541
|
+
}
|
|
1542
|
+
catch (error) {
|
|
1543
|
+
const reason = error instanceof Error ? error.message : String(error ?? 'unknown');
|
|
1544
|
+
return fail(reason);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1417
1547
|
export function isCompactionRequestWithNative(payload) {
|
|
1418
1548
|
const capability = 'isCompactionRequestJson';
|
|
1419
1549
|
const fail = (reason) => failNativeRequired(capability, reason);
|
|
@@ -2177,6 +2307,77 @@ export function convertResponsesOutputToInputItemsWithNative(response) {
|
|
|
2177
2307
|
return fail(reason);
|
|
2178
2308
|
}
|
|
2179
2309
|
}
|
|
2310
|
+
export function prepareResponsesConversationEntryWithNative(payload, context) {
|
|
2311
|
+
const capability = 'prepareResponsesConversationEntryJson';
|
|
2312
|
+
const fail = (reason) => failNativeRequired(capability, reason);
|
|
2313
|
+
if (isNativeDisabledByEnv()) {
|
|
2314
|
+
return fail('native disabled');
|
|
2315
|
+
}
|
|
2316
|
+
const fn = readNativeFunction(capability);
|
|
2317
|
+
if (!fn) {
|
|
2318
|
+
return fail();
|
|
2319
|
+
}
|
|
2320
|
+
const payloadJson = safeStringify(payload ?? null);
|
|
2321
|
+
const contextJson = safeStringify(context ?? null);
|
|
2322
|
+
if (!payloadJson || !contextJson) {
|
|
2323
|
+
return fail('json stringify failed');
|
|
2324
|
+
}
|
|
2325
|
+
try {
|
|
2326
|
+
const raw = fn(payloadJson, contextJson);
|
|
2327
|
+
if (typeof raw !== 'string' || !raw) {
|
|
2328
|
+
return fail('empty result');
|
|
2329
|
+
}
|
|
2330
|
+
const parsed = parseRecord(raw);
|
|
2331
|
+
if (!parsed) {
|
|
2332
|
+
return fail('invalid payload');
|
|
2333
|
+
}
|
|
2334
|
+
const basePayload = parsed.basePayload;
|
|
2335
|
+
const input = parsed.input;
|
|
2336
|
+
const tools = parsed.tools;
|
|
2337
|
+
if (!basePayload || typeof basePayload !== 'object' || Array.isArray(basePayload) || !Array.isArray(input)) {
|
|
2338
|
+
return fail('invalid payload');
|
|
2339
|
+
}
|
|
2340
|
+
return {
|
|
2341
|
+
basePayload: basePayload,
|
|
2342
|
+
input: input.filter((entry) => !!entry && typeof entry === 'object' && !Array.isArray(entry)),
|
|
2343
|
+
tools: Array.isArray(tools)
|
|
2344
|
+
? tools.filter((entry) => !!entry && typeof entry === 'object' && !Array.isArray(entry))
|
|
2345
|
+
: undefined
|
|
2346
|
+
};
|
|
2347
|
+
}
|
|
2348
|
+
catch (error) {
|
|
2349
|
+
const reason = error instanceof Error ? error.message : String(error ?? 'unknown');
|
|
2350
|
+
return fail(reason);
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
export function resumeResponsesConversationPayloadWithNative(entry, responseId, submitPayload, requestId) {
|
|
2354
|
+
const capability = 'resumeResponsesConversationPayloadJson';
|
|
2355
|
+
const fail = (reason) => failNativeRequired(capability, reason);
|
|
2356
|
+
if (isNativeDisabledByEnv()) {
|
|
2357
|
+
return fail('native disabled');
|
|
2358
|
+
}
|
|
2359
|
+
const fn = readNativeFunction(capability);
|
|
2360
|
+
if (!fn) {
|
|
2361
|
+
return fail();
|
|
2362
|
+
}
|
|
2363
|
+
const entryJson = safeStringify(entry ?? null);
|
|
2364
|
+
const submitPayloadJson = safeStringify(submitPayload ?? null);
|
|
2365
|
+
if (!entryJson || !submitPayloadJson) {
|
|
2366
|
+
return fail('json stringify failed');
|
|
2367
|
+
}
|
|
2368
|
+
try {
|
|
2369
|
+
const raw = fn(entryJson, String(responseId ?? ''), submitPayloadJson, requestId);
|
|
2370
|
+
if (typeof raw !== 'string' || !raw) {
|
|
2371
|
+
return fail('empty result');
|
|
2372
|
+
}
|
|
2373
|
+
const parsed = parseResponsesConversationResumeResult(raw);
|
|
2374
|
+
return parsed ?? fail('invalid payload');
|
|
2375
|
+
}
|
|
2376
|
+
catch (error) {
|
|
2377
|
+
const reason = error instanceof Error ? error.message : String(error ?? 'unknown');
|
|
2378
|
+
return fail(reason);
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2180
2381
|
export function enforceChatBudgetWithNative(chat, allowedBytes, systemTextLimit) {
|
|
2181
2382
|
const capability = 'enforceChatBudgetJson';
|
|
2182
2383
|
const fail = (reason) => failNativeRequired(capability, reason);
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export declare function parseRoutingInstructionKindsWithNative(request: unknown): string[];
|
|
2
|
+
export declare function parseRoutingInstructionsWithNative(messages: Array<Record<string, unknown>>): Array<Record<string, unknown>>;
|
|
2
3
|
export declare function cleanRoutingInstructionMarkersWithNative(request: Record<string, unknown>): Record<string, unknown>;
|
|
@@ -36,6 +36,19 @@ function parseRecordPayload(raw) {
|
|
|
36
36
|
return null;
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
+
function parseRecordArrayPayload(raw) {
|
|
40
|
+
try {
|
|
41
|
+
const parsed = JSON.parse(raw);
|
|
42
|
+
if (!Array.isArray(parsed)) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const records = parsed.filter((entry) => entry && typeof entry === 'object' && !Array.isArray(entry));
|
|
46
|
+
return records.length === parsed.length ? records : null;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
39
52
|
export function parseRoutingInstructionKindsWithNative(request) {
|
|
40
53
|
const capability = 'parseRoutingInstructionKindsJson';
|
|
41
54
|
const fail = (reason) => failNativeRequired(capability, reason);
|
|
@@ -60,6 +73,30 @@ export function parseRoutingInstructionKindsWithNative(request) {
|
|
|
60
73
|
return fail(reason);
|
|
61
74
|
}
|
|
62
75
|
}
|
|
76
|
+
export function parseRoutingInstructionsWithNative(messages) {
|
|
77
|
+
const capability = 'parseRoutingInstructionsJson';
|
|
78
|
+
const fail = (reason) => failNativeRequired(capability, reason);
|
|
79
|
+
const fn = readNativeFunction(capability);
|
|
80
|
+
if (!fn) {
|
|
81
|
+
return fail();
|
|
82
|
+
}
|
|
83
|
+
const messagesJson = safeStringify(messages);
|
|
84
|
+
if (!messagesJson) {
|
|
85
|
+
return fail('json stringify failed');
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const result = fn(messagesJson);
|
|
89
|
+
if (typeof result !== 'string' || !result) {
|
|
90
|
+
return fail('empty result');
|
|
91
|
+
}
|
|
92
|
+
const parsed = parseRecordArrayPayload(result);
|
|
93
|
+
return parsed ?? fail('invalid payload');
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
const reason = error instanceof Error ? error.message : String(error ?? 'unknown');
|
|
97
|
+
return fail(reason);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
63
100
|
export function cleanRoutingInstructionMarkersWithNative(request) {
|
|
64
101
|
const capability = 'cleanRoutingInstructionMarkersJson';
|
|
65
102
|
const fail = (reason) => failNativeRequired(capability, reason);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { LoadBalancingPolicy, RoutePoolTier } from '../types.js';
|
|
2
|
+
import type { ProviderRegistry } from '../provider-registry.js';
|
|
3
|
+
export type ResolvedTierLoadBalancing = {
|
|
4
|
+
strategy: LoadBalancingPolicy['strategy'];
|
|
5
|
+
weights?: Record<string, number>;
|
|
6
|
+
};
|
|
7
|
+
export declare function resolveTierLoadBalancing(tier: RoutePoolTier, globalPolicy?: LoadBalancingPolicy): ResolvedTierLoadBalancing;
|
|
8
|
+
export declare function resolveGroupWeight(groupId: string, weights?: Record<string, number>): number;
|
|
9
|
+
export declare function buildGroupWeights(groups: Map<string, string[]>, weights?: Record<string, number>): Record<string, number> | undefined;
|
|
10
|
+
export declare function hasNonUniformWeights(candidates: string[], weights?: Record<string, number>): boolean;
|
|
11
|
+
export declare function buildCandidateWeights(opts: {
|
|
12
|
+
candidates: string[];
|
|
13
|
+
providerRegistry: ProviderRegistry;
|
|
14
|
+
staticWeights?: Record<string, number>;
|
|
15
|
+
dynamicWeights?: Record<string, number>;
|
|
16
|
+
}): Record<string, number> | undefined;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { extractProviderId, getProviderModelId } from './key-parsing.js';
|
|
2
|
+
export function resolveTierLoadBalancing(tier, globalPolicy) {
|
|
3
|
+
const tierPolicy = tier.loadBalancing;
|
|
4
|
+
return {
|
|
5
|
+
strategy: tierPolicy?.strategy ?? globalPolicy?.strategy ?? 'round-robin',
|
|
6
|
+
weights: tierPolicy?.weights ?? globalPolicy?.weights
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export function resolveGroupWeight(groupId, weights) {
|
|
10
|
+
if (!weights) {
|
|
11
|
+
return 1;
|
|
12
|
+
}
|
|
13
|
+
const direct = weights[groupId];
|
|
14
|
+
if (typeof direct === 'number' && Number.isFinite(direct) && direct > 0) {
|
|
15
|
+
return direct;
|
|
16
|
+
}
|
|
17
|
+
const providerId = groupId.split('.')[0] ?? groupId;
|
|
18
|
+
const providerOnly = weights[providerId];
|
|
19
|
+
if (typeof providerOnly === 'number' && Number.isFinite(providerOnly) && providerOnly > 0) {
|
|
20
|
+
return providerOnly;
|
|
21
|
+
}
|
|
22
|
+
return 1;
|
|
23
|
+
}
|
|
24
|
+
export function buildGroupWeights(groups, weights) {
|
|
25
|
+
if (!groups.size || !weights) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
const out = {};
|
|
29
|
+
let hasExplicit = false;
|
|
30
|
+
for (const [groupId] of groups.entries()) {
|
|
31
|
+
const resolved = resolveGroupWeight(groupId, weights);
|
|
32
|
+
out[groupId] = resolved;
|
|
33
|
+
if (resolved !== 1) {
|
|
34
|
+
hasExplicit = true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return hasExplicit ? out : undefined;
|
|
38
|
+
}
|
|
39
|
+
export function hasNonUniformWeights(candidates, weights) {
|
|
40
|
+
if (!weights || candidates.length < 2) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
let ref;
|
|
44
|
+
for (const key of candidates) {
|
|
45
|
+
const raw = weights[key];
|
|
46
|
+
if (typeof raw !== 'number' || !Number.isFinite(raw)) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (ref === undefined) {
|
|
50
|
+
ref = raw;
|
|
51
|
+
}
|
|
52
|
+
else if (Math.abs(raw - ref) > 1e-6) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
export function buildCandidateWeights(opts) {
|
|
59
|
+
const { candidates, providerRegistry, staticWeights, dynamicWeights } = opts;
|
|
60
|
+
if ((!staticWeights || Object.keys(staticWeights).length === 0) && (!dynamicWeights || Object.keys(dynamicWeights).length === 0)) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
const out = {};
|
|
64
|
+
let hasExplicit = false;
|
|
65
|
+
for (const key of candidates) {
|
|
66
|
+
const dynamic = dynamicWeights?.[key];
|
|
67
|
+
const staticWeight = resolveCandidateWeight(key, staticWeights, providerRegistry);
|
|
68
|
+
const resolved = multiplyPositiveWeights(dynamic, staticWeight);
|
|
69
|
+
if (resolved !== undefined) {
|
|
70
|
+
out[key] = resolved;
|
|
71
|
+
if (resolved !== 1) {
|
|
72
|
+
hasExplicit = true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (!hasExplicit) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
function resolveCandidateWeight(key, weights, providerRegistry) {
|
|
82
|
+
if (!weights) {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
const direct = normalizePositiveWeight(weights[key]);
|
|
86
|
+
if (direct !== undefined) {
|
|
87
|
+
return direct;
|
|
88
|
+
}
|
|
89
|
+
const providerId = extractProviderId(key) ?? '';
|
|
90
|
+
if (!providerId) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const modelId = getProviderModelId(key, providerRegistry) ?? '';
|
|
95
|
+
if (modelId) {
|
|
96
|
+
const grouped = normalizePositiveWeight(weights[`${providerId}.${modelId}`]);
|
|
97
|
+
if (grouped !== undefined) {
|
|
98
|
+
return grouped;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// Ignore registry misses and fall back to provider-only weight.
|
|
104
|
+
}
|
|
105
|
+
return normalizePositiveWeight(weights[providerId]);
|
|
106
|
+
}
|
|
107
|
+
function normalizePositiveWeight(value) {
|
|
108
|
+
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : undefined;
|
|
109
|
+
}
|
|
110
|
+
function multiplyPositiveWeights(...values) {
|
|
111
|
+
let resolved;
|
|
112
|
+
for (const value of values) {
|
|
113
|
+
const normalized = normalizePositiveWeight(value);
|
|
114
|
+
if (normalized === undefined) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
resolved = resolved === undefined ? normalized : Math.max(1, Math.round(resolved * normalized));
|
|
118
|
+
}
|
|
119
|
+
return resolved;
|
|
120
|
+
}
|
|
@@ -3,6 +3,7 @@ import { type ResolvedContextWeightedConfig } from '../context-weighted.js';
|
|
|
3
3
|
import { type ResolvedHealthWeightedConfig } from '../health-weighted.js';
|
|
4
4
|
import type { RoutePoolTier } from '../types.js';
|
|
5
5
|
import type { SelectionDeps, TrySelectFromTierOptions } from './selection-deps.js';
|
|
6
|
+
import { type ResolvedTierLoadBalancing } from './tier-load-balancing.js';
|
|
6
7
|
export declare function selectProviderKeyWithQuotaBuckets(opts: {
|
|
7
8
|
routeName: string;
|
|
8
9
|
tier: RoutePoolTier;
|
|
@@ -19,6 +20,7 @@ export declare function selectProviderKeyWithQuotaBuckets(opts: {
|
|
|
19
20
|
nowForWeights: number;
|
|
20
21
|
healthWeightedCfg: ResolvedHealthWeightedConfig;
|
|
21
22
|
contextWeightedCfg: ResolvedContextWeightedConfig;
|
|
23
|
+
tierLoadBalancing: ResolvedTierLoadBalancing;
|
|
22
24
|
quotaView: NonNullable<SelectionDeps['quotaView']>;
|
|
23
25
|
isAvailable: (key: string) => boolean;
|
|
24
26
|
selectFirstAvailable: (keys: string[]) => string | null;
|
|
@@ -2,6 +2,7 @@ import { computeContextMultiplier } from '../context-weighted.js';
|
|
|
2
2
|
import { computeHealthWeight } from '../health-weighted.js';
|
|
3
3
|
import { buildQuotaBuckets } from './native-router-hotpath.js';
|
|
4
4
|
import { computeContextWeightMultipliers } from './context-weight-multipliers.js';
|
|
5
|
+
import { buildCandidateWeights, buildGroupWeights, hasNonUniformWeights } from './tier-load-balancing.js';
|
|
5
6
|
import { pickPriorityGroup } from './tier-priority.js';
|
|
6
7
|
import { extractProviderId, getProviderModelId } from './key-parsing.js';
|
|
7
8
|
function buildPrimaryTargetGroups(candidates, deps) {
|
|
@@ -25,52 +26,8 @@ function buildPrimaryTargetGroups(candidates, deps) {
|
|
|
25
26
|
}
|
|
26
27
|
return groups;
|
|
27
28
|
}
|
|
28
|
-
function resolveGroupWeight(groupId, weights) {
|
|
29
|
-
if (!weights) {
|
|
30
|
-
return 1;
|
|
31
|
-
}
|
|
32
|
-
const direct = weights[groupId];
|
|
33
|
-
if (typeof direct === 'number' && Number.isFinite(direct) && direct > 0) {
|
|
34
|
-
return direct;
|
|
35
|
-
}
|
|
36
|
-
const providerId = groupId.split('.')[0] ?? groupId;
|
|
37
|
-
const providerOnly = weights[providerId];
|
|
38
|
-
if (typeof providerOnly === 'number' && Number.isFinite(providerOnly) && providerOnly > 0) {
|
|
39
|
-
return providerOnly;
|
|
40
|
-
}
|
|
41
|
-
return 1;
|
|
42
|
-
}
|
|
43
|
-
function buildGroupWeights(groups, weights) {
|
|
44
|
-
if (!groups.size) {
|
|
45
|
-
return undefined;
|
|
46
|
-
}
|
|
47
|
-
const out = {};
|
|
48
|
-
for (const [groupId] of groups.entries()) {
|
|
49
|
-
out[groupId] = resolveGroupWeight(groupId, weights);
|
|
50
|
-
}
|
|
51
|
-
return out;
|
|
52
|
-
}
|
|
53
|
-
function hasNonUniformWeights(candidates, weights) {
|
|
54
|
-
if (!weights || candidates.length < 2) {
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
let ref;
|
|
58
|
-
for (const key of candidates) {
|
|
59
|
-
const raw = weights[key];
|
|
60
|
-
if (typeof raw !== 'number' || !Number.isFinite(raw)) {
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
if (ref === undefined) {
|
|
64
|
-
ref = raw;
|
|
65
|
-
}
|
|
66
|
-
else if (Math.abs(raw - ref) > 1e-6) {
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
29
|
export function selectProviderKeyWithQuotaBuckets(opts) {
|
|
73
|
-
const { routeName, tier, stickyKey, candidates, isSafePool, deps, options, contextResult, warnRatio, isRecoveryAttempt, now, nowForWeights, healthWeightedCfg, contextWeightedCfg, quotaView, isAvailable, selectFirstAvailable, applyAliasStickyQueuePinning, preferAntigravityAliasesOnRetry } = opts;
|
|
30
|
+
const { routeName, tier, stickyKey, candidates, isSafePool, deps, options, contextResult, warnRatio, isRecoveryAttempt, now, nowForWeights, healthWeightedCfg, contextWeightedCfg, tierLoadBalancing, quotaView, isAvailable, selectFirstAvailable, applyAliasStickyQueuePinning, preferAntigravityAliasesOnRetry } = opts;
|
|
74
31
|
const bucketInputs = candidates.map((key, order) => {
|
|
75
32
|
const entry = quotaView(key);
|
|
76
33
|
const penaltyRaw = entry?.selectionPenalty;
|
|
@@ -100,35 +57,56 @@ export function selectProviderKeyWithQuotaBuckets(opts) {
|
|
|
100
57
|
bucketCandidates = preferAntigravityAliasesOnRetry(bucketCandidates);
|
|
101
58
|
}
|
|
102
59
|
bucketCandidates = applyAliasStickyQueuePinning(bucketCandidates);
|
|
103
|
-
const
|
|
60
|
+
const quotaWeights = {};
|
|
104
61
|
for (const item of bucket) {
|
|
105
62
|
if (healthWeightedCfg.enabled) {
|
|
106
63
|
const entry = quotaView(item.key);
|
|
107
64
|
const { weight } = computeHealthWeight(entry, nowForWeights, healthWeightedCfg);
|
|
108
|
-
|
|
65
|
+
quotaWeights[item.key] = weight;
|
|
109
66
|
}
|
|
110
67
|
else {
|
|
111
|
-
|
|
68
|
+
quotaWeights[item.key] = Math.max(1, Math.floor(100 / (1 + Math.max(0, item.penalty))));
|
|
112
69
|
}
|
|
113
70
|
}
|
|
114
|
-
|
|
71
|
+
const contextWeights = (() => {
|
|
72
|
+
if (!isSafePool || !contextWeightedCfg.enabled) {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
115
75
|
const ctx = computeContextWeightMultipliers({
|
|
116
76
|
candidates: bucketCandidates,
|
|
117
77
|
usage: contextResult.usage,
|
|
118
78
|
warnRatio,
|
|
119
79
|
cfg: contextWeightedCfg
|
|
120
80
|
});
|
|
121
|
-
if (ctx) {
|
|
122
|
-
|
|
123
|
-
const m = computeContextMultiplier({
|
|
124
|
-
effectiveSafeRefTokens: ctx.ref,
|
|
125
|
-
effectiveSafeTokens: ctx.eff[key] ?? 1,
|
|
126
|
-
cfg: contextWeightedCfg
|
|
127
|
-
});
|
|
128
|
-
bucketWeights[key] = Math.max(1, Math.round((bucketWeights[key] ?? 1) * m));
|
|
129
|
-
}
|
|
81
|
+
if (!ctx) {
|
|
82
|
+
return undefined;
|
|
130
83
|
}
|
|
131
|
-
|
|
84
|
+
const out = {};
|
|
85
|
+
for (const key of bucketCandidates) {
|
|
86
|
+
const m = computeContextMultiplier({
|
|
87
|
+
effectiveSafeRefTokens: ctx.ref,
|
|
88
|
+
effectiveSafeTokens: ctx.eff[key] ?? 1,
|
|
89
|
+
cfg: contextWeightedCfg
|
|
90
|
+
});
|
|
91
|
+
out[key] = Math.max(1, Math.round(100 * m));
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
})();
|
|
95
|
+
const bucketWeights = buildCandidateWeights({
|
|
96
|
+
candidates: bucketCandidates,
|
|
97
|
+
providerRegistry: deps.providerRegistry,
|
|
98
|
+
staticWeights: tierLoadBalancing.weights,
|
|
99
|
+
dynamicWeights: Object.keys(quotaWeights).length || contextWeights
|
|
100
|
+
? Object.fromEntries(bucketCandidates.map((key) => {
|
|
101
|
+
const quotaWeight = quotaWeights[key];
|
|
102
|
+
const contextWeight = contextWeights?.[key];
|
|
103
|
+
const combined = typeof contextWeight === 'number'
|
|
104
|
+
? Math.max(1, Math.round((quotaWeight ?? 1) * contextWeight))
|
|
105
|
+
: quotaWeight;
|
|
106
|
+
return [key, combined ?? 1];
|
|
107
|
+
}))
|
|
108
|
+
: undefined
|
|
109
|
+
});
|
|
132
110
|
if (tier.mode === 'priority') {
|
|
133
111
|
if (!isRecoveryAttempt) {
|
|
134
112
|
const group = pickPriorityGroup({
|
|
@@ -142,13 +120,13 @@ export function selectProviderKeyWithQuotaBuckets(opts) {
|
|
|
142
120
|
}
|
|
143
121
|
const groupWeights = {};
|
|
144
122
|
for (const key of group.groupCandidates) {
|
|
145
|
-
groupWeights[key] = bucketWeights[key] ?? 1;
|
|
123
|
+
groupWeights[key] = bucketWeights?.[key] ?? 1;
|
|
146
124
|
}
|
|
147
125
|
const allowGrouped = !hasNonUniformWeights(group.groupCandidates, bucketWeights);
|
|
148
|
-
if (allowGrouped &&
|
|
126
|
+
if (allowGrouped && tierLoadBalancing.strategy !== 'sticky') {
|
|
149
127
|
const groups = buildPrimaryTargetGroups(group.groupCandidates, deps);
|
|
150
128
|
if (groups.size > 0) {
|
|
151
|
-
const groupWeightMap = buildGroupWeights(groups,
|
|
129
|
+
const groupWeightMap = buildGroupWeights(groups, tierLoadBalancing.weights);
|
|
152
130
|
const selected = deps.loadBalancer.selectGrouped({
|
|
153
131
|
routeName: `${routeName}:${tier.id}:priority:${priority}:group:${group.groupId}`,
|
|
154
132
|
groups,
|
|
@@ -185,17 +163,17 @@ export function selectProviderKeyWithQuotaBuckets(opts) {
|
|
|
185
163
|
continue;
|
|
186
164
|
}
|
|
187
165
|
const allowGrouped = !hasNonUniformWeights(bucketCandidates, bucketWeights);
|
|
188
|
-
if (allowGrouped &&
|
|
166
|
+
if (allowGrouped && tierLoadBalancing.strategy !== 'sticky') {
|
|
189
167
|
const groups = buildPrimaryTargetGroups(bucketCandidates, deps);
|
|
190
168
|
if (groups.size > 0) {
|
|
191
|
-
const groupWeightMap = buildGroupWeights(groups,
|
|
169
|
+
const groupWeightMap = buildGroupWeights(groups, tierLoadBalancing.weights);
|
|
192
170
|
const selected = deps.loadBalancer.selectGrouped({
|
|
193
171
|
routeName: `${routeName}:${tier.id}:${priority}`,
|
|
194
172
|
groups,
|
|
195
173
|
stickyKey: options.allowAliasRotation ? undefined : stickyKey,
|
|
196
174
|
weights: groupWeightMap,
|
|
197
175
|
availabilityCheck: isAvailable
|
|
198
|
-
}, tier.mode === 'round-robin' ? 'round-robin' :
|
|
176
|
+
}, tier.mode === 'round-robin' ? 'round-robin' : tierLoadBalancing.strategy);
|
|
199
177
|
if (selected) {
|
|
200
178
|
return selected;
|
|
201
179
|
}
|
|
@@ -207,7 +185,7 @@ export function selectProviderKeyWithQuotaBuckets(opts) {
|
|
|
207
185
|
stickyKey: options.allowAliasRotation ? undefined : stickyKey,
|
|
208
186
|
weights: bucketWeights,
|
|
209
187
|
availabilityCheck: isAvailable
|
|
210
|
-
}, tier.mode === 'round-robin' ? 'round-robin' :
|
|
188
|
+
}, tier.mode === 'round-robin' ? 'round-robin' : tierLoadBalancing.strategy);
|
|
211
189
|
if (selected) {
|
|
212
190
|
return selected;
|
|
213
191
|
}
|