9router 0.2.49 → 0.2.50

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 (119) hide show
  1. package/README.md +0 -1
  2. package/app/.next/BUILD_ID +1 -1
  3. package/app/.next/app-build-manifest.json +78 -78
  4. package/app/.next/app-path-routes-manifest.json +18 -18
  5. package/app/.next/build-manifest.json +2 -2
  6. package/app/.next/prerender-manifest.json +36 -36
  7. package/app/.next/server/app/(dashboard)/dashboard/cli-tools/page_client-reference-manifest.js +1 -1
  8. package/app/.next/server/app/(dashboard)/dashboard/combos/page_client-reference-manifest.js +1 -1
  9. package/app/.next/server/app/(dashboard)/dashboard/endpoint/page_client-reference-manifest.js +1 -1
  10. package/app/.next/server/app/(dashboard)/dashboard/page_client-reference-manifest.js +1 -1
  11. package/app/.next/server/app/(dashboard)/dashboard/profile/page_client-reference-manifest.js +1 -1
  12. package/app/.next/server/app/(dashboard)/dashboard/providers/[id]/page_client-reference-manifest.js +1 -1
  13. package/app/.next/server/app/(dashboard)/dashboard/providers/new/page_client-reference-manifest.js +1 -1
  14. package/app/.next/server/app/(dashboard)/dashboard/providers/page_client-reference-manifest.js +1 -1
  15. package/app/.next/server/app/(dashboard)/dashboard/translator/page_client-reference-manifest.js +1 -1
  16. package/app/.next/server/app/(dashboard)/dashboard/usage/page_client-reference-manifest.js +1 -1
  17. package/app/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/app/.next/server/app/_not-found.html +1 -1
  19. package/app/.next/server/app/_not-found.rsc +1 -1
  20. package/app/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -1
  21. package/app/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
  22. package/app/.next/server/app/api/cli-tools/claude-settings/route_client-reference-manifest.js +1 -1
  23. package/app/.next/server/app/api/cli-tools/codex-settings/route_client-reference-manifest.js +1 -1
  24. package/app/.next/server/app/api/cloud/auth/route_client-reference-manifest.js +1 -1
  25. package/app/.next/server/app/api/cloud/credentials/update/route_client-reference-manifest.js +1 -1
  26. package/app/.next/server/app/api/cloud/model/resolve/route_client-reference-manifest.js +1 -1
  27. package/app/.next/server/app/api/cloud/models/alias/route_client-reference-manifest.js +1 -1
  28. package/app/.next/server/app/api/combos/[id]/route_client-reference-manifest.js +1 -1
  29. package/app/.next/server/app/api/combos/route_client-reference-manifest.js +1 -1
  30. package/app/.next/server/app/api/init/route_client-reference-manifest.js +1 -1
  31. package/app/.next/server/app/api/keys/[id]/route_client-reference-manifest.js +1 -1
  32. package/app/.next/server/app/api/keys/route_client-reference-manifest.js +1 -1
  33. package/app/.next/server/app/api/models/alias/route_client-reference-manifest.js +1 -1
  34. package/app/.next/server/app/api/models/route_client-reference-manifest.js +1 -1
  35. package/app/.next/server/app/api/oauth/[provider]/[action]/route_client-reference-manifest.js +1 -1
  36. package/app/.next/server/app/api/oauth/kiro/import/route_client-reference-manifest.js +1 -1
  37. package/app/.next/server/app/api/oauth/kiro/social-authorize/route_client-reference-manifest.js +1 -1
  38. package/app/.next/server/app/api/oauth/kiro/social-exchange/route_client-reference-manifest.js +1 -1
  39. package/app/.next/server/app/api/pricing/route_client-reference-manifest.js +1 -1
  40. package/app/.next/server/app/api/providers/[id]/models/route_client-reference-manifest.js +1 -1
  41. package/app/.next/server/app/api/providers/[id]/route_client-reference-manifest.js +1 -1
  42. package/app/.next/server/app/api/providers/[id]/test/route_client-reference-manifest.js +1 -1
  43. package/app/.next/server/app/api/providers/client/route_client-reference-manifest.js +1 -1
  44. package/app/.next/server/app/api/providers/route_client-reference-manifest.js +1 -1
  45. package/app/.next/server/app/api/providers/validate/route_client-reference-manifest.js +1 -1
  46. package/app/.next/server/app/api/settings/route_client-reference-manifest.js +1 -1
  47. package/app/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  48. package/app/.next/server/app/api/sync/cloud/route_client-reference-manifest.js +1 -1
  49. package/app/.next/server/app/api/sync/initialize/route_client-reference-manifest.js +1 -1
  50. package/app/.next/server/app/api/tags/route_client-reference-manifest.js +1 -1
  51. package/app/.next/server/app/api/translator/load/route_client-reference-manifest.js +1 -1
  52. package/app/.next/server/app/api/translator/save/route_client-reference-manifest.js +1 -1
  53. package/app/.next/server/app/api/translator/send/route_client-reference-manifest.js +1 -1
  54. package/app/.next/server/app/api/translator/translate/route_client-reference-manifest.js +1 -1
  55. package/app/.next/server/app/api/usage/[connectionId]/route_client-reference-manifest.js +1 -1
  56. package/app/.next/server/app/api/usage/history/route_client-reference-manifest.js +1 -1
  57. package/app/.next/server/app/api/v1/api/chat/route_client-reference-manifest.js +1 -1
  58. package/app/.next/server/app/api/v1/chat/completions/route_client-reference-manifest.js +1 -1
  59. package/app/.next/server/app/api/v1/messages/count_tokens/route_client-reference-manifest.js +1 -1
  60. package/app/.next/server/app/api/v1/messages/route_client-reference-manifest.js +1 -1
  61. package/app/.next/server/app/api/v1/models/route_client-reference-manifest.js +1 -1
  62. package/app/.next/server/app/api/v1/responses/route_client-reference-manifest.js +1 -1
  63. package/app/.next/server/app/api/v1/route_client-reference-manifest.js +1 -1
  64. package/app/.next/server/app/api/v1beta/models/[...path]/route_client-reference-manifest.js +1 -1
  65. package/app/.next/server/app/api/v1beta/models/route_client-reference-manifest.js +1 -1
  66. package/app/.next/server/app/callback/page_client-reference-manifest.js +1 -1
  67. package/app/.next/server/app/callback.html +1 -1
  68. package/app/.next/server/app/callback.rsc +1 -1
  69. package/app/.next/server/app/dashboard/cli-tools.html +1 -1
  70. package/app/.next/server/app/dashboard/cli-tools.rsc +1 -1
  71. package/app/.next/server/app/dashboard/combos.html +1 -1
  72. package/app/.next/server/app/dashboard/combos.rsc +1 -1
  73. package/app/.next/server/app/dashboard/endpoint.html +1 -1
  74. package/app/.next/server/app/dashboard/endpoint.rsc +1 -1
  75. package/app/.next/server/app/dashboard/profile.html +1 -1
  76. package/app/.next/server/app/dashboard/profile.rsc +1 -1
  77. package/app/.next/server/app/dashboard/providers/new.html +1 -1
  78. package/app/.next/server/app/dashboard/providers/new.rsc +1 -1
  79. package/app/.next/server/app/dashboard/providers.html +1 -1
  80. package/app/.next/server/app/dashboard/providers.rsc +1 -1
  81. package/app/.next/server/app/dashboard/settings/pricing/page_client-reference-manifest.js +1 -1
  82. package/app/.next/server/app/dashboard/settings/pricing.html +1 -1
  83. package/app/.next/server/app/dashboard/settings/pricing.rsc +1 -1
  84. package/app/.next/server/app/dashboard/translator.html +1 -1
  85. package/app/.next/server/app/dashboard/translator.rsc +1 -1
  86. package/app/.next/server/app/dashboard/usage.html +1 -1
  87. package/app/.next/server/app/dashboard/usage.rsc +1 -1
  88. package/app/.next/server/app/dashboard.html +1 -1
  89. package/app/.next/server/app/dashboard.rsc +1 -1
  90. package/app/.next/server/app/index.html +1 -1
  91. package/app/.next/server/app/index.rsc +1 -1
  92. package/app/.next/server/app/landing/page_client-reference-manifest.js +1 -1
  93. package/app/.next/server/app/landing.html +1 -1
  94. package/app/.next/server/app/landing.rsc +1 -1
  95. package/app/.next/server/app/login/page_client-reference-manifest.js +1 -1
  96. package/app/.next/server/app/login.html +1 -1
  97. package/app/.next/server/app/login.rsc +1 -1
  98. package/app/.next/server/app/page_client-reference-manifest.js +1 -1
  99. package/app/.next/server/app-paths-manifest.json +18 -18
  100. package/app/.next/server/middleware-manifest.json +1 -1
  101. package/app/.next/server/pages/404.html +1 -1
  102. package/app/.next/server/pages/500.html +1 -1
  103. package/cli.js +49 -14
  104. package/package.json +2 -1
  105. package/src/cli/api/client.js +375 -0
  106. package/src/cli/menus/apiKeys.js +259 -0
  107. package/src/cli/menus/cliTools.js +221 -0
  108. package/src/cli/menus/combos.js +477 -0
  109. package/src/cli/menus/providers.js +448 -0
  110. package/src/cli/menus/settings.js +86 -0
  111. package/src/cli/terminalUI.js +86 -0
  112. package/src/cli/utils/display.js +156 -0
  113. package/src/cli/utils/format.js +125 -0
  114. package/src/cli/utils/input.js +211 -0
  115. package/src/cli/utils/menuHelper.js +155 -0
  116. package/src/cli/utils/modelSelector.js +133 -0
  117. package/hooks/model-websearch.cjs +0 -319
  118. /package/app/.next/static/{z-NGTtnYbvnsa63SSl6HV → vEruHXnDBNEM-jV9xL72D}/_buildManifest.js +0 -0
  119. /package/app/.next/static/{z-NGTtnYbvnsa63SSl6HV → vEruHXnDBNEM-jV9xL72D}/_ssgManifest.js +0 -0
