0xkobold 0.7.2 → 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 (252) hide show
  1. package/HEARTBEAT.md +239 -0
  2. package/IDENTITY.md +12 -0
  3. package/README.md +138 -4
  4. package/SOUL.md +21 -0
  5. package/dist/package.json +10 -5
  6. package/dist/src/agent/bootstrap-loader.js +295 -66
  7. package/dist/src/agent/bootstrap-loader.js.map +1 -1
  8. package/dist/src/agent/context-pruning.js +10 -5
  9. package/dist/src/agent/context-pruning.js.map +1 -1
  10. package/dist/src/agent/embedded-runner.js +29 -15
  11. package/dist/src/agent/embedded-runner.js.map +1 -1
  12. package/dist/src/agent/index.js +5 -2
  13. package/dist/src/agent/index.js.map +1 -1
  14. package/dist/src/agent/system-prompt.js +167 -20
  15. package/dist/src/agent/system-prompt.js.map +1 -1
  16. package/dist/src/agent/tools/spawn-agent.js +72 -5
  17. package/dist/src/agent/tools/spawn-agent.js.map +1 -1
  18. package/dist/src/channels/slack/webhook.js +2 -2
  19. package/dist/src/channels/slack/webhook.js.map +1 -1
  20. package/dist/src/channels/telegram/bot.js +4 -4
  21. package/dist/src/channels/telegram/bot.js.map +1 -1
  22. package/dist/src/channels/whatsapp/integration.js +4 -4
  23. package/dist/src/channels/whatsapp/integration.js.map +1 -1
  24. package/dist/src/cli/commands/gateway.js +9 -10
  25. package/dist/src/cli/commands/gateway.js.map +1 -1
  26. package/dist/src/cli/commands/init.js +90 -0
  27. package/dist/src/cli/commands/init.js.map +1 -1
  28. package/dist/src/cli/commands/setup.js +53 -0
  29. package/dist/src/cli/commands/setup.js.map +1 -1
  30. package/dist/src/cli/index.js +0 -0
  31. package/dist/src/config/unified-config.js +5 -0
  32. package/dist/src/config/unified-config.js.map +1 -1
  33. package/dist/src/cron/index.js +2 -0
  34. package/dist/src/cron/index.js.map +1 -1
  35. package/dist/src/cron/nl-parser.js +522 -0
  36. package/dist/src/cron/nl-parser.js.map +1 -0
  37. package/dist/src/cron/runner.js +6 -31
  38. package/dist/src/cron/runner.js.map +1 -1
  39. package/dist/src/event-bus/index.js.map +1 -1
  40. package/dist/src/extensions/core/agent-orchestrator-extension.js +200 -148
  41. package/dist/src/extensions/core/agent-orchestrator-extension.js.map +1 -1
  42. package/dist/src/extensions/core/diagnostics-extension.js +93 -56
  43. package/dist/src/extensions/core/diagnostics-extension.js.map +1 -1
  44. package/dist/src/extensions/core/draconic-safety-extension.js +256 -3
  45. package/dist/src/extensions/core/draconic-safety-extension.js.map +1 -1
  46. package/dist/src/extensions/core/heartbeat-extension.js +416 -150
  47. package/dist/src/extensions/core/heartbeat-extension.js.map +1 -1
  48. package/dist/src/extensions/core/{generative-agents-extension.js → learning-extension.js} +90 -128
  49. package/dist/src/extensions/core/learning-extension.js.map +1 -0
  50. package/dist/src/extensions/core/perennial-memory-extension.js +884 -87
  51. package/dist/src/extensions/core/perennial-memory-extension.js.map +1 -1
  52. package/dist/src/extensions/core/routed-ollama-extension.js +345 -0
  53. package/dist/src/extensions/core/routed-ollama-extension.js.map +1 -0
  54. package/dist/src/extensions/core/task-manager-extension.js +25 -2
  55. package/dist/src/extensions/core/task-manager-extension.js.map +1 -1
  56. package/dist/src/extensions/core/websearch-enhanced-extension.js +81 -23
  57. package/dist/src/extensions/core/websearch-enhanced-extension.js.map +1 -1
  58. package/dist/src/extensions/core/workspace-footer-extension.js +40 -63
  59. package/dist/src/extensions/core/workspace-footer-extension.js.map +1 -1
  60. package/dist/src/extensions/loader.js +5 -1
  61. package/dist/src/extensions/loader.js.map +1 -1
  62. package/dist/src/gateway/gateway-server.js +74 -3
  63. package/dist/src/gateway/gateway-server.js.map +1 -1
  64. package/dist/src/gateway/index.js +2 -2
  65. package/dist/src/gateway/index.js.map +1 -1
  66. package/dist/src/gateway/methods/agent.js +1 -1
  67. package/dist/src/gateway/methods/agent.js.map +1 -1
  68. package/dist/src/gateway/methods/index.js +4 -0
  69. package/dist/src/gateway/methods/index.js.map +1 -1
  70. package/dist/src/gateway/methods/node.js +261 -0
  71. package/dist/src/gateway/methods/node.js.map +1 -0
  72. package/dist/src/gateway/queue-modes.js +356 -0
  73. package/dist/src/gateway/queue-modes.js.map +1 -0
  74. package/dist/src/index.js +47 -6
  75. package/dist/src/index.js.map +1 -1
  76. package/dist/src/llm/community-analytics.js +569 -0
  77. package/dist/src/llm/community-analytics.js.map +1 -0
  78. package/dist/src/llm/index.js +28 -4
  79. package/dist/src/llm/index.js.map +1 -1
  80. package/dist/src/llm/model-discovery.js +335 -0
  81. package/dist/src/llm/model-discovery.js.map +1 -0
  82. package/dist/src/llm/model-popularity.js +566 -0
  83. package/dist/src/llm/model-popularity.js.map +1 -0
  84. package/dist/src/llm/model-scoring-db.js +553 -0
  85. package/dist/src/llm/model-scoring-db.js.map +1 -0
  86. package/dist/src/llm/multi-provider.js +258 -0
  87. package/dist/src/llm/multi-provider.js.map +1 -0
  88. package/dist/src/llm/ollama.js +133 -187
  89. package/dist/src/llm/ollama.js.map +1 -1
  90. package/dist/src/llm/router-commands.js +773 -0
  91. package/dist/src/llm/router-commands.js.map +1 -0
  92. package/dist/src/llm/router-core.js +600 -0
  93. package/dist/src/llm/router-core.js.map +1 -0
  94. package/dist/src/memory/checkpoint-manager.js +278 -0
  95. package/dist/src/memory/checkpoint-manager.js.map +1 -0
  96. package/dist/src/memory/conflict-detector.js +279 -0
  97. package/dist/src/memory/conflict-detector.js.map +1 -0
  98. package/dist/src/memory/context-graph.js +360 -0
  99. package/dist/src/memory/context-graph.js.map +1 -0
  100. package/dist/src/memory/dialectic/benchmark-cli.js +200 -0
  101. package/dist/src/memory/dialectic/benchmark-cli.js.map +1 -0
  102. package/dist/src/memory/dialectic/debug-reasoning.js +37 -0
  103. package/dist/src/memory/dialectic/debug-reasoning.js.map +1 -0
  104. package/dist/src/memory/dialectic/index.js +135 -0
  105. package/dist/src/memory/dialectic/index.js.map +1 -0
  106. package/dist/src/memory/dialectic/nudges.js +380 -0
  107. package/dist/src/memory/dialectic/nudges.js.map +1 -0
  108. package/dist/src/memory/dialectic/reasoning-engine.js +607 -0
  109. package/dist/src/memory/dialectic/reasoning-engine.js.map +1 -0
  110. package/dist/src/memory/dialectic/reasoning.js +390 -0
  111. package/dist/src/memory/dialectic/reasoning.js.map +1 -0
  112. package/dist/src/memory/dialectic/skill-creation.js +481 -0
  113. package/dist/src/memory/dialectic/skill-creation.js.map +1 -0
  114. package/dist/src/memory/dialectic/store.js +663 -0
  115. package/dist/src/memory/dialectic/store.js.map +1 -0
  116. package/dist/src/memory/dialectic/types.js +11 -0
  117. package/dist/src/memory/dialectic/types.js.map +1 -0
  118. package/dist/src/memory/index.js +24 -2
  119. package/dist/src/memory/index.js.map +1 -1
  120. package/dist/src/memory/memory-decay.js +350 -0
  121. package/dist/src/memory/memory-decay.js.map +1 -0
  122. package/dist/src/memory/memory-integration.js +5 -5
  123. package/dist/src/memory/memory-integration.js.map +1 -1
  124. package/dist/src/memory/migrate-memory-stream.js +97 -0
  125. package/dist/src/memory/migrate-memory-stream.js.map +1 -0
  126. package/dist/src/memory/session-memory-bridge.js +49 -5
  127. package/dist/src/memory/session-memory-bridge.js.map +1 -1
  128. package/dist/src/memory/session-store.js +123 -0
  129. package/dist/src/memory/session-store.js.map +1 -1
  130. package/dist/src/memory/smart-write-rules.js +164 -0
  131. package/dist/src/memory/smart-write-rules.js.map +1 -0
  132. package/dist/src/memory/tiered-memory.js +436 -0
  133. package/dist/src/memory/tiered-memory.js.map +1 -0
  134. package/dist/src/memory/types.js +6 -0
  135. package/dist/src/memory/types.js.map +1 -0
  136. package/dist/src/memory/verify-migration.js +99 -0
  137. package/dist/src/memory/verify-migration.js.map +1 -0
  138. package/dist/src/pi-config.js +11 -9
  139. package/dist/src/pi-config.js.map +1 -1
  140. package/dist/src/skills/conditional-skills.js +464 -0
  141. package/dist/src/skills/conditional-skills.js.map +1 -0
  142. package/dist/src/skills/index.js +5 -0
  143. package/dist/src/skills/index.js.map +1 -1
  144. package/dist/src/skills/loader.js +56 -0
  145. package/dist/src/skills/loader.js.map +1 -1
  146. package/dist/src/skills/skill-manage.js +417 -0
  147. package/dist/src/skills/skill-manage.js.map +1 -0
  148. package/dist/src/tui/commands/orchestration-commands.js +62 -0
  149. package/dist/src/tui/commands/orchestration-commands.js.map +1 -1
  150. package/package.json +10 -5
  151. package/skills/model-router-test.ts +65 -0
  152. package/dist/src/extensions/core/auto-security-scan-extension.js +0 -41
  153. package/dist/src/extensions/core/auto-security-scan-extension.js.map +0 -1
  154. package/dist/src/extensions/core/cloudflare-browser-extension.js +0 -389
  155. package/dist/src/extensions/core/cloudflare-browser-extension.js.map +0 -1
  156. package/dist/src/extensions/core/compaction-safeguard.js +0 -223
  157. package/dist/src/extensions/core/compaction-safeguard.js.map +0 -1
  158. package/dist/src/extensions/core/generative-agents-extension.js.map +0 -1
  159. package/dist/src/extensions/core/obsidian-bridge-extension.js +0 -488
  160. package/dist/src/extensions/core/obsidian-bridge-extension.js.map +0 -1
  161. package/dist/src/llm/router.js +0 -145
  162. package/dist/src/llm/router.js.map +0 -1
  163. package/skills/kobold-scan-skill/node_modules/.package-lock.json +0 -172
  164. package/skills/kobold-scan-skill/node_modules/ansi-styles/index.d.ts +0 -345
  165. package/skills/kobold-scan-skill/node_modules/ansi-styles/index.js +0 -163
  166. package/skills/kobold-scan-skill/node_modules/ansi-styles/license +0 -9
  167. package/skills/kobold-scan-skill/node_modules/ansi-styles/package.json +0 -56
  168. package/skills/kobold-scan-skill/node_modules/ansi-styles/readme.md +0 -152
  169. package/skills/kobold-scan-skill/node_modules/balanced-match/.github/FUNDING.yml +0 -2
  170. package/skills/kobold-scan-skill/node_modules/balanced-match/LICENSE.md +0 -21
  171. package/skills/kobold-scan-skill/node_modules/balanced-match/README.md +0 -97
  172. package/skills/kobold-scan-skill/node_modules/balanced-match/index.js +0 -62
  173. package/skills/kobold-scan-skill/node_modules/balanced-match/package.json +0 -48
  174. package/skills/kobold-scan-skill/node_modules/brace-expansion/.github/FUNDING.yml +0 -2
  175. package/skills/kobold-scan-skill/node_modules/brace-expansion/LICENSE +0 -21
  176. package/skills/kobold-scan-skill/node_modules/brace-expansion/README.md +0 -135
  177. package/skills/kobold-scan-skill/node_modules/brace-expansion/index.js +0 -203
  178. package/skills/kobold-scan-skill/node_modules/brace-expansion/package.json +0 -49
  179. package/skills/kobold-scan-skill/node_modules/chalk/index.d.ts +0 -415
  180. package/skills/kobold-scan-skill/node_modules/chalk/license +0 -9
  181. package/skills/kobold-scan-skill/node_modules/chalk/package.json +0 -68
  182. package/skills/kobold-scan-skill/node_modules/chalk/readme.md +0 -341
  183. package/skills/kobold-scan-skill/node_modules/chalk/source/index.js +0 -229
  184. package/skills/kobold-scan-skill/node_modules/chalk/source/templates.js +0 -134
  185. package/skills/kobold-scan-skill/node_modules/chalk/source/util.js +0 -39
  186. package/skills/kobold-scan-skill/node_modules/color-convert/CHANGELOG.md +0 -54
  187. package/skills/kobold-scan-skill/node_modules/color-convert/LICENSE +0 -21
  188. package/skills/kobold-scan-skill/node_modules/color-convert/README.md +0 -68
  189. package/skills/kobold-scan-skill/node_modules/color-convert/conversions.js +0 -839
  190. package/skills/kobold-scan-skill/node_modules/color-convert/index.js +0 -81
  191. package/skills/kobold-scan-skill/node_modules/color-convert/package.json +0 -48
  192. package/skills/kobold-scan-skill/node_modules/color-convert/route.js +0 -97
  193. package/skills/kobold-scan-skill/node_modules/color-name/LICENSE +0 -8
  194. package/skills/kobold-scan-skill/node_modules/color-name/README.md +0 -11
  195. package/skills/kobold-scan-skill/node_modules/color-name/index.js +0 -152
  196. package/skills/kobold-scan-skill/node_modules/color-name/package.json +0 -28
  197. package/skills/kobold-scan-skill/node_modules/commander/LICENSE +0 -22
  198. package/skills/kobold-scan-skill/node_modules/commander/Readme.md +0 -1129
  199. package/skills/kobold-scan-skill/node_modules/commander/esm.mjs +0 -16
  200. package/skills/kobold-scan-skill/node_modules/commander/index.js +0 -27
  201. package/skills/kobold-scan-skill/node_modules/commander/lib/argument.js +0 -147
  202. package/skills/kobold-scan-skill/node_modules/commander/lib/command.js +0 -2179
  203. package/skills/kobold-scan-skill/node_modules/commander/lib/error.js +0 -45
  204. package/skills/kobold-scan-skill/node_modules/commander/lib/help.js +0 -461
  205. package/skills/kobold-scan-skill/node_modules/commander/lib/option.js +0 -326
  206. package/skills/kobold-scan-skill/node_modules/commander/lib/suggestSimilar.js +0 -100
  207. package/skills/kobold-scan-skill/node_modules/commander/package-support.json +0 -16
  208. package/skills/kobold-scan-skill/node_modules/commander/package.json +0 -80
  209. package/skills/kobold-scan-skill/node_modules/commander/typings/index.d.ts +0 -891
  210. package/skills/kobold-scan-skill/node_modules/fs.realpath/LICENSE +0 -43
  211. package/skills/kobold-scan-skill/node_modules/fs.realpath/README.md +0 -33
  212. package/skills/kobold-scan-skill/node_modules/fs.realpath/index.js +0 -66
  213. package/skills/kobold-scan-skill/node_modules/fs.realpath/old.js +0 -303
  214. package/skills/kobold-scan-skill/node_modules/fs.realpath/package.json +0 -26
  215. package/skills/kobold-scan-skill/node_modules/glob/LICENSE +0 -15
  216. package/skills/kobold-scan-skill/node_modules/glob/README.md +0 -399
  217. package/skills/kobold-scan-skill/node_modules/glob/common.js +0 -244
  218. package/skills/kobold-scan-skill/node_modules/glob/glob.js +0 -790
  219. package/skills/kobold-scan-skill/node_modules/glob/package.json +0 -55
  220. package/skills/kobold-scan-skill/node_modules/glob/sync.js +0 -486
  221. package/skills/kobold-scan-skill/node_modules/has-flag/index.d.ts +0 -39
  222. package/skills/kobold-scan-skill/node_modules/has-flag/index.js +0 -8
  223. package/skills/kobold-scan-skill/node_modules/has-flag/license +0 -9
  224. package/skills/kobold-scan-skill/node_modules/has-flag/package.json +0 -46
  225. package/skills/kobold-scan-skill/node_modules/has-flag/readme.md +0 -89
  226. package/skills/kobold-scan-skill/node_modules/inflight/LICENSE +0 -15
  227. package/skills/kobold-scan-skill/node_modules/inflight/README.md +0 -37
  228. package/skills/kobold-scan-skill/node_modules/inflight/inflight.js +0 -54
  229. package/skills/kobold-scan-skill/node_modules/inflight/package.json +0 -29
  230. package/skills/kobold-scan-skill/node_modules/inherits/LICENSE +0 -16
  231. package/skills/kobold-scan-skill/node_modules/inherits/README.md +0 -42
  232. package/skills/kobold-scan-skill/node_modules/inherits/inherits.js +0 -9
  233. package/skills/kobold-scan-skill/node_modules/inherits/inherits_browser.js +0 -27
  234. package/skills/kobold-scan-skill/node_modules/inherits/package.json +0 -29
  235. package/skills/kobold-scan-skill/node_modules/minimatch/LICENSE +0 -15
  236. package/skills/kobold-scan-skill/node_modules/minimatch/README.md +0 -259
  237. package/skills/kobold-scan-skill/node_modules/minimatch/lib/path.js +0 -4
  238. package/skills/kobold-scan-skill/node_modules/minimatch/minimatch.js +0 -944
  239. package/skills/kobold-scan-skill/node_modules/minimatch/package.json +0 -35
  240. package/skills/kobold-scan-skill/node_modules/once/LICENSE +0 -15
  241. package/skills/kobold-scan-skill/node_modules/once/README.md +0 -79
  242. package/skills/kobold-scan-skill/node_modules/once/once.js +0 -42
  243. package/skills/kobold-scan-skill/node_modules/once/package.json +0 -33
  244. package/skills/kobold-scan-skill/node_modules/supports-color/browser.js +0 -5
  245. package/skills/kobold-scan-skill/node_modules/supports-color/index.js +0 -135
  246. package/skills/kobold-scan-skill/node_modules/supports-color/license +0 -9
  247. package/skills/kobold-scan-skill/node_modules/supports-color/package.json +0 -53
  248. package/skills/kobold-scan-skill/node_modules/supports-color/readme.md +0 -76
  249. package/skills/kobold-scan-skill/node_modules/wrappy/LICENSE +0 -15
  250. package/skills/kobold-scan-skill/node_modules/wrappy/README.md +0 -36
  251. package/skills/kobold-scan-skill/node_modules/wrappy/package.json +0 -29
  252. package/skills/kobold-scan-skill/node_modules/wrappy/wrappy.js +0 -33
