@jsonstudio/llms 0.6.954 → 0.6.1172

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/dist/conversion/hub/operation-table/operation-table-runner.d.ts +18 -0
  2. package/dist/conversion/hub/operation-table/operation-table-runner.js +158 -0
  3. package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.d.ts +8 -0
  4. package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +303 -0
  5. package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.d.ts +8 -0
  6. package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.js +413 -0
  7. package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.d.ts +7 -0
  8. package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +841 -0
  9. package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.d.ts +21 -0
  10. package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +535 -0
  11. package/dist/conversion/hub/ops/operations.d.ts +19 -0
  12. package/dist/conversion/hub/ops/operations.js +126 -0
  13. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +9 -0
  14. package/dist/conversion/hub/pipeline/hub-pipeline.js +489 -19
  15. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +6 -0
  16. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +11 -0
  17. package/dist/conversion/hub/policy/policy-engine.js +41 -9
  18. package/dist/conversion/hub/policy/protocol-spec.d.ts +25 -0
  19. package/dist/conversion/hub/policy/protocol-spec.js +73 -23
  20. package/dist/conversion/hub/process/chat-process.js +252 -41
  21. package/dist/conversion/hub/response/provider-response.js +175 -2
  22. package/dist/conversion/hub/response/response-runtime.js +1 -1
  23. package/dist/conversion/hub/semantic-mappers/anthropic-mapper.d.ts +1 -8
  24. package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +1 -365
  25. package/dist/conversion/hub/semantic-mappers/chat-mapper.d.ts +1 -8
  26. package/dist/conversion/hub/semantic-mappers/chat-mapper.js +1 -467
  27. package/dist/conversion/hub/semantic-mappers/gemini-mapper.d.ts +1 -7
  28. package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +1 -903
  29. package/dist/conversion/hub/semantic-mappers/responses-mapper.d.ts +1 -21
  30. package/dist/conversion/hub/semantic-mappers/responses-mapper.js +1 -593
  31. package/dist/conversion/hub/tool-surface/tool-surface-engine.d.ts +18 -0
  32. package/dist/conversion/hub/tool-surface/tool-surface-engine.js +571 -0
  33. package/dist/conversion/responses/responses-openai-bridge.js +14 -2
  34. package/dist/conversion/shared/bridge-message-utils.js +2 -8
  35. package/dist/conversion/shared/bridge-policies.js +5 -105
  36. package/dist/conversion/shared/gemini-tool-utils.js +89 -15
  37. package/dist/conversion/shared/protocol-field-allowlists.d.ts +7 -0
  38. package/dist/conversion/shared/protocol-field-allowlists.js +145 -0
  39. package/dist/conversion/shared/reasoning-tool-normalizer.js +4 -2
  40. package/dist/conversion/shared/snapshot-hooks.js +166 -3
  41. package/dist/conversion/shared/text-markup-normalizer.d.ts +2 -0
  42. package/dist/conversion/shared/text-markup-normalizer.js +345 -9
  43. package/dist/conversion/shared/thought-signature-validator.d.ts +52 -0
  44. package/dist/conversion/shared/thought-signature-validator.js +170 -0
  45. package/dist/conversion/shared/tool-argument-repairer.d.ts +39 -0
  46. package/dist/conversion/shared/tool-argument-repairer.js +56 -0
  47. package/dist/conversion/shared/tool-call-id-manager.d.ts +113 -0
  48. package/dist/conversion/shared/tool-call-id-manager.js +231 -0
  49. package/dist/conversion/shared/tool-canonicalizer.js +2 -11
  50. package/dist/router/virtual-router/bootstrap.js +70 -5
  51. package/dist/router/virtual-router/context-advisor.d.ts +4 -0
  52. package/dist/router/virtual-router/context-advisor.js +3 -0
  53. package/dist/router/virtual-router/context-weighted.d.ts +31 -0
  54. package/dist/router/virtual-router/context-weighted.js +54 -0
  55. package/dist/router/virtual-router/engine-selection.js +284 -47
  56. package/dist/router/virtual-router/engine.d.ts +3 -0
  57. package/dist/router/virtual-router/engine.js +142 -33
  58. package/dist/router/virtual-router/health-weighted.d.ts +25 -0
  59. package/dist/router/virtual-router/health-weighted.js +63 -0
  60. package/dist/router/virtual-router/load-balancer.d.ts +2 -0
  61. package/dist/router/virtual-router/load-balancer.js +45 -16
  62. package/dist/router/virtual-router/routing-instructions.js +17 -1
  63. package/dist/router/virtual-router/sticky-session-store.js +136 -24
  64. package/dist/router/virtual-router/stop-message-file-resolver.d.ts +1 -0
  65. package/dist/router/virtual-router/stop-message-file-resolver.js +74 -0
  66. package/dist/router/virtual-router/stop-message-state-sync.d.ts +15 -0
  67. package/dist/router/virtual-router/stop-message-state-sync.js +57 -0
  68. package/dist/router/virtual-router/types.d.ts +98 -0
  69. package/dist/servertool/clock/config.d.ts +7 -0
  70. package/dist/servertool/clock/config.js +27 -0
  71. package/dist/servertool/clock/daemon.d.ts +3 -0
  72. package/dist/servertool/clock/daemon.js +79 -0
  73. package/dist/servertool/clock/io.d.ts +2 -0
  74. package/dist/servertool/clock/io.js +13 -0
  75. package/dist/servertool/clock/paths.d.ts +4 -0
  76. package/dist/servertool/clock/paths.js +25 -0
  77. package/dist/servertool/clock/session-store.d.ts +3 -0
  78. package/dist/servertool/clock/session-store.js +56 -0
  79. package/dist/servertool/clock/state.d.ts +5 -0
  80. package/dist/servertool/clock/state.js +62 -0
  81. package/dist/servertool/clock/task-store.d.ts +5 -0
  82. package/dist/servertool/clock/task-store.js +4 -0
  83. package/dist/servertool/clock/tasks.d.ts +17 -0
  84. package/dist/servertool/clock/tasks.js +221 -0
  85. package/dist/servertool/clock/types.d.ts +36 -0
  86. package/dist/servertool/clock/types.js +1 -0
  87. package/dist/servertool/engine.d.ts +2 -0
  88. package/dist/servertool/engine.js +161 -7
  89. package/dist/servertool/followup-shadow.d.ts +16 -0
  90. package/dist/servertool/followup-shadow.js +145 -0
  91. package/dist/servertool/handlers/apply-patch-guard.js +1 -265
  92. package/dist/servertool/handlers/clock-auto.d.ts +1 -0
  93. package/dist/servertool/handlers/clock-auto.js +160 -0
  94. package/dist/servertool/handlers/clock.d.ts +1 -0
  95. package/dist/servertool/handlers/clock.js +197 -0
  96. package/dist/servertool/handlers/exec-command-guard.js +7 -555
  97. package/dist/servertool/handlers/followup-request-builder.d.ts +15 -7
  98. package/dist/servertool/handlers/followup-request-builder.js +248 -28
  99. package/dist/servertool/handlers/gemini-empty-reply-continue.js +62 -169
  100. package/dist/servertool/handlers/iflow-model-error-retry.js +18 -28
  101. package/dist/servertool/handlers/recursive-detection-guard.d.ts +1 -0
  102. package/dist/servertool/handlers/recursive-detection-guard.js +333 -0
  103. package/dist/servertool/handlers/stop-message-auto.js +47 -175
  104. package/dist/servertool/handlers/vision.d.ts +7 -1
  105. package/dist/servertool/handlers/vision.js +61 -117
  106. package/dist/servertool/handlers/web-search.d.ts +7 -1
  107. package/dist/servertool/handlers/web-search.js +122 -105
  108. package/dist/servertool/reenter-backend.d.ts +23 -0
  109. package/dist/servertool/reenter-backend.js +18 -0
  110. package/dist/servertool/server-side-tools.d.ts +3 -2
  111. package/dist/servertool/server-side-tools.js +64 -10
  112. package/dist/servertool/types.d.ts +92 -3
  113. package/dist/sse/json-to-sse/event-generators/responses.js +3 -21
  114. package/dist/sse/shared/serializers/responses-event-serializer.d.ts +8 -0
  115. package/dist/sse/shared/serializers/responses-event-serializer.js +19 -0
  116. package/dist/sse/shared/writer.js +24 -7
  117. package/dist/tools/apply-patch/execution-capturer.js +3 -1
  118. package/dist/tools/apply-patch/json/parse-loose.d.ts +3 -0
  119. package/dist/tools/apply-patch/json/parse-loose.js +139 -0
  120. package/dist/tools/apply-patch/patch-text/context-diff.d.ts +1 -0
  121. package/dist/tools/apply-patch/patch-text/context-diff.js +173 -0
  122. package/dist/tools/apply-patch/patch-text/git-diff.d.ts +1 -0
  123. package/dist/tools/apply-patch/patch-text/git-diff.js +138 -0
  124. package/dist/tools/apply-patch/patch-text/looks-like-patch.d.ts +1 -0
  125. package/dist/tools/apply-patch/patch-text/looks-like-patch.js +13 -0
  126. package/dist/tools/apply-patch/patch-text/normalize.d.ts +3 -0
  127. package/dist/tools/apply-patch/patch-text/normalize.js +262 -0
  128. package/dist/tools/apply-patch/structured/coercion.d.ts +3 -0
  129. package/dist/tools/apply-patch/structured/coercion.js +82 -0
  130. package/dist/tools/apply-patch/validation/shared.d.ts +3 -0
  131. package/dist/tools/apply-patch/validation/shared.js +6 -0
  132. package/dist/tools/apply-patch/validator.d.ts +2 -2
  133. package/dist/tools/apply-patch/validator.js +6 -556
  134. package/package.json +1 -1
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Tool Argument Repairer
3
+ *
4
+ * Tool calls require `function.arguments` to be a JSON string. Models often emit JSON-ish
5
+ * payloads (single quotes, fenced blocks, comments, trailing commas, etc.).
6
+ *
7
+ * This module provides a deterministic, best-effort repair surface that returns a JSON string
8
+ * or `{}` and never throws.
9
+ */
10
+ import { repairArgumentsToString } from './jsonish.js';
11
+ export class ToolArgumentRepairer {
12
+ repairToString(args) {
13
+ return repairArgumentsToString(args);
14
+ }
15
+ validateAndRepair(toolName, args) {
16
+ const repaired = this.repairToString(args);
17
+ try {
18
+ JSON.parse(repaired);
19
+ return {
20
+ repaired,
21
+ success: true
22
+ };
23
+ }
24
+ catch (error) {
25
+ return {
26
+ repaired,
27
+ success: false,
28
+ error: error instanceof Error ? error.message : String(error)
29
+ };
30
+ }
31
+ }
32
+ /**
33
+ * 批量修复工具参数
34
+ *
35
+ * @param toolCalls - 工具调用列表
36
+ * @returns 修复后的工具调用列表
37
+ */
38
+ repairToolCalls(toolCalls) {
39
+ return toolCalls.map((call) => ({
40
+ name: call.name,
41
+ arguments: this.repairToString(call.arguments)
42
+ }));
43
+ }
44
+ }
45
+ /**
46
+ * 快捷函数:修复工具参数
47
+ */
48
+ export function repairToolArguments(args) {
49
+ return new ToolArgumentRepairer().repairToString(args);
50
+ }
51
+ /**
52
+ * 快捷函数:验证并修复工具参数
53
+ */
54
+ export function validateToolArguments(toolName, args) {
55
+ return new ToolArgumentRepairer().validateAndRepair(toolName, args);
56
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Tool Call ID Manager
3
+ *
4
+ * 提供统一的工具调用 ID 生成、规范化和风格管理功能。
5
+ * 支持 'fc' (function call) 和 'preserve' 两种风格。
6
+ */
7
+ import type { JsonObject } from '../hub/types/json.js';
8
+ export type ToolCallIdStyle = 'fc' | 'preserve';
9
+ export interface ToolCallIdManagerOptions {
10
+ /**
11
+ * ID 风格
12
+ * - 'fc': 使用 fc_ 前缀的短 ID(如 fc_abc123)
13
+ * - 'preserve': 保留原始 ID
14
+ */
15
+ style?: ToolCallIdStyle;
16
+ /**
17
+ * ID 前缀(仅用于 'fc' 风格)
18
+ * 默认 'fc_'
19
+ */
20
+ prefix?: string;
21
+ /**
22
+ * ID 长度(仅用于 'fc' 风格)
23
+ * 默认 8
24
+ */
25
+ idLength?: number;
26
+ }
27
+ /**
28
+ * 工具调用 ID 管理器
29
+ */
30
+ export declare class ToolCallIdManager {
31
+ private options;
32
+ private counter;
33
+ constructor(options?: ToolCallIdManagerOptions);
34
+ /**
35
+ * 生成新的工具调用 ID
36
+ */
37
+ generateId(): string;
38
+ /**
39
+ * 规范化工具调用 ID
40
+ *
41
+ * @param id - 原始 ID
42
+ * @returns 规范化后的 ID
43
+ */
44
+ normalizeId(id: string | undefined | null): string;
45
+ /**
46
+ * 规范化工具调用 ID(带别名注册)
47
+ *
48
+ * @param id - 原始 ID
49
+ * @param aliasMap - 别名映射(用于 preserve 风格)
50
+ * @returns 规范化后的 ID
51
+ */
52
+ normalizeIdWithAlias(id: string | undefined | null, aliasMap?: Map<string, string>): string;
53
+ /**
54
+ * 批量规范化工具调用 ID
55
+ *
56
+ * @param ids - ID 列表
57
+ * @returns 规范化后的 ID 列表
58
+ */
59
+ normalizeIds(ids: (string | undefined | null)[]): string[];
60
+ /**
61
+ * 检查 ID 是否为有效的 fc_ 风格
62
+ */
63
+ isValidFcStyle(id: string): boolean;
64
+ /**
65
+ * 从 ID 中提取基础部分(移除前缀)
66
+ */
67
+ extractBaseId(id: string): string;
68
+ /**
69
+ * 重置计数器
70
+ */
71
+ resetCounter(): void;
72
+ /**
73
+ * 获取当前配置
74
+ */
75
+ getOptions(): ToolCallIdManagerOptions;
76
+ /**
77
+ * 更新配置
78
+ */
79
+ updateOptions(options: Partial<ToolCallIdManagerOptions>): void;
80
+ /**
81
+ * 生成 UUID(用于 preserve 风格)
82
+ */
83
+ private generateUUID;
84
+ }
85
+ /**
86
+ * 规范化工具调用 ID 值
87
+ *
88
+ * @param value - ID 值(可能是字符串或其他类型)
89
+ * @param forceGenerate - 如果为 true,总是生成新 ID
90
+ * @returns 规范化后的 ID 字符串
91
+ */
92
+ export declare function normalizeIdValue(value: unknown, forceGenerate?: boolean): string;
93
+ /**
94
+ * 从对象中提取工具调用 ID
95
+ *
96
+ * @param obj - 包含 ID 的对象
97
+ * @returns 提取的 ID
98
+ */
99
+ export declare function extractToolCallId(obj: unknown): string | undefined;
100
+ /**
101
+ * 创建工具调用 ID 转换器(用于 Responses 格式)
102
+ *
103
+ * @param style - ID 风格
104
+ * @returns 转换器函数
105
+ */
106
+ export declare function createToolCallIdTransformer(style: ToolCallIdStyle): ((id: string) => string) | null;
107
+ /**
108
+ * 在消息列表中强制应用工具调用 ID 风格
109
+ *
110
+ * @param messages - 消息列表
111
+ * @param transformer - ID 转换器
112
+ */
113
+ export declare function enforceToolCallIdStyle(messages: JsonObject[], transformer: (id: string) => string): void;
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Tool Call ID Manager
3
+ *
4
+ * 提供统一的工具调用 ID 生成、规范化和风格管理功能。
5
+ * 支持 'fc' (function call) 和 'preserve' 两种风格。
6
+ */
7
+ const DEFAULT_OPTIONS = {
8
+ style: 'fc',
9
+ prefix: 'fc_',
10
+ idLength: 8
11
+ };
12
+ /**
13
+ * 工具调用 ID 管理器
14
+ */
15
+ export class ToolCallIdManager {
16
+ options;
17
+ counter = 0;
18
+ constructor(options) {
19
+ this.options = { ...DEFAULT_OPTIONS, ...options };
20
+ }
21
+ /**
22
+ * 生成新的工具调用 ID
23
+ */
24
+ generateId() {
25
+ if (this.options.style === 'fc') {
26
+ return `${this.options.prefix}${this.counter++}`;
27
+ }
28
+ // preserve 风格:使用 UUID
29
+ return this.generateUUID();
30
+ }
31
+ /**
32
+ * 规范化工具调用 ID
33
+ *
34
+ * @param id - 原始 ID
35
+ * @returns 规范化后的 ID
36
+ */
37
+ normalizeId(id) {
38
+ if (!id) {
39
+ return this.generateId();
40
+ }
41
+ const trimmed = id.trim();
42
+ if (!trimmed) {
43
+ return this.generateId();
44
+ }
45
+ if (this.options.style === 'fc') {
46
+ // fc 风格:提取或生成 fc_ 风格 ID
47
+ const fcMatch = trimmed.match(/fc_([a-z0-9]+)/i);
48
+ if (fcMatch) {
49
+ return `fc_${fcMatch[1]}`;
50
+ }
51
+ // 如果不是 fc_ 风格,生成新的 fc_ ID
52
+ return this.generateId();
53
+ }
54
+ // preserve 风格:保留原始 ID
55
+ return trimmed;
56
+ }
57
+ /**
58
+ * 规范化工具调用 ID(带别名注册)
59
+ *
60
+ * @param id - 原始 ID
61
+ * @param aliasMap - 别名映射(用于 preserve 风格)
62
+ * @returns 规范化后的 ID
63
+ */
64
+ normalizeIdWithAlias(id, aliasMap) {
65
+ if (!id) {
66
+ return this.generateId();
67
+ }
68
+ const trimmed = id.trim();
69
+ if (!trimmed) {
70
+ return this.generateId();
71
+ }
72
+ if (this.options.style === 'fc') {
73
+ return this.normalizeId(id);
74
+ }
75
+ // preserve 风格:检查是否已有别名
76
+ if (aliasMap && aliasMap.has(trimmed)) {
77
+ return aliasMap.get(trimmed);
78
+ }
79
+ // 没有别名,保留原始 ID
80
+ return trimmed;
81
+ }
82
+ /**
83
+ * 批量规范化工具调用 ID
84
+ *
85
+ * @param ids - ID 列表
86
+ * @returns 规范化后的 ID 列表
87
+ */
88
+ normalizeIds(ids) {
89
+ return ids.map(id => this.normalizeId(id));
90
+ }
91
+ /**
92
+ * 检查 ID 是否为有效的 fc_ 风格
93
+ */
94
+ isValidFcStyle(id) {
95
+ return /^fc_[a-z0-9]+$/i.test(id);
96
+ }
97
+ /**
98
+ * 从 ID 中提取基础部分(移除前缀)
99
+ */
100
+ extractBaseId(id) {
101
+ if (this.options.style === 'fc') {
102
+ const match = id.match(/^fc_(.+)$/i);
103
+ return match ? match[1] : id;
104
+ }
105
+ return id;
106
+ }
107
+ /**
108
+ * 重置计数器
109
+ */
110
+ resetCounter() {
111
+ this.counter = 0;
112
+ }
113
+ /**
114
+ * 获取当前配置
115
+ */
116
+ getOptions() {
117
+ return { ...this.options };
118
+ }
119
+ /**
120
+ * 更新配置
121
+ */
122
+ updateOptions(options) {
123
+ this.options = { ...this.options, ...options };
124
+ }
125
+ /**
126
+ * 生成 UUID(用于 preserve 风格)
127
+ */
128
+ generateUUID() {
129
+ // 简单的 UUID 生成(生产环境建议使用 crypto.randomUUID())
130
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
131
+ const r = Math.random() * 16 | 0;
132
+ const v = c === 'x' ? r : (r & 0x3 | 0x8);
133
+ return v.toString(16);
134
+ });
135
+ }
136
+ }
137
+ /**
138
+ * 规范化工具调用 ID 值
139
+ *
140
+ * @param value - ID 值(可能是字符串或其他类型)
141
+ * @param forceGenerate - 如果为 true,总是生成新 ID
142
+ * @returns 规范化后的 ID 字符串
143
+ */
144
+ export function normalizeIdValue(value, forceGenerate = false) {
145
+ if (forceGenerate) {
146
+ const manager = new ToolCallIdManager();
147
+ return manager.generateId();
148
+ }
149
+ if (typeof value === 'string' && value.trim()) {
150
+ return value.trim();
151
+ }
152
+ // 生成默认 ID
153
+ const manager = new ToolCallIdManager();
154
+ return manager.generateId();
155
+ }
156
+ /**
157
+ * 从对象中提取工具调用 ID
158
+ *
159
+ * @param obj - 包含 ID 的对象
160
+ * @returns 提取的 ID
161
+ */
162
+ export function extractToolCallId(obj) {
163
+ if (!obj || typeof obj !== 'object') {
164
+ return undefined;
165
+ }
166
+ const record = obj;
167
+ // 按优先级查找:tool_call_id > call_id > id > tool_use_id
168
+ const id = record.tool_call_id ??
169
+ record.call_id ??
170
+ record.id ??
171
+ record.tool_use_id;
172
+ return typeof id === 'string' ? id : undefined;
173
+ }
174
+ /**
175
+ * 创建工具调用 ID 转换器(用于 Responses 格式)
176
+ *
177
+ * @param style - ID 风格
178
+ * @returns 转换器函数
179
+ */
180
+ export function createToolCallIdTransformer(style) {
181
+ const manager = new ToolCallIdManager({ style });
182
+ const aliasMap = new Map();
183
+ if (style === 'fc') {
184
+ return (id) => manager.normalizeId(id);
185
+ }
186
+ if (style === 'preserve') {
187
+ return (id) => {
188
+ const normalized = manager.normalizeIdWithAlias(id, aliasMap);
189
+ // 如果 ID 是新的,注册别名
190
+ if (!aliasMap.has(id)) {
191
+ aliasMap.set(id, normalized);
192
+ }
193
+ return normalized;
194
+ };
195
+ }
196
+ return null;
197
+ }
198
+ /**
199
+ * 在消息列表中强制应用工具调用 ID 风格
200
+ *
201
+ * @param messages - 消息列表
202
+ * @param transformer - ID 转换器
203
+ */
204
+ export function enforceToolCallIdStyle(messages, transformer) {
205
+ if (!messages || !Array.isArray(messages)) {
206
+ return;
207
+ }
208
+ for (const message of messages) {
209
+ if (!message || typeof message !== 'object')
210
+ continue;
211
+ const role = message.role;
212
+ // 处理 assistant 消息的 tool_calls
213
+ if (role === 'assistant' && Array.isArray(message.tool_calls)) {
214
+ for (const call of message.tool_calls) {
215
+ if (!call || typeof call !== 'object')
216
+ continue;
217
+ const id = extractToolCallId(call);
218
+ if (id) {
219
+ call.id = transformer(id);
220
+ }
221
+ }
222
+ }
223
+ // 处理 tool 消息的 tool_call_id
224
+ if (role === 'tool') {
225
+ const id = extractToolCallId(message);
226
+ if (id) {
227
+ message.tool_call_id = transformer(id);
228
+ }
229
+ }
230
+ }
231
+ }
@@ -1,17 +1,8 @@
1
1
  // Minimal canonicalizer to ensure tool_calls invariants without altering semantics
