@llmtune/cli 0.1.0 → 0.1.2
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 +1 -1
- package/dist/agent/conversation.d.ts +42 -0
- package/dist/agent/conversation.js +105 -0
- package/dist/agent/loop.d.ts +19 -0
- package/dist/agent/loop.js +185 -0
- package/dist/agent/planner.d.ts +8 -0
- package/dist/agent/planner.js +43 -0
- package/dist/auth/client.d.ts +4 -0
- package/dist/auth/client.js +24 -0
- package/dist/auth/config.d.ts +21 -0
- package/dist/auth/config.js +83 -0
- package/dist/commands/chat.d.ts +5 -0
- package/dist/commands/chat.js +27 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +37 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +93 -0
- package/dist/commands/marketplace.d.ts +6 -0
- package/dist/commands/marketplace.js +213 -0
- package/dist/commands/models.d.ts +2 -0
- package/dist/commands/models.js +53 -0
- package/dist/compact/history-store.d.ts +29 -0
- package/dist/compact/history-store.js +110 -0
- package/dist/compact/microcompact.d.ts +10 -0
- package/dist/compact/microcompact.js +43 -0
- package/dist/compact/service.d.ts +13 -0
- package/dist/compact/service.js +156 -0
- package/dist/context/analyzer.d.ts +26 -0
- package/dist/context/analyzer.js +99 -0
- package/dist/context/builder.d.ts +13 -0
- package/dist/context/builder.js +144 -0
- package/dist/context/cache.d.ts +6 -0
- package/dist/context/cache.js +8 -0
- package/dist/context/git-context.d.ts +9 -0
- package/dist/context/git-context.js +39 -0
- package/dist/context/llmtune-md.d.ts +6 -0
- package/dist/context/llmtune-md.js +73 -0
- package/dist/context/workspace.d.ts +11 -0
- package/dist/context/workspace.js +115 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -2
- package/dist/marketplace/client.d.ts +52 -0
- package/dist/marketplace/client.js +86 -0
- package/dist/memory/files.d.ts +14 -0
- package/dist/memory/files.js +116 -0
- package/dist/memory/service.d.ts +22 -0
- package/dist/memory/service.js +146 -0
- package/dist/repl/repl.d.ts +8 -0
- package/dist/repl/repl.js +375 -0
- package/dist/skills/args.d.ts +10 -0
- package/dist/skills/args.js +37 -0
- package/dist/skills/frontmatter.d.ts +6 -0
- package/dist/skills/frontmatter.js +44 -0
- package/dist/skills/loader.d.ts +5 -0
- package/dist/skills/loader.js +59 -0
- package/dist/skills/registry.d.ts +27 -0
- package/dist/skills/registry.js +162 -0
- package/dist/skills/signing/signer.d.ts +19 -0
- package/dist/skills/signing/signer.js +110 -0
- package/dist/skills/trust.d.ts +11 -0
- package/dist/skills/trust.js +42 -0
- package/dist/telemetry/logger.d.ts +51 -0
- package/dist/telemetry/logger.js +135 -0
- package/dist/tools/permissions.d.ts +20 -0
- package/dist/tools/permissions.js +58 -0
- package/dist/tools/protocol.d.ts +22 -0
- package/dist/tools/protocol.js +3 -0
- package/dist/tools/registry.d.ts +20 -0
- package/dist/tools/registry.js +77 -0
- package/dist/tools/sandbox/docker.d.ts +16 -0
- package/dist/tools/sandbox/docker.js +240 -0
- package/dist/tools/sandbox/index.d.ts +18 -0
- package/dist/tools/sandbox/index.js +80 -0
- package/dist/tools/tools/ask-user.d.ts +3 -0
- package/dist/tools/tools/ask-user.js +56 -0
- package/dist/tools/tools/bash.d.ts +3 -0
- package/dist/tools/tools/bash.js +85 -0
- package/dist/tools/tools/edit.d.ts +3 -0
- package/dist/tools/tools/edit.js +138 -0
- package/dist/tools/tools/glob.d.ts +3 -0
- package/dist/tools/tools/glob.js +63 -0
- package/dist/tools/tools/grep.d.ts +3 -0
- package/dist/tools/tools/grep.js +148 -0
- package/dist/tools/tools/read.d.ts +3 -0
- package/dist/tools/tools/read.js +85 -0
- package/dist/tools/tools/web-fetch.d.ts +3 -0
- package/dist/tools/tools/web-fetch.js +143 -0
- package/dist/tools/tools/write.d.ts +3 -0
- package/dist/tools/tools/write.js +84 -0
- package/dist/tools/validation.d.ts +13 -0
- package/dist/tools/validation.js +142 -0
- package/dist/utils/markdown.d.ts +9 -0
- package/dist/utils/markdown.js +89 -0
- package/dist/utils/streaming.d.ts +10 -0
- package/dist/utils/streaming.js +63 -0
- package/dist/utils/tokens.d.ts +12 -0
- package/dist/utils/tokens.js +44 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +9 -0
- package/package.json +2 -2
- package/dist/agent/conversation.d.ts.map +0 -1
- package/dist/agent/loop.d.ts.map +0 -1
- package/dist/agent/planner.d.ts.map +0 -1
- package/dist/auth/client.d.ts.map +0 -1
- package/dist/auth/config.d.ts.map +0 -1
- package/dist/commands/chat.d.ts.map +0 -1
- package/dist/commands/config.d.ts.map +0 -1
- package/dist/commands/login.d.ts.map +0 -1
- package/dist/commands/marketplace.d.ts.map +0 -1
- package/dist/commands/models.d.ts.map +0 -1
- package/dist/compact/history-store.d.ts.map +0 -1
- package/dist/compact/microcompact.d.ts.map +0 -1
- package/dist/compact/service.d.ts.map +0 -1
- package/dist/context/analyzer.d.ts.map +0 -1
- package/dist/context/builder.d.ts.map +0 -1
- package/dist/context/cache.d.ts.map +0 -1
- package/dist/context/git-context.d.ts.map +0 -1
- package/dist/context/llmtune-md.d.ts.map +0 -1
- package/dist/context/workspace.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/marketplace/client.d.ts.map +0 -1
- package/dist/memory/files.d.ts.map +0 -1
- package/dist/memory/service.d.ts.map +0 -1
- package/dist/repl/repl.d.ts.map +0 -1
- package/dist/skills/args.d.ts.map +0 -1
- package/dist/skills/frontmatter.d.ts.map +0 -1
- package/dist/skills/loader.d.ts.map +0 -1
- package/dist/skills/registry.d.ts.map +0 -1
- package/dist/skills/signing/signer.d.ts.map +0 -1
- package/dist/skills/trust.d.ts.map +0 -1
- package/dist/telemetry/logger.d.ts.map +0 -1
- package/dist/tools/permissions.d.ts.map +0 -1
- package/dist/tools/protocol.d.ts.map +0 -1
- package/dist/tools/registry.d.ts.map +0 -1
- package/dist/tools/sandbox/docker.d.ts.map +0 -1
- package/dist/tools/sandbox/index.d.ts.map +0 -1
- package/dist/tools/tools/ask-user.d.ts.map +0 -1
- package/dist/tools/tools/bash.d.ts.map +0 -1
- package/dist/tools/tools/edit.d.ts.map +0 -1
- package/dist/tools/tools/glob.d.ts.map +0 -1
- package/dist/tools/tools/grep.d.ts.map +0 -1
- package/dist/tools/tools/read.d.ts.map +0 -1
- package/dist/tools/tools/web-fetch.d.ts.map +0 -1
- package/dist/tools/tools/write.d.ts.map +0 -1
- package/dist/tools/validation.d.ts.map +0 -1
- package/dist/utils/markdown.d.ts.map +0 -1
- package/dist/utils/streaming.d.ts.map +0 -1
- package/dist/utils/tokens.d.ts.map +0 -1
- package/src/agent/conversation.ts +0 -140
- package/src/agent/loop.ts +0 -215
- package/src/agent/planner.ts +0 -55
- package/src/auth/client.ts +0 -19
- package/src/auth/config.ts +0 -89
- package/src/commands/chat.ts +0 -28
- package/src/commands/config.ts +0 -36
- package/src/commands/login.ts +0 -63
- package/src/commands/marketplace.ts +0 -190
- package/src/commands/models.ts +0 -74
- package/src/compact/history-store.ts +0 -101
- package/src/compact/microcompact.ts +0 -49
- package/src/compact/service.ts +0 -154
- package/src/context/analyzer.ts +0 -127
- package/src/context/builder.ts +0 -123
- package/src/context/cache.ts +0 -11
- package/src/context/git-context.ts +0 -58
- package/src/context/llmtune-md.ts +0 -48
- package/src/context/workspace.ts +0 -139
- package/src/index.ts +0 -100
- package/src/marketplace/client.ts +0 -118
- package/src/memory/files.ts +0 -81
- package/src/memory/service.ts +0 -124
- package/src/repl/repl.ts +0 -400
- package/src/skills/args.ts +0 -35
- package/src/skills/builtin/explain-code/SKILL.md +0 -30
- package/src/skills/frontmatter.ts +0 -47
- package/src/skills/loader.ts +0 -25
- package/src/skills/registry.ts +0 -155
- package/src/skills/signing/signer.ts +0 -101
- package/src/skills/trust.ts +0 -50
- package/src/telemetry/logger.ts +0 -108
- package/src/tools/permissions.ts +0 -83
- package/src/tools/protocol.ts +0 -24
- package/src/tools/registry.ts +0 -93
- package/src/tools/sandbox/docker.ts +0 -225
- package/src/tools/sandbox/index.ts +0 -91
- package/src/tools/tools/ask-user.ts +0 -60
- package/src/tools/tools/bash.ts +0 -97
- package/src/tools/tools/edit.ts +0 -111
- package/src/tools/tools/glob.ts +0 -68
- package/src/tools/tools/grep.ts +0 -121
- package/src/tools/tools/read.ts +0 -57
- package/src/tools/tools/web-fetch.ts +0 -158
- package/src/tools/tools/write.ts +0 -52
- package/src/tools/validation.ts +0 -164
- package/src/utils/markdown.ts +0 -96
- package/src/utils/streaming.ts +0 -63
- package/src/utils/tokens.ts +0 -41
- package/tsconfig.json +0 -20
package/README.md
CHANGED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface Message {
|
|
2
|
+
role: "system" | "user" | "assistant" | "tool";
|
|
3
|
+
content: string;
|
|
4
|
+
toolCalls?: ToolCallMessage[];
|
|
5
|
+
toolCallId?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ToolCallMessage {
|
|
8
|
+
id: string;
|
|
9
|
+
type: "function";
|
|
10
|
+
function: {
|
|
11
|
+
name: string;
|
|
12
|
+
arguments: string;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export interface ConversationMeta {
|
|
16
|
+
id: string;
|
|
17
|
+
createdAt: string;
|
|
18
|
+
updatedAt: string;
|
|
19
|
+
model: string;
|
|
20
|
+
messageCount: number;
|
|
21
|
+
totalTokens?: number;
|
|
22
|
+
}
|
|
23
|
+
export declare class Conversation {
|
|
24
|
+
messages: Message[];
|
|
25
|
+
id: string;
|
|
26
|
+
model: string;
|
|
27
|
+
createdAt: Date;
|
|
28
|
+
totalTokens: number;
|
|
29
|
+
constructor(model: string);
|
|
30
|
+
addSystemMessage(content: string): void;
|
|
31
|
+
addUserMessage(content: string): void;
|
|
32
|
+
addAssistantMessage(content: string, toolCalls?: ToolCallMessage[]): void;
|
|
33
|
+
addToolResult(toolCallId: string, content: string | Record<string, unknown>, isError?: boolean): void;
|
|
34
|
+
clear(): void;
|
|
35
|
+
getApiMessages(): Message[];
|
|
36
|
+
getTokenEstimate(): number;
|
|
37
|
+
getMeta(): ConversationMeta;
|
|
38
|
+
save(sessionsDir?: string): string;
|
|
39
|
+
static load(filePath: string): Conversation;
|
|
40
|
+
static listSessions(sessionsDir?: string): ConversationMeta[];
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=conversation.d.ts.map
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Conversation = void 0;
|
|
7
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const os_1 = __importDefault(require("os"));
|
|
11
|
+
class Conversation {
|
|
12
|
+
messages = [];
|
|
13
|
+
id;
|
|
14
|
+
model;
|
|
15
|
+
createdAt;
|
|
16
|
+
totalTokens = 0;
|
|
17
|
+
constructor(model) {
|
|
18
|
+
this.id = crypto_1.default.randomUUID();
|
|
19
|
+
this.model = model;
|
|
20
|
+
this.createdAt = new Date();
|
|
21
|
+
}
|
|
22
|
+
addSystemMessage(content) {
|
|
23
|
+
this.messages.push({ role: "system", content });
|
|
24
|
+
}
|
|
25
|
+
addUserMessage(content) {
|
|
26
|
+
this.messages.push({ role: "user", content });
|
|
27
|
+
}
|
|
28
|
+
addAssistantMessage(content, toolCalls) {
|
|
29
|
+
this.messages.push({ role: "assistant", content, toolCalls });
|
|
30
|
+
}
|
|
31
|
+
addToolResult(toolCallId, content, isError) {
|
|
32
|
+
const text = typeof content === "string" ? content : JSON.stringify(content);
|
|
33
|
+
this.messages.push({
|
|
34
|
+
role: "tool",
|
|
35
|
+
content: isError ? `Error: ${text}` : text,
|
|
36
|
+
toolCallId,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
clear() {
|
|
40
|
+
this.messages = [];
|
|
41
|
+
}
|
|
42
|
+
getApiMessages() {
|
|
43
|
+
return [...this.messages];
|
|
44
|
+
}
|
|
45
|
+
getTokenEstimate() {
|
|
46
|
+
return this.messages.reduce((total, msg) => {
|
|
47
|
+
const content = typeof msg.content === "string"
|
|
48
|
+
? msg.content
|
|
49
|
+
: JSON.stringify(msg.content);
|
|
50
|
+
return total + Math.ceil(content.length / 4);
|
|
51
|
+
}, 0);
|
|
52
|
+
}
|
|
53
|
+
getMeta() {
|
|
54
|
+
return {
|
|
55
|
+
id: this.id,
|
|
56
|
+
createdAt: this.createdAt.toISOString(),
|
|
57
|
+
updatedAt: new Date().toISOString(),
|
|
58
|
+
model: this.model,
|
|
59
|
+
messageCount: this.messages.length,
|
|
60
|
+
totalTokens: this.totalTokens || undefined,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
save(sessionsDir) {
|
|
64
|
+
const dir = sessionsDir || path_1.default.join(os_1.default.homedir(), ".llmtune", "sessions");
|
|
65
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
66
|
+
const filePath = path_1.default.join(dir, `${this.id}.json`);
|
|
67
|
+
const data = {
|
|
68
|
+
meta: this.getMeta(),
|
|
69
|
+
messages: this.messages,
|
|
70
|
+
};
|
|
71
|
+
fs_1.default.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
72
|
+
return filePath;
|
|
73
|
+
}
|
|
74
|
+
static load(filePath) {
|
|
75
|
+
const raw = fs_1.default.readFileSync(filePath, "utf-8");
|
|
76
|
+
const data = JSON.parse(raw);
|
|
77
|
+
const conv = new Conversation(data.meta.model);
|
|
78
|
+
conv.id = data.meta.id;
|
|
79
|
+
conv.createdAt = new Date(data.meta.createdAt);
|
|
80
|
+
conv.messages = data.messages;
|
|
81
|
+
conv.totalTokens = data.meta.totalTokens || 0;
|
|
82
|
+
return conv;
|
|
83
|
+
}
|
|
84
|
+
static listSessions(sessionsDir) {
|
|
85
|
+
const dir = sessionsDir || path_1.default.join(os_1.default.homedir(), ".llmtune", "sessions");
|
|
86
|
+
if (!fs_1.default.existsSync(dir))
|
|
87
|
+
return [];
|
|
88
|
+
return fs_1.default
|
|
89
|
+
.readdirSync(dir)
|
|
90
|
+
.filter((f) => f.endsWith(".json"))
|
|
91
|
+
.map((f) => {
|
|
92
|
+
try {
|
|
93
|
+
const raw = fs_1.default.readFileSync(path_1.default.join(dir, f), "utf-8");
|
|
94
|
+
return JSON.parse(raw).meta;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
.filter((m) => m !== null)
|
|
101
|
+
.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
exports.Conversation = Conversation;
|
|
105
|
+
//# sourceMappingURL=conversation.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
import { ToolRegistry } from "../tools/registry";
|
|
3
|
+
import { Conversation } from "./conversation";
|
|
4
|
+
export interface AgentLoopConfig {
|
|
5
|
+
model?: string;
|
|
6
|
+
maxTurns?: number;
|
|
7
|
+
verbose?: boolean;
|
|
8
|
+
cwd: string;
|
|
9
|
+
workspaceRoot: string;
|
|
10
|
+
}
|
|
11
|
+
export interface AgentLoopResult {
|
|
12
|
+
finalText: string;
|
|
13
|
+
totalToolCalls: number;
|
|
14
|
+
totalTokensIn: number;
|
|
15
|
+
totalTokensOut: number;
|
|
16
|
+
turns: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function runAgentLoop(client: OpenAI, conversation: Conversation, registry: ToolRegistry, userInput: string, config: AgentLoopConfig, onTextChunk?: (text: string) => void): Promise<AgentLoopResult>;
|
|
19
|
+
//# sourceMappingURL=loop.d.ts.map
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runAgentLoop = runAgentLoop;
|
|
7
|
+
const builder_1 = require("../context/builder");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
async function runAgentLoop(client, conversation, registry, userInput, config, onTextChunk) {
|
|
10
|
+
const model = config.model ?? "z-ai/GLM-5.1";
|
|
11
|
+
const maxTurns = config.maxTurns ?? 20;
|
|
12
|
+
conversation.addUserMessage(userInput);
|
|
13
|
+
const toolSpecs = registry.listSpecs();
|
|
14
|
+
const openaiTools = toolSpecs.map((spec) => ({
|
|
15
|
+
type: "function",
|
|
16
|
+
function: {
|
|
17
|
+
name: spec.name,
|
|
18
|
+
description: spec.description,
|
|
19
|
+
parameters: spec.inputSchema,
|
|
20
|
+
},
|
|
21
|
+
}));
|
|
22
|
+
const contextResult = await (0, builder_1.buildContextPrompt)(config.workspaceRoot, config.cwd);
|
|
23
|
+
const contextPrompt = contextResult.prompt;
|
|
24
|
+
let totalToolCalls = 0;
|
|
25
|
+
let totalTokensIn = 0;
|
|
26
|
+
let totalTokensOut = 0;
|
|
27
|
+
let turns = 0;
|
|
28
|
+
let finalText = "";
|
|
29
|
+
for (let turn = 0; turn < maxTurns; turn++) {
|
|
30
|
+
const apiMessages = conversation.getApiMessages();
|
|
31
|
+
const systemMessage = {
|
|
32
|
+
role: "system",
|
|
33
|
+
content: contextPrompt,
|
|
34
|
+
};
|
|
35
|
+
const allMessages = [
|
|
36
|
+
systemMessage,
|
|
37
|
+
...apiMessages.map((msg) => {
|
|
38
|
+
if (msg.role === "system")
|
|
39
|
+
return { role: "system", content: msg.content };
|
|
40
|
+
if (msg.role === "user")
|
|
41
|
+
return { role: "user", content: msg.content };
|
|
42
|
+
if (msg.role === "assistant") {
|
|
43
|
+
const m = {
|
|
44
|
+
role: "assistant",
|
|
45
|
+
content: msg.content || null,
|
|
46
|
+
};
|
|
47
|
+
if (msg.toolCalls && msg.toolCalls.length > 0) {
|
|
48
|
+
m.tool_calls = msg.toolCalls.map((tc) => ({
|
|
49
|
+
id: tc.id,
|
|
50
|
+
type: "function",
|
|
51
|
+
function: { name: tc.function.name, arguments: tc.function.arguments },
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
return m;
|
|
55
|
+
}
|
|
56
|
+
if (msg.role === "tool") {
|
|
57
|
+
return {
|
|
58
|
+
role: "tool",
|
|
59
|
+
tool_call_id: msg.toolCallId ?? "",
|
|
60
|
+
content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return { role: "user", content: msg.content };
|
|
64
|
+
}),
|
|
65
|
+
];
|
|
66
|
+
const stream = await client.chat.completions.create({
|
|
67
|
+
model,
|
|
68
|
+
messages: allMessages,
|
|
69
|
+
tools: openaiTools.length > 0 ? openaiTools : undefined,
|
|
70
|
+
stream: true,
|
|
71
|
+
temperature: 0.7,
|
|
72
|
+
max_tokens: 16384,
|
|
73
|
+
});
|
|
74
|
+
let assistantContent = "";
|
|
75
|
+
const toolCalls = [];
|
|
76
|
+
let currentToolCall = null;
|
|
77
|
+
for await (const chunk of stream) {
|
|
78
|
+
const delta = chunk.choices[0]?.delta;
|
|
79
|
+
if (!delta)
|
|
80
|
+
continue;
|
|
81
|
+
if (delta.content) {
|
|
82
|
+
assistantContent += delta.content;
|
|
83
|
+
if (onTextChunk)
|
|
84
|
+
onTextChunk(delta.content);
|
|
85
|
+
else
|
|
86
|
+
process.stdout.write(delta.content);
|
|
87
|
+
}
|
|
88
|
+
if (delta.tool_calls) {
|
|
89
|
+
for (const tc of delta.tool_calls) {
|
|
90
|
+
if (tc.id && tc.function?.name) {
|
|
91
|
+
currentToolCall = {
|
|
92
|
+
id: tc.id,
|
|
93
|
+
name: tc.function.name,
|
|
94
|
+
arguments: tc.function.arguments ?? "",
|
|
95
|
+
};
|
|
96
|
+
toolCalls.push({
|
|
97
|
+
id: tc.id,
|
|
98
|
+
type: "function",
|
|
99
|
+
function: { name: tc.function.name, arguments: tc.function.arguments ?? "" },
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
else if (currentToolCall && tc.function?.arguments) {
|
|
103
|
+
currentToolCall.arguments += tc.function.arguments;
|
|
104
|
+
const last = toolCalls[toolCalls.length - 1];
|
|
105
|
+
if (last)
|
|
106
|
+
last.function.arguments = currentToolCall.arguments;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (chunk.usage) {
|
|
111
|
+
totalTokensIn += chunk.usage.prompt_tokens ?? 0;
|
|
112
|
+
totalTokensOut += chunk.usage.completion_tokens ?? 0;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (!onTextChunk)
|
|
116
|
+
console.log();
|
|
117
|
+
turns++;
|
|
118
|
+
if (toolCalls.length === 0) {
|
|
119
|
+
conversation.addAssistantMessage(assistantContent);
|
|
120
|
+
finalText = assistantContent;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
conversation.addAssistantMessage(assistantContent, toolCalls);
|
|
124
|
+
for (const tc of toolCalls) {
|
|
125
|
+
totalToolCalls++;
|
|
126
|
+
let toolInput;
|
|
127
|
+
try {
|
|
128
|
+
toolInput = JSON.parse(tc.function.arguments);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
toolInput = { raw: tc.function.arguments };
|
|
132
|
+
}
|
|
133
|
+
const summary = summarizeToolInput(tc.function.name, toolInput);
|
|
134
|
+
console.log(chalk_1.default.cyan(` ▶ ${tc.function.name}`) + chalk_1.default.dim(` ${summary}`));
|
|
135
|
+
const toolCtx = {
|
|
136
|
+
workspaceRoot: config.workspaceRoot,
|
|
137
|
+
cwd: config.cwd,
|
|
138
|
+
};
|
|
139
|
+
const result = await registry.dispatchAsync(tc.function.name, toolInput, toolCtx);
|
|
140
|
+
if (result.isError) {
|
|
141
|
+
console.log(chalk_1.default.red(` ✗ ${tc.function.name}: ${String(result.output).slice(0, 200)}`));
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
console.log(chalk_1.default.green(` ✓ ${summarizeToolResult(tc.function.name, result.output)}`));
|
|
145
|
+
}
|
|
146
|
+
const resultStr = typeof result.output === "string" ? result.output : JSON.stringify(result.output);
|
|
147
|
+
conversation.addToolResult(tc.id, resultStr);
|
|
148
|
+
}
|
|
149
|
+
if (turn === maxTurns - 1) {
|
|
150
|
+
finalText = "[Max tool turns reached]";
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return { finalText, totalToolCalls, totalTokensIn, totalTokensOut, turns };
|
|
154
|
+
}
|
|
155
|
+
function summarizeToolInput(name, input) {
|
|
156
|
+
const n = name.toLowerCase();
|
|
157
|
+
if (n === "bash") {
|
|
158
|
+
const cmd = String(input.command ?? "").replace(/\n/g, " ");
|
|
159
|
+
return cmd.length > 60 ? cmd.slice(0, 57) + "..." : cmd;
|
|
160
|
+
}
|
|
161
|
+
if (n === "read" || n === "write" || n === "edit") {
|
|
162
|
+
return String(input.file_path ?? input.path ?? "");
|
|
163
|
+
}
|
|
164
|
+
if (n === "glob")
|
|
165
|
+
return String(input.pattern ?? "");
|
|
166
|
+
if (n === "grep")
|
|
167
|
+
return String(input.pattern ?? "");
|
|
168
|
+
return "";
|
|
169
|
+
}
|
|
170
|
+
function summarizeToolResult(name, output) {
|
|
171
|
+
if (typeof output === "string") {
|
|
172
|
+
return output.length > 100 ? output.slice(0, 97) + "..." : output;
|
|
173
|
+
}
|
|
174
|
+
if (typeof output === "object" && output !== null) {
|
|
175
|
+
const obj = output;
|
|
176
|
+
if (obj.error)
|
|
177
|
+
return `error: ${String(obj.error).slice(0, 80)}`;
|
|
178
|
+
if (obj.numFiles !== undefined)
|
|
179
|
+
return `${name} · ${obj.numFiles} results`;
|
|
180
|
+
if (obj.exit_code !== undefined)
|
|
181
|
+
return `${name} · exit ${obj.exit_code}`;
|
|
182
|
+
}
|
|
183
|
+
return `${name} completed`;
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=loop.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
export interface PlanResult {
|
|
3
|
+
needsTools: boolean;
|
|
4
|
+
toolsNeeded: string[];
|
|
5
|
+
plan: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function planToolUsage(client: OpenAI, model: string, userMessage: string, availableTools: string[]): Promise<PlanResult>;
|
|
8
|
+
//# sourceMappingURL=planner.d.ts.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.planToolUsage = planToolUsage;
|
|
4
|
+
const PLANNER_SYSTEM_PROMPT = `You are a tool-use planner. Given a user message and available tools, decide:
|
|
5
|
+
1. Does this request need any tools? (simple questions like "what is 2+2" do not)
|
|
6
|
+
2. If yes, which specific tools are needed?
|
|
7
|
+
3. What is the execution plan?
|
|
8
|
+
|
|
9
|
+
Respond in this exact JSON format:
|
|
10
|
+
{"needs_tools": boolean, "tools_needed": ["tool_name", ...], "plan": "brief description"}
|
|
11
|
+
|
|
12
|
+
Only include tools that are actually necessary. Do not over-plan.`;
|
|
13
|
+
async function planToolUsage(client, model, userMessage, availableTools) {
|
|
14
|
+
try {
|
|
15
|
+
const response = await client.chat.completions.create({
|
|
16
|
+
model,
|
|
17
|
+
messages: [
|
|
18
|
+
{ role: "system", content: PLANNER_SYSTEM_PROMPT },
|
|
19
|
+
{
|
|
20
|
+
role: "user",
|
|
21
|
+
content: `Available tools: ${availableTools.join(", ")}\n\nUser message: ${userMessage}`,
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
max_tokens: 200,
|
|
25
|
+
temperature: 0,
|
|
26
|
+
});
|
|
27
|
+
const content = response.choices[0]?.message?.content?.trim() ?? "";
|
|
28
|
+
const jsonMatch = content.match(/\{[\s\S]*\}/);
|
|
29
|
+
if (!jsonMatch) {
|
|
30
|
+
return { needsTools: true, toolsNeeded: [], plan: "proceed with all tools" };
|
|
31
|
+
}
|
|
32
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
33
|
+
return {
|
|
34
|
+
needsTools: parsed.needs_tools ?? true,
|
|
35
|
+
toolsNeeded: parsed.tools_needed ?? [],
|
|
36
|
+
plan: parsed.plan ?? "",
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return { needsTools: true, toolsNeeded: [], plan: "" };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=planner.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createClient = createClient;
|
|
7
|
+
exports.getDefaultModel = getDefaultModel;
|
|
8
|
+
const openai_1 = __importDefault(require("openai"));
|
|
9
|
+
const config_1 = require("./config");
|
|
10
|
+
function createClient() {
|
|
11
|
+
const apiKey = (0, config_1.loadConfig)().apiKey;
|
|
12
|
+
if (!apiKey) {
|
|
13
|
+
console.error("Not logged in. Run: llmtune login");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
return new openai_1.default({
|
|
17
|
+
apiKey,
|
|
18
|
+
baseURL: (0, config_1.getApiBase)(),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
function getDefaultModel() {
|
|
22
|
+
return (0, config_1.getDefaultModel)();
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface AuthConfig {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
apiBase: string;
|
|
4
|
+
defaultModel?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface AppConfig {
|
|
7
|
+
defaultProvider?: string;
|
|
8
|
+
providers?: Record<string, AuthConfig>;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
export declare function ensureConfigDir(): void;
|
|
12
|
+
export declare function loadConfig(): AppConfig;
|
|
13
|
+
export declare function saveConfig(config: AppConfig): void;
|
|
14
|
+
export declare function getApiKey(): string | null;
|
|
15
|
+
export declare function getApiBase(): string;
|
|
16
|
+
export declare function getDefaultModel(): string;
|
|
17
|
+
export declare function isLoggedIn(): boolean;
|
|
18
|
+
export declare function logout(): void;
|
|
19
|
+
export declare function getConfigPath(): string;
|
|
20
|
+
export declare function getConfigDir(): string;
|
|
21
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ensureConfigDir = ensureConfigDir;
|
|
7
|
+
exports.loadConfig = loadConfig;
|
|
8
|
+
exports.saveConfig = saveConfig;
|
|
9
|
+
exports.getApiKey = getApiKey;
|
|
10
|
+
exports.getApiBase = getApiBase;
|
|
11
|
+
exports.getDefaultModel = getDefaultModel;
|
|
12
|
+
exports.isLoggedIn = isLoggedIn;
|
|
13
|
+
exports.logout = logout;
|
|
14
|
+
exports.getConfigPath = getConfigPath;
|
|
15
|
+
exports.getConfigDir = getConfigDir;
|
|
16
|
+
const fs_1 = __importDefault(require("fs"));
|
|
17
|
+
const path_1 = __importDefault(require("path"));
|
|
18
|
+
const os_1 = __importDefault(require("os"));
|
|
19
|
+
const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), ".llmtune");
|
|
20
|
+
const CONFIG_FILE = path_1.default.join(CONFIG_DIR, "config.json");
|
|
21
|
+
function ensureConfigDir() {
|
|
22
|
+
if (!fs_1.default.existsSync(CONFIG_DIR)) {
|
|
23
|
+
fs_1.default.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
24
|
+
}
|
|
25
|
+
const memDir = path_1.default.join(CONFIG_DIR, "memory");
|
|
26
|
+
if (!fs_1.default.existsSync(memDir)) {
|
|
27
|
+
fs_1.default.mkdirSync(memDir, { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
const sessionsDir = path_1.default.join(CONFIG_DIR, "sessions");
|
|
30
|
+
if (!fs_1.default.existsSync(sessionsDir)) {
|
|
31
|
+
fs_1.default.mkdirSync(sessionsDir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
const logsDir = path_1.default.join(CONFIG_DIR, "logs");
|
|
34
|
+
if (!fs_1.default.existsSync(logsDir)) {
|
|
35
|
+
fs_1.default.mkdirSync(logsDir, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function loadConfig() {
|
|
39
|
+
ensureConfigDir();
|
|
40
|
+
if (!fs_1.default.existsSync(CONFIG_FILE)) {
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const raw = fs_1.default.readFileSync(CONFIG_FILE, "utf-8");
|
|
45
|
+
return JSON.parse(raw);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function saveConfig(config) {
|
|
52
|
+
ensureConfigDir();
|
|
53
|
+
fs_1.default.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
|
|
54
|
+
}
|
|
55
|
+
function getApiKey() {
|
|
56
|
+
const config = loadConfig();
|
|
57
|
+
return config.apiKey ?? null;
|
|
58
|
+
}
|
|
59
|
+
function getApiBase() {
|
|
60
|
+
const config = loadConfig();
|
|
61
|
+
return config.apiBase ?? "https://api.llmtune.io/api/agent/v1";
|
|
62
|
+
}
|
|
63
|
+
function getDefaultModel() {
|
|
64
|
+
const config = loadConfig();
|
|
65
|
+
return config.defaultModel ?? "z-ai/GLM-5.1";
|
|
66
|
+
}
|
|
67
|
+
function isLoggedIn() {
|
|
68
|
+
return getApiKey() !== null;
|
|
69
|
+
}
|
|
70
|
+
function logout() {
|
|
71
|
+
const config = loadConfig();
|
|
72
|
+
delete config.apiKey;
|
|
73
|
+
delete config.apiBase;
|
|
74
|
+
delete config.defaultModel;
|
|
75
|
+
saveConfig(config);
|
|
76
|
+
}
|
|
77
|
+
function getConfigPath() {
|
|
78
|
+
return CONFIG_FILE;
|
|
79
|
+
}
|
|
80
|
+
function getConfigDir() {
|
|
81
|
+
return CONFIG_DIR;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.chatCommand = chatCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const config_js_1 = require("../auth/config.js");
|
|
9
|
+
const client_js_1 = require("../auth/client.js");
|
|
10
|
+
const repl_js_1 = require("../repl/repl.js");
|
|
11
|
+
async function chatCommand(options) {
|
|
12
|
+
const config = (0, config_js_1.loadConfig)();
|
|
13
|
+
if (!config.apiKey) {
|
|
14
|
+
console.log(chalk_1.default.red('No API key configured. Run "llmtune login" first.'));
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const client = (0, client_js_1.createClient)();
|
|
18
|
+
const model = options.model || config.defaultModel || "z-ai/GLM-5.1";
|
|
19
|
+
const stream = options.stream !== false;
|
|
20
|
+
console.log(chalk_1.default.cyan(`\nLLMTune CLI`));
|
|
21
|
+
console.log(chalk_1.default.dim(`Connected to ${(0, config_js_1.getApiBase)()}`));
|
|
22
|
+
console.log(chalk_1.default.dim(`Model: ${model}`));
|
|
23
|
+
console.log(chalk_1.default.dim(`Stream: ${stream}`));
|
|
24
|
+
console.log(chalk_1.default.dim(`Type /help for commands, /exit to quit\n`));
|
|
25
|
+
await (0, repl_js_1.startRepl)({ client, model, stream });
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=chat.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.showConfig = showConfig;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const config_1 = require("../auth/config");
|
|
9
|
+
function showConfig() {
|
|
10
|
+
const configPath = (0, config_1.getConfigPath)();
|
|
11
|
+
try {
|
|
12
|
+
const config = (0, config_1.loadConfig)();
|
|
13
|
+
console.log(chalk_1.default.bold("\nConfiguration\n"));
|
|
14
|
+
console.log(` Config file: ${chalk_1.default.cyan(configPath)}`);
|
|
15
|
+
const apiKey = config.apiKey;
|
|
16
|
+
const apiBase = config.apiBase;
|
|
17
|
+
const defaultModel = config.defaultModel;
|
|
18
|
+
console.log(` API base: ${chalk_1.default.cyan(apiBase ?? "(not set)")}`);
|
|
19
|
+
console.log(` Default model: ${chalk_1.default.cyan(defaultModel || "(not set)")}`);
|
|
20
|
+
if (apiKey) {
|
|
21
|
+
const masked = apiKey.length > 12
|
|
22
|
+
? `${apiKey.slice(0, 6)}...${apiKey.slice(-4)}`
|
|
23
|
+
: "(set)";
|
|
24
|
+
console.log(` API key: ${chalk_1.default.green(masked)}`);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
console.log(` API key: ${chalk_1.default.red("(not set)")}`);
|
|
28
|
+
console.log(chalk_1.default.dim(`\n Run ${chalk_1.default.bold("llmtune login")} to configure your API key.`));
|
|
29
|
+
}
|
|
30
|
+
console.log();
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
console.log(chalk_1.default.dim(`\n No config found at ${configPath}`));
|
|
34
|
+
console.log(chalk_1.default.dim(` Run ${chalk_1.default.bold("llmtune login")} to get started.\n`));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=config.js.map
|