@jsonstudio/rcc 0.89.1348 → 0.89.1457

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 (102) hide show
  1. package/README.md +51 -1427
  2. package/dist/build-info.js +2 -2
  3. package/dist/cli/commands/config.js +3 -0
  4. package/dist/cli/commands/config.js.map +1 -1
  5. package/dist/cli/commands/init.js +3 -0
  6. package/dist/cli/commands/init.js.map +1 -1
  7. package/dist/cli/config/bundled-docs.js +2 -2
  8. package/dist/cli/config/bundled-docs.js.map +1 -1
  9. package/dist/cli/config/init-config.d.ts +2 -1
  10. package/dist/cli/config/init-config.js +33 -1
  11. package/dist/cli/config/init-config.js.map +1 -1
  12. package/dist/client/gemini/gemini-protocol-client.js +2 -1
  13. package/dist/client/gemini/gemini-protocol-client.js.map +1 -1
  14. package/dist/client/gemini-cli/gemini-cli-protocol-client.js +39 -15
  15. package/dist/client/gemini-cli/gemini-cli-protocol-client.js.map +1 -1
  16. package/dist/client/openai/chat-protocol-client.js +2 -1
  17. package/dist/client/openai/chat-protocol-client.js.map +1 -1
  18. package/dist/client/responses/responses-protocol-client.js +2 -1
  19. package/dist/client/responses/responses-protocol-client.js.map +1 -1
  20. package/dist/error-handling/quiet-error-handling-center.js +46 -8
  21. package/dist/error-handling/quiet-error-handling-center.js.map +1 -1
  22. package/dist/manager/modules/quota/provider-quota-daemon.events.js +4 -2
  23. package/dist/manager/modules/quota/provider-quota-daemon.events.js.map +1 -1
  24. package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.js +9 -6
  25. package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.js.map +1 -1
  26. package/dist/providers/auth/antigravity-userinfo-helper.d.ts +2 -1
  27. package/dist/providers/auth/antigravity-userinfo-helper.js +25 -4
  28. package/dist/providers/auth/antigravity-userinfo-helper.js.map +1 -1
  29. package/dist/providers/auth/tokenfile-auth.d.ts +2 -0
  30. package/dist/providers/auth/tokenfile-auth.js +33 -1
  31. package/dist/providers/auth/tokenfile-auth.js.map +1 -1
  32. package/dist/providers/core/config/camoufox-launcher.d.ts +5 -0
  33. package/dist/providers/core/config/camoufox-launcher.js +5 -0
  34. package/dist/providers/core/config/camoufox-launcher.js.map +1 -1
  35. package/dist/providers/core/config/service-profiles.js +7 -18
  36. package/dist/providers/core/config/service-profiles.js.map +1 -1
  37. package/dist/providers/core/runtime/base-provider.d.ts +0 -5
  38. package/dist/providers/core/runtime/base-provider.js +26 -112
  39. package/dist/providers/core/runtime/base-provider.js.map +1 -1
  40. package/dist/providers/core/runtime/gemini-cli-http-provider.d.ts +7 -0
  41. package/dist/providers/core/runtime/gemini-cli-http-provider.js +362 -93
  42. package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
  43. package/dist/providers/core/runtime/http-request-executor.d.ts +3 -0
  44. package/dist/providers/core/runtime/http-request-executor.js +110 -38
  45. package/dist/providers/core/runtime/http-request-executor.js.map +1 -1
  46. package/dist/providers/core/runtime/http-transport-provider.d.ts +3 -0
  47. package/dist/providers/core/runtime/http-transport-provider.js +80 -37
  48. package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
  49. package/dist/providers/core/runtime/rate-limit-manager.d.ts +1 -12
  50. package/dist/providers/core/runtime/rate-limit-manager.js +4 -77
  51. package/dist/providers/core/runtime/rate-limit-manager.js.map +1 -1
  52. package/dist/providers/core/utils/http-client.js +20 -43
  53. package/dist/providers/core/utils/http-client.js.map +1 -1
  54. package/dist/server/handlers/handler-utils.js +5 -1
  55. package/dist/server/handlers/handler-utils.js.map +1 -1
  56. package/dist/server/handlers/responses-handler.js +1 -1
  57. package/dist/server/handlers/responses-handler.js.map +1 -1
  58. package/dist/server/runtime/http-server/index.js +68 -29
  59. package/dist/server/runtime/http-server/index.js.map +1 -1
  60. package/dist/server/runtime/http-server/request-executor.js +50 -6
  61. package/dist/server/runtime/http-server/request-executor.js.map +1 -1
  62. package/dist/server/runtime/http-server/routes.js +4 -1
  63. package/dist/server/runtime/http-server/routes.js.map +1 -1
  64. package/dist/utils/strip-internal-keys.d.ts +12 -0
  65. package/dist/utils/strip-internal-keys.js +28 -0
  66. package/dist/utils/strip-internal-keys.js.map +1 -0
  67. package/docs/CHAT_PROCESS_PROTOCOL_AND_PIPELINE.md +221 -0
  68. package/docs/antigravity-gemini-format-cleanup.md +102 -0
  69. package/docs/antigravity-routing-contract.md +31 -0
  70. package/docs/chat-semantic-expansion-plan.md +8 -6
  71. package/docs/glm-chat-completions.md +1 -1
  72. package/docs/servertool-framework.md +65 -0
  73. package/docs/v2-architecture/README.md +6 -8
  74. package/docs/verified-configs/README.md +60 -0
  75. package/docs/verified-configs/v0.45.0/README.md +244 -0
  76. package/docs/verified-configs/v0.45.0/lmstudio-5521-gpt-oss-20b-mlx.json +135 -0
  77. package/docs/verified-configs/v0.45.0/merged-config.5521.json +1205 -0
  78. package/docs/verified-configs/v0.45.0/merged-config.qwen-5522.json +1559 -0
  79. package/docs/verified-configs/v0.45.0/qwen-5522-qwen3-coder-plus-final.json +221 -0
  80. package/docs/verified-configs/v0.45.0/qwen-5522-qwen3-coder-plus-fixed.json +242 -0
  81. package/docs/verified-configs/v0.45.0/qwen-5522-qwen3-coder-plus.json +242 -0
  82. package/package.json +17 -11
  83. package/scripts/build-core.mjs +3 -1
  84. package/scripts/ci/repo-sanity.mjs +138 -0
  85. package/scripts/mock-provider/run-regressions.mjs +157 -1
  86. package/scripts/run-bg.sh +0 -14
  87. package/scripts/tests/ci-jest.mjs +119 -0
  88. package/scripts/tools-dev/responses-debug-client/README.md +23 -0
  89. package/scripts/tools-dev/responses-debug-client/payloads/poem.json +13 -0
  90. package/scripts/tools-dev/responses-debug-client/payloads/sample-no-tools.json +98 -0
  91. package/scripts/tools-dev/responses-debug-client/payloads/text.json +13 -0
  92. package/scripts/tools-dev/responses-debug-client/payloads/tool.json +27 -0
  93. package/scripts/tools-dev/responses-debug-client/run.mjs +65 -0
  94. package/scripts/tools-dev/responses-debug-client/src/index.ts +281 -0
  95. package/scripts/tools-dev/run-llmswitch-chat.mjs +53 -0
  96. package/scripts/tools-dev/server-tools-dev/run-web-fetch.mjs +65 -0
  97. package/scripts/vendor-core.mjs +13 -3
  98. package/scripts/test-fc-responses.mjs +0 -66
  99. package/scripts/test-guidance.mjs +0 -100
  100. package/scripts/test-iflow-web-search.mjs +0 -141
  101. package/scripts/test-iflow.mjs +0 -379
  102. package/scripts/test-tool-exec.mjs +0 -26
