darkfoo-code 0.1.2 → 0.1.4
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/bin/darkfoo.mjs +3 -1
- package/dist/main.js +1991 -86
- package/package.json +1 -1
- package/dist/chunk-4KTJEE4A.js +0 -1023
- package/dist/chunk-BT7IPQDS.js +0 -19
- package/dist/chunk-GQXUHUV4.js +0 -171
- package/dist/chunk-OBL22IIN.js +0 -400
- package/dist/chunk-VSJKCANO.js +0 -117
- package/dist/providers-P7Z3JXQH.js +0 -24
- package/dist/query-E6NPBSUX.js +0 -7
- package/dist/system-prompt-YJSDZVOM.js +0 -6
- package/dist/theme-BQAEFGVB.js +0 -6
- package/dist/tools-34S775OZ.js +0 -22
package/dist/chunk-BT7IPQDS.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
// src/utils/theme.ts
|
|
2
|
-
var theme = {
|
|
3
|
-
cyan: "#5eead4",
|
|
4
|
-
cyanDim: "#3ab5a0",
|
|
5
|
-
pink: "#f472b6",
|
|
6
|
-
pinkDim: "#c2588e",
|
|
7
|
-
purple: "#a78bfa",
|
|
8
|
-
green: "#4ade80",
|
|
9
|
-
yellow: "#fbbf24",
|
|
10
|
-
red: "#ef4444",
|
|
11
|
-
text: "#e2e8f0",
|
|
12
|
-
dim: "#7e8ea6",
|
|
13
|
-
surface: "#111827",
|
|
14
|
-
bg: "#0c1021"
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export {
|
|
18
|
-
theme
|
|
19
|
-
};
|
package/dist/chunk-GQXUHUV4.js
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getProvider
|
|
3
|
-
} from "./chunk-OBL22IIN.js";
|
|
4
|
-
|
|
5
|
-
// src/query.ts
|
|
6
|
-
import { nanoid } from "nanoid";
|
|
7
|
-
|
|
8
|
-
// src/permissions.ts
|
|
9
|
-
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
10
|
-
import { join } from "path";
|
|
11
|
-
var SETTINGS_PATH = join(process.env.HOME || "~", ".darkfoo", "settings.json");
|
|
12
|
-
var cachedSettings = null;
|
|
13
|
-
async function loadSettings() {
|
|
14
|
-
if (cachedSettings) return cachedSettings;
|
|
15
|
-
try {
|
|
16
|
-
const raw = await readFile(SETTINGS_PATH, "utf-8");
|
|
17
|
-
cachedSettings = JSON.parse(raw);
|
|
18
|
-
return cachedSettings;
|
|
19
|
-
} catch {
|
|
20
|
-
cachedSettings = { permissionMode: "default", alwaysAllow: [] };
|
|
21
|
-
return cachedSettings;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
async function checkPermission(tool, args) {
|
|
25
|
-
const settings = await loadSettings();
|
|
26
|
-
if (settings.permissionMode === "auto") return "allow";
|
|
27
|
-
if (tool.isReadOnly) return "allow";
|
|
28
|
-
const argStr = JSON.stringify(args);
|
|
29
|
-
const matched = settings.alwaysAllow.find(
|
|
30
|
-
(r) => r.tool === tool.name && (!r.pattern || argStr.includes(r.pattern))
|
|
31
|
-
);
|
|
32
|
-
if (matched) return matched.decision;
|
|
33
|
-
return "ask";
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// src/query.ts
|
|
37
|
-
async function* query(params) {
|
|
38
|
-
const { model, tools, systemPrompt, signal } = params;
|
|
39
|
-
const messages = [...params.messages];
|
|
40
|
-
const maxTurns = params.maxTurns ?? 30;
|
|
41
|
-
let turns = 0;
|
|
42
|
-
const ollamaTools = tools.map((t) => t.toOllamaToolDef());
|
|
43
|
-
while (turns < maxTurns) {
|
|
44
|
-
turns++;
|
|
45
|
-
const ollamaMessages = toOllamaMessages(messages, systemPrompt);
|
|
46
|
-
let assistantContent = "";
|
|
47
|
-
const toolCalls = [];
|
|
48
|
-
const provider = getProvider();
|
|
49
|
-
for await (const event of provider.chatStream({ model, messages: ollamaMessages, tools: ollamaTools, signal })) {
|
|
50
|
-
yield event;
|
|
51
|
-
if (event.type === "text_delta") {
|
|
52
|
-
assistantContent += event.text;
|
|
53
|
-
}
|
|
54
|
-
if (event.type === "tool_call") {
|
|
55
|
-
toolCalls.push(event.toolCall);
|
|
56
|
-
}
|
|
57
|
-
if (event.type === "error") {
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
const assistantMsg = {
|
|
62
|
-
id: nanoid(),
|
|
63
|
-
role: "assistant",
|
|
64
|
-
content: assistantContent,
|
|
65
|
-
toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
|
|
66
|
-
timestamp: Date.now()
|
|
67
|
-
};
|
|
68
|
-
messages.push(assistantMsg);
|
|
69
|
-
yield { type: "assistant_message", message: assistantMsg };
|
|
70
|
-
if (toolCalls.length === 0) return;
|
|
71
|
-
const readOnlyCalls = [];
|
|
72
|
-
const writeCalls = [];
|
|
73
|
-
const unknownCalls = [];
|
|
74
|
-
for (const tc of toolCalls) {
|
|
75
|
-
const tool = tools.find((t) => t.name.toLowerCase() === tc.function.name.toLowerCase());
|
|
76
|
-
if (!tool) {
|
|
77
|
-
unknownCalls.push(tc);
|
|
78
|
-
} else if (tool.isReadOnly) {
|
|
79
|
-
readOnlyCalls.push({ tc, tool });
|
|
80
|
-
} else {
|
|
81
|
-
writeCalls.push({ tc, tool });
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
for (const tc of unknownCalls) {
|
|
85
|
-
const errOutput = `Unknown tool: ${tc.function.name}`;
|
|
86
|
-
yield { type: "tool_result", toolName: tc.function.name, output: errOutput, isError: true };
|
|
87
|
-
messages.push({ id: nanoid(), role: "tool", content: errOutput, toolName: tc.function.name, timestamp: Date.now() });
|
|
88
|
-
}
|
|
89
|
-
if (readOnlyCalls.length > 0) {
|
|
90
|
-
const results = await Promise.all(
|
|
91
|
-
readOnlyCalls.map(async ({ tc, tool }) => {
|
|
92
|
-
const coercedArgs = coerceToolArgs(tc.function.arguments, tool);
|
|
93
|
-
try {
|
|
94
|
-
return { tool, result: await tool.call(coercedArgs, { cwd: process.cwd(), abortSignal: signal }) };
|
|
95
|
-
} catch (err) {
|
|
96
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
97
|
-
return { tool, result: { output: `Tool execution error: ${msg}`, isError: true } };
|
|
98
|
-
}
|
|
99
|
-
})
|
|
100
|
-
);
|
|
101
|
-
for (const { tool, result } of results) {
|
|
102
|
-
yield { type: "tool_result", toolName: tool.name, output: result.output, isError: result.isError ?? false };
|
|
103
|
-
messages.push({ id: nanoid(), role: "tool", content: result.output, toolName: tool.name, timestamp: Date.now() });
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
for (const { tc, tool } of writeCalls) {
|
|
107
|
-
const coercedArgs = coerceToolArgs(tc.function.arguments, tool);
|
|
108
|
-
const permission = await checkPermission(tool, coercedArgs);
|
|
109
|
-
if (permission === "deny") {
|
|
110
|
-
const denied = "Tool blocked by permission rules.";
|
|
111
|
-
yield { type: "tool_result", toolName: tool.name, output: denied, isError: true };
|
|
112
|
-
messages.push({ id: nanoid(), role: "tool", content: denied, toolName: tool.name, timestamp: Date.now() });
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
let result;
|
|
116
|
-
try {
|
|
117
|
-
result = await tool.call(coercedArgs, { cwd: process.cwd(), abortSignal: signal });
|
|
118
|
-
} catch (err) {
|
|
119
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
120
|
-
result = { output: `Tool execution error: ${msg}`, isError: true };
|
|
121
|
-
}
|
|
122
|
-
yield { type: "tool_result", toolName: tool.name, output: result.output, isError: result.isError ?? false };
|
|
123
|
-
messages.push({ id: nanoid(), role: "tool", content: result.output, toolName: tool.name, timestamp: Date.now() });
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
yield { type: "error", error: `Reached maximum of ${maxTurns} tool-use turns.` };
|
|
127
|
-
}
|
|
128
|
-
function toOllamaMessages(messages, systemPrompt) {
|
|
129
|
-
const result = [
|
|
130
|
-
{ role: "system", content: systemPrompt }
|
|
131
|
-
];
|
|
132
|
-
for (const msg of messages) {
|
|
133
|
-
if (msg.role === "assistant") {
|
|
134
|
-
const ollamaMsg = {
|
|
135
|
-
role: "assistant",
|
|
136
|
-
content: msg.content
|
|
137
|
-
};
|
|
138
|
-
if (msg.toolCalls) {
|
|
139
|
-
ollamaMsg.tool_calls = msg.toolCalls;
|
|
140
|
-
}
|
|
141
|
-
result.push(ollamaMsg);
|
|
142
|
-
} else if (msg.role === "tool") {
|
|
143
|
-
result.push({ role: "tool", content: msg.content });
|
|
144
|
-
} else if (msg.role === "user") {
|
|
145
|
-
result.push({ role: "user", content: msg.content });
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
return result;
|
|
149
|
-
}
|
|
150
|
-
function coerceToolArgs(args, tool) {
|
|
151
|
-
const shape = tool.inputSchema.shape;
|
|
152
|
-
if (!shape) return args;
|
|
153
|
-
const coerced = { ...args };
|
|
154
|
-
for (const [key, val] of Object.entries(coerced)) {
|
|
155
|
-
if (typeof val !== "string") continue;
|
|
156
|
-
const fieldDef = shape[key];
|
|
157
|
-
if (!fieldDef) continue;
|
|
158
|
-
const typeName = fieldDef._def?.typeName === "ZodOptional" ? fieldDef._def?.innerType?._def?.typeName : fieldDef._def?.typeName;
|
|
159
|
-
if (typeName === "ZodBoolean") {
|
|
160
|
-
coerced[key] = val === "true";
|
|
161
|
-
} else if (typeName === "ZodNumber") {
|
|
162
|
-
const num = Number(val);
|
|
163
|
-
if (!isNaN(num)) coerced[key] = num;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
return coerced;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export {
|
|
170
|
-
query
|
|
171
|
-
};
|
package/dist/chunk-OBL22IIN.js
DELETED
|
@@ -1,400 +0,0 @@
|
|
|
1
|
-
// src/providers/index.ts
|
|
2
|
-
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
|
|
5
|
-
// src/providers/ollama.ts
|
|
6
|
-
var OllamaProvider = class {
|
|
7
|
-
name = "ollama";
|
|
8
|
-
label;
|
|
9
|
-
baseUrl;
|
|
10
|
-
constructor(baseUrl = "http://localhost:11434", label = "Ollama") {
|
|
11
|
-
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
12
|
-
this.label = label;
|
|
13
|
-
}
|
|
14
|
-
async *chatStream(params) {
|
|
15
|
-
const body = {
|
|
16
|
-
model: params.model,
|
|
17
|
-
messages: params.messages,
|
|
18
|
-
stream: true
|
|
19
|
-
};
|
|
20
|
-
if (params.tools && params.tools.length > 0) {
|
|
21
|
-
body.tools = params.tools;
|
|
22
|
-
}
|
|
23
|
-
let response;
|
|
24
|
-
try {
|
|
25
|
-
response = await fetch(`${this.baseUrl}/api/chat`, {
|
|
26
|
-
method: "POST",
|
|
27
|
-
headers: { "Content-Type": "application/json" },
|
|
28
|
-
body: JSON.stringify(body),
|
|
29
|
-
signal: params.signal
|
|
30
|
-
});
|
|
31
|
-
} catch (err) {
|
|
32
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
33
|
-
yield { type: "error", error: `Failed to connect to Ollama at ${this.baseUrl}: ${msg}` };
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
if (!response.ok) {
|
|
37
|
-
const text = await response.text().catch(() => "unknown error");
|
|
38
|
-
yield { type: "error", error: `Ollama error ${response.status}: ${text}` };
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
if (!response.body) {
|
|
42
|
-
yield { type: "error", error: "No response body from Ollama" };
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
const reader = response.body.getReader();
|
|
46
|
-
const decoder = new TextDecoder();
|
|
47
|
-
let buffer = "";
|
|
48
|
-
try {
|
|
49
|
-
while (true) {
|
|
50
|
-
const { done, value } = await reader.read();
|
|
51
|
-
if (done) break;
|
|
52
|
-
buffer += decoder.decode(value, { stream: true });
|
|
53
|
-
let newlineIdx;
|
|
54
|
-
while ((newlineIdx = buffer.indexOf("\n")) !== -1) {
|
|
55
|
-
const line = buffer.slice(0, newlineIdx).trim();
|
|
56
|
-
buffer = buffer.slice(newlineIdx + 1);
|
|
57
|
-
if (!line) continue;
|
|
58
|
-
let chunk;
|
|
59
|
-
try {
|
|
60
|
-
chunk = JSON.parse(line);
|
|
61
|
-
} catch {
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
if (chunk.message.content) {
|
|
65
|
-
yield { type: "text_delta", text: chunk.message.content };
|
|
66
|
-
}
|
|
67
|
-
if (chunk.message.tool_calls) {
|
|
68
|
-
for (const tc of chunk.message.tool_calls) {
|
|
69
|
-
yield { type: "tool_call", toolCall: normalizeToolCall(tc) };
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
} finally {
|
|
75
|
-
reader.releaseLock();
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
async chat(params) {
|
|
79
|
-
let content = "";
|
|
80
|
-
const toolCalls = [];
|
|
81
|
-
for await (const event of this.chatStream(params)) {
|
|
82
|
-
if (event.type === "text_delta") content += event.text;
|
|
83
|
-
if (event.type === "tool_call") toolCalls.push(event.toolCall);
|
|
84
|
-
}
|
|
85
|
-
return { content, toolCalls };
|
|
86
|
-
}
|
|
87
|
-
async listModels() {
|
|
88
|
-
try {
|
|
89
|
-
const res = await fetch(`${this.baseUrl}/api/tags`);
|
|
90
|
-
const data = await res.json();
|
|
91
|
-
return data.models.map((m) => ({ name: m.name, size: m.details.parameter_size, family: m.details.family }));
|
|
92
|
-
} catch {
|
|
93
|
-
return [];
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
async healthCheck() {
|
|
97
|
-
try {
|
|
98
|
-
const res = await fetch(`${this.baseUrl}/api/tags`, { signal: AbortSignal.timeout(5e3) });
|
|
99
|
-
return res.ok;
|
|
100
|
-
} catch {
|
|
101
|
-
return false;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
function normalizeToolCall(tc) {
|
|
106
|
-
let args = tc.function.arguments;
|
|
107
|
-
if (typeof args === "string") {
|
|
108
|
-
try {
|
|
109
|
-
args = JSON.parse(args);
|
|
110
|
-
} catch {
|
|
111
|
-
args = {};
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return { function: { name: tc.function.name, arguments: args } };
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// src/providers/openai-compat.ts
|
|
118
|
-
var OpenAICompatProvider = class {
|
|
119
|
-
name;
|
|
120
|
-
label;
|
|
121
|
-
baseUrl;
|
|
122
|
-
apiKey;
|
|
123
|
-
constructor(baseUrl, label, name = "openai", apiKey = "") {
|
|
124
|
-
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
125
|
-
this.label = label;
|
|
126
|
-
this.name = name;
|
|
127
|
-
this.apiKey = apiKey;
|
|
128
|
-
}
|
|
129
|
-
async *chatStream(params) {
|
|
130
|
-
const headers = { "Content-Type": "application/json" };
|
|
131
|
-
if (this.apiKey) headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
132
|
-
const messages = params.messages.map((m) => {
|
|
133
|
-
const msg = { role: m.role, content: m.content };
|
|
134
|
-
if (m.tool_calls) msg.tool_calls = m.tool_calls;
|
|
135
|
-
return msg;
|
|
136
|
-
});
|
|
137
|
-
const body = {
|
|
138
|
-
model: params.model,
|
|
139
|
-
messages,
|
|
140
|
-
stream: true
|
|
141
|
-
};
|
|
142
|
-
if (params.tools && params.tools.length > 0) {
|
|
143
|
-
body.tools = params.tools.map((t) => ({
|
|
144
|
-
type: "function",
|
|
145
|
-
function: {
|
|
146
|
-
name: t.function.name,
|
|
147
|
-
description: t.function.description,
|
|
148
|
-
parameters: t.function.parameters
|
|
149
|
-
}
|
|
150
|
-
}));
|
|
151
|
-
}
|
|
152
|
-
let response;
|
|
153
|
-
try {
|
|
154
|
-
response = await fetch(`${this.baseUrl}/v1/chat/completions`, {
|
|
155
|
-
method: "POST",
|
|
156
|
-
headers,
|
|
157
|
-
body: JSON.stringify(body),
|
|
158
|
-
signal: params.signal
|
|
159
|
-
});
|
|
160
|
-
} catch (err) {
|
|
161
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
162
|
-
yield { type: "error", error: `Failed to connect to ${this.label} at ${this.baseUrl}: ${msg}` };
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
if (!response.ok) {
|
|
166
|
-
const text = await response.text().catch(() => "unknown error");
|
|
167
|
-
yield { type: "error", error: `${this.label} error ${response.status}: ${text}` };
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
if (!response.body) {
|
|
171
|
-
yield { type: "error", error: `No response body from ${this.label}` };
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
const reader = response.body.getReader();
|
|
175
|
-
const decoder = new TextDecoder();
|
|
176
|
-
let buffer = "";
|
|
177
|
-
const pendingToolCalls = /* @__PURE__ */ new Map();
|
|
178
|
-
try {
|
|
179
|
-
while (true) {
|
|
180
|
-
const { done, value } = await reader.read();
|
|
181
|
-
if (done) break;
|
|
182
|
-
buffer += decoder.decode(value, { stream: true });
|
|
183
|
-
let newlineIdx;
|
|
184
|
-
while ((newlineIdx = buffer.indexOf("\n")) !== -1) {
|
|
185
|
-
const line = buffer.slice(0, newlineIdx).trim();
|
|
186
|
-
buffer = buffer.slice(newlineIdx + 1);
|
|
187
|
-
if (!line || line === "data: [DONE]") continue;
|
|
188
|
-
if (!line.startsWith("data: ")) continue;
|
|
189
|
-
const jsonStr = line.slice(6);
|
|
190
|
-
let chunk;
|
|
191
|
-
try {
|
|
192
|
-
chunk = JSON.parse(jsonStr);
|
|
193
|
-
} catch {
|
|
194
|
-
continue;
|
|
195
|
-
}
|
|
196
|
-
const delta = chunk.choices?.[0]?.delta;
|
|
197
|
-
if (!delta) continue;
|
|
198
|
-
if (delta.content) {
|
|
199
|
-
yield { type: "text_delta", text: delta.content };
|
|
200
|
-
}
|
|
201
|
-
if (delta.tool_calls) {
|
|
202
|
-
for (const tc of delta.tool_calls) {
|
|
203
|
-
const idx = tc.index ?? 0;
|
|
204
|
-
if (!pendingToolCalls.has(idx)) {
|
|
205
|
-
pendingToolCalls.set(idx, { name: "", arguments: "" });
|
|
206
|
-
}
|
|
207
|
-
const pending = pendingToolCalls.get(idx);
|
|
208
|
-
if (tc.function?.name) pending.name = tc.function.name;
|
|
209
|
-
if (tc.function?.arguments) pending.arguments += tc.function.arguments;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
const finishReason = chunk.choices?.[0]?.finish_reason;
|
|
213
|
-
if (finishReason === "tool_calls" || finishReason === "stop") {
|
|
214
|
-
for (const [, tc] of pendingToolCalls) {
|
|
215
|
-
if (tc.name) {
|
|
216
|
-
let args = {};
|
|
217
|
-
try {
|
|
218
|
-
args = JSON.parse(tc.arguments);
|
|
219
|
-
} catch {
|
|
220
|
-
}
|
|
221
|
-
yield { type: "tool_call", toolCall: { function: { name: tc.name, arguments: args } } };
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
pendingToolCalls.clear();
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
for (const [, tc] of pendingToolCalls) {
|
|
229
|
-
if (tc.name) {
|
|
230
|
-
let args = {};
|
|
231
|
-
try {
|
|
232
|
-
args = JSON.parse(tc.arguments);
|
|
233
|
-
} catch {
|
|
234
|
-
}
|
|
235
|
-
yield { type: "tool_call", toolCall: { function: { name: tc.name, arguments: args } } };
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
} finally {
|
|
239
|
-
reader.releaseLock();
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
async chat(params) {
|
|
243
|
-
let content = "";
|
|
244
|
-
const toolCalls = [];
|
|
245
|
-
for await (const event of this.chatStream(params)) {
|
|
246
|
-
if (event.type === "text_delta") content += event.text;
|
|
247
|
-
if (event.type === "tool_call") toolCalls.push(event.toolCall);
|
|
248
|
-
}
|
|
249
|
-
return { content, toolCalls };
|
|
250
|
-
}
|
|
251
|
-
async listModels() {
|
|
252
|
-
const headers = {};
|
|
253
|
-
if (this.apiKey) headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
254
|
-
try {
|
|
255
|
-
const res = await fetch(`${this.baseUrl}/v1/models`, { headers, signal: AbortSignal.timeout(5e3) });
|
|
256
|
-
const data = await res.json();
|
|
257
|
-
if (data.data) {
|
|
258
|
-
return data.data.map((m) => ({ name: m.id }));
|
|
259
|
-
}
|
|
260
|
-
if (data.models) {
|
|
261
|
-
return data.models.map((m) => ({ name: m.name || m.model || m.id || "unknown" }));
|
|
262
|
-
}
|
|
263
|
-
return [];
|
|
264
|
-
} catch {
|
|
265
|
-
return [];
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
async healthCheck() {
|
|
269
|
-
try {
|
|
270
|
-
const res = await fetch(`${this.baseUrl}/health`, { signal: AbortSignal.timeout(5e3) });
|
|
271
|
-
if (res.ok) return true;
|
|
272
|
-
const res2 = await fetch(`${this.baseUrl}/v1/models`, { signal: AbortSignal.timeout(5e3) });
|
|
273
|
-
return res2.ok;
|
|
274
|
-
} catch {
|
|
275
|
-
return false;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
// src/providers/index.ts
|
|
281
|
-
var SETTINGS_PATH = join(process.env.HOME || "~", ".darkfoo", "settings.json");
|
|
282
|
-
var DEFAULT_PROVIDERS = [
|
|
283
|
-
{ type: "ollama", name: "ollama", label: "Ollama", baseUrl: process.env.OLLAMA_HOST || "http://localhost:11434" },
|
|
284
|
-
{ type: "openai", name: "llama-cpp", label: "llama.cpp", baseUrl: "http://localhost:8081" },
|
|
285
|
-
{ type: "openai", name: "vllm", label: "vLLM", baseUrl: "http://localhost:8000" },
|
|
286
|
-
{ type: "openai", name: "tgi", label: "TGI", baseUrl: "http://localhost:8090" },
|
|
287
|
-
{ type: "openai", name: "lm-studio", label: "LM Studio", baseUrl: "http://localhost:1234" },
|
|
288
|
-
{ type: "openai", name: "localai", label: "LocalAI", baseUrl: "http://localhost:8080" }
|
|
289
|
-
];
|
|
290
|
-
var activeProviderName = "ollama";
|
|
291
|
-
var providerConfigs = [...DEFAULT_PROVIDERS];
|
|
292
|
-
var providerInstances = /* @__PURE__ */ new Map();
|
|
293
|
-
function createProvider(config) {
|
|
294
|
-
switch (config.type) {
|
|
295
|
-
case "ollama":
|
|
296
|
-
return new OllamaProvider(config.baseUrl, config.label);
|
|
297
|
-
case "openai":
|
|
298
|
-
return new OpenAICompatProvider(config.baseUrl, config.label, config.name, config.apiKey);
|
|
299
|
-
default:
|
|
300
|
-
throw new Error(`Unknown provider type: ${config.type}`);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
function ensureProvider(name) {
|
|
304
|
-
if (providerInstances.has(name)) return providerInstances.get(name);
|
|
305
|
-
const config = providerConfigs.find((c) => c.name === name);
|
|
306
|
-
if (!config) throw new Error(`Unknown provider: ${name}`);
|
|
307
|
-
const provider = createProvider(config);
|
|
308
|
-
providerInstances.set(name, provider);
|
|
309
|
-
return provider;
|
|
310
|
-
}
|
|
311
|
-
function getProvider() {
|
|
312
|
-
return ensureProvider(activeProviderName);
|
|
313
|
-
}
|
|
314
|
-
function getProviderByName(name) {
|
|
315
|
-
const config = providerConfigs.find((c) => c.name === name);
|
|
316
|
-
if (!config) return null;
|
|
317
|
-
return ensureProvider(name);
|
|
318
|
-
}
|
|
319
|
-
function getActiveProviderName() {
|
|
320
|
-
return activeProviderName;
|
|
321
|
-
}
|
|
322
|
-
function setActiveProvider(name) {
|
|
323
|
-
const config = providerConfigs.find((c) => c.name === name);
|
|
324
|
-
if (!config) throw new Error(`Unknown provider: ${name}. Available: ${providerConfigs.map((c) => c.name).join(", ")}`);
|
|
325
|
-
activeProviderName = name;
|
|
326
|
-
}
|
|
327
|
-
function getProviderConfigs() {
|
|
328
|
-
return providerConfigs;
|
|
329
|
-
}
|
|
330
|
-
function upsertProviderConfig(config) {
|
|
331
|
-
const idx = providerConfigs.findIndex((c) => c.name === config.name);
|
|
332
|
-
if (idx >= 0) {
|
|
333
|
-
providerConfigs[idx] = config;
|
|
334
|
-
} else {
|
|
335
|
-
providerConfigs.push(config);
|
|
336
|
-
}
|
|
337
|
-
providerInstances.delete(config.name);
|
|
338
|
-
}
|
|
339
|
-
function removeProviderConfig(name) {
|
|
340
|
-
const idx = providerConfigs.findIndex((c) => c.name === name);
|
|
341
|
-
if (idx < 0) return false;
|
|
342
|
-
providerConfigs.splice(idx, 1);
|
|
343
|
-
providerInstances.delete(name);
|
|
344
|
-
if (activeProviderName === name) activeProviderName = "ollama";
|
|
345
|
-
return true;
|
|
346
|
-
}
|
|
347
|
-
async function loadProviderSettings() {
|
|
348
|
-
try {
|
|
349
|
-
const raw = await readFile(SETTINGS_PATH, "utf-8");
|
|
350
|
-
const settings = JSON.parse(raw);
|
|
351
|
-
if (settings.providers) {
|
|
352
|
-
for (const custom of settings.providers) {
|
|
353
|
-
upsertProviderConfig(custom);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
if (settings.activeProvider) {
|
|
357
|
-
const config = providerConfigs.find((c) => c.name === settings.activeProvider);
|
|
358
|
-
if (config) activeProviderName = settings.activeProvider;
|
|
359
|
-
}
|
|
360
|
-
} catch {
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
async function saveProviderSettings() {
|
|
364
|
-
let settings = {};
|
|
365
|
-
try {
|
|
366
|
-
const raw = await readFile(SETTINGS_PATH, "utf-8");
|
|
367
|
-
settings = JSON.parse(raw);
|
|
368
|
-
} catch {
|
|
369
|
-
}
|
|
370
|
-
const customProviders = providerConfigs.filter(
|
|
371
|
-
(c) => !DEFAULT_PROVIDERS.some((d) => d.name === c.name && d.baseUrl === c.baseUrl)
|
|
372
|
-
);
|
|
373
|
-
settings.providers = customProviders;
|
|
374
|
-
settings.activeProvider = activeProviderName;
|
|
375
|
-
await mkdir(join(process.env.HOME || "~", ".darkfoo"), { recursive: true });
|
|
376
|
-
await writeFile(SETTINGS_PATH, JSON.stringify(settings, null, 2), "utf-8");
|
|
377
|
-
}
|
|
378
|
-
async function discoverProviders() {
|
|
379
|
-
const results = await Promise.all(
|
|
380
|
-
providerConfigs.map(async (config) => {
|
|
381
|
-
const provider = ensureProvider(config.name);
|
|
382
|
-
const online = await provider.healthCheck();
|
|
383
|
-
return { config, online };
|
|
384
|
-
})
|
|
385
|
-
);
|
|
386
|
-
return results;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
export {
|
|
390
|
-
getProvider,
|
|
391
|
-
getProviderByName,
|
|
392
|
-
getActiveProviderName,
|
|
393
|
-
setActiveProvider,
|
|
394
|
-
getProviderConfigs,
|
|
395
|
-
upsertProviderConfig,
|
|
396
|
-
removeProviderConfig,
|
|
397
|
-
loadProviderSettings,
|
|
398
|
-
saveProviderSettings,
|
|
399
|
-
discoverProviders
|
|
400
|
-
};
|
package/dist/chunk-VSJKCANO.js
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
// src/context-loader.ts
|
|
2
|
-
import { readFile, readdir, access } from "fs/promises";
|
|
3
|
-
import { join, dirname, resolve } from "path";
|
|
4
|
-
var CONTEXT_FILES = ["DARKFOO.md", ".darkfoo/DARKFOO.md"];
|
|
5
|
-
var RULES_DIR = ".darkfoo/rules";
|
|
6
|
-
var HOME_CONTEXT = join(process.env.HOME || "~", ".darkfoo", "DARKFOO.md");
|
|
7
|
-
async function loadProjectContext(cwd) {
|
|
8
|
-
const parts = [];
|
|
9
|
-
const globalContent = await tryRead(HOME_CONTEXT);
|
|
10
|
-
if (globalContent) {
|
|
11
|
-
parts.push(`# Global instructions (~/.darkfoo/DARKFOO.md)
|
|
12
|
-
|
|
13
|
-
${globalContent}`);
|
|
14
|
-
}
|
|
15
|
-
const visited = /* @__PURE__ */ new Set();
|
|
16
|
-
let dir = resolve(cwd);
|
|
17
|
-
while (dir && !visited.has(dir)) {
|
|
18
|
-
visited.add(dir);
|
|
19
|
-
for (const file of CONTEXT_FILES) {
|
|
20
|
-
const filePath = join(dir, file);
|
|
21
|
-
const content = await tryRead(filePath);
|
|
22
|
-
if (content) {
|
|
23
|
-
const rel = filePath.replace(cwd + "/", "");
|
|
24
|
-
parts.push(`# Project instructions (${rel})
|
|
25
|
-
|
|
26
|
-
${content}`);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
const parent = dirname(dir);
|
|
30
|
-
if (parent === dir) break;
|
|
31
|
-
dir = parent;
|
|
32
|
-
}
|
|
33
|
-
const rulesDir = join(cwd, RULES_DIR);
|
|
34
|
-
try {
|
|
35
|
-
await access(rulesDir);
|
|
36
|
-
const files = await readdir(rulesDir);
|
|
37
|
-
for (const file of files.filter((f) => f.endsWith(".md")).sort()) {
|
|
38
|
-
const content = await tryRead(join(rulesDir, file));
|
|
39
|
-
if (content) {
|
|
40
|
-
parts.push(`# Rule: ${file}
|
|
41
|
-
|
|
42
|
-
${content}`);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
} catch {
|
|
46
|
-
}
|
|
47
|
-
return parts.join("\n\n---\n\n");
|
|
48
|
-
}
|
|
49
|
-
async function tryRead(path) {
|
|
50
|
-
try {
|
|
51
|
-
return await readFile(path, "utf-8");
|
|
52
|
-
} catch {
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// src/system-prompt.ts
|
|
58
|
-
async function buildSystemPrompt(tools, cwd) {
|
|
59
|
-
const toolDescriptions = tools.map((t) => {
|
|
60
|
-
const params = Object.keys(
|
|
61
|
-
t.inputSchema.shape ?? {}
|
|
62
|
-
);
|
|
63
|
-
return `### ${t.name}
|
|
64
|
-
${t.description}
|
|
65
|
-
Parameters: ${params.join(", ")}`;
|
|
66
|
-
}).join("\n\n");
|
|
67
|
-
const projectContext = await loadProjectContext(cwd);
|
|
68
|
-
let prompt = `You are Darkfoo Code, a local AI coding assistant.
|
|
69
|
-
|
|
70
|
-
All text you output outside of tool use is displayed directly to the user. Use this to communicate, explain, and answer questions. You do NOT need to use a tool for every message.
|
|
71
|
-
|
|
72
|
-
IMPORTANT: Only use tools when the task genuinely requires them \u2014 reading files, writing code, running commands, searching the codebase. For conversation, greetings, explanations, opinions, or questions that don't need filesystem or shell access, just respond with text. Never call a tool with empty or meaningless arguments.
|
|
73
|
-
|
|
74
|
-
# Tools
|
|
75
|
-
|
|
76
|
-
You have access to the following tools. Call them ONLY when needed:
|
|
77
|
-
|
|
78
|
-
${toolDescriptions}
|
|
79
|
-
|
|
80
|
-
# Tool usage guidelines
|
|
81
|
-
|
|
82
|
-
- Read files before modifying them.
|
|
83
|
-
- Use absolute file paths when calling tools.
|
|
84
|
-
- Prefer Edit for targeted changes. Use Write only for new files or complete rewrites.
|
|
85
|
-
- Prefer Grep over Bash with grep. Prefer Glob over Bash with find.
|
|
86
|
-
- If a command fails, diagnose before retrying.
|
|
87
|
-
- Do not make changes beyond what was asked.
|
|
88
|
-
- When running shell commands, quote paths with spaces.
|
|
89
|
-
- Use TaskCreate/TaskUpdate to track multi-step work.
|
|
90
|
-
- Use EnterPlanMode before non-trivial implementation tasks.
|
|
91
|
-
|
|
92
|
-
# Context
|
|
93
|
-
|
|
94
|
-
- Working directory: ${cwd}
|
|
95
|
-
- Date: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
|
|
96
|
-
- Platform: ${process.platform}
|
|
97
|
-
- You are running locally with full filesystem access.
|
|
98
|
-
|
|
99
|
-
# Style
|
|
100
|
-
|
|
101
|
-
- Be concise and direct. Lead with the answer or action.
|
|
102
|
-
- Use markdown formatting for code blocks, lists, and emphasis.
|
|
103
|
-
- When referencing code, include the file path.
|
|
104
|
-
`;
|
|
105
|
-
if (projectContext) {
|
|
106
|
-
prompt += `
|
|
107
|
-
# Project Instructions
|
|
108
|
-
|
|
109
|
-
${projectContext}
|
|
110
|
-
`;
|
|
111
|
-
}
|
|
112
|
-
return prompt;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export {
|
|
116
|
-
buildSystemPrompt
|
|
117
|
-
};
|