@fudrouter/fsrouter 0.6.77 → 0.6.78

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 (52) hide show
  1. package/dist/lib/oauth/constants/oauth.js +2 -0
  2. package/dist/lib/oauth/providers.js +72 -0
  3. package/dist/open-sse/providers/index.js +1 -1
  4. package/dist/open-sse/providers/registry/nous-research.js +10 -2
  5. package/dist/routes/headroom/extras/route.js +2 -2
  6. package/dist/shared/constants/providers.js +1 -1
  7. package/open-sse/providers/index.js +1 -1
  8. package/open-sse/providers/registry/nous-research.js +10 -2
  9. package/package.json +1 -1
  10. package/public/.vite/manifest.json +197 -85
  11. package/public/assets/EndpointPageClient.BNiu_vhL.js +6 -0
  12. package/public/assets/Loading.CRvAR61e.js +1 -0
  13. package/public/assets/NoAuthProxyCard.BARTPHmg.js +89 -0
  14. package/public/assets/index.BbQSujX0.js +89 -0
  15. package/public/assets/page.2cfQ0soc.js +5 -0
  16. package/public/assets/page.Azr84Ml-.js +1 -0
  17. package/public/assets/page.B57i00nL.js +1 -0
  18. package/public/assets/page.BTTzRlp6.js +1 -0
  19. package/public/assets/page.BY5IysJR.js +2 -0
  20. package/public/assets/page.BYy5vdYY.js +1 -0
  21. package/public/assets/page.Bkhxy2VJ.js +1 -0
  22. package/public/assets/page.BrLVxn10.js +1 -0
  23. package/public/assets/page.C0TLs5yW.js +1 -0
  24. package/public/assets/page.C1MGHZJw.js +1 -0
  25. package/public/assets/page.C5qOhBBX.js +1 -0
  26. package/public/assets/page.CL7pbImn.js +1 -0
  27. package/public/assets/page.CLbi_S1p.js +1 -0
  28. package/public/assets/page.CNUurvtj.js +23 -0
  29. package/public/assets/page.CWyZKaVw.js +31 -0
  30. package/public/assets/page.CXKvW0hn.js +1 -0
  31. package/public/assets/page.CXXS5Pj6.js +4 -0
  32. package/public/assets/page.CavjztiR.js +1 -0
  33. package/public/assets/page.CiURc2ed.js +64 -0
  34. package/public/assets/page.CjbJHAqY.js +1 -0
  35. package/public/assets/page.CqKdcCXs.js +5 -0
  36. package/public/assets/page.CuZp1ItQ.js +1 -0
  37. package/public/assets/page.Cx7iFgCR.js +1 -0
  38. package/public/assets/page.D023izTv.js +6 -0
  39. package/public/assets/page.D5dHsYMc.js +1 -0
  40. package/public/assets/page.DIBe8_S7.js +1 -0
  41. package/public/assets/page.DOS_9ovb.js +1 -0
  42. package/public/assets/page.DTfhMnYX.js +1 -0
  43. package/public/assets/page.D_dG62bf.js +1 -0
  44. package/public/assets/page.DjsFF4p7.js +1 -0
  45. package/public/assets/page.DmjQJ44n.js +6 -0
  46. package/public/assets/page.Dsd5yK6z.js +1 -0
  47. package/public/assets/page.EBdicyw3.js +2 -0
  48. package/public/assets/page.K07l9G5d.js +1 -0
  49. package/public/assets/page.N_CNyJfs.js +1 -0
  50. package/public/assets/page.cIm7s2zC.js +1 -0
  51. package/public/assets/page.u8Bt3ap8.js +1 -0
  52. package/public/index.html +1 -1
@@ -118,6 +118,8 @@ export const KIMCHI_CONFIG = { ...PROVIDER_OAUTH["kimchi"] };
118
118
  // Endpoint: cli-chat-proxy.grok.com — same client_id as xai, different flow + scopes
119
119
  export const GROK_CLI_CONFIG = { ...PROVIDER_OAUTH["grok-cli"] };
120
120
 
121
+ export const NOUS_CONFIG = { ...PROVIDER_OAUTH["nous-research"] };
122
+
121
123
  // OAuth timeout (5 minutes)
122
124
  export const OAUTH_TIMEOUT = 300000;
123
125
 
@@ -28,6 +28,7 @@ import {
28
28
  CODEBUDDY_CONFIG,
29
29
  KIMCHI_CONFIG,
30
30
  GROK_CLI_CONFIG,
31
+ NOUS_CONFIG,
31
32
  getOAuthClientMetadata,
32
33
  } from "./constants/oauth.js";
33
34
  import { XAI_CONFIG, XAI_PKCE_VERIFIER_BYTES } from "./constants/xai.js";
@@ -372,6 +373,77 @@ const PROVIDERS = {
372
373
  },
373
374
  },
374
375
 
