titan-agent 2026.4.0
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/.env.example +21 -0
- package/LICENSE +29 -0
- package/README.md +305 -0
- package/assets/titan-logo.png +0 -0
- package/dist/agent/agent.js +1458 -0
- package/dist/agent/agent.js.map +1 -0
- package/dist/agent/generator.js +1078 -0
- package/dist/agent/generator.js.map +1 -0
- package/dist/cli/index.js +5064 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/gateway/server.js +4462 -0
- package/dist/gateway/server.js.map +1 -0
- package/dist/skills/builtin/apply_patch.js +368 -0
- package/dist/skills/builtin/apply_patch.js.map +1 -0
- package/dist/skills/builtin/auto_generate.js +1129 -0
- package/dist/skills/builtin/auto_generate.js.map +1 -0
- package/dist/skills/builtin/browser.js +357 -0
- package/dist/skills/builtin/browser.js.map +1 -0
- package/dist/skills/builtin/cron.js +410 -0
- package/dist/skills/builtin/cron.js.map +1 -0
- package/dist/skills/builtin/filesystem.js +386 -0
- package/dist/skills/builtin/filesystem.js.map +1 -0
- package/dist/skills/builtin/memory_skill.js +430 -0
- package/dist/skills/builtin/memory_skill.js.map +1 -0
- package/dist/skills/builtin/process.js +421 -0
- package/dist/skills/builtin/process.js.map +1 -0
- package/dist/skills/builtin/sessions.js +1729 -0
- package/dist/skills/builtin/sessions.js.map +1 -0
- package/dist/skills/builtin/shell.js +327 -0
- package/dist/skills/builtin/shell.js.map +1 -0
- package/dist/skills/builtin/vision.js +491 -0
- package/dist/skills/builtin/vision.js.map +1 -0
- package/dist/skills/builtin/voice.js +468 -0
- package/dist/skills/builtin/voice.js.map +1 -0
- package/dist/skills/builtin/web_fetch.js +331 -0
- package/dist/skills/builtin/web_fetch.js.map +1 -0
- package/dist/skills/builtin/web_search.js +317 -0
- package/dist/skills/builtin/web_search.js.map +1 -0
- package/dist/skills/builtin/webhook.js +323 -0
- package/dist/skills/builtin/webhook.js.map +1 -0
- package/dist/skills/registry.js +3369 -0
- package/dist/skills/registry.js.map +1 -0
- package/package.json +118 -0
|
@@ -0,0 +1,1078 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// src/utils/constants.ts
|
|
8
|
+
import { homedir } from "os";
|
|
9
|
+
import { join } from "path";
|
|
10
|
+
var TITAN_VERSION, TITAN_ASCII_LOGO, TITAN_HOME, TITAN_CONFIG_PATH, TITAN_DB_PATH, TITAN_WORKSPACE, TITAN_SKILLS_DIR, TITAN_LOGS_DIR, TITAN_MEMORY_DIR, AGENTS_MD, SOUL_MD, TOOLS_MD, DEFAULT_GATEWAY_HOST, DEFAULT_GATEWAY_PORT, DEFAULT_WEB_PORT, DEFAULT_MODEL, DEFAULT_MAX_TOKENS, DEFAULT_TEMPERATURE, SESSION_TIMEOUT_MS, DEFAULT_SANDBOX_MODE, ALLOWED_TOOLS_DEFAULT;
|
|
11
|
+
var init_constants = __esm({
|
|
12
|
+
"src/utils/constants.ts"() {
|
|
13
|
+
"use strict";
|
|
14
|
+
TITAN_VERSION = "2026.4.0";
|
|
15
|
+
TITAN_ASCII_LOGO = `
|
|
16
|
+
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
17
|
+
\u2551 \u2551
|
|
18
|
+
\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2551
|
|
19
|
+
\u2551 \u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2551
|
|
20
|
+
\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2551
|
|
21
|
+
\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2551
|
|
22
|
+
\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551 \u2551
|
|
23
|
+
\u2551 \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u2551
|
|
24
|
+
\u2551 \u2551
|
|
25
|
+
\u2551 The Intelligent Task Automation Network \u2551
|
|
26
|
+
\u2551 v${TITAN_VERSION} \u2551
|
|
27
|
+
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D`;
|
|
28
|
+
TITAN_HOME = join(homedir(), ".titan");
|
|
29
|
+
TITAN_CONFIG_PATH = join(TITAN_HOME, "titan.json");
|
|
30
|
+
TITAN_DB_PATH = join(TITAN_HOME, "titan.db");
|
|
31
|
+
TITAN_WORKSPACE = join(TITAN_HOME, "workspace");
|
|
32
|
+
TITAN_SKILLS_DIR = join(TITAN_WORKSPACE, "skills");
|
|
33
|
+
TITAN_LOGS_DIR = join(TITAN_HOME, "logs");
|
|
34
|
+
TITAN_MEMORY_DIR = join(TITAN_HOME, "memory");
|
|
35
|
+
AGENTS_MD = join(TITAN_WORKSPACE, "AGENTS.md");
|
|
36
|
+
SOUL_MD = join(TITAN_WORKSPACE, "SOUL.md");
|
|
37
|
+
TOOLS_MD = join(TITAN_WORKSPACE, "TOOLS.md");
|
|
38
|
+
DEFAULT_GATEWAY_HOST = "127.0.0.1";
|
|
39
|
+
DEFAULT_GATEWAY_PORT = 18789;
|
|
40
|
+
DEFAULT_WEB_PORT = 18790;
|
|
41
|
+
DEFAULT_MODEL = "anthropic/claude-sonnet-4-20250514";
|
|
42
|
+
DEFAULT_MAX_TOKENS = 8192;
|
|
43
|
+
DEFAULT_TEMPERATURE = 0.7;
|
|
44
|
+
SESSION_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
45
|
+
DEFAULT_SANDBOX_MODE = "host";
|
|
46
|
+
ALLOWED_TOOLS_DEFAULT = [
|
|
47
|
+
"shell",
|
|
48
|
+
"read_file",
|
|
49
|
+
"write_file",
|
|
50
|
+
"edit_file",
|
|
51
|
+
"list_dir",
|
|
52
|
+
"web_search",
|
|
53
|
+
"browser",
|
|
54
|
+
"cron",
|
|
55
|
+
"webhook",
|
|
56
|
+
"email",
|
|
57
|
+
"memory"
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// src/utils/helpers.ts
|
|
63
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
64
|
+
function ensureDir(dirPath) {
|
|
65
|
+
if (!existsSync(dirPath)) {
|
|
66
|
+
mkdirSync(dirPath, { recursive: true });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function readJsonFile(filePath) {
|
|
70
|
+
try {
|
|
71
|
+
if (!existsSync(filePath)) return null;
|
|
72
|
+
const content = readFileSync(filePath, "utf-8");
|
|
73
|
+
return JSON.parse(content);
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
var init_helpers = __esm({
|
|
79
|
+
"src/utils/helpers.ts"() {
|
|
80
|
+
"use strict";
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// src/config/schema.ts
|
|
85
|
+
import { z } from "zod";
|
|
86
|
+
var ProviderConfigSchema, ChannelConfigSchema, SecurityConfigSchema, GatewayConfigSchema, AgentConfigSchema, TitanConfigSchema;
|
|
87
|
+
var init_schema = __esm({
|
|
88
|
+
"src/config/schema.ts"() {
|
|
89
|
+
"use strict";
|
|
90
|
+
init_constants();
|
|
91
|
+
ProviderConfigSchema = z.object({
|
|
92
|
+
apiKey: z.string().optional(),
|
|
93
|
+
baseUrl: z.string().optional(),
|
|
94
|
+
model: z.string().optional(),
|
|
95
|
+
maxTokens: z.number().optional(),
|
|
96
|
+
temperature: z.number().min(0).max(2).optional()
|
|
97
|
+
});
|
|
98
|
+
ChannelConfigSchema = z.object({
|
|
99
|
+
enabled: z.boolean().default(false),
|
|
100
|
+
token: z.string().optional(),
|
|
101
|
+
apiKey: z.string().optional(),
|
|
102
|
+
allowFrom: z.array(z.string()).default([]),
|
|
103
|
+
dmPolicy: z.enum(["pairing", "open", "closed"]).default("pairing")
|
|
104
|
+
});
|
|
105
|
+
SecurityConfigSchema = z.object({
|
|
106
|
+
sandboxMode: z.enum(["host", "docker", "none"]).default(DEFAULT_SANDBOX_MODE),
|
|
107
|
+
allowedTools: z.array(z.string()).default(ALLOWED_TOOLS_DEFAULT),
|
|
108
|
+
deniedTools: z.array(z.string()).default([]),
|
|
109
|
+
maxConcurrentTasks: z.number().default(5),
|
|
110
|
+
commandTimeout: z.number().default(3e4),
|
|
111
|
+
fileSystemAllowlist: z.array(z.string()).default([]),
|
|
112
|
+
networkAllowlist: z.array(z.string()).default(["*"]),
|
|
113
|
+
shield: z.object({
|
|
114
|
+
enabled: z.boolean().default(true),
|
|
115
|
+
mode: z.enum(["standard", "strict"]).default("strict")
|
|
116
|
+
}).default({})
|
|
117
|
+
});
|
|
118
|
+
GatewayConfigSchema = z.object({
|
|
119
|
+
host: z.string().default(DEFAULT_GATEWAY_HOST),
|
|
120
|
+
port: z.number().default(DEFAULT_GATEWAY_PORT),
|
|
121
|
+
webPort: z.number().default(DEFAULT_WEB_PORT),
|
|
122
|
+
auth: z.object({
|
|
123
|
+
mode: z.enum(["none", "token", "password"]).default("token"),
|
|
124
|
+
token: z.string().optional(),
|
|
125
|
+
password: z.string().optional()
|
|
126
|
+
}).default({})
|
|
127
|
+
});
|
|
128
|
+
AgentConfigSchema = z.object({
|
|
129
|
+
model: z.string().default(DEFAULT_MODEL),
|
|
130
|
+
maxTokens: z.number().default(DEFAULT_MAX_TOKENS),
|
|
131
|
+
temperature: z.number().default(DEFAULT_TEMPERATURE),
|
|
132
|
+
systemPrompt: z.string().optional(),
|
|
133
|
+
workspace: z.string().optional(),
|
|
134
|
+
thinkingMode: z.enum(["off", "low", "medium", "high"]).default("medium")
|
|
135
|
+
});
|
|
136
|
+
TitanConfigSchema = z.object({
|
|
137
|
+
agent: AgentConfigSchema.default({}),
|
|
138
|
+
providers: z.object({
|
|
139
|
+
anthropic: ProviderConfigSchema.default({}),
|
|
140
|
+
openai: ProviderConfigSchema.default({}),
|
|
141
|
+
google: ProviderConfigSchema.default({}),
|
|
142
|
+
ollama: ProviderConfigSchema.default({})
|
|
143
|
+
}).default({}),
|
|
144
|
+
channels: z.object({
|
|
145
|
+
discord: ChannelConfigSchema.default({}),
|
|
146
|
+
telegram: ChannelConfigSchema.default({}),
|
|
147
|
+
slack: ChannelConfigSchema.default({}),
|
|
148
|
+
whatsapp: ChannelConfigSchema.default({}),
|
|
149
|
+
webchat: ChannelConfigSchema.default({}),
|
|
150
|
+
googlechat: ChannelConfigSchema.default({}),
|
|
151
|
+
matrix: ChannelConfigSchema.default({}),
|
|
152
|
+
signal: ChannelConfigSchema.default({}),
|
|
153
|
+
msteams: ChannelConfigSchema.default({}),
|
|
154
|
+
bluebubbles: ChannelConfigSchema.default({})
|
|
155
|
+
}).default({}),
|
|
156
|
+
gateway: GatewayConfigSchema.default({}),
|
|
157
|
+
security: SecurityConfigSchema.default({}),
|
|
158
|
+
memory: z.object({
|
|
159
|
+
enabled: z.boolean().default(true),
|
|
160
|
+
maxHistoryMessages: z.number().default(50),
|
|
161
|
+
vectorSearchEnabled: z.boolean().default(false)
|
|
162
|
+
}).default({}),
|
|
163
|
+
skills: z.object({
|
|
164
|
+
enabled: z.boolean().default(true),
|
|
165
|
+
autoDiscover: z.boolean().default(true),
|
|
166
|
+
marketplace: z.boolean().default(false)
|
|
167
|
+
}).default({}),
|
|
168
|
+
logging: z.object({
|
|
169
|
+
level: z.enum(["debug", "info", "warn", "error", "silent"]).default("info"),
|
|
170
|
+
file: z.boolean().default(true)
|
|
171
|
+
}).default({}),
|
|
172
|
+
autonomy: z.object({
|
|
173
|
+
/** autonomous = full auto, supervised = asks for dangerous ops, locked = asks for everything */
|
|
174
|
+
mode: z.enum(["autonomous", "supervised", "locked"]).default("supervised"),
|
|
175
|
+
/** Auto-approve moderate-risk tools in main session (cli/webchat) */
|
|
176
|
+
autoApproveMainSession: z.boolean().default(true),
|
|
177
|
+
/** Timeout for HITL approval requests (ms). Auto-deny after timeout. */
|
|
178
|
+
approvalTimeoutMs: z.number().default(6e4),
|
|
179
|
+
/** Notify user of auto-approved actions */
|
|
180
|
+
notifyOnAutoApprove: z.boolean().default(true)
|
|
181
|
+
}).default({})
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// src/utils/logger.ts
|
|
187
|
+
import chalk from "chalk";
|
|
188
|
+
function formatTimestamp() {
|
|
189
|
+
return chalk.gray((/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19));
|
|
190
|
+
}
|
|
191
|
+
function log(level, component, message, ...args) {
|
|
192
|
+
if (level < currentLevel) return;
|
|
193
|
+
const prefix = `${formatTimestamp()} ${LEVEL_LABELS[level]} ${chalk.blue(`[${component}]`)}`;
|
|
194
|
+
console.log(`${prefix} ${message}`, ...args);
|
|
195
|
+
}
|
|
196
|
+
var LEVEL_LABELS, currentLevel, logger, logger_default;
|
|
197
|
+
var init_logger = __esm({
|
|
198
|
+
"src/utils/logger.ts"() {
|
|
199
|
+
"use strict";
|
|
200
|
+
LEVEL_LABELS = {
|
|
201
|
+
[0 /* DEBUG */]: chalk.gray("DEBUG"),
|
|
202
|
+
[1 /* INFO */]: chalk.cyan("INFO "),
|
|
203
|
+
[2 /* WARN */]: chalk.yellow("WARN "),
|
|
204
|
+
[3 /* ERROR */]: chalk.red("ERROR"),
|
|
205
|
+
[4 /* SILENT */]: ""
|
|
206
|
+
};
|
|
207
|
+
currentLevel = 1 /* INFO */;
|
|
208
|
+
logger = {
|
|
209
|
+
debug: (component, msg, ...args) => log(0 /* DEBUG */, component, msg, ...args),
|
|
210
|
+
info: (component, msg, ...args) => log(1 /* INFO */, component, msg, ...args),
|
|
211
|
+
warn: (component, msg, ...args) => log(2 /* WARN */, component, msg, ...args),
|
|
212
|
+
error: (component, msg, ...args) => log(3 /* ERROR */, component, msg, ...args)
|
|
213
|
+
};
|
|
214
|
+
logger_default = logger;
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// src/config/config.ts
|
|
219
|
+
import { existsSync as existsSync2 } from "fs";
|
|
220
|
+
function getDefaultConfig() {
|
|
221
|
+
return TitanConfigSchema.parse({});
|
|
222
|
+
}
|
|
223
|
+
function loadConfig() {
|
|
224
|
+
if (cachedConfig) return cachedConfig;
|
|
225
|
+
ensureDir(TITAN_HOME);
|
|
226
|
+
let rawConfig = {};
|
|
227
|
+
if (existsSync2(TITAN_CONFIG_PATH)) {
|
|
228
|
+
const loaded = readJsonFile(TITAN_CONFIG_PATH);
|
|
229
|
+
if (loaded) {
|
|
230
|
+
rawConfig = loaded;
|
|
231
|
+
logger_default.debug(COMPONENT, `Loaded config from ${TITAN_CONFIG_PATH}`);
|
|
232
|
+
} else {
|
|
233
|
+
logger_default.warn(COMPONENT, `Failed to parse config at ${TITAN_CONFIG_PATH}, using defaults`);
|
|
234
|
+
}
|
|
235
|
+
} else {
|
|
236
|
+
logger_default.info(COMPONENT, "No config file found, using defaults");
|
|
237
|
+
}
|
|
238
|
+
applyEnvOverrides(rawConfig);
|
|
239
|
+
const result = TitanConfigSchema.safeParse(rawConfig);
|
|
240
|
+
if (!result.success) {
|
|
241
|
+
logger_default.warn(COMPONENT, "Config validation issues, using defaults for invalid fields");
|
|
242
|
+
logger_default.debug(COMPONENT, result.error.message);
|
|
243
|
+
cachedConfig = getDefaultConfig();
|
|
244
|
+
} else {
|
|
245
|
+
cachedConfig = result.data;
|
|
246
|
+
}
|
|
247
|
+
return cachedConfig;
|
|
248
|
+
}
|
|
249
|
+
function applyEnvOverrides(config) {
|
|
250
|
+
const envMap = {
|
|
251
|
+
TITAN_MODEL: (val) => setNested(config, "agent.model", val),
|
|
252
|
+
TITAN_GATEWAY_PORT: (val) => setNested(config, "gateway.port", parseInt(val, 10)),
|
|
253
|
+
TITAN_GATEWAY_HOST: (val) => setNested(config, "gateway.host", val),
|
|
254
|
+
TITAN_LOG_LEVEL: (val) => setNested(config, "logging.level", val),
|
|
255
|
+
ANTHROPIC_API_KEY: (val) => setNested(config, "providers.anthropic.apiKey", val),
|
|
256
|
+
OPENAI_API_KEY: (val) => setNested(config, "providers.openai.apiKey", val),
|
|
257
|
+
GOOGLE_API_KEY: (val) => setNested(config, "providers.google.apiKey", val),
|
|
258
|
+
OLLAMA_BASE_URL: (val) => setNested(config, "providers.ollama.baseUrl", val),
|
|
259
|
+
DISCORD_TOKEN: (val) => setNested(config, "channels.discord.token", val),
|
|
260
|
+
TELEGRAM_TOKEN: (val) => setNested(config, "channels.telegram.token", val),
|
|
261
|
+
SLACK_TOKEN: (val) => setNested(config, "channels.slack.token", val)
|
|
262
|
+
};
|
|
263
|
+
for (const [envKey, setter] of Object.entries(envMap)) {
|
|
264
|
+
const val = process.env[envKey];
|
|
265
|
+
if (val) {
|
|
266
|
+
setter(val);
|
|
267
|
+
logger_default.debug(COMPONENT, `Applied env override: ${envKey}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function setNested(obj, path2, value) {
|
|
272
|
+
const parts = path2.split(".");
|
|
273
|
+
let current = obj;
|
|
274
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
275
|
+
if (!current[parts[i]] || typeof current[parts[i]] !== "object") {
|
|
276
|
+
current[parts[i]] = {};
|
|
277
|
+
}
|
|
278
|
+
current = current[parts[i]];
|
|
279
|
+
}
|
|
280
|
+
current[parts[parts.length - 1]] = value;
|
|
281
|
+
}
|
|
282
|
+
var COMPONENT, cachedConfig;
|
|
283
|
+
var init_config = __esm({
|
|
284
|
+
"src/config/config.ts"() {
|
|
285
|
+
"use strict";
|
|
286
|
+
init_constants();
|
|
287
|
+
init_helpers();
|
|
288
|
+
init_schema();
|
|
289
|
+
init_logger();
|
|
290
|
+
COMPONENT = "Config";
|
|
291
|
+
cachedConfig = null;
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// src/providers/base.ts
|
|
296
|
+
var LLMProvider;
|
|
297
|
+
var init_base = __esm({
|
|
298
|
+
"src/providers/base.ts"() {
|
|
299
|
+
"use strict";
|
|
300
|
+
LLMProvider = class {
|
|
301
|
+
/** Get the provider identifier from a model string like "anthropic/claude-3" */
|
|
302
|
+
static parseModelId(modelId) {
|
|
303
|
+
const parts = modelId.split("/");
|
|
304
|
+
if (parts.length >= 2) {
|
|
305
|
+
return { provider: parts[0], model: parts.slice(1).join("/") };
|
|
306
|
+
}
|
|
307
|
+
return { provider: "anthropic", model: modelId };
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// src/providers/anthropic.ts
|
|
314
|
+
import { v4 as uuid } from "uuid";
|
|
315
|
+
var COMPONENT2, AnthropicProvider;
|
|
316
|
+
var init_anthropic = __esm({
|
|
317
|
+
"src/providers/anthropic.ts"() {
|
|
318
|
+
"use strict";
|
|
319
|
+
init_base();
|
|
320
|
+
init_config();
|
|
321
|
+
init_logger();
|
|
322
|
+
COMPONENT2 = "Anthropic";
|
|
323
|
+
AnthropicProvider = class extends LLMProvider {
|
|
324
|
+
name = "anthropic";
|
|
325
|
+
displayName = "Anthropic (Claude)";
|
|
326
|
+
get apiKey() {
|
|
327
|
+
const config = loadConfig();
|
|
328
|
+
return config.providers.anthropic.apiKey || process.env.ANTHROPIC_API_KEY || "";
|
|
329
|
+
}
|
|
330
|
+
get baseUrl() {
|
|
331
|
+
const config = loadConfig();
|
|
332
|
+
return config.providers.anthropic.baseUrl || "https://api.anthropic.com";
|
|
333
|
+
}
|
|
334
|
+
async chat(options) {
|
|
335
|
+
const model = options.model || "claude-sonnet-4-20250514";
|
|
336
|
+
const apiKey = this.apiKey;
|
|
337
|
+
if (!apiKey) throw new Error("Anthropic API key not configured");
|
|
338
|
+
logger_default.debug(COMPONENT2, `Chat request: model=${model}, messages=${options.messages.length}`);
|
|
339
|
+
const systemMessage = options.messages.find((m) => m.role === "system");
|
|
340
|
+
const nonSystemMessages = options.messages.filter((m) => m.role !== "system");
|
|
341
|
+
const body = {
|
|
342
|
+
model: model.replace("anthropic/", ""),
|
|
343
|
+
max_tokens: options.maxTokens || 8192,
|
|
344
|
+
messages: nonSystemMessages.map((m) => ({
|
|
345
|
+
role: m.role === "tool" ? "user" : m.role,
|
|
346
|
+
content: m.role === "tool" ? [{ type: "tool_result", tool_use_id: m.toolCallId, content: m.content }] : m.content
|
|
347
|
+
}))
|
|
348
|
+
};
|
|
349
|
+
if (systemMessage) {
|
|
350
|
+
body.system = systemMessage.content;
|
|
351
|
+
}
|
|
352
|
+
if (options.tools && options.tools.length > 0) {
|
|
353
|
+
body.tools = options.tools.map((t) => ({
|
|
354
|
+
name: t.function.name,
|
|
355
|
+
description: t.function.description,
|
|
356
|
+
input_schema: t.function.parameters
|
|
357
|
+
}));
|
|
358
|
+
}
|
|
359
|
+
if (options.temperature !== void 0) {
|
|
360
|
+
body.temperature = options.temperature;
|
|
361
|
+
}
|
|
362
|
+
const response = await fetch(`${this.baseUrl}/v1/messages`, {
|
|
363
|
+
method: "POST",
|
|
364
|
+
headers: {
|
|
365
|
+
"Content-Type": "application/json",
|
|
366
|
+
"x-api-key": apiKey,
|
|
367
|
+
"anthropic-version": "2023-06-01"
|
|
368
|
+
},
|
|
369
|
+
body: JSON.stringify(body)
|
|
370
|
+
});
|
|
371
|
+
if (!response.ok) {
|
|
372
|
+
const errorText = await response.text();
|
|
373
|
+
throw new Error(`Anthropic API error (${response.status}): ${errorText}`);
|
|
374
|
+
}
|
|
375
|
+
const data = await response.json();
|
|
376
|
+
const content = data.content;
|
|
377
|
+
let textContent = "";
|
|
378
|
+
const toolCalls = [];
|
|
379
|
+
for (const block of content) {
|
|
380
|
+
if (block.type === "text") {
|
|
381
|
+
textContent += block.text;
|
|
382
|
+
} else if (block.type === "tool_use") {
|
|
383
|
+
toolCalls.push({
|
|
384
|
+
id: block.id,
|
|
385
|
+
type: "function",
|
|
386
|
+
function: {
|
|
387
|
+
name: block.name,
|
|
388
|
+
arguments: JSON.stringify(block.input)
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const usage = data.usage;
|
|
394
|
+
return {
|
|
395
|
+
id: data.id || uuid(),
|
|
396
|
+
content: textContent,
|
|
397
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
|
|
398
|
+
usage: usage ? {
|
|
399
|
+
promptTokens: usage.input_tokens,
|
|
400
|
+
completionTokens: usage.output_tokens,
|
|
401
|
+
totalTokens: usage.input_tokens + usage.output_tokens
|
|
402
|
+
} : void 0,
|
|
403
|
+
finishReason: toolCalls.length > 0 ? "tool_calls" : "stop",
|
|
404
|
+
model
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
async *chatStream(options) {
|
|
408
|
+
try {
|
|
409
|
+
const response = await this.chat(options);
|
|
410
|
+
if (response.content) {
|
|
411
|
+
yield { type: "text", content: response.content };
|
|
412
|
+
}
|
|
413
|
+
if (response.toolCalls) {
|
|
414
|
+
for (const tc of response.toolCalls) {
|
|
415
|
+
yield { type: "tool_call", toolCall: tc };
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
yield { type: "done" };
|
|
419
|
+
} catch (error) {
|
|
420
|
+
yield { type: "error", error: error.message };
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
async listModels() {
|
|
424
|
+
return [
|
|
425
|
+
"claude-opus-4-0",
|
|
426
|
+
"claude-sonnet-4-20250514",
|
|
427
|
+
"claude-haiku-4-20250414",
|
|
428
|
+
"claude-3-5-sonnet-20241022",
|
|
429
|
+
"claude-3-5-haiku-20241022"
|
|
430
|
+
];
|
|
431
|
+
}
|
|
432
|
+
async healthCheck() {
|
|
433
|
+
try {
|
|
434
|
+
if (!this.apiKey) return false;
|
|
435
|
+
const response = await fetch(`${this.baseUrl}/v1/messages`, {
|
|
436
|
+
method: "POST",
|
|
437
|
+
headers: {
|
|
438
|
+
"Content-Type": "application/json",
|
|
439
|
+
"x-api-key": this.apiKey,
|
|
440
|
+
"anthropic-version": "2023-06-01"
|
|
441
|
+
},
|
|
442
|
+
body: JSON.stringify({
|
|
443
|
+
model: "claude-haiku-4-20250414",
|
|
444
|
+
max_tokens: 1,
|
|
445
|
+
messages: [{ role: "user", content: "ping" }]
|
|
446
|
+
})
|
|
447
|
+
});
|
|
448
|
+
return response.ok || response.status === 400;
|
|
449
|
+
} catch {
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
// src/providers/openai.ts
|
|
458
|
+
import { v4 as uuid2 } from "uuid";
|
|
459
|
+
var COMPONENT3, OpenAIProvider;
|
|
460
|
+
var init_openai = __esm({
|
|
461
|
+
"src/providers/openai.ts"() {
|
|
462
|
+
"use strict";
|
|
463
|
+
init_base();
|
|
464
|
+
init_config();
|
|
465
|
+
init_logger();
|
|
466
|
+
COMPONENT3 = "OpenAI";
|
|
467
|
+
OpenAIProvider = class extends LLMProvider {
|
|
468
|
+
name = "openai";
|
|
469
|
+
displayName = "OpenAI (GPT)";
|
|
470
|
+
get apiKey() {
|
|
471
|
+
const config = loadConfig();
|
|
472
|
+
return config.providers.openai.apiKey || process.env.OPENAI_API_KEY || "";
|
|
473
|
+
}
|
|
474
|
+
get baseUrl() {
|
|
475
|
+
const config = loadConfig();
|
|
476
|
+
return config.providers.openai.baseUrl || "https://api.openai.com";
|
|
477
|
+
}
|
|
478
|
+
async chat(options) {
|
|
479
|
+
const model = options.model || "gpt-4o";
|
|
480
|
+
const apiKey = this.apiKey;
|
|
481
|
+
if (!apiKey) throw new Error("OpenAI API key not configured");
|
|
482
|
+
logger_default.debug(COMPONENT3, `Chat request: model=${model}, messages=${options.messages.length}`);
|
|
483
|
+
const body = {
|
|
484
|
+
model: model.replace("openai/", ""),
|
|
485
|
+
messages: options.messages.map((m) => {
|
|
486
|
+
if (m.role === "tool") {
|
|
487
|
+
return { role: "tool", content: m.content, tool_call_id: m.toolCallId };
|
|
488
|
+
}
|
|
489
|
+
if (m.role === "assistant" && m.toolCalls) {
|
|
490
|
+
return {
|
|
491
|
+
role: "assistant",
|
|
492
|
+
content: m.content || null,
|
|
493
|
+
tool_calls: m.toolCalls.map((tc) => ({
|
|
494
|
+
id: tc.id,
|
|
495
|
+
type: "function",
|
|
496
|
+
function: { name: tc.function.name, arguments: tc.function.arguments }
|
|
497
|
+
}))
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
return { role: m.role, content: m.content };
|
|
501
|
+
}),
|
|
502
|
+
max_tokens: options.maxTokens || 8192
|
|
503
|
+
};
|
|
504
|
+
if (options.tools && options.tools.length > 0) {
|
|
505
|
+
body.tools = options.tools;
|
|
506
|
+
}
|
|
507
|
+
if (options.temperature !== void 0) {
|
|
508
|
+
body.temperature = options.temperature;
|
|
509
|
+
}
|
|
510
|
+
const response = await fetch(`${this.baseUrl}/v1/chat/completions`, {
|
|
511
|
+
method: "POST",
|
|
512
|
+
headers: {
|
|
513
|
+
"Content-Type": "application/json",
|
|
514
|
+
Authorization: `Bearer ${apiKey}`
|
|
515
|
+
},
|
|
516
|
+
body: JSON.stringify(body)
|
|
517
|
+
});
|
|
518
|
+
if (!response.ok) {
|
|
519
|
+
const errorText = await response.text();
|
|
520
|
+
throw new Error(`OpenAI API error (${response.status}): ${errorText}`);
|
|
521
|
+
}
|
|
522
|
+
const data = await response.json();
|
|
523
|
+
const choices = data.choices;
|
|
524
|
+
const choice = choices[0];
|
|
525
|
+
const message = choice.message;
|
|
526
|
+
const toolCalls = [];
|
|
527
|
+
if (message.tool_calls) {
|
|
528
|
+
for (const tc of message.tool_calls) {
|
|
529
|
+
const fn = tc.function;
|
|
530
|
+
toolCalls.push({
|
|
531
|
+
id: tc.id,
|
|
532
|
+
type: "function",
|
|
533
|
+
function: { name: fn.name, arguments: fn.arguments }
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
const usage = data.usage;
|
|
538
|
+
return {
|
|
539
|
+
id: data.id || uuid2(),
|
|
540
|
+
content: message.content || "",
|
|
541
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
|
|
542
|
+
usage: usage ? {
|
|
543
|
+
promptTokens: usage.prompt_tokens,
|
|
544
|
+
completionTokens: usage.completion_tokens,
|
|
545
|
+
totalTokens: usage.total_tokens
|
|
546
|
+
} : void 0,
|
|
547
|
+
finishReason: toolCalls.length > 0 ? "tool_calls" : choice.finish_reason || "stop",
|
|
548
|
+
model
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
async *chatStream(options) {
|
|
552
|
+
try {
|
|
553
|
+
const response = await this.chat(options);
|
|
554
|
+
if (response.content) {
|
|
555
|
+
yield { type: "text", content: response.content };
|
|
556
|
+
}
|
|
557
|
+
if (response.toolCalls) {
|
|
558
|
+
for (const tc of response.toolCalls) {
|
|
559
|
+
yield { type: "tool_call", toolCall: tc };
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
yield { type: "done" };
|
|
563
|
+
} catch (error) {
|
|
564
|
+
yield { type: "error", error: error.message };
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
async listModels() {
|
|
568
|
+
return ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo", "o1", "o1-mini", "o3-mini"];
|
|
569
|
+
}
|
|
570
|
+
async healthCheck() {
|
|
571
|
+
try {
|
|
572
|
+
if (!this.apiKey) return false;
|
|
573
|
+
const response = await fetch(`${this.baseUrl}/v1/models`, {
|
|
574
|
+
headers: { Authorization: `Bearer ${this.apiKey}` }
|
|
575
|
+
});
|
|
576
|
+
return response.ok;
|
|
577
|
+
} catch {
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
// src/providers/google.ts
|
|
586
|
+
import { v4 as uuid3 } from "uuid";
|
|
587
|
+
var COMPONENT4, GoogleProvider;
|
|
588
|
+
var init_google = __esm({
|
|
589
|
+
"src/providers/google.ts"() {
|
|
590
|
+
"use strict";
|
|
591
|
+
init_base();
|
|
592
|
+
init_config();
|
|
593
|
+
init_logger();
|
|
594
|
+
COMPONENT4 = "Google";
|
|
595
|
+
GoogleProvider = class extends LLMProvider {
|
|
596
|
+
name = "google";
|
|
597
|
+
displayName = "Google (Gemini)";
|
|
598
|
+
get apiKey() {
|
|
599
|
+
const config = loadConfig();
|
|
600
|
+
return config.providers.google.apiKey || process.env.GOOGLE_API_KEY || "";
|
|
601
|
+
}
|
|
602
|
+
async chat(options) {
|
|
603
|
+
const model = (options.model || "gemini-2.0-flash").replace("google/", "");
|
|
604
|
+
const apiKey = this.apiKey;
|
|
605
|
+
if (!apiKey) throw new Error("Google API key not configured");
|
|
606
|
+
logger_default.debug(COMPONENT4, `Chat request: model=${model}, messages=${options.messages.length}`);
|
|
607
|
+
const systemInstruction = options.messages.find((m) => m.role === "system")?.content;
|
|
608
|
+
const contents = options.messages.filter((m) => m.role !== "system").map((m) => ({
|
|
609
|
+
role: m.role === "assistant" ? "model" : "user",
|
|
610
|
+
parts: [{ text: m.content }]
|
|
611
|
+
}));
|
|
612
|
+
const body = {
|
|
613
|
+
contents,
|
|
614
|
+
generationConfig: {
|
|
615
|
+
maxOutputTokens: options.maxTokens || 8192,
|
|
616
|
+
temperature: options.temperature ?? 0.7
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
if (systemInstruction) {
|
|
620
|
+
body.systemInstruction = { parts: [{ text: systemInstruction }] };
|
|
621
|
+
}
|
|
622
|
+
if (options.tools && options.tools.length > 0) {
|
|
623
|
+
body.tools = [{
|
|
624
|
+
functionDeclarations: options.tools.map((t) => ({
|
|
625
|
+
name: t.function.name,
|
|
626
|
+
description: t.function.description,
|
|
627
|
+
parameters: t.function.parameters
|
|
628
|
+
}))
|
|
629
|
+
}];
|
|
630
|
+
}
|
|
631
|
+
const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`;
|
|
632
|
+
const response = await fetch(url, {
|
|
633
|
+
method: "POST",
|
|
634
|
+
headers: { "Content-Type": "application/json" },
|
|
635
|
+
body: JSON.stringify(body)
|
|
636
|
+
});
|
|
637
|
+
if (!response.ok) {
|
|
638
|
+
const errorText = await response.text();
|
|
639
|
+
throw new Error(`Google API error (${response.status}): ${errorText}`);
|
|
640
|
+
}
|
|
641
|
+
const data = await response.json();
|
|
642
|
+
const candidates = data.candidates;
|
|
643
|
+
let textContent = "";
|
|
644
|
+
const toolCalls = [];
|
|
645
|
+
if (candidates && candidates.length > 0) {
|
|
646
|
+
const parts = candidates[0].content?.parts || [];
|
|
647
|
+
for (const part of parts) {
|
|
648
|
+
if (part.text) {
|
|
649
|
+
textContent += part.text;
|
|
650
|
+
}
|
|
651
|
+
if (part.functionCall) {
|
|
652
|
+
const fc = part.functionCall;
|
|
653
|
+
toolCalls.push({
|
|
654
|
+
id: uuid3(),
|
|
655
|
+
type: "function",
|
|
656
|
+
function: {
|
|
657
|
+
name: fc.name,
|
|
658
|
+
arguments: JSON.stringify(fc.args)
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
const usageMeta = data.usageMetadata;
|
|
665
|
+
return {
|
|
666
|
+
id: uuid3(),
|
|
667
|
+
content: textContent,
|
|
668
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
|
|
669
|
+
usage: usageMeta ? {
|
|
670
|
+
promptTokens: usageMeta.promptTokenCount || 0,
|
|
671
|
+
completionTokens: usageMeta.candidatesTokenCount || 0,
|
|
672
|
+
totalTokens: usageMeta.totalTokenCount || 0
|
|
673
|
+
} : void 0,
|
|
674
|
+
finishReason: toolCalls.length > 0 ? "tool_calls" : "stop",
|
|
675
|
+
model: `google/${model}`
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
async *chatStream(options) {
|
|
679
|
+
try {
|
|
680
|
+
const response = await this.chat(options);
|
|
681
|
+
if (response.content) yield { type: "text", content: response.content };
|
|
682
|
+
if (response.toolCalls) {
|
|
683
|
+
for (const tc of response.toolCalls) yield { type: "tool_call", toolCall: tc };
|
|
684
|
+
}
|
|
685
|
+
yield { type: "done" };
|
|
686
|
+
} catch (error) {
|
|
687
|
+
yield { type: "error", error: error.message };
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
async listModels() {
|
|
691
|
+
return ["gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.0-flash", "gemini-1.5-pro"];
|
|
692
|
+
}
|
|
693
|
+
async healthCheck() {
|
|
694
|
+
try {
|
|
695
|
+
if (!this.apiKey) return false;
|
|
696
|
+
const url = `https://generativelanguage.googleapis.com/v1beta/models?key=${this.apiKey}`;
|
|
697
|
+
const response = await fetch(url);
|
|
698
|
+
return response.ok;
|
|
699
|
+
} catch {
|
|
700
|
+
return false;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
// src/providers/ollama.ts
|
|
708
|
+
import { v4 as uuid4 } from "uuid";
|
|
709
|
+
var COMPONENT5, OllamaProvider;
|
|
710
|
+
var init_ollama = __esm({
|
|
711
|
+
"src/providers/ollama.ts"() {
|
|
712
|
+
"use strict";
|
|
713
|
+
init_base();
|
|
714
|
+
init_config();
|
|
715
|
+
init_logger();
|
|
716
|
+
COMPONENT5 = "Ollama";
|
|
717
|
+
OllamaProvider = class extends LLMProvider {
|
|
718
|
+
name = "ollama";
|
|
719
|
+
displayName = "Ollama (Local)";
|
|
720
|
+
get baseUrl() {
|
|
721
|
+
const config = loadConfig();
|
|
722
|
+
return config.providers.ollama.baseUrl || process.env.OLLAMA_BASE_URL || "http://localhost:11434";
|
|
723
|
+
}
|
|
724
|
+
async chat(options) {
|
|
725
|
+
const model = (options.model || "llama3.1").replace("ollama/", "");
|
|
726
|
+
logger_default.debug(COMPONENT5, `Chat request: model=${model}, messages=${options.messages.length}`);
|
|
727
|
+
const body = {
|
|
728
|
+
model,
|
|
729
|
+
messages: options.messages.map((m) => ({
|
|
730
|
+
role: m.role,
|
|
731
|
+
content: m.content
|
|
732
|
+
})),
|
|
733
|
+
stream: false,
|
|
734
|
+
options: {
|
|
735
|
+
num_predict: options.maxTokens || 8192,
|
|
736
|
+
temperature: options.temperature ?? 0.7
|
|
737
|
+
}
|
|
738
|
+
};
|
|
739
|
+
if (options.tools && options.tools.length > 0) {
|
|
740
|
+
body.tools = options.tools.map((t) => ({
|
|
741
|
+
type: "function",
|
|
742
|
+
function: {
|
|
743
|
+
name: t.function.name,
|
|
744
|
+
description: t.function.description,
|
|
745
|
+
parameters: t.function.parameters
|
|
746
|
+
}
|
|
747
|
+
}));
|
|
748
|
+
}
|
|
749
|
+
const response = await fetch(`${this.baseUrl}/api/chat`, {
|
|
750
|
+
method: "POST",
|
|
751
|
+
headers: { "Content-Type": "application/json" },
|
|
752
|
+
body: JSON.stringify(body)
|
|
753
|
+
});
|
|
754
|
+
if (!response.ok) {
|
|
755
|
+
const errorText = await response.text();
|
|
756
|
+
throw new Error(`Ollama error (${response.status}): ${errorText}`);
|
|
757
|
+
}
|
|
758
|
+
const data = await response.json();
|
|
759
|
+
const message = data.message;
|
|
760
|
+
const toolCalls = [];
|
|
761
|
+
if (message.tool_calls) {
|
|
762
|
+
for (const tc of message.tool_calls) {
|
|
763
|
+
const fn = tc.function;
|
|
764
|
+
toolCalls.push({
|
|
765
|
+
id: uuid4(),
|
|
766
|
+
type: "function",
|
|
767
|
+
function: {
|
|
768
|
+
name: fn.name,
|
|
769
|
+
arguments: JSON.stringify(fn.arguments)
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
return {
|
|
775
|
+
id: uuid4(),
|
|
776
|
+
content: message.content || "",
|
|
777
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
|
|
778
|
+
usage: {
|
|
779
|
+
promptTokens: data.prompt_eval_count || 0,
|
|
780
|
+
completionTokens: data.eval_count || 0,
|
|
781
|
+
totalTokens: (data.prompt_eval_count || 0) + (data.eval_count || 0)
|
|
782
|
+
},
|
|
783
|
+
finishReason: toolCalls.length > 0 ? "tool_calls" : "stop",
|
|
784
|
+
model: `ollama/${model}`
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
async *chatStream(options) {
|
|
788
|
+
try {
|
|
789
|
+
const response = await this.chat(options);
|
|
790
|
+
if (response.content) yield { type: "text", content: response.content };
|
|
791
|
+
if (response.toolCalls) {
|
|
792
|
+
for (const tc of response.toolCalls) yield { type: "tool_call", toolCall: tc };
|
|
793
|
+
}
|
|
794
|
+
yield { type: "done" };
|
|
795
|
+
} catch (error) {
|
|
796
|
+
yield { type: "error", error: error.message };
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
async listModels() {
|
|
800
|
+
try {
|
|
801
|
+
const response = await fetch(`${this.baseUrl}/api/tags`);
|
|
802
|
+
if (!response.ok) return [];
|
|
803
|
+
const data = await response.json();
|
|
804
|
+
return (data.models || []).map((m) => m.name);
|
|
805
|
+
} catch {
|
|
806
|
+
return [];
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
async healthCheck() {
|
|
810
|
+
try {
|
|
811
|
+
const response = await fetch(`${this.baseUrl}/api/tags`);
|
|
812
|
+
return response.ok;
|
|
813
|
+
} catch {
|
|
814
|
+
return false;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
// src/providers/router.ts
|
|
822
|
+
function initProviders() {
|
|
823
|
+
if (initialized) return;
|
|
824
|
+
providers.set("anthropic", new AnthropicProvider());
|
|
825
|
+
providers.set("openai", new OpenAIProvider());
|
|
826
|
+
providers.set("google", new GoogleProvider());
|
|
827
|
+
providers.set("ollama", new OllamaProvider());
|
|
828
|
+
initialized = true;
|
|
829
|
+
}
|
|
830
|
+
function resolveModel(modelId) {
|
|
831
|
+
initProviders();
|
|
832
|
+
const { provider: providerName, model } = LLMProvider.parseModelId(modelId);
|
|
833
|
+
const provider = providers.get(providerName);
|
|
834
|
+
if (!provider) {
|
|
835
|
+
throw new Error(`Unknown provider: ${providerName}. Available: ${Array.from(providers.keys()).join(", ")}`);
|
|
836
|
+
}
|
|
837
|
+
return { provider, model };
|
|
838
|
+
}
|
|
839
|
+
async function chat(options) {
|
|
840
|
+
const modelId = options.model || "anthropic/claude-sonnet-4-20250514";
|
|
841
|
+
const { provider, model } = resolveModel(modelId);
|
|
842
|
+
logger_default.info(COMPONENT6, `Routing to ${provider.displayName} (model: ${model})`);
|
|
843
|
+
try {
|
|
844
|
+
return await provider.chat({ ...options, model });
|
|
845
|
+
} catch (error) {
|
|
846
|
+
logger_default.error(COMPONENT6, `Provider ${provider.name} failed: ${error.message}`);
|
|
847
|
+
const failoverOrder = ["anthropic", "openai", "google", "ollama"];
|
|
848
|
+
for (const fallbackName of failoverOrder) {
|
|
849
|
+
if (fallbackName === provider.name) continue;
|
|
850
|
+
const fallback = providers.get(fallbackName);
|
|
851
|
+
if (!fallback) continue;
|
|
852
|
+
try {
|
|
853
|
+
const healthy = await fallback.healthCheck();
|
|
854
|
+
if (!healthy) continue;
|
|
855
|
+
const models = await fallback.listModels();
|
|
856
|
+
if (models.length === 0) continue;
|
|
857
|
+
logger_default.warn(COMPONENT6, `Failing over to ${fallback.displayName} (model: ${models[0]})`);
|
|
858
|
+
return await fallback.chat({ ...options, model: models[0] });
|
|
859
|
+
} catch {
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
throw error;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
var COMPONENT6, providers, initialized;
|
|
867
|
+
var init_router = __esm({
|
|
868
|
+
"src/providers/router.ts"() {
|
|
869
|
+
"use strict";
|
|
870
|
+
init_base();
|
|
871
|
+
init_anthropic();
|
|
872
|
+
init_openai();
|
|
873
|
+
init_google();
|
|
874
|
+
init_ollama();
|
|
875
|
+
init_logger();
|
|
876
|
+
COMPONENT6 = "Router";
|
|
877
|
+
providers = /* @__PURE__ */ new Map();
|
|
878
|
+
initialized = false;
|
|
879
|
+
}
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
// src/agent/toolRunner.ts
|
|
883
|
+
function registerTool(handler) {
|
|
884
|
+
toolRegistry.set(handler.name, handler);
|
|
885
|
+
logger_default.debug(COMPONENT7, `Registered tool: ${handler.name}`);
|
|
886
|
+
}
|
|
887
|
+
var COMPONENT7, toolRegistry;
|
|
888
|
+
var init_toolRunner = __esm({
|
|
889
|
+
"src/agent/toolRunner.ts"() {
|
|
890
|
+
"use strict";
|
|
891
|
+
init_logger();
|
|
892
|
+
init_config();
|
|
893
|
+
COMPONENT7 = "ToolRunner";
|
|
894
|
+
toolRegistry = /* @__PURE__ */ new Map();
|
|
895
|
+
}
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
// src/skills/registry.ts
|
|
899
|
+
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2 } from "fs";
|
|
900
|
+
import { join as join2 } from "path";
|
|
901
|
+
function registerSkill(meta, handler) {
|
|
902
|
+
registeredSkills.set(meta.name, meta);
|
|
903
|
+
registerTool(handler);
|
|
904
|
+
logger_default.debug(COMPONENT8, `Registered skill: ${meta.name} (${meta.source})`);
|
|
905
|
+
}
|
|
906
|
+
async function loadAutoSkills() {
|
|
907
|
+
const autoDir = join2(TITAN_HOME, "skills", "auto");
|
|
908
|
+
if (!existsSync3(autoDir)) return;
|
|
909
|
+
logger_default.info(COMPONENT8, "Checking for auto-generated skills...");
|
|
910
|
+
const files = readdirSync(autoDir).filter((f) => f.endsWith(".js"));
|
|
911
|
+
let loadedCount = 0;
|
|
912
|
+
for (const file of files) {
|
|
913
|
+
try {
|
|
914
|
+
const skillPath = join2(autoDir, file);
|
|
915
|
+
const modulePath = `file://${skillPath}?t=${Date.now()}`;
|
|
916
|
+
const mod = await import(modulePath);
|
|
917
|
+
if (mod.default && mod.default.name && mod.default.execute) {
|
|
918
|
+
const handler = mod.default;
|
|
919
|
+
const meta = {
|
|
920
|
+
name: handler.name,
|
|
921
|
+
description: handler.description || "Auto-generated skill",
|
|
922
|
+
version: "1.0.0",
|
|
923
|
+
source: "workspace",
|
|
924
|
+
// Consider it part of user workspace
|
|
925
|
+
enabled: true
|
|
926
|
+
};
|
|
927
|
+
registerSkill(meta, handler);
|
|
928
|
+
loadedCount++;
|
|
929
|
+
}
|
|
930
|
+
} catch (e) {
|
|
931
|
+
logger_default.error(COMPONENT8, `Failed to load auto skill ${file}: ${e.message}`);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
if (loadedCount > 0) {
|
|
935
|
+
logger_default.info(COMPONENT8, `Successfully loaded ${loadedCount} auto-generated skills`);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
var COMPONENT8, registeredSkills;
|
|
939
|
+
var init_registry = __esm({
|
|
940
|
+
"src/skills/registry.ts"() {
|
|
941
|
+
"use strict";
|
|
942
|
+
init_constants();
|
|
943
|
+
init_toolRunner();
|
|
944
|
+
init_helpers();
|
|
945
|
+
init_logger();
|
|
946
|
+
COMPONENT8 = "Skills";
|
|
947
|
+
registeredSkills = /* @__PURE__ */ new Map();
|
|
948
|
+
}
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
// src/agent/generator.ts
|
|
952
|
+
import path from "path";
|
|
953
|
+
import { existsSync as existsSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
954
|
+
import { execSync } from "child_process";
|
|
955
|
+
async function generateAndInstallSkill(description, requestedName) {
|
|
956
|
+
logger_default.info(COMPONENT9, `Initiating auto-generation for skill: "${requestedName}"`);
|
|
957
|
+
if (!existsSync4(AUTO_SKILLS_DIR)) {
|
|
958
|
+
mkdirSync2(AUTO_SKILLS_DIR, { recursive: true });
|
|
959
|
+
}
|
|
960
|
+
try {
|
|
961
|
+
const config = loadConfig();
|
|
962
|
+
const model = config.agent.model;
|
|
963
|
+
logger_default.debug(COMPONENT9, `Prompting ${model} to generate code...`);
|
|
964
|
+
const messages = [
|
|
965
|
+
{ role: "user", content: GENERATOR_PROMPT + `
|
|
966
|
+
Requested Capability: ${description}
|
|
967
|
+
Requested Name: ${requestedName}` }
|
|
968
|
+
];
|
|
969
|
+
const response = await chat({ messages, model });
|
|
970
|
+
let code = response.content;
|
|
971
|
+
if (code.startsWith("```typescript")) {
|
|
972
|
+
code = code.replace(/^\`\`\`typescript\n/, "").replace(/\n\`\`\`$/, "");
|
|
973
|
+
} else if (code.startsWith("```")) {
|
|
974
|
+
code = code.replace(/^\`\`\`\n/, "").replace(/\n\`\`\`$/, "");
|
|
975
|
+
}
|
|
976
|
+
if (code.includes("process.exit") || code.includes("rm -rf /*") || code.includes("fs.rmSync('/'")) {
|
|
977
|
+
return { success: false, error: "Generated code failed safety static analysis." };
|
|
978
|
+
}
|
|
979
|
+
const nameMatch = code.match(/name:\s*['"]([a-z0-9_]+)['"]/);
|
|
980
|
+
const finalName = nameMatch ? nameMatch[1] : requestedName.replace(/[^a-z0-9_]/g, "_");
|
|
981
|
+
const tsFilePath = path.join(AUTO_SKILLS_DIR, `${finalName}.ts`);
|
|
982
|
+
const jsFilePath = path.join(AUTO_SKILLS_DIR, `${finalName}.js`);
|
|
983
|
+
writeFileSync2(tsFilePath, code, "utf-8");
|
|
984
|
+
logger_default.info(COMPONENT9, `Wrote generated source to ${tsFilePath}`);
|
|
985
|
+
try {
|
|
986
|
+
execSync(`npx tsc ${tsFilePath} --module NodeNext --moduleResolution NodeNext --target ES2022`, { stdio: "pipe" });
|
|
987
|
+
logger_default.info(COMPONENT9, `Compiled ${finalName}.ts successfully.`);
|
|
988
|
+
} catch (compileError) {
|
|
989
|
+
logger_default.error(COMPONENT9, `Compilation failed for ${finalName}`);
|
|
990
|
+
return { success: false, error: `Compilation failed: ${compileError.message || "Unknown error"}` };
|
|
991
|
+
}
|
|
992
|
+
if (!existsSync4(jsFilePath)) {
|
|
993
|
+
return { success: false, error: "Compilation did not produce a .js file." };
|
|
994
|
+
}
|
|
995
|
+
await loadAutoSkills();
|
|
996
|
+
logger_default.info(COMPONENT9, `\u2728 Successfully generated and installed new skill: ${finalName}`);
|
|
997
|
+
return {
|
|
998
|
+
success: true,
|
|
999
|
+
skillName: finalName,
|
|
1000
|
+
filePath: tsFilePath
|
|
1001
|
+
};
|
|
1002
|
+
} catch (e) {
|
|
1003
|
+
logger_default.error(COMPONENT9, `Auto-generation failed: ${e.message}`);
|
|
1004
|
+
return { success: false, error: e.message };
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
var COMPONENT9, AUTO_SKILLS_DIR, GENERATOR_PROMPT;
|
|
1008
|
+
var init_generator = __esm({
|
|
1009
|
+
"src/agent/generator.ts"() {
|
|
1010
|
+
init_config();
|
|
1011
|
+
init_router();
|
|
1012
|
+
init_constants();
|
|
1013
|
+
init_registry();
|
|
1014
|
+
init_logger();
|
|
1015
|
+
COMPONENT9 = "SkillGenerator";
|
|
1016
|
+
AUTO_SKILLS_DIR = path.join(TITAN_HOME, "skills", "auto");
|
|
1017
|
+
GENERATOR_PROMPT = `You are the core intelligence of TITAN, an elite autonomous AI agent framework.
|
|
1018
|
+
Your task is to generate a new, fully functional TypeScript tool (skill) based on the user's request.
|
|
1019
|
+
|
|
1020
|
+
CRITICAL REQUIREMENTS FOR THE CODE:
|
|
1021
|
+
1. It MUST export a "default" object that implements the "ToolConfig" interface.
|
|
1022
|
+
2. It MUST use ESM imports (e.g., "import { ... } from '...'").
|
|
1023
|
+
3. It CAN ONLY use built-in Node.js modules (fs, path, crypto, child_process, os, http, https, url) unless you are absolutely certain a specific npm package is guaranteed to be installed globally. Prefer built-ins.
|
|
1024
|
+
4. It MUST handle errors gracefully and return descriptive string messages.
|
|
1025
|
+
5. Do NOT wrap the output in markdown code blocks. Output ONLY the raw TypeScript code.
|
|
1026
|
+
|
|
1027
|
+
Here is the exact ToolConfig interface you must implement:
|
|
1028
|
+
export interface ToolConfig {
|
|
1029
|
+
name: string; // lowercase, alphanumeric, underscores only (e.g. 'my_custom_tool')
|
|
1030
|
+
description: string; // concise explanation of what the tool does
|
|
1031
|
+
parameters: any; // Zod schema converted to JSON schema format for LLM function calling
|
|
1032
|
+
execute: (args: any, config: any) => Promise<string>; // the async execution function
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
Example JSON schema for parameters (must follow this exact structure):
|
|
1036
|
+
{
|
|
1037
|
+
"type": "object",
|
|
1038
|
+
"properties": {
|
|
1039
|
+
"filePath": {
|
|
1040
|
+
"type": "string",
|
|
1041
|
+
"description": "Absolute path to the file"
|
|
1042
|
+
}
|
|
1043
|
+
},
|
|
1044
|
+
"required": ["filePath"]
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
Example implementation format:
|
|
1048
|
+
import fs from 'fs';
|
|
1049
|
+
|
|
1050
|
+
export default {
|
|
1051
|
+
name: "count_lines",
|
|
1052
|
+
description: "Counts the number of lines in a given text file.",
|
|
1053
|
+
parameters: {
|
|
1054
|
+
type: "object",
|
|
1055
|
+
properties: {
|
|
1056
|
+
filePath: { type: "string" }
|
|
1057
|
+
},
|
|
1058
|
+
required: ["filePath"]
|
|
1059
|
+
},
|
|
1060
|
+
execute: async (args, config) => {
|
|
1061
|
+
try {
|
|
1062
|
+
const content = fs.readFileSync(args.filePath, 'utf-8');
|
|
1063
|
+
return \`File has \${content.split('\\n').length} lines.\`;
|
|
1064
|
+
} catch (e) {
|
|
1065
|
+
return \`Error: \${e.message}\`;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
};
|
|
1069
|
+
|
|
1070
|
+
Now, generate the TypeScript code for the following requested tool capability:
|
|
1071
|
+
`;
|
|
1072
|
+
}
|
|
1073
|
+
});
|
|
1074
|
+
init_generator();
|
|
1075
|
+
export {
|
|
1076
|
+
generateAndInstallSkill
|
|
1077
|
+
};
|
|
1078
|
+
//# sourceMappingURL=generator.js.map
|