@jsonstudio/rcc 0.89.333 → 0.89.524

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 (165) hide show
  1. package/dist/build-info.js +3 -3
  2. package/dist/build-info.js.map +1 -1
  3. package/dist/cli.js +62 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/token-daemon.d.ts +2 -0
  6. package/dist/commands/token-daemon.js +183 -0
  7. package/dist/commands/token-daemon.js.map +1 -0
  8. package/dist/index.js +4 -3
  9. package/dist/index.js.map +1 -1
  10. package/dist/modules/llmswitch/bridge.d.ts +1 -1
  11. package/dist/modules/llmswitch/bridge.js +3 -2
  12. package/dist/modules/llmswitch/bridge.js.map +1 -1
  13. package/dist/modules/pipeline/utils/colored-logger.js +3 -1
  14. package/dist/modules/pipeline/utils/colored-logger.js.map +1 -1
  15. package/dist/providers/auth/gemini-cli-userinfo-helper.js +12 -2
  16. package/dist/providers/auth/gemini-cli-userinfo-helper.js.map +1 -1
  17. package/dist/providers/auth/oauth-lifecycle.js +261 -22
  18. package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
  19. package/dist/providers/core/config/oauth-flows.d.ts +23 -0
  20. package/dist/providers/core/config/oauth-flows.js +92 -5
  21. package/dist/providers/core/config/oauth-flows.js.map +1 -1
  22. package/dist/providers/core/config/provider-oauth-configs.js +9 -3
  23. package/dist/providers/core/config/provider-oauth-configs.js.map +1 -1
  24. package/dist/providers/core/config/service-profiles.js +18 -10
  25. package/dist/providers/core/config/service-profiles.js.map +1 -1
  26. package/dist/providers/core/runtime/gemini-cli-http-provider.js +87 -20
  27. package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
  28. package/dist/providers/core/runtime/http-request-executor.d.ts +1 -0
  29. package/dist/providers/core/runtime/http-request-executor.js +41 -1
  30. package/dist/providers/core/runtime/http-request-executor.js.map +1 -1
  31. package/dist/providers/core/runtime/http-transport-provider.d.ts +2 -0
  32. package/dist/providers/core/runtime/http-transport-provider.js +37 -2
  33. package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
  34. package/dist/providers/core/runtime/responses-provider.js +8 -3
  35. package/dist/providers/core/runtime/responses-provider.js.map +1 -1
  36. package/dist/providers/core/runtime/vision-debug-utils.d.ts +13 -0
  37. package/dist/providers/core/runtime/vision-debug-utils.js +114 -0
  38. package/dist/providers/core/runtime/vision-debug-utils.js.map +1 -0
  39. package/dist/providers/core/strategies/oauth-auth-code-flow.js +75 -26
  40. package/dist/providers/core/strategies/oauth-auth-code-flow.js.map +1 -1
  41. package/dist/providers/core/utils/http-client.js +2 -1
  42. package/dist/providers/core/utils/http-client.js.map +1 -1
  43. package/dist/providers/core/utils/snapshot-writer.d.ts +1 -1
  44. package/dist/providers/core/utils/snapshot-writer.js.map +1 -1
  45. package/dist/server/handlers/sse-dispatcher.js +22 -2
  46. package/dist/server/handlers/sse-dispatcher.js.map +1 -1
  47. package/dist/server/runtime/http-server/index.d.ts +9 -0
  48. package/dist/server/runtime/http-server/index.js +512 -144
  49. package/dist/server/runtime/http-server/index.js.map +1 -1
  50. package/dist/server/runtime/http-server/request-executor.d.ts +10 -0
  51. package/dist/server/runtime/http-server/request-executor.js +553 -159
  52. package/dist/server/runtime/http-server/request-executor.js.map +1 -1
  53. package/dist/server/runtime/http-server/routes.d.ts +5 -0
  54. package/dist/server/runtime/http-server/routes.js +69 -0
  55. package/dist/server/runtime/http-server/routes.js.map +1 -1
  56. package/dist/server/runtime/http-server/runtime-manager.js +18 -0
  57. package/dist/server/runtime/http-server/runtime-manager.js.map +1 -1
  58. package/dist/server/utils/utf8-chunk-buffer.d.ts +43 -0
  59. package/dist/server/utils/utf8-chunk-buffer.js +132 -0
  60. package/dist/server/utils/utf8-chunk-buffer.js.map +1 -0
  61. package/dist/token-daemon/index.d.ts +7 -0
  62. package/dist/token-daemon/index.js +242 -0
  63. package/dist/token-daemon/index.js.map +1 -0
  64. package/dist/token-daemon/server-utils.d.ts +33 -0
  65. package/dist/token-daemon/server-utils.js +155 -0
  66. package/dist/token-daemon/server-utils.js.map +1 -0
  67. package/dist/token-daemon/token-daemon.d.ts +20 -0
  68. package/dist/token-daemon/token-daemon.js +144 -0
  69. package/dist/token-daemon/token-daemon.js.map +1 -0
  70. package/dist/token-daemon/token-types.d.ts +44 -0
  71. package/dist/token-daemon/token-types.js +18 -0
  72. package/dist/token-daemon/token-types.js.map +1 -0
  73. package/dist/token-daemon/token-utils.d.ts +17 -0
  74. package/dist/token-daemon/token-utils.js +153 -0
  75. package/dist/token-daemon/token-utils.js.map +1 -0
  76. package/dist/tools/semantic-replay.js +7 -6
  77. package/dist/tools/semantic-replay.js.map +1 -1
  78. package/dist/utils/error-handler-registry.d.ts +36 -0
  79. package/dist/utils/error-handler-registry.js +93 -7
  80. package/dist/utils/error-handler-registry.js.map +1 -1
  81. package/node_modules/@jsonstudio/llms/README.md +2 -0
  82. package/node_modules/@jsonstudio/llms/dist/conversion/codecs/gemini-openai-codec.js +137 -5
  83. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/gemini-web-search.d.ts +17 -0
  84. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/gemini-web-search.js +68 -0
  85. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-image-content.d.ts +2 -0
  86. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-image-content.js +83 -0
  87. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-vision-prompt.d.ts +11 -0
  88. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-vision-prompt.js +177 -0
  89. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-web-search.d.ts +2 -0
  90. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-web-search.js +63 -0
  91. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/universal-shape-filter.js +11 -0
  92. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-gemini.json +17 -0
  93. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-glm.json +190 -181
  94. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-iflow.json +195 -195
  95. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-lmstudio.json +43 -43
  96. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-qwen.json +20 -20
  97. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/responses-c4m.json +42 -42
  98. package/node_modules/@jsonstudio/llms/dist/conversion/config/sample-config.json +1 -1
  99. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +24 -0
  100. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-types.d.ts +8 -0
  101. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline.js +39 -4
  102. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/target-utils.js +6 -0
  103. package/node_modules/@jsonstudio/llms/dist/conversion/hub/process/chat-process.js +213 -1
  104. package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/provider-response.d.ts +34 -0
  105. package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/provider-response.js +84 -24
  106. package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/server-side-tools.d.ts +26 -0
  107. package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/server-side-tools.js +383 -0
  108. package/node_modules/@jsonstudio/llms/dist/conversion/hub/semantic-mappers/gemini-mapper.js +241 -14
  109. package/node_modules/@jsonstudio/llms/dist/conversion/hub/semantic-mappers/responses-mapper.js +17 -1
  110. package/node_modules/@jsonstudio/llms/dist/conversion/hub/standardized-bridge.js +14 -0
  111. package/node_modules/@jsonstudio/llms/dist/conversion/hub/types/standardized.d.ts +1 -0
  112. package/node_modules/@jsonstudio/llms/dist/conversion/responses/responses-openai-bridge.js +82 -3
  113. package/node_modules/@jsonstudio/llms/dist/conversion/shared/anthropic-message-utils.js +92 -3
  114. package/node_modules/@jsonstudio/llms/dist/conversion/shared/bridge-message-utils.js +137 -10
  115. package/node_modules/@jsonstudio/llms/dist/conversion/shared/responses-output-builder.js +43 -2
  116. package/node_modules/@jsonstudio/llms/dist/conversion/shared/snapshot-utils.js +17 -47
  117. package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-filter-pipeline.js +1 -0
  118. package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-mapping.js +25 -2
  119. package/node_modules/@jsonstudio/llms/dist/index.d.ts +1 -0
  120. package/node_modules/@jsonstudio/llms/dist/index.js +1 -0
  121. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/bootstrap.js +308 -43
  122. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/classifier.js +11 -17
  123. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/context-advisor.d.ts +0 -2
  124. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/context-advisor.js +0 -12
  125. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine.d.ts +17 -2
  126. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine.js +332 -95
  127. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/features.js +1 -1
  128. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/message-utils.js +36 -24
  129. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/provider-registry.js +2 -1
  130. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/token-counter.js +14 -3
  131. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/types.d.ts +66 -2
  132. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/types.js +2 -1
  133. package/node_modules/@jsonstudio/llms/dist/servertool/engine.d.ts +27 -0
  134. package/node_modules/@jsonstudio/llms/dist/servertool/engine.js +60 -0
  135. package/node_modules/@jsonstudio/llms/dist/servertool/flow-types.d.ts +40 -0
  136. package/node_modules/@jsonstudio/llms/dist/servertool/flow-types.js +1 -0
  137. package/node_modules/@jsonstudio/llms/dist/servertool/handlers/vision.d.ts +1 -0
  138. package/node_modules/@jsonstudio/llms/dist/servertool/handlers/vision.js +194 -0
  139. package/node_modules/@jsonstudio/llms/dist/servertool/handlers/web-search.d.ts +1 -0
  140. package/node_modules/@jsonstudio/llms/dist/servertool/handlers/web-search.js +638 -0
  141. package/node_modules/@jsonstudio/llms/dist/servertool/orchestration-types.d.ts +33 -0
  142. package/node_modules/@jsonstudio/llms/dist/servertool/orchestration-types.js +1 -0
  143. package/node_modules/@jsonstudio/llms/dist/servertool/registry.d.ts +18 -0
  144. package/node_modules/@jsonstudio/llms/dist/servertool/registry.js +27 -0
  145. package/node_modules/@jsonstudio/llms/dist/servertool/server-side-tools.d.ts +8 -0
  146. package/node_modules/@jsonstudio/llms/dist/servertool/server-side-tools.js +208 -0
  147. package/node_modules/@jsonstudio/llms/dist/servertool/types.d.ts +88 -0
  148. package/node_modules/@jsonstudio/llms/dist/servertool/types.js +1 -0
  149. package/node_modules/@jsonstudio/llms/dist/servertool/vision-tool.d.ts +2 -0
  150. package/node_modules/@jsonstudio/llms/dist/servertool/vision-tool.js +185 -0
  151. package/node_modules/@jsonstudio/llms/dist/sse/json-to-sse/event-generators/responses.js +15 -3
  152. package/node_modules/@jsonstudio/llms/dist/sse/sse-to-json/builders/response-builder.js +6 -3
  153. package/node_modules/@jsonstudio/llms/dist/sse/sse-to-json/gemini-sse-to-json-converter.js +27 -1
  154. package/node_modules/@jsonstudio/llms/dist/sse/types/gemini-types.d.ts +20 -1
  155. package/node_modules/@jsonstudio/llms/dist/sse/types/responses-types.js +1 -1
  156. package/node_modules/@jsonstudio/llms/dist/telemetry/stats-center.d.ts +73 -0
  157. package/node_modules/@jsonstudio/llms/dist/telemetry/stats-center.js +280 -0
  158. package/node_modules/@jsonstudio/llms/package.json +1 -1
  159. package/package.json +2 -2
  160. package/scripts/pack-mode.mjs +2 -1
  161. package/scripts/publish-rcc.mjs +20 -4
  162. package/scripts/tests/virtual-router-health.mjs +141 -6
  163. package/dist/tools/replay-request.d.ts +0 -0
  164. package/dist/tools/replay-request.js +0 -2
  165. package/dist/tools/replay-request.js.map +0 -1