@@ -0,0 +1,258 @@
1
+ /**
2
+ * Multi-Provider Router
3
+ *
4
+ * Extends AdaptiveModelRouter to support multiple LLM providers.
5
+ * Routes requests to the appropriate provider based on model prefix.
6
+ *
7
+ * Model prefixes:
8
+ * - ollama/* → OllamaProvider (local or cloud)
9
+ * - claude/* → AnthropicProvider
10
+ * - anthropic/* → AnthropicProvider (alias)
11
+ *
12
+ * Usage:
13
+ * const router = await getMultiProviderRouter();
14
+ * const response = await router.chat({ model: 'claude/claude-3-5-sonnet', ... });
15
+ */
16
+ import { createRouter } from './router-core';
17
+ import { getOllamaProvider } from './ollama';
18
+ import { createAnthropicProvider } from './anthropic';
19
+ // ============================================================================
20
+ // Provider Registry
21
+ // ============================================================================
22
+ const DEFAULT_PROVIDERS = [
23
+ {
24
+ name: 'ollama',
25
+ prefix: 'ollama/',
26
+ create: () => getOllamaProvider(),
27
+ },
28
+ {
29
+ name: 'claude',
30
+ prefix: 'claude/',
31
+ create: () => createAnthropicProvider(),
32
+ },
33
+ {
34
+ name: 'anthropic',
35
+ prefix: 'anthropic/',
36
+ create: () => createAnthropicProvider(), // Alias
37
+ },
38
+ ];
39
+ class ProviderRegistry {
40
+ providers = new Map();
41
+ configs;
42
+ status = new Map();
43
+ constructor(configs = DEFAULT_PROVIDERS) {
44
+ this.configs = configs;
45
+ }
46
+ /**
47
+ * Get provider for a model
48
+ */
49
+ getProvider(model) {
50
+ // Extract prefix (e.g., "ollama/", "claude/")
51
+ const prefixMatch = this.configs.find(cfg => model.startsWith(cfg.prefix));
52
+ if (prefixMatch) {
53
+ const providerName = prefixMatch.name;
54
+ // Cache provider instance
55
+ if (!this.providers.has(providerName)) {
56
+ this.providers.set(providerName, prefixMatch.create());
57
+ }
58
+ return this.providers.get(providerName);
59
+ }
60
+ // Default to Ollama if no prefix
61
+ if (!this.providers.has('ollama')) {
62
+ this.providers.set('ollama', getOllamaProvider());
63
+ }
64
+ return this.providers.get('ollama');
65
+ }
66
+ /**
67
+ * Get provider name from model
68
+ */
69
+ getProviderName(model) {
70
+ const prefixMatch = this.configs.find(cfg => model.startsWith(cfg.prefix));
71
+ return prefixMatch?.name ?? 'ollama';
72
+ }
73
+ /**
74
+ * Strip provider prefix from model name
75
+ */
76
+ stripPrefix(model) {
77
+ const prefixMatch = this.configs.find(cfg => model.startsWith(cfg.prefix));
78
+ if (prefixMatch) {
79
+ return model.slice(prefixMatch.prefix.length);
80
+ }
81
+ return model;
82
+ }
83
+ /**
84
+ * Check if provider is available
85
+ */
86
+ async checkAvailability(providerName) {
87
+ const config = this.configs.find(cfg => cfg.name === providerName);
88
+ if (!config)
89
+ return false;
90
+ try {
91
+ const provider = this.getProvider(`${config.prefix}test`);
92
+ // Try to list models or make a minimal call
93
+ if (provider.listModels) {
94
+ const models = await provider.listModels();
95
+ return models.length > 0;
96
+ }
97
+ return true;
98
+ }
99
+ catch {
100
+ return false;
101
+ }
102
+ }
103
+ /**
104
+ * Get all provider statuses
105
+ */
106
+ async getStatuses() {
107
+ const statuses = [];
108
+ for (const config of this.configs) {
109
+ const available = await this.checkAvailability(config.name);
110
+ const provider = this.providers.get(config.name);
111
+ statuses.push({
112
+ name: config.name,
113
+ available,
114
+ modelCount: available ? (await provider?.listModels?.() ?? []).length : 0,
115
+ lastChecked: Date.now(),
116
+ });
117
+ }
118
+ return statuses;
119
+ }
120
+ /**
121
+ * Register a new provider
122
+ */
123
+ register(config) {
124
+ this.configs.push(config);
125
+ }
126
+ /**
127
+ * List configured providers
128
+ */
129
+ listProviders() {
130
+ return this.configs.map(cfg => cfg.name);
131
+ }
132
+ }
133
+ // ============================================================================
134
+ // Multi-Provider Router
135
+ // ============================================================================
136
+ let registryInstance = null;
137
+ let multiProviderRouter = null;
138
+ export class MultiProviderRouter {
139
+ name = 'multi-provider';
140
+ registry;
141
+ defaultProvider = 'ollama';
142
+ routers = new Map();
143
+ constructor(registry) {
144
+ this.registry = registry;
145
+ }
146
+ /**
147
+ * Chat using appropriate provider
148
+ */
149
+ async chat(options) {
150
+ const providerName = this.registry.getProviderName(options.model);
151
+ const provider = this.registry.getProvider(options.model);
152
+ const modelName = this.registry.stripPrefix(options.model);
153
+ // Adapt ChatOptions for the provider
154
+ const providerOptions = {
155
+ ...options,
156
+ model: modelName,
157
+ };
158
+ console.log(`[MultiProvider] Routing to ${providerName}: ${modelName}`);
159
+ // Track timing for performance
160
+ const startTime = Date.now();
161
+ try {
162
+ const response = await provider.chat(providerOptions);
163
+ const duration = Date.now() - startTime;
164
+ console.log(`[MultiProvider] ${providerName}/${modelName} completed in ${duration}ms`);
165
+ // Add provider info to response
166
+ return {
167
+ ...response,
168
+ model: `${providerName}/${modelName}`,
169
+ };
170
+ }
171
+ catch (error) {
172
+ const duration = Date.now() - startTime;
173
+ console.error(`[MultiProvider] ${providerName}/${modelName} failed after ${duration}ms:`, error);
174
+ throw error;
175
+ }
176
+ }
177
+ /**
178
+ * List models from all providers
179
+ */
180
+ async listModels() {
181
+ const allModels = [];
182
+ for (const providerName of this.registry.listProviders()) {
183
+ try {
184
+ const provider = this.registry.getProvider(`${providerName}/test`);
185
+ if (provider.listModels) {
186
+ const models = await provider.listModels();
187
+ for (const model of models) {
188
+ allModels.push(`${providerName}/${model}`);
189
+ }
190
+ }
191
+ }
192
+ catch (error) {
193
+ console.warn(`[MultiProvider] Failed to list models from ${providerName}:`, error);
194
+ }
195
+ }
196
+ return allModels;
197
+ }
198
+ /**
199
+ * Get provider registry
200
+ */
201
+ getRegistry() {
202
+ return this.registry;
203
+ }
204
+ /**
205
+ * Get adaptive router for a specific provider
206
+ */
207
+ async getRouterForProvider(providerName) {
208
+ if (this.routers.has(providerName)) {
209
+ return this.routers.get(providerName);
210
+ }
211
+ const config = this.registry.listProviders().includes(providerName);
212
+ if (!config)
213
+ return null;
214
+ const provider = this.registry.getProvider(`${providerName}/default`);
215
+ const router = await createRouter(provider, 'default');
216
+ this.routers.set(providerName, router);
217
+ return router;
218
+ }
219
+ }
220
+ // ============================================================================
221
+ // Singleton Access
222
+ // ============================================================================
223
+ export function getProviderRegistry() {
224
+ if (!registryInstance) {
225
+ registryInstance = new ProviderRegistry();
226
+ }
227
+ return registryInstance;
228
+ }
229
+ export async function getMultiProviderRouter() {
230
+ if (!multiProviderRouter) {
231
+ const registry = getProviderRegistry();
232
+ multiProviderRouter = new MultiProviderRouter(registry);
233
+ console.log('[MultiProvider] Initialized with providers:', registry.listProviders());
234
+ }
235
+ return multiProviderRouter;
236
+ }
237
+ /**
238
+ * Convenience: Chat with automatic provider routing
239
+ */
240
+ export async function chat(options) {
241
+ const router = await getMultiProviderRouter();
242
+ return router.chat(options);
243
+ }
244
+ /**
245
+ * Convenience: List all available models with provider prefixes
246
+ */
247
+ export async function listAllModels() {
248
+ const router = await getMultiProviderRouter();
249
+ return router.listModels();
250
+ }
251
+ /**
252
+ * Convenience: Get provider statuses
253
+ */
254
+ export async function getProviderStatuses() {
255
+ const registry = getProviderRegistry();
256
+ return registry.getStatuses();
257
+ }
258
+ //# sourceMappingURL=multi-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi-provider.js","sourceRoot":"","sources":["../../../src/llm/multi-provider.ts"],"names":[],"mappings":"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"}
@@ -1,243 +1,179 @@
1
1
  /**
2
2
  * Ollama LLM Provider
3
3
  *
4
- * Unified local + cloud support via ollama-extension.ts
5
- * Routing logic is now handled in the extension, this provider
6
- * just makes the actual API calls.
4
+ * Unified local + cloud support using official Ollama client.
5
+ * Uses OpenAI-compatible endpoints (/v1) for pi-coding-agent compatibility.
7
6
  */
