@fudrouter/fsrouter 0.6.84 → 0.6.86

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 (58) hide show
  1. package/dist/open-sse/handlers/ttsProviders/index.js +1 -1
  2. package/dist/open-sse/services/usage/misc.js +19 -0
  3. package/dist/open-sse/services/usage/nous.js +36 -0
  4. package/dist/open-sse/services/usage.js +4 -0
  5. package/dist/open-sse/shared/clineAuth.js +21 -2
  6. package/dist/open-sse/utils/error.js +14 -0
  7. package/dist/routes/providers/[id]/test/testUtils.js +11 -11
  8. package/dist/routes/usage/[connectionId]/route.js +1 -1
  9. package/open-sse/handlers/ttsProviders/index.js +1 -1
  10. package/open-sse/services/usage/misc.js +19 -0
  11. package/open-sse/services/usage/nous.js +36 -0
  12. package/open-sse/services/usage.js +4 -0
  13. package/open-sse/shared/clineAuth.js +21 -2
  14. package/open-sse/utils/error.js +14 -0
  15. package/package.json +1 -1
  16. package/public/.vite/manifest.json +85 -85
  17. package/public/assets/EndpointPageClient.B6118Vl0.js +6 -0
  18. package/public/assets/Loading.CKcPqzyp.js +1 -0
  19. package/public/assets/NoAuthProxyCard.C8gs3p9D.js +89 -0
  20. package/public/assets/index.CemlR_k0.js +89 -0
  21. package/public/assets/page.B8uovCa-.js +2 -0
  22. package/public/assets/page.B9AYvEAq.js +1 -0
  23. package/public/assets/page.BC8_Skab.js +1 -0
  24. package/public/assets/page.BGiDmMTs.js +1 -0
  25. package/public/assets/page.BH0jk80k.js +1 -0
  26. package/public/assets/page.BIo8aNfA.js +64 -0
  27. package/public/assets/page.BJ6h2zDl.js +1 -0
  28. package/public/assets/page.BO2F41Lc.js +23 -0
  29. package/public/assets/page.BPX2jyWY.js +1 -0
  30. package/public/assets/page.BSjBIFVt.js +1 -0
  31. package/public/assets/page.B_Tgnl-b.js +5 -0
  32. package/public/assets/page.BaHkKogf.js +1 -0
  33. package/public/assets/page.BdfPcUya.js +1 -0
  34. package/public/assets/page.BrhMwL3Z.js +2 -0
  35. package/public/assets/page.BwjqeY_m.js +6 -0
  36. package/public/assets/page.CDVWVylS.js +1 -0
  37. package/public/assets/page.CEw2SuPk.js +4 -0
  38. package/public/assets/page.CQU7BVUj.js +1 -0
  39. package/public/assets/page.CvBBDiKu.js +1 -0
  40. package/public/assets/page.CzOg3TqA.js +5 -0
  41. package/public/assets/page.D2Y19JAi.js +1 -0
  42. package/public/assets/page.DAzdg8fd.js +1 -0
  43. package/public/assets/page.DIb0IloM.js +1 -0
  44. package/public/assets/page.DO1n3ZJL.js +1 -0
  45. package/public/assets/page.DOsZhkAT.js +1 -0
  46. package/public/assets/page.DP_7ZXFk.js +1 -0
  47. package/public/assets/page.DliO3TaK.js +1 -0
  48. package/public/assets/page.DnJi9BLz.js +1 -0
  49. package/public/assets/page.DomMS7Cy.js +1 -0
  50. package/public/assets/page.DraAfhp6.js +31 -0
  51. package/public/assets/page.FJ3Dx8uu.js +1 -0
  52. package/public/assets/page.LtaYaOOQ.js +6 -0
  53. package/public/assets/page.SRSYeHUs.js +1 -0
  54. package/public/assets/page.rQUdj203.js +1 -0
  55. package/public/assets/page.sVpnihMe.js +1 -0
  56. package/public/assets/page.x5y6h1mx.js +1 -0
  57. package/public/assets/page.z06UnjTW.js +1 -0
  58. package/public/index.html +1 -1
