noumen 0.6.0 → 0.8.0

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 (137) hide show
  1. package/README.md +237 -93
  2. package/dist/a2a/index.d.ts +5 -7
  3. package/dist/a2a/index.js +3 -4
  4. package/dist/a2a/index.js.map +1 -1
  5. package/dist/acp/index.d.ts +5 -7
  6. package/dist/acp/index.js +0 -1
  7. package/dist/acp/index.js.map +1 -1
  8. package/dist/{agent-DWE4_P5X.d.ts → agent-D0gl-qYi.d.ts} +89 -34
  9. package/dist/{chunk-6MMYCGJQ.js → chunk-5HY4IYNT.js} +1529 -2321
  10. package/dist/chunk-5HY4IYNT.js.map +1 -0
  11. package/dist/chunk-BC5BLWBC.js +21 -0
  12. package/dist/chunk-BC5BLWBC.js.map +1 -0
  13. package/dist/{chunk-XZN4QZLK.js → chunk-CX4BL6PC.js} +25 -15
  14. package/dist/chunk-CX4BL6PC.js.map +1 -0
  15. package/dist/{chunk-5GEX6ZSB.js → chunk-HQISH4D7.js} +60 -1
  16. package/dist/chunk-HQISH4D7.js.map +1 -0
  17. package/dist/{chunk-Y45R3PQL.js → chunk-NUCJXOUV.js} +32 -18
  18. package/dist/{chunk-Y45R3PQL.js.map → chunk-NUCJXOUV.js.map} +1 -1
  19. package/dist/chunk-OPFFLQZL.js +40 -0
  20. package/dist/chunk-OPFFLQZL.js.map +1 -0
  21. package/dist/chunk-PDEAJ272.js +660 -0
  22. package/dist/chunk-PDEAJ272.js.map +1 -0
  23. package/dist/chunk-PKHLGGEC.js +115 -0
  24. package/dist/chunk-PKHLGGEC.js.map +1 -0
  25. package/dist/chunk-XQTNXRE7.js +176 -0
  26. package/dist/chunk-XQTNXRE7.js.map +1 -0
  27. package/dist/chunk-XZPAA5TO.js +817 -0
  28. package/dist/chunk-XZPAA5TO.js.map +1 -0
  29. package/dist/cli/index.js +77 -42
  30. package/dist/cli/index.js.map +1 -1
  31. package/dist/client/index.d.ts +1 -2
  32. package/dist/client/index.js +0 -2
  33. package/dist/client/index.js.map +1 -1
  34. package/dist/client-JJFLE6RT.js +9 -0
  35. package/dist/{computer-BPdxSo6X.d.ts → computer-DzMR92tK.d.ts} +1 -1
  36. package/dist/docker.d.ts +2 -2
  37. package/dist/docker.js +0 -1
  38. package/dist/docker.js.map +1 -1
  39. package/dist/e2b.d.ts +2 -2
  40. package/dist/e2b.js +0 -1
  41. package/dist/e2b.js.map +1 -1
  42. package/dist/freestyle.d.ts +2 -2
  43. package/dist/freestyle.js +0 -1
  44. package/dist/freestyle.js.map +1 -1
  45. package/dist/{headless-FFU2DESQ.js → headless-25DU4MJQ.js} +1 -3
  46. package/dist/{headless-FFU2DESQ.js.map → headless-25DU4MJQ.js.map} +1 -1
  47. package/dist/{history-snip-64GYP4ZL.js → history-snip-HAWNAYKY.js} +1 -2
  48. package/dist/index.d.ts +351 -72
  49. package/dist/index.js +54 -55
  50. package/dist/jsonrpc/index.js +0 -1
  51. package/dist/local.d.ts +168 -0
  52. package/dist/local.js +40 -0
  53. package/dist/local.js.map +1 -0
  54. package/dist/lsp/index.d.ts +4 -5
  55. package/dist/lsp/index.js +0 -1
  56. package/dist/{lsp-PS3BWIHC.js → lsp-3APWNKB2.js} +1 -2
  57. package/dist/{manager-DLXK63XC.js → manager-Z5EQ7YYV.js} +1 -2
  58. package/dist/mcp/index.d.ts +16 -8
  59. package/dist/mcp/index.js +5 -6
  60. package/dist/mcp/index.js.map +1 -1
  61. package/dist/{mcp-auth-AEI2R4ZC.js → mcp-auth-NOIQPF7W.js} +1 -2
  62. package/dist/{provider-factory-TUHU3DIG.js → provider-factory-KNBSHXJ6.js} +3 -3
  63. package/dist/{render-GRN4ZSSW.js → render-4VEODRK7.js} +1 -2
  64. package/dist/{resolve-6KUZNEYW.js → resolve-AGQZFMKD.js} +3 -3
  65. package/dist/sandbox-DAqQo0Tj.d.ts +49 -0
  66. package/dist/sandbox-index-ODNREIFA.js +32 -0
  67. package/dist/sandbox-index-ODNREIFA.js.map +1 -0
  68. package/dist/server/index.d.ts +18 -7
  69. package/dist/server/index.js +9 -5
  70. package/dist/server/index.js.map +1 -1
  71. package/dist/{server-BzNGKTP6.d.ts → server-DFXdlqyX.d.ts} +1 -1
  72. package/dist/{spinner-OJNR6NFO.js → spinner-72JEISPK.js} +1 -2
  73. package/dist/sprites.d.ts +2 -2
  74. package/dist/sprites.js +0 -1
  75. package/dist/sprites.js.map +1 -1
  76. package/dist/ssh.d.ts +2 -2
  77. package/dist/ssh.js +0 -1
  78. package/dist/ssh.js.map +1 -1
  79. package/dist/{types-DhXwOQwD.d.ts → types-BX4ALqoN.d.ts} +76 -4
  80. package/dist/{types-kiGBF35b.d.ts → types-DLZNyF5t.d.ts} +125 -1
  81. package/dist/unsandboxed.d.ts +59 -0
  82. package/dist/unsandboxed.js +32 -0
  83. package/dist/unsandboxed.js.map +1 -0
  84. package/dist/{uuid-RVN2T26F.js → uuid-CVTNAPEB.js} +1 -2
  85. package/dist/{zod-7YXKWYMC.js → zod-VKURGPRT.js} +1 -2
  86. package/package.json +35 -50
  87. package/dist/cache-BlBwXXPS.d.ts +0 -38
  88. package/dist/chunk-5GEX6ZSB.js.map +0 -1
  89. package/dist/chunk-6MMYCGJQ.js.map +0 -1
  90. package/dist/chunk-7IQCQI2G.js +0 -94
  91. package/dist/chunk-7IQCQI2G.js.map +0 -1
  92. package/dist/chunk-CCM2AXZG.js +0 -16
  93. package/dist/chunk-CCM2AXZG.js.map +0 -1
  94. package/dist/chunk-DGUM43GV.js +0 -11
  95. package/dist/chunk-HEQQQGK5.js +0 -131
  96. package/dist/chunk-HEQQQGK5.js.map +0 -1
  97. package/dist/chunk-I3JTUFPK.js +0 -171
  98. package/dist/chunk-I3JTUFPK.js.map +0 -1
  99. package/dist/chunk-XZN4QZLK.js.map +0 -1
  100. package/dist/chunk-ZXSDKBYB.js +0 -474
  101. package/dist/chunk-ZXSDKBYB.js.map +0 -1
  102. package/dist/client-CRRO2376.js +0 -10
  103. package/dist/providers/anthropic.d.ts +0 -19
  104. package/dist/providers/anthropic.js +0 -36
  105. package/dist/providers/anthropic.js.map +0 -1
  106. package/dist/providers/bedrock.d.ts +0 -39
  107. package/dist/providers/bedrock.js +0 -56
  108. package/dist/providers/bedrock.js.map +0 -1
  109. package/dist/providers/gemini.d.ts +0 -17
  110. package/dist/providers/gemini.js +0 -262
  111. package/dist/providers/gemini.js.map +0 -1
  112. package/dist/providers/ollama.d.ts +0 -13
  113. package/dist/providers/ollama.js +0 -20
  114. package/dist/providers/ollama.js.map +0 -1
  115. package/dist/providers/openai.d.ts +0 -21
  116. package/dist/providers/openai.js +0 -9
  117. package/dist/providers/openrouter.d.ts +0 -16
  118. package/dist/providers/openrouter.js +0 -24
  119. package/dist/providers/openrouter.js.map +0 -1
  120. package/dist/providers/vertex.d.ts +0 -42
  121. package/dist/providers/vertex.js +0 -68
  122. package/dist/providers/vertex.js.map +0 -1
  123. package/dist/sandbox-9qeMTNrD.d.ts +0 -126
  124. package/dist/types-CD0rUKKT.d.ts +0 -109
  125. package/dist/uuid-RVN2T26F.js.map +0 -1
  126. package/dist/zod-7YXKWYMC.js.map +0 -1
  127. /package/dist/{chunk-DGUM43GV.js.map → client-JJFLE6RT.js.map} +0 -0
  128. /package/dist/{client-CRRO2376.js.map → history-snip-HAWNAYKY.js.map} +0 -0
  129. /package/dist/{history-snip-64GYP4ZL.js.map → lsp-3APWNKB2.js.map} +0 -0
  130. /package/dist/{lsp-PS3BWIHC.js.map → manager-Z5EQ7YYV.js.map} +0 -0
  131. /package/dist/{manager-DLXK63XC.js.map → mcp-auth-NOIQPF7W.js.map} +0 -0
  132. /package/dist/{mcp-auth-AEI2R4ZC.js.map → provider-factory-KNBSHXJ6.js.map} +0 -0
  133. /package/dist/{provider-factory-TUHU3DIG.js.map → render-4VEODRK7.js.map} +0 -0
  134. /package/dist/{providers/openai.js.map → resolve-AGQZFMKD.js.map} +0 -0
  135. /package/dist/{render-GRN4ZSSW.js.map → spinner-72JEISPK.js.map} +0 -0
  136. /package/dist/{resolve-6KUZNEYW.js.map → uuid-CVTNAPEB.js.map} +0 -0
  137. /package/dist/{spinner-OJNR6NFO.js.map → zod-VKURGPRT.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config/dot-dirs.ts","../src/utils/image-resizer.ts"],"sourcesContent":["import type { VirtualFs } from \"../virtual/fs.js\";\n\n/**\n * Configuration for which dot-directory names the agent recognizes when\n * reading and writing auxiliary state (context files, skills, sessions,\n * tasks, checkpoints, CLI config, MCP auth tokens, etc).\n *\n * The list is ordered by preference:\n * - `names[0]` is the canonical write target.\n * - For single-file reads (e.g. `config.json`), callers use first-hit-wins\n * across candidates in order.\n * - For additive reads (e.g. rules, skills), every candidate is loaded and\n * later entries in the list have higher precedence when resolving\n * collisions (mirrors the context loader's layer ordering).\n *\n * The same list applies to both project scope (cwd ancestors) and user\n * scope (home directory). There is intentionally no per-scope split — if\n * a caller needs that, they can route through different resolvers.\n */\nexport interface DotDirConfig {\n names: string[];\n}\n\n/**\n * Default dot-directory configuration. Recognizes `.noumen` (canonical)\n * and `.claude` (compatibility), preferring `.noumen` for writes.\n */\nexport const DEFAULT_DOT_DIRS: DotDirConfig = {\n names: [\".noumen\", \".claude\"],\n};\n\n/**\n * Pure path resolver over a `DotDirConfig`. Does no I/O — callers pair it\n * with `readFirstDotDir` / `readAllDotDirs` (VirtualFs helpers below) or\n * their own filesystem code.\n */\nexport interface DotDirResolver {\n /** The underlying configuration (exposed for permissions and logging). */\n config: DotDirConfig;\n\n /**\n * Absolute paths for each candidate dot-dir under `base`, in preference\n * order (most preferred first). `base` is appended with a `/` separator\n * unless already present.\n */\n candidates(base: string): string[];\n\n /** The canonical write target (`candidates(base)[0]`). */\n writePath(base: string): string;\n\n /**\n * Each candidate joined with `rel`, in preference order. Use this for\n * first-hit-wins reads (e.g. `config.json`).\n */\n joinRead(base: string, rel: string): string[];\n\n /** Write path joined with `rel`. */\n joinWrite(base: string, rel: string): string;\n}\n\nfunction joinPath(base: string, rel: string): string {\n if (!rel) return base;\n // Strip trailing slashes on base (keep root \"/\" as empty so \"/\" + \"foo\" → \"/foo\").\n const trimmedBase = base.replace(/\\/+$/, \"\");\n const trimmedRel = rel.replace(/^\\/+/, \"\");\n return `${trimmedBase}/${trimmedRel}`;\n}\n\nexport function createDotDirResolver(config: DotDirConfig = DEFAULT_DOT_DIRS): DotDirResolver {\n if (!config.names || config.names.length === 0) {\n throw new Error(\"DotDirConfig.names must be a non-empty array\");\n }\n\n const names = config.names;\n\n return {\n config: { names: [...names] },\n candidates(base: string): string[] {\n return names.map((n) => joinPath(base, n));\n },\n writePath(base: string): string {\n return joinPath(base, names[0]);\n },\n joinRead(base: string, rel: string): string[] {\n return names.map((n) => joinPath(joinPath(base, n), rel));\n },\n joinWrite(base: string, rel: string): string {\n return joinPath(joinPath(base, names[0]), rel);\n },\n };\n}\n\n/**\n * Read the first file that exists across the resolver's candidate paths\n * under `base/<name>/<rel>`. Returns `null` if none match.\n */\nexport async function readFirstDotDir(\n fs: VirtualFs,\n resolver: DotDirResolver,\n base: string,\n rel: string,\n): Promise<{ path: string; content: string } | null> {\n for (const candidate of resolver.joinRead(base, rel)) {\n try {\n const content = await fs.readFile(candidate);\n return { path: candidate, content };\n } catch {\n // keep walking\n }\n }\n return null;\n}\n\n/**\n * Read every candidate file that exists under `base/<name>/<rel>`. Results\n * are ordered from lowest to highest precedence (matching the context\n * loader's layer-stacking convention where later entries win).\n */\nexport async function readAllDotDirs(\n fs: VirtualFs,\n resolver: DotDirResolver,\n base: string,\n rel: string,\n): Promise<Array<{ path: string; content: string }>> {\n const candidates = resolver.joinRead(base, rel);\n const results: Array<{ path: string; content: string }> = [];\n\n // Walk candidates in reverse so that names[0] (the preferred/write dir)\n // ends up last — i.e. highest precedence for stacking consumers.\n for (let i = candidates.length - 1; i >= 0; i--) {\n const candidate = candidates[i];\n try {\n const content = await fs.readFile(candidate);\n results.push({ path: candidate, content });\n } catch {\n // skip\n }\n }\n return results;\n}\n","/**\n * Image resize / compress pipeline.\n *\n * Ported from claude-code's imageResizer.ts. Uses `sharp` (optional peer\n * dependency) for dimension caps, iterative quality reduction, and API\n * base64 size guards. Gracefully degrades when sharp is not installed.\n */\n\n/** Maximum base64-encoded image size (API enforced by most providers). */\nexport const API_IMAGE_MAX_BASE64_SIZE = 5 * 1024 * 1024; // 5 MB\n\n/** Target raw size before base64 encoding (base64 inflates by ~4/3). */\nexport const IMAGE_TARGET_RAW_SIZE = Math.floor(\n (API_IMAGE_MAX_BASE64_SIZE * 3) / 4,\n); // ~3.75 MB\n\nexport const IMAGE_MAX_WIDTH = 8000;\nexport const IMAGE_MAX_HEIGHT = 8000;\n\nexport interface ImageDimensions {\n width: number;\n height: number;\n}\n\nexport interface ResizedImage {\n buffer: Buffer;\n mediaType: string;\n dimensions?: ImageDimensions;\n}\n\nexport interface CompressedImageResult {\n base64: string;\n mediaType: string;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet _sharp: any | null | undefined;\n\nasync function getSharp(): Promise<any | null> {\n if (_sharp !== undefined) return _sharp;\n try {\n // Dynamic import with variable to prevent TypeScript from resolving at compile time\n const moduleName = \"sharp\";\n const mod = await import(/* @vite-ignore */ moduleName);\n _sharp = mod.default ?? mod;\n return _sharp;\n } catch {\n _sharp = null;\n return null;\n }\n}\n\n/**\n * Resize and downsample an image buffer if it exceeds dimension or size\n * limits. Returns the (possibly unchanged) buffer with mediaType info.\n */\nexport async function maybeResizeAndDownsampleImageBuffer(\n imageBuffer: Buffer,\n originalSize: number,\n ext: string,\n): Promise<ResizedImage> {\n const sharp = await getSharp();\n if (!sharp) {\n if (imageBuffer.length > IMAGE_TARGET_RAW_SIZE) {\n console.warn(\n `[noumen] Image is ${(imageBuffer.length / 1024 / 1024).toFixed(1)}MB ` +\n `but sharp is not installed — cannot resize. Install sharp for image optimization.`,\n );\n }\n return {\n buffer: imageBuffer,\n mediaType: extToMediaType(ext),\n };\n }\n\n let img = sharp(imageBuffer);\n const meta = await img.metadata();\n const width = meta.width ?? 0;\n const height = meta.height ?? 0;\n\n let needsResize =\n width > IMAGE_MAX_WIDTH ||\n height > IMAGE_MAX_HEIGHT ||\n imageBuffer.length > IMAGE_TARGET_RAW_SIZE;\n\n if (!needsResize) {\n return {\n buffer: imageBuffer,\n mediaType: extToMediaType(ext),\n dimensions: { width, height },\n };\n }\n\n // Dimension cap\n if (width > IMAGE_MAX_WIDTH || height > IMAGE_MAX_HEIGHT) {\n const scale = Math.min(IMAGE_MAX_WIDTH / width, IMAGE_MAX_HEIGHT / height);\n const newWidth = Math.round(width * scale);\n const newHeight = Math.round(height * scale);\n img = img.resize(newWidth, newHeight, { fit: \"inside\", withoutEnlargement: true });\n }\n\n // Try JPEG at decreasing quality levels\n const qualities = [85, 60, 40];\n for (const q of qualities) {\n const buf = await img.jpeg({ quality: q, mozjpeg: true }).toBuffer();\n if (buf.length <= IMAGE_TARGET_RAW_SIZE) {\n const jpgMeta = await sharp(buf).metadata();\n return {\n buffer: buf,\n mediaType: \"jpeg\",\n dimensions: {\n width: jpgMeta.width ?? 0,\n height: jpgMeta.height ?? 0,\n },\n };\n }\n }\n\n // Try PNG palette mode as last resort\n const pngBuf = await img.png({ palette: true, quality: 40 }).toBuffer();\n if (pngBuf.length <= IMAGE_TARGET_RAW_SIZE) {\n const pngMeta = await sharp(pngBuf).metadata();\n return {\n buffer: pngBuf,\n mediaType: \"png\",\n dimensions: {\n width: pngMeta.width ?? 0,\n height: pngMeta.height ?? 0,\n },\n };\n }\n\n // Return best-effort JPEG Q40 even if over budget\n const fallback = await img.jpeg({ quality: 40, mozjpeg: true }).toBuffer();\n const fallbackMeta = await sharp(fallback).metadata();\n return {\n buffer: fallback,\n mediaType: \"jpeg\",\n dimensions: {\n width: fallbackMeta.width ?? 0,\n height: fallbackMeta.height ?? 0,\n },\n };\n}\n\n/**\n * Decode base64 image block, resize, re-encode.\n */\nexport async function maybeResizeAndDownsampleImageBlock(imageBlock: {\n data: string;\n media_type: string;\n}): Promise<{\n data: string;\n media_type: string;\n dimensions?: ImageDimensions;\n}> {\n const imageBuffer = Buffer.from(imageBlock.data, \"base64\");\n const ext = imageBlock.media_type.split(\"/\")[1] || \"png\";\n\n const resized = await maybeResizeAndDownsampleImageBuffer(\n imageBuffer,\n imageBuffer.length,\n ext,\n );\n\n return {\n data: resized.buffer.toString(\"base64\"),\n media_type: `image/${resized.mediaType}`,\n dimensions: resized.dimensions,\n };\n}\n\n/**\n * Compress an image to fit within a token budget.\n * Token formula: tokens ≈ base64_chars × 0.125\n */\nexport async function compressImageBufferWithTokenLimit(\n imageBuffer: Buffer,\n maxTokens: number,\n originalMediaType?: string,\n): Promise<CompressedImageResult> {\n const maxBase64Chars = Math.floor(maxTokens / 0.125);\n const maxBytes = Math.floor(maxBase64Chars * 0.75);\n\n const sharp = await getSharp();\n if (!sharp) {\n const base64 = imageBuffer.toString(\"base64\");\n return {\n base64,\n mediaType: originalMediaType?.split(\"/\")[1] || \"png\",\n };\n }\n\n const qualities = [85, 60, 40, 20];\n for (const q of qualities) {\n const buf = await sharp(imageBuffer)\n .jpeg({ quality: q, mozjpeg: true })\n .toBuffer();\n if (buf.length <= maxBytes) {\n return { base64: buf.toString(\"base64\"), mediaType: \"jpeg\" };\n }\n }\n\n // Progressive dimension reduction\n const meta = await sharp(imageBuffer).metadata();\n let w = meta.width ?? 800;\n let h = meta.height ?? 600;\n\n for (let scale = 0.75; scale >= 0.25; scale -= 0.25) {\n const nw = Math.round(w * scale);\n const nh = Math.round(h * scale);\n const buf = await sharp(imageBuffer)\n .resize(nw, nh, { fit: \"inside\" })\n .jpeg({ quality: 40, mozjpeg: true })\n .toBuffer();\n if (buf.length <= maxBytes) {\n return { base64: buf.toString(\"base64\"), mediaType: \"jpeg\" };\n }\n }\n\n // Best effort\n const buf = await sharp(imageBuffer)\n .resize(Math.round(w * 0.25), Math.round(h * 0.25), { fit: \"inside\" })\n .jpeg({ quality: 20, mozjpeg: true })\n .toBuffer();\n return { base64: buf.toString(\"base64\"), mediaType: \"jpeg\" };\n}\n\nfunction extToMediaType(ext: string): string {\n const lower = ext.toLowerCase().replace(/^\\./, \"\");\n switch (lower) {\n case \"jpg\":\n case \"jpeg\":\n return \"jpeg\";\n case \"png\":\n return \"png\";\n case \"gif\":\n return \"gif\";\n case \"webp\":\n return \"webp\";\n case \"svg\":\n return \"svg+xml\";\n default:\n return \"png\";\n }\n}\n\nexport const IMAGE_EXTENSIONS = new Set([\n \".png\",\n \".jpg\",\n \".jpeg\",\n \".gif\",\n \".webp\",\n \".svg\",\n]);\n\n/**\n * Create dimension metadata text for the model (helps with coordinate reasoning).\n */\nexport function createImageMetadataText(dims: ImageDimensions): string {\n return `Image dimensions: ${dims.width}×${dims.height}px`;\n}\n"],"mappings":"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","names":["buf"]}