8
- // Cloud URL for fallback
9
- const CLOUD_URL = 'https://ollama.com';
10
- // Helper functions (previously imported from extension)
11
- async function checkLocalOllama() {
12
- try {
13
- const res = await fetch('http://localhost:11434/api/tags', {
14
- method: 'GET',
15
- signal: AbortSignal.timeout(5000)
16
- });
17
- return res.ok;
18
- }
19
- catch {
20
- return false;
21
- }
22
- }
23
- function getOllamaBaseUrl(modelId) {
24
- // If model has cloud/ prefix or no local Ollama, use cloud
25
- if (modelId.includes('cloud/'))
26
- return CLOUD_URL;
27
- return 'http://localhost:11434';
28
- }
29
- function getOllamaApiKey() {
30
- return process.env.OLLAMA_API_KEY;
31
- }
32
- function modelRequiresApiKey(modelId) {
33
- // Cloud models require API key
34
- return modelId.includes('cloud/') || modelId.startsWith('cloud-');
35
- }
7
+ import { Ollama } from 'ollama';
8
+ import { loadConfigFromEnv, DEFAULT_CONFIG, } from '@0xkobold/pi-ollama/shared';
36
9
  /**
37
10
  * Ollama Provider
38
- * Supports both local and cloud modes
11
+ * Supports both local and cloud modes using official client
39
12
  */