376
+ // Nous Research (Nous Portal) — OAuth device code flow.
377
+ // Portal has no manual API-key page; auth is device-code only (client_id hermes-cli).
378
+ "nous-research": {
379
+ config: NOUS_CONFIG,
380
+ flowType: "device_code",
381
+ requestDeviceCode: async (config) => {
382
+ const body = new URLSearchParams({ client_id: config.clientId });
383
+ if (config.scope) body.set("scope", config.scope);
384
+ const response = await fetch(config.deviceCodeUrl, {
385
+ method: "POST",
386
+ headers: {
387
+ "Content-Type": "application/x-www-form-urlencoded",
388
+ Accept: "application/json",
389
+ },
390
+ body,
391
+ });
392
+ if (!response.ok) {
393
+ const error = await response.text();
394
+ throw new Error(`Nous device code request failed: ${error}`);
395
+ }
396
+ return await response.json();
397
+ },
398
+ pollToken: async (config, deviceCode) => {
399
+ const response = await fetch(config.tokenUrl, {
400
+ method: "POST",
401
+ headers: {
402
+ "Content-Type": "application/x-www-form-urlencoded",
403
+ Accept: "application/json",
404
+ },
405
+ body: new URLSearchParams({
406
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
407
+ device_code: deviceCode,
408
+ client_id: config.clientId,
409
+ }),
410
+ });
411
+ let data;
412
+ try {
413
+ data = await response.json();
414
+ } catch {
415
+ const text = await response.text();
416
+ data = { error: "invalid_response", error_description: text };
417
+ }
418
+ const pending =
419
+ data?.error === "authorization_pending" || data?.error === "slow_down";
420
+ return { ok: response.ok || pending, data };
421
+ },
422
+ mapTokens: (tokens) => {
423
+ let email = null;
424
+ try {
425
+ const parts = String(tokens.access_token || "").split(".");
426
+ if (parts.length >= 2) {
427
+ const b64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
428
+ const padded = b64 + "=".repeat((4 - (b64.length % 4)) % 4);
429
+ const claims = JSON.parse(Buffer.from(padded, "base64").toString("utf8"));
430
+ email = claims.email || claims.sub || null;
431
+ }
432
+ } catch { /* ignore */ }
433
+ return {
434
+ accessToken: tokens.access_token,
435
+ refreshToken: tokens.refresh_token || null,
436
+ expiresIn: tokens.expires_in,
437
+ scope: tokens.scope,
438
+ email: email || undefined,
439
+ providerSpecificData: {
440
+ authMethod: "device_code",
441
+ email: email || null,
442
+ },
443
+ };
444
+ },
445
+ },
446
+
375
447
  "gemini-cli": {
376
448
  config: GEMINI_CONFIG,
377
449
  flowType: "authorization_code",
@@ -6,7 +6,7 @@ import { buildTtsProviderModels } from "../config/ttsModels.js";
6
6
 
7
7
  // oauth block is canonical for these fields; inject into transport so executors reading
8
8
  // this.config.{clientId,clientSecret,tokenUrl} keep working without duplicating in transport
9
- const OAUTH_INJECT_FIELDS = ["clientId", "clientSecret", "tokenUrl"];
9
+ const OAUTH_INJECT_FIELDS = ["clientId", "clientSecret", "tokenUrl", "refreshUrl"];
10
10
 
11
11
  // transport: re-apply shared default (format:"openai") + inject oauth-canonical fields
12
12
  function buildTransport(transport, oauth) {
@@ -14,8 +14,16 @@ export default {
14
14
  apiKeyUrl: "https://portal.nousresearch.com",
15
15
  },
16
16
  },
17
- category: "apikey",
18
- authModes: ["apikey"],
17
+ category: "oauth",
18
+ authModes: ["oauth"],
19
+ oauth: {
20
+ clientId: "hermes-cli",
21
+ deviceCodeUrl: "https://portal.nousresearch.com/api/oauth/device/code",
22
+ tokenUrl: "https://portal.nousresearch.com/api/oauth/token",
23
+ refreshUrl: "https://portal.nousresearch.com/api/oauth/token",
24
+ scope: "inference:invoke",
25
+ refreshLeadMs: 5 * 60 * 1000,
26
+ },
19
27
  transport: {
20
28
  baseUrl: "https://inference-api.nousresearch.com/v1/chat/completions",
21
29
  format: "openai",
@@ -16,7 +16,7 @@ export async function GET(req, res) {
16
16
  }
17
17
  export async function POST(req, res) {
18
18
  try {
19
- const body = await req.json().catch(() => ({}));
19
+ const body = req.body || {};
20
20
  const extras = Array.isArray(body.extras) ? body.extras : [];
21
21
  if (extras.length === 0) {
22
22
  return res.status(400).json({ error: "No extras requested" });
@@ -37,7 +37,7 @@ export async function POST(req, res) {
37
37
  }
38
38
  export async function DELETE(req, res) {
39
39
  try {
40
- const body = await req.json().catch(() => ({}));
40
+ const body = req.body || {};
41
41
  const extras = Array.isArray(body.extras) ? body.extras : [];
42
42
  if (extras.length === 0) {
43
43
  return res.status(400).json({ error: "No extras requested" });
@@ -147,7 +147,7 @@ export const APIKEY_PROVIDERS = {
147
147
  // nscale: { id: "nscale", alias: "nscale", name: "nScale", icon: "token", color: "#0891B2", textIcon: "NS", website: "https://nscale.com", notice: { text: "$5 free credits on signup.", apiKeyUrl: "https://console.nscale.com/api-keys" }, serviceKinds: ["llm"] },
148
148
  // baseten: { id: "baseten", alias: "baseten", name: "Baseten", icon: "deployed_code", color: "#111827", textIcon: "BT", website: "https://baseten.co", notice: { text: "$30 free trial credits for GPU inference.", apiKeyUrl: "https://app.baseten.co/settings/api_keys" }, serviceKinds: ["llm"] },
149
149
  // publicai: { id: "publicai", alias: "publicai", name: "PublicAI", icon: "public", color: "#059669", textIcon: "PA", website: "https://publicai.co", notice: { text: "Free community inference tier.", apiKeyUrl: "https://publicai.co" }, serviceKinds: ["llm"] },
150
- "nous-research": { id: "nous-research", alias: "nous", name: "Nous Research", icon: "hub", color: "#2563EB", textIcon: "NO", website: "https://portal.nousresearch.com", notice: { text: "Free tier: 50 RPM, 500K TPM — no credit card.", apiKeyUrl: "https://portal.nousresearch.com" }, serviceKinds: ["llm"] },
150
+ "nous-research": { id: "nous-research", alias: "nous", name: "Nous Research", icon: "hub", color: "#2563EB", textIcon: "NO", website: "https://portal.nousresearch.com", notice: { text: "Free tier: 50 RPM, 500K TPM — no credit card. Sign in with your Nous Portal account (device login, no API key needed).", signupUrl: "https://portal.nousresearch.com" }, serviceKinds: ["llm"], authModes: ["oauth"], hasOAuth: true },
151
151
  // glhf: { id: "glhf", alias: "glhf", name: "GLHF Chat", icon: "hub", color: "#10B981", textIcon: "GH", website: "https://glhf.chat", notice: { text: "Free tier for open-source model inference.", apiKeyUrl: "https://glhf.chat/users/settings/api" }, passthroughModels: true, serviceKinds: ["llm"] },
152
152
  "ollama-local": { id: "ollama-local", alias: "ollama-local", name: "Ollama Local", icon: "cloud", color: "#ffffffff", textIcon: "OL", website: "https://ollama.com" },
153
153
  "vertex-partner": { id: "vertex-partner", alias: "vxp", name: "Vertex Partner", icon: "cloud", color: "#34A853", textIcon: "VP", website: "https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-partner-models", notice: { apiKeyUrl: "https://console.cloud.google.com/iam-admin/serviceaccounts" } },
@@ -6,7 +6,7 @@ import { buildTtsProviderModels } from "../config/ttsModels.js";
6
6
 
7
7
  // oauth block is canonical for these fields; inject into transport so executors reading
8
8
  // this.config.{clientId,clientSecret,tokenUrl} keep working without duplicating in transport
9
- const OAUTH_INJECT_FIELDS = ["clientId", "clientSecret", "tokenUrl"];
9
+ const OAUTH_INJECT_FIELDS = ["clientId", "clientSecret", "tokenUrl", "refreshUrl"];
10
10
 
11
11
  // transport: re-apply shared default (format:"openai") + inject oauth-canonical fields
12
12
  function buildTransport(transport, oauth) {
@@ -14,8 +14,16 @@ export default {
14
14
  apiKeyUrl: "https://portal.nousresearch.com",
15
15
  },
16
16
  },
17
- category: "apikey",
18
- authModes: ["apikey"],
17
+ category: "oauth",
18
+ authModes: ["oauth"],
19
+ oauth: {
20
+ clientId: "hermes-cli",
21
+ deviceCodeUrl: "https://portal.nousresearch.com/api/oauth/device/code",
22
+ tokenUrl: "https://portal.nousresearch.com/api/oauth/token",
23
+ refreshUrl: "https://portal.nousresearch.com/api/oauth/token",
24
+ scope: "inference:invoke",
25
+ refreshLeadMs: 5 * 60 * 1000,
26
+ },
19
27
  transport: {
20
28
  baseUrl: "https://inference-api.nousresearch.com/v1/chat/completions",
21
29
  format: "openai",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fudrouter/fsrouter",
3
- "version": "0.6.77",
3
+ "version": "0.6.78",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "9Router v2 — Express Backend (API, SSE, DB, Auth, MITM)",