@ridit/lens 0.3.7 → 0.3.9

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 (96) hide show
  1. package/dist/index.mjs +105368 -274002
  2. package/package.json +13 -19
  3. package/src/colors.ts +15 -15
  4. package/src/commands/chat.tsx +32 -23
  5. package/src/commands/provider.tsx +11 -238
  6. package/src/commands/repo.tsx +66 -120
  7. package/src/commands/timeline.tsx +11 -22
  8. package/src/components/ChatView.tsx +238 -0
  9. package/src/components/Message.tsx +46 -0
  10. package/src/components/ToolCall.tsx +67 -0
  11. package/src/components/chat/ChatView.tsx +550 -0
  12. package/src/components/chat/Message.tsx +152 -0
  13. package/src/components/chat/StatusBar.tsx +214 -0
  14. package/src/components/chat/TextArea.tsx +173 -176
  15. package/src/components/provider/ApiKeyStep.tsx +207 -199
  16. package/src/components/provider/ModelStep.tsx +90 -88
  17. package/src/components/provider/ProviderSetup.tsx +331 -0
  18. package/src/components/provider/ProviderTypeStep.tsx +53 -61
  19. package/src/components/repo/StepRow.tsx +68 -69
  20. package/src/components/timeline/TimelineView.tsx +840 -0
  21. package/src/components/toolcall-utils.ts +103 -0
  22. package/src/components/watch/RunView.tsx +497 -0
  23. package/src/hooks/useChatInput.ts +49 -0
  24. package/src/hooks/useCommandHandler.ts +117 -0
  25. package/src/index.tsx +386 -139
  26. package/src/utils/git.ts +149 -155
  27. package/src/utils/repo.ts +62 -69
  28. package/src/utils/thinking.tsx +64 -0
  29. package/src/utils/watch.ts +165 -307
  30. package/tests/message.test.ts +38 -0
  31. package/tests/toolcall-utils.test.ts +111 -0
  32. package/tsconfig.json +8 -24
  33. package/CLAUDE.md +0 -50
  34. package/LENS.md +0 -48
  35. package/LICENSE +0 -21
  36. package/README.md +0 -93
  37. package/addons/README.md +0 -55
  38. package/addons/clean-cache.js +0 -48
  39. package/addons/generate-readme.js +0 -67
  40. package/addons/git-stats.js +0 -29
  41. package/addons/run-tests.js +0 -127
  42. package/src/commands/commit.tsx +0 -668
  43. package/src/commands/review.tsx +0 -294
  44. package/src/commands/run.tsx +0 -56
  45. package/src/commands/task.tsx +0 -36
  46. package/src/components/chat/ChatMessage.tsx +0 -195
  47. package/src/components/chat/ChatOverlays.tsx +0 -399
  48. package/src/components/chat/ChatRunner.tsx +0 -517
  49. package/src/components/chat/hooks/useChat.ts +0 -631
  50. package/src/components/chat/hooks/useChatInput.ts +0 -79
  51. package/src/components/chat/hooks/useCommandHandlers.ts +0 -327
  52. package/src/components/provider/ProviderPicker.tsx +0 -76
  53. package/src/components/provider/RemoveProviderStep.tsx +0 -82
  54. package/src/components/repo/DiffViewer.tsx +0 -175
  55. package/src/components/repo/FileReviewer.tsx +0 -70
  56. package/src/components/repo/FileViewer.tsx +0 -60
  57. package/src/components/repo/IssueFixer.tsx +0 -666
  58. package/src/components/repo/LensFileMenu.tsx +0 -115
  59. package/src/components/repo/NoProviderPrompt.tsx +0 -28
  60. package/src/components/repo/PreviewRunner.tsx +0 -217
  61. package/src/components/repo/RepoAnalysis.tsx +0 -534
  62. package/src/components/task/TaskRunner.tsx +0 -396
  63. package/src/components/timeline/CommitDetail.tsx +0 -272
  64. package/src/components/timeline/CommitList.tsx +0 -162
  65. package/src/components/timeline/TimelineChat.tsx +0 -166
  66. package/src/components/timeline/TimelineRunner.tsx +0 -1285
  67. package/src/components/watch/RunRunner.tsx +0 -929
  68. package/src/prompts/fewshot.ts +0 -252
  69. package/src/prompts/index.ts +0 -2
  70. package/src/prompts/system.ts +0 -285
  71. package/src/tools/chart.ts +0 -202
  72. package/src/tools/convert-image.ts +0 -312
  73. package/src/tools/files.ts +0 -253
  74. package/src/tools/git.ts +0 -603
  75. package/src/tools/index.ts +0 -17
  76. package/src/tools/pdf.ts +0 -164
  77. package/src/tools/shell.ts +0 -96
  78. package/src/tools/view-image.ts +0 -335
  79. package/src/tools/web.ts +0 -212
  80. package/src/types/chat.ts +0 -86
  81. package/src/types/config.ts +0 -20
  82. package/src/types/repo.ts +0 -54
  83. package/src/utils/addons/loadAddons.ts +0 -34
  84. package/src/utils/ai.ts +0 -321
  85. package/src/utils/chat.ts +0 -326
  86. package/src/utils/chatHistory.ts +0 -121
  87. package/src/utils/config.ts +0 -61
  88. package/src/utils/files.ts +0 -105
  89. package/src/utils/intentClassifier.ts +0 -58
  90. package/src/utils/lensfile.ts +0 -142
  91. package/src/utils/llm.ts +0 -81
  92. package/src/utils/memory.ts +0 -209
  93. package/src/utils/preview.ts +0 -119
  94. package/src/utils/stats.ts +0 -174
  95. package/src/utils/tools/builtins.ts +0 -377
  96. package/src/utils/tools/registry.ts +0 -105
