@leterhong/multichat 1.6.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 (214) hide show
  1. package/.agents/plugins/marketplace.json +32 -0
  2. package/.agents/plugins/plugins/developer-toolkit/.codex-plugin/plugin.json +24 -0
  3. package/.agents/plugins/plugins/developer-toolkit/skills/code-review/SKILL.md +9 -0
  4. package/.agents/plugins/plugins/developer-toolkit/skills/code-review/agents/openai.yaml +3 -0
  5. package/.agents/plugins/plugins/developer-toolkit/skills/release-notes/SKILL.md +9 -0
  6. package/.agents/plugins/plugins/developer-toolkit/skills/release-notes/agents/openai.yaml +3 -0
  7. package/.agents/plugins/plugins/developer-toolkit/skills/security-review/SKILL.md +9 -0
  8. package/.agents/plugins/plugins/developer-toolkit/skills/security-review/agents/openai.yaml +3 -0
  9. package/.agents/plugins/plugins/developer-toolkit/skills/test-plan/SKILL.md +9 -0
  10. package/.agents/plugins/plugins/developer-toolkit/skills/test-plan/agents/openai.yaml +3 -0
  11. package/.agents/plugins/plugins/writing-toolkit/.codex-plugin/plugin.json +24 -0
  12. package/.agents/plugins/plugins/writing-toolkit/skills/meeting-notes/SKILL.md +9 -0
  13. package/.agents/plugins/plugins/writing-toolkit/skills/meeting-notes/agents/openai.yaml +3 -0
  14. package/.agents/plugins/plugins/writing-toolkit/skills/resume-tune/SKILL.md +9 -0
  15. package/.agents/plugins/plugins/writing-toolkit/skills/resume-tune/agents/openai.yaml +3 -0
  16. package/.agents/plugins/plugins/writing-toolkit/skills/weekly-report/SKILL.md +9 -0
  17. package/.agents/plugins/plugins/writing-toolkit/skills/weekly-report/agents/openai.yaml +3 -0
  18. package/.agents/skills/.gitkeep +1 -0
  19. package/.agents/skills/wanghong-handwritten-ppt/SKILL.md +172 -0
  20. package/.agents/skills/wanghong-handwritten-ppt/assets/HTML-PPT-LICENSE +21 -0
  21. package/.agents/skills/wanghong-handwritten-ppt/assets/NEAT-ANNOTATIONS-LICENSE +21 -0
  22. package/.agents/skills/wanghong-handwritten-ppt/assets/animations.css +138 -0
  23. package/.agents/skills/wanghong-handwritten-ppt/assets/base.css +150 -0
  24. package/.agents/skills/wanghong-handwritten-ppt/assets/deepseek-example.css +893 -0
  25. package/.agents/skills/wanghong-handwritten-ppt/assets/neat-annotations.css +152 -0
  26. package/.agents/skills/wanghong-handwritten-ppt/assets/preview-cover.png +0 -0
  27. package/.agents/skills/wanghong-handwritten-ppt/assets/preview-diagram.png +0 -0
  28. package/.agents/skills/wanghong-handwritten-ppt/assets/preview-summary.png +0 -0
  29. package/.agents/skills/wanghong-handwritten-ppt/assets/preview-table.png +0 -0
  30. package/.agents/skills/wanghong-handwritten-ppt/assets/runtime.js +960 -0
  31. package/.agents/skills/wanghong-handwritten-ppt/assets/template.css +198 -0
  32. package/.agents/skills/wanghong-handwritten-ppt/evals/evals.json +20 -0
  33. package/.agents/skills/wanghong-handwritten-ppt/examples/deepseek-v4-flash/index.html +414 -0
  34. package/.agents/skills/wanghong-handwritten-ppt/examples/deepseek-v4-flash/style.css +897 -0
  35. package/.agents/skills/wanghong-handwritten-ppt/references/prompts.md +170 -0
  36. package/.agents/skills/wanghong-handwritten-ppt/references/style-guide.md +133 -0
  37. package/.agents/skills/wanghong-handwritten-ppt/scripts/check_deck.py +41 -0
  38. package/.agents/skills/wanghong-handwritten-ppt/scripts/prepare_render_html.py +137 -0
  39. package/.agents/skills/wanghong-handwritten-ppt/scripts/render.sh +110 -0
  40. package/.agents/skills/wanghong-handwritten-ppt/templates/deck.html +94 -0
  41. package/.agents/skills/wanghong-handwritten-ppt/tests/test_font_contract.py +73 -0
  42. package/LICENSE +21 -0
  43. package/README.md +186 -0
  44. package/backend/adapters/README.md +77 -0
  45. package/backend/adapters/anthropic.ts +115 -0
  46. package/backend/adapters/base.ts +47 -0
  47. package/backend/adapters/index.ts +41 -0
  48. package/backend/adapters/lmstudio.ts +7 -0
  49. package/backend/adapters/moonshot.ts +7 -0
  50. package/backend/adapters/ollama.ts +7 -0
  51. package/backend/adapters/openai-compatible.ts +51 -0
  52. package/backend/adapters/openai.ts +7 -0
  53. package/backend/adapters/qwen.ts +112 -0
  54. package/backend/adapters/wenxin.ts +136 -0
  55. package/backend/adapters/zhipu.ts +93 -0
  56. package/backend/dist/adapters/anthropic.js +130 -0
  57. package/backend/dist/adapters/anthropic.js.map +1 -0
  58. package/backend/dist/adapters/base.js +38 -0
  59. package/backend/dist/adapters/base.js.map +1 -0
  60. package/backend/dist/adapters/index.js +39 -0
  61. package/backend/dist/adapters/index.js.map +1 -0
  62. package/backend/dist/adapters/lmstudio.js +8 -0
  63. package/backend/dist/adapters/lmstudio.js.map +1 -0
  64. package/backend/dist/adapters/moonshot.js +8 -0
  65. package/backend/dist/adapters/moonshot.js.map +1 -0
  66. package/backend/dist/adapters/ollama.js +8 -0
  67. package/backend/dist/adapters/ollama.js.map +1 -0
  68. package/backend/dist/adapters/openai-compatible.js +47 -0
  69. package/backend/dist/adapters/openai-compatible.js.map +1 -0
  70. package/backend/dist/adapters/openai.js +8 -0
  71. package/backend/dist/adapters/openai.js.map +1 -0
  72. package/backend/dist/adapters/qwen.js +103 -0
  73. package/backend/dist/adapters/qwen.js.map +1 -0
  74. package/backend/dist/adapters/wenxin.js +114 -0
  75. package/backend/dist/adapters/wenxin.js.map +1 -0
  76. package/backend/dist/adapters/zhipu.js +86 -0
  77. package/backend/dist/adapters/zhipu.js.map +1 -0
  78. package/backend/dist/extensions/demo-mcp-server.js +75 -0
  79. package/backend/dist/extensions/demo-mcp-server.js.map +1 -0
  80. package/backend/dist/extensions/importer.js +828 -0
  81. package/backend/dist/extensions/importer.js.map +1 -0
  82. package/backend/dist/extensions/manager.js +1154 -0
  83. package/backend/dist/extensions/manager.js.map +1 -0
  84. package/backend/dist/lib/catalog.js +30 -0
  85. package/backend/dist/lib/catalog.js.map +1 -0
  86. package/backend/dist/lib/context.js +59 -0
  87. package/backend/dist/lib/context.js.map +1 -0
  88. package/backend/dist/lib/errors.js +75 -0
  89. package/backend/dist/lib/errors.js.map +1 -0
  90. package/backend/dist/lib/provider-store.js +116 -0
  91. package/backend/dist/lib/provider-store.js.map +1 -0
  92. package/backend/dist/lib/redact.js +11 -0
  93. package/backend/dist/lib/redact.js.map +1 -0
  94. package/backend/dist/lib/runtime-store.js +28 -0
  95. package/backend/dist/lib/runtime-store.js.map +1 -0
  96. package/backend/dist/lib/sqlite-store.js +218 -0
  97. package/backend/dist/lib/sqlite-store.js.map +1 -0
  98. package/backend/dist/lib/sse.js +46 -0
  99. package/backend/dist/lib/sse.js.map +1 -0
  100. package/backend/dist/lib/ssrf.js +131 -0
  101. package/backend/dist/lib/ssrf.js.map +1 -0
  102. package/backend/dist/lib/store.js +84 -0
  103. package/backend/dist/lib/store.js.map +1 -0
  104. package/backend/dist/lib/usage.js +219 -0
  105. package/backend/dist/lib/usage.js.map +1 -0
  106. package/backend/dist/lib/util.js +38 -0
  107. package/backend/dist/lib/util.js.map +1 -0
  108. package/backend/dist/lib/workspace-store.js +152 -0
  109. package/backend/dist/lib/workspace-store.js.map +1 -0
  110. package/backend/dist/mcp.js +429 -0
  111. package/backend/dist/mcp.js.map +1 -0
  112. package/backend/dist/routes/agents.js +51 -0
  113. package/backend/dist/routes/agents.js.map +1 -0
  114. package/backend/dist/routes/assistants.js +32 -0
  115. package/backend/dist/routes/assistants.js.map +1 -0
  116. package/backend/dist/routes/chat.js +697 -0
  117. package/backend/dist/routes/chat.js.map +1 -0
  118. package/backend/dist/routes/control-plane.js +200 -0
  119. package/backend/dist/routes/control-plane.js.map +1 -0
  120. package/backend/dist/routes/conversations.js +66 -0
  121. package/backend/dist/routes/conversations.js.map +1 -0
  122. package/backend/dist/routes/extensions-import.js +22 -0
  123. package/backend/dist/routes/extensions-import.js.map +1 -0
  124. package/backend/dist/routes/import.js +85 -0
  125. package/backend/dist/routes/import.js.map +1 -0
  126. package/backend/dist/routes/mcp-servers.js +56 -0
  127. package/backend/dist/routes/mcp-servers.js.map +1 -0
  128. package/backend/dist/routes/meta.js +37 -0
  129. package/backend/dist/routes/meta.js.map +1 -0
  130. package/backend/dist/routes/mock.js +28 -0
  131. package/backend/dist/routes/mock.js.map +1 -0
  132. package/backend/dist/routes/plugins.js +41 -0
  133. package/backend/dist/routes/plugins.js.map +1 -0
  134. package/backend/dist/routes/providers.js +197 -0
  135. package/backend/dist/routes/providers.js.map +1 -0
  136. package/backend/dist/routes/runs.js +57 -0
  137. package/backend/dist/routes/runs.js.map +1 -0
  138. package/backend/dist/routes/skills.js +73 -0
  139. package/backend/dist/routes/skills.js.map +1 -0
  140. package/backend/dist/routes/tools.js +40 -0
  141. package/backend/dist/routes/tools.js.map +1 -0
  142. package/backend/dist/routes/usage.js +14 -0
  143. package/backend/dist/routes/usage.js.map +1 -0
  144. package/backend/dist/routes/workspaces.js +158 -0
  145. package/backend/dist/routes/workspaces.js.map +1 -0
  146. package/backend/dist/runtime/agent.js +518 -0
  147. package/backend/dist/runtime/agent.js.map +1 -0
  148. package/backend/dist/server.js +103 -0
  149. package/backend/dist/server.js.map +1 -0
  150. package/backend/dist/types.js +3 -0
  151. package/backend/dist/types.js.map +1 -0
  152. package/backend/extensions/demo-mcp-server.js +70 -0
  153. package/backend/extensions/importer.ts +709 -0
  154. package/backend/extensions/manager.ts +1055 -0
  155. package/backend/lib/catalog.ts +28 -0
  156. package/backend/lib/context.ts +61 -0
  157. package/backend/lib/errors.ts +78 -0
  158. package/backend/lib/provider-store.ts +116 -0
  159. package/backend/lib/redact.ts +9 -0
  160. package/backend/lib/runtime-store.ts +38 -0
  161. package/backend/lib/sqlite-store.ts +220 -0
  162. package/backend/lib/sse.ts +42 -0
  163. package/backend/lib/ssrf.ts +120 -0
  164. package/backend/lib/store.ts +79 -0
  165. package/backend/lib/usage.ts +212 -0
  166. package/backend/lib/util.ts +31 -0
  167. package/backend/lib/workspace-store.ts +150 -0
  168. package/backend/mcp.ts +417 -0
  169. package/backend/package.json +34 -0
  170. package/backend/prompts.json +82 -0
  171. package/backend/routes/agents.ts +55 -0
  172. package/backend/routes/assistants.ts +30 -0
  173. package/backend/routes/chat.ts +660 -0
  174. package/backend/routes/control-plane.ts +165 -0
  175. package/backend/routes/conversations.ts +62 -0
  176. package/backend/routes/extensions-import.ts +15 -0
  177. package/backend/routes/import.ts +72 -0
  178. package/backend/routes/mcp-servers.ts +36 -0
  179. package/backend/routes/meta.ts +38 -0
  180. package/backend/routes/mock.ts +25 -0
  181. package/backend/routes/plugins.ts +27 -0
  182. package/backend/routes/providers.ts +166 -0
  183. package/backend/routes/runs.ts +55 -0
  184. package/backend/routes/skills.ts +52 -0
  185. package/backend/routes/tools.ts +40 -0
  186. package/backend/routes/usage.ts +13 -0
  187. package/backend/routes/workspaces.ts +124 -0
  188. package/backend/runtime/agent.ts +474 -0
  189. package/backend/server.ts +106 -0
  190. package/backend/types.ts +126 -0
  191. package/bin/multichat.mjs +535 -0
  192. package/frontend/dist/assets/agentPicker-C39VzmYa.js +13 -0
  193. package/frontend/dist/assets/compare-BpaGD32J.js +1 -0
  194. package/frontend/dist/assets/conversations-COuLmg99.js +7 -0
  195. package/frontend/dist/assets/data-ZH7Dux1K.js +1 -0
  196. package/frontend/dist/assets/extensionImport-CnTWKtWm.js +28 -0
  197. package/frontend/dist/assets/importExport-B3mB-9zs.js +70 -0
  198. package/frontend/dist/assets/index-CASVCOUO.js +34 -0
  199. package/frontend/dist/assets/index-DSvbjNLc.css +1 -0
  200. package/frontend/dist/assets/init-ZPK483vB.js +1 -0
  201. package/frontend/dist/assets/jsx-runtime-ZSsbr0oW.js +9 -0
  202. package/frontend/dist/assets/modal-CgHocv3n.js +2 -0
  203. package/frontend/dist/assets/modelPicker-EUmmVnbj.js +14 -0
  204. package/frontend/dist/assets/pluginsUI-B6jGUQkx.js +16 -0
  205. package/frontend/dist/assets/render-DgAg1K8A.js +1 -0
  206. package/frontend/dist/assets/send-BJhZ_5YA.js +14 -0
  207. package/frontend/dist/assets/settings-DdnYwZl-.js +2 -0
  208. package/frontend/dist/assets/shell-DFV2khQl.js +1 -0
  209. package/frontend/dist/assets/workbench-BY2J4vWC.js +41 -0
  210. package/frontend/dist/icon-maskable.svg +4 -0
  211. package/frontend/dist/icon.svg +4 -0
  212. package/frontend/dist/index.html +21 -0
  213. package/frontend/dist/manifest.webmanifest +26 -0
  214. package/package.json +58 -0