@@ -33,7 +33,7 @@ export async function synthesizeViaConfig(provider, text, model, credentials) {
33
33
  if (!handler) return null;
34
34
  const apiKey = credentials?.apiKey;
35
35
  if (cfg.authType !== "none" && !apiKey) throw new Error(`${provider} API key required`);
36
- const { PROVIDER_MODELS } = await import("open-sse/config/providerModels.js");
36
+ const { PROVIDER_MODELS } = await import('../../../open-sse/config/providerModels.js');
37
37
  const ttsModels = (PROVIDER_MODELS[provider] || []).filter(m => (m.kind || m.type) === "tts");
38
38
  const defaultModel = ttsModels[0]?.id || "";
39
39
  const { modelId, voiceId } = parseModelVoice(model, defaultModel, "", ttsModels);
@@ -267,3 +267,22 @@ export async function getQoderUsage(accessToken, proxyOptions = null) {
267
267
  return { message: `Qoder connected. Unable to fetch usage: ${error.message}` };
268
268
  }
269
269
  }
270
+
271
+ /**
272
+ * Cloudflare Workers AI Usage
273
+ * Free tier: 10,000 neurons/day. No public usage API — returns free tier info.
274
+ */
275
+ export async function getCloudflareAiUsage(apiKey, providerSpecificData) {
276
+ return {
277
+ plan: "Workers AI Free",
278
+ quotas: {
279
+ neurons: {
280
+ used: 0,
281
+ total: 10000,
282
+ resetAt: null,
283
+ unlimited: false,
284
+ displayName: "Neurons (daily free)",
285
+ },
286
+ },
287
+ };
288
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Nous Research usage/quota fetcher.
3
+ *
4
+ * Nous Portal free tier (no credit card): 50 RPM, 500K TPM.
5
+ * Nous does not expose a public per-account usage/quota API, so we surface the
6
+ * published free-tier limits as a static quota entry. Requests count against
7
+ * these limits and reset on a rolling window; without a live counter we report
8
+ * the plan cap and leave `remaining` open (Infinity) so the UI shows the tier.
9
+ *
10
+ * @param {string} accessToken - OAuth Bearer token (unused for static tier info)
11
+ * @returns {Promise<{quotas: Array, plan: string}>}
12
+ */
13
+ export async function getNousUsage(accessToken) {
14
+ void accessToken; // reserved for future live quota endpoint
15
+ return {
16
+ plan: "Free Tier (Nous Portal)",
17
+ quotas: [
18
+ {
19
+ label: "Requests / min",
20
+ total: 50,
21
+ used: 0,
22
+ remaining: 50,
23
+ unit: "rpm",
24
+ resetAt: null,
25
+ },
26
+ {
27
+ label: "Tokens / min",
28
+ total: 500000,
29
+ used: 0,
30
+ remaining: 500000,
31
+ unit: "tpm",
32
+ resetAt: null,
33
+ },
34
+ ],
35
+ };
36
+ }
@@ -12,6 +12,7 @@ import { getKiroUsage } from "./usage/kiro.js";
12
12
  import { getMiniMaxUsage } from "./usage/minimax.js";
13
13
  import { getCodeBuddyCnUsage } from "./usage/codebuddy-cn.js";
14
14
  import { getGrokCliUsage } from "./usage/grok-cli.js";
15
+ import { getNousUsage } from "./usage/nous.js";
15
16
  import {
16
17
  getQwenUsage,
17
18
  getIflowUsage,
@@ -19,6 +20,7 @@ import {
19
20
  getGlmUsage,
20
21
  getVercelAiGatewayUsage,
21
22
  getQoderUsage,
23
+ getCloudflareAiUsage,
22
24
  } from "./usage/misc.js";
23
25
 
24
26
  /**
@@ -45,6 +47,8 @@ const USAGE_HANDLERS = {
45
47
  "vercel-ai-gateway": (c) => getVercelAiGatewayUsage(c.apiKey, c.proxyOptions),
46
48
  "codebuddy-cn": (c) => getCodeBuddyCnUsage(c.accessToken, c.apiKey, c.providerSpecificData, c.proxyOptions),
47
49
  "grok-cli": (c) => getGrokCliUsage(c.accessToken, c.providerSpecificData, c.proxyOptions),
50
+ "nous-research": (c) => getNousUsage(c.accessToken),
51
+ "cloudflare-ai": (c) => getCloudflareAiUsage(c.apiKey, c.providerSpecificData),
48
52
  };
49
53
 
50
54
  export async function getUsageForProvider(connection, proxyOptions = null) {
@@ -1,6 +1,25 @@
1
- import pkg from "../../package.json" with { type: "json" };
1
+ import { createRequire } from "module";
2
+ import { fileURLToPath } from "url";
3
+ import { dirname } from "path";
4
+ import { existsSync } from "fs";
2
5
 
3
- const APP_VERSION = pkg.version || "0.0.0";
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+
8
+ // Resolve package.json by walking up — works in both src (open-sse/shared)
9
+ // and dist (dist/open-sse/shared) layouts.
10
+ function resolvePkgPath() {
11
+ let d = __dirname;
12
+ for (let i = 0; i < 6; i++) {
13
+ const p = `${d}/package.json`;
14
+ if (existsSync(p)) return p;
15
+ d = dirname(d);
16
+ }
17
+ return null;
18
+ }
19
+
20
+ const _require = createRequire(import.meta.url);
21
+ const _pkgPath = resolvePkgPath();
22
+ const APP_VERSION = _pkgPath ? _require(_pkgPath).version || "0.0.0" : "0.0.0";
4
23
 
5
24
  export function getClineAccessToken(token) {
6
25
  if (typeof token !== "string") return "";
@@ -145,3 +145,17 @@ export function formatProviderError(error, provider, model, statusCode) {
145
145
  const causeStr = causeCode || causeMsg ? ` (cause: ${[causeCode, causeMsg].filter(Boolean).join(": ")})` : "";
146
146
  return `[${code}]: ${message}${causeStr}`;
147
147
  }
148
+
149
+ /**
150
+ * Strip internal details from an error message so they don't leak upstream paths.
151
+ * @param {unknown} message
152
+ * @returns {string}
153
+ */
154
+ export function sanitizeErrorMessage(message) {
155
+ if (message == null) return "";
156
+ const str = typeof message === "string" ? message : String(message);
157
+ // Remove absolute filesystem paths (e.g. /usr/local/lib/node_modules/.../file.js)
158
+ return str.replace(/\/[\w./-]+\.(js|ts|mjs|cjs)(:\d+:\d+)?/g, (m) =>
159
+ m.length > 40 ? "[internal]" : m,
160
+ );
161
+ }
@@ -1,8 +1,8 @@
1
- import { getProviderConnectionById, updateProviderConnection } from '../../../../lib/localDb.js';
2
- import { resolveConnectionProxyConfig } from '../../../../lib/network/connectionProxy.js';
3
- import { testProxyUrl } from '../../../../lib/network/proxyTest.js';
4
- import { isOpenAICompatibleProvider, isAnthropicCompatibleProvider } from '../../../../shared/constants/providers.js';
5
- import { PROVIDER_ENDPOINTS } from '../../../../shared/constants/config.js';
1
+ import { getProviderConnectionById, updateProviderConnection } from '../../../../../dist/lib/localDb.js';
2
+ import { resolveConnectionProxyConfig } from '../../../../../dist/lib/network/connectionProxy.js';
3
+ import { testProxyUrl } from '../../../../../dist/lib/network/proxyTest.js';
4
+ import { isOpenAICompatibleProvider, isAnthropicCompatibleProvider } from '../../../../../dist/shared/constants/providers.js';
5
+ import { PROVIDER_ENDPOINTS } from '../../../../../dist/shared/constants/config.js';
6
6
  import { getDefaultModel } from '../../../../../open-sse/config/providerModels.js';
7
7
  import { resolveOllamaLocalHost } from '../../../../../open-sse/config/providers.js';
8
8
  import {
@@ -17,8 +17,8 @@ import {
17
17
  CLAUDE_CONFIG,
18
18
  CLINE_CONFIG,
19
19
  KILOCODE_CONFIG,
20
- } from '../../../../lib/oauth/constants/oauth.js';
21
- import { buildClineHeaders } from '../../../../shared/utils/clineAuth.js';
20
+ } from '../../../../../dist/lib/oauth/constants/oauth.js';
21
+ import { buildClineHeaders } from '../../../../../dist/shared/utils/clineAuth.js';
22
22
 
23
23
  // OAuth provider test endpoints
24
24
  const OAUTH_TEST_CONFIG = {
@@ -334,7 +334,7 @@ async function testOAuthConnection(connection, effectiveProxy = null) {
334
334
  async function fetchWithConnectionProxy(url, options = {}, effectiveProxy = null) {
335
335
  // Vercel relay: forward via relay URL
336
336
  if (effectiveProxy?.vercelRelayUrl) {
337
- const { proxyAwareFetch } = await import("open-sse/utils/proxyFetch.js");
337
+ const { proxyAwareFetch } = await import('../../../../../open-sse/utils/proxyFetch.js');
338
338
  return proxyAwareFetch(url, options, {
339
339
  vercelRelayUrl: effectiveProxy.vercelRelayUrl,
340
340
  });
@@ -344,7 +344,7 @@ async function fetchWithConnectionProxy(url, options = {}, effectiveProxy = null
344
344
  return fetch(url, options);
345
345
  }
346
346
 
347
- const { proxyAwareFetch } = await import("open-sse/utils/proxyFetch.js");
347
+ const { proxyAwareFetch } = await import('../../../../../open-sse/utils/proxyFetch.js');
348
348
  return proxyAwareFetch(url, options, {
349
349
  connectionProxyEnabled: true,
350
350
  connectionProxyUrl: effectiveProxy.connectionProxyUrl,
@@ -694,7 +694,7 @@ async function testApiKeyConnection(connection, effectiveProxy = null) {
694
694
  return { valid, error: errorMessage };
695
695
  }
696
696
  case "leonardo": {
697
- const { refreshLeonardoToken } = await import("open-sse/services/tokenRefresh.js");
697
+ const { refreshLeonardoToken } = await import('../../../../../open-sse/services/tokenRefresh.js');
698
698
  const cookie = connection.apiKey || connection.cookie;
699
699
  if (!cookie) return { valid: false, error: "Cookie tidak ditemukan" };
700
700
  const result = await refreshLeonardoToken(cookie, console);
@@ -715,7 +715,7 @@ async function testApiKeyConnection(connection, effectiveProxy = null) {
715
715
  }
716
716
  }
717
717
  case "weavy": {
718
- const { refreshWeavyToken } = await import("open-sse/services/tokenRefresh.js");
718
+ const { refreshWeavyToken } = await import('../../../../../open-sse/services/tokenRefresh.js');
719
719
  const email = connection.email || connection.name;
720
720
  if (!email) return { valid: false, error: "Email/name tidak ditemukan" };
721
721
  const result = await refreshWeavyToken(email, connection, console);
@@ -1,5 +1,5 @@
1
1
  // Ensure proxyFetch is loaded to patch globalThis.fetch
2
- import "open-sse/index.js";
2
+ import '../../../../open-sse/index.js';
3
3
  import { getProviderConnectionById, updateProviderConnection } from "../../../lib/localDb.js";
4
4
  import { getUsageForProvider } from "../../../open-sse/services/usage.js"; // Watcher trigger comment
5
5
  import { getExecutor } from "../../../open-sse/executors/index.js";
@@ -33,7 +33,7 @@ export async function synthesizeViaConfig(provider, text, model, credentials) {
33
33
  if (!handler) return null;
34
34
  const apiKey = credentials?.apiKey;
35
35
  if (cfg.authType !== "none" && !apiKey) throw new Error(`${provider} API key required`);
36
- const { PROVIDER_MODELS } = await import("open-sse/config/providerModels.js");
36
+ const { PROVIDER_MODELS } = await import('../../../open-sse/config/providerModels.js');
37
37
  const ttsModels = (PROVIDER_MODELS[provider] || []).filter(m => (m.kind || m.type) === "tts");
38
38
  const defaultModel = ttsModels[0]?.id || "";
39
39
  const { modelId, voiceId } = parseModelVoice(model, defaultModel, "", ttsModels);
@@ -267,3 +267,22 @@ export async function getQoderUsage(accessToken, proxyOptions = null) {
267
267
  return { message: `Qoder connected. Unable to fetch usage: ${error.message}` };
268
268
  }
269
269
  }
270
+
271
+ /**
272
+ * Cloudflare Workers AI Usage
273
+ * Free tier: 10,000 neurons/day. No public usage API — returns free tier info.
274
+ */
275
+ export async function getCloudflareAiUsage(apiKey, providerSpecificData) {
276
+ return {
277
+ plan: "Workers AI Free",
278
+ quotas: {
279
+ neurons: {
280
+ used: 0,
281
+ total: 10000,
282
+ resetAt: null,
283
+ unlimited: false,
284
+ displayName: "Neurons (daily free)",
285
+ },
286
+ },
287
+ };
288
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Nous Research usage/quota fetcher.
3
+ *
4
+ * Nous Portal free tier (no credit card): 50 RPM, 500K TPM.
5
+ * Nous does not expose a public per-account usage/quota API, so we surface the
6
+ * published free-tier limits as a static quota entry. Requests count against
7
+ * these limits and reset on a rolling window; without a live counter we report
8
+ * the plan cap and leave `remaining` open (Infinity) so the UI shows the tier.
9
+ *
10
+ * @param {string} accessToken - OAuth Bearer token (unused for static tier info)
11
+ * @returns {Promise<{quotas: Array, plan: string}>}
12
+ */
13
+ export async function getNousUsage(accessToken) {
14
+ void accessToken; // reserved for future live quota endpoint
15
+ return {
16
+ plan: "Free Tier (Nous Portal)",
17
+ quotas: [
18
+ {
19
+ label: "Requests / min",
20
+ total: 50,
21
+ used: 0,
22
+ remaining: 50,
23
+ unit: "rpm",
24
+ resetAt: null,
25
+ },
26
+ {
27
+ label: "Tokens / min",
28
+ total: 500000,
29
+ used: 0,
30
+ remaining: 500000,
31
+ unit: "tpm",
32
+ resetAt: null,
33
+ },
34
+ ],
35
+ };
36
+ }
@@ -12,6 +12,7 @@ import { getKiroUsage } from "./usage/kiro.js";
12
12
  import { getMiniMaxUsage } from "./usage/minimax.js";
13
13
  import { getCodeBuddyCnUsage } from "./usage/codebuddy-cn.js";
14
14
  import { getGrokCliUsage } from "./usage/grok-cli.js";
15
+ import { getNousUsage } from "./usage/nous.js";
15
16
  import {
16
17
  getQwenUsage,
17
18
  getIflowUsage,
@@ -19,6 +20,7 @@ import {
19
20
  getGlmUsage,
20
21
  getVercelAiGatewayUsage,
21
22
  getQoderUsage,
23
+ getCloudflareAiUsage,
22
24
  } from "./usage/misc.js";
23
25
 
24
26
  /**
@@ -45,6 +47,8 @@ const USAGE_HANDLERS = {
45
47
  "vercel-ai-gateway": (c) => getVercelAiGatewayUsage(c.apiKey, c.proxyOptions),
46
48
  "codebuddy-cn": (c) => getCodeBuddyCnUsage(c.accessToken, c.apiKey, c.providerSpecificData, c.proxyOptions),
47
49
  "grok-cli": (c) => getGrokCliUsage(c.accessToken, c.providerSpecificData, c.proxyOptions),
50
+ "nous-research": (c) => getNousUsage(c.accessToken),
51
+ "cloudflare-ai": (c) => getCloudflareAiUsage(c.apiKey, c.providerSpecificData),
48
52
  };
49
53
 
50
54
  export async function getUsageForProvider(connection, proxyOptions = null) {
@@ -1,6 +1,25 @@
1
- import pkg from "../../package.json" with { type: "json" };
1
+ import { createRequire } from "module";
2
+ import { fileURLToPath } from "url";
3
+ import { dirname } from "path";
4
+ import { existsSync } from "fs";
2
5
 
3
- const APP_VERSION = pkg.version || "0.0.0";
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+
8
+ // Resolve package.json by walking up — works in both src (open-sse/shared)
9
+ // and dist (dist/open-sse/shared) layouts.
10
+ function resolvePkgPath() {
11
+ let d = __dirname;
12
+ for (let i = 0; i < 6; i++) {
13
+ const p = `${d}/package.json`;
14
+ if (existsSync(p)) return p;
15
+ d = dirname(d);
16
+ }
17
+ return null;
18
+ }
19
+
20
+ const _require = createRequire(import.meta.url);
21
+ const _pkgPath = resolvePkgPath();
22
+ const APP_VERSION = _pkgPath ? _require(_pkgPath).version || "0.0.0" : "0.0.0";
4
23
 
5
24
  export function getClineAccessToken(token) {
6
25
  if (typeof token !== "string") return "";
@@ -145,3 +145,17 @@ export function formatProviderError(error, provider, model, statusCode) {
145
145
  const causeStr = causeCode || causeMsg ? ` (cause: ${[causeCode, causeMsg].filter(Boolean).join(": ")})` : "";
146
146
  return `[${code}]: ${message}${causeStr}`;
147
147
  }
148
+
149
+ /**
150
+ * Strip internal details from an error message so they don't leak upstream paths.
151
+ * @param {unknown} message
152
+ * @returns {string}
153
+ */
154
+ export function sanitizeErrorMessage(message) {
155
+ if (message == null) return "";
156
+ const str = typeof message === "string" ? message : String(message);
157
+ // Remove absolute filesystem paths (e.g. /usr/local/lib/node_modules/.../file.js)
158
+ return str.replace(/\/[\w./-]+\.(js|ts|mjs|cjs)(:\d+:\d+)?/g, (m) =>
159
+ m.length > 40 ? "[internal]" : m,
160
+ );
161
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fudrouter/fsrouter",
3
- "version": "0.6.84",
3
+ "version": "0.6.86",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "9Router v2 — Express Backend (API, SSE, DB, Auth, MITM)",