@tenex-chat/backend 0.9.5 → 0.9.6
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.
- package/README.md +5 -1
- package/dist/daemon-wrapper.cjs +47 -0
- package/dist/index.js +59268 -0
- package/dist/wrapper.js +171 -0
- package/package.json +19 -27
- package/src/agents/AgentRegistry.ts +9 -7
- package/src/agents/AgentStorage.ts +24 -1
- package/src/agents/agent-installer.ts +6 -0
- package/src/agents/agent-loader.ts +7 -2
- package/src/agents/constants.ts +10 -2
- package/src/agents/execution/AgentExecutor.ts +35 -6
- package/src/agents/execution/StreamCallbacks.ts +53 -13
- package/src/agents/execution/StreamExecutionHandler.ts +110 -16
- package/src/agents/execution/StreamSetup.ts +19 -9
- package/src/agents/execution/ToolEventHandlers.ts +112 -0
- package/src/agents/role-categories.ts +53 -0
- package/src/agents/types/runtime.ts +7 -0
- package/src/agents/types/storage.ts +7 -0
- package/src/commands/agent/import/openclaw-distiller.ts +63 -7
- package/src/commands/agent/import/openclaw-reader.ts +54 -0
- package/src/commands/agent/import/openclaw.ts +120 -29
- package/src/commands/agent/index.ts +83 -2
- package/src/commands/setup/display.ts +123 -0
- package/src/commands/setup/embed.ts +13 -13
- package/src/commands/setup/global-system-prompt.ts +15 -17
- package/src/commands/setup/image.ts +17 -20
- package/src/commands/setup/interactive.ts +37 -20
- package/src/commands/setup/llm.ts +12 -7
- package/src/commands/setup/onboarding.ts +1580 -248
- package/src/commands/setup/providers.ts +3 -3
- package/src/conversations/ConversationStore.ts +23 -2
- package/src/conversations/MessageBuilder.ts +51 -73
- package/src/conversations/formatters/utils/conversation-transcript-formatter.ts +425 -0
- package/src/conversations/search/embeddings/ConversationEmbeddingService.ts +40 -98
- package/src/conversations/search/embeddings/ConversationIndexingJob.ts +40 -52
- package/src/conversations/services/ConversationSummarizer.ts +1 -2
- package/src/conversations/types.ts +11 -0
- package/src/daemon/Daemon.ts +78 -57
- package/src/daemon/ProjectRuntime.ts +6 -12
- package/src/daemon/SubscriptionManager.ts +13 -0
- package/src/daemon/index.ts +0 -1
- package/src/event-handler/index.ts +1 -0
- package/src/index.ts +20 -1
- package/src/llm/ChunkHandler.ts +1 -1
- package/src/llm/FinishHandler.ts +28 -4
- package/src/llm/LLMConfigEditor.ts +218 -106
- package/src/llm/index.ts +0 -4
- package/src/llm/meta/MetaModelResolver.ts +3 -18
- package/src/llm/middleware/message-sanitizer.ts +153 -0
- package/src/llm/providers/ollama-models.ts +0 -38
- package/src/llm/service.ts +50 -15
- package/src/llm/types.ts +0 -12
- package/src/llm/utils/ConfigurationManager.ts +88 -465
- package/src/llm/utils/ConfigurationTester.ts +42 -185
- package/src/llm/utils/ModelSelector.ts +156 -92
- package/src/llm/utils/ProviderConfigUI.ts +10 -141
- package/src/llm/utils/models-dev-cache.ts +102 -23
- package/src/llm/utils/provider-select-prompt.ts +284 -0
- package/src/llm/utils/provider-setup.ts +81 -34
- package/src/llm/utils/variant-list-prompt.ts +361 -0
- package/src/nostr/AgentEventDecoder.ts +1 -0
- package/src/nostr/AgentEventEncoder.ts +37 -0
- package/src/nostr/AgentProfilePublisher.ts +13 -0
- package/src/nostr/AgentPublisher.ts +26 -0
- package/src/nostr/kinds.ts +1 -0
- package/src/nostr/ndkClient.ts +4 -1
- package/src/nostr/types.ts +12 -0
- package/src/prompts/fragments/25-rag-instructions.ts +22 -21
- package/src/prompts/fragments/31-agents-md-guidance.ts +7 -21
- package/src/prompts/fragments/index.ts +2 -0
- package/src/prompts/utils/systemPromptBuilder.ts +18 -28
- package/src/services/AgentDefinitionMonitor.ts +8 -0
- package/src/services/ConfigService.ts +34 -0
- package/src/services/PubkeyService.ts +7 -1
- package/src/services/compression/CompressionService.ts +133 -74
- package/src/services/compression/compression-utils.ts +110 -19
- package/src/services/config/types.ts +0 -6
- package/src/services/dispatch/AgentDispatchService.ts +79 -0
- package/src/services/intervention/InterventionService.ts +78 -5
- package/src/services/nip46/Nip46SigningService.ts +30 -1
- package/src/services/projects/ProjectContext.ts +8 -6
- package/src/services/rag/RAGCollectionRegistry.ts +199 -0
- package/src/services/rag/RAGDatabaseService.ts +2 -7
- package/src/services/rag/RAGOperations.ts +25 -45
- package/src/services/rag/RAGService.ts +0 -31
- package/src/services/rag/RagSubscriptionService.ts +71 -122
- package/src/services/rag/rag-utils.ts +13 -0
- package/src/services/ral/RALRegistry.ts +25 -184
- package/src/services/reports/ReportEmbeddingService.ts +63 -113
- package/src/services/search/UnifiedSearchService.ts +115 -4
- package/src/services/search/index.ts +1 -0
- package/src/services/search/projectFilter.ts +20 -4
- package/src/services/search/providers/ConversationSearchProvider.ts +1 -0
- package/src/services/search/providers/GenericCollectionSearchProvider.ts +81 -0
- package/src/services/search/providers/LessonSearchProvider.ts +1 -8
- package/src/services/search/providers/ReportSearchProvider.ts +1 -0
- package/src/services/search/types.ts +24 -3
- package/src/services/trust-pubkeys/SystemPubkeyListService.ts +148 -0
- package/src/services/trust-pubkeys/TrustPubkeyService.ts +70 -9
- package/src/telemetry/setup.ts +2 -13
- package/src/tools/implementations/ask.ts +3 -3
- package/src/tools/implementations/conversation_get.ts +28 -268
- package/src/tools/implementations/fs_grep.ts +6 -6
- package/src/tools/implementations/fs_read.ts +2 -0
- package/src/tools/implementations/fs_write.ts +2 -0
- package/src/tools/implementations/learn.ts +38 -50
- package/src/tools/implementations/rag_add_documents.ts +6 -4
- package/src/tools/implementations/rag_create_collection.ts +37 -4
- package/src/tools/implementations/rag_delete_collection.ts +9 -0
- package/src/tools/implementations/{search.ts → rag_search.ts} +31 -25
- package/src/tools/registry.ts +7 -8
- package/src/tools/types.ts +11 -2
- package/src/tools/utils/transcript-args.ts +13 -0
- package/src/utils/cli-theme.ts +13 -0
- package/src/utils/logger.ts +55 -0
- package/src/utils/metadataKeys.ts +17 -0
- package/src/utils/sqlEscaping.ts +39 -0
- package/src/wrapper.ts +7 -3
- package/dist/src/index.js +0 -46790
- package/dist/tenex-backend-wrapper.cjs +0 -3
- package/src/agents/execution/constants.ts +0 -16
- package/src/agents/execution/index.ts +0 -3
- package/src/agents/index.ts +0 -4
- package/src/commands/agent.ts +0 -235
- package/src/conversations/formatters/DelegationXmlFormatter.ts +0 -64
- package/src/conversations/formatters/index.ts +0 -9
- package/src/conversations/index.ts +0 -2
- package/src/conversations/utils/content-utils.ts +0 -69
- package/src/daemon/UnixSocketTransport.ts +0 -318
- package/src/event-handler/newConversation.ts +0 -165
- package/src/events/NDKProjectStatus.ts +0 -384
- package/src/events/index.ts +0 -4
- package/src/lib/json-parser.ts +0 -30
- package/src/llm/RecordingState.ts +0 -37
- package/src/llm/StreamPublisher.ts +0 -40
- package/src/llm/middleware/flight-recorder.ts +0 -188
- package/src/llm/utils/claudeCodePromptCompiler.ts +0 -141
- package/src/nostr/constants.ts +0 -38
- package/src/prompts/core/index.ts +0 -3
- package/src/prompts/index.ts +0 -21
- package/src/services/image/index.ts +0 -12
- package/src/services/status/index.ts +0 -11
- package/src/telemetry/diagnostics.ts +0 -27
- package/src/tools/implementations/rag_query.ts +0 -107
- package/src/types/index.ts +0 -46
- package/src/utils/agentFetcher.ts +0 -107
- package/src/utils/conversation-utils.ts +0 -1
- package/src/utils/process.ts +0 -49
|
@@ -1,71 +1,52 @@
|
|
|
1
1
|
import type { CompleteEvent, ContentEvent, StreamErrorEvent } from "@/llm/types";
|
|
2
2
|
import { config as configService } from "@/services/ConfigService";
|
|
3
3
|
import type { TenexLLMs } from "@/services/config/types";
|
|
4
|
-
import { isMetaModelConfiguration } from "@/services/config/types";
|
|
5
|
-
import chalk from "chalk";
|
|
6
|
-
import inquirer from "inquirer";
|
|
7
|
-
import { z } from "zod";
|
|
8
4
|
import { llmServiceFactory } from "../LLMServiceFactory";
|
|
9
5
|
|
|
10
|
-
/**
|
|
11
|
-
* Extended type for editor use - includes providers
|
|
12
|
-
*/
|
|
13
6
|
type TenexLLMsWithProviders = TenexLLMs & {
|
|
14
7
|
providers: Record<string, { apiKey: string | string[] }>;
|
|
15
8
|
};
|
|
16
9
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
10
|
+
export type TestResult = { success: true } | { success: false; error: string };
|
|
11
|
+
|
|
12
|
+
function silenceConsole(): () => void {
|
|
13
|
+
const origLog = console.log;
|
|
14
|
+
const origWarn = console.warn;
|
|
15
|
+
const origError = console.error;
|
|
16
|
+
const origInfo = console.info;
|
|
17
|
+
const noop = () => {};
|
|
18
|
+
console.log = noop;
|
|
19
|
+
console.warn = noop;
|
|
20
|
+
console.error = noop;
|
|
21
|
+
console.info = noop;
|
|
22
|
+
return () => {
|
|
23
|
+
console.log = origLog;
|
|
24
|
+
console.warn = origWarn;
|
|
25
|
+
console.error = origError;
|
|
26
|
+
console.info = origInfo;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
30
|
+
export class ConfigurationTester {
|
|
31
|
+
/**
|
|
32
|
+
* Run a silent test against a configuration. Returns pass/fail result.
|
|
33
|
+
* All console output is suppressed during the test.
|
|
34
|
+
*/
|
|
35
|
+
static async runTest(llmsConfig: TenexLLMsWithProviders, configName: string): Promise<TestResult> {
|
|
36
|
+
if (!llmsConfig.configurations[configName]) {
|
|
37
|
+
return { success: false, error: "configuration not found" };
|
|
27
38
|
}
|
|
28
39
|
|
|
29
|
-
const
|
|
30
|
-
{
|
|
31
|
-
type: "select",
|
|
32
|
-
name: "name",
|
|
33
|
-
message: "Select configuration to test:",
|
|
34
|
-
choices: configNames.map((n) => {
|
|
35
|
-
const cfg = llmsConfig.configurations[n];
|
|
36
|
-
const isMeta = isMetaModelConfiguration(cfg);
|
|
37
|
-
const label = n === llmsConfig.default ? `${n} (default)` : n;
|
|
38
|
-
return {
|
|
39
|
-
name: isMeta ? `${label} [meta model]` : label,
|
|
40
|
-
value: n,
|
|
41
|
-
};
|
|
42
|
-
}),
|
|
43
|
-
},
|
|
44
|
-
]);
|
|
40
|
+
const restoreConsole = silenceConsole();
|
|
45
41
|
|
|
46
42
|
try {
|
|
47
|
-
// Load full config first (needed for getLLMConfig and MCP server configs)
|
|
48
43
|
await configService.loadConfig();
|
|
44
|
+
const llmConfig = configService.getLLMConfig(configName);
|
|
49
45
|
|
|
50
|
-
// Use getLLMConfig to resolve meta models to their default variant
|
|
51
|
-
const llmConfig = configService.getLLMConfig(name);
|
|
52
|
-
const rawConfig = llmsConfig.configurations[name];
|
|
53
|
-
const isMeta = isMetaModelConfiguration(rawConfig);
|
|
54
|
-
|
|
55
|
-
console.log(chalk.yellow(`\nTesting configuration "${name}"${isMeta ? " (meta model - using default variant)" : ""}...`));
|
|
56
|
-
console.log(chalk.gray(`Provider: ${llmConfig.provider}, Model: ${llmConfig.model}`));
|
|
57
|
-
|
|
58
|
-
// Initialize providers before testing
|
|
59
46
|
await llmServiceFactory.initializeProviders(llmsConfig.providers);
|
|
60
|
-
|
|
61
|
-
// Create the service using the factory
|
|
62
47
|
const service = llmServiceFactory.createService(llmConfig);
|
|
63
48
|
|
|
64
|
-
|
|
65
|
-
const handleContent = (event: ContentEvent): void => {
|
|
66
|
-
process.stdout.write(chalk.cyan(event.delta));
|
|
67
|
-
};
|
|
68
|
-
service.on("content", handleContent);
|
|
49
|
+
service.on("content", (_event: ContentEvent) => {});
|
|
69
50
|
|
|
70
51
|
const completePromise = new Promise<CompleteEvent>((resolve) => {
|
|
71
52
|
service.once("complete", resolve);
|
|
@@ -76,154 +57,30 @@ export class ConfigurationTester {
|
|
|
76
57
|
});
|
|
77
58
|
});
|
|
78
59
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
console.log(chalk.white("Response: "));
|
|
82
|
-
const [, completeEvent] = await Promise.all([
|
|
60
|
+
await Promise.all([
|
|
83
61
|
service.stream(
|
|
84
62
|
[{ role: "user", content: "Say 'Hello, TENEX!' in exactly those words." }],
|
|
85
63
|
{}
|
|
86
64
|
),
|
|
87
|
-
|
|
65
|
+
Promise.race([completePromise, errorPromise]),
|
|
88
66
|
]);
|
|
89
67
|
|
|
90
|
-
|
|
91
|
-
console.log(chalk.green("✅ Test successful!"));
|
|
92
|
-
|
|
93
|
-
// Show usage stats if available
|
|
94
|
-
if (completeEvent.usage) {
|
|
95
|
-
const usage = completeEvent.usage;
|
|
96
|
-
const inputTokens = usage.inputTokens ?? "?";
|
|
97
|
-
const outputTokens = usage.outputTokens ?? "?";
|
|
98
|
-
const totalTokens = usage.totalTokens ?? "?";
|
|
99
|
-
console.log(
|
|
100
|
-
chalk.gray(`\nTokens: ${inputTokens} + ${outputTokens} = ${totalTokens}`)
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
105
|
-
} catch (error: unknown) {
|
|
106
|
-
console.log(chalk.red("\n❌ Test failed!"));
|
|
107
|
-
|
|
108
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
109
|
-
if (errorMessage) {
|
|
110
|
-
console.log(chalk.red(`Error: ${errorMessage}`));
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Check for common issues
|
|
114
|
-
if (errorMessage?.includes("401") || errorMessage?.includes("Unauthorized")) {
|
|
115
|
-
console.log(chalk.yellow("\n💡 Invalid or expired API key"));
|
|
116
|
-
} else if (errorMessage?.includes("404")) {
|
|
117
|
-
console.log(chalk.yellow(`\n💡 Model for configuration '${name}' may not be available`));
|
|
118
|
-
} else if (errorMessage?.includes("rate limit")) {
|
|
119
|
-
console.log(chalk.yellow("\n💡 Rate limit hit. Please wait and try again"));
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Test a configuration for summarization using generateObject
|
|
128
|
-
*/
|
|
129
|
-
static async testSummarization(llmsConfig: TenexLLMsWithProviders, configName: string): Promise<void> {
|
|
130
|
-
const rawConfig = llmsConfig.configurations[configName];
|
|
131
|
-
if (!rawConfig) {
|
|
132
|
-
console.log(chalk.red(`❌ Configuration "${configName}" not found`));
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Use getLLMConfig to resolve meta models to their default variant
|
|
137
|
-
const llmConfig = configService.getLLMConfig(configName);
|
|
138
|
-
const isMeta = isMetaModelConfiguration(rawConfig);
|
|
139
|
-
|
|
140
|
-
console.log(chalk.yellow(`\nTesting summarization with "${configName}"${isMeta ? " (meta model - using default variant)" : ""}...`));
|
|
141
|
-
console.log(chalk.gray(`Provider: ${llmConfig.provider}, Model: ${llmConfig.model}`));
|
|
142
|
-
|
|
143
|
-
// Schema that mimics what we'd use for kind 513 summaries
|
|
144
|
-
const SummarySchema = z.object({
|
|
145
|
-
title: z.string().describe("A brief title for the summary"),
|
|
146
|
-
summary: z.string().describe("A concise summary of the conversation"),
|
|
147
|
-
keyPoints: z.array(z.string()).describe("Key points from the conversation"),
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
try {
|
|
151
|
-
// Load full config (needed for MCP server configs in agent providers)
|
|
152
|
-
await configService.loadConfig();
|
|
153
|
-
|
|
154
|
-
// Initialize providers before testing
|
|
155
|
-
await llmServiceFactory.initializeProviders(llmsConfig.providers);
|
|
156
|
-
|
|
157
|
-
// Create the service using the factory
|
|
158
|
-
const service = llmServiceFactory.createService(llmConfig);
|
|
159
|
-
|
|
160
|
-
console.log(chalk.cyan("📡 Testing generateObject..."));
|
|
161
|
-
|
|
162
|
-
const testConversation = `
|
|
163
|
-
User: I need help setting up authentication for my web app.
|
|
164
|
-
Assistant: I can help with that. What authentication method are you considering?
|
|
165
|
-
User: I'm thinking OAuth with Google and GitHub.
|
|
166
|
-
Assistant: Great choice. OAuth is secure and user-friendly. Let me outline the steps...
|
|
167
|
-
`;
|
|
168
|
-
|
|
169
|
-
const result = await service.generateObject(
|
|
170
|
-
[
|
|
171
|
-
{
|
|
172
|
-
role: "system",
|
|
173
|
-
content:
|
|
174
|
-
"You are a helpful assistant that summarizes conversations. Generate a structured summary.",
|
|
175
|
-
},
|
|
176
|
-
{
|
|
177
|
-
role: "user",
|
|
178
|
-
content: `Summarize this conversation:\n${testConversation}`,
|
|
179
|
-
},
|
|
180
|
-
],
|
|
181
|
-
SummarySchema
|
|
182
|
-
);
|
|
183
|
-
|
|
184
|
-
console.log(chalk.green("\n✅ generateObject test successful!"));
|
|
185
|
-
console.log(chalk.white("\nGenerated summary:"));
|
|
186
|
-
console.log(chalk.cyan(` Title: ${result.object.title}`));
|
|
187
|
-
console.log(chalk.cyan(` Summary: ${result.object.summary}`));
|
|
188
|
-
console.log(chalk.cyan(" Key Points:"));
|
|
189
|
-
for (const point of result.object.keyPoints) {
|
|
190
|
-
console.log(chalk.cyan(` • ${point}`));
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Show usage stats if available
|
|
194
|
-
if (result.usage) {
|
|
195
|
-
const { inputTokens, outputTokens, totalTokens } = result.usage;
|
|
196
|
-
console.log(chalk.gray(`\nTokens: ${inputTokens} + ${outputTokens} = ${totalTokens}`));
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
68
|
+
return { success: true };
|
|
200
69
|
} catch (error: unknown) {
|
|
201
|
-
console.log(chalk.red("\n❌ generateObject test failed!"));
|
|
202
|
-
|
|
203
70
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
204
|
-
|
|
205
|
-
console.log(chalk.red(`Error: ${errorMessage}`));
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Check for common issues
|
|
71
|
+
let hint = errorMessage;
|
|
209
72
|
if (errorMessage?.includes("401") || errorMessage?.includes("Unauthorized")) {
|
|
210
|
-
|
|
73
|
+
hint = "invalid or expired API key";
|
|
211
74
|
} else if (errorMessage?.includes("404")) {
|
|
212
|
-
|
|
75
|
+
hint = "model not available";
|
|
213
76
|
} else if (errorMessage?.includes("rate limit")) {
|
|
214
|
-
|
|
215
|
-
} else if (
|
|
216
|
-
errorMessage?.includes("structured output") ||
|
|
217
|
-
errorMessage?.includes("json")
|
|
218
|
-
) {
|
|
219
|
-
console.log(
|
|
220
|
-
chalk.yellow(
|
|
221
|
-
"\n💡 This model may not support structured output (generateObject)"
|
|
222
|
-
)
|
|
223
|
-
);
|
|
77
|
+
hint = "rate limited";
|
|
224
78
|
}
|
|
225
|
-
|
|
226
|
-
|
|
79
|
+
return { success: false, error: hint };
|
|
80
|
+
} finally {
|
|
81
|
+
// Delay restore so async logger stragglers are swallowed
|
|
82
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
83
|
+
restoreConsole();
|
|
227
84
|
}
|
|
228
85
|
}
|
|
229
86
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import inquirer from "inquirer";
|
|
3
|
-
import {
|
|
3
|
+
import { amber, inquirerTheme } from "@/utils/cli-theme";
|
|
4
|
+
import { fetchOllamaModels } from "../providers/ollama-models";
|
|
4
5
|
import { fetchOpenRouterModels, getPopularModels } from "../providers/openrouter-models";
|
|
6
|
+
import { ensureCacheLoaded, getProviderModels } from "./models-dev-cache";
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Utility class for interactive model selection
|
|
@@ -9,7 +11,7 @@ import { fetchOpenRouterModels, getPopularModels } from "../providers/openrouter
|
|
|
9
11
|
*/
|
|
10
12
|
export class ModelSelector {
|
|
11
13
|
/**
|
|
12
|
-
* Select an Ollama model interactively
|
|
14
|
+
* Select an Ollama model interactively with fuzzy search
|
|
13
15
|
*/
|
|
14
16
|
static async selectOllamaModel(currentModel?: string): Promise<string> {
|
|
15
17
|
console.log(chalk.gray("Fetching available Ollama models..."));
|
|
@@ -19,23 +21,38 @@ export class ModelSelector {
|
|
|
19
21
|
if (ollamaModels.length > 0) {
|
|
20
22
|
console.log(chalk.green(`✓ Found ${ollamaModels.length} installed models`));
|
|
21
23
|
|
|
22
|
-
const
|
|
24
|
+
const allChoices = [
|
|
23
25
|
...ollamaModels.map((m) => ({
|
|
24
26
|
name: `${m.name} ${chalk.gray(`(${m.size})`)}`,
|
|
25
27
|
value: m.name,
|
|
28
|
+
short: m.name,
|
|
26
29
|
})),
|
|
27
|
-
|
|
28
|
-
{ name: chalk.cyan("→ Type model name manually"), value: "__manual__" },
|
|
30
|
+
{ name: chalk.cyan("→ Type model name manually"), value: "__manual__", short: "manual" },
|
|
29
31
|
];
|
|
30
32
|
|
|
31
33
|
const { selectedModel } = await inquirer.prompt([
|
|
32
34
|
{
|
|
33
|
-
type: "
|
|
35
|
+
type: "search",
|
|
34
36
|
name: "selectedModel",
|
|
35
37
|
message: "Select model:",
|
|
36
|
-
|
|
38
|
+
source: (term: string | undefined) => {
|
|
39
|
+
if (!term) return allChoices;
|
|
40
|
+
const lower = term.toLowerCase();
|
|
41
|
+
const filtered = allChoices.filter(
|
|
42
|
+
(c) =>
|
|
43
|
+
c.value === "__manual__" ||
|
|
44
|
+
c.value.toLowerCase().includes(lower)
|
|
45
|
+
);
|
|
46
|
+
return filtered;
|
|
47
|
+
},
|
|
37
48
|
default: currentModel,
|
|
38
|
-
|
|
49
|
+
theme: {
|
|
50
|
+
...inquirerTheme,
|
|
51
|
+
style: {
|
|
52
|
+
...inquirerTheme.style,
|
|
53
|
+
searchTerm: (text: string) => amber(text || chalk.gray("Search models...")),
|
|
54
|
+
},
|
|
55
|
+
},
|
|
39
56
|
},
|
|
40
57
|
]);
|
|
41
58
|
|
|
@@ -45,44 +62,13 @@ export class ModelSelector {
|
|
|
45
62
|
|
|
46
63
|
return selectedModel;
|
|
47
64
|
}
|
|
48
|
-
console.log(
|
|
49
|
-
console.log(chalk.gray("
|
|
50
|
-
|
|
51
|
-
const popular = getPopularOllamaModels();
|
|
52
|
-
const choices = [];
|
|
53
|
-
for (const [category, models] of Object.entries(popular)) {
|
|
54
|
-
choices.push(new inquirer.Separator(`--- ${category} ---`));
|
|
55
|
-
choices.push(
|
|
56
|
-
...models.map((m) => ({
|
|
57
|
-
name: m,
|
|
58
|
-
value: m,
|
|
59
|
-
}))
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
choices.push(new inquirer.Separator());
|
|
64
|
-
choices.push({ name: chalk.cyan("→ Type model name manually"), value: "__manual__" });
|
|
65
|
-
|
|
66
|
-
const { selectedModel } = await inquirer.prompt([
|
|
67
|
-
{
|
|
68
|
-
type: "select",
|
|
69
|
-
name: "selectedModel",
|
|
70
|
-
message: "Select model:",
|
|
71
|
-
default: currentModel,
|
|
72
|
-
choices,
|
|
73
|
-
pageSize: 15,
|
|
74
|
-
},
|
|
75
|
-
]);
|
|
76
|
-
|
|
77
|
-
if (selectedModel === "__manual__") {
|
|
78
|
-
return await ModelSelector.promptManualModel(currentModel || "llama3.1:8b");
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return selectedModel;
|
|
65
|
+
console.log(amber("⚠️ No local Ollama models were discovered."));
|
|
66
|
+
console.log(chalk.gray("Check `ollama list` and run `ollama pull <model>` if needed."));
|
|
67
|
+
return await ModelSelector.promptManualModel(currentModel || "llama3.1:8b");
|
|
82
68
|
}
|
|
83
69
|
|
|
84
70
|
/**
|
|
85
|
-
* Select an OpenRouter model interactively
|
|
71
|
+
* Select an OpenRouter model interactively with fuzzy search
|
|
86
72
|
*/
|
|
87
73
|
static async selectOpenRouterModel(currentModel?: string): Promise<string> {
|
|
88
74
|
console.log(chalk.gray("Fetching available OpenRouter models..."));
|
|
@@ -92,51 +78,42 @@ export class ModelSelector {
|
|
|
92
78
|
if (openRouterModels.length > 0) {
|
|
93
79
|
console.log(chalk.green(`✓ Found ${openRouterModels.length} available models`));
|
|
94
80
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
if (!modelsByProvider[provider]) {
|
|
100
|
-
modelsByProvider[provider] = [];
|
|
101
|
-
}
|
|
102
|
-
modelsByProvider[provider].push(model);
|
|
103
|
-
}
|
|
81
|
+
const allChoices = openRouterModels.map((model) => {
|
|
82
|
+
const pricing = `$${model.pricing.prompt}/$${model.pricing.completion}/1M`;
|
|
83
|
+
const context = `${Math.round(model.context_length / 1000)}k`;
|
|
84
|
+
const freeTag = model.id.endsWith(":free") ? chalk.green(" [FREE]") : "";
|
|
104
85
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
new inquirer.Separator(chalk.yellow(`--- ${provider.toUpperCase()} ---`))
|
|
112
|
-
);
|
|
113
|
-
const providerModels = modelsByProvider[provider];
|
|
114
|
-
|
|
115
|
-
for (const model of providerModels) {
|
|
116
|
-
const pricing = `$${model.pricing.prompt}/$${model.pricing.completion}/1M`;
|
|
117
|
-
const context = `${Math.round(model.context_length / 1000)}k`;
|
|
118
|
-
const freeTag = model.id.endsWith(":free") ? chalk.green(" [FREE]") : "";
|
|
119
|
-
|
|
120
|
-
choices.push({
|
|
121
|
-
name: `${model.id}${freeTag} ${chalk.gray(`- ${context} context, ${pricing}`)}`,
|
|
122
|
-
value: model.id,
|
|
123
|
-
short: model.id,
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
}
|
|
86
|
+
return {
|
|
87
|
+
name: `${model.id}${freeTag} ${chalk.gray(`- ${context} ctx, ${pricing}`)}`,
|
|
88
|
+
value: model.id,
|
|
89
|
+
short: model.id,
|
|
90
|
+
};
|
|
91
|
+
});
|
|
127
92
|
|
|
128
|
-
|
|
129
|
-
choices.push({ name: chalk.cyan("→ Type model ID manually"), value: "__manual__" });
|
|
93
|
+
allChoices.push({ name: chalk.cyan("→ Type model ID manually"), value: "__manual__", short: "manual" });
|
|
130
94
|
|
|
131
95
|
const { selectedModel } = await inquirer.prompt([
|
|
132
96
|
{
|
|
133
|
-
type: "
|
|
97
|
+
type: "search",
|
|
134
98
|
name: "selectedModel",
|
|
135
99
|
message: "Select model:",
|
|
136
|
-
|
|
100
|
+
source: (term: string | undefined) => {
|
|
101
|
+
if (!term) return allChoices;
|
|
102
|
+
const lower = term.toLowerCase();
|
|
103
|
+
return allChoices.filter(
|
|
104
|
+
(c) =>
|
|
105
|
+
c.value === "__manual__" ||
|
|
106
|
+
c.value.toLowerCase().includes(lower)
|
|
107
|
+
);
|
|
108
|
+
},
|
|
137
109
|
default: currentModel,
|
|
138
|
-
|
|
139
|
-
|
|
110
|
+
theme: {
|
|
111
|
+
...inquirerTheme,
|
|
112
|
+
style: {
|
|
113
|
+
...inquirerTheme.style,
|
|
114
|
+
searchTerm: (text: string) => amber(text || chalk.gray("Search models...")),
|
|
115
|
+
},
|
|
116
|
+
},
|
|
140
117
|
},
|
|
141
118
|
]);
|
|
142
119
|
|
|
@@ -146,7 +123,7 @@ export class ModelSelector {
|
|
|
146
123
|
|
|
147
124
|
return selectedModel;
|
|
148
125
|
}
|
|
149
|
-
console.log(
|
|
126
|
+
console.log(amber("⚠️ Failed to fetch models from OpenRouter API"));
|
|
150
127
|
console.log(
|
|
151
128
|
chalk.gray("You can still enter a model ID manually or select from popular models.")
|
|
152
129
|
);
|
|
@@ -160,30 +137,44 @@ export class ModelSelector {
|
|
|
160
137
|
{ name: "Quick select from popular models", value: "quick" },
|
|
161
138
|
{ name: "Type model ID manually", value: "manual" },
|
|
162
139
|
],
|
|
140
|
+
theme: inquirerTheme,
|
|
163
141
|
},
|
|
164
142
|
]);
|
|
165
143
|
|
|
166
144
|
if (selectionMethod === "quick") {
|
|
167
145
|
const popular = getPopularModels();
|
|
168
|
-
const
|
|
146
|
+
const popularChoices: Array<{ name: string; value: string; short: string }> = [];
|
|
169
147
|
for (const [category, models] of Object.entries(popular)) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
name: m,
|
|
148
|
+
for (const m of models) {
|
|
149
|
+
popularChoices.push({
|
|
150
|
+
name: `${m} ${chalk.gray(`(${category})`)}`,
|
|
174
151
|
value: m,
|
|
175
|
-
|
|
176
|
-
|
|
152
|
+
short: m,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
177
155
|
}
|
|
178
156
|
|
|
179
157
|
const { selectedModel } = await inquirer.prompt([
|
|
180
158
|
{
|
|
181
|
-
type: "
|
|
159
|
+
type: "search",
|
|
182
160
|
name: "selectedModel",
|
|
183
161
|
message: "Select model:",
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
162
|
+
source: (term: string | undefined) => {
|
|
163
|
+
if (!term) return popularChoices;
|
|
164
|
+
const lower = term.toLowerCase();
|
|
165
|
+
return popularChoices.filter(
|
|
166
|
+
(c) =>
|
|
167
|
+
c.value.toLowerCase().includes(lower) ||
|
|
168
|
+
c.name.toLowerCase().includes(lower)
|
|
169
|
+
);
|
|
170
|
+
},
|
|
171
|
+
theme: {
|
|
172
|
+
...inquirerTheme,
|
|
173
|
+
style: {
|
|
174
|
+
...inquirerTheme.style,
|
|
175
|
+
searchTerm: (text: string) => amber(text || chalk.gray("Search models...")),
|
|
176
|
+
},
|
|
177
|
+
},
|
|
187
178
|
},
|
|
188
179
|
]);
|
|
189
180
|
return selectedModel;
|
|
@@ -191,6 +182,78 @@ export class ModelSelector {
|
|
|
191
182
|
return await ModelSelector.promptManualModel(currentModel || "openai/gpt-4");
|
|
192
183
|
}
|
|
193
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Select a model from models.dev data (for Anthropic, OpenAI, etc.)
|
|
187
|
+
* Returns { id, name } so callers can use the human name for config naming.
|
|
188
|
+
*/
|
|
189
|
+
static async selectModelsDevModel(
|
|
190
|
+
provider: string,
|
|
191
|
+
defaultModel?: string
|
|
192
|
+
): Promise<{ id: string; name: string }> {
|
|
193
|
+
await ensureCacheLoaded();
|
|
194
|
+
const models = getProviderModels(provider);
|
|
195
|
+
|
|
196
|
+
if (models.length > 0) {
|
|
197
|
+
const allChoices = [
|
|
198
|
+
...models.map((m) => {
|
|
199
|
+
const ctx = m.limit?.context
|
|
200
|
+
? `${Math.round(m.limit.context / 1000)}k ctx`
|
|
201
|
+
: "";
|
|
202
|
+
const pricing = m.cost
|
|
203
|
+
? `$${m.cost.input}/$${m.cost.output}/M`
|
|
204
|
+
: "";
|
|
205
|
+
const meta = [ctx, pricing].filter(Boolean).join(", ");
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
name: `${m.name} ${chalk.gray(`(${m.id})`)} ${chalk.gray(meta ? `- ${meta}` : "")}`,
|
|
209
|
+
value: m.id,
|
|
210
|
+
short: m.id,
|
|
211
|
+
humanName: m.name,
|
|
212
|
+
};
|
|
213
|
+
}),
|
|
214
|
+
{ name: chalk.cyan("→ Type model ID manually"), value: "__manual__", short: "manual", humanName: "" },
|
|
215
|
+
];
|
|
216
|
+
|
|
217
|
+
const { selectedModel } = await inquirer.prompt([
|
|
218
|
+
{
|
|
219
|
+
type: "search",
|
|
220
|
+
name: "selectedModel",
|
|
221
|
+
message: "Select model:",
|
|
222
|
+
source: (term: string | undefined) => {
|
|
223
|
+
if (!term) return allChoices;
|
|
224
|
+
const lower = term.toLowerCase();
|
|
225
|
+
return allChoices.filter(
|
|
226
|
+
(c) =>
|
|
227
|
+
c.value === "__manual__" ||
|
|
228
|
+
c.value.toLowerCase().includes(lower) ||
|
|
229
|
+
c.humanName.toLowerCase().includes(lower)
|
|
230
|
+
);
|
|
231
|
+
},
|
|
232
|
+
default: defaultModel,
|
|
233
|
+
theme: {
|
|
234
|
+
...inquirerTheme,
|
|
235
|
+
style: {
|
|
236
|
+
...inquirerTheme.style,
|
|
237
|
+
searchTerm: (text: string) => amber(text || chalk.gray("Search models...")),
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
]);
|
|
242
|
+
|
|
243
|
+
if (selectedModel === "__manual__") {
|
|
244
|
+
const id = await ModelSelector.promptManualModel(defaultModel || "");
|
|
245
|
+
return { id, name: id };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const selected = allChoices.find((c) => c.value === selectedModel);
|
|
249
|
+
return { id: selectedModel, name: selected?.humanName || selectedModel };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// No models.dev data available — fall back to manual
|
|
253
|
+
const id = await ModelSelector.promptManualModel(defaultModel || "");
|
|
254
|
+
return { id, name: id };
|
|
255
|
+
}
|
|
256
|
+
|
|
194
257
|
/**
|
|
195
258
|
* Prompt for manual model entry
|
|
196
259
|
*/
|
|
@@ -205,6 +268,7 @@ export class ModelSelector {
|
|
|
205
268
|
if (!input.trim()) return "Model name is required";
|
|
206
269
|
return true;
|
|
207
270
|
},
|
|
271
|
+
theme: inquirerTheme,
|
|
208
272
|
},
|
|
209
273
|
]);
|
|
210
274
|
return inputModel;
|