@@ -0,0 +1,133 @@
1
+ const api = require("../api/client");
2
+ const { prompt } = require("./input");
3
+ const { clearScreen } = require("./display");
4
+
5
+ // Provider alias order: OAuth first, then API Key (matches ModelSelectModal)
6
+ const PROVIDER_ALIAS_ORDER = [
7
+ "cc", "ag", "cx", "if", "qw", "gc", "gh", "kr",
8
+ "openrouter", "glm", "kimi", "minimax", "openai", "anthropic", "gemini"
9
+ ];
10
+
11
+ // Alias to display name mapping
12
+ const PROVIDER_ALIAS_NAMES = {
13
+ cc: "Claude Code",
14
+ ag: "Antigravity",
15
+ cx: "OpenAI Codex",
16
+ if: "iFlow AI",
17
+ qw: "Qwen Code",
18
+ gc: "Gemini CLI",
19
+ gh: "GitHub Copilot",
20
+ kr: "Kiro AI",
21
+ openrouter: "OpenRouter",
22
+ glm: "GLM Coding",
23
+ kimi: "Kimi Coding",
24
+ minimax: "Minimax Coding",
25
+ openai: "OpenAI",
26
+ anthropic: "Anthropic",
27
+ gemini: "Gemini"
28
+ };
29
+
30
+ /**
31
+ * Get all available models grouped by provider + combos
32
+ * @returns {Promise<{combos: Array, groups: Object}>}
33
+ */
34
+ async function getAvailableModelsGrouped() {
35
+ const result = await api.getAvailableModels();
36
+ if (!result.success) return { combos: [], groups: {} };
37
+
38
+ const models = result.data?.data || [];
39
+ const combos = [];
40
+ const groups = {};
41
+
42
+ models.forEach(m => {
43
+ if (m.owned_by === "combo") {
44
+ combos.push(m.id);
45
+ } else {
46
+ const provider = m.owned_by;
47
+ if (!groups[provider]) {
48
+ groups[provider] = [];
49
+ }
50
+ groups[provider].push(m.id);
51
+ }
52
+ });
53
+
54
+ return { combos, groups };
55
+ }
56
+
57
+ /**
58
+ * Display model list and prompt for selection
59
+ * @param {string} title - Title to display
60
+ * @param {string} currentValue - Current selected value (optional)
61
+ * @returns {Promise<string|null>} Selected model ID or null if cancelled
62
+ */
63
+ async function selectModelFromList(title, currentValue = "") {
64
+ const { combos, groups } = await getAvailableModelsGrouped();
65
+
66
+ const totalModels = combos.length + Object.values(groups).flat().length;
67
+ if (totalModels === 0) {
68
+ return null;
69
+ }
70
+
71
+ // Build flat list for selection
72
+ const allModels = [];
73
+
74
+ // Display
75
+ clearScreen();
76
+ console.log(`\n🎯 ${title}`);
77
+ console.log("=".repeat(50));
78
+ if (currentValue) {
79
+ console.log(`Current: ${currentValue}\n`);
80
+ } else {
81
+ console.log();
82
+ }
83
+
84
+ let idx = 1;
85
+
86
+ // Combos first
87
+ if (combos.length > 0) {
88
+ console.log("[Combos]");
89
+ combos.forEach(combo => {
90
+ console.log(` ${idx}. ${combo}`);
91
+ allModels.push(combo);
92
+ idx++;
93
+ });
94
+ console.log();
95
+ }
96
+
97
+ // Provider groups in order (by alias)
98
+ const sortedProviders = Object.keys(groups).sort((a, b) => {
99
+ const idxA = PROVIDER_ALIAS_ORDER.indexOf(a);
100
+ const idxB = PROVIDER_ALIAS_ORDER.indexOf(b);
101
+ return (idxA === -1 ? 999 : idxA) - (idxB === -1 ? 999 : idxB);
102
+ });
103
+
104
+ sortedProviders.forEach(provider => {
105
+ const providerName = PROVIDER_ALIAS_NAMES[provider] || provider;
106
+ console.log(`[${providerName}]`);
107
+ groups[provider].forEach(model => {
108
+ console.log(` ${idx}. ${model}`);
109
+ allModels.push(model);
110
+ idx++;
111
+ });
112
+ console.log();
113
+ });
114
+
115
+ console.log(" 0. Cancel\n");
116
+
117
+ // Prompt for number input
118
+ const input = await prompt("Enter number: ");
119
+ const num = parseInt(input, 10);
120
+
121
+ if (isNaN(num) || num === 0 || num < 0 || num > allModels.length) {
122
+ return null;
123
+ }
124
+
125
+ return allModels[num - 1];
126
+ }
127
+
128
+ module.exports = {
129
+ selectModelFromList,
130
+ getAvailableModelsGrouped,
131
+ PROVIDER_ALIAS_ORDER,
132
+ PROVIDER_ALIAS_NAMES
133
+ };
@@ -1,319 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * 9ROUTER WebSearch Hook - Model-based WebSearch via API
4
- *
5
- * Intercepts Claude's WebSearch tool and executes search via 9ROUTER API.
6
- * Reads configuration from ~/.claude/settings.json (set by dashboard).
7
- *
8
- * Environment Variables (from settings.json):
9
- * ANTHROPIC_BASE_URL - API endpoint URL
10
- * ANTHROPIC_AUTH_TOKEN - API authentication token
11
- * 9ROUTER_WEBSEARCH_MODEL - Model to use for search (e.g., gemini/gemini-2.5-flash)
12
- *
13
- * Exit codes:
14
- * 0 - Allow tool (pass-through to native WebSearch)
15
- * 2 - Block tool (deny with results/message)
16
- */
17
-
18
- const https = require("https");
19
- const http = require("http");
20
- const fs = require("fs");
21
- const path = require("path");
22
- const os = require("os");
23
-
24
- // ============================================================================
25
- // CONFIGURATION
26
- // ============================================================================
27
-
28
- const SEARCH_PROMPT = `You are a helpful assistant. Answer the following question directly using your knowledge. Do NOT use any tools, do NOT output tool calls, do NOT output XML tags like <tool_code> or <invoke>. Just provide a direct, comprehensive text answer.
29
-
30
- Instructions:
31
- 1. Provide current, accurate information based on your knowledge
32
- 2. Include relevant URLs/sources when available
33
- 3. Be concise but thorough - prioritize key facts
34
- 4. Focus on factual information from reliable sources
35
- 5. If information may be outdated, note the discrepancy
36
- 6. Format output clearly with sections if the topic is complex
37
- 7. IMPORTANT: Output plain text only, no tool calls
38
-
39
- Question: `;
40
-
41
- const MIN_VALID_RESPONSE_LENGTH = 20;
42
- const DEFAULT_TIMEOUT_MS = 60000;
43
-
44
- // ============================================================================
45
- // SETTINGS READER
46
- // ============================================================================
47
-
48
- function getClaudeSettingsPath() {
49
- return path.join(os.homedir(), ".claude", "settings.json");
50
- }
51
-
52
- function readSettings() {
53
- try {
54
- const settingsPath = getClaudeSettingsPath();
55
- const content = fs.readFileSync(settingsPath, "utf-8");
56
- return JSON.parse(content);
57
- } catch {
58
- return null;
59
- }
60
- }
61
-
62
- function getConfig() {
63
- const settings = readSettings();
64
- const env = settings?.env || {};
65
-
66
- return {
67
- baseUrl: env.ANTHROPIC_BASE_URL || process.env.ANTHROPIC_BASE_URL,
68
- authToken: env.ANTHROPIC_AUTH_TOKEN || process.env.ANTHROPIC_AUTH_TOKEN,
69
- model: env.9ROUTER_WEBSEARCH_MODEL || process.env.9ROUTER_WEBSEARCH_MODEL || "gemini/gemini-2.5-flash",
70
- };
71
- }
72
-
73
- // ============================================================================
74
- // API CLIENT
75
- // ============================================================================
76
-
77
- function makeRequest(url, options, body) {
78
- return new Promise((resolve, reject) => {
79
- const urlObj = new URL(url);
80
- const isHttps = urlObj.protocol === "https:";
81
- const client = isHttps ? https : http;
82
-
83
- const req = client.request(url, options, (res) => {
84
- let data = "";
85
- res.on("data", (chunk) => (data += chunk));
86
- res.on("end", () => {
87
- resolve({ status: res.statusCode, data });
88
- });
89
- });
90
-
91
- req.on("error", reject);
92
- req.on("timeout", () => {
93
- req.destroy();
94
- reject(new Error("Request timeout"));
95
- });
96
-
97
- if (body) {
98
- req.write(body);
99
- }
100
- req.end();
101
- });
102
- }
103
-
104
- async function searchWithModel(query, config) {
105
- const { baseUrl, authToken, model } = config;
106
-
107
- if (!baseUrl) {
108
- return { success: false, error: "ANTHROPIC_BASE_URL not configured" };
109
- }
110
-
111
- const apiUrl = `${baseUrl}/api/v1/chat/completions`;
112
- const prompt = SEARCH_PROMPT + query;
113
-
114
- const requestBody = JSON.stringify({
115
- model: model,
116
- messages: [{ role: "user", content: prompt }],
117
- stream: false,
118
- max_tokens: 4096,
119
- tool_choice: "none", // Disable tool use, return text directly
120
- });
121
-
122
- const headers = {
123
- "Content-Type": "application/json",
124
- "Content-Length": Buffer.byteLength(requestBody),
125
- };
126
-
127
- if (authToken) {
128
- headers["Authorization"] = `Bearer ${authToken}`;
129
- }
130
-
131
- try {
132
- const response = await makeRequest(
133
- apiUrl,
134
- {
135
- method: "POST",
136
- headers,
137
- timeout: DEFAULT_TIMEOUT_MS,
138
- },
139
- requestBody
140
- );
141
-
142
- if (response.status !== 200) {
143
- return {
144
- success: false,
145
- error: `API returned ${response.status}: ${response.data.slice(0, 200)}`,
146
- };
147
- }
148
-
149
- const result = JSON.parse(response.data);
150
-
151
- // Support both OpenAI and Claude response formats
152
- let content = "";
153
-
154
- // OpenAI format: choices[0].message.content
155
- if (result.choices?.[0]?.message?.content) {
156
- content = result.choices[0].message.content;
157
- }
158
- // Claude format: content array with type "text"
159
- else if (Array.isArray(result.content)) {
160
- const textBlock = result.content.find(c => c.type === "text");
161
- content = textBlock?.text || "";
162
- }
163
- // Direct content string
164
- else if (typeof result.content === "string") {
165
- content = result.content;
166
- }
167
-
168
- if (!content || content.length < MIN_VALID_RESPONSE_LENGTH) {
169
- return { success: false, error: "Empty or too short response" };
170
- }
171
-
172
- return { success: true, content, model };
173
- } catch (err) {
174
- return { success: false, error: err.message || "Unknown error" };
175
- }
176
- }
177
-
178
- // ============================================================================
179
- // OUTPUT FORMATTERS
180
- // ============================================================================
181
-
182
- function formatSearchResults(query, content, model) {
183
- // Keep it simple - just the content, Claude will see the context
184
- return content;
185
- }
186
-
187
- function outputSuccess(query, content, model) {
188
- const formattedResults = `[WebSearch via ${model}]\nQuery: "${query}"\n\n${content}`;
189
-
190
- // Block tool with results, suppress duplicate output
191
- const output = {
192
- decision: "block",
193
- reason: formattedResults,
194
- suppressOutput: true,
195
- };
196
-
197
- console.log(JSON.stringify(output));
198
- process.exit(0);
199
- }
200
-
201
- function outputError(query, error, model) {
202
- const message = [
203
- `[WebSearch - ${model} Error]`,
204
- "",
205
- `Error: ${error}`,
206
- "",
207
- `Query: "${query}"`,
208
- "",
209
- "Troubleshooting:",
210
- " - Check ANTHROPIC_BASE_URL in ~/.claude/settings.json",
211
- " - Verify the model is available in your providers",
212
- " - Run 9router dashboard to configure settings",
213
- ].join("\n");
214
-
215
- const output = {
216
- decision: "block",
217
- reason: `WebSearch failed: ${error}`,
218
- hookSpecificOutput: {
219
- hookEventName: "PreToolUse",
220
- permissionDecision: "deny",
221
- permissionDecisionReason: message,
222
- },
223
- };
224
-
225
- console.log(JSON.stringify(output));
226
- process.exit(2);
227
- }
228
-
229
- function outputNotConfigured(query) {
230
- const message = [
231
- "[WebSearch - Not Configured]",
232
- "",
233
- "WebSearch model is not configured.",
234
- "",
235
- "To configure:",
236
- " 1. Run 9router (or npm run dev)",
237
- " 2. Go to Dashboard > CLI Tools",
238
- " 3. Set WebSearch Model (e.g., gemini/gemini-2.5-flash)",
239
- " 4. Click Apply",
240
- "",
241
- `Query: "${query}"`,
242
- ].join("\n");
243
-
244
- const output = {
245
- decision: "block",
246
- reason: "WebSearch not configured",
247
- hookSpecificOutput: {
248
- hookEventName: "PreToolUse",
249
- permissionDecision: "deny",
250
- permissionDecisionReason: message,
251
- },
252
- };
253
-
254
- console.log(JSON.stringify(output));
255
- process.exit(2);
256
- }
257
-
258
- // ============================================================================
259
- // MAIN HOOK LOGIC
260
- // ============================================================================
261
-
262
- let input = "";
263
- process.stdin.setEncoding("utf8");
264
- process.stdin.on("data", (chunk) => {
265
- input += chunk;
266
- });
267
- process.stdin.on("end", () => {
268
- processHook();
269
- });
270
- process.stdin.on("error", () => {
271
- process.exit(0);
272
- });
273
-
274
- async function processHook() {
275
- try {
276
- const data = JSON.parse(input);
277
-
278
- // Only handle WebSearch tool
279
- if (data.tool_name !== "WebSearch") {
280
- process.exit(0);
281
- }
282
-
283
- const query = data.tool_input?.query || "";
284
- if (!query) {
285
- process.exit(0);
286
- }
287
-
288
- const config = getConfig();
289
-
290
- if (!config.baseUrl || !config.model) {
291
- outputNotConfigured(query);
292
- return;
293
- }
294
-
295
- if (process.env.9ROUTER_DEBUG) {
296
- console.error(`[9ROUTER Hook] Searching with model: ${config.model}`);
297
- console.error(`[9ROUTER Hook] API URL: ${config.baseUrl}`);
298
- }
299
-
300
- const result = await searchWithModel(query, config);
301
-
302
- if (result.success) {
303
- outputSuccess(query, result.content, result.model);
304
- } else {
305
- // Fallback to native WebSearch when API fails
306
- if (process.env.9ROUTER_DEBUG) {
307
- console.error(`[9ROUTER Hook] API failed: ${result.error}, falling back to native WebSearch`);
308
- }
309
- process.exit(0);
310
- }
311
- } catch (err) {
312
- if (process.env.9ROUTER_DEBUG) {
313
- console.error("[9ROUTER Hook] Parse error:", err.message);
314
- }
315
- // Fallback to native WebSearch on any error
316
- process.exit(0);
317
- }
318
- }
319
-