@@ -0,0 +1,27 @@
1
+ const SERVER_TOOL_HANDLERS = Object.create(null);
2
+ const AUTO_SERVER_TOOL_HANDLERS = [];
3
+ export function registerServerToolHandler(name, handler, options) {
4
+ if (!name || typeof name !== 'string' || typeof handler !== 'function')
5
+ return;
6
+ const key = name.trim().toLowerCase();
7
+ if (!key)
8
+ return;
9
+ const trigger = options?.trigger ?? 'tool_call';
10
+ const entry = { name: key, trigger, handler };
11
+ if (trigger === 'auto') {
12
+ AUTO_SERVER_TOOL_HANDLERS.push(entry);
13
+ return;
14
+ }
15
+ SERVER_TOOL_HANDLERS[key] = entry;
16
+ }
17
+ export function getServerToolHandler(name) {
18
+ if (!name || typeof name !== 'string')
19
+ return undefined;
20
+ const key = name.trim().toLowerCase();
21
+ if (!key)
22
+ return undefined;
23
+ return SERVER_TOOL_HANDLERS[key];
24
+ }
25
+ export function listAutoServerToolHandlers() {
26
+ return [...AUTO_SERVER_TOOL_HANDLERS];
27
+ }
@@ -0,0 +1,8 @@
1
+ import type { JsonObject } from '../conversion/hub/types/json.js';
2
+ import type { ServerSideToolEngineOptions, ServerSideToolEngineResult, ToolCall } from './types.js';
3
+ import './handlers/web-search.js';
4
+ import './handlers/vision.js';
5
+ export declare function runServerSideToolEngine(options: ServerSideToolEngineOptions): Promise<ServerSideToolEngineResult>;
6
+ export declare function extractToolCalls(chatResponse: JsonObject): ToolCall[];
7
+ export declare function cloneJson<T>(value: T): T;
8
+ export declare function extractTextFromChatLike(payload: JsonObject): string;
@@ -0,0 +1,208 @@
1
+ import { getServerToolHandler, listAutoServerToolHandlers } from './registry.js';
2
+ import './handlers/web-search.js';
3
+ import './handlers/vision.js';
4
+ export async function runServerSideToolEngine(options) {
5
+ const base = asObject(options.chatResponse);
6
+ if (!base) {
7
+ return { mode: 'passthrough', finalChatResponse: options.chatResponse };
8
+ }
9
+ const toolCalls = extractToolCalls(base);
10
+ const contextBase = {
11
+ base,
12
+ toolCalls,
13
+ adapterContext: options.adapterContext,
14
+ options
15
+ };
16
+ for (const toolCall of toolCalls) {
17
+ const entry = getServerToolHandler(toolCall.name);
18
+ if (!entry || entry.trigger !== 'tool_call')
19
+ continue;
20
+ const ctx = { ...contextBase, toolCall };
21
+ const result = await runHandler(entry.handler, ctx);
22
+ if (result) {
23
+ return {
24
+ mode: 'tool_flow',
25
+ finalChatResponse: result.chatResponse,
26
+ execution: result.execution
27
+ };
28
+ }
29
+ }
30
+ for (const entry of listAutoServerToolHandlers()) {
31
+ const result = await runHandler(entry.handler, contextBase);
32
+ if (result) {
33
+ return {
34
+ mode: 'tool_flow',
35
+ finalChatResponse: result.chatResponse,
36
+ execution: result.execution
37
+ };
38
+ }
39
+ }
40
+ return { mode: 'passthrough', finalChatResponse: base };
41
+ }
42
+ async function runHandler(handler, ctx) {
43
+ try {
44
+ return await handler(ctx);
45
+ }
46
+ catch (error) {
47
+ try {
48
+ // eslint-disable-next-line no-console
49
+ console.error('[servertool] handler failed', error);
50
+ }
51
+ catch {
52
+ /* logging best-effort */
53
+ }
54
+ return null;
55
+ }
56
+ }
57
+ export function extractToolCalls(chatResponse) {
58
+ const choices = getArray(chatResponse.choices);
59
+ const calls = [];
60
+ for (const choice of choices) {
61
+ const choiceObj = asObject(choice);
62
+ if (!choiceObj)
63
+ continue;
64
+ const message = asObject(choiceObj.message);
65
+ if (!message)
66
+ continue;
67
+ const toolCalls = getArray(message.tool_calls);
68
+ for (const raw of toolCalls) {
69
+ const tc = asObject(raw);
70
+ if (!tc)
71
+ continue;
72
+ const id = typeof tc.id === 'string' && tc.id.trim() ? tc.id.trim() : '';
73
+ const fn = asObject(tc.function);
74
+ const name = fn && typeof fn.name === 'string' && fn.name.trim() ? fn.name.trim() : '';
75
+ const args = fn && typeof fn.arguments === 'string' ? fn.arguments : '';
76
+ if (!id || !name)
77
+ continue;
78
+ calls.push({ id, name, arguments: args });
79
+ }
80
+ }
81
+ return calls;
82
+ }
83
+ function asObject(value) {
84
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
85
+ }
86
+ function getArray(value) {
87
+ return Array.isArray(value) ? value : [];
88
+ }
89
+ export function cloneJson(value) {
90
+ return JSON.parse(JSON.stringify(value));
91
+ }
92
+ export function extractTextFromChatLike(payload) {
93
+ let current = payload;
94
+ const visited = new Set();
95
+ while (current && typeof current === 'object' && !Array.isArray(current) && !visited.has(current)) {
96
+ visited.add(current);
97
+ if (Array.isArray(current.choices) || Array.isArray(current.output)) {
98
+ break;
99
+ }
100
+ const data = current.data;
101
+ if (data && typeof data === 'object' && !Array.isArray(data)) {
102
+ current = data;
103
+ continue;
104
+ }
105
+ const response = current.response;
106
+ if (response && typeof response === 'object' && !Array.isArray(response)) {
107
+ current = response;
108
+ continue;
109
+ }
110
+ break;
111
+ }
112
+ const choices = getArray(current.choices);
113
+ if (!choices.length)
114
+ return '';
115
+ const first = asObject(choices[0]);
116
+ if (!first)
117
+ return '';
118
+ const message = asObject(first.message);
119
+ if (!message)
120
+ return '';
121
+ const content = message.content;
122
+ if (typeof content === 'string')
123
+ return content.trim();
124
+ const parts = getArray(content);
125
+ const texts = [];
126
+ for (const part of parts) {
127
+ if (typeof part === 'string') {
128
+ texts.push(part);
129
+ }
130
+ else if (part && typeof part === 'object') {
131
+ const record = part;
132
+ if (typeof record.text === 'string') {
133
+ texts.push(record.text);
134
+ }
135
+ else if (typeof record.content === 'string') {
136
+ texts.push(record.content);
137
+ }
138
+ }
139
+ }
140
+ const joinedFromChoices = texts.join('\n').trim();
141
+ if (joinedFromChoices) {
142
+ return joinedFromChoices;
143
+ }
144
+ const output = current.output;
145
+ if (Array.isArray(output)) {
146
+ const altTexts = [];
147
+ for (const entry of output) {
148
+ if (!entry || typeof entry !== 'object')
149
+ continue;
150
+ const blocks = entry.content;
151
+ const blockArray = Array.isArray(blocks) ? blocks : [];
152
+ for (const block of blockArray) {
153
+ if (!block || typeof block !== 'object')
154
+ continue;
155
+ const record = block;
156
+ if (typeof record.text === 'string') {
157
+ altTexts.push(record.text);
158
+ }
159
+ else if (typeof record.output_text === 'string') {
160
+ altTexts.push(record.output_text);
161
+ }
162
+ else if (typeof record.content === 'string') {
163
+ altTexts.push(record.content);
164
+ }
165
+ }
166
+ }
167
+ const joined = altTexts.join('\n').trim();
168
+ if (joined) {
169
+ return joined;
170
+ }
171
+ }
172
+ const webSearchRaw = current.web_search ??
173
+ payload.web_search;
174
+ if (Array.isArray(webSearchRaw) && webSearchRaw.length > 0) {
175
+ const items = webSearchRaw
176
+ .filter((entry) => !!entry && typeof entry === 'object' && !Array.isArray(entry))
177
+ .slice(0, 5);
178
+ const lines = [];
179
+ items.forEach((item, index) => {
180
+ const idx = (typeof item.refer === 'string' && item.refer.trim()) || String(index + 1);
181
+ const title = typeof item.title === 'string' && item.title.trim() ? item.title.trim() : '';
182
+ const media = typeof item.media === 'string' && item.media.trim() ? item.media.trim() : '';
183
+ const date = typeof item.publish_date === 'string' && item.publish_date.trim() ? item.publish_date.trim() : '';
184
+ const contentText = typeof item.content === 'string' && item.content.trim() ? item.content.trim() : '';
185
+ const link = typeof item.link === 'string' && item.link.trim() ? item.link.trim() : '';
186
+ const headerParts = [];
187
+ if (title)
188
+ headerParts.push(title);
189
+ if (media)
190
+ headerParts.push(media);
191
+ if (date)
192
+ headerParts.push(date);
193
+ const header = headerParts.length ? headerParts.join(' · ') : undefined;
194
+ const segments = [];
195
+ segments.push(`【${idx}】${header ?? '搜索结果'}`);
196
+ if (contentText)
197
+ segments.push(contentText);
198
+ if (link)
199
+ segments.push(link);
200
+ lines.push(segments.join('\n'));
201
+ });
202
+ const combined = lines.join('\n\n').trim();
203
+ if (combined) {
204
+ return combined;
205
+ }
206
+ }
207
+ return '';
208
+ }
@@ -0,0 +1,88 @@
1
+ import type { AdapterContext } from '../conversion/hub/types/chat-envelope.js';
2
+ import type { JsonObject } from '../conversion/hub/types/json.js';
3
+ /**
4
+ * ProviderInvoker 由 Host 注入,用于在 llmswitch-core 内部发起二次 provider 请求。
5
+ * 该接口对 ServerTool 框架是抽象的,不关心 HTTP 细节。
6
+ */
7
+ export type ProviderInvoker = (options: {
8
+ providerKey: string;
9
+ providerType?: string;
10
+ modelId?: string;
11
+ providerProtocol: string;
12
+ payload: JsonObject;
13
+ entryEndpoint: string;
14
+ requestId: string;
15
+ /**
16
+ * 可选的路由提示,用于在 Host 侧强制通过虚拟路由命中特定 route
17
+ *(例如 web_search),保持所有二次请求仍然走标准 HubPipeline。
18
+ */
19
+ routeHint?: string;
20
+ }) => Promise<{
21
+ providerResponse: JsonObject;
22
+ }>;
23
+ /**
24
+ * ToolCall:对齐 OpenAI style 的工具调用表示。
25
+ */
26
+ export interface ToolCall {
27
+ id: string;
28
+ name: string;
29
+ arguments: string;
30
+ }
31
+ /**
32
+ * ServerSideToolEngineOptions:ServerTool 引擎入参(ChatCompletion 视角)。
33
+ */
34
+ export interface ServerSideToolEngineOptions {
35
+ chatResponse: JsonObject;
36
+ adapterContext: AdapterContext;
37
+ entryEndpoint: string;
38
+ requestId: string;
39
+ providerProtocol: string;
40
+ providerInvoker?: ProviderInvoker;
41
+ reenterPipeline?: (options: {
42
+ entryEndpoint: string;
43
+ requestId: string;
44
+ body: JsonObject;
45
+ metadata?: JsonObject;
46
+ }) => Promise<{
47
+ body?: JsonObject;
48
+ __sse_responses?: unknown;
49
+ format?: string;
50
+ }>;
51
+ }
52
+ export interface ServerToolFollowupPlan {
53
+ requestIdSuffix: string;
54
+ payload: JsonObject;
55
+ metadata?: JsonObject;
56
+ }
57
+ export interface ServerToolExecution {
58
+ flowId: string;
59
+ followup?: ServerToolFollowupPlan;
60
+ }
61
+ /**
62
+ * ServerSideToolEngineResult:ServerTool 引擎出参。
63
+ */
64
+ export interface ServerSideToolEngineResult {
65
+ mode: 'passthrough' | 'tool_flow';
66
+ finalChatResponse: JsonObject;
67
+ execution?: ServerToolExecution;
68
+ }
69
+ /**
70
+ * ServerToolHandlerContext:单个工具 handler 的上下文入参。
71
+ */
72
+ export interface ServerToolHandlerContext {
73
+ base: JsonObject;
74
+ toolCall?: ToolCall;
75
+ toolCalls: ToolCall[];
76
+ adapterContext: AdapterContext;
77
+ options: ServerSideToolEngineOptions;
78
+ }
79
+ export interface ServerToolHandlerResult {
80
+ chatResponse: JsonObject;
81
+ execution: ServerToolExecution;
82
+ }
83
+ /**
84
+ * ServerToolHandler:统一的 ServerTool handler 接口。
85
+ * 后续 web_search / vision / 其它工具都会以 handler 形式挂载到注册表。
86
+ */
87
+ export type ServerToolHandler = (ctx: ServerToolHandlerContext) => Promise<ServerToolHandlerResult | null>;
88
+ export type { JsonObject, JsonValue } from '../conversion/hub/types/json.js';
@@ -0,0 +1,2 @@
1
+ import type { ServerToolFlow } from './flow-types.js';
2
+ export declare const visionToolFlow: ServerToolFlow;
@@ -0,0 +1,185 @@
1
+ import { extractTextFromChatLike } from './server-side-tools.js';
2
+ const VISION_FLOW_ID = 'vision_flow';
3
+ export const visionToolFlow = {
4
+ id: VISION_FLOW_ID,
5
+ shouldRun(context) {
6
+ if (!context.options.reenterPipeline) {
7
+ return false;
8
+ }
9
+ return shouldRunVisionFlow(context);
10
+ },
11
+ async run(context, helpers) {
12
+ const captured = context.capturedChatRequest;
13
+ if (!captured) {
14
+ return null;
15
+ }
16
+ const visionPayload = buildVisionAnalysisPayload(captured);
17
+ if (!visionPayload) {
18
+ return null;
19
+ }
20
+ const visionResponse = await helpers.callReenterHop({
21
+ requestIdSuffix: ':vision',
22
+ body: visionPayload,
23
+ metadata: {
24
+ routeHint: 'vision'
25
+ }
26
+ });
27
+ const visionBody = asObject(visionResponse?.body);
28
+ if (!visionBody) {
29
+ return null;
30
+ }
31
+ const visionSummary = extractTextFromChatLike(visionBody);
32
+ if (!visionSummary) {
33
+ return null;
34
+ }
35
+ const followupPayload = buildVisionFollowupPayload(captured, visionSummary);
36
+ if (!followupPayload) {
37
+ return null;
38
+ }
39
+ const followupRouteHint = helpers.getRouteHintForFollowup('vision');
40
+ const followup = await helpers.callReenterHop({
41
+ requestIdSuffix: ':vision_followup',
42
+ body: followupPayload,
43
+ metadata: followupRouteHint ? { routeHint: followupRouteHint } : undefined
44
+ });
45
+ const followupBody = asObject(followup?.body);
46
+ if (followupBody) {
47
+ return {
48
+ chat: followupBody,
49
+ executed: true,
50
+ flowId: VISION_FLOW_ID
51
+ };
52
+ }
53
+ return null;
54
+ }
55
+ };
56
+ function shouldRunVisionFlow(context) {
57
+ const adapterCtx = context.options.adapterContext;
58
+ const followupFlag = adapterCtx.serverToolFollowup === true || adapterCtx.serverToolFollowup === 'true';
59
+ if (followupFlag) {
60
+ return false;
61
+ }
62
+ return adapterCtx.hasImageAttachment === true || adapterCtx.hasImageAttachment === 'true';
63
+ }
64
+ function asObject(value) {
65
+ if (!value || typeof value !== 'object')
66
+ return undefined;
67
+ if (Array.isArray(value))
68
+ return undefined;
69
+ return value;
70
+ }
71
+ function buildVisionAnalysisPayload(source) {
72
+ if (!source || typeof source !== 'object') {
73
+ return null;
74
+ }
75
+ const payload = {};
76
+ if (typeof source.model === 'string' && source.model.trim()) {
77
+ payload.model = source.model.trim();
78
+ }
79
+ if (Array.isArray(source.messages)) {
80
+ payload.messages = cloneJson(source.messages);
81
+ }
82
+ else {
83
+ return null;
84
+ }
85
+ if (Array.isArray(source.tools) && source.tools.length) {
86
+ payload.tools = cloneJson(source.tools);
87
+ }
88
+ const parameters = source.parameters;
89
+ if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
90
+ const params = cloneJson(parameters);
91
+ Object.assign(payload, params);
92
+ }
93
+ return payload;
94
+ }
95
+ function buildVisionFollowupPayload(source, summary) {
96
+ if (!source || typeof source !== 'object') {
97
+ return null;
98
+ }
99
+ const payload = {};
100
+ if (typeof source.model === 'string' && source.model.trim()) {
101
+ payload.model = source.model.trim();
102
+ }
103
+ payload.messages = injectVisionSummary(source.messages, summary);
104
+ if (Array.isArray(source.tools) && source.tools.length) {
105
+ payload.tools = cloneJson(source.tools);
106
+ }
107
+ const parameters = source.parameters;
108
+ if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
109
+ const params = cloneJson(parameters);
110
+ Object.assign(payload, params);
111
+ }
112
+ return payload;
113
+ }
114
+ function injectVisionSummary(source, summary) {
115
+ const messages = Array.isArray(source) ? cloneJson(source) : [];
116
+ let injected = false;
117
+ for (const message of messages) {
118
+ if (!message || typeof message !== 'object')
119
+ continue;
120
+ const content = message.content;
121
+ if (!Array.isArray(content))
122
+ continue;
123
+ const nextParts = [];
124
+ let removed = false;
125
+ for (const part of content) {
126
+ if (part && typeof part === 'object') {
127
+ const typeValue = typeof part.type === 'string'
128
+ ? part.type.toLowerCase()
129
+ : '';
130
+ if (typeValue.includes('image')) {
131
+ removed = true;
132
+ continue;
133
+ }
134
+ }
135
+ nextParts.push(part);
136
+ }
137
+ if (removed) {
138
+ nextParts.push({
139
+ type: 'text',
140
+ text: `[Vision] ${summary}`
141
+ });
142
+ message.content = nextParts;
143
+ injected = true;
144
+ }
145
+ }
146
+ if (!injected) {
147
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
148
+ const msg = messages[i];
149
+ if (!msg || typeof msg !== 'object')
150
+ continue;
151
+ const role = typeof msg.role === 'string'
152
+ ? msg.role.toLowerCase()
153
+ : '';
154
+ if (role !== 'user')
155
+ continue;
156
+ const content = msg.content;
157
+ if (Array.isArray(content)) {
158
+ content.push({
159
+ type: 'text',
160
+ text: `[Vision] ${summary}`
161
+ });
162
+ injected = true;
163
+ break;
164
+ }
165
+ if (typeof content === 'string' && content.length) {
166
+ msg.content = `${content}\n[Vision] ${summary}`;
167
+ }
168
+ else {
169
+ msg.content = `[Vision] ${summary}`;
170
+ }
171
+ injected = true;
172
+ break;
173
+ }
174
+ }
175
+ if (!injected) {
176
+ messages.push({
177
+ role: 'system',
178
+ content: `[Vision] ${summary}`
179
+ });
180
+ }
181
+ return messages;
182
+ }
183
+ function cloneJson(value) {
184
+ return JSON.parse(JSON.stringify(value));
185
+ }
@@ -8,11 +8,22 @@ function cloneRegex(source) {
8
8
  return new RegExp(source.source, source.flags);
9
9
  }