@@ -1,9 +1,11 @@
1
- import {
2
- maybeResizeAndDownsampleImageBuffer
3
- } from "./chunk-5GEX6ZSB.js";
4
1
  import {
5
2
  buildMcpToolName
6
3
  } from "./chunk-4SQA2UCV.js";
4
+ import {
5
+ DEFAULT_DOT_DIRS,
6
+ createDotDirResolver,
7
+ maybeResizeAndDownsampleImageBuffer
8
+ } from "./chunk-HQISH4D7.js";
7
9
 
8
10
  // src/mcp/client.ts
9
11
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
@@ -287,13 +289,22 @@ var InMemoryTokenStorage = class {
287
289
  }
288
290
  };
289
291
  var FileTokenStorage = class {
290
- filePath;
291
- constructor(filePath) {
292
- this.filePath = filePath ?? path.join(
293
- process.env.HOME ?? process.env.USERPROFILE ?? ".",
294
- ".noumen",
295
- "mcp-oauth-tokens.json"
296
- );
292
+ /** Explicit path override, if provided. */
293
+ explicitPath;
294
+ resolver;
295
+ homeBase;
296
+ constructor(filePath, resolver) {
297
+ this.explicitPath = filePath;
298
+ this.resolver = resolver ?? createDotDirResolver(DEFAULT_DOT_DIRS);
299
+ this.homeBase = process.env.HOME ?? process.env.USERPROFILE ?? ".";
300
+ }
301
+ get writePath() {
302
+ if (this.explicitPath) return this.explicitPath;
303
+ return this.resolver.joinWrite(this.homeBase, "mcp-oauth-tokens.json");
304
+ }
305
+ readCandidatePaths() {
306
+ if (this.explicitPath) return [this.explicitPath];
307
+ return this.resolver.joinRead(this.homeBase, "mcp-oauth-tokens.json");
297
308
  }
298
309
  async load(serverKey) {
299
310
  const all = await this.readAll();
@@ -310,16 +321,19 @@ var FileTokenStorage = class {
310
321
  await this.writeAll(all);
311
322
  }
312
323
  async readAll() {
313
- try {
314
- const raw = await fs.readFile(this.filePath, "utf-8");
315
- return JSON.parse(raw);
316
- } catch {
317
- return {};
324
+ for (const candidate of this.readCandidatePaths()) {
325
+ try {
326
+ const raw = await fs.readFile(candidate, "utf-8");
327
+ return JSON.parse(raw);
328
+ } catch {
329
+ }
318
330
  }
331
+ return {};
319
332
  }
320
333
  async writeAll(data) {
321
- await fs.mkdir(path.dirname(this.filePath), { recursive: true });
322
- await fs.writeFile(this.filePath, JSON.stringify(data, null, 2), "utf-8");
334
+ const target = this.writePath;
335
+ await fs.mkdir(path.dirname(target), { recursive: true });
336
+ await fs.writeFile(target, JSON.stringify(data, null, 2), "utf-8");
323
337
  }
324
338
  };
325
339
 
@@ -681,4 +695,4 @@ export {
681
695
  FileTokenStorage,
682
696
  McpClientManager
683
697
  };
684
- //# sourceMappingURL=chunk-Y45R3PQL.js.map
698
+ //# sourceMappingURL=chunk-NUCJXOUV.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/mcp/client.ts","../src/mcp/auth/provider.ts","../src/mcp/auth/callback-server.ts","../src/mcp/auth/storage.ts"],"sourcesContent":["import { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StdioClientTransport } from \"@modelcontextprotocol/sdk/client/stdio.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { SSEClientTransport } from \"@modelcontextprotocol/sdk/client/sse.js\";\nimport { WebSocketClientTransport } from \"@modelcontextprotocol/sdk/client/websocket.js\";\nimport { UnauthorizedError } from \"@modelcontextprotocol/sdk/client/auth.js\";\nimport type { OAuthClientProvider } from \"@modelcontextprotocol/sdk/client/auth.js\";\nimport type { Tool as McpSdkTool } from \"@modelcontextprotocol/sdk/types.js\";\nimport type { Tool, ToolResult } from \"../tools/types.js\";\nimport type { ContentPart } from \"../session/types.js\";\nimport { maybeResizeAndDownsampleImageBuffer } from \"../utils/image-resizer.js\";\nimport type {\n McpServerConfig,\n McpHttpServerConfig,\n McpSseServerConfig,\n McpConnection,\n McpToolInfo,\n} from \"./types.js\";\nimport type { TokenStorage, McpOAuthConfig } from \"./auth/types.js\";\nimport { NoumenOAuthProvider } from \"./auth/provider.js\";\nimport { findAvailablePort } from \"./auth/callback-server.js\";\nimport { InMemoryTokenStorage } from \"./auth/storage.js\";\nimport { buildMcpToolName } from \"./normalization.js\";\n\nconst DEFAULT_TOOL_TIMEOUT_MS = 60_000;\nconst MAX_TEXT_RESULT_BYTES = 1_024 * 1_024; // 1 MB\n\nexport interface McpClientManagerOptions {\n /**\n * Default token storage used for servers that declare `oauth` config\n * but no custom `authProvider`. Falls back to InMemoryTokenStorage.\n */\n tokenStorage?: TokenStorage;\n /**\n * Called when a server requires interactive OAuth and the user must\n * visit an authorization URL. Passed through to NoumenOAuthProvider.\n */\n onAuthorizationUrl?: (url: string) => void | Promise<void>;\n}\n\nexport class McpClientManager {\n private connections: Map<string, McpConnection> = new Map();\n private serverConfigs: Record<string, McpServerConfig>;\n private tokenStorage: TokenStorage;\n private onAuthorizationUrl?: (url: string) => void | Promise<void>;\n\n constructor(\n mcpServers: Record<string, McpServerConfig>,\n options?: McpClientManagerOptions,\n ) {\n this.serverConfigs = mcpServers;\n this.tokenStorage = options?.tokenStorage ?? new InMemoryTokenStorage();\n this.onAuthorizationUrl = options?.onAuthorizationUrl;\n }\n\n async connect(): Promise<void> {\n const entries = Object.entries(this.serverConfigs);\n const results = await Promise.allSettled(\n entries.map(([name, config]) => this.connectToServer(name, config)),\n );\n\n for (let i = 0; i < results.length; i++) {\n const result = results[i];\n const name = entries[i][0];\n if (result.status === \"rejected\") {\n this.connections.set(name, {\n name,\n client: null,\n status: \"failed\",\n config: entries[i][1],\n cleanup: async () => {},\n });\n }\n }\n }\n\n private async connectToServer(\n name: string,\n config: McpServerConfig,\n ): Promise<void> {\n const client = new Client({ name: `noumen-${name}`, version: \"0.1.0\" });\n let transport: StdioClientTransport | StreamableHTTPClientTransport | SSEClientTransport | WebSocketClientTransport;\n let cleanup: () => Promise<void>;\n\n const configType = \"type\" in config ? config.type : \"stdio\";\n\n switch (configType) {\n case \"http\": {\n const httpConfig = config as McpHttpServerConfig;\n const url = new URL(httpConfig.url);\n const authProvider = await this.resolveAuthProvider(name, httpConfig);\n transport = new StreamableHTTPClientTransport(url, {\n authProvider: authProvider ?? undefined,\n requestInit: httpConfig.headers\n ? { headers: httpConfig.headers }\n : undefined,\n });\n cleanup = async () => { await transport.close(); };\n break;\n }\n\n case \"sse\": {\n const sseConfig = config as McpSseServerConfig;\n const url = new URL(sseConfig.url);\n const authProvider = await this.resolveAuthProvider(name, sseConfig);\n transport = new SSEClientTransport(url, {\n authProvider: authProvider ?? undefined,\n requestInit: sseConfig.headers\n ? { headers: sseConfig.headers }\n : undefined,\n });\n cleanup = async () => { await transport.close(); };\n break;\n }\n\n case \"websocket\": {\n const wsConfig = config as { url: string };\n const url = new URL(wsConfig.url);\n transport = new WebSocketClientTransport(url);\n cleanup = async () => { await transport.close(); };\n break;\n }\n\n default: {\n const stdioConfig = config as {\n command: string;\n args?: string[];\n env?: Record<string, string>;\n };\n transport = new StdioClientTransport({\n command: stdioConfig.command,\n args: stdioConfig.args,\n env: stdioConfig.env\n ? ({ ...process.env, ...stdioConfig.env } as Record<string, string>)\n : undefined,\n });\n cleanup = async () => { await transport.close(); };\n break;\n }\n }\n\n try {\n await client.connect(transport);\n } catch (err) {\n if (err instanceof UnauthorizedError) {\n this.connections.set(name, {\n name,\n client,\n status: \"needs-auth\",\n config,\n cleanup,\n });\n return;\n }\n throw err;\n }\n\n this.connections.set(name, {\n name,\n client,\n status: \"connected\",\n config,\n cleanup,\n });\n }\n\n /**\n * Resolve an OAuthClientProvider for an HTTP or SSE server config.\n * Returns null if the server doesn't require authentication.\n */\n private async resolveAuthProvider(\n serverName: string,\n config: McpHttpServerConfig | McpSseServerConfig,\n ): Promise<OAuthClientProvider | null> {\n if (config.authProvider) return config.authProvider;\n if (!config.oauth) return null;\n\n const oauth = config.oauth;\n const serverKey = `${serverName}|${config.url}`;\n const port = await findAvailablePort(oauth.callbackPort);\n\n return new NoumenOAuthProvider(serverKey, {\n storage: this.tokenStorage,\n clientId: oauth.clientId,\n clientSecret: oauth.clientSecret,\n callbackPort: port,\n onAuthorizationUrl: this.onAuthorizationUrl,\n clientMetadata: {\n redirect_uris: [`http://localhost:${port}/callback`],\n client_name: `noumen-${serverName}`,\n grant_types: [\"authorization_code\", \"refresh_token\"],\n response_types: [\"code\"],\n scope: oauth.scopes,\n },\n });\n }\n\n async getTools(): Promise<Tool[]> {\n const tools: Tool[] = [];\n\n for (const [serverName, conn] of this.connections) {\n if (conn.status !== \"connected\" || !conn.client) continue;\n\n try {\n const result = await conn.client.listTools();\n for (const mcpTool of result.tools) {\n tools.push(this.mapMcpTool(serverName, mcpTool));\n }\n } catch {\n // Server failed to list tools; skip it\n }\n }\n\n return tools;\n }\n\n private mapMcpTool(serverName: string, mcpTool: McpSdkTool): Tool {\n const qualifiedName = buildMcpToolName(serverName, mcpTool.name);\n\n const parameters = (mcpTool.inputSchema ?? {\n type: \"object\" as const,\n properties: {},\n }) as Tool[\"parameters\"];\n\n const mcpInfo: McpToolInfo = {\n serverName,\n toolName: mcpTool.name,\n };\n\n return {\n name: qualifiedName,\n description: mcpTool.description ?? \"\",\n parameters,\n mcpInfo,\n call: async (args: Record<string, unknown>): Promise<ToolResult> => {\n return this.callTool(serverName, mcpTool.name, args);\n },\n };\n }\n\n async callTool(\n serverName: string,\n toolName: string,\n args: Record<string, unknown>,\n options?: { timeoutMs?: number },\n ): Promise<ToolResult> {\n const conn = this.connections.get(serverName);\n if (!conn || conn.status !== \"connected\" || !conn.client) {\n return {\n content: `MCP server \"${serverName}\" is not connected`,\n isError: true,\n };\n }\n\n try {\n const timeoutMs = options?.timeoutMs ?? DEFAULT_TOOL_TIMEOUT_MS;\n const abortController = new AbortController();\n const timer = setTimeout(() => abortController.abort(), timeoutMs);\n\n let result: Awaited<ReturnType<Client[\"callTool\"]>>;\n try {\n result = await conn.client.callTool(\n { name: toolName, arguments: args },\n undefined,\n { signal: abortController.signal },\n );\n } finally {\n clearTimeout(timer);\n }\n\n const contentBlocks = result.content as Array<{\n type: string;\n text?: string;\n data?: string;\n mimeType?: string;\n blob?: string;\n }> | undefined;\n\n if (!contentBlocks) {\n return { content: JSON.stringify(result), isError: result.isError === true };\n }\n\n const parts: ContentPart[] = [];\n for (const block of contentBlocks) {\n if (block.type === \"text\") {\n parts.push({ type: \"text\", text: block.text ?? \"\" });\n } else if (block.type === \"image\" && block.data) {\n const imageBuffer = Buffer.from(block.data, \"base64\");\n const ext = block.mimeType?.split(\"/\")[1] || \"png\";\n try {\n const resized = await maybeResizeAndDownsampleImageBuffer(\n imageBuffer,\n imageBuffer.length,\n ext,\n );\n parts.push({\n type: \"image\",\n data: resized.buffer.toString(\"base64\"),\n media_type: `image/${resized.mediaType}`,\n });\n } catch {\n parts.push({\n type: \"image\",\n data: block.data,\n media_type: block.mimeType ?? \"image/png\",\n });\n }\n } else if (block.type === \"resource\" && block.blob) {\n const isImage = block.mimeType?.startsWith(\"image/\") ?? false;\n if (isImage) {\n const imageBuffer = Buffer.from(block.blob, \"base64\");\n const ext = block.mimeType?.split(\"/\")[1] || \"png\";\n try {\n const resized = await maybeResizeAndDownsampleImageBuffer(\n imageBuffer,\n imageBuffer.length,\n ext,\n );\n parts.push({\n type: \"image\",\n data: resized.buffer.toString(\"base64\"),\n media_type: `image/${resized.mediaType}`,\n });\n } catch {\n parts.push({\n type: \"image\",\n data: block.blob,\n media_type: block.mimeType ?? \"image/png\",\n });\n }\n } else {\n parts.push({ type: \"text\", text: JSON.stringify(block) });\n }\n } else {\n parts.push({ type: \"text\", text: JSON.stringify(block) });\n }\n }\n\n // If all parts are text, flatten to a single string for simpler downstream handling\n if (parts.every((p) => p.type === \"text\")) {\n let text = parts\n .map((p) => (p as { text: string }).text)\n .join(\"\\n\");\n if (Buffer.byteLength(text, \"utf-8\") > MAX_TEXT_RESULT_BYTES) {\n text = text.slice(0, MAX_TEXT_RESULT_BYTES) + \"\\n...[output truncated]\";\n }\n return { content: text, isError: result.isError === true };\n }\n\n return { content: parts, isError: result.isError === true };\n } catch (err) {\n const isAbort =\n err instanceof DOMException && err.name === \"AbortError\";\n const message = isAbort\n ? `MCP tool \"${toolName}\" on server \"${serverName}\" timed out`\n : err instanceof Error ? err.message : String(err);\n return { content: message, isError: true };\n }\n }\n\n getConnectionStatus(): Array<{\n name: string;\n status: string;\n toolCount?: number;\n }> {\n return Array.from(this.connections.values()).map((c) => ({\n name: c.name,\n status: c.status,\n }));\n }\n\n /**\n * Returns server names that are in `needs-auth` status and require\n * interactive OAuth before they can be used.\n */\n getServersNeedingAuth(): string[] {\n return Array.from(this.connections.entries())\n .filter(([, conn]) => conn.status === \"needs-auth\")\n .map(([name]) => name);\n }\n\n /**\n * Reconnect a server by closing its existing connection and\n * establishing a new one. Useful after completing OAuth.\n */\n async reconnect(serverName: string): Promise<void> {\n const conn = this.connections.get(serverName);\n if (conn) {\n await conn.cleanup().catch(() => {});\n this.connections.delete(serverName);\n }\n\n const config = this.serverConfigs[serverName];\n if (!config) return;\n\n try {\n await this.connectToServer(serverName, config);\n } catch {\n this.connections.set(serverName, {\n name: serverName,\n client: null,\n status: \"failed\",\n config,\n cleanup: async () => {},\n });\n }\n }\n\n /**\n * Trigger interactive OAuth for a `needs-auth` server, then reconnect.\n * Runs the full MCP SDK auth orchestrator with a local callback server.\n *\n * Returns the authorization URL if the flow requires user interaction,\n * or null if the server connected without browser auth (e.g. cached tokens).\n */\n async performAuth(\n serverName: string,\n options?: { signal?: AbortSignal },\n ): Promise<{ authUrl?: string }> {\n const config = this.serverConfigs[serverName];\n if (!config) {\n throw new Error(`Unknown MCP server: ${serverName}`);\n }\n\n const configType = \"type\" in config ? config.type : \"stdio\";\n if (configType !== \"http\" && configType !== \"sse\") {\n throw new Error(\n `OAuth is only supported for HTTP and SSE transports, got: ${configType}`,\n );\n }\n\n const httpConfig = config as McpHttpServerConfig | McpSseServerConfig;\n if (!httpConfig.oauth && !httpConfig.authProvider) {\n throw new Error(\n `Server \"${serverName}\" has no OAuth configuration`,\n );\n }\n\n let capturedAuthUrl: string | undefined;\n const originalCallback = this.onAuthorizationUrl;\n\n this.onAuthorizationUrl = async (url: string) => {\n capturedAuthUrl = url;\n if (originalCallback) await originalCallback(url);\n };\n\n try {\n await this.reconnect(serverName);\n } finally {\n this.onAuthorizationUrl = originalCallback;\n }\n\n return { authUrl: capturedAuthUrl };\n }\n\n async close(): Promise<void> {\n const cleanups = Array.from(this.connections.values()).map((c) =>\n c.cleanup().catch(() => {}),\n );\n await Promise.all(cleanups);\n this.connections.clear();\n }\n}\n","import { randomBytes } from \"node:crypto\";\nimport { exec } from \"node:child_process\";\nimport type {\n OAuthClientProvider,\n OAuthDiscoveryState,\n} from \"@modelcontextprotocol/sdk/client/auth.js\";\nimport type {\n OAuthTokens,\n OAuthClientMetadata,\n OAuthClientInformationMixed,\n} from \"@modelcontextprotocol/sdk/shared/auth.js\";\nimport type { TokenStorage, OAuthProviderOptions } from \"./types.js\";\n\n/**\n * OAuthClientProvider implementation backed by noumen's pluggable\n * TokenStorage. Handles all persistence (tokens, client info, PKCE\n * verifiers, discovery state) through the storage interface and\n * delegates browser/UI concerns to configurable callbacks.\n */\nexport class NoumenOAuthProvider implements OAuthClientProvider {\n private serverKey: string;\n private storage: TokenStorage;\n private _clientMetadata: OAuthClientMetadata;\n private _callbackPort: number;\n private _onAuthorizationUrl?: (url: string) => void | Promise<void>;\n private _signal?: AbortSignal;\n private _preRegisteredClientId?: string;\n private _preRegisteredClientSecret?: string;\n private _state: string | null = null;\n\n constructor(serverKey: string, options: OAuthProviderOptions) {\n this.serverKey = serverKey;\n this.storage = options.storage;\n this._clientMetadata = options.clientMetadata;\n this._callbackPort = options.callbackPort ?? 3118;\n this._onAuthorizationUrl = options.onAuthorizationUrl;\n this._signal = options.signal;\n this._preRegisteredClientId = options.clientId;\n this._preRegisteredClientSecret = options.clientSecret;\n }\n\n get redirectUrl(): string {\n return `http://localhost:${this._callbackPort}/callback`;\n }\n\n get clientMetadata(): OAuthClientMetadata {\n return this._clientMetadata;\n }\n\n async state(): Promise<string> {\n if (!this._state) {\n this._state = randomBytes(32).toString(\"base64url\");\n }\n return this._state;\n }\n\n async clientInformation(): Promise<OAuthClientInformationMixed | undefined> {\n if (this._preRegisteredClientId) {\n const info: OAuthClientInformationMixed = {\n client_id: this._preRegisteredClientId,\n };\n if (this._preRegisteredClientSecret) {\n (info as Record<string, unknown>).client_secret =\n this._preRegisteredClientSecret;\n }\n return info;\n }\n\n const data = await this.storage.load(this.serverKey);\n return data?.clientInformation;\n }\n\n async saveClientInformation(\n clientInformation: OAuthClientInformationMixed,\n ): Promise<void> {\n const data = (await this.storage.load(this.serverKey)) ?? {};\n data.clientInformation = clientInformation;\n await this.storage.save(this.serverKey, data);\n }\n\n async tokens(): Promise<OAuthTokens | undefined> {\n const data = await this.storage.load(this.serverKey);\n if (!data?.tokens) return undefined;\n\n const REFRESH_BUFFER_MS = 5 * 60 * 1000;\n if (\n data.expiresAt &&\n data.tokens.refresh_token &&\n Date.now() >= data.expiresAt - REFRESH_BUFFER_MS\n ) {\n return undefined;\n }\n return data.tokens;\n }\n\n async saveTokens(tokens: OAuthTokens): Promise<void> {\n const data = (await this.storage.load(this.serverKey)) ?? {};\n data.tokens = tokens;\n if (tokens.expires_in != null) {\n data.expiresAt = Date.now() + tokens.expires_in * 1000;\n }\n await this.storage.save(this.serverKey, data);\n }\n\n async redirectToAuthorization(authorizationUrl: URL): Promise<void> {\n const urlStr = authorizationUrl.toString();\n\n if (this._onAuthorizationUrl) {\n await this._onAuthorizationUrl(urlStr);\n return;\n }\n\n openBrowser(urlStr);\n }\n\n async saveCodeVerifier(codeVerifier: string): Promise<void> {\n const data = (await this.storage.load(this.serverKey)) ?? {};\n data.codeVerifier = codeVerifier;\n await this.storage.save(this.serverKey, data);\n }\n\n async codeVerifier(): Promise<string> {\n const data = await this.storage.load(this.serverKey);\n return data?.codeVerifier ?? \"\";\n }\n\n async invalidateCredentials(\n scope: \"all\" | \"client\" | \"tokens\" | \"verifier\" | \"discovery\",\n ): Promise<void> {\n if (scope === \"all\") {\n this._state = null;\n await this.storage.delete(this.serverKey);\n return;\n }\n\n const data = await this.storage.load(this.serverKey);\n if (!data) return;\n\n switch (scope) {\n case \"client\":\n delete data.clientInformation;\n break;\n case \"tokens\":\n delete data.tokens;\n delete data.expiresAt;\n break;\n case \"verifier\":\n delete data.codeVerifier;\n break;\n case \"discovery\":\n delete data.discoveryState;\n break;\n }\n await this.storage.save(this.serverKey, data);\n }\n\n async saveDiscoveryState(state: OAuthDiscoveryState): Promise<void> {\n const data = (await this.storage.load(this.serverKey)) ?? {};\n data.discoveryState = state;\n await this.storage.save(this.serverKey, data);\n }\n\n async discoveryState(): Promise<OAuthDiscoveryState | undefined> {\n const data = await this.storage.load(this.serverKey);\n return data?.discoveryState;\n }\n}\n\nfunction openBrowser(url: string): void {\n const platform = process.platform;\n const cmd =\n platform === \"darwin\"\n ? \"open\"\n : platform === \"win32\"\n ? \"start\"\n : \"xdg-open\";\n exec(`${cmd} ${JSON.stringify(url)}`);\n}\n","import * as http from \"node:http\";\n\nconst DEFAULT_FALLBACK_PORT = 3118;\nconst EPHEMERAL_PORT_MIN = 49152;\nconst EPHEMERAL_PORT_MAX = 65535;\nconst MAX_PORT_ATTEMPTS = 50;\nconst CALLBACK_TIMEOUT_MS = 5 * 60 * 1000;\n\n/**\n * Find an available local port for the OAuth callback server.\n * Tries the preferred port first, then random ephemeral ports.\n */\nexport async function findAvailablePort(preferred?: number): Promise<number> {\n if (preferred != null) {\n const ok = await isPortAvailable(preferred);\n if (ok) return preferred;\n }\n\n for (let i = 0; i < MAX_PORT_ATTEMPTS; i++) {\n const port =\n EPHEMERAL_PORT_MIN +\n Math.floor(Math.random() * (EPHEMERAL_PORT_MAX - EPHEMERAL_PORT_MIN + 1));\n const ok = await isPortAvailable(port);\n if (ok) return port;\n }\n\n return DEFAULT_FALLBACK_PORT;\n}\n\nfunction isPortAvailable(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const server = http.createServer();\n server.once(\"error\", () => resolve(false));\n server.listen(port, \"127.0.0.1\", () => {\n server.close(() => resolve(true));\n });\n });\n}\n\nexport interface OAuthCallbackResult {\n code: string;\n state?: string;\n}\n\n/**\n * Lightweight HTTP server on localhost that receives the OAuth redirect\n * callback. Validates the state parameter when expected and resolves\n * with the authorization code.\n */\nexport class OAuthCallbackServer {\n private server: http.Server | null = null;\n private port = 0;\n\n /**\n * Start listening on a local port and return a promise that resolves\n * when the authorization callback is received.\n */\n async start(options?: {\n expectedState?: string;\n callbackPort?: number;\n signal?: AbortSignal;\n }): Promise<{\n port: number;\n redirectUri: string;\n waitForCallback: () => Promise<OAuthCallbackResult>;\n }> {\n const port = await findAvailablePort(options?.callbackPort);\n this.port = port;\n const redirectUri = `http://localhost:${port}/callback`;\n\n const callbackPromise = new Promise<OAuthCallbackResult>(\n (resolve, reject) => {\n const server = http.createServer((req, res) => {\n if (!req.url?.startsWith(\"/callback\")) {\n res.writeHead(404);\n res.end(\"Not found\");\n return;\n }\n\n const parsed = new URL(req.url, `http://localhost:${port}`);\n const code = parsed.searchParams.get(\"code\") ?? undefined;\n const state = parsed.searchParams.get(\"state\") ?? undefined;\n const error = parsed.searchParams.get(\"error\") ?? undefined;\n const errorDescription = parsed.searchParams.get(\"error_description\") ?? undefined;\n\n if (error) {\n res.writeHead(400, { \"Content-Type\": \"text/html\" });\n res.end(\n `<html><body><h1>Authorization Error</h1><p>${escapeHtml(error)}: ${escapeHtml(errorDescription ?? \"\")}</p></body></html>`,\n );\n cleanup();\n reject(new Error(`OAuth error: ${error} - ${errorDescription ?? \"\"}`));\n return;\n }\n\n if (!code) {\n res.writeHead(400, { \"Content-Type\": \"text/html\" });\n res.end(\n \"<html><body><h1>Error</h1><p>Missing authorization code.</p></body></html>\",\n );\n cleanup();\n reject(new Error(\"Missing authorization code in callback\"));\n return;\n }\n\n if (options?.expectedState && state !== options.expectedState) {\n res.writeHead(400, { \"Content-Type\": \"text/html\" });\n res.end(\n \"<html><body><h1>Error</h1><p>State parameter mismatch.</p></body></html>\",\n );\n cleanup();\n reject(new Error(\"OAuth state mismatch\"));\n return;\n }\n\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(\n \"<html><body><h1>Authorization Successful</h1><p>You can close this window.</p></body></html>\",\n );\n cleanup();\n resolve({ code, state });\n });\n\n this.server = server;\n\n const timeout = setTimeout(() => {\n cleanup();\n reject(new Error(\"OAuth callback timeout\"));\n }, CALLBACK_TIMEOUT_MS);\n\n const cleanup = () => {\n clearTimeout(timeout);\n this.close();\n };\n\n if (options?.signal) {\n if (options.signal.aborted) {\n cleanup();\n reject(new Error(\"OAuth callback aborted\"));\n return;\n }\n options.signal.addEventListener(\n \"abort\",\n () => {\n cleanup();\n reject(new Error(\"OAuth callback aborted\"));\n },\n { once: true },\n );\n }\n\n server.listen(port, \"127.0.0.1\");\n server.unref();\n },\n );\n\n return { port, redirectUri, waitForCallback: () => callbackPromise };\n }\n\n close(): void {\n if (this.server) {\n try {\n this.server.close();\n } catch {\n // already closed\n }\n this.server = null;\n }\n }\n}\n\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n}\n","import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport type { TokenStorage, OAuthTokenData } from \"./types.js\";\n\n/**\n * In-memory token storage. Suitable for tests, short-lived processes,\n * and situations where persistence across restarts is not needed.\n */\nexport class InMemoryTokenStorage implements TokenStorage {\n private store = new Map<string, OAuthTokenData>();\n\n async load(serverKey: string): Promise<OAuthTokenData | undefined> {\n return this.store.get(serverKey);\n }\n\n async save(serverKey: string, data: OAuthTokenData): Promise<void> {\n this.store.set(serverKey, data);\n }\n\n async delete(serverKey: string): Promise<void> {\n this.store.delete(serverKey);\n }\n}\n\n/**\n * File-backed token storage. Persists all server tokens to a single JSON\n * file so they survive process restarts. Each server key maps to its own\n * entry in the file.\n */\nexport class FileTokenStorage implements TokenStorage {\n private filePath: string;\n\n constructor(filePath?: string) {\n this.filePath =\n filePath ??\n path.join(\n process.env.HOME ?? process.env.USERPROFILE ?? \".\",\n \".noumen\",\n \"mcp-oauth-tokens.json\",\n );\n }\n\n async load(serverKey: string): Promise<OAuthTokenData | undefined> {\n const all = await this.readAll();\n return all[serverKey];\n }\n\n async save(serverKey: string, data: OAuthTokenData): Promise<void> {\n const all = await this.readAll();\n all[serverKey] = data;\n await this.writeAll(all);\n }\n\n async delete(serverKey: string): Promise<void> {\n const all = await this.readAll();\n delete all[serverKey];\n await this.writeAll(all);\n }\n\n private async readAll(): Promise<Record<string, OAuthTokenData>> {\n try {\n const raw = await fs.readFile(this.filePath, \"utf-8\");\n return JSON.parse(raw) as Record<string, OAuthTokenData>;\n } catch {\n return {};\n }\n }\n\n private async writeAll(data: Record<string, OAuthTokenData>): Promise<void> {\n await fs.mkdir(path.dirname(this.filePath), { recursive: true });\n await fs.writeFile(this.filePath, JSON.stringify(data, null, 2), \"utf-8\");\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/mcp/client.ts","../src/mcp/auth/provider.ts","../src/mcp/auth/callback-server.ts","../src/mcp/auth/storage.ts"],"sourcesContent":["import { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StdioClientTransport } from \"@modelcontextprotocol/sdk/client/stdio.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { SSEClientTransport } from \"@modelcontextprotocol/sdk/client/sse.js\";\nimport { WebSocketClientTransport } from \"@modelcontextprotocol/sdk/client/websocket.js\";\nimport { UnauthorizedError } from \"@modelcontextprotocol/sdk/client/auth.js\";\nimport type { OAuthClientProvider } from \"@modelcontextprotocol/sdk/client/auth.js\";\nimport type { Tool as McpSdkTool } from \"@modelcontextprotocol/sdk/types.js\";\nimport type { Tool, ToolResult } from \"../tools/types.js\";\nimport type { ContentPart } from \"../session/types.js\";\nimport { maybeResizeAndDownsampleImageBuffer } from \"../utils/image-resizer.js\";\nimport type {\n McpServerConfig,\n McpHttpServerConfig,\n McpSseServerConfig,\n McpConnection,\n McpToolInfo,\n} from \"./types.js\";\nimport type { TokenStorage, McpOAuthConfig } from \"./auth/types.js\";\nimport { NoumenOAuthProvider } from \"./auth/provider.js\";\nimport { findAvailablePort } from \"./auth/callback-server.js\";\nimport { InMemoryTokenStorage } from \"./auth/storage.js\";\nimport { buildMcpToolName } from \"./normalization.js\";\n\nconst DEFAULT_TOOL_TIMEOUT_MS = 60_000;\nconst MAX_TEXT_RESULT_BYTES = 1_024 * 1_024; // 1 MB\n\nexport interface McpClientManagerOptions {\n /**\n * Default token storage used for servers that declare `oauth` config\n * but no custom `authProvider`. Falls back to InMemoryTokenStorage.\n */\n tokenStorage?: TokenStorage;\n /**\n * Called when a server requires interactive OAuth and the user must\n * visit an authorization URL. Passed through to NoumenOAuthProvider.\n */\n onAuthorizationUrl?: (url: string) => void | Promise<void>;\n}\n\nexport class McpClientManager {\n private connections: Map<string, McpConnection> = new Map();\n private serverConfigs: Record<string, McpServerConfig>;\n private tokenStorage: TokenStorage;\n private onAuthorizationUrl?: (url: string) => void | Promise<void>;\n\n constructor(\n mcpServers: Record<string, McpServerConfig>,\n options?: McpClientManagerOptions,\n ) {\n this.serverConfigs = mcpServers;\n this.tokenStorage = options?.tokenStorage ?? new InMemoryTokenStorage();\n this.onAuthorizationUrl = options?.onAuthorizationUrl;\n }\n\n async connect(): Promise<void> {\n const entries = Object.entries(this.serverConfigs);\n const results = await Promise.allSettled(\n entries.map(([name, config]) => this.connectToServer(name, config)),\n );\n\n for (let i = 0; i < results.length; i++) {\n const result = results[i];\n const name = entries[i][0];\n if (result.status === \"rejected\") {\n this.connections.set(name, {\n name,\n client: null,\n status: \"failed\",\n config: entries[i][1],\n cleanup: async () => {},\n });\n }\n }\n }\n\n private async connectToServer(\n name: string,\n config: McpServerConfig,\n ): Promise<void> {\n const client = new Client({ name: `noumen-${name}`, version: \"0.1.0\" });\n let transport: StdioClientTransport | StreamableHTTPClientTransport | SSEClientTransport | WebSocketClientTransport;\n let cleanup: () => Promise<void>;\n\n const configType = \"type\" in config ? config.type : \"stdio\";\n\n switch (configType) {\n case \"http\": {\n const httpConfig = config as McpHttpServerConfig;\n const url = new URL(httpConfig.url);\n const authProvider = await this.resolveAuthProvider(name, httpConfig);\n transport = new StreamableHTTPClientTransport(url, {\n authProvider: authProvider ?? undefined,\n requestInit: httpConfig.headers\n ? { headers: httpConfig.headers }\n : undefined,\n });\n cleanup = async () => { await transport.close(); };\n break;\n }\n\n case \"sse\": {\n const sseConfig = config as McpSseServerConfig;\n const url = new URL(sseConfig.url);\n const authProvider = await this.resolveAuthProvider(name, sseConfig);\n transport = new SSEClientTransport(url, {\n authProvider: authProvider ?? undefined,\n requestInit: sseConfig.headers\n ? { headers: sseConfig.headers }\n : undefined,\n });\n cleanup = async () => { await transport.close(); };\n break;\n }\n\n case \"websocket\": {\n const wsConfig = config as { url: string };\n const url = new URL(wsConfig.url);\n transport = new WebSocketClientTransport(url);\n cleanup = async () => { await transport.close(); };\n break;\n }\n\n default: {\n const stdioConfig = config as {\n command: string;\n args?: string[];\n env?: Record<string, string>;\n };\n transport = new StdioClientTransport({\n command: stdioConfig.command,\n args: stdioConfig.args,\n env: stdioConfig.env\n ? ({ ...process.env, ...stdioConfig.env } as Record<string, string>)\n : undefined,\n });\n cleanup = async () => { await transport.close(); };\n break;\n }\n }\n\n try {\n await client.connect(transport);\n } catch (err) {\n if (err instanceof UnauthorizedError) {\n this.connections.set(name, {\n name,\n client,\n status: \"needs-auth\",\n config,\n cleanup,\n });\n return;\n }\n throw err;\n }\n\n this.connections.set(name, {\n name,\n client,\n status: \"connected\",\n config,\n cleanup,\n });\n }\n\n /**\n * Resolve an OAuthClientProvider for an HTTP or SSE server config.\n * Returns null if the server doesn't require authentication.\n */\n private async resolveAuthProvider(\n serverName: string,\n config: McpHttpServerConfig | McpSseServerConfig,\n ): Promise<OAuthClientProvider | null> {\n if (config.authProvider) return config.authProvider;\n if (!config.oauth) return null;\n\n const oauth = config.oauth;\n const serverKey = `${serverName}|${config.url}`;\n const port = await findAvailablePort(oauth.callbackPort);\n\n return new NoumenOAuthProvider(serverKey, {\n storage: this.tokenStorage,\n clientId: oauth.clientId,\n clientSecret: oauth.clientSecret,\n callbackPort: port,\n onAuthorizationUrl: this.onAuthorizationUrl,\n clientMetadata: {\n redirect_uris: [`http://localhost:${port}/callback`],\n client_name: `noumen-${serverName}`,\n grant_types: [\"authorization_code\", \"refresh_token\"],\n response_types: [\"code\"],\n scope: oauth.scopes,\n },\n });\n }\n\n async getTools(): Promise<Tool[]> {\n const tools: Tool[] = [];\n\n for (const [serverName, conn] of this.connections) {\n if (conn.status !== \"connected\" || !conn.client) continue;\n\n try {\n const result = await conn.client.listTools();\n for (const mcpTool of result.tools) {\n tools.push(this.mapMcpTool(serverName, mcpTool));\n }\n } catch {\n // Server failed to list tools; skip it\n }\n }\n\n return tools;\n }\n\n private mapMcpTool(serverName: string, mcpTool: McpSdkTool): Tool {\n const qualifiedName = buildMcpToolName(serverName, mcpTool.name);\n\n const parameters = (mcpTool.inputSchema ?? {\n type: \"object\" as const,\n properties: {},\n }) as Tool[\"parameters\"];\n\n const mcpInfo: McpToolInfo = {\n serverName,\n toolName: mcpTool.name,\n };\n\n return {\n name: qualifiedName,\n description: mcpTool.description ?? \"\",\n parameters,\n mcpInfo,\n call: async (args: Record<string, unknown>): Promise<ToolResult> => {\n return this.callTool(serverName, mcpTool.name, args);\n },\n };\n }\n\n async callTool(\n serverName: string,\n toolName: string,\n args: Record<string, unknown>,\n options?: { timeoutMs?: number },\n ): Promise<ToolResult> {\n const conn = this.connections.get(serverName);\n if (!conn || conn.status !== \"connected\" || !conn.client) {\n return {\n content: `MCP server \"${serverName}\" is not connected`,\n isError: true,\n };\n }\n\n try {\n const timeoutMs = options?.timeoutMs ?? DEFAULT_TOOL_TIMEOUT_MS;\n const abortController = new AbortController();\n const timer = setTimeout(() => abortController.abort(), timeoutMs);\n\n let result: Awaited<ReturnType<Client[\"callTool\"]>>;\n try {\n result = await conn.client.callTool(\n { name: toolName, arguments: args },\n undefined,\n { signal: abortController.signal },\n );\n } finally {\n clearTimeout(timer);\n }\n\n const contentBlocks = result.content as Array<{\n type: string;\n text?: string;\n data?: string;\n mimeType?: string;\n blob?: string;\n }> | undefined;\n\n if (!contentBlocks) {\n return { content: JSON.stringify(result), isError: result.isError === true };\n }\n\n const parts: ContentPart[] = [];\n for (const block of contentBlocks) {\n if (block.type === \"text\") {\n parts.push({ type: \"text\", text: block.text ?? \"\" });\n } else if (block.type === \"image\" && block.data) {\n const imageBuffer = Buffer.from(block.data, \"base64\");\n const ext = block.mimeType?.split(\"/\")[1] || \"png\";\n try {\n const resized = await maybeResizeAndDownsampleImageBuffer(\n imageBuffer,\n imageBuffer.length,\n ext,\n );\n parts.push({\n type: \"image\",\n data: resized.buffer.toString(\"base64\"),\n media_type: `image/${resized.mediaType}`,\n });\n } catch {\n parts.push({\n type: \"image\",\n data: block.data,\n media_type: block.mimeType ?? \"image/png\",\n });\n }\n } else if (block.type === \"resource\" && block.blob) {\n const isImage = block.mimeType?.startsWith(\"image/\") ?? false;\n if (isImage) {\n const imageBuffer = Buffer.from(block.blob, \"base64\");\n const ext = block.mimeType?.split(\"/\")[1] || \"png\";\n try {\n const resized = await maybeResizeAndDownsampleImageBuffer(\n imageBuffer,\n imageBuffer.length,\n ext,\n );\n parts.push({\n type: \"image\",\n data: resized.buffer.toString(\"base64\"),\n media_type: `image/${resized.mediaType}`,\n });\n } catch {\n parts.push({\n type: \"image\",\n data: block.blob,\n media_type: block.mimeType ?? \"image/png\",\n });\n }\n } else {\n parts.push({ type: \"text\", text: JSON.stringify(block) });\n }\n } else {\n parts.push({ type: \"text\", text: JSON.stringify(block) });\n }\n }\n\n // If all parts are text, flatten to a single string for simpler downstream handling\n if (parts.every((p) => p.type === \"text\")) {\n let text = parts\n .map((p) => (p as { text: string }).text)\n .join(\"\\n\");\n if (Buffer.byteLength(text, \"utf-8\") > MAX_TEXT_RESULT_BYTES) {\n text = text.slice(0, MAX_TEXT_RESULT_BYTES) + \"\\n...[output truncated]\";\n }\n return { content: text, isError: result.isError === true };\n }\n\n return { content: parts, isError: result.isError === true };\n } catch (err) {\n const isAbort =\n err instanceof DOMException && err.name === \"AbortError\";\n const message = isAbort\n ? `MCP tool \"${toolName}\" on server \"${serverName}\" timed out`\n : err instanceof Error ? err.message : String(err);\n return { content: message, isError: true };\n }\n }\n\n getConnectionStatus(): Array<{\n name: string;\n status: string;\n toolCount?: number;\n }> {\n return Array.from(this.connections.values()).map((c) => ({\n name: c.name,\n status: c.status,\n }));\n }\n\n /**\n * Returns server names that are in `needs-auth` status and require\n * interactive OAuth before they can be used.\n */\n getServersNeedingAuth(): string[] {\n return Array.from(this.connections.entries())\n .filter(([, conn]) => conn.status === \"needs-auth\")\n .map(([name]) => name);\n }\n\n /**\n * Reconnect a server by closing its existing connection and\n * establishing a new one. Useful after completing OAuth.\n */\n async reconnect(serverName: string): Promise<void> {\n const conn = this.connections.get(serverName);\n if (conn) {\n await conn.cleanup().catch(() => {});\n this.connections.delete(serverName);\n }\n\n const config = this.serverConfigs[serverName];\n if (!config) return;\n\n try {\n await this.connectToServer(serverName, config);\n } catch {\n this.connections.set(serverName, {\n name: serverName,\n client: null,\n status: \"failed\",\n config,\n cleanup: async () => {},\n });\n }\n }\n\n /**\n * Trigger interactive OAuth for a `needs-auth` server, then reconnect.\n * Runs the full MCP SDK auth orchestrator with a local callback server.\n *\n * Returns the authorization URL if the flow requires user interaction,\n * or null if the server connected without browser auth (e.g. cached tokens).\n */\n async performAuth(\n serverName: string,\n options?: { signal?: AbortSignal },\n ): Promise<{ authUrl?: string }> {\n const config = this.serverConfigs[serverName];\n if (!config) {\n throw new Error(`Unknown MCP server: ${serverName}`);\n }\n\n const configType = \"type\" in config ? config.type : \"stdio\";\n if (configType !== \"http\" && configType !== \"sse\") {\n throw new Error(\n `OAuth is only supported for HTTP and SSE transports, got: ${configType}`,\n );\n }\n\n const httpConfig = config as McpHttpServerConfig | McpSseServerConfig;\n if (!httpConfig.oauth && !httpConfig.authProvider) {\n throw new Error(\n `Server \"${serverName}\" has no OAuth configuration`,\n );\n }\n\n let capturedAuthUrl: string | undefined;\n const originalCallback = this.onAuthorizationUrl;\n\n this.onAuthorizationUrl = async (url: string) => {\n capturedAuthUrl = url;\n if (originalCallback) await originalCallback(url);\n };\n\n try {\n await this.reconnect(serverName);\n } finally {\n this.onAuthorizationUrl = originalCallback;\n }\n\n return { authUrl: capturedAuthUrl };\n }\n\n async close(): Promise<void> {\n const cleanups = Array.from(this.connections.values()).map((c) =>\n c.cleanup().catch(() => {}),\n );\n await Promise.all(cleanups);\n this.connections.clear();\n }\n}\n","import { randomBytes } from \"node:crypto\";\nimport { exec } from \"node:child_process\";\nimport type {\n OAuthClientProvider,\n OAuthDiscoveryState,\n} from \"@modelcontextprotocol/sdk/client/auth.js\";\nimport type {\n OAuthTokens,\n OAuthClientMetadata,\n OAuthClientInformationMixed,\n} from \"@modelcontextprotocol/sdk/shared/auth.js\";\nimport type { TokenStorage, OAuthProviderOptions } from \"./types.js\";\n\n/**\n * OAuthClientProvider implementation backed by noumen's pluggable\n * TokenStorage. Handles all persistence (tokens, client info, PKCE\n * verifiers, discovery state) through the storage interface and\n * delegates browser/UI concerns to configurable callbacks.\n */\nexport class NoumenOAuthProvider implements OAuthClientProvider {\n private serverKey: string;\n private storage: TokenStorage;\n private _clientMetadata: OAuthClientMetadata;\n private _callbackPort: number;\n private _onAuthorizationUrl?: (url: string) => void | Promise<void>;\n private _signal?: AbortSignal;\n private _preRegisteredClientId?: string;\n private _preRegisteredClientSecret?: string;\n private _state: string | null = null;\n\n constructor(serverKey: string, options: OAuthProviderOptions) {\n this.serverKey = serverKey;\n this.storage = options.storage;\n this._clientMetadata = options.clientMetadata;\n this._callbackPort = options.callbackPort ?? 3118;\n this._onAuthorizationUrl = options.onAuthorizationUrl;\n this._signal = options.signal;\n this._preRegisteredClientId = options.clientId;\n this._preRegisteredClientSecret = options.clientSecret;\n }\n\n get redirectUrl(): string {\n return `http://localhost:${this._callbackPort}/callback`;\n }\n\n get clientMetadata(): OAuthClientMetadata {\n return this._clientMetadata;\n }\n\n async state(): Promise<string> {\n if (!this._state) {\n this._state = randomBytes(32).toString(\"base64url\");\n }\n return this._state;\n }\n\n async clientInformation(): Promise<OAuthClientInformationMixed | undefined> {\n if (this._preRegisteredClientId) {\n const info: OAuthClientInformationMixed = {\n client_id: this._preRegisteredClientId,\n };\n if (this._preRegisteredClientSecret) {\n (info as Record<string, unknown>).client_secret =\n this._preRegisteredClientSecret;\n }\n return info;\n }\n\n const data = await this.storage.load(this.serverKey);\n return data?.clientInformation;\n }\n\n async saveClientInformation(\n clientInformation: OAuthClientInformationMixed,\n ): Promise<void> {\n const data = (await this.storage.load(this.serverKey)) ?? {};\n data.clientInformation = clientInformation;\n await this.storage.save(this.serverKey, data);\n }\n\n async tokens(): Promise<OAuthTokens | undefined> {\n const data = await this.storage.load(this.serverKey);\n if (!data?.tokens) return undefined;\n\n const REFRESH_BUFFER_MS = 5 * 60 * 1000;\n if (\n data.expiresAt &&\n data.tokens.refresh_token &&\n Date.now() >= data.expiresAt - REFRESH_BUFFER_MS\n ) {\n return undefined;\n }\n return data.tokens;\n }\n\n async saveTokens(tokens: OAuthTokens): Promise<void> {\n const data = (await this.storage.load(this.serverKey)) ?? {};\n data.tokens = tokens;\n if (tokens.expires_in != null) {\n data.expiresAt = Date.now() + tokens.expires_in * 1000;\n }\n await this.storage.save(this.serverKey, data);\n }\n\n async redirectToAuthorization(authorizationUrl: URL): Promise<void> {\n const urlStr = authorizationUrl.toString();\n\n if (this._onAuthorizationUrl) {\n await this._onAuthorizationUrl(urlStr);\n return;\n }\n\n openBrowser(urlStr);\n }\n\n async saveCodeVerifier(codeVerifier: string): Promise<void> {\n const data = (await this.storage.load(this.serverKey)) ?? {};\n data.codeVerifier = codeVerifier;\n await this.storage.save(this.serverKey, data);\n }\n\n async codeVerifier(): Promise<string> {\n const data = await this.storage.load(this.serverKey);\n return data?.codeVerifier ?? \"\";\n }\n\n async invalidateCredentials(\n scope: \"all\" | \"client\" | \"tokens\" | \"verifier\" | \"discovery\",\n ): Promise<void> {\n if (scope === \"all\") {\n this._state = null;\n await this.storage.delete(this.serverKey);\n return;\n }\n\n const data = await this.storage.load(this.serverKey);\n if (!data) return;\n\n switch (scope) {\n case \"client\":\n delete data.clientInformation;\n break;\n case \"tokens\":\n delete data.tokens;\n delete data.expiresAt;\n break;\n case \"verifier\":\n delete data.codeVerifier;\n break;\n case \"discovery\":\n delete data.discoveryState;\n break;\n }\n await this.storage.save(this.serverKey, data);\n }\n\n async saveDiscoveryState(state: OAuthDiscoveryState): Promise<void> {\n const data = (await this.storage.load(this.serverKey)) ?? {};\n data.discoveryState = state;\n await this.storage.save(this.serverKey, data);\n }\n\n async discoveryState(): Promise<OAuthDiscoveryState | undefined> {\n const data = await this.storage.load(this.serverKey);\n return data?.discoveryState;\n }\n}\n\nfunction openBrowser(url: string): void {\n const platform = process.platform;\n const cmd =\n platform === \"darwin\"\n ? \"open\"\n : platform === \"win32\"\n ? \"start\"\n : \"xdg-open\";\n exec(`${cmd} ${JSON.stringify(url)}`);\n}\n","import * as http from \"node:http\";\n\nconst DEFAULT_FALLBACK_PORT = 3118;\nconst EPHEMERAL_PORT_MIN = 49152;\nconst EPHEMERAL_PORT_MAX = 65535;\nconst MAX_PORT_ATTEMPTS = 50;\nconst CALLBACK_TIMEOUT_MS = 5 * 60 * 1000;\n\n/**\n * Find an available local port for the OAuth callback server.\n * Tries the preferred port first, then random ephemeral ports.\n */\nexport async function findAvailablePort(preferred?: number): Promise<number> {\n if (preferred != null) {\n const ok = await isPortAvailable(preferred);\n if (ok) return preferred;\n }\n\n for (let i = 0; i < MAX_PORT_ATTEMPTS; i++) {\n const port =\n EPHEMERAL_PORT_MIN +\n Math.floor(Math.random() * (EPHEMERAL_PORT_MAX - EPHEMERAL_PORT_MIN + 1));\n const ok = await isPortAvailable(port);\n if (ok) return port;\n }\n\n return DEFAULT_FALLBACK_PORT;\n}\n\nfunction isPortAvailable(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const server = http.createServer();\n server.once(\"error\", () => resolve(false));\n server.listen(port, \"127.0.0.1\", () => {\n server.close(() => resolve(true));\n });\n });\n}\n\nexport interface OAuthCallbackResult {\n code: string;\n state?: string;\n}\n\n/**\n * Lightweight HTTP server on localhost that receives the OAuth redirect\n * callback. Validates the state parameter when expected and resolves\n * with the authorization code.\n */\nexport class OAuthCallbackServer {\n private server: http.Server | null = null;\n private port = 0;\n\n /**\n * Start listening on a local port and return a promise that resolves\n * when the authorization callback is received.\n */\n async start(options?: {\n expectedState?: string;\n callbackPort?: number;\n signal?: AbortSignal;\n }): Promise<{\n port: number;\n redirectUri: string;\n waitForCallback: () => Promise<OAuthCallbackResult>;\n }> {\n const port = await findAvailablePort(options?.callbackPort);\n this.port = port;\n const redirectUri = `http://localhost:${port}/callback`;\n\n const callbackPromise = new Promise<OAuthCallbackResult>(\n (resolve, reject) => {\n const server = http.createServer((req, res) => {\n if (!req.url?.startsWith(\"/callback\")) {\n res.writeHead(404);\n res.end(\"Not found\");\n return;\n }\n\n const parsed = new URL(req.url, `http://localhost:${port}`);\n const code = parsed.searchParams.get(\"code\") ?? undefined;\n const state = parsed.searchParams.get(\"state\") ?? undefined;\n const error = parsed.searchParams.get(\"error\") ?? undefined;\n const errorDescription = parsed.searchParams.get(\"error_description\") ?? undefined;\n\n if (error) {\n res.writeHead(400, { \"Content-Type\": \"text/html\" });\n res.end(\n `<html><body><h1>Authorization Error</h1><p>${escapeHtml(error)}: ${escapeHtml(errorDescription ?? \"\")}</p></body></html>`,\n );\n cleanup();\n reject(new Error(`OAuth error: ${error} - ${errorDescription ?? \"\"}`));\n return;\n }\n\n if (!code) {\n res.writeHead(400, { \"Content-Type\": \"text/html\" });\n res.end(\n \"<html><body><h1>Error</h1><p>Missing authorization code.</p></body></html>\",\n );\n cleanup();\n reject(new Error(\"Missing authorization code in callback\"));\n return;\n }\n\n if (options?.expectedState && state !== options.expectedState) {\n res.writeHead(400, { \"Content-Type\": \"text/html\" });\n res.end(\n \"<html><body><h1>Error</h1><p>State parameter mismatch.</p></body></html>\",\n );\n cleanup();\n reject(new Error(\"OAuth state mismatch\"));\n return;\n }\n\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(\n \"<html><body><h1>Authorization Successful</h1><p>You can close this window.</p></body></html>\",\n );\n cleanup();\n resolve({ code, state });\n });\n\n this.server = server;\n\n const timeout = setTimeout(() => {\n cleanup();\n reject(new Error(\"OAuth callback timeout\"));\n }, CALLBACK_TIMEOUT_MS);\n\n const cleanup = () => {\n clearTimeout(timeout);\n this.close();\n };\n\n if (options?.signal) {\n if (options.signal.aborted) {\n cleanup();\n reject(new Error(\"OAuth callback aborted\"));\n return;\n }\n options.signal.addEventListener(\n \"abort\",\n () => {\n cleanup();\n reject(new Error(\"OAuth callback aborted\"));\n },\n { once: true },\n );\n }\n\n server.listen(port, \"127.0.0.1\");\n server.unref();\n },\n );\n\n return { port, redirectUri, waitForCallback: () => callbackPromise };\n }\n\n close(): void {\n if (this.server) {\n try {\n this.server.close();\n } catch {\n // already closed\n }\n this.server = null;\n }\n }\n}\n\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n}\n","import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport type { TokenStorage, OAuthTokenData } from \"./types.js\";\nimport {\n DEFAULT_DOT_DIRS,\n createDotDirResolver,\n type DotDirResolver,\n} from \"../../config/dot-dirs.js\";\n\n/**\n * In-memory token storage. Suitable for tests, short-lived processes,\n * and situations where persistence across restarts is not needed.\n */\nexport class InMemoryTokenStorage implements TokenStorage {\n private store = new Map<string, OAuthTokenData>();\n\n async load(serverKey: string): Promise<OAuthTokenData | undefined> {\n return this.store.get(serverKey);\n }\n\n async save(serverKey: string, data: OAuthTokenData): Promise<void> {\n this.store.set(serverKey, data);\n }\n\n async delete(serverKey: string): Promise<void> {\n this.store.delete(serverKey);\n }\n}\n\n/**\n * File-backed token storage. Persists all server tokens to a single JSON\n * file so they survive process restarts. Each server key maps to its own\n * entry in the file.\n *\n * Reads walk all candidate dot-dirs in preference order, so tokens written\n * by an older install under `.claude/` keep working; writes always target\n * the resolver's canonical write dir (default `.noumen/`).\n */\nexport class FileTokenStorage implements TokenStorage {\n /** Explicit path override, if provided. */\n private explicitPath?: string;\n private resolver: DotDirResolver;\n private homeBase: string;\n\n constructor(filePath?: string, resolver?: DotDirResolver) {\n this.explicitPath = filePath;\n this.resolver = resolver ?? createDotDirResolver(DEFAULT_DOT_DIRS);\n this.homeBase = process.env.HOME ?? process.env.USERPROFILE ?? \".\";\n }\n\n private get writePath(): string {\n if (this.explicitPath) return this.explicitPath;\n return this.resolver.joinWrite(this.homeBase, \"mcp-oauth-tokens.json\");\n }\n\n private readCandidatePaths(): string[] {\n if (this.explicitPath) return [this.explicitPath];\n return this.resolver.joinRead(this.homeBase, \"mcp-oauth-tokens.json\");\n }\n\n async load(serverKey: string): Promise<OAuthTokenData | undefined> {\n const all = await this.readAll();\n return all[serverKey];\n }\n\n async save(serverKey: string, data: OAuthTokenData): Promise<void> {\n const all = await this.readAll();\n all[serverKey] = data;\n await this.writeAll(all);\n }\n\n async delete(serverKey: string): Promise<void> {\n const all = await this.readAll();\n delete all[serverKey];\n await this.writeAll(all);\n }\n\n private async readAll(): Promise<Record<string, OAuthTokenData>> {\n for (const candidate of this.readCandidatePaths()) {\n try {\n const raw = await fs.readFile(candidate, \"utf-8\");\n return JSON.parse(raw) as Record<string, OAuthTokenData>;\n } catch {\n // keep walking\n }\n }\n return {};\n }\n\n private async writeAll(data: Record<string, OAuthTokenData>): Promise<void> {\n const target = this.writePath;\n await fs.mkdir(path.dirname(target), { recursive: true });\n await fs.writeFile(target, JSON.stringify(data, null, 2), \"utf-8\");\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,40 @@
1
+ // src/providers/types.ts
2
+ var ChatStreamError = class extends Error {
3
+ status;
4
+ retryAfter;
5
+ constructor(message, opts) {
6
+ super(message, { cause: opts?.cause });
7
+ this.name = "ChatStreamError";
8
+ this.status = opts?.status;
9
+ this.retryAfter = opts?.retryAfter;
10
+ }
11
+ };
12
+
13
+ // src/providers/cache.ts
14
+ function sortToolDefinitionsForCache(tools, mcpToolNames) {
15
+ const byName = (a, b) => a.function.name.localeCompare(b.function.name);
16
+ if (!mcpToolNames || mcpToolNames.size === 0) {
17
+ return [...tools].sort(byName);
18
+ }
19
+ const builtIn = [];
20
+ const mcp = [];
21
+ for (const t of tools) {
22
+ if (mcpToolNames.has(t.function.name)) {
23
+ mcp.push(t);
24
+ } else {
25
+ builtIn.push(t);
26
+ }
27
+ }
28
+ return [...builtIn.sort(byName), ...mcp.sort(byName)];
29
+ }
30
+ function getMessageCacheBreakpointIndex(messages, skipCacheWrite) {
31
+ if (messages.length === 0) return -1;
32
+ return skipCacheWrite && messages.length >= 2 ? messages.length - 2 : messages.length - 1;
33
+ }
34
+
35
+ export {
36
+ ChatStreamError,
37
+ sortToolDefinitionsForCache,
38
+ getMessageCacheBreakpointIndex
39
+ };
40
+ //# sourceMappingURL=chunk-OPFFLQZL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/providers/types.ts","../src/providers/cache.ts"],"sourcesContent":["import type { ChatMessage } from \"../session/types.js\";\nimport type { ThinkingConfig } from \"../thinking/types.js\";\n\nexport interface ToolParameterProperty {\n type: string;\n description?: string;\n enum?: string[];\n default?: unknown;\n minimum?: number;\n maximum?: number;\n}\n\nexport interface ToolDefinition {\n type: \"function\";\n function: {\n name: string;\n description: string;\n parameters: {\n type: \"object\";\n properties: Record<string, ToolParameterProperty>;\n required?: string[];\n };\n };\n}\n\n// Streaming chunk types (OpenAI-compatible)\n\nexport interface ChatStreamDelta {\n role?: \"assistant\";\n content?: string | null;\n thinking_content?: string | null;\n thinking_signature?: string | null;\n /** Opaque data payload for Anthropic redacted_thinking blocks. */\n redacted_thinking_data?: string | null;\n tool_calls?: Array<{\n index: number;\n id?: string;\n type?: \"function\";\n function?: {\n name?: string;\n arguments?: string;\n };\n }>;\n}\n\nexport interface ChatStreamChoice {\n index: number;\n delta: ChatStreamDelta;\n finish_reason: string | null;\n}\n\nexport interface ChatStreamChunk {\n id: string;\n choices: ChatStreamChoice[];\n model: string;\n usage?: {\n prompt_tokens: number;\n completion_tokens: number;\n total_tokens: number;\n cache_read_tokens?: number;\n cache_creation_tokens?: number;\n thinking_tokens?: number;\n };\n}\n\nexport interface ChatCompletionUsage {\n prompt_tokens: number;\n completion_tokens: number;\n total_tokens: number;\n cache_read_tokens?: number;\n cache_creation_tokens?: number;\n thinking_tokens?: number;\n}\n\n/**\n * Structured output format. When provided, the model is constrained to\n * produce a response matching the given JSON schema.\n *\n * - `json_schema`: the model must produce JSON conforming to the given schema.\n * - `json_object`: the model must produce valid JSON (no specific schema).\n */\nexport type OutputFormat = JsonSchemaOutputFormat | JsonObjectOutputFormat;\n\nexport interface JsonSchemaOutputFormat {\n type: \"json_schema\";\n /** JSON Schema object describing the expected output shape. */\n schema: Record<string, unknown>;\n /** Optional name for the schema (required by some providers). */\n name?: string;\n /** When true, the provider enforces strict schema adherence. */\n strict?: boolean;\n}\n\nexport interface JsonObjectOutputFormat {\n type: \"json_object\";\n}\n\nexport interface ChatParams {\n model: string;\n messages: ChatMessage[];\n tools?: ToolDefinition[];\n max_tokens?: number;\n system?: string;\n temperature?: number;\n thinking?: ThinkingConfig;\n /**\n * Fine-grained reasoning effort hint. Honored by providers whose\n * reasoning-capable models expose an explicit effort knob (OpenAI\n * GPT-5 / o-series today). Providers that don't recognise the knob\n * ignore it. Use `\"minimal\"` for cheap structural calls (e.g.\n * auto-title, classification) so the model doesn't burn the whole\n * `max_tokens` budget on internal reasoning before emitting output.\n */\n reasoningEffort?: \"minimal\" | \"low\" | \"medium\" | \"high\";\n /** Constrain the model to produce structured output matching this schema. */\n outputFormat?: OutputFormat;\n /**\n * When true, the provider should place the cache breakpoint on the\n * second-to-last message instead of the last. Used by subagent forks\n * to avoid writing fork-only tails into the shared prompt cache.\n */\n skipCacheWrite?: boolean;\n /** Abort signal — providers should forward this to cancel in-flight HTTP requests. */\n signal?: AbortSignal;\n}\n\nexport interface AIProvider {\n chat(params: ChatParams): AsyncIterable<ChatStreamChunk>;\n /**\n * Optional fallback model name, used when no `model` is provided at the\n * Thread / Agent level. Consumers should pass explicit models when they\n * can, but this lets each provider ship a sensible default without\n * forcing Thread to hardcode provider-specific strings.\n */\n readonly defaultModel?: string;\n}\n\n/**\n * Extended error type that providers can throw to convey retry-relevant metadata.\n * Consumers (like the retry engine) can inspect these fields without knowing\n * provider-specific SDK error types.\n */\nexport class ChatStreamError extends Error {\n status?: number;\n retryAfter?: string;\n\n constructor(\n message: string,\n opts?: { status?: number; retryAfter?: string; cause?: unknown },\n ) {\n super(message, { cause: opts?.cause });\n this.name = \"ChatStreamError\";\n this.status = opts?.status;\n this.retryAfter = opts?.retryAfter;\n }\n}\n","/**\n * Provider-agnostic prompt caching utilities.\n *\n * Stable tool ordering prevents cache invalidation when the tool set is\n * unchanged. The breakpoint index helper determines which message gets a\n * single cache_control marker per request (matching claude-code's strategy).\n */\n\nimport type { ToolDefinition } from \"./types.js\";\nimport type { ChatMessage } from \"../session/types.js\";\n\nexport type CacheScope = \"global\" | \"org\";\n\nexport interface CacheControlConfig {\n enabled: boolean;\n /** TTL for cached content. When set, produces `ttl: '1h'` in cache_control. */\n ttl?: \"1h\";\n /** Scope for shared cache across sessions/orgs. */\n scope?: CacheScope;\n}\n\n/**\n * Sort tool definitions deterministically for prompt cache stability.\n *\n * Strategy (matching claude-code's assembleToolPool): built-in tools form a\n * contiguous prefix sorted by name, followed by MCP/external tools sorted by\n * name. Tools with `mcpInfo` on the original Tool object are treated as MCP;\n * everything else is built-in. Since ToolDefinition doesn't carry mcpInfo,\n * callers can pass an optional set of MCP tool names to partition correctly.\n */\nexport function sortToolDefinitionsForCache(\n tools: ToolDefinition[],\n mcpToolNames?: ReadonlySet<string>,\n): ToolDefinition[] {\n const byName = (a: ToolDefinition, b: ToolDefinition) =>\n a.function.name.localeCompare(b.function.name);\n\n if (!mcpToolNames || mcpToolNames.size === 0) {\n return [...tools].sort(byName);\n }\n\n const builtIn: ToolDefinition[] = [];\n const mcp: ToolDefinition[] = [];\n\n for (const t of tools) {\n if (mcpToolNames.has(t.function.name)) {\n mcp.push(t);\n } else {\n builtIn.push(t);\n }\n }\n\n return [...builtIn.sort(byName), ...mcp.sort(byName)];\n}\n\n/**\n * Determine which message index should receive the cache_control breakpoint.\n *\n * Exactly one message per request is marked. Normally the last message;\n * for forked agents with skipCacheWrite the second-to-last so the fork\n * doesn't write its own tail into the cache.\n */\nexport function getMessageCacheBreakpointIndex(\n messages: ChatMessage[],\n skipCacheWrite?: boolean,\n): number {\n if (messages.length === 0) return -1;\n return skipCacheWrite && messages.length >= 2\n ? messages.length - 2\n : messages.length - 1;\n}\n"],"mappings":";AA8IO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EACzC;AAAA,EACA;AAAA,EAEA,YACE,SACA,MACA;AACA,UAAM,SAAS,EAAE,OAAO,MAAM,MAAM,CAAC;AACrC,SAAK,OAAO;AACZ,SAAK,SAAS,MAAM;AACpB,SAAK,aAAa,MAAM;AAAA,EAC1B;AACF;;;AC7HO,SAAS,4BACd,OACA,cACkB;AAClB,QAAM,SAAS,CAAC,GAAmB,MACjC,EAAE,SAAS,KAAK,cAAc,EAAE,SAAS,IAAI;AAE/C,MAAI,CAAC,gBAAgB,aAAa,SAAS,GAAG;AAC5C,WAAO,CAAC,GAAG,KAAK,EAAE,KAAK,MAAM;AAAA,EAC/B;AAEA,QAAM,UAA4B,CAAC;AACnC,QAAM,MAAwB,CAAC;AAE/B,aAAW,KAAK,OAAO;AACrB,QAAI,aAAa,IAAI,EAAE,SAAS,IAAI,GAAG;AACrC,UAAI,KAAK,CAAC;AAAA,IACZ,OAAO;AACL,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AAEA,SAAO,CAAC,GAAG,QAAQ,KAAK,MAAM,GAAG,GAAG,IAAI,KAAK,MAAM,CAAC;AACtD;AASO,SAAS,+BACd,UACA,gBACQ;AACR,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,SAAO,kBAAkB,SAAS,UAAU,IACxC,SAAS,SAAS,IAClB,SAAS,SAAS;AACxB;","names":[]}