@@ -0,0 +1,94 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>学术手写风演示</title>
7
+ <link rel="stylesheet" href="../assets/base.css">
8
+ <link rel="stylesheet" href="../assets/animations.css">
9
+ <link rel="stylesheet" href="../assets/neat-annotations.css">
10
+ <link rel="stylesheet" href="../assets/template.css">
11
+ </head>
12
+ <body>
13
+ <main class="deck">
14
+ <section class="slide cover is-active" data-title="封面">
15
+ <h1>在这里写一行报告标题</h1>
16
+ <div class="cover-meta">
17
+ <span>作者</span>
18
+ <span>场合</span>
19
+ <span>August 2, 2026</span>
20
+ </div>
21
+ <aside class="notes">用一两句话说明今天想解决的总问题。</aside>
22
+ </section>
23
+
24
+ <section class="slide" data-title="变化">
25
+ <h2 class="slide-title">先说清楚,发生了什么变化?</h2>
26
+ <div class="flow">
27
+ <div class="flow-box">起点</div>
28
+ <div class="flow-arrow">→</div>
29
+ <div class="flow-box"><span class="mark-yellow">关键动作</span></div>
30
+ <div class="flow-arrow">→</div>
31
+ <div class="flow-box"><span class="mark-green">结果</span></div>
32
+ </div>
33
+ <div class="result-box">这里写这一页的结论</div>
34
+ <aside class="notes">沿着三步流程讲清楚因果关系。</aside>
35
+ </section>
36
+
37
+ <section class="slide" data-title="图解">
38
+ <h2 class="slide-title">用一个小图解释核心判断</h2>
39
+ <div class="hand-grid">
40
+ <div class="notes-list">
41
+ <div>横轴:成本、时间或规模</div>
42
+ <div>纵轴:质量、能力或收益</div>
43
+ <div><span class="mark-coral">例外</span> 用珊瑚色标出</div>
44
+ <div><span class="ann ann-n ann-green" data-note="推荐位置">甜点档</span> 用绿色标出</div>
45
+ </div>
46
+ <svg viewBox="0 0 720 450" role="img" aria-label="手绘坐标图">
47
+ <defs>
48
+ <filter id="wobble"><feTurbulence baseFrequency="0.018" numOctaves="1" seed="3" result="n"/><feDisplacementMap in="SourceGraphic" in2="n" scale="1.4"/></filter>
49
+ </defs>
50
+ <g filter="url(#wobble)" fill="none" stroke="var(--ink)" stroke-width="4" stroke-linecap="round">
51
+ <path d="M90 360 L650 360"/>
52
+ <path d="M90 360 L90 50"/>
53
+ <path d="M105 80 C230 150 240 260 350 305 C450 345 540 346 620 348" stroke="var(--blue)"/>
54
+ <circle cx="372" cy="313" r="15" fill="var(--magenta)" stroke="none"/>
55
+ <circle cx="585" cy="345" r="12" fill="var(--green)" stroke="none"/>
56
+ </g>
57
+ <text x="610" y="400" font-size="28">成本</text>
58
+ <text x="28" y="74" font-size="28">质量</text>
59
+ </svg>
60
+ </div>
61
+ <aside class="notes">先解释坐标,再指出推荐位置。</aside>
62
+ </section>
63
+
64
+ <section class="slide" data-title="对比">
65
+ <h2 class="slide-title">把几个关键数字放在一起</h2>
66
+ <table class="hand-table">
67
+ <thead><tr><th>方案</th><th>体积</th><th>质量</th><th>适合谁</th></tr></thead>
68
+ <tbody>
69
+ <tr><td>方案 A</td><td>较大</td><td>最高</td><td>追求上限</td></tr>
70
+ <tr><td><span class="mark-green">方案 B</span></td><td>适中</td><td>接近上限</td><td>大多数人</td></tr>
71
+ <tr><td>方案 C</td><td>最小</td><td>有损失</td><td>资源有限</td></tr>
72
+ </tbody>
73
+ </table>
74
+ <div class="result-box" style="margin-top:52px">低于某个档位,质量损失会突然抬头</div>
75
+ <aside class="notes">让观众先看推荐行,再解释上下两行的取舍。</aside>
76
+ </section>
77
+
78
+ <section class="slide" data-title="总结">
79
+ <h2 class="slide-title">最后只留下一条判断</h2>
80
+ <div class="formula-summary">
81
+ <span>能力</span> + <span>成本</span> + <span>可控</span> + <span>透明</span>
82
+ </div>
83
+ <aside class="notes">把整场报告收束成一句能记住的话。</aside>
84
+ </section>
85
+
86
+ <section class="slide thanks" data-title="结束">
87
+ <h2>Thank you!</h2>
88
+ <div class="result-box">作者署名 · 日期</div>
89
+ <aside class="notes">感谢观众,进入提问。</aside>
90
+ </section>
91
+ </main>
92
+ <script src="../assets/runtime.js"></script>
93
+ </body>
94
+ </html>
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import unittest
5
+ from pathlib import Path
6
+
7
+
8
+ SKILL_DIR = Path(__file__).resolve().parents[1]
9
+ TARGET_FAMILY = "HanziPen SC"
10
+
11
+
12
+ class PreviewFontContractTests(unittest.TestCase):
13
+ def test_template_uses_one_exact_family(self) -> None:
14
+ css = (SKILL_DIR / "assets" / "template.css").read_text(encoding="utf-8")
15
+ self.assertIn(f'--hand: "{TARGET_FAMILY}";', css)
16
+ self.assertIn("--font-sans: var(--hand);", css)
17
+ self.assertIn("--font-display: var(--hand);", css)
18
+ self.assertIn("--font-mono: var(--hand);", css)
19
+ self.assertIn("--font-serif: var(--hand);", css)
20
+
21
+ def test_demo_and_reusable_css_share_the_same_family(self) -> None:
22
+ paths = (
23
+ SKILL_DIR / "assets" / "deepseek-example.css",
24
+ SKILL_DIR / "examples" / "deepseek-v4-flash" / "style.css",
25
+ )
26
+ for path in paths:
27
+ with self.subTest(path=path):
28
+ css = path.read_text(encoding="utf-8")
29
+ self.assertIn(f'--hand-font: "{TARGET_FAMILY}";', css)
30
+ self.assertIn("--font-mono: var(--hand-font);", css)
31
+ self.assertIn("--font-serif: var(--hand-font);", css)
32
+
33
+ def test_shared_assets_use_the_same_family(self) -> None:
34
+ base = (SKILL_DIR / "assets" / "base.css").read_text(encoding="utf-8")
35
+ annotations = (SKILL_DIR / "assets" / "neat-annotations.css").read_text(
36
+ encoding="utf-8"
37
+ )
38
+ runtime = (SKILL_DIR / "assets" / "runtime.js").read_text(
39
+ encoding="utf-8"
40
+ )
41
+ self.assertIn(f"--font-sans: '{TARGET_FAMILY}';", base)
42
+ self.assertIn(f"--font-serif: '{TARGET_FAMILY}';", base)
43
+ self.assertIn(f"--font-mono: '{TARGET_FAMILY}';", base)
44
+ self.assertIn(f"--ann-font: '{TARGET_FAMILY}';", annotations)
45
+ self.assertIn(f'font-family: "{TARGET_FAMILY}";', runtime)
46
+
47
+ def test_instructions_use_preview_as_the_glyph_reference(self) -> None:
48
+ skill = (SKILL_DIR / "SKILL.md").read_text(encoding="utf-8")
49
+ guide = (SKILL_DIR / "references" / "style-guide.md").read_text(
50
+ encoding="utf-8"
51
+ )
52
+ self.assertIn("assets/preview-cover.png", skill)
53
+ self.assertIn("assets/preview-cover.png", guide)
54
+ self.assertIn("不设置其他中文字体候选", skill)
55
+
56
+ def test_static_renderer_injects_and_checks_the_font_file(self) -> None:
57
+ preparer = (SKILL_DIR / "scripts" / "prepare_render_html.py").read_text(
58
+ encoding="utf-8"
59
+ )
60
+ renderer = (SKILL_DIR / "scripts" / "render.sh").read_text(encoding="utf-8")
61
+ self.assertIn("@font-face", preparer)
62
+ self.assertIn('DEMO_FONT_POSTSCRIPT = "HanziPenSC-W3"', preparer)
63
+ self.assertIn("validate_font_file(font)", preparer)
64
+ self.assertIn("dataset.wanghongFontReady", preparer)
65
+ self.assertIn("prepare_render_html.py", renderer)
66
+ self.assertIn("--dump-dom", renderer)
67
+ self.assertIn('data-wanghong-font-ready="yes"', renderer)
68
+ self.assertIn("com_apple_MobileAsset_Font8", renderer)
69
+ self.assertIn("re.findall", renderer)
70
+
71
+
72
+ if __name__ == "__main__":
73
+ unittest.main()
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MultiChat contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,186 @@
1
+ # MultiChat
2
+
3
+ [![CI](https://github.com/Leterhong/MultiChat/actions/workflows/ci.yml/badge.svg)](https://github.com/Leterhong/MultiChat/actions/workflows/ci.yml)
4
+ [![Node.js 22.13+](https://img.shields.io/badge/Node.js-22.13%2B-339933?logo=nodedotjs&logoColor=white)](https://nodejs.org/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-287254.svg)](./LICENSE)
6
+
7
+ > 把模型、运行配置、工具与项目上下文,放进同一个本地工作台。
8
+
9
+ MultiChat 是一个开源、本地优先的多模型工作台。你可以在浏览器中连接不同模型,保存可复用的运行配置,并按任务组合 Skill、内置工具、MCP 与 Plugin。
10
+
11
+ 每类能力保留自己的格式、来源和安全边界,再由运行配置按需组合;对话、项目文件、扩展状态与运行记录都集中在一个工作区中。
12
+
13
+ ## 当前阶段
14
+
15
+ > [!IMPORTANT]
16
+ > MultiChat 正在持续迭代,数据结构、扩展接口与 CLI 仍可能变化。当前版本适合本机和可信项目环境,不建议直接暴露到公网。
17
+
18
+ 对话、Agent、Skill、MCP、Plugin 导入、本地运行、用量分析、断点续跑与执行审计链路已经可用。更多隔离能力、协作能力和正式 npm 发行仍在继续完善。
19
+
20
+ ## 快速开始
21
+
22
+ 安装 [Node.js](https://nodejs.org/) 22.13 或更高版本,然后运行:
23
+
24
+ ```bash
25
+ npx --yes github:Leterhong/MultiChat web
26
+ ```
27
+
28
+ 命令会从 GitHub 获取当前版本,在本机启动 Web UI,并尝试自动打开 `http://127.0.0.1:3000`。当前目录是默认工作区,运行数据保存在 `~/.multichat/data`;按 `Ctrl+C` 即可停止服务。
29
+
30
+ 常用参数:
31
+
32
+ ```bash
33
+ # 更换端口并禁止自动打开浏览器
34
+ npx --yes github:Leterhong/MultiChat web --port 3100 --no-open
35
+
36
+ # 指定工作区与数据目录
37
+ npx --yes github:Leterhong/MultiChat web \
38
+ --workspace ./my-agent-project \
39
+ --data-dir ./.multichat-data
40
+ ```
41
+
42
+ CLI 还提供 `doctor`、`init`、`pull` 与 `deploy`:
43
+
44
+ ```bash
45
+ npx --yes github:Leterhong/MultiChat --help
46
+ npx --yes github:Leterhong/MultiChat doctor
47
+ ```
48
+
49
+ `doctor` 会实际探测 `node:sqlite` 与 `DatabaseSync`、生产构建、目录权限和监听端口。若 SQLite 在受限运行环境初始化失败,服务会明确告警并自动降级到 JSON 存储;也可以主动设置 `MULTICHAT_STORE=json`。
50
+
51
+ > [!NOTE]
52
+ > 当前 Node.js(含 22.x/24.x)中 `node:sqlite` 仍是实验特性,Node 官方尚未宣布稳定。MultiChat 已把 SQLite 作为默认存储,但请把 `multichat.sqlite3` 视为「可随时由 MultiChat 迁移的内部格式」,不要直接读写或长期依赖其表结构;需要长期备份时使用项目快照功能,或设置 `MULTICHAT_STORE=json` 使用透明可读的 JSON 存储。
53
+
54
+ ## 核心体验
55
+
56
+ - **统一管理**:在一个界面中组织模型、对话、工作区和项目文件;
57
+ - **并行模型实验**:让 2–4 个模型在相同参数、项目文件和记忆上下文下并行回答;实验保存在当前设备,可随时恢复,并把选定结果直接采用到正式对话;
58
+ - **运行配置**:自由搭配模型、系统指令、Skill、内置工具与 MCP server,并只向本轮注入实际选中的能力;
59
+ - **上下文检查**:开始运行前核对模型连接、项目上下文与能力组合,估算文件上下文 Token,运行后直接查看最近一次步骤、工具调用与 Token;
60
+ - **快速命令**:`Ctrl+K` 搜索操作,`Ctrl+I` 检查上下文,`Ctrl+M` 发起模型实验;
61
+ - **安全扩展**:导入 Skill、MCP 配置和 Plugin,启用前检查冲突、风险与变更;
62
+ - **每日用量中心**:按 7 天、30 天或全部时间查看输入/输出 Token、趋势、模型占比、活跃热力图与提供方健康;
63
+ - **执行与复盘**:保留模型步骤、工具调用、审批、错误与 Token 构成,并复查每次请求实际携带的上下文;
64
+ - **可恢复运行**:高风险工具审批与迭代上限都会形成持久化检查点,关闭响应或重启服务后仍可继续;
65
+ - **项目知识与记忆**:搜索项目文件并保留文件、行号引用,只将用户确认的长期事实和偏好写入项目记忆;
66
+ - **能力护照**:集中展示 Skill、MCP、Plugin 与内置工具的来源、版本、权限、信任状态、风险和结构问题;
67
+ - **项目快照**:保存项目设置、文件、记忆和关联 Agent,恢复前自动备份,便于安全试验和回滚;
68
+ - **本地安全账本**:Provider 密钥加密落盘且不会返回浏览器,用量记录只保存统计元数据,不保存提示词正文;
69
+ - **适配不同设备**:工作台面向桌面与移动屏幕响应式设计,并支持深色主题、键盘操作和无障碍焦点管理。
70
+
71
+ ## 工程结构
72
+
73
+ MultiChat 把宿主运行时、Web 客户端和扩展边界分开维护:
74
+
75
+ ```text
76
+ backend/ TypeScript API、运行时、SQLite 存储与扩展管理
77
+ backend/dist/ 预编译的 Node.js 生产运行文件
78
+ frontend/src/core/ 客户端状态、API、主题与基础工具
79
+ frontend/src/app/ React / TSX 应用外壳与语义化导航
80
+ frontend/src/components/ React 消息流、输入区与安全 Markdown 组件
81
+ frontend/src/design/ CSS 设计 Token 与主题变量
82
+ frontend/src/modules/ 工作台、对话、设置、导入与观察界面
83
+ bin/ multichat 命令入口
84
+ .agents/ 项目 Skills 与插件资源
85
+ ```
86
+
87
+ 一次请求会被记录为 Run;Run 包含一个或多个 Turn,Turn 再由模型请求、工具调用和审批 Step 组成。运行日志和检查点是复查、继续、分支和诊断的统一事实来源。业务数据默认保存在单文件 SQLite 数据库中,首次启动会自动读取已有 JSON 数据;设置 `MULTICHAT_STORE=json` 可继续使用旧存储方式。
88
+
89
+ ## 扩展方式
90
+
91
+ | 类型 | 用途 | 入口 |
92
+ |---|---|---|
93
+ | Skill | 封装工作流、知识、脚本与资源 | `SKILL.md` |
94
+ | MCP | 连接独立的标准工具服务 | STDIO / Streamable HTTP |
95
+ | Plugin | 分发一组完整扩展资源 | `.codex-plugin/plugin.json` |
96
+
97
+ Skill 支持单文件、完整目录和 ZIP,并会原样保留但不执行 `x-*` 厂商扩展元数据;MCP 支持界面配置与 JSON;Plugin 支持符合当前兼容范围的 ZIP 和完整目录。三类扩展分别管理来源、作用域与启用状态,导入时不会自动执行。
98
+
99
+ 导入分为“预检”和“安装”两个阶段。MultiChat 会检查路径穿越、压缩包限制、格式错误、同名冲突、组件越界与高风险凭据位置,并对 Plugin MCP 中的字面量凭据执行更严格限制,再通过暂存、原子替换与回滚机制完成安装。新导入的扩展默认停用,MCP 同时默认为未受信任。
100
+
101
+ Skill 中的脚本只会随包保存并列出路径,不会自动执行。当前 Plugin Runtime 会加载包内的 **Skills 与 MCP servers**;其他 JS/TS、Hooks、Commands、Apps 等资源可以随包保存,但不会未经隔离和授权直接注入 MultiChat 主进程。
102
+
103
+ ## Docker / Linux 服务器
104
+
105
+ MultiChat 是纯 Node.js 应用,Linux(x64/arm64)开箱即跑。两种常驻方式:
106
+
107
+ **Docker(推荐,数据与工作区分离挂载):**
108
+
109
+ ```bash
110
+ # 使用官方镜像(推送到 main / 打 tag 时由 CI 自动构建发布)
111
+ docker run -d --name multichat -p 3000:3000 -v multichat-data:/data -v "$PWD:/workspace" ghcr.io/leterhong/multichat:latest
112
+
113
+ # 本地构建
114
+ docker build -t multichat .
115
+ docker run -d -p 3000:3000 -v multichat-data:/data -v "$PWD:/workspace" multichat
116
+ ```
117
+
118
+ 容器内 `/data` 保存运行数据(SQLite、加密密钥账本),`/workspace` 是 Agent 工作区(项目文件、.agents 扩展)。镜像以非 root 用户运行,默认监听 `0.0.0.0:3000`。
119
+
120
+ **systemd 常驻(不用容器时):**
121
+
122
+ ```ini
123
+ # /etc/systemd/system/multichat.service
124
+ [Unit]
125
+ Description=MultiChat local agent workspace
126
+ After=network-online.target
127
+
128
+ [Service]
129
+ Type=simple
130
+ User=%i
131
+ WorkingDirectory=/home/%i/multichat-workspace
132
+ ExecStart=/usr/bin/env node /home/%i/multichat/bin/multichat.mjs web --host 127.0.0.1 --port 3000
133
+ Restart=on-failure
134
+
135
+ [Install]
136
+ WantedBy=default.target
137
+ ```
138
+
139
+ > [!IMPORTANT]
140
+ > 把服务暴露到局域网或公网前,务必设置 `MULTICHAT_API_TOKEN`(保护 `/api/*` 与 `/v1/*`,包括 OpenAI 兼容透传端点)或 `MULTICHAT_BASIC_AUTH`;`/v1/chat/completions` 会使用你配置的模型密钥,无鉴权暴露等于把密钥借给别人。
141
+
142
+ ## 从源码运行
143
+
144
+ ```bash
145
+ git clone https://github.com/Leterhong/MultiChat.git
146
+ cd MultiChat
147
+
148
+ npm install
149
+ npm --prefix backend install
150
+ npm --prefix frontend install
151
+ npm run build
152
+ npm start
153
+ ```
154
+
155
+ 前端使用 React + TSX 数据驱动消息流、输入区、模型实验、Plugin 与 MCP 管理界面,长对话由虚拟列表渲染;Markdown 经过白名单消毒后再显示。静态 HTML 只保留浏览器挂载节点,Zustand 是业务数据与界面编排状态的统一来源,CSS 设计 Token 统一主题。后端 TypeScript 会先编译为 `backend/dist`,生产环境直接由 Node.js 运行,不依赖启动时转译。提交变更前可以运行:
156
+
157
+ ```bash
158
+ npm --prefix frontend run typecheck
159
+ npm --prefix frontend run lint
160
+ npm --prefix frontend run lint:css
161
+ npm --prefix frontend test
162
+ npm --prefix backend run typecheck
163
+ npm --prefix backend test
164
+ npm run build
165
+ npm test
166
+ ```
167
+
168
+ ## 安全边界
169
+
170
+ - 本地优先不等于所有推理都在本地完成;云端模型和远程 MCP 仍会收到相应请求;
171
+ - STDIO MCP 以当前用户权限运行本机进程,不是沙箱;
172
+ - HTTP MCP 默认限制 localhost、内网和 link-local 地址,只有显式允许后才会连接;
173
+ - 凭据应通过环境变量引用,不要写进扩展包、配置、示例或 Git 提交;
174
+ - 服务默认只监听 `127.0.0.1`,对外提供访问时需要可信网络或额外认证;
175
+ - 可通过 `MULTICHAT_API_TOKEN`(兼容 `MC_API_TOKEN`)为 `/api/*` 与 `/v1/*` 启用 Bearer Token,或通过 `MULTICHAT_BASIC_AUTH=user:password` 启用 Basic Auth;
176
+ - STDIO MCP 子进程默认只继承启动所需的最小系统环境,额外变量必须在 MCP 配置中显式声明。
177
+
178
+ ## 参与项目
179
+
180
+ 欢迎通过 [Issues](https://github.com/Leterhong/MultiChat/issues) 提交问题与建议,也欢迎发起 [Pull Requests](https://github.com/Leterhong/MultiChat/pulls)。
181
+
182
+ 涉及本机命令、网络访问、扩展格式或默认信任策略的变更,请在提交说明中单独列出安全影响。
183
+
184
+ ## 开源协议
185
+
186
+ MultiChat 使用 [MIT License](./LICENSE) 开源。
@@ -0,0 +1,77 @@
1
+ # LLM Provider Adapters
2
+
3
+ 适配器模式实现,支持国内外主流大模型原生接口。
4
+
5
+ ## 架构
6
+
7
+ ```
8
+ adapters/
9
+ ├── base.js # 基础适配器接口
10
+ ├── openai.js # OpenAI 兼容适配器
11
+ ├── zhipu.js # 智谱 GLM 适配器
12
+ ├── moonshot.js # 月之暗面 Kimi 适配器
13
+ ├── qwen.js # 阿里通义千问适配器
14
+ ├── wenxin.js # 百度文心一言适配器
15
+ └── index.js # 适配器工厂
16
+ ```
17
+
18
+ ## 支持的厂商
19
+
20
+ ### 1. OpenAI 兼容 (openai)
21
+ - 适用于所有 OpenAI 兼容接口
22
+ - 需要配置: Base URL, API Key
23
+
24
+ ### 2. 智谱 GLM (zhipu)
25
+ - 原生智谱 API 支持
26
+ - 自动处理 JWT Token 生成
27
+ - API Key 格式: `apiKey.secret`
28
+ - 默认端点: `https://open.bigmodel.cn/api/paas/v4/chat/completions`
29
+
30
+ ### 3. 月之暗面 Kimi (moonshot)
31
+ - 原生 Kimi API 支持
32
+ - 支持超长上下文
33
+ - 默认端点: `https://api.moonshot.cn/v1/chat/completions`
34
+
35
+ ### 4. 阿里通义千问 (qwen)
36
+ - DashScope 协议支持
37
+ - 自动处理消息格式转换
38
+ - 默认端点: `https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation`
39
+
40
+ ### 5. 百度文心一言 (wenxin)
41
+ - 自动处理 OAuth 2.0 认证
42
+ - API Key 格式: `apiKey:secretKey`
43
+ - 支持 ERNIE 系列模型
44
+
45
+ ## 添加新适配器
46
+
47
+ 1. 创建新的适配器类继承 `BaseAdapter`
48
+ 2. 实现必需的方法:
49
+ - `transformMessages()` - 转换消息格式
50
+ - `buildRequestBody()` - 构建请求体
51
+ - `getHeaders()` - 获取请求头
52
+ - `getEndpoint()` - 获取 API 端点
53
+ - `transformSSEChunk()` - 转换流式响应
54
+ - `transformResponse()` - 转换非流式响应
55
+ 3. 可选实现 `prepare()` - 预处理(如获取 access token)
56
+ 4. 在 `index.js` 中注册新适配器
57
+
58
+ ## 示例
59
+
60
+ ```javascript
61
+ const { createAdapter } = require('./adapters');
62
+
63
+ // 创建智谱适配器
64
+ const adapter = createAdapter({
65
+ apiType: 'zhipu',
66
+ apiKey: 'your-api-key.your-secret',
67
+ model: 'glm-4'
68
+ });
69
+
70
+ // 准备请求
71
+ await adapter.prepare();
72
+
73
+ // 构建请求
74
+ const endpoint = adapter.getEndpoint();
75
+ const headers = adapter.getHeaders();
76
+ const body = adapter.buildRequestBody('glm-4', messages, true);
77
+ ```
@@ -0,0 +1,115 @@
1
+ const BaseAdapter = require('./base');
2
+ import type { ChatMessage, ChatRequestOptions, JsonRecord } from '../types';
3
+
4
+ function asText(content: unknown) {
5
+ if (typeof content === 'string') return content;
6
+ if (!Array.isArray(content)) return '';
7
+ return content.filter(x => x && x.type === 'text').map(x => x.text || '').join('');
8
+ }
9
+
10
+ class AnthropicAdapter extends BaseAdapter {
11
+ transformMessages(messages: ChatMessage[]) {
12
+ const out: any[] = [];
13
+ for (const message of messages || []) {
14
+ if (message.role === 'system') continue;
15
+ if (message.role === 'tool') {
16
+ out.push({ role: 'user', content: [{ type: 'tool_result', tool_use_id: message.tool_call_id, content: String(message.content || '') }] });
17
+ continue;
18
+ }
19
+ if (message.role === 'assistant' && Array.isArray(message.tool_calls)) {
20
+ const content: any[] = [];
21
+ if (message.content) content.push({ type: 'text', text: String(message.content) });
22
+ for (const call of message.tool_calls) {
23
+ content.push({ type: 'tool_use', id: call.id, name: call.function?.name, input: this.parseArguments(call.function?.arguments) });
24
+ }
25
+ out.push({ role: 'assistant', content });
26
+ continue;
27
+ }
28
+ out.push({ role: message.role === 'assistant' ? 'assistant' : 'user', content: message.content });
29
+ }
30
+ return out;
31
+ }
32
+
33
+ parseArguments(value: string | undefined) {
34
+ try { return JSON.parse(value || '{}'); } catch { return {}; }
35
+ }
36
+
37
+ buildRequestBody(model: string, messages: ChatMessage[], stream: boolean, options: ChatRequestOptions = {}) {
38
+ const system = (messages || []).filter(m => m.role === 'system').map(m => asText(m.content)).filter(Boolean).join('\n\n');
39
+ const body: JsonRecord = {
40
+ model,
41
+ messages: this.transformMessages(messages),
42
+ max_tokens: options.max_tokens || 4096,
43
+ stream,
44
+ };
45
+ if (system) body.system = system;
46
+ for (const key of ['temperature', 'top_p', 'stop_sequences']) if (options[key] !== undefined) body[key] = options[key];
47
+ if (Array.isArray(options.tools)) {
48
+ body.tools = options.tools.map(tool => ({
49
+ name: tool.function?.name,
50
+ description: tool.function?.description || '',
51
+ input_schema: tool.function?.parameters || { type: 'object', properties: {}, required: [] },
52
+ }));
53
+ }
54
+ if (options.tool_choice !== undefined) {
55
+ const choice = options.tool_choice;
56
+ body.tool_choice = choice === 'auto' ? { type: 'auto' } : choice === 'required' ? { type: 'any' } : choice?.function?.name ? { type: 'tool', name: choice.function.name } : undefined;
57
+ }
58
+ return body;
59
+ }
60
+
61
+ getHeaders() {
62
+ return {
63
+ 'Content-Type': 'application/json',
64
+ 'x-api-key': this.config.apiKey || '',
65
+ 'anthropic-version': '2023-06-01',
66
+ };
67
+ }
68
+
69
+ getEndpoint() {
70
+ const base = (this.config.baseUrl || 'https://api.anthropic.com/v1').replace(/\/+$/, '');
71
+ return `${base}${/\/v1$/i.test(base) ? '' : '/v1'}/messages`;
72
+ }
73
+
74
+ transformSSEChunk(line: string) {
75
+ if (!line.startsWith('data:')) return null;
76
+ const raw = line.slice(5).trim();
77
+ if (!raw || raw === '[DONE]') return null;
78
+ let event;
79
+ try { event = JSON.parse(raw); } catch { return null; }
80
+ if (event.type === 'message_start') {
81
+ return { id: event.message?.id, model: event.message?.model, choices: [], usage: event.message?.usage };
82
+ }
83
+ if (event.type === 'content_block_start') {
84
+ const block = event.content_block || {};
85
+ if (block.type === 'tool_use') return { choices: [{ delta: { tool_calls: [{ index: event.index || 0, id: block.id, type: 'function', function: { name: block.name, arguments: '' } }] } }] };
86
+ return null;
87
+ }
88
+ if (event.type === 'content_block_delta') {
89
+ const delta = event.delta || {};
90
+ if (delta.type === 'text_delta' && delta.text) return { choices: [{ delta: { content: delta.text } }] };
91
+ if (delta.type === 'input_json_delta' && delta.partial_json) return { choices: [{ delta: { tool_calls: [{ index: event.index || 0, function: { arguments: delta.partial_json } }] } }] };
92
+ return null;
93
+ }
94
+ if (event.type === 'message_delta') {
95
+ const stop = event.delta?.stop_reason;
96
+ const finish = stop === 'tool_use' ? 'tool_calls' : (stop || 'stop');
97
+ return { choices: [{ delta: {}, finish_reason: finish }], usage: event.usage ? { completion_tokens: event.usage.output_tokens } : undefined };
98
+ }
99
+ return null;
100
+ }
101
+
102
+ transformResponse(data: JsonRecord) {
103
+ const content = (data.content || []).filter(x => x.type === 'text').map(x => x.text || '').join('');
104
+ return {
105
+ id: data.id,
106
+ object: 'chat.completion',
107
+ created: Math.floor(Date.now() / 1000),
108
+ model: data.model,
109
+ choices: [{ index: 0, message: { role: 'assistant', content }, finish_reason: data.stop_reason === 'tool_use' ? 'tool_calls' : 'stop' }],
110
+ usage: data.usage ? { prompt_tokens: data.usage.input_tokens, completion_tokens: data.usage.output_tokens, total_tokens: (data.usage.input_tokens || 0) + (data.usage.output_tokens || 0) } : undefined,
111
+ };
112
+ }
113
+ }
114
+
115
+ module.exports = AnthropicAdapter;
@@ -0,0 +1,47 @@
1
+ // Base adapter interface for all LLM providers
2
+ import type { AdapterConfig, ChatMessage, ChatRequestOptions, JsonRecord } from '../types';
3
+
4
+ class BaseAdapter {
5
+ config: AdapterConfig;
6
+
7
+ constructor(config: AdapterConfig) {
8
+ this.config = config;
9
+ }
10
+
11
+ // Convert internal message format to provider-specific format
12
+ transformMessages(_messages: ChatMessage[]): any[] {
13
+ throw new Error('transformMessages must be implemented');
14
+ }
15
+
16
+ // Build request body for the provider
17
+ buildRequestBody(_model: string, _messages: ChatMessage[], _stream: boolean, _options: ChatRequestOptions = {}): JsonRecord {
18
+ throw new Error('buildRequestBody must be implemented');
19
+ }
20
+
21
+ // Get request headers
22
+ getHeaders() {
23
+ throw new Error('getHeaders must be implemented');
24
+ }
25
+
26
+ // Get API endpoint URL
27
+ getEndpoint() {
28
+ throw new Error('getEndpoint must be implemented');
29
+ }
30
+
31
+ // Transform SSE chunk to standard format
32
+ transformSSEChunk(_line: string): JsonRecord | null {
33
+ throw new Error('transformSSEChunk must be implemented');
34
+ }
35
+
36
+ // Transform non-streaming response to standard format
37
+ transformResponse(_data: JsonRecord): JsonRecord {
38
+ throw new Error('transformResponse must be implemented');
39
+ }
40
+
41
+ // Prepare before making request (e.g., get access token)
42
+ async prepare() {
43
+ // Optional: override if needed
44
+ }
45
+ }
46
+
47
+ module.exports = BaseAdapter;
@@ -0,0 +1,41 @@
1
+ const OpenAIAdapter = require('./openai');
2
+ const ZhipuAdapter = require('./zhipu');
3
+ const MoonshotAdapter = require('./moonshot');
4
+ const QwenAdapter = require('./qwen');
5
+ const WenxinAdapter = require('./wenxin');
6
+ const OllamaAdapter = require('./ollama');
7
+ const LMStudioAdapter = require('./lmstudio');
8
+ const AnthropicAdapter = require('./anthropic');
9
+
10
+ const ADAPTER_MAP = {
11
+ 'openai': OpenAIAdapter,
12
+ 'zhipu': ZhipuAdapter,
13
+ 'moonshot': MoonshotAdapter,
14
+ 'kimi': MoonshotAdapter,
15
+ 'qwen': QwenAdapter,
16
+ 'wenxin': WenxinAdapter,
17
+ 'ernie': WenxinAdapter,
18
+ 'ollama': OllamaAdapter,
19
+ 'lmstudio': LMStudioAdapter,
20
+ 'anthropic': AnthropicAdapter
21
+ };
22
+
23
+ function createAdapter(provider) {
24
+ const adapterType = provider.apiType || 'openai';
25
+ const AdapterClass = ADAPTER_MAP[adapterType.toLowerCase()];
26
+
27
+ if (!AdapterClass) {
28
+ throw new Error(`Unknown adapter type: ${adapterType}`);
29
+ }
30
+
31
+ return new AdapterClass({
32
+ apiKey: provider.apiKey,
33
+ baseUrl: provider.baseUrl,
34
+ model: provider.model
35
+ });
36
+ }
37
+
38
+ module.exports = {
39
+ createAdapter,
40
+ ADAPTER_MAP
41
+ };
@@ -0,0 +1,7 @@
1
+ const OpenAICompatibleAdapter = require('./openai-compatible');
2
+
3
+ class LMStudioAdapter extends OpenAICompatibleAdapter {
4
+ constructor(config) { super(config, { defaultBaseUrl: 'http://localhost:1234', auth: 'optional' }); }
5
+ }
6
+
7
+ module.exports = LMStudioAdapter;