@@ -1,379 +0,0 @@
1
- #!/usr/bin/env node
2
- // iFlow test script with OAuth token lifecycle management + optional tool SSE loop
3
- // Examples:
4
- // - Ensure token then proxy chat: RC_BASE=http://127.0.0.1:5506 node scripts/test-iflow.mjs --mode=proxy --endpoint=/v1/chat/completions
5
- // - Ensure token then upstream chat: IFLOW_CLIENT_ID=... node scripts/test-iflow.mjs --mode=upstream
6
- // - Responses tool loop (one-shot delta): RC_BASE=http://127.0.0.1:5506 node scripts/test-iflow.mjs --mode=proxy --endpoint=/v1/responses --tools
7
-
8
- import fs from 'fs';
9
- import fsp from 'fs/promises';
10
- import os from 'os';
11
- import path from 'path';
12
- import { spawn } from 'child_process';
13
-
14
- /**
15
- * Options via env/args
16
- * - MODE: proxy | upstream (default: proxy)
17
- * - RC_BASE: default http://127.0.0.1:5506
18
- * - RC_ENDPOINT: default /v1/chat/completions (also supports /v1/responses)
19
- * - IFLOW_MODEL: default gpt-4o-mini
20
- * - TEXT: default "hello from RouteCodex test"
21
- * - CONFIG: RouteCodex user config path (default ~/.routecodex/config/v2/iflow-only.json)
22
- * - tools: flag to run /v1/responses SSE tool loop
23
- */
24
-
25
- const args = Object.fromEntries(process.argv.slice(2).map(kv => {
26
- const m = kv.match(/^--([^=]+)=(.*)$/);
27
- if (m) return [m[1], m[2]];
28
- if (kv.startsWith('--')) return [kv.slice(2), true];
29
- return [kv, true];
30
- }));
31
-
32
- const MODE = String(args.mode || process.env.MODE || 'proxy');
33
- const RC_BASE = String(process.env.RC_BASE || 'http://127.0.0.1:5506').replace(/\/$/, '');
34
- const RC_ENDPOINT = String(args.endpoint || process.env.RC_ENDPOINT || '/v1/chat/completions');
35
- // 默认模型更新为 iFlow-ROME-30BA3B,除非通过 IFLOW_MODEL 显式覆盖。
36
- const IFLOW_MODEL = String(process.env.IFLOW_MODEL || 'iFlow-ROME-30BA3B');
37
- const TEXT = String(process.env.TEXT || 'hello from RouteCodex test');
38
- const RUN_TOOLS = !!args.tools;
39
- const CONFIG_PATH = expandHome(String(process.env.CONFIG || args.config || path.join(os.homedir(), '.routecodex', 'config', 'v2', 'iflow-only.json')));
40
-
41
- function expandHome(p) { return p.startsWith('~') ? path.join(os.homedir(), p.slice(1)) : p; }
42
-
43
- async function loadIFlowOAuthConfig(configPath) {
44
- const raw = await fsp.readFile(configPath, 'utf-8');
45
- const j = JSON.parse(raw);
46
- const prov = j?.virtualrouter?.providers?.iflow;
47
- if (!prov) throw new Error('iflow provider not found in config');
48
- const oauth = prov?.oauth?.default || {};
49
- return {
50
- clientId: process.env.IFLOW_CLIENT_ID || oauth.clientId,
51
- deviceCodeUrl: process.env.IFLOW_DEVICE_CODE_URL || oauth.deviceCodeUrl || oauth.device_code_url,
52
- tokenUrl: process.env.IFLOW_TOKEN_URL || oauth.tokenUrl,
53
- scopes: Array.isArray(oauth.scopes) ? oauth.scopes : (typeof oauth.scope === 'string' ? oauth.scope.split(/[\s,]+/).filter(Boolean) : ['inference']),
54
- tokenFile: expandHome(oauth.tokenFile || path.join(os.homedir(), '.routecodex', 'tokens', 'iflow-default.json')),
55
- apiBase: prov?.baseURL || 'https://api.iflow.cn/v1'
56
- };
57
- }
58
-
59
- async function readToken(file) {
60
- try { const txt = await fsp.readFile(file, 'utf-8'); return JSON.parse(txt); } catch { return null; }
61
- }
62
-
63
- async function saveToken(file, tok) {
64
- await fsp.mkdir(path.dirname(file), { recursive: true });
65
- const withIssued = { ...tok };
66
- if (!withIssued.issued_at) withIssued.issued_at = Math.floor(Date.now()/1000);
67
- await fsp.writeFile(file, JSON.stringify(withIssued, null, 2), 'utf-8');
68
- }
69
-
70
- function isExpired(tok, skewSec = 60) {
71
- const now = Math.floor(Date.now()/1000);
72
- const issued = Number(tok.issued_at || 0);
73
- const expAt = tok.expires_at ? Number(tok.expires_at) : (tok.expires_in ? issued + Number(tok.expires_in) : 0);
74
- if (!expAt) return false; // if unknown, assume valid
75
- return now >= (expAt - skewSec);
76
- }
77
-
78
- async function refreshToken(oauth, tok) {
79
- if (!tok?.refresh_token) throw new Error('No refresh_token to refresh');
80
- const body = new URLSearchParams();
81
- body.set('grant_type', 'refresh_token');
82
- body.set('client_id', oauth.clientId);
83
- body.set('refresh_token', tok.refresh_token);
84
- if (process.env.IFLOW_CLIENT_SECRET) body.set('client_secret', process.env.IFLOW_CLIENT_SECRET);
85
- const res = await fetch(oauth.tokenUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body });
86
- const text = await res.text();
87
- if (!res.ok) throw new Error(`refresh failed ${res.status}: ${text}`);
88
- const j = JSON.parse(text);
89
- const merged = { ...tok, ...j, issued_at: Math.floor(Date.now()/1000) };
90
- if (j.expires_in && !j.expires_at) merged.expires_at = merged.issued_at + Number(j.expires_in);
91
- return merged;
92
- }
93
-
94
- async function openBrowser(url) {
95
- try {
96
- const platform = process.platform;
97
- if (platform === 'darwin') spawn('open', [url], { stdio: 'ignore', detached: true }).unref();
98
- else if (platform === 'win32') spawn('cmd', ['/c', 'start', '""', url]);
99
- else spawn('xdg-open', [url], { stdio: 'ignore', detached: true }).unref();
100
- } catch { /* ignore */ }
101
- }
102
-
103
- async function deviceCodeFlow(oauth) {
104
- const body = new URLSearchParams();
105
- body.set('client_id', oauth.clientId);
106
- if (oauth.scopes?.length) body.set('scope', oauth.scopes.join(' '));
107
- const res = await fetch(oauth.deviceCodeUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body });
108
- const txt = await res.text();
109
- if (!res.ok) throw new Error(`device code request failed ${res.status}: ${txt}`);
110
- const dc = JSON.parse(txt);
111
- const verifyUrl = dc.verification_uri_complete || dc.verification_uri;
112
- console.log(`Open this URL to authorize:
113
- ${verifyUrl}
114
- code: ${dc.user_code || '(auto)'}`);
115
- if (verifyUrl) await openBrowser(verifyUrl);
116
- const intervalMs = Math.max(5, Number(dc.interval || 5)) * 1000;
117
- const deadline = Date.now() + (Number(dc.expires_in || 600) * 1000);
118
- while (Date.now() < deadline) {
119
- await new Promise(r => setTimeout(r, intervalMs));
120
- const poll = new URLSearchParams();
121
- poll.set('grant_type', 'urn:ietf:params:oauth:grant-type:device_code');
122
- poll.set('device_code', dc.device_code);
123
- poll.set('client_id', oauth.clientId);
124
- if (process.env.IFLOW_CLIENT_SECRET) poll.set('client_secret', process.env.IFLOW_CLIENT_SECRET);
125
- const pr = await fetch(oauth.tokenUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: poll });
126
- const ptxt = await pr.text();
127
- if (pr.status === 200) {
128
- const tok = JSON.parse(ptxt);
129
- tok.issued_at = Math.floor(Date.now()/1000);
130
- if (tok.expires_in && !tok.expires_at) tok.expires_at = tok.issued_at + Number(tok.expires_in);
131
- return tok;
132
- }
133
- try {
134
- const j = JSON.parse(ptxt);
135
- const err = j.error || '';
136
- if (err === 'authorization_pending') continue;
137
- if (err === 'slow_down') { await new Promise(r => setTimeout(r, 5000)); continue; }
138
- throw new Error(`device flow error: ${ptxt}`);
139
- } catch {
140
- throw new Error(`device flow error: ${ptxt}`);
141
- }
142
- }
143
- throw new Error('device code expired');
144
- }
145
-
146
- async function ensureToken(oauth) {
147
- let tok = await readToken(oauth.tokenFile);
148
- if (tok && !isExpired(tok)) return tok;
149
- if (tok && tok.refresh_token) {
150
- try {
151
- console.log('[iflow] refreshing token...');
152
- tok = await refreshToken(oauth, tok);
153
- await saveToken(oauth.tokenFile, tok);
154
- return tok;
155
- } catch (e) {
156
- console.warn(`[iflow] refresh failed: ${e?.message || e}`);
157
- }
158
- }
159
- console.log('[iflow] starting device-code flow to obtain new token...');
160
- const newTok = await deviceCodeFlow(oauth);
161
- await saveToken(oauth.tokenFile, newTok);
162
- return newTok;
163
- }
164
-
165
- async function requestUpstreamChat(oauth, token) {
166
- const url = `${oauth.apiBase.replace(/\/$/, '')}/chat/completions`;
167
- const payload = { model: IFLOW_MODEL, messages: [{ role: 'user', content: TEXT }], stream: false };
168
- const res = await fetch(url, { method: 'POST', headers: { 'Authorization': `Bearer ${token.access_token}`, 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
169
- const body = await res.text();
170
- console.log(`[UPSTREAM] status=${res.status}`);
171
- try { console.log(JSON.stringify(JSON.parse(body), null, 2)); } catch { console.log(body); }
172
- }
173
-
174
- async function requestUpstreamWebSearch(oauth, token) {
175
- const url = `${oauth.apiBase.replace(/\/$/, '')}/chat/completions`;
176
- const payload = {
177
- model: IFLOW_MODEL,
178
- messages: [
179
- {
180
- role: 'system',
181
- content: 'You are an up-to-date web search engine. Call the web_search tool to fetch current results, then answer based on the tool output.'
182
- },
183
- {
184
- role: 'user',
185
- content: `${TEXT}. 请先调用 web_search 工具检索相关信息,再根据搜索结果回答。`
186
- }
187
- ],
188
- tools: [
189
- {
190
- type: 'function',
191
- function: {
192
- name: 'web_search',
193
- description: 'Perform web search over the public internet and return up-to-date results.',
194
- parameters: {
195
- type: 'object',
196
- properties: {
197
- query: {
198
- type: 'string',
199
- description: 'Search query string.'
200
- },
201
- recency: {
202
- type: 'string',
203
- description: 'Optional recency filter such as "day", "week", or "month".'
204
- },
205
- count: {
206
- type: 'integer',
207
- minimum: 1,
208
- maximum: 50,
209
- description: 'Maximum number of search results to retrieve (1-50).'
210
- }
211
- },
212
- required: ['query']
213
- }
214
- }
215
- }
216
- ],
217
- tool_choice: {
218
- type: 'function',
219
- function: {
220
- name: 'web_search'
221
- }
222
- },
223
- stream: false
224
- };
225
-
226
- const res = await fetch(url, {
227
- method: 'POST',
228
- headers: {
229
- 'Authorization': `Bearer ${token.access_token}`,
230
- 'Content-Type': 'application/json'
231
- },
232
- body: JSON.stringify(payload)
233
- });
234
- const text = await res.text();
235
- console.log(`[UPSTREAM][web_search] status=${res.status}`);
236
- let json = null;
237
- try {
238
- json = JSON.parse(text);
239
- console.log(JSON.stringify(json, null, 2));
240
- } catch {
241
- console.log(text);
242
- throw new Error('iflow web_search returned non-JSON payload');
243
- }
244
- if (!res.ok) {
245
- throw new Error(`iflow web_search failed: HTTP ${res.status}`);
246
- }
247
- const firstChoice = Array.isArray(json.choices) ? json.choices[0] : null;
248
- const msg = firstChoice?.message || {};
249
- const toolCalls = Array.isArray(msg.tool_calls) ? msg.tool_calls : [];
250
- console.log(`[UPSTREAM][web_search] tool_calls=${toolCalls.length}`);
251
- if (!toolCalls.length) {
252
- console.warn('[UPSTREAM][web_search] no tool_calls returned, web_search tool may not be enabled for this model.');
253
- } else {
254
- const names = toolCalls
255
- .map((tc) => (tc && tc.function && typeof tc.function.name === 'string' ? tc.function.name : ''))
256
- .filter(Boolean);
257
- console.log(`[UPSTREAM][web_search] tool names: ${names.join(', ')}`);
258
- }
259
- }
260
-
261
- async function requestProxyChat() {
262
- const url = `${RC_BASE}${RC_ENDPOINT}`;
263
- const payload = { model: IFLOW_MODEL, messages: [{ role: 'user', content: TEXT }], stream: false };
264
- const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
265
- const body = await res.text();
266
- console.log(`[PROXY] ${url} status=${res.status}`);
267
- try { console.log(JSON.stringify(JSON.parse(body), null, 2)); } catch { console.log(body); }
268
- }
269
-
270
- async function runResponsesToolLoop() {
271
- const url = `${RC_BASE}/v1/responses`;
272
- const payload = {
273
- model: IFLOW_MODEL,
274
- input: [ { type: 'text', text: `${TEXT}. 请使用可用的工具完成任务。` } ],
275
- stream: true
276
- };
277
- const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'text/event-stream' }, body: JSON.stringify(payload) });
278
- if (!res.ok || !res.body) { console.error(`[RESPONSES] HTTP ${res.status}`); console.log(await res.text()); return; }
279
- const reader = res.body.getReader();
280
- const decoder = new TextDecoder();
281
- let buf = '';
282
- let responseId = '';
283
- let required = null;
284
- console.log('[RESPONSES] streaming...');
285
- while (true) {
286
- const { value, done } = await reader.read();
287
- if (done) break;
288
- buf += decoder.decode(value, { stream: true });
289
- const chunks = buf.split(/\n\n/);
290
- buf = chunks.pop() || '';
291
- for (const chunk of chunks) {
292
- const lines = chunk.split(/\n/);
293
- let ev = '';
294
- let data = '';
295
- for (const ln of lines) {
296
- if (ln.startsWith('event:')) ev = ln.slice(6).trim();
297
- if (ln.startsWith('data:')) data += ln.slice(5).trim();
298
- }
299
- if (!data) continue;
300
- try {
301
- const j = JSON.parse(data);
302
- if (j?.response?.id && !responseId) responseId = j.response.id;
303
- if (ev === 'response.required_action' || (j?.type === 'response.required_action')) {
304
- required = j;
305
- }
306
- if (ev === 'response.done' || j?.type === 'response.done') {
307
- console.log('[RESPONSES] done');
308
- }
309
- } catch { /* ignore */ }
310
- }
311
- if (required && responseId) break; // got required_action
312
- }
313
- if (!required || !responseId) { console.warn('[RESPONSES] no required_action found'); return; }
314
- const toolCalls = required?.response?.required_action?.submit_tool_outputs?.tool_calls || [];
315
- console.log(`[RESPONSES] tool_calls: ${toolCalls.length}`);
316
- const outputs = toolCalls.map(tc => ({ tool_call_id: tc?.id || tc?.tool_call_id || '', output: 'ok' }));
317
- const contUrl = `${RC_BASE}/v1/responses/${encodeURIComponent(responseId)}/submit_tool_outputs`;
318
- const contPayload = { model: IFLOW_MODEL, tool_outputs: outputs, stream: true };
319
- const cont = await fetch(contUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'text/event-stream' }, body: JSON.stringify(contPayload) });
320
- console.log(`[RESPONSES][CONT] status=${cont.status}`);
321
- // Read until response.done or short timeout
322
- if (cont.body) {
323
- const reader2 = cont.body.getReader();
324
- const decoder2 = new TextDecoder();
325
- let buf2 = '';
326
- const until = Date.now() + 15000; // 15s max
327
- let finished = false;
328
- while (Date.now() < until) {
329
- const { value, done } = await reader2.read();
330
- if (done) break;
331
- buf2 += decoder2.decode(value, { stream: true });
332
- const parts = buf2.split(/\n\n/);
333
- buf2 = parts.pop() || '';
334
- for (const ch of parts) {
335
- const lines = ch.split(/\n/);
336
- let ev = '';
337
- let data = '';
338
- for (const ln of lines) {
339
- if (ln.startsWith('event:')) ev = ln.slice(6).trim();
340
- if (ln.startsWith('data:')) data += ln.slice(5).trim();
341
- }
342
- try {
343
- const jj = JSON.parse(data);
344
- if (ev === 'response.done' || jj?.type === 'response.done') {
345
- console.log('[RESPONSES][CONT] done');
346
- finished = true;
347
- break;
348
- }
349
- } catch { /* ignore */ }
350
- }
351
- if (finished) break;
352
- }
353
- try { await reader2.cancel(); } catch { /* ignore */ }
354
- }
355
- }
356
-
357
- async function main() {
358
- // 1) Load OAuth config & ensure token freshness before any real request
359
- const oauth = await loadIFlowOAuthConfig(CONFIG_PATH);
360
- const token = await ensureToken(oauth);
361
-
362
- // 2) Perform requested action
363
- if (MODE === 'upstream') {
364
- await requestUpstreamChat(oauth, token);
365
- return;
366
- }
367
- if (MODE === 'websearch') {
368
- await requestUpstreamWebSearch(oauth, token);
369
- return;
370
- }
371
- if (RUN_TOOLS) {
372
- await requestProxyChat(); // warmup
373
- await runResponsesToolLoop();
374
- return;
375
- }
376
- await requestProxyChat();
377
- }
378
-
379
- main().catch(err => { console.error(err); process.exit(1); });
@@ -1,26 +0,0 @@
1
- import { executeTool } from '../dist/server/utils/tool-executor.js';
2
-
3
- async function run() {
4
- const payloads = [
5
- { name: 'string_cmd_readme_head', args: { command: 'find . -type f -name "README*" | head -20' } },
6
- { name: 'string_cmd_md_grep', args: { command: 'find . -type f -name "*.md" | grep -i readme | head -20' } },
7
- { name: 'argv_cmd_simple', args: { command: ['find', '.', '-type', 'f', '-name', 'README*'] } },
8
- ];
9
-
10
- for (const p of payloads) {
11
- const argStr = JSON.stringify(p.args);
12
- const res = await executeTool({ id: 'test', name: 'shell', args: argStr });
13
- console.log('--- case:', p.name, '---');
14
- console.log('args:', argStr);
15
- if (res.error) {
16
- console.log('ERROR:', res.error);
17
- } else {
18
- const out = (res.output || '').split('\n').slice(0, 10).join('\n');
19
- console.log('OK (first 10 lines)');
20
- console.log(out);
21
- }
22
- }
23
- }
24
-
25
- run().catch(e => { console.error(e); process.exit(1); });
26
-