10
10
  function getChunkSize(config) {
11
- return Math.max(1, config.chunkSize || DEFAULT_RESPONSES_EVENT_GENERATOR_CONFIG.chunkSize);
11
+ const size = typeof config.chunkSize === 'number'
12
+ ? config.chunkSize
13
+ : DEFAULT_RESPONSES_EVENT_GENERATOR_CONFIG.chunkSize;
14
+ if (size !== undefined && size <= 0) {
15
+ return null;
16
+ }
17
+ return Math.max(1, size || 1);
12
18
  }
13
19
  function chunkText(text, config) {
20
+ const size = getChunkSize(config);
21
+ if (size === null) {
22
+ // chunking explicitly disabled
23
+ return [text];
24
+ }
14
25
  try {
15
- return StringUtils.chunkString(text, getChunkSize(config), cloneRegex(TEXT_CHUNK_BOUNDARY));
26
+ return StringUtils.chunkString(text, size, cloneRegex(TEXT_CHUNK_BOUNDARY));
16
27
  }
17
28
  catch {
18
29
  return [text];
@@ -84,7 +95,8 @@ function normalizeUsage(usage) {
84
95
  }
85
96
  // 默认配置
86
97
  export const DEFAULT_RESPONSES_EVENT_GENERATOR_CONFIG = {
87
- chunkSize: 12,
98
+ // 默认关闭文本切片,让上游模型的分块行为原样透传
99
+ chunkSize: 0,
88
100
  chunkDelayMs: 8,
89
101
  enableIdGeneration: true,
90
102
  enableTimestampGeneration: true,
@@ -215,9 +215,10 @@ export class ResponsesResponseBuilder {
215
215
  let normalizedType = typeof rawType === 'string'
216
216
  ? rawType.replace(/^response\./, '')
217
217
  : 'message';
218
- // 将自定义工具调用规整为标准 function_call,便于后续统一转换为 Chat 工具调用
219
- if (normalizedType === 'custom_tool_call')
218
+ // web_search_call 视为标准 function_call,便于统一作为 Chat 工具调用暴露给上层。
219
+ if (normalizedType === 'web_search_call') {
220
220
  normalizedType = 'function_call';
221
+ }
221
222
  return {
222
223
  ...event,
223
224
  type: 'output_item.start',
@@ -719,7 +720,9 @@ export class ResponsesResponseBuilder {
719
720
  default:
720
721
  throw new Error(`Unknown output item type: ${state.type}`);
721
722
  }
722
- outputItems.push(outputItem);
723
+ if (outputItem) {
724
+ outputItems.push(outputItem);
725
+ }
723
726
  }
724
727
  const hasMessage = outputItems.some(item => item.type === 'message');
725
728
  const hasReasoning = outputItems.some(item => item.type === 'reasoning');
@@ -70,9 +70,35 @@ export class GeminiSseToJsonConverter {
70
70
  if (!accumulator.has(candidateIndex)) {
71
71
  accumulator.set(candidateIndex, { role, parts: [] });
72
72
  }
73
+ const candidate = accumulator.get(candidateIndex);
73
74
  const normalizedParts = this.normalizeReasoningPart(part, context);
74
75
  for (const normalizedPart of normalizedParts) {
75
- accumulator.get(candidateIndex).parts.push(normalizedPart);
76
+ // For text parts, accumulate into the last text part instead of creating new ones
77
+ if ('text' in normalizedPart && typeof normalizedPart.text === 'string') {
78
+ const lastPart = candidate.parts[candidate.parts.length - 1];
79
+ if (lastPart && 'text' in lastPart && typeof lastPart.text === 'string') {
80
+ // Append to existing text part
81
+ lastPart.text += normalizedPart.text;
82
+ }
83
+ else {
84
+ // Create new text part
85
+ candidate.parts.push(normalizedPart);
86
+ }
87
+ }
88
+ else if ('reasoning' in normalizedPart && typeof normalizedPart.reasoning === 'string') {
89
+ // For reasoning parts, also accumulate
90
+ const lastPart = candidate.parts[candidate.parts.length - 1];
91
+ if (lastPart && 'reasoning' in lastPart && typeof lastPart.reasoning === 'string') {
92
+ lastPart.reasoning += normalizedPart.reasoning;
93
+ }
94
+ else {
95
+ candidate.parts.push(normalizedPart);
96
+ }
97
+ }
98
+ else {
99
+ // For other part types (functionCall, functionResponse, etc.), add as separate parts
100
+ candidate.parts.push(normalizedPart);
101
+ }
76
102
  }
77
103
  }
78
104
  buildResponse(accumulator, donePayload) {
@@ -7,12 +7,14 @@ export interface GeminiContentFunctionCallPart {
7
7
  functionCall: {
8
8
  name: string;
9
9
  args?: Record<string, unknown>;
10
+ id?: string;
10
11
  [key: string]: unknown;
11
12
  };
12
13
  }
13
14
  export interface GeminiContentFunctionResponsePart {
14
15
  functionResponse: {
15
16
  name?: string;
17
+ id?: string;
16
18
  response?: unknown;
17
19
  [key: string]: unknown;
18
20
  };
@@ -23,7 +25,24 @@ export interface GeminiContentInlineDataPart {
23
25
  data: string;
24
26
  };
25
27
  }
26
- export type GeminiContentPart = GeminiContentTextPart | GeminiContentFunctionCallPart | GeminiContentFunctionResponsePart | GeminiContentInlineDataPart | Record<string, unknown>;
28
+ export interface GeminiContentThoughtPart {
29
+ thought: string;
30
+ }
31
+ export interface GeminiContentExecutableCodePart {
32
+ executableCode: {
33
+ language?: string;
34
+ code?: string;
35
+ [key: string]: unknown;
36
+ };
37
+ }
38
+ export interface GeminiContentCodeExecutionResultPart {
39
+ codeExecutionResult: {
40
+ outcome?: string;
41
+ output?: string;
42
+ [key: string]: unknown;
43
+ };
44
+ }
45
+ export type GeminiContentPart = GeminiContentTextPart | GeminiContentFunctionCallPart | GeminiContentFunctionResponsePart | GeminiContentInlineDataPart | GeminiContentThoughtPart | GeminiContentExecutableCodePart | GeminiContentCodeExecutionResultPart | Record<string, unknown>;
27
46
  export interface GeminiCandidate {
28
47
  content?: {
29
48
  role?: string;
@@ -23,7 +23,7 @@ export const DEFAULT_RESPONSES_CONVERSION_CONFIG = {
23
23
  defaultChunkSize: 12,
24
24
  defaultDelayMs: 8,
25
25
  reasoningChunkSize: 24,
26
- textChunkSize: 12,
26
+ textChunkSize: 128,
27
27
  functionCallChunkSize: 24,
28
28
  enableEventValidation: true,
29
29
  enableSequenceValidation: true,