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.
Files changed (43) hide show
  1. package/.env.example +21 -0
  2. package/LICENSE +29 -0
  3. package/README.md +305 -0
  4. package/assets/titan-logo.png +0 -0
  5. package/dist/agent/agent.js +1458 -0
  6. package/dist/agent/agent.js.map +1 -0
  7. package/dist/agent/generator.js +1078 -0
  8. package/dist/agent/generator.js.map +1 -0
  9. package/dist/cli/index.js +5064 -0
  10. package/dist/cli/index.js.map +1 -0
  11. package/dist/gateway/server.js +4462 -0
  12. package/dist/gateway/server.js.map +1 -0
  13. package/dist/skills/builtin/apply_patch.js +368 -0
  14. package/dist/skills/builtin/apply_patch.js.map +1 -0
  15. package/dist/skills/builtin/auto_generate.js +1129 -0
  16. package/dist/skills/builtin/auto_generate.js.map +1 -0
  17. package/dist/skills/builtin/browser.js +357 -0
  18. package/dist/skills/builtin/browser.js.map +1 -0
  19. package/dist/skills/builtin/cron.js +410 -0
  20. package/dist/skills/builtin/cron.js.map +1 -0
  21. package/dist/skills/builtin/filesystem.js +386 -0
  22. package/dist/skills/builtin/filesystem.js.map +1 -0
  23. package/dist/skills/builtin/memory_skill.js +430 -0
  24. package/dist/skills/builtin/memory_skill.js.map +1 -0
  25. package/dist/skills/builtin/process.js +421 -0
  26. package/dist/skills/builtin/process.js.map +1 -0
  27. package/dist/skills/builtin/sessions.js +1729 -0
  28. package/dist/skills/builtin/sessions.js.map +1 -0
  29. package/dist/skills/builtin/shell.js +327 -0
  30. package/dist/skills/builtin/shell.js.map +1 -0
  31. package/dist/skills/builtin/vision.js +491 -0
  32. package/dist/skills/builtin/vision.js.map +1 -0
  33. package/dist/skills/builtin/voice.js +468 -0
  34. package/dist/skills/builtin/voice.js.map +1 -0
  35. package/dist/skills/builtin/web_fetch.js +331 -0
  36. package/dist/skills/builtin/web_fetch.js.map +1 -0
  37. package/dist/skills/builtin/web_search.js +317 -0
  38. package/dist/skills/builtin/web_search.js.map +1 -0
  39. package/dist/skills/builtin/webhook.js +323 -0
  40. package/dist/skills/builtin/webhook.js.map +1 -0
  41. package/dist/skills/registry.js +3369 -0
  42. package/dist/skills/registry.js.map +1 -0
  43. package/package.json +118 -0