2
+ import { repairToolArguments } from './tool-argument-repairer.js';
2
3
  function isObject(v) {
3
4
  return !!v && typeof v === 'object' && !Array.isArray(v);
4
5
  }
5
- function repairArgumentsToString(args) {
6
- if (typeof args === 'string')
7
- return args;
8
- try {
9
- return JSON.stringify(args ?? {});
10
- }
11
- catch {
12
- return String(args);
13
- }
14
- }
15
6
  export function canonicalizeChatResponseTools(payload) {
16
7
  try {
17
8
  const out = isObject(payload) ? JSON.parse(JSON.stringify(payload)) : payload;
@@ -36,7 +27,7 @@ export function canonicalizeChatResponseTools(payload) {
36
27
  try {
37
28
  const fn = tc && tc.function ? tc.function : undefined;
38
29
  if (fn)
39
- fn.arguments = repairArgumentsToString(fn.arguments);
30
+ fn.arguments = repairToolArguments(fn.arguments);
40
31
  }
41
32
  catch { /* ignore */ }
42
33
  }
@@ -30,6 +30,7 @@ export function bootstrapVirtualRouterConfig(input) {
30
30
  const webSearch = normalizeWebSearch(section.webSearch, routingSource);
31
31
  validateWebSearchRouting(webSearch, routingSource);
32
32
  const execCommandGuard = normalizeExecCommandGuard(section.execCommandGuard);
33
+ const clock = normalizeClock(section.clock);
33
34
  const { runtimeEntries, aliasIndex, modelIndex } = buildProviderRuntimeEntries(providersSource);
34
35
  const { routing, targetKeys } = expandRoutingTable(routingSource, aliasIndex);
35
36
  if (!routing.default || routing.default.length === 0) {
@@ -67,7 +68,8 @@ export function bootstrapVirtualRouterConfig(input) {
67
68
  health,
68
69
  contextRouting,
69
70
  ...(webSearch ? { webSearch } : {}),
70
- ...(execCommandGuard ? { execCommandGuard } : {})
71
+ ...(execCommandGuard ? { execCommandGuard } : {}),
72
+ ...(clock ? { clock } : {})
71
73
  };
72
74
  return {
73
75
  config,
@@ -90,7 +92,31 @@ function extractVirtualRouterSection(input) {
90
92
  const contextRouting = normalizeContextRouting(section.contextRouting ?? root.contextRouting);
91
93
  const webSearch = section.webSearch ?? root.webSearch;
92
94
  const execCommandGuard = section.execCommandGuard ?? root.execCommandGuard;
93
- return { providers, routing, classifier, loadBalancing, health, contextRouting, webSearch, execCommandGuard };
95
+ const clock = section.clock ?? root.clock;
96
+ return { providers, routing, classifier, loadBalancing, health, contextRouting, webSearch, execCommandGuard, clock };
97
+ }
98
+ function normalizeClock(raw) {
99
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
100
+ return undefined;
101
+ }
102
+ const record = raw;
103
+ const enabled = record.enabled === true ||
104
+ (typeof record.enabled === 'string' && record.enabled.trim().toLowerCase() === 'true') ||
105
+ (typeof record.enabled === 'number' && record.enabled === 1);
106
+ if (!enabled) {
107
+ return undefined;
108
+ }
109
+ const out = { enabled: true };
110
+ if (typeof record.retentionMs === 'number' && Number.isFinite(record.retentionMs) && record.retentionMs >= 0) {
111
+ out.retentionMs = Math.floor(record.retentionMs);
112
+ }
113
+ if (typeof record.dueWindowMs === 'number' && Number.isFinite(record.dueWindowMs) && record.dueWindowMs >= 0) {
114
+ out.dueWindowMs = Math.floor(record.dueWindowMs);
115
+ }
116
+ if (typeof record.tickMs === 'number' && Number.isFinite(record.tickMs) && record.tickMs >= 0) {
117
+ out.tickMs = Math.floor(record.tickMs);
118
+ }
119
+ return out;
94
120
  }
95
121
  function buildProviderRuntimeEntries(providers) {
96
122
  const runtimeEntries = {};
@@ -1130,9 +1156,48 @@ function normalizeLoadBalancing(input) {
1130
1156
  weightsEntries[key] = value;
1131
1157
  }
1132
1158
  }
1133
- return Object.keys(weightsEntries).length
1134
- ? { strategy, weights: weightsEntries }
1135
- : { strategy };
1159
+ const healthWeightedRaw = asRecord(record.healthWeighted);
1160
+ const healthWeighted = Object.keys(healthWeightedRaw).length > 0
1161
+ ? {
1162
+ ...(typeof healthWeightedRaw.enabled === 'boolean' ? { enabled: healthWeightedRaw.enabled } : {}),
1163
+ ...(typeof healthWeightedRaw.recoverToBestOnRetry === 'boolean'
1164
+ ? { recoverToBestOnRetry: healthWeightedRaw.recoverToBestOnRetry }
1165
+ : {}),
1166
+ ...(typeof healthWeightedRaw.baseWeight === 'number' && Number.isFinite(healthWeightedRaw.baseWeight)
1167
+ ? { baseWeight: healthWeightedRaw.baseWeight }
1168
+ : {}),
1169
+ ...(typeof healthWeightedRaw.minMultiplier === 'number' && Number.isFinite(healthWeightedRaw.minMultiplier)
1170
+ ? { minMultiplier: healthWeightedRaw.minMultiplier }
1171
+ : {}),
1172
+ ...(typeof healthWeightedRaw.beta === 'number' && Number.isFinite(healthWeightedRaw.beta)
1173
+ ? { beta: healthWeightedRaw.beta }
1174
+ : {}),
1175
+ ...(typeof healthWeightedRaw.halfLifeMs === 'number' && Number.isFinite(healthWeightedRaw.halfLifeMs)
1176
+ ? { halfLifeMs: healthWeightedRaw.halfLifeMs }
1177
+ : {})
1178
+ }
1179
+ : undefined;
1180
+ const contextWeightedRaw = asRecord(record.contextWeighted);
1181
+ const contextWeighted = Object.keys(contextWeightedRaw).length > 0
1182
+ ? {
1183
+ ...(typeof contextWeightedRaw.enabled === 'boolean' ? { enabled: contextWeightedRaw.enabled } : {}),
1184
+ ...(typeof contextWeightedRaw.clientCapTokens === 'number' && Number.isFinite(contextWeightedRaw.clientCapTokens)
1185
+ ? { clientCapTokens: contextWeightedRaw.clientCapTokens }
1186
+ : {}),
1187
+ ...(typeof contextWeightedRaw.gamma === 'number' && Number.isFinite(contextWeightedRaw.gamma)
1188
+ ? { gamma: contextWeightedRaw.gamma }
1189
+ : {}),
1190
+ ...(typeof contextWeightedRaw.maxMultiplier === 'number' && Number.isFinite(contextWeightedRaw.maxMultiplier)
1191
+ ? { maxMultiplier: contextWeightedRaw.maxMultiplier }
1192
+ : {})
1193
+ }
1194
+ : undefined;
1195
+ return {
1196
+ strategy,
1197
+ ...(Object.keys(weightsEntries).length ? { weights: weightsEntries } : {}),
1198
+ ...(healthWeighted ? { healthWeighted } : {}),
1199
+ ...(contextWeighted ? { contextWeighted } : {})
1200
+ };
1136
1201
  }
1137
1202
  function coerceRatio(value) {
1138
1203
  if (typeof value === 'number' && Number.isFinite(value)) {
@@ -16,4 +16,8 @@ export declare class ContextAdvisor {
16
16
  private hardLimit;
17
17
  configure(config?: VirtualRouterContextRoutingConfig | null): void;
18
18
  classify(pool: string[], estimatedTokens: number, resolveProfile: (key: string) => ProviderProfile): ContextAdvisorResult;
19
+ getConfig(): {
20
+ warnRatio: number;
21
+ hardLimit: boolean;
22
+ };
19
23
  }
@@ -55,6 +55,9 @@ export class ContextAdvisor {
55
55
  allOverflow: safe.length === 0 && risky.length === 0 && overflow.length > 0
56
56
  };
57
57
  }
58
+ getConfig() {
59
+ return { warnRatio: this.warnRatio, hardLimit: this.hardLimit };
60
+ }
58
61
  }
59
62
  function clampWarnRatio(value) {
60
63
  if (!Number.isFinite(value)) {
@@ -0,0 +1,31 @@
1
+ import type { ContextWeightedLoadBalancingConfig } from './types.js';
2
+ export type ResolvedContextWeightedConfig = Required<{
3
+ enabled: boolean;
4
+ clientCapTokens: number;
5
+ gamma: number;
6
+ maxMultiplier: number;
7
+ }>;
8
+ /**
9
+ * Context-weighted constant table (defaults).
10
+ *
11
+ * Intended behavior:
12
+ * - Prefer smaller effective safe context windows early, so that larger windows remain available later.
13
+ * - Compensation is proportional by default (`gamma=1`), but capped by `maxMultiplier`.
14
+ *
15
+ * Notes:
16
+ * - `clientCapTokens` is the maximum effective context the client can consume, even if the model supports more.
17
+ * - The effective safe window is computed using ContextAdvisor's `warnRatio` and model "slack" above the client cap.
18
+ * - If a model has slack >= the reserved margin, it effectively gets the full client cap as safe window.
19
+ */
20
+ export declare const DEFAULT_CONTEXT_WEIGHTED_CONFIG: ResolvedContextWeightedConfig;
21
+ export declare function resolveContextWeightedConfig(raw?: ContextWeightedLoadBalancingConfig | null): ResolvedContextWeightedConfig;
22
+ export declare function computeEffectiveSafeWindowTokens(options: {
23
+ modelMaxTokens: number;
24
+ warnRatio: number;
25
+ clientCapTokens: number;
26
+ }): number;
27
+ export declare function computeContextMultiplier(options: {
28
+ effectiveSafeRefTokens: number;
29
+ effectiveSafeTokens: number;
30
+ cfg: ResolvedContextWeightedConfig;
31
+ }): number;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Context-weighted constant table (defaults).
3
+ *
4
+ * Intended behavior:
5
+ * - Prefer smaller effective safe context windows early, so that larger windows remain available later.
6
+ * - Compensation is proportional by default (`gamma=1`), but capped by `maxMultiplier`.
7
+ *
8
+ * Notes:
9
+ * - `clientCapTokens` is the maximum effective context the client can consume, even if the model supports more.
10
+ * - The effective safe window is computed using ContextAdvisor's `warnRatio` and model "slack" above the client cap.
11
+ * - If a model has slack >= the reserved margin, it effectively gets the full client cap as safe window.
12
+ */
13
+ export const DEFAULT_CONTEXT_WEIGHTED_CONFIG = {
14
+ enabled: false,
15
+ clientCapTokens: 200_000,
16
+ gamma: 1,
17
+ maxMultiplier: 2
18
+ };
19
+ export function resolveContextWeightedConfig(raw) {
20
+ const enabled = raw?.enabled ?? DEFAULT_CONTEXT_WEIGHTED_CONFIG.enabled;
21
+ const clientCapTokens = typeof raw?.clientCapTokens === 'number' && Number.isFinite(raw.clientCapTokens) && raw.clientCapTokens > 0
22
+ ? Math.floor(raw.clientCapTokens)
23
+ : DEFAULT_CONTEXT_WEIGHTED_CONFIG.clientCapTokens;
24
+ const gamma = typeof raw?.gamma === 'number' && Number.isFinite(raw.gamma) && raw.gamma > 0
25
+ ? raw.gamma
26
+ : DEFAULT_CONTEXT_WEIGHTED_CONFIG.gamma;
27
+ const maxMultiplier = typeof raw?.maxMultiplier === 'number' && Number.isFinite(raw.maxMultiplier) && raw.maxMultiplier >= 1
28
+ ? raw.maxMultiplier
29
+ : DEFAULT_CONTEXT_WEIGHTED_CONFIG.maxMultiplier;
30
+ return { enabled, clientCapTokens, gamma, maxMultiplier };
31
+ }
32
+ export function computeEffectiveSafeWindowTokens(options) {
33
+ const modelMaxTokens = typeof options.modelMaxTokens === 'number' && Number.isFinite(options.modelMaxTokens) && options.modelMaxTokens > 0
34
+ ? Math.floor(options.modelMaxTokens)
35
+ : 1;
36
+ const clientCapTokens = typeof options.clientCapTokens === 'number' && Number.isFinite(options.clientCapTokens) && options.clientCapTokens > 0
37
+ ? Math.floor(options.clientCapTokens)
38
+ : DEFAULT_CONTEXT_WEIGHTED_CONFIG.clientCapTokens;
39
+ const warnRatio = typeof options.warnRatio === 'number' && Number.isFinite(options.warnRatio) && options.warnRatio > 0 && options.warnRatio < 1
40
+ ? options.warnRatio
41
+ : 0.9;
42
+ const effectiveMax = Math.min(modelMaxTokens, clientCapTokens);
43
+ const reserve = Math.ceil(effectiveMax * (1 - warnRatio));
44
+ const slack = Math.max(0, modelMaxTokens - clientCapTokens);
45
+ const reserveEff = Math.max(0, reserve - slack);
46
+ return Math.max(1, effectiveMax - reserveEff);
47
+ }
48
+ export function computeContextMultiplier(options) {
49
+ const ref = Math.max(1, Math.floor(options.effectiveSafeRefTokens));
50
+ const cur = Math.max(1, Math.floor(options.effectiveSafeTokens));
51
+ const ratio = ref / cur;
52
+ const raw = Math.pow(Math.max(1, ratio), options.cfg.gamma);
53
+ return Math.min(options.cfg.maxMultiplier, raw);
54
+ }