infinicode 1.0.0 → 2.0.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 (157) hide show
  1. package/.opencode/plugins/home-logo-animation.tsx +120 -0
  2. package/.opencode/plugins/routing-mode-display.tsx +135 -0
  3. package/.opencode/themes/infinibot-gold.json +222 -0
  4. package/.opencode/tui.json +8 -0
  5. package/README.md +527 -75
  6. package/dist/ascii-video-animation.d.ts +2 -0
  7. package/dist/ascii-video-animation.js +243 -0
  8. package/dist/cli.js +195 -50
  9. package/dist/commands/console.d.ts +6 -0
  10. package/dist/commands/console.js +111 -0
  11. package/dist/commands/kernel-setup.d.ts +3 -0
  12. package/dist/commands/kernel-setup.js +303 -0
  13. package/dist/commands/mcp.d.ts +12 -0
  14. package/dist/commands/mcp.js +96 -0
  15. package/dist/commands/mission.d.ts +16 -0
  16. package/dist/commands/mission.js +301 -0
  17. package/dist/commands/models.d.ts +3 -1
  18. package/dist/commands/models.js +111 -55
  19. package/dist/commands/providers.d.ts +6 -0
  20. package/dist/commands/providers.js +95 -0
  21. package/dist/commands/run.d.ts +2 -1
  22. package/dist/commands/run.js +349 -59
  23. package/dist/commands/serve.d.ts +18 -0
  24. package/dist/commands/serve.js +127 -0
  25. package/dist/commands/setup.d.ts +1 -1
  26. package/dist/commands/setup.js +77 -44
  27. package/dist/commands/status.d.ts +2 -1
  28. package/dist/commands/status.js +46 -30
  29. package/dist/commands/workers.d.ts +5 -0
  30. package/dist/commands/workers.js +103 -0
  31. package/dist/kernel/autonomy/goal-loop.d.ts +49 -0
  32. package/dist/kernel/autonomy/goal-loop.js +113 -0
  33. package/dist/kernel/autonomy/index.d.ts +8 -0
  34. package/dist/kernel/autonomy/index.js +7 -0
  35. package/dist/kernel/browser/browser-controller.d.ts +57 -0
  36. package/dist/kernel/browser/browser-controller.js +175 -0
  37. package/dist/kernel/checkpoint-engine.d.ts +17 -0
  38. package/dist/kernel/checkpoint-engine.js +128 -0
  39. package/dist/kernel/command-system.d.ts +37 -0
  40. package/dist/kernel/command-system.js +239 -0
  41. package/dist/kernel/config-schema.d.ts +53 -0
  42. package/dist/kernel/config-schema.js +36 -0
  43. package/dist/kernel/event-bus.d.ts +19 -0
  44. package/dist/kernel/event-bus.js +65 -0
  45. package/dist/kernel/federation/auto-update.d.ts +36 -0
  46. package/dist/kernel/federation/auto-update.js +57 -0
  47. package/dist/kernel/federation/compute-router.d.ts +27 -0
  48. package/dist/kernel/federation/compute-router.js +44 -0
  49. package/dist/kernel/federation/config-sync.d.ts +33 -0
  50. package/dist/kernel/federation/config-sync.js +37 -0
  51. package/dist/kernel/federation/discovery.d.ts +11 -0
  52. package/dist/kernel/federation/discovery.js +44 -0
  53. package/dist/kernel/federation/event-bridge.d.ts +30 -0
  54. package/dist/kernel/federation/event-bridge.js +61 -0
  55. package/dist/kernel/federation/federation-plugin.d.ts +24 -0
  56. package/dist/kernel/federation/federation-plugin.js +35 -0
  57. package/dist/kernel/federation/federation.d.ts +126 -0
  58. package/dist/kernel/federation/federation.js +335 -0
  59. package/dist/kernel/federation/index.d.ts +31 -0
  60. package/dist/kernel/federation/index.js +23 -0
  61. package/dist/kernel/federation/moltfed-adapter.d.ts +30 -0
  62. package/dist/kernel/federation/moltfed-adapter.js +52 -0
  63. package/dist/kernel/federation/moltfed-connector.d.ts +98 -0
  64. package/dist/kernel/federation/moltfed-connector.js +193 -0
  65. package/dist/kernel/federation/node-identity.d.ts +19 -0
  66. package/dist/kernel/federation/node-identity.js +86 -0
  67. package/dist/kernel/federation/peer-mesh.d.ts +46 -0
  68. package/dist/kernel/federation/peer-mesh.js +117 -0
  69. package/dist/kernel/federation/protocol.d.ts +20 -0
  70. package/dist/kernel/federation/protocol.js +61 -0
  71. package/dist/kernel/federation/role-context.d.ts +5 -0
  72. package/dist/kernel/federation/role-context.js +45 -0
  73. package/dist/kernel/federation/telemetry.d.ts +21 -0
  74. package/dist/kernel/federation/telemetry.js +138 -0
  75. package/dist/kernel/federation/transport-http.d.ts +44 -0
  76. package/dist/kernel/federation/transport-http.js +207 -0
  77. package/dist/kernel/federation/types.d.ts +212 -0
  78. package/dist/kernel/federation/types.js +3 -0
  79. package/dist/kernel/free-providers.d.ts +28 -0
  80. package/dist/kernel/free-providers.js +162 -0
  81. package/dist/kernel/frontend-scoring.d.ts +21 -0
  82. package/dist/kernel/frontend-scoring.js +125 -0
  83. package/dist/kernel/index.d.ts +63 -0
  84. package/dist/kernel/index.js +45 -0
  85. package/dist/kernel/kernel.d.ts +75 -0
  86. package/dist/kernel/kernel.js +223 -0
  87. package/dist/kernel/logger.d.ts +18 -0
  88. package/dist/kernel/logger.js +26 -0
  89. package/dist/kernel/mcp/index.d.ts +9 -0
  90. package/dist/kernel/mcp/index.js +8 -0
  91. package/dist/kernel/mcp/mcp-server.d.ts +27 -0
  92. package/dist/kernel/mcp/mcp-server.js +178 -0
  93. package/dist/kernel/mission-engine.d.ts +42 -0
  94. package/dist/kernel/mission-engine.js +160 -0
  95. package/dist/kernel/orchestrator.d.ts +51 -0
  96. package/dist/kernel/orchestrator.js +226 -0
  97. package/dist/kernel/plugin-manager.d.ts +28 -0
  98. package/dist/kernel/plugin-manager.js +76 -0
  99. package/dist/kernel/plugins/browser-plugin.d.ts +22 -0
  100. package/dist/kernel/plugins/browser-plugin.js +34 -0
  101. package/dist/kernel/plugins/dashboard-plugin.d.ts +17 -0
  102. package/dist/kernel/plugins/dashboard-plugin.js +72 -0
  103. package/dist/kernel/plugins/discord-plugin.d.ts +11 -0
  104. package/dist/kernel/plugins/discord-plugin.js +35 -0
  105. package/dist/kernel/plugins/metrics-plugin.d.ts +33 -0
  106. package/dist/kernel/plugins/metrics-plugin.js +86 -0
  107. package/dist/kernel/plugins/search-plugin.d.ts +17 -0
  108. package/dist/kernel/plugins/search-plugin.js +20 -0
  109. package/dist/kernel/plugins/slack-plugin.d.ts +11 -0
  110. package/dist/kernel/plugins/slack-plugin.js +35 -0
  111. package/dist/kernel/plugins/telegram-plugin.d.ts +20 -0
  112. package/dist/kernel/plugins/telegram-plugin.js +122 -0
  113. package/dist/kernel/policies.d.ts +33 -0
  114. package/dist/kernel/policies.js +105 -0
  115. package/dist/kernel/provider-discovery.d.ts +41 -0
  116. package/dist/kernel/provider-discovery.js +179 -0
  117. package/dist/kernel/provider-manager.d.ts +38 -0
  118. package/dist/kernel/provider-manager.js +206 -0
  119. package/dist/kernel/provider-url.d.ts +18 -0
  120. package/dist/kernel/provider-url.js +45 -0
  121. package/dist/kernel/providers/gemini-provider.d.ts +43 -0
  122. package/dist/kernel/providers/gemini-provider.js +203 -0
  123. package/dist/kernel/providers/ollama-provider.d.ts +44 -0
  124. package/dist/kernel/providers/ollama-provider.js +241 -0
  125. package/dist/kernel/providers/openai-compatible-provider.d.ts +43 -0
  126. package/dist/kernel/providers/openai-compatible-provider.js +233 -0
  127. package/dist/kernel/recovery-manager.d.ts +38 -0
  128. package/dist/kernel/recovery-manager.js +156 -0
  129. package/dist/kernel/router.d.ts +44 -0
  130. package/dist/kernel/router.js +222 -0
  131. package/dist/kernel/sample-missions.d.ts +11 -0
  132. package/dist/kernel/sample-missions.js +132 -0
  133. package/dist/kernel/scheduler.d.ts +30 -0
  134. package/dist/kernel/scheduler.js +147 -0
  135. package/dist/kernel/sdk/harness-sdk.d.ts +37 -0
  136. package/dist/kernel/sdk/harness-sdk.js +48 -0
  137. package/dist/kernel/sdk/plugin-sdk.d.ts +27 -0
  138. package/dist/kernel/sdk/plugin-sdk.js +40 -0
  139. package/dist/kernel/search/search-controller.d.ts +32 -0
  140. package/dist/kernel/search/search-controller.js +141 -0
  141. package/dist/kernel/setup.d.ts +13 -0
  142. package/dist/kernel/setup.js +59 -0
  143. package/dist/kernel/types.d.ts +479 -0
  144. package/dist/kernel/types.js +7 -0
  145. package/dist/kernel/verification-engine.d.ts +33 -0
  146. package/dist/kernel/verification-engine.js +287 -0
  147. package/dist/kernel/worker-runtime.d.ts +66 -0
  148. package/dist/kernel/worker-runtime.js +261 -0
  149. package/dist/kernel/workers/browser-worker.d.ts +6 -0
  150. package/dist/kernel/workers/browser-worker.js +92 -0
  151. package/dist/kernel/workers/builtin-workers.d.ts +20 -0
  152. package/dist/kernel/workers/builtin-workers.js +120 -0
  153. package/dist/kernel/workers/research-worker.d.ts +5 -0
  154. package/dist/kernel/workers/research-worker.js +90 -0
  155. package/dist/prompt-ascii-video-animation.d.ts +2 -0
  156. package/dist/prompt-ascii-video-animation.js +243 -0
  157. package/package.json +43 -9