@@ -0,0 +1,1129 @@
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/logger.ts
63
+ import chalk from "chalk";
64
+ function formatTimestamp() {
65
+ return chalk.gray((/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19));
66
+ }
67
+ function log(level, component, message, ...args) {
68
+ if (level < currentLevel) return;
69
+ const prefix = `${formatTimestamp()} ${LEVEL_LABELS[level]} ${chalk.blue(`[${component}]`)}`;
70
+ console.log(`${prefix} ${message}`, ...args);
71
+ }
72
+ var LEVEL_LABELS, currentLevel, logger, logger_default;
73
+ var init_logger = __esm({
74
+ "src/utils/logger.ts"() {
75
+ "use strict";
76
+ LEVEL_LABELS = {
77
+ [0 /* DEBUG */]: chalk.gray("DEBUG"),
78
+ [1 /* INFO */]: chalk.cyan("INFO "),
79
+ [2 /* WARN */]: chalk.yellow("WARN "),
80
+ [3 /* ERROR */]: chalk.red("ERROR"),
81
+ [4 /* SILENT */]: ""
82
+ };
83
+ currentLevel = 1 /* INFO */;
84
+ logger = {
85
+ debug: (component, msg, ...args) => log(0 /* DEBUG */, component, msg, ...args),
86
+ info: (component, msg, ...args) => log(1 /* INFO */, component, msg, ...args),
87
+ warn: (component, msg, ...args) => log(2 /* WARN */, component, msg, ...args),
88
+ error: (component, msg, ...args) => log(3 /* ERROR */, component, msg, ...args)
89
+ };
90
+ logger_default = logger;
91
+ }
92
+ });
93
+
94
+ // src/utils/helpers.ts
95
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
96
+ function ensureDir(dirPath) {
97
+ if (!existsSync(dirPath)) {
98
+ mkdirSync(dirPath, { recursive: true });
99
+ }
100
+ }
101
+ function readJsonFile(filePath) {
102
+ try {
103
+ if (!existsSync(filePath)) return null;
104
+ const content = readFileSync(filePath, "utf-8");
105
+ return JSON.parse(content);
106
+ } catch {
107
+ return null;
108
+ }
109
+ }
110
+ var init_helpers = __esm({
111
+ "src/utils/helpers.ts"() {
112
+ "use strict";
113
+ }
114
+ });
115
+
116
+ // src/config/schema.ts
117
+ import { z } from "zod";
118
+ var ProviderConfigSchema, ChannelConfigSchema, SecurityConfigSchema, GatewayConfigSchema, AgentConfigSchema, TitanConfigSchema;
119
+ var init_schema = __esm({
120
+ "src/config/schema.ts"() {
121
+ "use strict";
122
+ init_constants();
123
+ ProviderConfigSchema = z.object({
124
+ apiKey: z.string().optional(),
125
+ baseUrl: z.string().optional(),
126
+ model: z.string().optional(),
127
+ maxTokens: z.number().optional(),
128
+ temperature: z.number().min(0).max(2).optional()
129
+ });
130
+ ChannelConfigSchema = z.object({
131
+ enabled: z.boolean().default(false),
132
+ token: z.string().optional(),
133
+ apiKey: z.string().optional(),
134
+ allowFrom: z.array(z.string()).default([]),
135
+ dmPolicy: z.enum(["pairing", "open", "closed"]).default("pairing")
136
+ });
137
+ SecurityConfigSchema = z.object({
138
+ sandboxMode: z.enum(["host", "docker", "none"]).default(DEFAULT_SANDBOX_MODE),
139
+ allowedTools: z.array(z.string()).default(ALLOWED_TOOLS_DEFAULT),
140
+ deniedTools: z.array(z.string()).default([]),
141
+ maxConcurrentTasks: z.number().default(5),
142
+ commandTimeout: z.number().default(3e4),
143
+ fileSystemAllowlist: z.array(z.string()).default([]),
144
+ networkAllowlist: z.array(z.string()).default(["*"]),
145
+ shield: z.object({
146
+ enabled: z.boolean().default(true),
147
+ mode: z.enum(["standard", "strict"]).default("strict")
148
+ }).default({})
149
+ });
150
+ GatewayConfigSchema = z.object({
151
+ host: z.string().default(DEFAULT_GATEWAY_HOST),
152
+ port: z.number().default(DEFAULT_GATEWAY_PORT),
153
+ webPort: z.number().default(DEFAULT_WEB_PORT),
154
+ auth: z.object({
155
+ mode: z.enum(["none", "token", "password"]).default("token"),
156
+ token: z.string().optional(),
157
+ password: z.string().optional()
158
+ }).default({})
159
+ });
160
+ AgentConfigSchema = z.object({
161
+ model: z.string().default(DEFAULT_MODEL),
162
+ maxTokens: z.number().default(DEFAULT_MAX_TOKENS),
163
+ temperature: z.number().default(DEFAULT_TEMPERATURE),
164
+ systemPrompt: z.string().optional(),
165
+ workspace: z.string().optional(),
166
+ thinkingMode: z.enum(["off", "low", "medium", "high"]).default("medium")
167
+ });
168
+ TitanConfigSchema = z.object({
169
+ agent: AgentConfigSchema.default({}),
170
+ providers: z.object({
171
+ anthropic: ProviderConfigSchema.default({}),
172
+ openai: ProviderConfigSchema.default({}),
173
+ google: ProviderConfigSchema.default({}),
174
+ ollama: ProviderConfigSchema.default({})
175
+ }).default({}),
176
+ channels: z.object({
177
+ discord: ChannelConfigSchema.default({}),
178
+ telegram: ChannelConfigSchema.default({}),
179
+ slack: ChannelConfigSchema.default({}),
180
+ whatsapp: ChannelConfigSchema.default({}),
181
+ webchat: ChannelConfigSchema.default({}),
182
+ googlechat: ChannelConfigSchema.default({}),
183
+ matrix: ChannelConfigSchema.default({}),
184
+ signal: ChannelConfigSchema.default({}),
185
+ msteams: ChannelConfigSchema.default({}),
186
+ bluebubbles: ChannelConfigSchema.default({})
187
+ }).default({}),
188
+ gateway: GatewayConfigSchema.default({}),
189
+ security: SecurityConfigSchema.default({}),
190
+ memory: z.object({
191
+ enabled: z.boolean().default(true),
192
+ maxHistoryMessages: z.number().default(50),
193
+ vectorSearchEnabled: z.boolean().default(false)
194
+ }).default({}),
195
+ skills: z.object({
196
+ enabled: z.boolean().default(true),
197
+ autoDiscover: z.boolean().default(true),
198
+ marketplace: z.boolean().default(false)
199
+ }).default({}),
200
+ logging: z.object({
201
+ level: z.enum(["debug", "info", "warn", "error", "silent"]).default("info"),
202
+ file: z.boolean().default(true)
203
+ }).default({}),
204
+ autonomy: z.object({
205
+ /** autonomous = full auto, supervised = asks for dangerous ops, locked = asks for everything */
206
+ mode: z.enum(["autonomous", "supervised", "locked"]).default("supervised"),
207
+ /** Auto-approve moderate-risk tools in main session (cli/webchat) */
208
+ autoApproveMainSession: z.boolean().default(true),
209
+ /** Timeout for HITL approval requests (ms). Auto-deny after timeout. */
210
+ approvalTimeoutMs: z.number().default(6e4),
211
+ /** Notify user of auto-approved actions */
212
+ notifyOnAutoApprove: z.boolean().default(true)
213
+ }).default({})
214
+ });
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/agent/toolRunner.ts
296
+ function registerTool(handler2) {
297
+ toolRegistry.set(handler2.name, handler2);
298
+ logger_default.debug(COMPONENT2, `Registered tool: ${handler2.name}`);
299
+ }
300
+ var COMPONENT2, toolRegistry;
301
+ var init_toolRunner = __esm({
302
+ "src/agent/toolRunner.ts"() {
303
+ "use strict";
304
+ init_logger();
305
+ init_config();
306
+ COMPONENT2 = "ToolRunner";
307
+ toolRegistry = /* @__PURE__ */ new Map();
308
+ }
309
+ });
310
+
311
+ // src/skills/registry.ts
312
+ import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2 } from "fs";
313
+ import { join as join2 } from "path";
314
+ function registerSkill(meta2, handler2) {
315
+ registeredSkills.set(meta2.name, meta2);
316
+ registerTool(handler2);
317
+ logger_default.debug(COMPONENT3, `Registered skill: ${meta2.name} (${meta2.source})`);
318
+ }
319
+ async function loadAutoSkills() {
320
+ const autoDir = join2(TITAN_HOME, "skills", "auto");
321
+ if (!existsSync3(autoDir)) return;
322
+ logger_default.info(COMPONENT3, "Checking for auto-generated skills...");
323
+ const files = readdirSync(autoDir).filter((f) => f.endsWith(".js"));
324
+ let loadedCount = 0;
325
+ for (const file of files) {
326
+ try {
327
+ const skillPath = join2(autoDir, file);
328
+ const modulePath = `file://${skillPath}?t=${Date.now()}`;
329
+ const mod = await import(modulePath);
330
+ if (mod.default && mod.default.name && mod.default.execute) {
331
+ const handler2 = mod.default;
332
+ const meta2 = {
333
+ name: handler2.name,
334
+ description: handler2.description || "Auto-generated skill",
335
+ version: "1.0.0",
336
+ source: "workspace",
337
+ // Consider it part of user workspace
338
+ enabled: true
339
+ };
340
+ registerSkill(meta2, handler2);
341
+ loadedCount++;
342
+ }
343
+ } catch (e) {
344
+ logger_default.error(COMPONENT3, `Failed to load auto skill ${file}: ${e.message}`);
345
+ }
346
+ }
347
+ if (loadedCount > 0) {
348
+ logger_default.info(COMPONENT3, `Successfully loaded ${loadedCount} auto-generated skills`);
349
+ }
350
+ }
351
+ var COMPONENT3, registeredSkills;
352
+ var init_registry = __esm({
353
+ "src/skills/registry.ts"() {
354
+ "use strict";
355
+ init_constants();
356
+ init_toolRunner();
357
+ init_helpers();
358
+ init_logger();
359
+ COMPONENT3 = "Skills";
360
+ registeredSkills = /* @__PURE__ */ new Map();
361
+ }
362
+ });
363
+
364
+ // src/providers/base.ts
365
+ var LLMProvider;
366
+ var init_base = __esm({
367
+ "src/providers/base.ts"() {
368
+ "use strict";
369
+ LLMProvider = class {
370
+ /** Get the provider identifier from a model string like "anthropic/claude-3" */
371
+ static parseModelId(modelId) {
372
+ const parts = modelId.split("/");
373
+ if (parts.length >= 2) {
374
+ return { provider: parts[0], model: parts.slice(1).join("/") };
375
+ }
376
+ return { provider: "anthropic", model: modelId };
377
+ }
378
+ };
379
+ }
380
+ });
381
+
382
+ // src/providers/anthropic.ts
383
+ import { v4 as uuid } from "uuid";
384
+ var COMPONENT4, AnthropicProvider;
385
+ var init_anthropic = __esm({
386
+ "src/providers/anthropic.ts"() {
387
+ "use strict";
388
+ init_base();
389
+ init_config();
390
+ init_logger();
391
+ COMPONENT4 = "Anthropic";
392
+ AnthropicProvider = class extends LLMProvider {
393
+ name = "anthropic";
394
+ displayName = "Anthropic (Claude)";
395
+ get apiKey() {
396
+ const config = loadConfig();
397
+ return config.providers.anthropic.apiKey || process.env.ANTHROPIC_API_KEY || "";
398
+ }
399
+ get baseUrl() {
400
+ const config = loadConfig();
401
+ return config.providers.anthropic.baseUrl || "https://api.anthropic.com";
402
+ }
403
+ async chat(options) {
404
+ const model = options.model || "claude-sonnet-4-20250514";
405
+ const apiKey = this.apiKey;
406
+ if (!apiKey) throw new Error("Anthropic API key not configured");
407
+ logger_default.debug(COMPONENT4, `Chat request: model=${model}, messages=${options.messages.length}`);
408
+ const systemMessage = options.messages.find((m) => m.role === "system");
409
+ const nonSystemMessages = options.messages.filter((m) => m.role !== "system");
410
+ const body = {
411
+ model: model.replace("anthropic/", ""),
412
+ max_tokens: options.maxTokens || 8192,
413
+ messages: nonSystemMessages.map((m) => ({
414
+ role: m.role === "tool" ? "user" : m.role,
415
+ content: m.role === "tool" ? [{ type: "tool_result", tool_use_id: m.toolCallId, content: m.content }] : m.content
416
+ }))
417
+ };
418
+ if (systemMessage) {
419
+ body.system = systemMessage.content;
420
+ }
421
+ if (options.tools && options.tools.length > 0) {
422
+ body.tools = options.tools.map((t) => ({
423
+ name: t.function.name,
424
+ description: t.function.description,
425
+ input_schema: t.function.parameters
426
+ }));
427
+ }
428
+ if (options.temperature !== void 0) {
429
+ body.temperature = options.temperature;
430
+ }
431
+ const response = await fetch(`${this.baseUrl}/v1/messages`, {
432
+ method: "POST",
433
+ headers: {
434
+ "Content-Type": "application/json",
435
+ "x-api-key": apiKey,
436
+ "anthropic-version": "2023-06-01"
437
+ },
438
+ body: JSON.stringify(body)
439
+ });
440
+ if (!response.ok) {
441
+ const errorText = await response.text();
442
+ throw new Error(`Anthropic API error (${response.status}): ${errorText}`);
443
+ }
444
+ const data = await response.json();
445
+ const content = data.content;
446
+ let textContent = "";
447
+ const toolCalls = [];
448
+ for (const block of content) {
449
+ if (block.type === "text") {
450
+ textContent += block.text;
451
+ } else if (block.type === "tool_use") {
452
+ toolCalls.push({
453
+ id: block.id,
454
+ type: "function",
455
+ function: {
456
+ name: block.name,
457
+ arguments: JSON.stringify(block.input)
458
+ }
459
+ });
460
+ }
461
+ }
462
+ const usage = data.usage;
463
+ return {
464
+ id: data.id || uuid(),
465
+ content: textContent,
466
+ toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
467
+ usage: usage ? {
468
+ promptTokens: usage.input_tokens,
469
+ completionTokens: usage.output_tokens,
470
+ totalTokens: usage.input_tokens + usage.output_tokens
471
+ } : void 0,
472
+ finishReason: toolCalls.length > 0 ? "tool_calls" : "stop",
473
+ model
474
+ };
475
+ }
476
+ async *chatStream(options) {
477
+ try {
478
+ const response = await this.chat(options);
479
+ if (response.content) {
480
+ yield { type: "text", content: response.content };
481
+ }
482
+ if (response.toolCalls) {
483
+ for (const tc of response.toolCalls) {
484
+ yield { type: "tool_call", toolCall: tc };
485
+ }
486
+ }
487
+ yield { type: "done" };
488
+ } catch (error) {
489
+ yield { type: "error", error: error.message };
490
+ }
491
+ }
492
+ async listModels() {
493
+ return [
494
+ "claude-opus-4-0",
495
+ "claude-sonnet-4-20250514",
496
+ "claude-haiku-4-20250414",
497
+ "claude-3-5-sonnet-20241022",
498
+ "claude-3-5-haiku-20241022"
499
+ ];
500
+ }
501
+ async healthCheck() {
502
+ try {
503
+ if (!this.apiKey) return false;
504
+ const response = await fetch(`${this.baseUrl}/v1/messages`, {
505
+ method: "POST",
506
+ headers: {
507
+ "Content-Type": "application/json",
508
+ "x-api-key": this.apiKey,
509
+ "anthropic-version": "2023-06-01"
510
+ },
511
+ body: JSON.stringify({
512
+ model: "claude-haiku-4-20250414",
513
+ max_tokens: 1,
514
+ messages: [{ role: "user", content: "ping" }]
515
+ })
516
+ });
517
+ return response.ok || response.status === 400;
518
+ } catch {
519
+ return false;
520
+ }
521
+ }
522
+ };
523
+ }
524
+ });
525
+
526
+ // src/providers/openai.ts
527
+ import { v4 as uuid2 } from "uuid";
528
+ var COMPONENT5, OpenAIProvider;
529
+ var init_openai = __esm({
530
+ "src/providers/openai.ts"() {
531
+ "use strict";
532
+ init_base();
533
+ init_config();
534
+ init_logger();
535
+ COMPONENT5 = "OpenAI";
536
+ OpenAIProvider = class extends LLMProvider {
537
+ name = "openai";
538
+ displayName = "OpenAI (GPT)";
539
+ get apiKey() {
540
+ const config = loadConfig();
541
+ return config.providers.openai.apiKey || process.env.OPENAI_API_KEY || "";
542
+ }
543
+ get baseUrl() {
544
+ const config = loadConfig();
545
+ return config.providers.openai.baseUrl || "https://api.openai.com";
546
+ }
547
+ async chat(options) {
548
+ const model = options.model || "gpt-4o";
549
+ const apiKey = this.apiKey;
550
+ if (!apiKey) throw new Error("OpenAI API key not configured");
551
+ logger_default.debug(COMPONENT5, `Chat request: model=${model}, messages=${options.messages.length}`);
552
+ const body = {
553
+ model: model.replace("openai/", ""),
554
+ messages: options.messages.map((m) => {
555
+ if (m.role === "tool") {
556
+ return { role: "tool", content: m.content, tool_call_id: m.toolCallId };
557
+ }
558
+ if (m.role === "assistant" && m.toolCalls) {
559
+ return {
560
+ role: "assistant",
561
+ content: m.content || null,
562
+ tool_calls: m.toolCalls.map((tc) => ({
563
+ id: tc.id,
564
+ type: "function",
565
+ function: { name: tc.function.name, arguments: tc.function.arguments }
566
+ }))
567
+ };
568
+ }
569
+ return { role: m.role, content: m.content };
570
+ }),
571
+ max_tokens: options.maxTokens || 8192
572
+ };
573
+ if (options.tools && options.tools.length > 0) {
574
+ body.tools = options.tools;
575
+ }
576
+ if (options.temperature !== void 0) {
577
+ body.temperature = options.temperature;
578
+ }
579
+ const response = await fetch(`${this.baseUrl}/v1/chat/completions`, {
580
+ method: "POST",
581
+ headers: {
582
+ "Content-Type": "application/json",
583
+ Authorization: `Bearer ${apiKey}`
584
+ },
585
+ body: JSON.stringify(body)
586
+ });
587
+ if (!response.ok) {
588
+ const errorText = await response.text();
589
+ throw new Error(`OpenAI API error (${response.status}): ${errorText}`);
590
+ }
591
+ const data = await response.json();
592
+ const choices = data.choices;
593
+ const choice = choices[0];
594
+ const message = choice.message;
595
+ const toolCalls = [];
596
+ if (message.tool_calls) {
597
+ for (const tc of message.tool_calls) {
598
+ const fn = tc.function;
599
+ toolCalls.push({
600
+ id: tc.id,
601
+ type: "function",
602
+ function: { name: fn.name, arguments: fn.arguments }
603
+ });
604
+ }
605
+ }
606
+ const usage = data.usage;
607
+ return {
608
+ id: data.id || uuid2(),
609
+ content: message.content || "",
610
+ toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
611
+ usage: usage ? {
612
+ promptTokens: usage.prompt_tokens,
613
+ completionTokens: usage.completion_tokens,
614
+ totalTokens: usage.total_tokens
615
+ } : void 0,
616
+ finishReason: toolCalls.length > 0 ? "tool_calls" : choice.finish_reason || "stop",
617
+ model
618
+ };
619
+ }
620
+ async *chatStream(options) {
621
+ try {
622
+ const response = await this.chat(options);
623
+ if (response.content) {
624
+ yield { type: "text", content: response.content };
625
+ }
626
+ if (response.toolCalls) {
627
+ for (const tc of response.toolCalls) {
628
+ yield { type: "tool_call", toolCall: tc };
629
+ }
630
+ }
631
+ yield { type: "done" };
632
+ } catch (error) {
633
+ yield { type: "error", error: error.message };
634
+ }
635
+ }
636
+ async listModels() {
637
+ return ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo", "o1", "o1-mini", "o3-mini"];
638
+ }
639
+ async healthCheck() {
640
+ try {
641
+ if (!this.apiKey) return false;
642
+ const response = await fetch(`${this.baseUrl}/v1/models`, {
643
+ headers: { Authorization: `Bearer ${this.apiKey}` }
644
+ });
645
+ return response.ok;
646
+ } catch {
647
+ return false;
648
+ }
649
+ }
650
+ };
651
+ }
652
+ });
653
+
654
+ // src/providers/google.ts
655
+ import { v4 as uuid3 } from "uuid";
656
+ var COMPONENT6, GoogleProvider;
657
+ var init_google = __esm({
658
+ "src/providers/google.ts"() {
659
+ "use strict";
660
+ init_base();
661
+ init_config();
662
+ init_logger();
663
+ COMPONENT6 = "Google";
664
+ GoogleProvider = class extends LLMProvider {
665
+ name = "google";
666
+ displayName = "Google (Gemini)";
667
+ get apiKey() {
668
+ const config = loadConfig();
669
+ return config.providers.google.apiKey || process.env.GOOGLE_API_KEY || "";
670
+ }
671
+ async chat(options) {
672
+ const model = (options.model || "gemini-2.0-flash").replace("google/", "");
673
+ const apiKey = this.apiKey;
674
+ if (!apiKey) throw new Error("Google API key not configured");
675
+ logger_default.debug(COMPONENT6, `Chat request: model=${model}, messages=${options.messages.length}`);
676
+ const systemInstruction = options.messages.find((m) => m.role === "system")?.content;
677
+ const contents = options.messages.filter((m) => m.role !== "system").map((m) => ({
678
+ role: m.role === "assistant" ? "model" : "user",
679
+ parts: [{ text: m.content }]
680
+ }));
681
+ const body = {
682
+ contents,
683
+ generationConfig: {
684
+ maxOutputTokens: options.maxTokens || 8192,
685
+ temperature: options.temperature ?? 0.7
686
+ }
687
+ };
688
+ if (systemInstruction) {
689
+ body.systemInstruction = { parts: [{ text: systemInstruction }] };
690
+ }
691
+ if (options.tools && options.tools.length > 0) {
692
+ body.tools = [{
693
+ functionDeclarations: options.tools.map((t) => ({
694
+ name: t.function.name,
695
+ description: t.function.description,
696
+ parameters: t.function.parameters
697
+ }))
698
+ }];
699
+ }
700
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`;
701
+ const response = await fetch(url, {
702
+ method: "POST",
703
+ headers: { "Content-Type": "application/json" },
704
+ body: JSON.stringify(body)
705
+ });
706
+ if (!response.ok) {
707
+ const errorText = await response.text();
708
+ throw new Error(`Google API error (${response.status}): ${errorText}`);
709
+ }
710
+ const data = await response.json();
711
+ const candidates = data.candidates;
712
+ let textContent = "";
713
+ const toolCalls = [];
714
+ if (candidates && candidates.length > 0) {
715
+ const parts = candidates[0].content?.parts || [];
716
+ for (const part of parts) {
717
+ if (part.text) {
718
+ textContent += part.text;
719
+ }
720
+ if (part.functionCall) {
721
+ const fc = part.functionCall;
722
+ toolCalls.push({
723
+ id: uuid3(),
724
+ type: "function",
725
+ function: {
726
+ name: fc.name,
727
+ arguments: JSON.stringify(fc.args)
728
+ }
729
+ });
730
+ }
731
+ }
732
+ }
733
+ const usageMeta = data.usageMetadata;
734
+ return {
735
+ id: uuid3(),
736
+ content: textContent,
737
+ toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
738
+ usage: usageMeta ? {
739
+ promptTokens: usageMeta.promptTokenCount || 0,
740
+ completionTokens: usageMeta.candidatesTokenCount || 0,
741
+ totalTokens: usageMeta.totalTokenCount || 0
742
+ } : void 0,
743
+ finishReason: toolCalls.length > 0 ? "tool_calls" : "stop",
744
+ model: `google/${model}`
745
+ };
746
+ }
747
+ async *chatStream(options) {
748
+ try {
749
+ const response = await this.chat(options);
750
+ if (response.content) yield { type: "text", content: response.content };
751
+ if (response.toolCalls) {
752
+ for (const tc of response.toolCalls) yield { type: "tool_call", toolCall: tc };
753
+ }
754
+ yield { type: "done" };
755
+ } catch (error) {
756
+ yield { type: "error", error: error.message };
757
+ }
758
+ }
759
+ async listModels() {
760
+ return ["gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.0-flash", "gemini-1.5-pro"];
761
+ }
762
+ async healthCheck() {
763
+ try {
764
+ if (!this.apiKey) return false;
765
+ const url = `https://generativelanguage.googleapis.com/v1beta/models?key=${this.apiKey}`;
766
+ const response = await fetch(url);
767
+ return response.ok;
768
+ } catch {
769
+ return false;
770
+ }
771
+ }
772
+ };
773
+ }
774
+ });
775
+
776
+ // src/providers/ollama.ts
777
+ import { v4 as uuid4 } from "uuid";
778
+ var COMPONENT7, OllamaProvider;
779
+ var init_ollama = __esm({
780
+ "src/providers/ollama.ts"() {
781
+ "use strict";
782
+ init_base();
783
+ init_config();
784
+ init_logger();
785
+ COMPONENT7 = "Ollama";
786
+ OllamaProvider = class extends LLMProvider {
787
+ name = "ollama";
788
+ displayName = "Ollama (Local)";
789
+ get baseUrl() {
790
+ const config = loadConfig();
791
+ return config.providers.ollama.baseUrl || process.env.OLLAMA_BASE_URL || "http://localhost:11434";
792
+ }
793
+ async chat(options) {
794
+ const model = (options.model || "llama3.1").replace("ollama/", "");
795
+ logger_default.debug(COMPONENT7, `Chat request: model=${model}, messages=${options.messages.length}`);
796
+ const body = {
797
+ model,
798
+ messages: options.messages.map((m) => ({
799
+ role: m.role,
800
+ content: m.content
801
+ })),
802
+ stream: false,
803
+ options: {
804
+ num_predict: options.maxTokens || 8192,
805
+ temperature: options.temperature ?? 0.7
806
+ }
807
+ };
808
+ if (options.tools && options.tools.length > 0) {
809
+ body.tools = options.tools.map((t) => ({
810
+ type: "function",
811
+ function: {
812
+ name: t.function.name,
813
+ description: t.function.description,
814
+ parameters: t.function.parameters
815
+ }
816
+ }));
817
+ }
818
+ const response = await fetch(`${this.baseUrl}/api/chat`, {
819
+ method: "POST",
820
+ headers: { "Content-Type": "application/json" },
821
+ body: JSON.stringify(body)
822
+ });
823
+ if (!response.ok) {
824
+ const errorText = await response.text();
825
+ throw new Error(`Ollama error (${response.status}): ${errorText}`);
826
+ }
827
+ const data = await response.json();
828
+ const message = data.message;
829
+ const toolCalls = [];
830
+ if (message.tool_calls) {
831
+ for (const tc of message.tool_calls) {
832
+ const fn = tc.function;
833
+ toolCalls.push({
834
+ id: uuid4(),
835
+ type: "function",
836
+ function: {
837
+ name: fn.name,
838
+ arguments: JSON.stringify(fn.arguments)
839
+ }
840
+ });
841
+ }
842
+ }
843
+ return {
844
+ id: uuid4(),
845
+ content: message.content || "",
846
+ toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
847
+ usage: {
848
+ promptTokens: data.prompt_eval_count || 0,
849
+ completionTokens: data.eval_count || 0,
850
+ totalTokens: (data.prompt_eval_count || 0) + (data.eval_count || 0)
851
+ },
852
+ finishReason: toolCalls.length > 0 ? "tool_calls" : "stop",
853
+ model: `ollama/${model}`
854
+ };
855
+ }
856
+ async *chatStream(options) {
857
+ try {
858
+ const response = await this.chat(options);
859
+ if (response.content) yield { type: "text", content: response.content };
860
+ if (response.toolCalls) {
861
+ for (const tc of response.toolCalls) yield { type: "tool_call", toolCall: tc };
862
+ }
863
+ yield { type: "done" };
864
+ } catch (error) {
865
+ yield { type: "error", error: error.message };
866
+ }
867
+ }
868
+ async listModels() {
869
+ try {
870
+ const response = await fetch(`${this.baseUrl}/api/tags`);
871
+ if (!response.ok) return [];
872
+ const data = await response.json();
873
+ return (data.models || []).map((m) => m.name);
874
+ } catch {
875
+ return [];
876
+ }
877
+ }
878
+ async healthCheck() {
879
+ try {
880
+ const response = await fetch(`${this.baseUrl}/api/tags`);
881
+ return response.ok;
882
+ } catch {
883
+ return false;
884
+ }
885
+ }
886
+ };
887
+ }
888
+ });
889
+
890
+ // src/providers/router.ts
891
+ function initProviders() {
892
+ if (initialized) return;
893
+ providers.set("anthropic", new AnthropicProvider());
894
+ providers.set("openai", new OpenAIProvider());
895
+ providers.set("google", new GoogleProvider());
896
+ providers.set("ollama", new OllamaProvider());
897
+ initialized = true;
898
+ }
899
+ function resolveModel(modelId) {
900
+ initProviders();
901
+ const { provider: providerName, model } = LLMProvider.parseModelId(modelId);
902
+ const provider = providers.get(providerName);
903
+ if (!provider) {
904
+ throw new Error(`Unknown provider: ${providerName}. Available: ${Array.from(providers.keys()).join(", ")}`);
905
+ }
906
+ return { provider, model };
907
+ }
908
+ async function chat(options) {
909
+ const modelId = options.model || "anthropic/claude-sonnet-4-20250514";
910
+ const { provider, model } = resolveModel(modelId);
911
+ logger_default.info(COMPONENT8, `Routing to ${provider.displayName} (model: ${model})`);
912
+ try {
913
+ return await provider.chat({ ...options, model });
914
+ } catch (error) {
915
+ logger_default.error(COMPONENT8, `Provider ${provider.name} failed: ${error.message}`);
916
+ const failoverOrder = ["anthropic", "openai", "google", "ollama"];
917
+ for (const fallbackName of failoverOrder) {
918
+ if (fallbackName === provider.name) continue;
919
+ const fallback = providers.get(fallbackName);
920
+ if (!fallback) continue;
921
+ try {
922
+ const healthy = await fallback.healthCheck();
923
+ if (!healthy) continue;
924
+ const models = await fallback.listModels();
925
+ if (models.length === 0) continue;
926
+ logger_default.warn(COMPONENT8, `Failing over to ${fallback.displayName} (model: ${models[0]})`);
927
+ return await fallback.chat({ ...options, model: models[0] });
928
+ } catch {
929
+ continue;
930
+ }
931
+ }
932
+ throw error;
933
+ }
934
+ }
935
+ var COMPONENT8, providers, initialized;
936
+ var init_router = __esm({
937
+ "src/providers/router.ts"() {
938
+ "use strict";
939
+ init_base();
940
+ init_anthropic();
941
+ init_openai();
942
+ init_google();
943
+ init_ollama();
944
+ init_logger();
945
+ COMPONENT8 = "Router";
946
+ providers = /* @__PURE__ */ new Map();
947
+ initialized = false;
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
+ "use strict";
1011
+ init_config();
1012
+ init_router();
1013
+ init_constants();
1014
+ init_registry();
1015
+ init_logger();
1016
+ COMPONENT9 = "SkillGenerator";
1017
+ AUTO_SKILLS_DIR = path.join(TITAN_HOME, "skills", "auto");
1018
+ GENERATOR_PROMPT = `You are the core intelligence of TITAN, an elite autonomous AI agent framework.
1019
+ Your task is to generate a new, fully functional TypeScript tool (skill) based on the user's request.
1020
+
1021
+ CRITICAL REQUIREMENTS FOR THE CODE:
1022
+ 1. It MUST export a "default" object that implements the "ToolConfig" interface.
1023
+ 2. It MUST use ESM imports (e.g., "import { ... } from '...'").
1024
+ 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.
1025
+ 4. It MUST handle errors gracefully and return descriptive string messages.
1026
+ 5. Do NOT wrap the output in markdown code blocks. Output ONLY the raw TypeScript code.
1027
+
1028
+ Here is the exact ToolConfig interface you must implement:
1029
+ export interface ToolConfig {
1030
+ name: string; // lowercase, alphanumeric, underscores only (e.g. 'my_custom_tool')
1031
+ description: string; // concise explanation of what the tool does
1032
+ parameters: any; // Zod schema converted to JSON schema format for LLM function calling
1033
+ execute: (args: any, config: any) => Promise<string>; // the async execution function
1034
+ }
1035
+
1036
+ Example JSON schema for parameters (must follow this exact structure):
1037
+ {
1038
+ "type": "object",
1039
+ "properties": {
1040
+ "filePath": {
1041
+ "type": "string",
1042
+ "description": "Absolute path to the file"
1043
+ }
1044
+ },
1045
+ "required": ["filePath"]
1046
+ }
1047
+
1048
+ Example implementation format:
1049
+ import fs from 'fs';
1050
+
1051
+ export default {
1052
+ name: "count_lines",
1053
+ description: "Counts the number of lines in a given text file.",
1054
+ parameters: {
1055
+ type: "object",
1056
+ properties: {
1057
+ filePath: { type: "string" }
1058
+ },
1059
+ required: ["filePath"]
1060
+ },
1061
+ execute: async (args, config) => {
1062
+ try {
1063
+ const content = fs.readFileSync(args.filePath, 'utf-8');
1064
+ return \`File has \${content.split('\\n').length} lines.\`;
1065
+ } catch (e) {
1066
+ return \`Error: \${e.message}\`;
1067
+ }
1068
+ }
1069
+ };
1070
+
1071
+ Now, generate the TypeScript code for the following requested tool capability:
1072
+ `;
1073
+ }
1074
+ });
1075
+
1076
+ // src/skills/builtin/auto_generate.ts
1077
+ function registerAutoGenerateSkill() {
1078
+ registerSkill(meta, handler);
1079
+ }
1080
+ var meta, handler;
1081
+ var init_auto_generate = __esm({
1082
+ "src/skills/builtin/auto_generate.ts"() {
1083
+ init_registry();
1084
+ init_generator();
1085
+ meta = {
1086
+ name: "auto_generate_skill",
1087
+ description: "Auto-generates, compiles, and installs a new TypeScript tool/skill when TITAN lacks a necessary capability.",
1088
+ version: "1.0.0",
1089
+ source: "bundled",
1090
+ enabled: true
1091
+ };
1092
+ handler = {
1093
+ name: "auto_generate_skill",
1094
+ description: "Use this tool to write a NEW skill/tool when you realize you cannot complete the user request with your standard tools. Generates real TypeScript code, compiles it, and hot-reloads it into your capabilities.",
1095
+ parameters: {
1096
+ type: "object",
1097
+ properties: {
1098
+ capability_description: {
1099
+ type: "string",
1100
+ description: "A detailed explanation of exactly what this new tool should do, what arguments it should take, and what it should return."
1101
+ },
1102
+ suggested_name: {
1103
+ type: "string",
1104
+ description: 'A short, lowercase name with underscores for the new tool (e.g., "parse_csv", "resize_image").'
1105
+ }
1106
+ },
1107
+ required: ["capability_description", "suggested_name"]
1108
+ },
1109
+ execute: async (args) => {
1110
+ const capability = args.capability_description;
1111
+ const name = args.suggested_name;
1112
+ if (!capability || !name) {
1113
+ return "Error: missing required arguments capability_description or suggested_name.";
1114
+ }
1115
+ const result = await generateAndInstallSkill(capability, name);
1116
+ if (result.success) {
1117
+ return `\u2728 SUCCESS! New skill '${result.skillName}' has been generated, compiled, and hot-loaded into your tools. You can now use this tool natively!`;
1118
+ } else {
1119
+ return `Failed to generate skill: ${result.error}`;
1120
+ }
1121
+ }
1122
+ };
1123
+ }
1124
+ });
1125
+ init_auto_generate();
1126
+ export {
1127
+ registerAutoGenerateSkill
1128
+ };
1129
+ //# sourceMappingURL=auto_generate.js.map