@takimcizgisi/ajan 0.2.5
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/LICENSE +674 -0
- package/README.md +366 -0
- package/config/models.json +54 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +741 -0
- package/dist/cli/tui.d.ts +145 -0
- package/dist/cli/tui.js +1003 -0
- package/dist/config/configManager.d.ts +18 -0
- package/dist/config/configManager.js +204 -0
- package/dist/config/types.d.ts +98 -0
- package/dist/config/types.js +2 -0
- package/dist/core/agent.d.ts +88 -0
- package/dist/core/agent.js +222 -0
- package/dist/electron/main.d.ts +1 -0
- package/dist/electron/main.js +364 -0
- package/dist/electron/preload.d.ts +1 -0
- package/dist/electron/preload.js +67 -0
- package/dist/engine/llama.d.ts +39 -0
- package/dist/engine/llama.js +146 -0
- package/dist/engine/modelManager.d.ts +18 -0
- package/dist/engine/modelManager.js +73 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/service.d.ts +90 -0
- package/dist/service.js +268 -0
- package/dist/tools/data.d.ts +3 -0
- package/dist/tools/data.js +111 -0
- package/dist/tools/edit.d.ts +2 -0
- package/dist/tools/edit.js +48 -0
- package/dist/tools/file.d.ts +6 -0
- package/dist/tools/file.js +274 -0
- package/dist/tools/filesys.d.ts +3 -0
- package/dist/tools/filesys.js +74 -0
- package/dist/tools/memory.d.ts +3 -0
- package/dist/tools/memory.js +69 -0
- package/dist/tools/patch.d.ts +15 -0
- package/dist/tools/patch.js +129 -0
- package/dist/tools/registry.d.ts +10 -0
- package/dist/tools/registry.js +64 -0
- package/dist/tools/taskComplete.d.ts +2 -0
- package/dist/tools/taskComplete.js +19 -0
- package/dist/tools/terminal.d.ts +2 -0
- package/dist/tools/terminal.js +130 -0
- package/dist/tools/utils.d.ts +7 -0
- package/dist/tools/utils.js +63 -0
- package/dist/tools/web.d.ts +8 -0
- package/dist/tools/web.js +181 -0
- package/dist/utils/clipboard.d.ts +2 -0
- package/dist/utils/clipboard.js +40 -0
- package/dist/utils/lock.d.ts +12 -0
- package/dist/utils/lock.js +68 -0
- package/dist/utils/paths.d.ts +13 -0
- package/dist/utils/paths.js +58 -0
- package/dist/utils/projectContext.d.ts +2 -0
- package/dist/utils/projectContext.js +24 -0
- package/dist/utils/sacGuard.d.ts +4 -0
- package/dist/utils/sacGuard.js +47 -0
- package/dist/utils/sessions.d.ts +29 -0
- package/dist/utils/sessions.js +64 -0
- package/dist/utils/spinner.d.ts +10 -0
- package/dist/utils/spinner.js +32 -0
- package/package.json +106 -0
- package/src/gui/assets/image/AJAN_LOGO.svg +1 -0
- package/src/gui/assets/image/ajan.png +0 -0
- package/src/gui/assets/image/logo.svg +1 -0
- package/src/gui/assets/image/opencode.svg +18 -0
- package/src/gui/css/style.css +430 -0
- package/src/gui/html/chat.html +34 -0
- package/src/gui/html/downloads.html +11 -0
- package/src/gui/html/hakkinda.html +26 -0
- package/src/gui/html/index.html +82 -0
- package/src/gui/html/projects.html +12 -0
- package/src/gui/html/settings.html +4 -0
- package/src/gui/js/app.js +197 -0
- package/src/gui/js/chat.js +434 -0
- package/src/gui/js/downloads.js +172 -0
- package/src/gui/js/hakkinda.js +16 -0
- package/src/gui/js/projects.js +113 -0
- package/src/gui/js/settings.js +177 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { existsSync, statSync } from "node:fs";
|
|
2
|
+
import { createModelDownloader } from "node-llama-cpp";
|
|
3
|
+
import { resolveModelFilePath } from "../config/configManager.js";
|
|
4
|
+
import { getModelsDir } from "../utils/paths.js";
|
|
5
|
+
import { logger } from "../utils/paths.js";
|
|
6
|
+
export function formatBytes(bytes) {
|
|
7
|
+
if (!Number.isFinite(bytes) || bytes <= 0)
|
|
8
|
+
return "?";
|
|
9
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
10
|
+
let value = bytes;
|
|
11
|
+
let unit = 0;
|
|
12
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
13
|
+
value /= 1024;
|
|
14
|
+
unit++;
|
|
15
|
+
}
|
|
16
|
+
return `${value.toFixed(unit === 0 ? 0 : 2)} ${units[unit]}`;
|
|
17
|
+
}
|
|
18
|
+
export function formatTime(ms) {
|
|
19
|
+
if (!Number.isFinite(ms) || ms <= 0)
|
|
20
|
+
return "?";
|
|
21
|
+
const totalSeconds = Math.floor(ms / 1000);
|
|
22
|
+
const h = Math.floor(totalSeconds / 3600);
|
|
23
|
+
const m = Math.floor((totalSeconds % 3600) / 60);
|
|
24
|
+
const s = totalSeconds % 60;
|
|
25
|
+
if (h > 0)
|
|
26
|
+
return `${h}sa ${m}dk`;
|
|
27
|
+
if (m > 0)
|
|
28
|
+
return `${m}dk ${s}sn`;
|
|
29
|
+
return `${s}sn`;
|
|
30
|
+
}
|
|
31
|
+
export async function downloadModel(model, options = {}) {
|
|
32
|
+
const dirPath = getModelsDir();
|
|
33
|
+
const targetPath = resolveModelFilePath(model);
|
|
34
|
+
if (existsSync(targetPath)) {
|
|
35
|
+
logger.info(`Model zaten mevcut: ${targetPath}`);
|
|
36
|
+
return targetPath;
|
|
37
|
+
}
|
|
38
|
+
logger.info(`Model indiriliyor: ${model.uri}`);
|
|
39
|
+
const downloader = await createModelDownloader({
|
|
40
|
+
modelUri: model.uri,
|
|
41
|
+
dirPath,
|
|
42
|
+
fileName: model.fileName,
|
|
43
|
+
skipExisting: true,
|
|
44
|
+
deleteTempFileOnCancel: false,
|
|
45
|
+
showCliProgress: false,
|
|
46
|
+
onProgress: (status) => {
|
|
47
|
+
options.onProgress?.(status);
|
|
48
|
+
if (options.showProgress) {
|
|
49
|
+
const pct = status.totalSize > 0 ? Math.round((status.downloadedSize / status.totalSize) * 100) : 0;
|
|
50
|
+
const speed = `${formatBytes(status.averageSpeed)}/sn`;
|
|
51
|
+
const eta = formatTime(status.estimatedTimeLeft);
|
|
52
|
+
process.stdout.write(`\r [indirme] %${pct} · ${formatBytes(status.downloadedSize)}/${formatBytes(status.totalSize)} · ${speed} · kalan ${eta} `);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
const downloadedPath = await downloader.download({ signal: options.signal });
|
|
57
|
+
if (options.showProgress)
|
|
58
|
+
process.stdout.write("\n");
|
|
59
|
+
logger.info(`Model indirildi: ${downloadedPath}`);
|
|
60
|
+
return downloadedPath;
|
|
61
|
+
}
|
|
62
|
+
export function getModelFileSize(path) {
|
|
63
|
+
try {
|
|
64
|
+
return statSync(path).size;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export function isDownloaded(model) {
|
|
71
|
+
return existsSync(resolveModelFilePath(model));
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=modelManager.js.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { AjanAgent, type AgentOptions, type AgentEvents } from "./core/agent.js";
|
|
2
|
+
export { AjanService, type ServiceEvents, type ModelWithStatus } from "./service.js";
|
|
3
|
+
export { LlamaEngine, resolveChatWrapper } from "./engine/llama.js";
|
|
4
|
+
export { downloadModel, formatBytes, formatTime, isDownloaded, getModelFileSize, type DownloadProgress } from "./engine/modelManager.js";
|
|
5
|
+
export { DEFAULT_CONFIG, loadConfig, saveConfig, getModelsRegistry, getModelById, refreshRemoteModels, listInstalledModels, isModelInstalled, removeModelFile, resolveModelFilePath } from "./config/configManager.js";
|
|
6
|
+
export { createCoreTools, buildSessionFunctions } from "./tools/registry.js";
|
|
7
|
+
export { parseUnifiedDiff } from "./tools/patch.js";
|
|
8
|
+
export type { AjanConfig, ModelDefinition, RemoteModelsRegistry, SessionConfig, AgentTool, ToolHandler, ToolExecutionContext, ToolExecutionResult, ChatWrapperName, GpuBackend } from "./config/types.js";
|
|
9
|
+
export { getDataDir, getModelsDir, getConfigPath, getLogDir, setLogLevel, logger } from "./utils/paths.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { AjanAgent } from "./core/agent.js";
|
|
2
|
+
export { AjanService } from "./service.js";
|
|
3
|
+
export { LlamaEngine, resolveChatWrapper } from "./engine/llama.js";
|
|
4
|
+
export { downloadModel, formatBytes, formatTime, isDownloaded, getModelFileSize } from "./engine/modelManager.js";
|
|
5
|
+
export { DEFAULT_CONFIG, loadConfig, saveConfig, getModelsRegistry, getModelById, refreshRemoteModels, listInstalledModels, isModelInstalled, removeModelFile, resolveModelFilePath } from "./config/configManager.js";
|
|
6
|
+
export { createCoreTools, buildSessionFunctions } from "./tools/registry.js";
|
|
7
|
+
export { parseUnifiedDiff } from "./tools/patch.js";
|
|
8
|
+
export { getDataDir, getModelsDir, getConfigPath, getLogDir, setLogLevel, logger } from "./utils/paths.js";
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { ChatHistoryItem } from "node-llama-cpp";
|
|
2
|
+
import { type AgentEvents } from "./core/agent.js";
|
|
3
|
+
import type { StoredSession } from "./utils/sessions.js";
|
|
4
|
+
import type { AjanConfig, ModelDefinition } from "./config/types.js";
|
|
5
|
+
export type ServiceEvents = AgentEvents & {
|
|
6
|
+
onModelLoadProgress?: (pct: number) => void;
|
|
7
|
+
onModelLoadComplete?: () => void;
|
|
8
|
+
};
|
|
9
|
+
export type ModelWithStatus = {
|
|
10
|
+
model: ModelDefinition;
|
|
11
|
+
path: string;
|
|
12
|
+
installed: boolean;
|
|
13
|
+
active: boolean;
|
|
14
|
+
};
|
|
15
|
+
export declare class AjanService {
|
|
16
|
+
private agent;
|
|
17
|
+
private config;
|
|
18
|
+
private events;
|
|
19
|
+
private _active;
|
|
20
|
+
private _currentAbort;
|
|
21
|
+
constructor(events?: ServiceEvents);
|
|
22
|
+
private ensureAgent;
|
|
23
|
+
/** Event'leri güncelle (Electron IPC yeniden bağlanırken kullanılır) */
|
|
24
|
+
updateEvents(events: ServiceEvents): void;
|
|
25
|
+
sendMessage(message: string, modelId?: string): Promise<{
|
|
26
|
+
ok: boolean;
|
|
27
|
+
response?: string;
|
|
28
|
+
steps?: number;
|
|
29
|
+
completed?: boolean;
|
|
30
|
+
error?: string;
|
|
31
|
+
}>;
|
|
32
|
+
abort(): void;
|
|
33
|
+
reset(): Promise<void>;
|
|
34
|
+
getChatHistory(): ChatHistoryItem[];
|
|
35
|
+
setChatHistory(history: ChatHistoryItem[]): void;
|
|
36
|
+
getContextStats(): {
|
|
37
|
+
usedTokens: number;
|
|
38
|
+
contextSize: number;
|
|
39
|
+
} | null;
|
|
40
|
+
getConfig(): AjanConfig;
|
|
41
|
+
setConfig(patch: Record<string, unknown>): Promise<void>;
|
|
42
|
+
listProjects(): {
|
|
43
|
+
path: string;
|
|
44
|
+
active: boolean;
|
|
45
|
+
}[];
|
|
46
|
+
addProject(path: string): Promise<{
|
|
47
|
+
ok: boolean;
|
|
48
|
+
error?: string;
|
|
49
|
+
}>;
|
|
50
|
+
removeProject(path: string): Promise<{
|
|
51
|
+
ok: boolean;
|
|
52
|
+
error?: string;
|
|
53
|
+
}>;
|
|
54
|
+
setActiveProject(path: string | null): Promise<{
|
|
55
|
+
ok: boolean;
|
|
56
|
+
error?: string;
|
|
57
|
+
}>;
|
|
58
|
+
getModelInfo(): {
|
|
59
|
+
modelId: string;
|
|
60
|
+
name: string;
|
|
61
|
+
installed: boolean;
|
|
62
|
+
} | null;
|
|
63
|
+
listModels(): ModelWithStatus[];
|
|
64
|
+
installModel(modelId: string): Promise<{
|
|
65
|
+
ok: boolean;
|
|
66
|
+
error?: string;
|
|
67
|
+
}>;
|
|
68
|
+
removeModel(modelId: string): Promise<{
|
|
69
|
+
ok: boolean;
|
|
70
|
+
error?: string;
|
|
71
|
+
}>;
|
|
72
|
+
useModel(modelId: string): Promise<{
|
|
73
|
+
ok: boolean;
|
|
74
|
+
error?: string;
|
|
75
|
+
}>;
|
|
76
|
+
refreshModels(): Promise<{
|
|
77
|
+
ok: boolean;
|
|
78
|
+
count?: number;
|
|
79
|
+
error?: string;
|
|
80
|
+
}>;
|
|
81
|
+
getModelsRegistry(): ModelDefinition[];
|
|
82
|
+
saveSession(session: StoredSession): Promise<void>;
|
|
83
|
+
loadSession(id: string): Promise<StoredSession | null>;
|
|
84
|
+
listSessions(): Promise<StoredSession[]>;
|
|
85
|
+
generateSessionId(): string;
|
|
86
|
+
detectGpu(): Promise<string[]>;
|
|
87
|
+
get isActive(): boolean;
|
|
88
|
+
private recreateAgent;
|
|
89
|
+
dispose(): Promise<void>;
|
|
90
|
+
}
|
package/dist/service.js
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { AjanAgent } from "./core/agent.js";
|
|
2
|
+
import { loadConfig, saveConfig, getModelById, isModelInstalled, listInstalledModels as listInstalledFromDisk, removeModelFile, refreshRemoteModels, getModelsRegistry } from "./config/configManager.js";
|
|
3
|
+
import { saveSession as saveSessionFile, loadSession as loadSessionFile, listSessions as listSessionsFromDisk, generateSessionId } from "./utils/sessions.js";
|
|
4
|
+
export class AjanService {
|
|
5
|
+
agent = null;
|
|
6
|
+
config;
|
|
7
|
+
events;
|
|
8
|
+
_active = false;
|
|
9
|
+
_currentAbort = null;
|
|
10
|
+
constructor(events = {}) {
|
|
11
|
+
this.config = loadConfig();
|
|
12
|
+
this.events = events;
|
|
13
|
+
}
|
|
14
|
+
// ── Agent Lifecycle ──
|
|
15
|
+
ensureAgent() {
|
|
16
|
+
if (this.agent)
|
|
17
|
+
return;
|
|
18
|
+
this.agent = new AjanAgent({
|
|
19
|
+
config: this.config,
|
|
20
|
+
events: {
|
|
21
|
+
onTextChunk: this.events.onTextChunk,
|
|
22
|
+
onThinkingChunk: this.events.onThinkingChunk,
|
|
23
|
+
onToolCall: this.events.onToolCall,
|
|
24
|
+
onToolResult: this.events.onToolResult,
|
|
25
|
+
onComplete: this.events.onComplete,
|
|
26
|
+
onStepStart: this.events.onStepStart,
|
|
27
|
+
onStepEnd: this.events.onStepEnd,
|
|
28
|
+
onContextTrimmed: this.events.onContextTrimmed,
|
|
29
|
+
onError: this.events.onError
|
|
30
|
+
},
|
|
31
|
+
onLoadProgress: this.events.onModelLoadProgress,
|
|
32
|
+
onLoadComplete: this.events.onModelLoadComplete
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/** Event'leri güncelle (Electron IPC yeniden bağlanırken kullanılır) */
|
|
36
|
+
updateEvents(events) {
|
|
37
|
+
this.events = events;
|
|
38
|
+
// Agent zaten varsa, yeni event'leri uygulamak için dispose + recreate gerekir
|
|
39
|
+
// Ancak çoğu durumda event'ler proxy üzerinden bağlanır, bu yüzden sadece saklıyoruz
|
|
40
|
+
}
|
|
41
|
+
// ── Prompt ──
|
|
42
|
+
async sendMessage(message, modelId) {
|
|
43
|
+
if (this._active)
|
|
44
|
+
return { ok: false, error: "Meşgul" };
|
|
45
|
+
// Model değişikliği varsa
|
|
46
|
+
if (modelId && modelId !== this.config.modelId) {
|
|
47
|
+
this.config.modelId = modelId;
|
|
48
|
+
saveConfig(this.config);
|
|
49
|
+
await this.recreateAgent();
|
|
50
|
+
}
|
|
51
|
+
this.ensureAgent();
|
|
52
|
+
if (!this.agent)
|
|
53
|
+
return { ok: false, error: "Agent başlatılamadı" };
|
|
54
|
+
this._active = true;
|
|
55
|
+
const controller = new AbortController();
|
|
56
|
+
this._currentAbort = controller;
|
|
57
|
+
try {
|
|
58
|
+
const result = await this.agent.promptTask(message, { signal: controller.signal });
|
|
59
|
+
return {
|
|
60
|
+
ok: true,
|
|
61
|
+
response: result.response,
|
|
62
|
+
steps: result.steps,
|
|
63
|
+
completed: result.completed
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
if (controller.signal.aborted)
|
|
68
|
+
return { ok: false, error: "İptal edildi" };
|
|
69
|
+
return { ok: false, error: err.message };
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
if (this._currentAbort === controller)
|
|
73
|
+
this._currentAbort = null;
|
|
74
|
+
this._active = false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
abort() {
|
|
78
|
+
this._currentAbort?.abort();
|
|
79
|
+
}
|
|
80
|
+
async reset() {
|
|
81
|
+
this.agent?.resetConversation();
|
|
82
|
+
}
|
|
83
|
+
// ── History ──
|
|
84
|
+
getChatHistory() {
|
|
85
|
+
return this.agent?.getChatHistory() ?? [];
|
|
86
|
+
}
|
|
87
|
+
setChatHistory(history) {
|
|
88
|
+
this.agent?.setChatHistory(history);
|
|
89
|
+
}
|
|
90
|
+
getContextStats() {
|
|
91
|
+
return this.agent?.getContextStats() ?? null;
|
|
92
|
+
}
|
|
93
|
+
// ── Config ──
|
|
94
|
+
getConfig() {
|
|
95
|
+
return { ...this.config };
|
|
96
|
+
}
|
|
97
|
+
async setConfig(patch) {
|
|
98
|
+
const merged = { ...this.config, ...patch };
|
|
99
|
+
if (patch.session && typeof patch.session === "object") {
|
|
100
|
+
merged.session = { ...this.config.session, ...patch.session };
|
|
101
|
+
}
|
|
102
|
+
saveConfig(merged);
|
|
103
|
+
this.config = merged;
|
|
104
|
+
await this.recreateAgent();
|
|
105
|
+
}
|
|
106
|
+
// ── Projects ──
|
|
107
|
+
listProjects() {
|
|
108
|
+
const projects = this.config.projects ?? [];
|
|
109
|
+
return projects.map(path => ({ path, active: path === this.config.activeProject }));
|
|
110
|
+
}
|
|
111
|
+
async addProject(path) {
|
|
112
|
+
const projects = this.config.projects ?? [];
|
|
113
|
+
if (projects.includes(path))
|
|
114
|
+
return { ok: true };
|
|
115
|
+
this.config.projects = [...projects, path];
|
|
116
|
+
saveConfig(this.config);
|
|
117
|
+
return { ok: true };
|
|
118
|
+
}
|
|
119
|
+
async removeProject(path) {
|
|
120
|
+
const projects = this.config.projects ?? [];
|
|
121
|
+
this.config.projects = projects.filter(p => p !== path);
|
|
122
|
+
if (this.config.activeProject === path)
|
|
123
|
+
this.config.activeProject = undefined;
|
|
124
|
+
saveConfig(this.config);
|
|
125
|
+
return { ok: true };
|
|
126
|
+
}
|
|
127
|
+
async setActiveProject(path) {
|
|
128
|
+
if (path === null) {
|
|
129
|
+
this.config.activeProject = undefined;
|
|
130
|
+
saveConfig(this.config);
|
|
131
|
+
return { ok: true };
|
|
132
|
+
}
|
|
133
|
+
const projects = this.config.projects ?? [];
|
|
134
|
+
if (!projects.includes(path))
|
|
135
|
+
return { ok: false, error: `Proje kayıtlı değil: ${path}` };
|
|
136
|
+
this.config.activeProject = path;
|
|
137
|
+
saveConfig(this.config);
|
|
138
|
+
return { ok: true };
|
|
139
|
+
}
|
|
140
|
+
// ── Models ──
|
|
141
|
+
getModelInfo() {
|
|
142
|
+
const model = getModelById(this.config.modelId);
|
|
143
|
+
if (!model)
|
|
144
|
+
return null;
|
|
145
|
+
return {
|
|
146
|
+
modelId: model.id,
|
|
147
|
+
name: model.name,
|
|
148
|
+
installed: isModelInstalled(model)
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
listModels() {
|
|
152
|
+
const installed = listInstalledFromDisk();
|
|
153
|
+
return installed.map(item => ({
|
|
154
|
+
model: item.model,
|
|
155
|
+
path: item.path,
|
|
156
|
+
installed: item.installed,
|
|
157
|
+
active: item.model.id === this.config.modelId
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
async installModel(modelId) {
|
|
161
|
+
const model = getModelById(modelId);
|
|
162
|
+
if (!model)
|
|
163
|
+
return { ok: false, error: `Model bulunamadı: ${modelId}` };
|
|
164
|
+
try {
|
|
165
|
+
const { downloadModel } = await import("./engine/modelManager.js");
|
|
166
|
+
await downloadModel(model, {
|
|
167
|
+
showProgress: true,
|
|
168
|
+
onProgress: (progress) => {
|
|
169
|
+
const pct = progress.totalSize > 0
|
|
170
|
+
? Math.round((progress.downloadedSize / progress.totalSize) * 100)
|
|
171
|
+
: progress.percent ?? 0;
|
|
172
|
+
this.events.onModelLoadProgress?.(pct);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
return { ok: true };
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
return { ok: false, error: err.message };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async removeModel(modelId) {
|
|
182
|
+
const model = getModelById(modelId);
|
|
183
|
+
if (!model)
|
|
184
|
+
return { ok: false, error: `Model bulunamadı: ${modelId}` };
|
|
185
|
+
const removed = removeModelFile(model);
|
|
186
|
+
if (!removed)
|
|
187
|
+
return { ok: false, error: "Model dosyası bulunamadı" };
|
|
188
|
+
// Aktif model silindiyse config'i temizle/kurulu bir modele geç
|
|
189
|
+
if (this.config.modelId === modelId) {
|
|
190
|
+
const installed = listInstalledFromDisk().find(m => m.installed);
|
|
191
|
+
if (installed) {
|
|
192
|
+
this.config.modelId = installed.model.id;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
this.config.modelId = "";
|
|
196
|
+
}
|
|
197
|
+
saveConfig(this.config);
|
|
198
|
+
await this.recreateAgent();
|
|
199
|
+
}
|
|
200
|
+
return { ok: true };
|
|
201
|
+
}
|
|
202
|
+
async useModel(modelId) {
|
|
203
|
+
const model = getModelById(modelId);
|
|
204
|
+
if (!model)
|
|
205
|
+
return { ok: false, error: `Model bulunamadı: ${modelId}` };
|
|
206
|
+
if (!isModelInstalled(model))
|
|
207
|
+
return { ok: false, error: `Model kurulu değil: ${modelId}` };
|
|
208
|
+
this.config.modelId = modelId;
|
|
209
|
+
saveConfig(this.config);
|
|
210
|
+
await this.recreateAgent();
|
|
211
|
+
return { ok: true };
|
|
212
|
+
}
|
|
213
|
+
async refreshModels() {
|
|
214
|
+
try {
|
|
215
|
+
const models = await refreshRemoteModels();
|
|
216
|
+
return { ok: true, count: models.length };
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
return { ok: false, error: err.message };
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
getModelsRegistry() {
|
|
223
|
+
return getModelsRegistry();
|
|
224
|
+
}
|
|
225
|
+
// ── Sessions ──
|
|
226
|
+
async saveSession(session) {
|
|
227
|
+
await saveSessionFile(session);
|
|
228
|
+
}
|
|
229
|
+
async loadSession(id) {
|
|
230
|
+
const data = await loadSessionFile(id);
|
|
231
|
+
if (data && this.agent) {
|
|
232
|
+
this.agent.setChatHistory(data.history);
|
|
233
|
+
}
|
|
234
|
+
return data;
|
|
235
|
+
}
|
|
236
|
+
async listSessions() {
|
|
237
|
+
return await listSessionsFromDisk();
|
|
238
|
+
}
|
|
239
|
+
generateSessionId() {
|
|
240
|
+
return generateSessionId();
|
|
241
|
+
}
|
|
242
|
+
// ── GPU ──
|
|
243
|
+
async detectGpu() {
|
|
244
|
+
try {
|
|
245
|
+
const { LlamaEngine } = await import("./engine/llama.js");
|
|
246
|
+
return await LlamaEngine.detectSupportedGpus();
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
return [];
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// ── Status ──
|
|
253
|
+
get isActive() {
|
|
254
|
+
return this._active;
|
|
255
|
+
}
|
|
256
|
+
// ── Internal ──
|
|
257
|
+
async recreateAgent() {
|
|
258
|
+
await this.agent?.dispose();
|
|
259
|
+
this.agent = null;
|
|
260
|
+
this.ensureAgent();
|
|
261
|
+
}
|
|
262
|
+
async dispose() {
|
|
263
|
+
this._currentAbort?.abort();
|
|
264
|
+
await this.agent?.dispose();
|
|
265
|
+
this.agent = null;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { extname, resolve } from "node:path";
|
|
3
|
+
import { load as loadYaml } from "js-yaml";
|
|
4
|
+
import AdmZip from "adm-zip";
|
|
5
|
+
import { limitOutput, resolveUserPath } from "./utils.js";
|
|
6
|
+
function describeStructure(value, depth = 0) {
|
|
7
|
+
if (depth > 2)
|
|
8
|
+
return typeof value;
|
|
9
|
+
if (Array.isArray(value)) {
|
|
10
|
+
const sample = value.slice(0, 3).map((v) => describeStructure(v, depth + 1));
|
|
11
|
+
return `dizi[${value.length}]<${sample.join(", ")}${value.length > 3 ? ", …" : ""}>`;
|
|
12
|
+
}
|
|
13
|
+
if (value && typeof value === "object") {
|
|
14
|
+
const keys = Object.keys(value);
|
|
15
|
+
const shown = keys.slice(0, 12).map((k) => `${k}: ${describeStructure(value[k], depth + 1)}`);
|
|
16
|
+
return `nesne{${keys.length}}{ ${shown.join(", ")}${keys.length > 12 ? ", …" : ""} }`;
|
|
17
|
+
}
|
|
18
|
+
if (typeof value === "string")
|
|
19
|
+
return `metin(${value.length})`;
|
|
20
|
+
return String(value);
|
|
21
|
+
}
|
|
22
|
+
export const readDataTool = {
|
|
23
|
+
name: "read_data",
|
|
24
|
+
description: "JSON veya YAML dosyasını ayrıştırıp özetini ve yapısını döner. Yapılandırma dosyalarını incelemek için idealdir.",
|
|
25
|
+
parameters: {
|
|
26
|
+
type: "object",
|
|
27
|
+
properties: {
|
|
28
|
+
path: { type: "string", description: "JSON/YAML dosya yolu" }
|
|
29
|
+
},
|
|
30
|
+
required: ["path"]
|
|
31
|
+
},
|
|
32
|
+
handler: async (params, ctx) => {
|
|
33
|
+
const p = params.path?.trim();
|
|
34
|
+
if (!p)
|
|
35
|
+
return { ok: false, output: "path zorunludur." };
|
|
36
|
+
const filePath = resolveUserPath(ctx.cwd, p);
|
|
37
|
+
if (!existsSync(filePath))
|
|
38
|
+
return { ok: false, output: `Dosya bulunamadı: ${p}` };
|
|
39
|
+
if (!statSync(filePath).isFile())
|
|
40
|
+
return { ok: false, output: `Bu bir dosya değil: ${p}` };
|
|
41
|
+
const ext = extname(filePath).toLowerCase();
|
|
42
|
+
let data;
|
|
43
|
+
try {
|
|
44
|
+
const raw = readFileSync(filePath, "utf8");
|
|
45
|
+
if (ext === ".yaml" || ext === ".yml") {
|
|
46
|
+
data = loadYaml(raw);
|
|
47
|
+
}
|
|
48
|
+
else if (ext === ".json") {
|
|
49
|
+
data = JSON.parse(raw);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
try {
|
|
53
|
+
data = JSON.parse(raw);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
data = loadYaml(raw);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
return { ok: false, output: `Ayrıştırma hatası: ${err.message}` };
|
|
62
|
+
}
|
|
63
|
+
const pretty = JSON.stringify(data, null, 2);
|
|
64
|
+
const summary = `Dosya: ${filePath}\nYapı: ${describeStructure(data)}\n\n${pretty}`;
|
|
65
|
+
return { ok: true, output: limitOutput(summary, Math.min(ctx.config.maxToolOutput ?? 40_000, 20_000)) };
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
export const unzipFileTool = {
|
|
69
|
+
name: "unzip_file",
|
|
70
|
+
description: "ZIP arşivini hedef klasöre açar. Hedef verilmezse zip'in yanına açılır.",
|
|
71
|
+
parameters: {
|
|
72
|
+
type: "object",
|
|
73
|
+
properties: {
|
|
74
|
+
path: { type: "string", description: "ZIP dosyası yolu" },
|
|
75
|
+
destination: { type: "string", description: "Hedef klasör (opsiyonel)" }
|
|
76
|
+
},
|
|
77
|
+
required: ["path"]
|
|
78
|
+
},
|
|
79
|
+
handler: async (params, ctx) => {
|
|
80
|
+
const p = params.path?.trim();
|
|
81
|
+
if (!p)
|
|
82
|
+
return { ok: false, output: "path zorunludur." };
|
|
83
|
+
const zipPath = resolveUserPath(ctx.cwd, p);
|
|
84
|
+
if (!existsSync(zipPath))
|
|
85
|
+
return { ok: false, output: `Dosya bulunamadı: ${p}` };
|
|
86
|
+
let destDir;
|
|
87
|
+
if (params.destination?.trim()) {
|
|
88
|
+
destDir = resolveUserPath(ctx.cwd, params.destination.trim());
|
|
89
|
+
if (!existsSync(destDir))
|
|
90
|
+
mkdirSync(destDir, { recursive: true });
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
destDir = resolve(zipPath, "..");
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
const zip = new AdmZip(zipPath);
|
|
97
|
+
const entries = zip.getEntries();
|
|
98
|
+
zip.extractAllTo(destDir, true);
|
|
99
|
+
const names = entries
|
|
100
|
+
.filter((e) => !e.isDirectory)
|
|
101
|
+
.slice(0, 30)
|
|
102
|
+
.map((e) => e.entryName);
|
|
103
|
+
const more = entries.length > 30 ? `\n… ve ${entries.length - 30} dosya daha` : "";
|
|
104
|
+
return { ok: true, output: `${entries.length} girdi açıldı → ${destDir}\n${names.join("\n")}${more}` };
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
return { ok: false, output: `Açma başarısız: ${err.message}` };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
//# sourceMappingURL=data.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { resolveUserPath } from "./utils.js";
|
|
3
|
+
function normalizeLines(text) {
|
|
4
|
+
return text.replace(/\r\n/g, "\n");
|
|
5
|
+
}
|
|
6
|
+
export const editFileTool = {
|
|
7
|
+
name: "edit_file",
|
|
8
|
+
description: "Bir dosyada tam eşleşen eski metni yeni metinle değiştirir (hedefli düzenleme). " +
|
|
9
|
+
"Eski metin dosyada yalnızca BİR kez geçmelidir; birden fazla geçiyorsa replaceAll=true kullanın. " +
|
|
10
|
+
"Küçük ve hedefli değişikliklerde write_file'a göre daha güvenilirdir.",
|
|
11
|
+
parameters: {
|
|
12
|
+
type: "object",
|
|
13
|
+
properties: {
|
|
14
|
+
path: { type: "string", description: "Düzenlenecek dosyanın yolu" },
|
|
15
|
+
oldString: { type: "string", description: "Değiştirilecek mevcut metin (birebir eşleşme)" },
|
|
16
|
+
newString: { type: "string", description: "Eski metnin yerine geçecek yeni metin" },
|
|
17
|
+
replaceAll: { type: "boolean", description: "true ise tüm eşleşmeleri değiştirir (varsayılan: false)" },
|
|
18
|
+
cwd: { type: "string", description: "Göreli yolların çözüleceği dizin" }
|
|
19
|
+
},
|
|
20
|
+
required: ["path", "oldString", "newString"]
|
|
21
|
+
},
|
|
22
|
+
handler: async (params, ctx) => {
|
|
23
|
+
const filePath = resolveUserPath(params.cwd ?? ctx.cwd, params.path);
|
|
24
|
+
if (!existsSync(filePath)) {
|
|
25
|
+
return { ok: false, output: `Dosya bulunamadı: ${params.path}` };
|
|
26
|
+
}
|
|
27
|
+
const original = normalizeLines(readFileSync(filePath, "utf8"));
|
|
28
|
+
const oldText = normalizeLines(params.oldString);
|
|
29
|
+
const newText = normalizeLines(params.newString);
|
|
30
|
+
if (oldText.length === 0) {
|
|
31
|
+
return { ok: false, output: "oldString boş olamaz." };
|
|
32
|
+
}
|
|
33
|
+
const count = original.split(oldText).length - 1;
|
|
34
|
+
if (count === 0) {
|
|
35
|
+
return { ok: false, output: `Eşleşme bulunamadı: "${params.oldString.slice(0, 120)}". Dosya içeriği değişmiş olabilir; read_file ile kontrol edin.` };
|
|
36
|
+
}
|
|
37
|
+
if (count > 1 && !params.replaceAll) {
|
|
38
|
+
return {
|
|
39
|
+
ok: false,
|
|
40
|
+
output: `"${params.oldString.slice(0, 120)}" dosyada ${count} kez geçiyor. Tek bir eşleşmeyi hedefleyin ya da replaceAll=true kullanın.`
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const result = params.replaceAll ? original.split(oldText).join(newText) : original.replace(oldText, newText);
|
|
44
|
+
writeFileSync(filePath, result, "utf8");
|
|
45
|
+
return { ok: true, output: `Dosya düzenlendi: ${filePath} (${params.replaceAll ? count : 1} değişiklik)` };
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
//# sourceMappingURL=edit.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AgentTool } from "../config/types.js";
|
|
2
|
+
export declare const readFileTool: AgentTool;
|
|
3
|
+
export declare const writeFileTool: AgentTool;
|
|
4
|
+
export declare const listDirTool: AgentTool;
|
|
5
|
+
export declare const searchFilesTool: AgentTool;
|
|
6
|
+
export declare const grepTool: AgentTool;
|