@@ -0,0 +1,203 @@
1
+ const GEMINI_DEFAULT_BASE = 'https://generativelanguage.googleapis.com/v1beta';
2
+ export class GeminiProvider {
3
+ info;
4
+ apiKey;
5
+ baseURL;
6
+ timeoutMs;
7
+ knownModels;
8
+ constructor(options) {
9
+ this.apiKey = options.apiKey;
10
+ this.baseURL = (options.baseURL ?? GEMINI_DEFAULT_BASE).replace(/\/$/, '');
11
+ this.timeoutMs = options.timeoutMs ?? 30_000;
12
+ this.knownModels = options.knownModels ?? [];
13
+ this.info = {
14
+ id: options.id ?? 'gemini',
15
+ name: options.name ?? 'Gemini',
16
+ type: 'cloud',
17
+ healthy: false,
18
+ available: !!this.apiKey,
19
+ baseURL: this.baseURL,
20
+ };
21
+ }
22
+ async capabilities() {
23
+ const models = await this.fetchModels();
24
+ return {
25
+ streaming: true,
26
+ functionCalling: true,
27
+ vision: models.some(m => m.supportsVision),
28
+ maxContextLength: Math.max(8192, ...models.map(m => m.contextLength)),
29
+ models,
30
+ };
31
+ }
32
+ async complete(request) {
33
+ const start = Date.now();
34
+ const modelId = stripModelPrefix(request.model);
35
+ const url = `${this.baseURL}/models/${modelId}:generateContent?key=${this.apiKey}`;
36
+ const body = this.buildBody(request);
37
+ const response = await fetch(url, {
38
+ method: 'POST',
39
+ headers: { 'Content-Type': 'application/json' },
40
+ body: JSON.stringify(body),
41
+ signal: AbortSignal.timeout(this.timeoutMs),
42
+ });
43
+ if (!response.ok) {
44
+ throw new Error(`Gemini completion failed: HTTP ${response.status} ${await safeText(response)}`);
45
+ }
46
+ const data = (await response.json());
47
+ const content = data.candidates?.[0]?.content?.parts?.map(p => p.text ?? '').join('') ?? '';
48
+ const tokensIn = data.usageMetadata?.promptTokenCount ?? 0;
49
+ const tokensOut = data.usageMetadata?.candidatesTokenCount ?? 0;
50
+ return {
51
+ content,
52
+ providerId: this.info.id,
53
+ modelId: request.model,
54
+ tokensIn,
55
+ tokensOut,
56
+ durationMs: Date.now() - start,
57
+ };
58
+ }
59
+ async *completeStream(request) {
60
+ const modelId = stripModelPrefix(request.model);
61
+ const url = `${this.baseURL}/models/${modelId}:streamGenerateContent?key=${this.apiKey}&alt=sse`;
62
+ const body = this.buildBody(request);
63
+ const response = await fetch(url, {
64
+ method: 'POST',
65
+ headers: { 'Content-Type': 'application/json' },
66
+ body: JSON.stringify(body),
67
+ signal: AbortSignal.timeout(this.timeoutMs * 2),
68
+ });
69
+ if (!response.ok || !response.body) {
70
+ throw new Error(`Gemini stream failed: HTTP ${response.status}`);
71
+ }
72
+ const reader = response.body.getReader();
73
+ const decoder = new TextDecoder();
74
+ let buffer = '';
75
+ while (true) {
76
+ const { done, value } = await reader.read();
77
+ if (done)
78
+ break;
79
+ buffer += decoder.decode(value, { stream: true });
80
+ const lines = buffer.split('\n');
81
+ buffer = lines.pop() ?? '';
82
+ for (const line of lines) {
83
+ const trimmed = line.trim();
84
+ if (!trimmed.startsWith('data: '))
85
+ continue;
86
+ const payload = trimmed.slice(6);
87
+ if (payload === '[DONE]') {
88
+ yield { delta: '', done: true, providerId: this.info.id, modelId: request.model };
89
+ return;
90
+ }
91
+ try {
92
+ const chunk = JSON.parse(payload);
93
+ const delta = chunk.candidates?.[0]?.content?.parts?.map(p => p.text ?? '').join('') ?? '';
94
+ if (delta) {
95
+ yield { delta, done: false, providerId: this.info.id, modelId: request.model };
96
+ }
97
+ }
98
+ catch {
99
+ // skip malformed
100
+ }
101
+ }
102
+ }
103
+ }
104
+ async refresh() {
105
+ await this.fetchModels();
106
+ }
107
+ async verify() {
108
+ const start = Date.now();
109
+ try {
110
+ let model = this.knownModels[0]?.id;
111
+ if (!model) {
112
+ try {
113
+ model = (await this.fetchModels())[0]?.id;
114
+ }
115
+ catch {
116
+ // fall through to default
117
+ }
118
+ }
119
+ if (!model)
120
+ model = 'gemini-2.0-flash';
121
+ await this.complete({ model, prompt: 'ping', maxTokens: 1, temperature: 0 });
122
+ return { ok: true, latencyMs: Date.now() - start };
123
+ }
124
+ catch (err) {
125
+ return { ok: false, latencyMs: Date.now() - start, error: err instanceof Error ? err.message : String(err) };
126
+ }
127
+ }
128
+ buildBody(request) {
129
+ const contents = [];
130
+ if (request.systemPrompt) {
131
+ // Gemini accepts systemInstruction separately
132
+ }
133
+ if (request.context) {
134
+ const ctxStr = Object.entries(request.context)
135
+ .map(([k, v]) => `${k}: ${typeof v === 'string' ? v : JSON.stringify(v)}`)
136
+ .join('\n');
137
+ contents.push({ role: 'user', parts: [{ text: ctxStr }] });
138
+ }
139
+ contents.push({ role: 'user', parts: [{ text: request.prompt }] });
140
+ const body = {
141
+ contents,
142
+ generationConfig: {
143
+ temperature: request.temperature ?? 0.3,
144
+ maxOutputTokens: request.maxTokens ?? 4096,
145
+ },
146
+ };
147
+ if (request.systemPrompt) {
148
+ body.systemInstruction = { parts: [{ text: request.systemPrompt }] };
149
+ }
150
+ return body;
151
+ }
152
+ async fetchModels() {
153
+ try {
154
+ const url = `${this.baseURL}/models?key=${this.apiKey}`;
155
+ const response = await fetch(url, { signal: AbortSignal.timeout(this.timeoutMs) });
156
+ if (!response.ok)
157
+ throw new Error(`HTTP ${response.status}`);
158
+ const data = (await response.json());
159
+ return (data.models ?? [])
160
+ .filter(m => (m.supportedGenerationMethods ?? []).includes('generateContent'))
161
+ .map(m => this.toProviderModel(m.name, m.inputTokenLimit, m.displayName));
162
+ }
163
+ catch {
164
+ return this.knownModels.map(m => this.toProviderModel(m.id, m.contextLength, m.name));
165
+ }
166
+ }
167
+ toProviderModel(rawName, inputTokenLimit, displayName) {
168
+ const id = stripModelPrefix(rawName);
169
+ const known = this.knownModels.find(m => stripModelPrefix(m.id) === id);
170
+ const lower = id.toLowerCase();
171
+ const capabilities = known?.capabilities ?? ['reasoning'];
172
+ if (!capabilities.includes('reasoning'))
173
+ capabilities.push('reasoning');
174
+ if (lower.includes('code') || lower.includes('coder'))
175
+ capabilities.push('coding');
176
+ if (lower.includes('vision') || lower.includes('vl'))
177
+ capabilities.push('vision');
178
+ return {
179
+ id,
180
+ name: displayName ?? known?.name ?? id,
181
+ providerId: this.info.id,
182
+ contextLength: inputTokenLimit ?? known?.contextLength ?? 32_000,
183
+ capabilities,
184
+ supportsStreaming: known?.supportsStreaming ?? true,
185
+ supportsFunctionCalling: known?.supportsFunctionCalling ?? true,
186
+ supportsVision: known?.supportsVision ?? lower.includes('vision'),
187
+ benchmarkScore: known?.benchmarkScore,
188
+ maxRequestTokens: known?.maxRequestTokens,
189
+ costPer1kTokens: known?.costPer1kTokens,
190
+ };
191
+ }
192
+ }
193
+ function stripModelPrefix(name) {
194
+ return name.replace(/^models\//, '');
195
+ }
196
+ async function safeText(response) {
197
+ try {
198
+ return await response.text();
199
+ }
200
+ catch {
201
+ return '';
202
+ }
203
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * OpenKernel — Ollama Provider
3
+ *
4
+ * Migrates the existing infinicode Ollama master logic (LAN + Tailscale fallback)
5
+ * into the provider-agnostic kernel interface.
6
+ *
7
+ * Exposes /api/tags models as ProviderModel entries, and routes completion
8
+ * requests through the OpenAI-compatible /v1/chat/completions endpoint.
9
+ */
10
+ import type { CompletionChunk, CompletionRequest, CompletionResult, ProviderCapabilities, ProviderInfo, ProviderInterface, ProviderModel } from '../types.js';
11
+ export interface OllamaProviderOptions {
12
+ id?: string;
13
+ name?: string;
14
+ baseURL: string;
15
+ fallbackURL?: string;
16
+ defaultModel?: string;
17
+ timeoutMs?: number;
18
+ }
19
+ export declare class OllamaProvider implements ProviderInterface {
20
+ info: ProviderInfo;
21
+ private baseURL;
22
+ private fallbackURL?;
23
+ private activeURL;
24
+ private defaultModel?;
25
+ private timeoutMs;
26
+ private cachedModels;
27
+ constructor(options: OllamaProviderOptions);
28
+ capabilities(): Promise<ProviderCapabilities>;
29
+ complete(request: CompletionRequest): Promise<CompletionResult>;
30
+ completeStream(request: CompletionRequest): AsyncIterable<CompletionChunk>;
31
+ refresh(): Promise<void>;
32
+ verify(): Promise<{
33
+ ok: boolean;
34
+ latencyMs: number;
35
+ error?: string;
36
+ }>;
37
+ private fetchModels;
38
+ private toProviderModel;
39
+ private guessContextLength;
40
+ private buildMessages;
41
+ private resolveURL;
42
+ getDefaultModel(): string | undefined;
43
+ getCachedModels(): ProviderModel[];
44
+ }
@@ -0,0 +1,241 @@
1
+ export class OllamaProvider {
2
+ info;
3
+ baseURL;
4
+ fallbackURL;
5
+ activeURL;
6
+ defaultModel;
7
+ timeoutMs;
8
+ cachedModels = [];
9
+ constructor(options) {
10
+ const id = options.id ?? 'ollama';
11
+ this.baseURL = options.baseURL.replace(/\/$/, '');
12
+ this.fallbackURL = options.fallbackURL?.replace(/\/$/, '');
13
+ this.activeURL = this.baseURL;
14
+ this.defaultModel = options.defaultModel;
15
+ this.timeoutMs = options.timeoutMs ?? 10_000;
16
+ this.info = {
17
+ id,
18
+ name: options.name ?? 'Ollama',
19
+ type: 'local',
20
+ healthy: false,
21
+ available: false,
22
+ baseURL: this.baseURL,
23
+ };
24
+ }
25
+ async capabilities() {
26
+ const models = await this.fetchModels();
27
+ return {
28
+ streaming: true,
29
+ functionCalling: false,
30
+ vision: models.some(m => m.capabilities.includes('vision')),
31
+ maxContextLength: Math.max(8192, ...models.map(m => m.contextLength)),
32
+ models,
33
+ };
34
+ }
35
+ async complete(request) {
36
+ const start = Date.now();
37
+ const url = await this.resolveURL();
38
+ const endpoint = `${url}/v1/chat/completions`;
39
+ const body = {
40
+ model: request.model,
41
+ messages: this.buildMessages(request),
42
+ temperature: request.temperature ?? 0.3,
43
+ max_tokens: request.maxTokens ?? 4096,
44
+ stream: false,
45
+ };
46
+ const response = await fetch(endpoint, {
47
+ method: 'POST',
48
+ headers: { 'Content-Type': 'application/json' },
49
+ body: JSON.stringify(body),
50
+ signal: AbortSignal.timeout(this.timeoutMs * 3),
51
+ });
52
+ if (!response.ok) {
53
+ throw new Error(`Ollama completion failed: HTTP ${response.status}`);
54
+ }
55
+ const data = (await response.json());
56
+ const content = data.choices?.[0]?.message?.content ?? '';
57
+ const tokensIn = data.usage?.prompt_tokens ?? 0;
58
+ const tokensOut = data.usage?.completion_tokens ?? 0;
59
+ return {
60
+ content,
61
+ providerId: this.info.id,
62
+ modelId: request.model,
63
+ tokensIn,
64
+ tokensOut,
65
+ durationMs: Date.now() - start,
66
+ };
67
+ }
68
+ async *completeStream(request) {
69
+ const url = await this.resolveURL();
70
+ const endpoint = `${url}/v1/chat/completions`;
71
+ const body = {
72
+ model: request.model,
73
+ messages: this.buildMessages(request),
74
+ temperature: request.temperature ?? 0.3,
75
+ max_tokens: request.maxTokens ?? 4096,
76
+ stream: true,
77
+ };
78
+ const response = await fetch(endpoint, {
79
+ method: 'POST',
80
+ headers: { 'Content-Type': 'application/json' },
81
+ body: JSON.stringify(body),
82
+ signal: AbortSignal.timeout(this.timeoutMs * 6),
83
+ });
84
+ if (!response.ok || !response.body) {
85
+ throw new Error(`Ollama stream failed: HTTP ${response.status}`);
86
+ }
87
+ const reader = response.body.getReader();
88
+ const decoder = new TextDecoder();
89
+ let buffer = '';
90
+ while (true) {
91
+ const { done, value } = await reader.read();
92
+ if (done)
93
+ break;
94
+ buffer += decoder.decode(value, { stream: true });
95
+ const lines = buffer.split('\n');
96
+ buffer = lines.pop() ?? '';
97
+ for (const line of lines) {
98
+ const trimmed = line.trim();
99
+ if (!trimmed.startsWith('data: '))
100
+ continue;
101
+ const payload = trimmed.slice(6);
102
+ if (payload === '[DONE]') {
103
+ yield { delta: '', done: true, providerId: this.info.id, modelId: request.model };
104
+ return;
105
+ }
106
+ try {
107
+ const chunk = JSON.parse(payload);
108
+ const delta = chunk.choices?.[0]?.delta?.content ?? '';
109
+ if (delta) {
110
+ yield { delta, done: false, providerId: this.info.id, modelId: request.model };
111
+ }
112
+ }
113
+ catch {
114
+ // skip malformed
115
+ }
116
+ }
117
+ }
118
+ }
119
+ async refresh() {
120
+ await this.fetchModels();
121
+ }
122
+ async verify() {
123
+ const start = Date.now();
124
+ try {
125
+ await this.resolveURL(); // throws if the Ollama host is unreachable
126
+ const model = this.defaultModel ?? (await this.fetchModels())[0]?.id;
127
+ if (!model)
128
+ return { ok: true, latencyMs: Date.now() - start }; // reachable, no models pulled yet
129
+ await this.complete({ model, prompt: 'ping', maxTokens: 1, temperature: 0 });
130
+ return { ok: true, latencyMs: Date.now() - start };
131
+ }
132
+ catch (err) {
133
+ return { ok: false, latencyMs: Date.now() - start, error: err instanceof Error ? err.message : String(err) };
134
+ }
135
+ }
136
+ async fetchModels() {
137
+ const url = await this.resolveURL();
138
+ const response = await fetch(`${url}/api/tags`, {
139
+ signal: AbortSignal.timeout(this.timeoutMs),
140
+ });
141
+ if (!response.ok) {
142
+ throw new Error(`Ollama tags failed: HTTP ${response.status}`);
143
+ }
144
+ const data = (await response.json());
145
+ this.cachedModels = (data.models ?? []).map(m => this.toProviderModel(m));
146
+ return this.cachedModels;
147
+ }
148
+ toProviderModel(m) {
149
+ const name = m.name;
150
+ const lower = name.toLowerCase();
151
+ const capabilities = ['reasoning'];
152
+ if (lower.includes('code') || lower.includes('coder') || lower.includes('stral')) {
153
+ capabilities.push('coding');
154
+ }
155
+ if (lower.includes('vision') || lower.includes('llava') || lower.includes('bakllava')) {
156
+ capabilities.push('vision', 'ocr');
157
+ }
158
+ const paramSize = m.details?.parameter_size;
159
+ const ctxGuess = this.guessContextLength(name, paramSize);
160
+ return {
161
+ id: name,
162
+ name,
163
+ providerId: this.info.id,
164
+ contextLength: ctxGuess,
165
+ capabilities,
166
+ supportsStreaming: true,
167
+ supportsFunctionCalling: false,
168
+ supportsVision: capabilities.includes('vision'),
169
+ costPer1kTokens: { input: 0, output: 0 },
170
+ metadata: {
171
+ size: m.size,
172
+ parameterSize: paramSize,
173
+ quantization: m.details?.quantization_level,
174
+ family: m.details?.family,
175
+ modifiedAt: m.modified_at,
176
+ },
177
+ };
178
+ }
179
+ guessContextLength(name, paramSize) {
180
+ const lower = name.toLowerCase();
181
+ if (lower.includes('128k'))
182
+ return 128_000;
183
+ if (lower.includes('32k'))
184
+ return 32_000;
185
+ if (lower.includes('mistral') || lower.includes('mixtral'))
186
+ return 32_000;
187
+ if (lower.includes('qwen2.5'))
188
+ return 32_000;
189
+ if (lower.includes('llama3.1'))
190
+ return 128_000;
191
+ if (lower.includes('codestral'))
192
+ return 32_000;
193
+ if (paramSize) {
194
+ const size = parseFloat(paramSize);
195
+ if (size >= 70)
196
+ return 128_000;
197
+ if (size >= 30)
198
+ return 32_000;
199
+ }
200
+ return 8192;
201
+ }
202
+ buildMessages(request) {
203
+ const messages = [];
204
+ if (request.systemPrompt) {
205
+ messages.push({ role: 'system', content: request.systemPrompt });
206
+ }
207
+ if (request.context) {
208
+ const ctxStr = Object.entries(request.context)
209
+ .map(([k, v]) => `${k}: ${typeof v === 'string' ? v : JSON.stringify(v)}`)
210
+ .join('\n');
211
+ messages.push({ role: 'system', content: ctxStr });
212
+ }
213
+ messages.push({ role: 'user', content: request.prompt });
214
+ return messages;
215
+ }
216
+ async resolveURL() {
217
+ const candidates = [this.activeURL, this.fallbackURL, this.baseURL].filter((u, i, arr) => !!u && arr.indexOf(u) === i);
218
+ for (const candidate of candidates) {
219
+ try {
220
+ const response = await fetch(`${candidate}/api/tags`, {
221
+ signal: AbortSignal.timeout(Math.min(this.timeoutMs, 5000)),
222
+ });
223
+ if (response.ok) {
224
+ this.activeURL = candidate;
225
+ this.info.baseURL = candidate;
226
+ return candidate;
227
+ }
228
+ }
229
+ catch {
230
+ // try next
231
+ }
232
+ }
233
+ throw new Error(`Ollama unreachable: ${candidates.join(', ')}`);
234
+ }
235
+ getDefaultModel() {
236
+ return this.defaultModel;
237
+ }
238
+ getCachedModels() {
239
+ return this.cachedModels;
240
+ }
241
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * OpenKernel — OpenAI-Compatible Provider
3
+ *
4
+ * Generic provider for any endpoint speaking the OpenAI chat completions API.
5
+ * Powers: OpenRouter, Groq, GitHub Models, NVIDIA, HF, vLLM, LM Studio, SGLang, etc.
6
+ */
7
+ import type { CompletionChunk, CompletionRequest, CompletionResult, ProviderCapabilities, ProviderInfo, ProviderInterface, ProviderModel } from '../types.js';
8
+ export interface OpenAICompatibleOptions {
9
+ id: string;
10
+ name: string;
11
+ type?: 'local' | 'cloud';
12
+ baseURL: string;
13
+ apiKey?: string;
14
+ headerName?: string;
15
+ timeoutMs?: number;
16
+ knownModels?: Array<Partial<ProviderModel> & {
17
+ id: string;
18
+ }>;
19
+ }
20
+ export declare class OpenAICompatibleProvider implements ProviderInterface {
21
+ info: ProviderInfo;
22
+ private baseURL;
23
+ private apiKey?;
24
+ private headerName;
25
+ private timeoutMs;
26
+ private knownModels;
27
+ constructor(options: OpenAICompatibleOptions);
28
+ capabilities(): Promise<ProviderCapabilities>;
29
+ complete(request: CompletionRequest): Promise<CompletionResult>;
30
+ completeStream(request: CompletionRequest): AsyncIterable<CompletionChunk>;
31
+ refresh(): Promise<void>;
32
+ verify(): Promise<{
33
+ ok: boolean;
34
+ latencyMs: number;
35
+ error?: string;
36
+ rateLimitTokens?: number;
37
+ }>;
38
+ private parseTokenLimit;
39
+ private fetchModels;
40
+ private toProviderModel;
41
+ private headers;
42
+ private buildMessages;
43
+ }