skyloom 1.4.5 → 1.5.1
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/config/default.yaml +47 -0
- package/config/providers.yaml +39 -0
- package/package.json +1 -1
- package/src/cli/main.ts +46 -65
- package/src/core/llm.ts +111 -45
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Skyloom default configuration
|
|
2
|
+
llm:
|
|
3
|
+
default_model: gpt-4o
|
|
4
|
+
language: zh
|
|
5
|
+
max_retries: 2
|
|
6
|
+
temperature: 0.7
|
|
7
|
+
max_tokens: 4096
|
|
8
|
+
|
|
9
|
+
agents:
|
|
10
|
+
fog:
|
|
11
|
+
model: gpt-4o
|
|
12
|
+
temperature: 0.7
|
|
13
|
+
rain:
|
|
14
|
+
model: gpt-4o
|
|
15
|
+
temperature: 0.7
|
|
16
|
+
frost:
|
|
17
|
+
model: gpt-4o
|
|
18
|
+
temperature: 0.3
|
|
19
|
+
snow:
|
|
20
|
+
model: gpt-4o
|
|
21
|
+
temperature: 0.5
|
|
22
|
+
dew:
|
|
23
|
+
model: gpt-4o
|
|
24
|
+
temperature: 0.3
|
|
25
|
+
fair:
|
|
26
|
+
model: gpt-4o
|
|
27
|
+
temperature: 0.9
|
|
28
|
+
|
|
29
|
+
memory:
|
|
30
|
+
db_path: ~/.skyloom/memory.db
|
|
31
|
+
short_term_limit: 100
|
|
32
|
+
max_persisted_messages: 2000
|
|
33
|
+
|
|
34
|
+
workspace:
|
|
35
|
+
path: auto
|
|
36
|
+
|
|
37
|
+
cli:
|
|
38
|
+
default_agent: fog
|
|
39
|
+
approval_mode: interactive
|
|
40
|
+
|
|
41
|
+
plugins:
|
|
42
|
+
enabled: true
|
|
43
|
+
directories:
|
|
44
|
+
- ~/.skyloom/plugins
|
|
45
|
+
|
|
46
|
+
mcp:
|
|
47
|
+
servers: []
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Provider catalog — API key env vars, base URLs, docs
|
|
2
|
+
openai:
|
|
3
|
+
env_var: OPENAI_API_KEY
|
|
4
|
+
base_url: https://api.openai.com/v1
|
|
5
|
+
docs_url: https://platform.openai.com/api-keys
|
|
6
|
+
|
|
7
|
+
anthropic:
|
|
8
|
+
env_var: ANTHROPIC_API_KEY
|
|
9
|
+
base_url: https://api.anthropic.com/v1
|
|
10
|
+
docs_url: https://console.anthropic.com/settings/keys
|
|
11
|
+
|
|
12
|
+
deepseek:
|
|
13
|
+
env_var: DEEPSEEK_API_KEY
|
|
14
|
+
base_url: https://api.deepseek.com/v1
|
|
15
|
+
docs_url: https://platform.deepseek.com/api_keys
|
|
16
|
+
|
|
17
|
+
groq:
|
|
18
|
+
env_var: GROQ_API_KEY
|
|
19
|
+
base_url: https://api.groq.com/openai/v1
|
|
20
|
+
|
|
21
|
+
mistral:
|
|
22
|
+
env_var: MISTRAL_API_KEY
|
|
23
|
+
base_url: https://api.mistral.ai/v1
|
|
24
|
+
|
|
25
|
+
cohere:
|
|
26
|
+
env_var: COHERE_API_KEY
|
|
27
|
+
base_url: https://api.cohere.ai/v1
|
|
28
|
+
|
|
29
|
+
openrouter:
|
|
30
|
+
env_var: OPENROUTER_API_KEY
|
|
31
|
+
base_url: https://openrouter.ai/api/v1
|
|
32
|
+
|
|
33
|
+
gemini:
|
|
34
|
+
env_var: GEMINI_API_KEY
|
|
35
|
+
base_url: https://generativelanguage.googleapis.com/v1beta
|
|
36
|
+
|
|
37
|
+
ollama:
|
|
38
|
+
base_url: http://localhost:11434/v1
|
|
39
|
+
env_var: OLLAMA_HOST
|
package/package.json
CHANGED
package/src/cli/main.ts
CHANGED
|
@@ -143,92 +143,73 @@ function render(text: string): string[] {
|
|
|
143
143
|
/* ═══════════════════════════════════════
|
|
144
144
|
Chat loop
|
|
145
145
|
═══════════════════════════════════════ */
|
|
146
|
+
/* Check for API key availability */
|
|
147
|
+
function checkApiKeys(): string | null {
|
|
148
|
+
const keys = ["DEEPSEEK_API_KEY","OPENAI_API_KEY","ANTHROPIC_API_KEY","GROQ_API_KEY","OPENROUTER_API_KEY"];
|
|
149
|
+
for (const k of keys) { if (process.env[k]) return k; }
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
|
|
146
153
|
async function chat(agentName: string, modelOverride?: string): Promise<void> {
|
|
154
|
+
const haveKey = checkApiKeys();
|
|
155
|
+
if (!haveKey) {
|
|
156
|
+
process.stdout.write("\n" + chalk.yellow(" ⚠ No API key configured.\n"));
|
|
157
|
+
process.stdout.write(chalk.dim(" Set one: $env:DEEPSEEK_API_KEY = \"sk-your-key\" (PowerShell)\n"));
|
|
158
|
+
process.stdout.write(chalk.dim(" export DEEPSEEK_API_KEY=sk-your-key (Bash)\n\n"));
|
|
159
|
+
process.stdout.write(chalk.dim(" Then run: sky\n\n"));
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
|
|
147
163
|
const ctx = createSystemContext();
|
|
148
164
|
let agent = ctx.agentMap.get(agentName);
|
|
149
|
-
if (!agent) { process.stdout.write(chalk.red(
|
|
165
|
+
if (!agent) { process.stdout.write(chalk.red("Unknown agent: " + agentName) + "\n"); return; }
|
|
150
166
|
await agent.init();
|
|
167
|
+
// eslint-disable-next-line prefer-const
|
|
168
|
+
let currentAgent = agent; // mutable for agent switching
|
|
151
169
|
welcome(agent);
|
|
152
170
|
|
|
153
|
-
|
|
154
|
-
const history: string[] = [];
|
|
171
|
+
process.stdout.write(chalk.dim(" Key: " + haveKey + "\n\n"));
|
|
155
172
|
|
|
156
|
-
|
|
157
|
-
const inp = line.trim();
|
|
158
|
-
if (!inp) continue;
|
|
173
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
159
174
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
175
|
+
function ask() { rl.question(chalk.cyan(" " + currentAgent.displayName + " ❯ "), handler); }
|
|
176
|
+
async function handler(inp: string) {
|
|
177
|
+
inp = inp.trim();
|
|
178
|
+
if (!inp) { ask(); return; }
|
|
164
179
|
|
|
165
|
-
const
|
|
166
|
-
let handled = false;
|
|
180
|
+
const cmdL = inp.toLowerCase();
|
|
167
181
|
|
|
168
182
|
// Agent switch
|
|
169
183
|
for (const n of AGENT_NAMES) {
|
|
170
|
-
if (
|
|
171
|
-
const a = ctx.agentMap.get(n);
|
|
172
|
-
if (a) { await a.init(); agent = a; process.stdout.write(chalk.dim(` ⟳ ${AGENT_DISPLAY[n]}\n`)); }
|
|
173
|
-
handled = true; break;
|
|
174
|
-
}
|
|
184
|
+
if (cmdL === "/" + n) { const a = ctx.agentMap.get(n); if (a) { await a.init(); currentAgent = a; } process.stdout.write(chalk.dim(" ⟳ " + AGENT_DISPLAY[n] + "\n")); ask(); return; }
|
|
175
185
|
}
|
|
176
186
|
|
|
177
|
-
if (
|
|
178
|
-
if (
|
|
179
|
-
if (
|
|
180
|
-
if (
|
|
181
|
-
if (
|
|
182
|
-
if (
|
|
183
|
-
if (
|
|
184
|
-
if (
|
|
185
|
-
if (
|
|
186
|
-
if (cmdLower === "/memory") { process.stdout.write(chalk.dim(` Short-term: ${agent.memory.shortTerm.length} msgs · Working: ${Object.keys(agent.memory.working).length} keys\n\n`)); handled = true; }
|
|
187
|
-
if (cmdLower === "/workspace") { process.stdout.write(chalk.dim(` ${ctx.workspacePath || "default"}\n\n`)); handled = true; }
|
|
188
|
-
if (cmdLower === "/mcp") { process.stdout.write(chalk.dim(` ${ctx.mcpStatus?.join(", ") || "none"}\n\n`)); handled = true; }
|
|
189
|
-
if (cmdLower === "/sessions") { const ss = await agent.memory.listSessions(); if (ss.length) { for (const s of ss.slice(0, 10)) process.stdout.write(chalk.dim(` ${s.id?.slice(0, 10)}... ${s.preview || ""} (${s.messageCount || 0} msgs)\n`)); } else process.stdout.write(chalk.dim(" No saved sessions\n")); process.stdout.write("\n"); handled = true; }
|
|
190
|
-
if (cmdLower.startsWith("/model")) { process.stdout.write(chalk.dim(" Configure in ~/.skyloom/config.yaml\n\n")); handled = true; }
|
|
191
|
-
|
|
192
|
-
if (handled) continue;
|
|
193
|
-
|
|
194
|
-
// Task orchestration
|
|
195
|
-
if (cmdLower.startsWith("/task ")) { const g = inp.slice(6).trim(); if (g) { process.stdout.write(chalk.cyan(`\n ✦ ${g}\n\n`)); await runTask(g); } continue; }
|
|
196
|
-
|
|
197
|
-
// Unknown slash → help
|
|
198
|
-
if (inp.startsWith("/")) { process.stdout.write(helpText()); continue; }
|
|
187
|
+
if (cmdL === "/quit" || cmdL === "/exit") { process.stdout.write(chalk.dim("\n Session ended\n")); rl.close(); await ctx.closeAll(); process.exit(0); return; }
|
|
188
|
+
if (cmdL === "/help") { process.stdout.write(helpText()); ask(); return; }
|
|
189
|
+
if (cmdL === "/clear") { console.clear(); welcome(agent); process.stdout.write(chalk.dim(" Key: " + haveKey + "\n\n")); ask(); return; }
|
|
190
|
+
if (cmdL === "/status") { process.stdout.write(chalk.bold("\n " + currentAgent.displayName + " (" + currentAgent.name + ")\n") + chalk.dim(" State: " + currentAgent.state + " · Memory: " + currentAgent.memory.shortTerm.length + " msgs\n\n")); ask(); return; }
|
|
191
|
+
if (cmdL === "/cost") { process.stdout.write(chalk.bold("\n Total: " + formatCost(ctx.llm.getTotalCost()) + "\n\n")); ask(); return; }
|
|
192
|
+
if (cmdL === "/compact") { const r = await currentAgent.compact(); process.stdout.write(chalk.green(" ✓ " + r + "\n\n")); ask(); return; }
|
|
193
|
+
if (cmdL === "/version") { process.stdout.write(" Skyloom v" + VERSION + "\n"); ask(); return; }
|
|
194
|
+
if (cmdL.startsWith("/task ")) { const g = inp.slice(6); process.stdout.write(chalk.cyan("\n ✦ " + g + "\n\n")); await runTask(g); ask(); return; }
|
|
195
|
+
if (inp.startsWith("/")) { process.stdout.write(helpText()); ask(); return; }
|
|
199
196
|
|
|
200
197
|
// ── Chat ──
|
|
201
|
-
process.stdout.write(chalk.dim(
|
|
202
|
-
|
|
198
|
+
process.stdout.write(chalk.dim(" " + currentAgent.displayName + " thinking...\r"));
|
|
203
199
|
try {
|
|
204
|
-
const response = await
|
|
205
|
-
process.stdout.write("\r" + " ".repeat(40) + "\r");
|
|
206
|
-
|
|
200
|
+
const response = await currentAgent.chat(inp);
|
|
201
|
+
process.stdout.write("\r" + " ".repeat(40) + "\r\n");
|
|
202
|
+
const lines = render(response);
|
|
203
|
+
for (const l of lines) process.stdout.write(l + "\n");
|
|
207
204
|
process.stdout.write("\n");
|
|
208
|
-
|
|
209
|
-
process.stdout.write("
|
|
210
|
-
|
|
211
|
-
process.stdout.write(chalk.red(`\n ✗ ${(e as Error).message || e}\n\n`));
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Auto-continue
|
|
215
|
-
if (MODE.current === InteractiveMode.AUTO && agent.memory.shortTerm.length) {
|
|
216
|
-
const last = agent.memory.shortTerm[agent.memory.shortTerm.length - 1];
|
|
217
|
-
if (last?.content && /(?:接下来|下一步|继续|next|let me|I'[vl]l)/i.test(last.content.split("\n").slice(-4).join("\n"))) {
|
|
218
|
-
process.stdout.write(chalk.yellow(" [auto]\n"));
|
|
219
|
-
try {
|
|
220
|
-
const r2 = await agent.chat("请继续完成");
|
|
221
|
-
process.stdout.write("\n");
|
|
222
|
-
for (const l of render(r2)) process.stdout.write(l + "\n");
|
|
223
|
-
process.stdout.write("\n");
|
|
224
|
-
} catch { /* ignore */ }
|
|
225
|
-
}
|
|
205
|
+
} catch (e: any) {
|
|
206
|
+
process.stdout.write("\r" + " ".repeat(40) + "\r");
|
|
207
|
+
process.stdout.write(chalk.red(" ✗ " + (e.message || e) + "\n\n"));
|
|
226
208
|
}
|
|
209
|
+
ask();
|
|
227
210
|
}
|
|
228
211
|
|
|
229
|
-
|
|
230
|
-
await ctx.closeAll();
|
|
231
|
-
process.exit(0);
|
|
212
|
+
ask();
|
|
232
213
|
}
|
|
233
214
|
|
|
234
215
|
/* ═══════════════════════════════════════
|
package/src/core/llm.ts
CHANGED
|
@@ -662,63 +662,129 @@ export class LLMClient {
|
|
|
662
662
|
}
|
|
663
663
|
|
|
664
664
|
/**
|
|
665
|
-
* Complete with retry logic
|
|
665
|
+
* Complete with retry logic — real HTTP call to LLM API.
|
|
666
666
|
*/
|
|
667
667
|
private async completeWithRetry(
|
|
668
668
|
model: string,
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
669
|
+
messages: Record<string, unknown>[],
|
|
670
|
+
agentName?: string,
|
|
671
|
+
tools?: string[],
|
|
672
|
+
stream: boolean = false,
|
|
673
673
|
overrides?: Record<string, unknown>
|
|
674
674
|
): Promise<LLMResponse> {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
675
|
+
const temperature = (overrides?.temperature as number) ?? 0.7;
|
|
676
|
+
const maxTokens = (overrides?.maxTokens as number) ?? 4096;
|
|
677
|
+
const maxRetries = (this.config.llm as any)?.maxRetries ?? 2;
|
|
678
|
+
const isAnthropic = model.includes("claude") || model.startsWith("anthropic/");
|
|
679
|
+
|
|
680
|
+
let lastError: Error | null = null;
|
|
681
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
682
|
+
try {
|
|
683
|
+
if (attempt > 0) await new Promise(r => setTimeout(r, 1000 * Math.pow(2, attempt - 1)));
|
|
684
|
+
|
|
685
|
+
let content: string;
|
|
686
|
+
let toolCalls: ToolCall[] = [];
|
|
687
|
+
let usage: UsageStats = { promptTokens: 0, completionTokens: 0 };
|
|
688
|
+
|
|
689
|
+
if (isAnthropic) {
|
|
690
|
+
const r = await this.callAnthropic(model, messages, tools, temperature, maxTokens);
|
|
691
|
+
content = r.content; toolCalls = r.toolCalls; usage = r.usage;
|
|
692
|
+
} else {
|
|
693
|
+
const r = await this.callOpenAI(model, messages, tools, temperature, maxTokens);
|
|
694
|
+
content = r.content; toolCalls = r.toolCalls; usage = r.usage;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
const name = agentName || "default";
|
|
698
|
+
if (!this.usageStats.has(name)) this.usageStats.set(name, { prompt_tokens: 0, completion_tokens: 0, calls: 0, cost: 0 });
|
|
699
|
+
const s = this.usageStats.get(name)!;
|
|
700
|
+
s.prompt_tokens += usage.promptTokens; s.completion_tokens += usage.completionTokens; s.calls += 1;
|
|
701
|
+
const cost = estimateCost(model, usage.promptTokens, usage.completionTokens);
|
|
702
|
+
s.cost += cost; this.totalCost += cost;
|
|
703
|
+
|
|
704
|
+
return { content, toolCalls, model, usage, cost, truncated: false };
|
|
705
|
+
} catch (e: any) {
|
|
706
|
+
lastError = e;
|
|
707
|
+
if (attempt >= maxRetries) throw e;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
throw lastError || new Error("Unknown error");
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
private async callOpenAI(
|
|
714
|
+
m: string, messages: Record<string, unknown>[], tools?: string[], temp?: number, maxTok?: number
|
|
715
|
+
): Promise<{ content: string; toolCalls: ToolCall[]; usage: UsageStats }> {
|
|
716
|
+
const apiKey = this.getApiKey(m);
|
|
717
|
+
const baseUrl = this.getBaseUrl(m);
|
|
718
|
+
const body: Record<string, unknown> = { model: m, messages, temperature: temp ?? 0.7, max_tokens: maxTok ?? 4096 };
|
|
719
|
+
if (tools?.length) {
|
|
720
|
+
const defs = tools.map(t => this._toolRegistry.get(t)).filter(Boolean) as any[];
|
|
721
|
+
if (defs.length) body.tools = defs.map(t => ({ type: "function", function: { name: t.name, description: t.description, parameters: this.paramsToSchema(t.parameters || []) } }));
|
|
722
|
+
}
|
|
723
|
+
const resp = await fetch(baseUrl + "/chat/completions", { method: "POST", headers: { "Content-Type": "application/json", Authorization: "Bearer " + apiKey }, body: JSON.stringify(body) });
|
|
724
|
+
if (!resp.ok) { const e: any = new Error("API " + resp.status + ": " + ((await resp.text()).slice(0, 200))); e.status_code = resp.status; throw e; }
|
|
725
|
+
const data: any = await resp.json();
|
|
726
|
+
const msg = data.choices?.[0]?.message || {};
|
|
727
|
+
return { content: msg.content || "", toolCalls: (msg.tool_calls || []).map((tc: any) => ({ id: tc.id, type: "function", function: { name: tc.function?.name || "", arguments: tc.function?.arguments || "{}" } })), usage: { promptTokens: data.usage?.prompt_tokens || 0, completionTokens: data.usage?.completion_tokens || 0 } };
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
private async callAnthropic(
|
|
731
|
+
m: string, messages: Record<string, unknown>[], tools?: string[], temp?: number, maxTok?: number
|
|
732
|
+
): Promise<{ content: string; toolCalls: ToolCall[]; usage: UsageStats }> {
|
|
733
|
+
const apiKey = this.getApiKey("anthropic");
|
|
734
|
+
const body: Record<string, unknown> = { model: m, max_tokens: maxTok ?? 4096, messages: messages.filter(msg => msg.role !== "system"), temperature: temp ?? 0.7 };
|
|
735
|
+
const sys = messages.find(msg => msg.role === "system"); if (sys) body.system = sys.content;
|
|
736
|
+
if (tools?.length) {
|
|
737
|
+
const defs = tools.map(t => this._toolRegistry.get(t)).filter(Boolean) as any[];
|
|
738
|
+
if (defs.length) body.tools = defs.map(t => ({ name: t.name, description: t.description, input_schema: this.paramsToSchema(t.parameters || []) }));
|
|
739
|
+
}
|
|
740
|
+
const resp = await fetch("https://api.anthropic.com/v1/messages", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": apiKey, "anthropic-version": "2023-06-01" }, body: JSON.stringify(body) });
|
|
741
|
+
if (!resp.ok) { const e: any = new Error("API " + resp.status + ": " + ((await resp.text()).slice(0, 200))); e.status_code = resp.status; throw e; }
|
|
742
|
+
const data: any = await resp.json(); let content = ""; const toolCalls: ToolCall[] = [];
|
|
743
|
+
for (const b of data.content || []) { if (b.type === "text") content += b.text; if (b.type === "tool_use") toolCalls.push({ id: b.id, type: "function", function: { name: b.name, arguments: JSON.stringify(b.input) } }); }
|
|
744
|
+
return { content, toolCalls, usage: { promptTokens: data.usage?.input_tokens || 0, completionTokens: data.usage?.output_tokens || 0 } };
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
private paramsToSchema(params: any[]): Record<string, any> {
|
|
748
|
+
const props: Record<string, any> = {};
|
|
749
|
+
for (const p of params) props[p.name] = { type: p.type === "integer" ? "integer" : p.type === "number" ? "number" : p.type === "boolean" ? "boolean" : "string", description: p.description };
|
|
750
|
+
const required = params.filter(p => p.required).map(p => p.name);
|
|
751
|
+
return { type: "object", properties: props, ...(required.length > 0 ? { required } : {}) };
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
private getApiKey(model: string): string {
|
|
755
|
+
let provider = "openai"; const [pr] = splitProvider(model); if (pr) provider = pr;
|
|
756
|
+
else { const l = model.toLowerCase(); if (l.includes("claude")) provider = "anthropic"; else if (l.includes("deepseek")) provider = "deepseek"; else if (l.includes("groq")) provider = "groq"; else if (l.includes("openrouter")) provider = "openrouter"; else if (l.includes("gemini")) provider = "gemini"; }
|
|
757
|
+
const envMap = getProviderEnvMap();
|
|
758
|
+
const envVar = envMap.get(provider) || (provider.toUpperCase() + "_API_KEY");
|
|
759
|
+
const key = process.env[envVar];
|
|
760
|
+
if (!key) throw new Error("Missing " + envVar + ". Set environment variable or configure in ~/.skyloom/config.yaml");
|
|
761
|
+
return key;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
private getBaseUrl(model: string): string {
|
|
765
|
+
let provider = "openai"; const [pr] = splitProvider(model); if (pr) provider = pr;
|
|
766
|
+
else { const l = model.toLowerCase(); if (l.includes("claude")) return "https://api.anthropic.com/v1"; else if (l.includes("deepseek")) return "https://api.deepseek.com/v1"; else if (l.includes("groq")) return "https://api.groq.com/openai/v1"; else if (l.includes("openrouter")) return "https://openrouter.ai/api/v1"; else if (l.includes("ollama")) return ((process.env.OLLAMA_HOST || "http://localhost:11434") + "/v1"); }
|
|
767
|
+
if (provider === "deepseek") return "https://api.deepseek.com/v1";
|
|
768
|
+
if (provider === "groq") return "https://api.groq.com/openai/v1";
|
|
769
|
+
if (provider === "openrouter") return "https://openrouter.ai/api/v1";
|
|
770
|
+
if (provider === "ollama") return ((process.env.OLLAMA_HOST || "http://localhost:11434") + "/v1");
|
|
771
|
+
return "https://api.openai.com/v1";
|
|
695
772
|
}
|
|
696
773
|
|
|
697
|
-
/**
|
|
698
|
-
* Stream a completion (placeholder).
|
|
699
|
-
*/
|
|
700
774
|
async *stream(
|
|
701
|
-
|
|
702
|
-
_agentName?: string
|
|
775
|
+
messages: Record<string, unknown>[], agentName?: string
|
|
703
776
|
): AsyncGenerator<string> {
|
|
704
|
-
|
|
705
|
-
yield
|
|
777
|
+
const response = await this.complete(messages, agentName);
|
|
778
|
+
yield response.content;
|
|
706
779
|
}
|
|
707
780
|
|
|
708
|
-
/**
|
|
709
|
-
* Stream completion with tool awareness (placeholder).
|
|
710
|
-
*/
|
|
711
781
|
async *streamWithTools(
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
_tools?: string[],
|
|
715
|
-
_toolRegistry?: ToolRegistry,
|
|
716
|
-
_overrides?: Record<string, unknown>
|
|
782
|
+
messages: Record<string, unknown>[], agentName?: string, tools?: string[],
|
|
783
|
+
_toolRegistry?: ToolRegistry, overrides?: Record<string, unknown>
|
|
717
784
|
): AsyncGenerator<StreamEvent> {
|
|
718
|
-
|
|
719
|
-
yield {
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
};
|
|
785
|
+
const response = await this.complete(messages, agentName, tools, false, overrides);
|
|
786
|
+
if (response.content) yield { type: "content", text: response.content };
|
|
787
|
+
for (const tc of response.toolCalls || []) yield { type: "tool_call", toolCall: tc };
|
|
788
|
+
yield { type: "done", usage: response.usage, reasoningContent: response.reasoningContent };
|
|
723
789
|
}
|
|
724
790
|
}
|