40
13
  export class OllamaProvider {
41
14
  name = 'ollama';
42
- baseUrl = 'http://localhost:11434';
15
+ clients;
43
16
  cloudOnly = false;
44
17
  constructor() {
45
- // Check if we have local Ollama
18
+ // Merge env config with defaults
19
+ const envConfig = loadConfigFromEnv();
20
+ const config = {
21
+ baseUrl: envConfig.baseUrl ?? process.env.OLLAMA_BASE_URL ?? DEFAULT_CONFIG.baseUrl,
22
+ cloudUrl: envConfig.cloudUrl ?? process.env.OLLAMA_CLOUD_URL ?? DEFAULT_CONFIG.cloudUrl,
23
+ apiKey: envConfig.apiKey ?? process.env.OLLAMA_API_KEY ?? DEFAULT_CONFIG.apiKey,
24
+ };
25
+ // Create clients
26
+ this.clients = {
27
+ local: new Ollama({ host: config.baseUrl }),
28
+ cloud: config.apiKey
29
+ ? new Ollama({ host: config.cloudUrl, headers: { Authorization: `Bearer ${config.apiKey}` } })
30
+ : null,
31
+ };
46
32
  this.detectMode();
47
33
  }
48
34
  async detectMode() {
49
- const hasLocal = await checkLocalOllama();
50
- this.cloudOnly = !hasLocal;
51
- this.baseUrl = hasLocal ? 'http://localhost:11434' : CLOUD_URL;
52
- }
53
- /**
54
- * Update base URL based on current mode and model
55
- */
56
- updateBaseUrl(modelId) {
57
- // Use the extension's routing logic
58
- this.baseUrl = getOllamaBaseUrl(modelId);
35
+ try {
36
+ await this.clients.local.list();
37
+ this.cloudOnly = false;
38
+ }
39
+ catch {
40
+ this.cloudOnly = true;
41
+ }
59
42
  }
60
43
  /**
61
- * Check if running in cloud-only mode
44
+ * Get the appropriate client for a model
62
45
  */
63
- isCloudOnly() {
64
- return this.cloudOnly;
46
+ getClient(model) {
47
+ // If model has :cloud suffix, use cloud client
48
+ if (model.endsWith(':cloud') && this.clients.cloud) {
49
+ return { client: this.clients.cloud, isCloud: true };
50
+ }
51
+ // If local is unavailable, use cloud
52
+ if (this.cloudOnly && this.clients.cloud) {
53
+ return { client: this.clients.cloud, isCloud: true };
54
+ }
55
+ // Default to local
56
+ return { client: this.clients.local, isCloud: false };
65
57
  }
66
58
  async chat(options) {
67
- // Set base URL based on model
68
- this.updateBaseUrl(options.model);
69
- const isCloud = this.baseUrl === CLOUD_URL || options.model.includes('cloud/');
70
- // Get model name (strip prefix)
71
- const model = options.model.replace(/^ollama\//, '').replace(/^cloud\//, '');
72
- // Build messages
59
+ const { client, isCloud } = this.getClient(options.model);
60
+ const model = options.model.replace(':cloud', '');
61
+ // Convert messages to OpenAI format
73
62
  const messages = options.messages.map((m) => ({
74
63
  role: m.role,
75
64
  content: m.content,
76
65
  }));
77
- // Build request body
78
- const body = {
79
- model,
80
- messages,
81
- stream: options.stream ?? false,
82
- options: {
83
- temperature: options.temperature ?? 0.7,
84
- num_predict: options.maxTokens ?? 4096,
85
- },
86
- };
87
- // Add tools if provided
88
- if (options.tools && options.tools.length > 0) {
89
- body.tools = options.tools.map(t => ({
90
- type: t.type,
91
- function: t.function,
92
- }));
93
- }
94
- // Build headers
95
- const headers = {
96
- 'Content-Type': 'application/json',
97
- };
98
- // Add auth for cloud mode
99
- if (isCloud || modelRequiresApiKey(options.model)) {
100
- const apiKey = getOllamaApiKey();
101
- if (apiKey) {
102
- headers['Authorization'] = `Bearer ${apiKey}`;
103
- }
66
+ try {
67
+ // Use the chat API
68
+ const response = await client.chat({
69
+ model,
70
+ messages,
71
+ options: {
72
+ temperature: options.temperature ?? 0.7,
73
+ num_predict: options.maxTokens ?? 4096,
74
+ },
75
+ });
76
+ return {
77
+ content: response.message.content,
78
+ usage: {
79
+ inputTokens: response.prompt_eval_count ?? 0,
80
+ outputTokens: response.eval_count ?? 0,
81
+ },
82
+ model: options.model,
83
+ };
104
84
  }
105
- const res = await fetch(`${this.baseUrl}/api/chat`, {
106
- method: 'POST',
107
- headers,
108
- body: JSON.stringify(body),
109
- signal: options.signal,
110
- });
111
- if (!res.ok) {
112
- const err = await res.text().catch(() => res.statusText);
113
- throw new Error(`Ollama ${isCloud ? 'Cloud' : 'Local'} error: ${err}`);
85
+ catch (err) {
86
+ throw new Error(`Ollama ${isCloud ? 'Cloud' : 'Local'} error: ${err.message}`);
114
87
  }
115
- const data = await res.json();
116
- // Parse tool calls from response
117
- const toolCalls = data.message?.tool_calls?.map((tc) => ({
118
- id: `call-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
119
- function: {
120
- name: tc.function?.name ?? tc.function,
121
- arguments: typeof tc.function?.arguments === 'string'
122
- ? JSON.parse(tc.function.arguments)
123
- : tc.function?.arguments ?? {},
124
- },
125
- }));
126
- // Calculate usage (Ollama provides eval counts)
127
- const usage = {
128
- inputTokens: data.prompt_eval_count ?? 0,
129
- outputTokens: data.eval_count ?? 0,
130
- };
131
- return {
132
- content: data.message?.content ?? '',
133
- toolCalls,
134
- model: options.model,
135
- usage,
136
- };
137
88
  }
138
89
  async *chatStream(options) {
139
- // Set base URL based on model
140
- this.updateBaseUrl(options.model);
141
- const isCloud = this.baseUrl === CLOUD_URL || options.model.includes('cloud/');
142
- // Get model name (strip prefix)
143
- const model = options.model.replace(/^ollama\//, '').replace(/^cloud\//, '');
144
- // Build messages
90
+ const { client, isCloud } = this.getClient(options.model);
91
+ const model = options.model.replace(':cloud', '');
92
+ // Convert messages to OpenAI format
145
93
  const messages = options.messages.map((m) => ({
146
94
  role: m.role,
147
95
  content: m.content,
148
96
  }));
149
- const body = {
150
- model,
151
- messages,
152
- stream: true,
153
- options: {
154
- temperature: options.temperature ?? 0.7,
155
- num_predict: options.maxTokens ?? 4096,
156
- },
157
- };
158
- // Build headers
159
- const headers = {
160
- 'Content-Type': 'application/json',
161
- };
162
- // Add auth for cloud mode
163
- if (isCloud || modelRequiresApiKey(options.model)) {
164
- const apiKey = getOllamaApiKey();
165
- if (apiKey) {
166
- headers['Authorization'] = `Bearer ${apiKey}`;
167
- }
168
- }
169
- const res = await fetch(`${this.baseUrl}/api/chat`, {
170
- method: 'POST',
171
- headers,
172
- body: JSON.stringify(body),
173
- signal: options.signal,
174
- });
175
- if (!res.ok) {
176
- const err = await res.text().catch(() => res.statusText);
177
- throw new Error(`Ollama ${isCloud ? 'Cloud' : 'Local'} error: ${err}`);
178
- }
179
- if (!res.body) {
180
- throw new Error('No response body');
181
- }
182
- // Process stream
183
- const reader = res.body.getReader();
184
- const decoder = new TextDecoder();
185
97
  try {
186
- while (true) {
187
- const { done, value } = await reader.read();
188
- if (done)
189
- break;
190
- const chunk = decoder.decode(value, { stream: true });
191
- const lines = chunk.split('\n').filter(line => line.trim());
192
- for (const line of lines) {
193
- try {
194
- const data = JSON.parse(line);
195
- if (data.message?.content) {
196
- yield data.message.content;
197
- }
198
- }
199
- catch {
200
- // Ignore parse errors
201
- }
98
+ const stream = await client.chat({
99
+ model,
100
+ messages,
101
+ stream: true,
102
+ options: {
103
+ temperature: options.temperature ?? 0.7,
104
+ num_predict: options.maxTokens ?? 4096,
105
+ },
106
+ });
107
+ for await (const chunk of stream) {
108
+ if (chunk.message?.content) {
109
+ yield chunk.message.content;
202
110
  }
203
111
  }
204
112
  }
205
- finally {
206
- reader.releaseLock();
113
+ catch (err) {
114
+ throw new Error(`Ollama stream error: ${err.message}`);
207
115
  }
208
116
  }
209
117
  async listModels() {
210
- // Try local first
211
- const hasLocal = await checkLocalOllama();
212
118
  try {
213
- const headers = {};
214
- const url = hasLocal ? 'http://localhost:11434' : CLOUD_URL;
215
- if (!hasLocal) {
216
- const apiKey = getOllamaApiKey();
217
- if (apiKey)
218
- headers['Authorization'] = `Bearer ${apiKey}`;
119
+ const { models } = await this.clients.local.list();
120
+ const modelNames = models.map(m => m.name);
121
+ // Also list cloud models if available
122
+ if (this.clients.cloud && !this.cloudOnly) {
123
+ try {
124
+ const { models: cloudModels } = await this.clients.cloud.list();
125
+ const cloudNames = cloudModels.map(m => `${m.name}:cloud`);
126
+ return [...modelNames, ...cloudNames];
127
+ }
128
+ catch {
129
+ // Cloud unavailable, just return local
130
+ }
219
131
  }
220
- const res = await fetch(`${url}/api/tags`, { headers });
221
- if (!res.ok)
222
- return [];
223
- const data = await res.json();
224
- return (data.models ?? []).map((m) => m.name);
132
+ return modelNames;
225
133
  }
226
134
  catch {
135
+ // If local fails, try cloud
136
+ if (this.clients.cloud) {
137
+ const { models } = await this.clients.cloud.list();
138
+ return models.map(m => `${m.name}:cloud`);
139
+ }
227
140
  return [];
228
141
  }
229
142
  }
143
+ /**
144
+ * Check if Ollama is running (local mode)
145
+ */
146
+ async isLocalRunning() {
147
+ try {
148
+ await this.clients.local.list();
149
+ return true;
150
+ }
151
+ catch {
152
+ return false;
153
+ }
154
+ }
155
+ /**
156
+ * Check if running in cloud-only mode
157
+ */
158
+ isCloudOnly() {
159
+ return this.cloudOnly;
160
+ }
230
161
  }
231
162
  /**
232
163
  * Check if Ollama is running (local mode)
233
- * Re-exported from extension for compatibility
234
164
  */
235
165
  export async function isOllamaRunning() {
236
- return checkLocalOllama();
166
+ try {
167
+ const client = new Ollama({ host: process.env.OLLAMA_HOST || 'http://localhost:11434' });
168
+ await client.list();
169
+ return true;
170
+ }
171
+ catch {
172
+ return false;
173
+ }
237
174
  }
238
175
  /**
239
176
  * List available Ollama models
240
- * Re-exported from extension for compatibility
241
177
  */
242
178
  export async function listOllamaModels() {
243
179
  const provider = new OllamaProvider();
@@ -249,5 +185,15 @@ export async function listOllamaModels() {
249
185
  export function createOllamaProvider() {
250
186
  return new OllamaProvider();
251
187
  }
188
+ /**
189
+ * Get singleton Ollama provider instance
190
+ */
191
+ let ollamaProviderInstance = null;
192
+ export function getOllamaProvider() {
193
+ if (!ollamaProviderInstance) {
194
+ ollamaProviderInstance = new OllamaProvider();
195
+ }
196
+ return ollamaProviderInstance;
197
+ }
252
198
  export default createOllamaProvider;
253
199
  //# sourceMappingURL=ollama.js.map