@@ -1,121 +0,0 @@
1
- import {
2
- existsSync,
3
- mkdirSync,
4
- readFileSync,
5
- readdirSync,
6
- writeFileSync,
7
- unlinkSync,
8
- } from "fs";
9
- import path from "path";
10
- import os from "os";
11
- import type { Message } from "../types/chat";
12
-
13
- const LENS_DIR = path.join(os.homedir(), ".lens");
14
- const CHATS_DIR = path.join(LENS_DIR, "chats");
15
-
16
- export type SavedChat = {
17
- name: string;
18
- repoPath: string;
19
- messages: Message[];
20
- savedAt: string;
21
- userMessageCount: number;
22
- };
23
-
24
- function ensureChatsDir(): void {
25
- if (!existsSync(CHATS_DIR)) mkdirSync(CHATS_DIR, { recursive: true });
26
- }
27
-
28
- function chatFilePath(name: string): string {
29
- const safe = name.replace(/[^a-z0-9_-]/gi, "-").toLowerCase();
30
- return path.join(CHATS_DIR, `${safe}.json`);
31
- }
32
-
33
- export function saveChat(
34
- name: string,
35
- repoPath: string,
36
- messages: Message[],
37
- ): void {
38
- ensureChatsDir();
39
- const data: SavedChat = {
40
- name,
41
- repoPath,
42
- messages,
43
- savedAt: new Date().toISOString(),
44
- userMessageCount: messages.filter((m) => m.role === "user").length,
45
- };
46
- writeFileSync(chatFilePath(name), JSON.stringify(data, null, 2), "utf-8");
47
- }
48
-
49
- export function loadChat(name: string): SavedChat | null {
50
- const filePath = chatFilePath(name);
51
- if (!existsSync(filePath)) return null;
52
- try {
53
- return JSON.parse(readFileSync(filePath, "utf-8")) as SavedChat;
54
- } catch {
55
- return null;
56
- }
57
- }
58
-
59
- export function listChats(repoPath?: string): SavedChat[] {
60
- ensureChatsDir();
61
- const files = readdirSync(CHATS_DIR).filter((f) => f.endsWith(".json"));
62
- const chats: SavedChat[] = [];
63
- for (const file of files) {
64
- try {
65
- const data = JSON.parse(
66
- readFileSync(path.join(CHATS_DIR, file), "utf-8"),
67
- ) as SavedChat;
68
- if (!repoPath || data.repoPath === repoPath) chats.push(data);
69
- } catch {
70
- // skip corrupt files
71
- }
72
- }
73
- return chats.sort(
74
- (a, b) => new Date(b.savedAt).getTime() - new Date(a.savedAt).getTime(),
75
- );
76
- }
77
-
78
- export function deleteChat(name: string): boolean {
79
- const filePath = chatFilePath(name);
80
- if (!existsSync(filePath)) return false;
81
- try {
82
- unlinkSync(filePath);
83
- return true;
84
- } catch {
85
- return false;
86
- }
87
- }
88
-
89
- export function getChatNameSuggestions(messages: Message[]): string[] {
90
- const userMsgs = messages
91
- .filter((m) => m.role === "user")
92
- .map((m) => m.content.toLowerCase().trim());
93
-
94
- const date = new Date().toISOString().slice(0, 10);
95
-
96
- if (userMsgs.length === 0) {
97
- return [`chat-${date}`, `session-${date}`, `new-chat`];
98
- }
99
-
100
- const suggestions: string[] = [];
101
-
102
- const toSlug = (s: string) =>
103
- s
104
- .replace(/[^a-z0-9\s]/g, "")
105
- .split(/\s+/)
106
- .filter(Boolean)
107
- .slice(0, 4)
108
- .join("-");
109
-
110
- const firstSlug = toSlug(userMsgs[0]!);
111
- if (firstSlug) suggestions.push(firstSlug);
112
-
113
- if (userMsgs.length > 1) {
114
- const lastSlug = toSlug(userMsgs[userMsgs.length - 1]!);
115
- if (lastSlug && lastSlug !== firstSlug) suggestions.push(lastSlug);
116
- }
117
-
118
- suggestions.push(`session-${date}`);
119
-
120
- return suggestions.slice(0, 3);
121
- }
@@ -1,61 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
2
- import path from "path";
3
- import os from "os";
4
- import type { Config, Provider } from "../types/config";
5
-
6
- const CONFIG_DIR = path.join(os.homedir(), ".lens");
7
- const CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
8
-
9
- export function configExists(): boolean {
10
- return existsSync(CONFIG_PATH);
11
- }
12
-
13
- export function loadConfig(): Config {
14
- if (!configExists()) return { providers: [] };
15
- try {
16
- return JSON.parse(readFileSync(CONFIG_PATH, "utf-8")) as Config;
17
- } catch {
18
- return { providers: [] };
19
- }
20
- }
21
-
22
- export function saveConfig(config: Config): void {
23
- if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
24
- writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
25
- }
26
-
27
- export function addProvider(provider: Provider): void {
28
- const config = loadConfig();
29
- const existing = config.providers.findIndex((p) => p.id === provider.id);
30
- if (existing >= 0) {
31
- config.providers[existing] = provider;
32
- } else {
33
- config.providers.push(provider);
34
- }
35
- if (!config.defaultProviderId) config.defaultProviderId = provider.id;
36
- saveConfig(config);
37
- }
38
-
39
- export function setDefaultProvider(id: string): void {
40
- const config = loadConfig();
41
- config.defaultProviderId = id;
42
- saveConfig(config);
43
- }
44
-
45
- export function getDefaultProvider(): Provider | undefined {
46
- const config = loadConfig();
47
- return config.providers.find((p) => p.id === config.defaultProviderId);
48
- }
49
-
50
- export const DEFAULT_MODELS: Record<string, string[]> = {
51
- anthropic: [
52
- "claude-sonnet-4-20250514",
53
- "claude-opus-4-20250514",
54
- "claude-haiku-4-5-20251001",
55
- ],
56
- openai: ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo"],
57
- ollama: ["llama3", "mistral", "codellama", "phi3"],
58
- custom: [],
59
- };
60
-
61
- export type CustomResult = { apiKey: string; baseUrl: string };
@@ -1,105 +0,0 @@
1
- import { readFileSync } from "fs";
2
- import path from "path";
3
- import { exec } from "child_process";
4
- import figures from "figures";
5
- import type { FileTree, ImportantFile } from "../types/repo";
6
-
7
- export const IMPORTANT_PATTERNS = [
8
- /^\.gitignore$/,
9
- /^\.env(\..+)?$/,
10
- /^\.env\.example$/,
11
- /^package\.json$/,
12
- /^tsconfig(\..+)?\.json$/,
13
- /^jsconfig\.json$/,
14
- /^vite\.config\.(ts|js)$/,
15
- /^webpack\.config\.(ts|js)$/,
16
- /^rollup\.config\.(ts|js)$/,
17
- /^babel\.config\.(ts|js|json)$/,
18
- /^\.babelrc$/,
19
- /^eslint\.config\.(ts|js|json)$/,
20
- /^\.eslintrc(\..+)?$/,
21
- /^\.prettierrc(\..+)?$/,
22
- /^prettier\.config\.(ts|js)$/,
23
- /^docker-compose(\..+)?\.yml$/,
24
- /^Dockerfile(\..+)?$/,
25
- /^README(\..+)?\.md$/,
26
- /^LICENSE(\..+)?$/,
27
- /^Makefile$/,
28
- /^\.github\/workflows\/.+\.yml$/,
29
- ];
30
-
31
- export function isImportantFile(filePath: string): boolean {
32
- const fileName = path.basename(filePath);
33
- return IMPORTANT_PATTERNS.some(
34
- (pattern) => pattern.test(fileName) || pattern.test(filePath),
35
- );
36
- }
37
-
38
- export function buildTree(files: string[]): FileTree[] {
39
- const root: FileTree[] = [];
40
- for (const file of files) {
41
- const parts = file.split("/");
42
- let current = root;
43
- for (let i = 0; i < parts.length; i++) {
44
- const part = parts[i] ?? "";
45
- const isFile = i === parts.length - 1;
46
- const existing = current.find((n) => n.name === part);
47
- if (existing) {
48
- if (!isFile && existing.children) current = existing.children;
49
- } else {
50
- const node: FileTree = isFile
51
- ? { name: part }
52
- : { name: part, children: [] };
53
- current.push(node);
54
- if (!isFile && node.children) current = node.children;
55
- }
56
- }
57
- }
58
- return root;
59
- }
60
-
61
- export function fetchFileTree(repoPath: string): Promise<string[]> {
62
- return new Promise((resolve, reject) => {
63
- exec("git ls-files", { cwd: repoPath }, (err, stdout) => {
64
- if (err) return reject(err);
65
- resolve(stdout.trim().split("\n").filter(Boolean));
66
- });
67
- });
68
- }
69
-
70
- export function readImportantFiles(
71
- repoPath: string,
72
- files: string[],
73
- ): ImportantFile[] {
74
- if (files.length > 100) return [];
75
- return files.filter(isImportantFile).flatMap((filePath) => {
76
- try {
77
- const content = readFileSync(path.join(repoPath, filePath), "utf-8");
78
- return [{ path: filePath, content }];
79
- } catch {
80
- return [];
81
- }
82
- });
83
- }
84
-
85
- const FILE_ICON: Record<string, string> = {
86
- ".gitignore": figures.pointer,
87
- "package.json": figures.nodejs,
88
- Dockerfile: figures.square,
89
- Makefile: figures.play,
90
- "README.md": figures.info,
91
- LICENSE: figures.star,
92
- ".env": figures.warning,
93
- ".env.example": figures.warning,
94
- };
95
-
96
- export function iconForFile(filePath: string): string {
97
- const name = path.basename(filePath);
98
- for (const [key, icon] of Object.entries(FILE_ICON)) {
99
- if (name === key || name.startsWith(key)) return icon;
100
- }
101
- if (name.endsWith(".json")) return figures.arrowRight;
102
- if (name.endsWith(".yml") || name.endsWith(".yaml")) return figures.bullet;
103
- if (name.startsWith(".")) return figures.dot;
104
- return figures.pointerSmall;
105
- }
@@ -1,58 +0,0 @@
1
- /**
2
- * Classifies user message intent to scope which tools the LLM is allowed to use.
3
- *
4
- * readonly → only read/search/fetch tools exposed (no write, delete, shell)
5
- * mutating → all tools exposed
6
- * any → all tools exposed (ambiguous / can't tell)
7
- */
8
- export type Intent = "readonly" | "mutating" | "any";
9
-
10
- const READONLY_PATTERNS: RegExp[] = [
11
- // listing / exploring
12
- /\b(list|ls|dir|show|display|print|dump)\b/i,
13
- /\bwhat(('?s| is| are| does)\b| files| folder)/i,
14
- /\b(folder|directory|file) (structure|tree|layout|contents?)\b/i,
15
- /\bexplore\b/i,
16
-
17
- // reading / explaining
18
- /\b(read|open|view|look at|check out|inspect|peek)\b/i,
19
- /\b(explain|describe|summarize|summarise|tell me about|walk me through)\b/i,
20
- /\bhow does\b/i,
21
- /\bwhat('?s| is) (in|inside|this|that|the)\b/i,
22
-
23
- // searching
24
- /\b(find|search|grep|locate|where is|where are)\b/i,
25
- /\b(look for|scan|trace)\b/i,
26
-
27
- // understanding
28
- /\bunderstand\b/i,
29
- /\bshow me (how|what|where|why)\b/i,
30
- ];
31
-
32
- const MUTATING_PATTERNS: RegExp[] = [
33
- // writing
34
- /\b(write|create|make|generate|add|build|scaffold|init|initialize|setup|set up)\b/i,
35
- /\b(new file|new folder|new component|new page|new route)\b/i,
36
-
37
- // editing
38
- /\b(edit|modify|update|change|refactor|rename|move|migrate)\b/i,
39
- /\b(fix|patch|resolve|correct|debug|repair)\b/i,
40
- /\b(implement|add .+ to|insert|inject|append|prepend)\b/i,
41
-
42
- // deleting
43
- /\b(delete|remove|drop|clean ?up|purge|wipe)\b/i,
44
-
45
- // running
46
- /\b(run|execute|install|deploy|build|test|start|launch|compile|lint|format)\b/i,
47
- ];
48
-
49
- export function classifyIntent(userMessage: string): Intent {
50
- const text = userMessage.trim();
51
-
52
- const mutatingScore = MUTATING_PATTERNS.filter((p) => p.test(text)).length;
53
- const readonlyScore = READONLY_PATTERNS.filter((p) => p.test(text)).length;
54
-
55
- if (mutatingScore === 0 && readonlyScore > 0) return "readonly";
56
- if (mutatingScore > 0) return "mutating";
57
- return "any";
58
- }
@@ -1,142 +0,0 @@
1
- import { existsSync, readFileSync, writeFileSync } from "fs";
2
- import path from "path";
3
- import type { AnalysisResult } from "../types/repo";
4
-
5
- export const LENS_FILENAME = "LENS.md";
6
-
7
- export type LensFile = {
8
- overview: string;
9
- importantFolders: string[];
10
- tooling: Record<string, string>;
11
- keyFiles: string[];
12
- patterns: string[];
13
- architecture: string;
14
- suggestions: string[];
15
- generatedAt: string;
16
- lastUpdated: string;
17
- };
18
-
19
- export function lensFilePath(repoPath: string): string {
20
- return path.join(repoPath, LENS_FILENAME);
21
- }
22
-
23
- export function lensFileExists(repoPath: string): boolean {
24
- return existsSync(lensFilePath(repoPath));
25
- }
26
-
27
- function renderLensFile(data: LensFile): string {
28
- const toolingLines = Object.entries(data.tooling)
29
- .map(([k, v]) => `- **${k}**: ${v}`)
30
- .join("\n");
31
-
32
- return `# Lens
33
- > Generated: ${data.generatedAt}${data.lastUpdated !== data.generatedAt ? ` | Updated: ${data.lastUpdated}` : ""}
34
-
35
- ## Overview
36
- ${data.overview}
37
-
38
- ## Architecture
39
- ${data.architecture}
40
-
41
- ## Tooling & Conventions
42
- ${toolingLines || "- Not yet determined"}
43
-
44
- ## Important Folders
45
- ${data.importantFolders.map((f) => `- ${f}`).join("\n") || "- None"}
46
-
47
- ## Key Files
48
- ${data.keyFiles.map((f) => `- ${f}`).join("\n") || "- None"}
49
-
50
- ## Patterns & Idioms
51
- ${data.patterns.map((p) => `- ${p}`).join("\n") || "- None"}
52
-
53
- ## Suggestions
54
- ${data.suggestions.map((s) => `- ${s}`).join("\n") || "- None"}
55
-
56
- <!--lens-json
57
- ${JSON.stringify(data)}
58
- lens-json-->
59
- `;
60
- }
61
-
62
- export function writeLensFile(repoPath: string, result: AnalysisResult): void {
63
- const now = new Date().toISOString();
64
- const data: LensFile = {
65
- overview: result.overview,
66
- importantFolders: result.importantFolders,
67
- tooling: result.tooling ?? {},
68
- keyFiles: result.keyFiles ?? [],
69
- patterns: result.patterns ?? [],
70
- architecture: result.architecture ?? "",
71
- suggestions: result.suggestions,
72
- generatedAt: now,
73
- lastUpdated: now,
74
- };
75
- writeFileSync(lensFilePath(repoPath), renderLensFile(data), "utf-8");
76
- }
77
-
78
- export function patchLensFile(
79
- repoPath: string,
80
- patch: Partial<AnalysisResult>,
81
- ): void {
82
- const existing = readLensFile(repoPath);
83
- const now = new Date().toISOString();
84
-
85
- const base: LensFile = existing ?? {
86
- overview: "",
87
- importantFolders: [],
88
- tooling: {},
89
- keyFiles: [],
90
- patterns: [],
91
- architecture: "",
92
- suggestions: [],
93
- generatedAt: now,
94
- lastUpdated: now,
95
- };
96
-
97
- const merged: LensFile = {
98
- ...base,
99
- lastUpdated: now,
100
- overview: patch.overview ?? base.overview,
101
- architecture: patch.architecture ?? base.architecture,
102
- tooling: { ...base.tooling, ...(patch.tooling ?? {}) },
103
- importantFolders: dedup([
104
- ...base.importantFolders,
105
- ...(patch.importantFolders ?? []),
106
- ]),
107
- keyFiles: dedup([...base.keyFiles, ...(patch.keyFiles ?? [])]),
108
- patterns: dedup([...base.patterns, ...(patch.patterns ?? [])]),
109
- suggestions: dedup([...base.suggestions, ...(patch.suggestions ?? [])]),
110
- };
111
-
112
- writeFileSync(lensFilePath(repoPath), renderLensFile(merged), "utf-8");
113
- }
114
-
115
- function dedup(arr: string[]): string[] {
116
- return [...new Map(arr.map((s) => [s.trim().toLowerCase(), s])).values()];
117
- }
118
-
119
- export function readLensFile(repoPath: string): LensFile | null {
120
- const filePath = lensFilePath(repoPath);
121
- if (!existsSync(filePath)) return null;
122
- try {
123
- const content = readFileSync(filePath, "utf-8");
124
- const match = content.match(/<!--lens-json\n([\s\S]*?)\nlens-json-->/);
125
- if (!match) return null;
126
- return JSON.parse(match[1]!) as LensFile;
127
- } catch {
128
- return null;
129
- }
130
- }
131
-
132
- export function lensFileToAnalysisResult(lf: LensFile): AnalysisResult {
133
- return {
134
- overview: lf.overview,
135
- importantFolders: lf.importantFolders,
136
- tooling: lf.tooling,
137
- keyFiles: lf.keyFiles,
138
- patterns: lf.patterns,
139
- architecture: lf.architecture,
140
- suggestions: lf.suggestions,
141
- };
142
- }
package/src/utils/llm.ts DELETED
@@ -1,81 +0,0 @@
1
- import type { Provider } from "../types/config";
2
-
3
- type Message = { role: "user" | "assistant"; content: string };
4
-
5
- export async function runPrompt(
6
- provider: Provider,
7
- prompt: string,
8
- ): Promise<string> {
9
- if (provider.type === "anthropic") {
10
- return runAnthropic(provider, prompt);
11
- }
12
- if (provider.type === "openai" || provider.type === "custom") {
13
- return runOpenAICompat(provider, prompt);
14
- }
15
- if (provider.type === "ollama") {
16
- return runOllama(provider, prompt);
17
- }
18
- throw new Error(`Unknown provider type: ${provider.type}`);
19
- }
20
-
21
- async function runAnthropic(
22
- provider: Provider,
23
- prompt: string,
24
- ): Promise<string> {
25
- const res = await fetch("https://api.anthropic.com/v1/messages", {
26
- method: "POST",
27
- headers: {
28
- "Content-Type": "application/json",
29
- "x-api-key": provider.apiKey ?? "",
30
- "anthropic-version": "2023-06-01",
31
- },
32
- body: JSON.stringify({
33
- model: provider.model,
34
- max_tokens: 2000,
35
- messages: [{ role: "user", content: prompt }],
36
- }),
37
- });
38
- if (!res.ok) throw new Error(`Anthropic error: ${res.statusText}`);
39
- const data = (await res.json()) as any;
40
- return data.content
41
- .filter((b: { type: string }) => b.type === "text")
42
- .map((b: { text: string }) => b.text)
43
- .join("");
44
- }
45
-
46
- async function runOpenAICompat(
47
- provider: Provider,
48
- prompt: string,
49
- ): Promise<string> {
50
- const baseUrl = provider.baseUrl ?? "https://api.openai.com/v1";
51
- const res = await fetch(`${baseUrl}/chat/completions`, {
52
- method: "POST",
53
- headers: {
54
- "Content-Type": "application/json",
55
- Authorization: `Bearer ${provider.apiKey ?? ""}`,
56
- },
57
- body: JSON.stringify({
58
- model: provider.model,
59
- messages: [{ role: "user", content: prompt }],
60
- }),
61
- });
62
- if (!res.ok) throw new Error(`OpenAI error: ${res.statusText}`);
63
- const data = (await res.json()) as any;
64
- return data.choices?.[0]?.message?.content ?? "";
65
- }
66
-
67
- async function runOllama(provider: Provider, prompt: string): Promise<string> {
68
- const baseUrl = provider.baseUrl ?? "http://localhost:11434";
69
- const res = await fetch(`${baseUrl}/api/chat`, {
70
- method: "POST",
71
- headers: { "Content-Type": "application/json" },
72
- body: JSON.stringify({
73
- model: provider.model,
74
- stream: false,
75
- messages: [{ role: "user", content: prompt }],
76
- }),
77
- });
78
- if (!res.ok) throw new Error(`Ollama error: ${res.statusText}`);
79
- const data = (await res.json()) as any;
80
- return data.message?.content ?? "";
81
- }