@aiaiaichain/agent 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +157 -0
  2. package/bin/aiai-mcp +31 -0
  3. package/bin/aiaiaicli +60 -0
  4. package/dist/api/ExtensionAPI.d.ts +68 -0
  5. package/dist/api/ExtensionAPI.js +9 -0
  6. package/dist/api/Registry.d.ts +24 -0
  7. package/dist/api/Registry.js +58 -0
  8. package/dist/cli.d.ts +6 -0
  9. package/dist/cli.js +252 -0
  10. package/dist/core/AgentDir.d.ts +10 -0
  11. package/dist/core/AgentDir.js +74 -0
  12. package/dist/core/ChainConfig.d.ts +19 -0
  13. package/dist/core/ChainConfig.js +65 -0
  14. package/dist/core/EnvLoader.d.ts +15 -0
  15. package/dist/core/EnvLoader.js +58 -0
  16. package/dist/index.d.ts +41 -0
  17. package/dist/index.js +42 -0
  18. package/dist/loader.d.ts +11 -0
  19. package/dist/loader.js +73 -0
  20. package/dist/mcp/entry.d.ts +5 -0
  21. package/dist/mcp/entry.js +10 -0
  22. package/dist/mcp/server.d.ts +14 -0
  23. package/dist/mcp/server.js +137 -0
  24. package/dist/models/CostTracker.d.ts +38 -0
  25. package/dist/models/CostTracker.js +75 -0
  26. package/dist/models/ModelRegistry.d.ts +70 -0
  27. package/dist/models/ModelRegistry.js +163 -0
  28. package/dist/runner/AgentRunner.d.ts +54 -0
  29. package/dist/runner/AgentRunner.js +171 -0
  30. package/dist/runner/ModelClient.d.ts +30 -0
  31. package/dist/runner/ModelClient.js +84 -0
  32. package/dist/runner/SwarmRouter.d.ts +23 -0
  33. package/dist/runner/SwarmRouter.js +24 -0
  34. package/dist/runner/ToolDispatcher.d.ts +13 -0
  35. package/dist/runner/ToolDispatcher.js +34 -0
  36. package/dist/scheduler/AgentScheduler.d.ts +48 -0
  37. package/dist/scheduler/AgentScheduler.js +111 -0
  38. package/dist/session/ContextStore.d.ts +28 -0
  39. package/dist/session/ContextStore.js +85 -0
  40. package/dist/session/GoalManager.d.ts +43 -0
  41. package/dist/session/GoalManager.js +108 -0
  42. package/dist/session/MemoryStore.d.ts +27 -0
  43. package/dist/session/MemoryStore.js +92 -0
  44. package/dist/session/SessionManager.d.ts +24 -0
  45. package/dist/session/SessionManager.js +57 -0
  46. package/dist/setup/SetupWizard.d.ts +13 -0
  47. package/dist/setup/SetupWizard.js +71 -0
  48. package/dist/tools/MarketSentiment.d.ts +20 -0
  49. package/dist/tools/MarketSentiment.js +211 -0
  50. package/dist/tools/NewsSentiment.d.ts +36 -0
  51. package/dist/tools/NewsSentiment.js +141 -0
  52. package/dist/tools/PriceFeed.d.ts +85 -0
  53. package/dist/tools/PriceFeed.js +134 -0
  54. package/dist/tools/TechnicalAnalysis.d.ts +50 -0
  55. package/dist/tools/TechnicalAnalysis.js +234 -0
  56. package/dist/tui/App.d.ts +20 -0
  57. package/dist/tui/App.js +484 -0
  58. package/dist/tui/ModelSelector.d.ts +18 -0
  59. package/dist/tui/ModelSelector.js +59 -0
  60. package/dist/tui/REPL.d.ts +22 -0
  61. package/dist/tui/REPL.js +48 -0
  62. package/dist/tui/StatusBar.d.ts +14 -0
  63. package/dist/tui/StatusBar.js +13 -0
  64. package/dist/tui/theme.d.ts +27 -0
  65. package/dist/tui/theme.js +38 -0
  66. package/dist/util/safeLog.d.ts +8 -0
  67. package/dist/util/safeLog.js +38 -0
  68. package/package.json +64 -0
@@ -0,0 +1,75 @@
1
+ /**
2
+ * CostTracker — tracks token usage and cost across sessions.
3
+ */
4
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
5
+ import { resolve } from "node:path";
6
+ import { Type } from "@sinclair/typebox";
7
+ import { AgentDir } from "../core/AgentDir.js";
8
+ const LIFETIME_PATH = resolve(AgentDir.getHome(), "lifetime-cost.json");
9
+ export class CostTracker {
10
+ registry;
11
+ sessionEntry = { entries: [], totalCost: 0, totalTokens: 0 };
12
+ lifetime = { sessions: 0, totalCost: 0, totalTokens: 0 };
13
+ loaded = false;
14
+ constructor(registry) {
15
+ this.registry = registry;
16
+ }
17
+ track(model, promptTokens, completionTokens) {
18
+ if (!this.loaded)
19
+ this.loadLifetime();
20
+ const cost = this.estimateCost(model, promptTokens, completionTokens);
21
+ const entry = { model, promptTokens, completionTokens, cost, timestamp: Date.now() };
22
+ this.sessionEntry.entries.push(entry);
23
+ this.sessionEntry.totalCost += cost;
24
+ this.sessionEntry.totalTokens += promptTokens + completionTokens;
25
+ }
26
+ estimateCost(model, prompt, completion) {
27
+ const all = this.registry.getAllModels();
28
+ const m = all.find(m => m.id === model);
29
+ if (!m)
30
+ return (prompt + completion) * 0.000001;
31
+ const p = parseFloat(m.pricing.prompt) || DEFAULT_RATES.prompt;
32
+ const c = parseFloat(m.pricing.completion) || DEFAULT_RATES.completion;
33
+ return (prompt * p) + (completion * c);
34
+ }
35
+ statusLine() {
36
+ return `$${this.sessionEntry.totalCost.toFixed(4)}`;
37
+ }
38
+ saveLifetime() {
39
+ this.lifetime.sessions++;
40
+ this.lifetime.totalCost += this.sessionEntry.totalCost;
41
+ this.lifetime.totalTokens += this.sessionEntry.totalTokens;
42
+ this.writeLifetime();
43
+ }
44
+ loadLifetime() {
45
+ try {
46
+ if (existsSync(LIFETIME_PATH)) {
47
+ this.lifetime = JSON.parse(readFileSync(LIFETIME_PATH, "utf-8"));
48
+ }
49
+ }
50
+ catch { /* fresh start */ }
51
+ this.loaded = true;
52
+ }
53
+ writeLifetime() {
54
+ try {
55
+ writeFileSync(LIFETIME_PATH, JSON.stringify(this.lifetime, null, 2), "utf-8");
56
+ }
57
+ catch { /* non-fatal */ }
58
+ }
59
+ costReportParams = Type.Object({});
60
+ async costReportTool() {
61
+ const s = this.sessionEntry;
62
+ const l = this.lifetime;
63
+ return {
64
+ content: [{
65
+ type: "text",
66
+ text: [
67
+ `Session cost: $${s.totalCost.toFixed(4)} (${s.totalTokens} tokens, ${s.entries.length} calls)`,
68
+ `Lifetime: $${l.totalCost.toFixed(4)} (${l.totalTokens} tokens, ${l.sessions} sessions)`,
69
+ ].join("\n"),
70
+ }],
71
+ };
72
+ }
73
+ }
74
+ const DEFAULT_RATES = { prompt: 0.000001, completion: 0.000002 };
75
+ //# sourceMappingURL=CostTracker.js.map
@@ -0,0 +1,70 @@
1
+ /**
2
+ * ModelRegistry — discovers available models via OpenRouter API.
3
+ * Caches results and provides tiered model pools.
4
+ */
5
+ import type { ToolResult } from "../api/ExtensionAPI.js";
6
+ export type ModelTier = "orchestrator" | "analyst" | "worker" | "free";
7
+ export interface OpenRouterModel {
8
+ id: string;
9
+ name: string;
10
+ created: number;
11
+ description: string;
12
+ context_length: number;
13
+ architecture: {
14
+ modality: string;
15
+ tokenizer: string;
16
+ instruct_type: string | null;
17
+ };
18
+ pricing: {
19
+ prompt: string;
20
+ completion: string;
21
+ image: string;
22
+ request: string;
23
+ };
24
+ top_provider: {
25
+ max_completion_tokens: number | null;
26
+ is_moderated: boolean;
27
+ };
28
+ per_request_limits: {
29
+ prompt_tokens: number | null;
30
+ completion_tokens: number | null;
31
+ };
32
+ }
33
+ export interface TieredModel {
34
+ model: OpenRouterModel;
35
+ tier: ModelTier;
36
+ available: boolean;
37
+ failures: number;
38
+ }
39
+ export interface TieredPool {
40
+ tier: ModelTier;
41
+ models: TieredModel[];
42
+ }
43
+ export declare function classifyModel(model: OpenRouterModel): ModelTier;
44
+ export declare class ModelRegistry {
45
+ private models;
46
+ private tiered;
47
+ private initialized;
48
+ initialise(): Promise<void>;
49
+ get modelCount(): number;
50
+ getPool(tier: ModelTier): TieredModel[];
51
+ getAllModels(): OpenRouterModel[];
52
+ getTier(modelId: string): ModelTier | undefined;
53
+ resolvePrimary(): string;
54
+ listModelsParams: import("@sinclair/typebox").TObject<{
55
+ query: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
56
+ limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
57
+ available_only: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
58
+ }>;
59
+ pickModelParams: import("@sinclair/typebox").TObject<{
60
+ tier: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
61
+ min_context: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
62
+ }>;
63
+ summaryParams: import("@sinclair/typebox").TObject<{}>;
64
+ listModelsTool(_id: string, params: Record<string, unknown>): Promise<ToolResult>;
65
+ pickModelTool(_id: string, params: Record<string, unknown>): Promise<ToolResult>;
66
+ summaryTool(): Promise<ToolResult>;
67
+ private getFallbackModels;
68
+ }
69
+ export declare const modelRegistry: ModelRegistry;
70
+ //# sourceMappingURL=ModelRegistry.d.ts.map
@@ -0,0 +1,163 @@
1
+ /**
2
+ * ModelRegistry — discovers available models via OpenRouter API.
3
+ * Caches results and provides tiered model pools.
4
+ */
5
+ import { env } from "../core/EnvLoader.js";
6
+ import { Type } from "@sinclair/typebox";
7
+ const DEFAULT_PRICING = {
8
+ prompt: 0.000001,
9
+ completion: 0.000002,
10
+ };
11
+ // ── Tier classification logic ─────────────────────────────────────────────────
12
+ export function classifyModel(model) {
13
+ const id = model.id.toLowerCase();
14
+ const name = model.name.toLowerCase();
15
+ // High-end reasoning / largest models
16
+ if (id.includes("o1") || id.includes("o3") || id.includes("claude-3.5-sonnet") ||
17
+ id.includes("claude-4") || id.includes("gemini-2.5-pro") ||
18
+ id.includes("gpt-4o") || !id.includes("mini") && (id.includes("gpt-4") || id.includes("chatgpt-4o")))
19
+ return "orchestrator";
20
+ // Mid-range capable models
21
+ if (id.includes("sonnet") || id.includes("gpt-4o-mini") || id.includes("claude-3-haiku") ||
22
+ id.includes("gemini-2.0-flash") || id.includes("gemini-1.5-pro") ||
23
+ id.includes("mistral-large") || id.includes("llama-3.1-70b") || id.includes("llama-3.3-70b") ||
24
+ id.includes("command-r-plus") || id.includes("deepseek-v2") || id.includes("qwen-2.5-72b") ||
25
+ id.includes("mixtral-8x22b"))
26
+ return "analyst";
27
+ // Small, fast, cheap models
28
+ if (id.includes("haiku") || id.includes("flash") || id.includes("mistral-7b") ||
29
+ id.includes("llama-3.1-8b") || id.includes("llama-3.2") || id.includes("phi-3") ||
30
+ id.includes("qwen-2.5-7b") || id.includes("gemma-2") || id.includes("deepseek-v2-lite") ||
31
+ id.includes("ministral-3b") || id.includes("command-r-08-2024") ||
32
+ id.includes("mixtral-8x7b") || id.includes("nemotron-4") || id.includes("claude-3-opus"))
33
+ return "worker";
34
+ return "free";
35
+ }
36
+ // ── Model Registry ────────────────────────────────────────────────────────────
37
+ export class ModelRegistry {
38
+ models = [];
39
+ tiered = new Map();
40
+ initialized = false;
41
+ async initialise() {
42
+ if (this.initialized)
43
+ return;
44
+ const apiKey = env.get("OPENROUTER_API_KEY");
45
+ try {
46
+ const response = await fetch("https://openrouter.ai/api/v1/models", {
47
+ headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
48
+ });
49
+ if (response.ok) {
50
+ const data = (await response.json());
51
+ this.models = (data.data ?? []).filter(m => m.id && m.name);
52
+ }
53
+ }
54
+ catch {
55
+ // Fallback: use a curated list of known models
56
+ this.models = this.getFallbackModels();
57
+ }
58
+ // Build tiered pools
59
+ this.tiered.clear();
60
+ const tiers = ["orchestrator", "analyst", "worker", "free"];
61
+ for (const tier of tiers)
62
+ this.tiered.set(tier, []);
63
+ for (const model of this.models) {
64
+ const tier = classifyModel(model);
65
+ this.tiered.get(tier)?.push({
66
+ model,
67
+ tier,
68
+ available: true,
69
+ failures: 0,
70
+ });
71
+ }
72
+ this.initialized = true;
73
+ }
74
+ get modelCount() { return this.models.length; }
75
+ getPool(tier) {
76
+ return this.tiered.get(tier) ?? [];
77
+ }
78
+ getAllModels() {
79
+ return [...this.models];
80
+ }
81
+ getTier(modelId) {
82
+ const model = this.models.find(m => m.id === modelId);
83
+ if (!model)
84
+ return undefined;
85
+ return classifyModel(model);
86
+ }
87
+ resolvePrimary() {
88
+ const tier = "orchestrator";
89
+ const pool = this.tiered.get(tier) ?? [];
90
+ const available = pool.filter(tm => tm.available && tm.failures < 3);
91
+ if (available.length > 0)
92
+ return available[0].model.id;
93
+ // Fall through tiers
94
+ for (const t of ["analyst", "worker", "free"]) {
95
+ const fallback = (this.tiered.get(t) ?? []).filter(tm => tm.available && tm.failures < 3);
96
+ if (fallback.length > 0)
97
+ return fallback[0].model.id;
98
+ }
99
+ return "openai/gpt-4o-mini";
100
+ }
101
+ // ── Tools exposed to the agent ──────────────────────────────────────────
102
+ listModelsParams = Type.Object({
103
+ query: Type.Optional(Type.String({ description: "Search query (name/provider)" })),
104
+ limit: Type.Optional(Type.Number({ description: "Max results", default: 20 })),
105
+ available_only: Type.Optional(Type.Boolean({ default: true })),
106
+ });
107
+ pickModelParams = Type.Object({
108
+ tier: Type.Optional(Type.String({ description: "Tier: orchestrator, analyst, worker, free" })),
109
+ min_context: Type.Optional(Type.Number({ description: "Minimum context length in tokens" })),
110
+ });
111
+ summaryParams = Type.Object({});
112
+ async listModelsTool(_id, params) {
113
+ let items = [...this.models];
114
+ const query = (params.query || "").toLowerCase();
115
+ if (query)
116
+ items = items.filter(m => m.id.toLowerCase().includes(query) || m.name.toLowerCase().includes(query));
117
+ const limit = params.limit || 20;
118
+ const top = items.slice(0, limit);
119
+ const lines = top.map(m => {
120
+ const tier = classifyModel(m);
121
+ const tierIcon = { orchestrator: "⭐", analyst: "🔬", worker: "⚡", free: "🆓" }[tier] ?? "❓";
122
+ return `${tierIcon} ${m.id} — ${m.name}`;
123
+ });
124
+ return {
125
+ content: [{ type: "text", text: `Models (${items.length} matching):\n${lines.join("\n")}` }],
126
+ };
127
+ }
128
+ async pickModelTool(_id, params) {
129
+ const tier = params.tier || "worker";
130
+ const minCtx = params.min_context || 0;
131
+ const pool = this.tiered.get(tier) ?? [];
132
+ const available = pool.filter(tm => tm.available && tm.failures < 3 && tm.model.context_length >= minCtx);
133
+ if (available.length === 0) {
134
+ return { content: [{ type: "text", text: `No available models in tier "${tier}"${minCtx > 0 ? ` with ${minCtx}+ context` : ""}` }] };
135
+ }
136
+ const pick = available[0];
137
+ return { content: [{ type: "text", text: `Selected: ${pick.model.id} (${pick.model.name}) — tier: ${tier}` }] };
138
+ }
139
+ async summaryTool() {
140
+ const pool = this.tiered;
141
+ const lines = [`Total models: ${this.models.length}`];
142
+ for (const tier of ["orchestrator", "analyst", "worker", "free"]) {
143
+ const p = pool.get(tier) ?? [];
144
+ const avail = p.filter(tm => tm.available).length;
145
+ lines.push(` ${tier}: ${p.length} total, ${avail} available`);
146
+ }
147
+ return { content: [{ type: "text", text: lines.join("\n") }] };
148
+ }
149
+ // ── Fallback models ─────────────────────────────────────────────────────
150
+ getFallbackModels() {
151
+ return [
152
+ { id: "openai/gpt-4o", name: "GPT-4o", created: 2024, description: "OpenAI GPT-4o", context_length: 128000, architecture: { modality: "text+image", tokenizer: "cl100k_base", instruct_type: null }, pricing: { prompt: "2.5e-6", completion: "1e-5", image: "0", request: "0" }, top_provider: { max_completion_tokens: 4096, is_moderated: true }, per_request_limits: null },
153
+ { id: "openai/gpt-4o-mini", name: "GPT-4o Mini", created: 2024, description: "OpenAI GPT-4o Mini", context_length: 128000, architecture: { modality: "text+image", tokenizer: "cl100k_base", instruct_type: null }, pricing: { prompt: "1.5e-7", completion: "6e-7", image: "0", request: "0" }, top_provider: { max_completion_tokens: 4096, is_moderated: true }, per_request_limits: null },
154
+ { id: "anthropic/claude-3.5-sonnet", name: "Claude 3.5 Sonnet", created: 2024, description: "Anthropic Claude 3.5 Sonnet", context_length: 200000, architecture: { modality: "text", tokenizer: "claude", instruct_type: null }, pricing: { prompt: "3e-6", completion: "1.5e-5", image: "0", request: "0" }, top_provider: { max_completion_tokens: 8192, is_moderated: true }, per_request_limits: null },
155
+ { id: "anthropic/claude-3-haiku", name: "Claude 3 Haiku", created: 2024, description: "Anthropic Claude 3 Haiku", context_length: 200000, architecture: { modality: "text", tokenizer: "claude", instruct_type: null }, pricing: { prompt: "2.5e-7", completion: "1.25e-6", image: "0", request: "0" }, top_provider: { max_completion_tokens: 4096, is_moderated: true }, per_request_limits: null },
156
+ { id: "google/gemini-2.0-flash-001", name: "Gemini 2.0 Flash", created: 2024, description: "Google Gemini 2.0 Flash", context_length: 1048576, architecture: { modality: "text+image", tokenizer: "gemini", instruct_type: null }, pricing: { prompt: "1e-7", completion: "4e-7", image: "0", request: "0" }, top_provider: { max_completion_tokens: 8192, is_moderated: false }, per_request_limits: null },
157
+ { id: "meta-llama/llama-3.1-8b-instruct", name: "Llama 3.1 8B", created: 2024, description: "Meta Llama 3.1 8B", context_length: 131072, architecture: { modality: "text", tokenizer: "tiktoken", instruct_type: null }, pricing: { prompt: "2e-8", completion: "2e-8", image: "0", request: "0" }, top_provider: { max_completion_tokens: 4096, is_moderated: false }, per_request_limits: null },
158
+ { id: "mistralai/mistral-small", name: "Mistral Small", created: 2024, description: "Mistral Small", context_length: 32000, architecture: { modality: "text", tokenizer: "mistral", instruct_type: null }, pricing: { prompt: "1e-7", completion: "3e-7", image: "0", request: "0" }, top_provider: { max_completion_tokens: 8192, is_moderated: true }, per_request_limits: null },
159
+ ];
160
+ }
161
+ }
162
+ export const modelRegistry = new ModelRegistry();
163
+ //# sourceMappingURL=ModelRegistry.js.map
@@ -0,0 +1,54 @@
1
+ /**
2
+ * AgentRunner — main agent loop. Handles tool calling, streaming, and message processing.
3
+ * The AI responds with text or tool calls; we parse, dispatch, and feed results back.
4
+ */
5
+ import type { Registry } from "../api/Registry.js";
6
+ import type { SessionManager } from "../session/SessionManager.js";
7
+ import type { SessionContext } from "../api/ExtensionAPI.js";
8
+ import type { ModelRegistry } from "../models/ModelRegistry.js";
9
+ import type { CostTracker } from "../models/CostTracker.js";
10
+ import type { GoalManager } from "../session/GoalManager.js";
11
+ import type { ContextStore } from "../session/ContextStore.js";
12
+ export type RunnerEvent = {
13
+ type: "text_delta";
14
+ text: string;
15
+ } | {
16
+ type: "tool_start";
17
+ name: string;
18
+ } | {
19
+ type: "tool_done";
20
+ name: string;
21
+ result: string;
22
+ isError: boolean;
23
+ } | {
24
+ type: "turn_done";
25
+ } | {
26
+ type: "error";
27
+ message: string;
28
+ } | {
29
+ type: "approval_request";
30
+ toolName: string;
31
+ args: string;
32
+ approve: (ok: boolean) => void;
33
+ };
34
+ export declare class AgentRunner {
35
+ private registry;
36
+ private session;
37
+ private dispatcher;
38
+ private onEvent;
39
+ private sessionCtx;
40
+ private effectLevel;
41
+ private modelReg;
42
+ private costTracker?;
43
+ private goalManager?;
44
+ private contextStore?;
45
+ private abortController;
46
+ private _pendingApproval?;
47
+ constructor(registry: Registry, session: SessionManager, onEvent: (event: RunnerEvent) => void, sessionCtx: SessionContext, effectLevel: string, modelReg: ModelRegistry, costTracker?: CostTracker, goalManager?: GoalManager, contextStore?: ContextStore);
48
+ setEffectLevel(level: string): void;
49
+ run(input: string): Promise<void>;
50
+ private executeTool;
51
+ private requiresApproval;
52
+ abort(): void;
53
+ }
54
+ //# sourceMappingURL=AgentRunner.d.ts.map
@@ -0,0 +1,171 @@
1
+ /**
2
+ * AgentRunner — main agent loop. Handles tool calling, streaming, and message processing.
3
+ * The AI responds with text or tool calls; we parse, dispatch, and feed results back.
4
+ */
5
+ import { ToolDispatcher } from "./ToolDispatcher.js";
6
+ import { ModelClient, resolveModelConfig } from "./ModelClient.js";
7
+ export class AgentRunner {
8
+ registry;
9
+ session;
10
+ dispatcher;
11
+ onEvent;
12
+ sessionCtx;
13
+ effectLevel;
14
+ modelReg;
15
+ costTracker;
16
+ goalManager;
17
+ contextStore;
18
+ abortController = null;
19
+ _pendingApproval;
20
+ constructor(registry, session, onEvent, sessionCtx, effectLevel, modelReg, costTracker, goalManager, contextStore) {
21
+ this.registry = registry;
22
+ this.session = session;
23
+ this.dispatcher = new ToolDispatcher(registry);
24
+ this.onEvent = onEvent;
25
+ this.sessionCtx = sessionCtx;
26
+ this.effectLevel = effectLevel;
27
+ this.modelReg = modelReg;
28
+ this.costTracker = costTracker;
29
+ this.goalManager = goalManager;
30
+ this.contextStore = contextStore;
31
+ }
32
+ setEffectLevel(level) {
33
+ this.effectLevel = level;
34
+ }
35
+ async run(input) {
36
+ this.session.addMessage("user", input);
37
+ const config = resolveModelConfig(this.modelReg);
38
+ const messages = this.session.getMessages();
39
+ const systemPrompt = this.session.getSystemPrompt();
40
+ let fullResponse = "";
41
+ let toolCallBuffer = "";
42
+ this.abortController = new AbortController();
43
+ const handleEvent = (event) => {
44
+ if (event.type === "text_delta") {
45
+ fullResponse += event.text;
46
+ // Check for tool call patterns
47
+ toolCallBuffer += event.text;
48
+ // Simple JSON tool call detection
49
+ const toolCallMatch = toolCallBuffer.match(/\{("tool"|"name"):\s*"([^"]+)"\s*,\s*"args"|"params"|"parameters":\s*(\{[^}]+\})\s*\}/);
50
+ if (toolCallMatch) {
51
+ const toolName = toolCallMatch[2];
52
+ let args = {};
53
+ try {
54
+ args = JSON.parse(toolCallMatch[3]);
55
+ }
56
+ catch { /* use empty args */ }
57
+ fullResponse = fullResponse.replace(toolCallMatch[0], "").trim();
58
+ toolCallBuffer = "";
59
+ if (toolName !== "noop") {
60
+ this.onEvent({ type: "tool_start", name: toolName });
61
+ // Check if tool requires approval
62
+ const tool = this.dispatcher.getTool(toolName);
63
+ if (tool && this.requiresApproval(toolName)) {
64
+ this.onEvent({
65
+ type: "approval_request",
66
+ toolName,
67
+ args: JSON.stringify(args),
68
+ approve: (ok) => {
69
+ if (ok) {
70
+ this.executeTool(toolName, args, fullResponse, systemPrompt, messages, config);
71
+ }
72
+ else {
73
+ this.onEvent({
74
+ type: "tool_done",
75
+ name: toolName,
76
+ result: JSON.stringify({ error: "User denied approval" }),
77
+ isError: true,
78
+ });
79
+ this.session.addMessage("assistant", fullResponse || `(tool ${toolName} was denied)`);
80
+ this.onEvent({ type: "turn_done" });
81
+ }
82
+ },
83
+ });
84
+ return;
85
+ }
86
+ this.executeTool(toolName, args, fullResponse, systemPrompt, messages, config);
87
+ }
88
+ }
89
+ this.onEvent({ type: "text_delta", text: event.text });
90
+ }
91
+ else if (event.type === "turn_done") {
92
+ if (fullResponse.trim()) {
93
+ this.session.addMessage("assistant", fullResponse.trim());
94
+ if (this.costTracker) {
95
+ this.costTracker.track(config.model, messages.reduce((s, m) => s + m.content.length, 0), fullResponse.length);
96
+ }
97
+ }
98
+ this.onEvent({ type: "turn_done" });
99
+ }
100
+ else if (event.type === "error") {
101
+ // Check if it's a streaming error that might be tool-call related
102
+ if (toolCallBuffer.includes("tool") || toolCallBuffer.includes("function")) {
103
+ // Try to extract tool name from error
104
+ const nameMatch = toolCallBuffer.match(/"name"\s*:\s*"([^"]+)"/);
105
+ const name = nameMatch ? nameMatch[1] : "unknown";
106
+ this.onEvent({
107
+ type: "tool_done",
108
+ name,
109
+ result: JSON.stringify({ error: event.message }),
110
+ isError: true,
111
+ });
112
+ }
113
+ this.onEvent({ type: "error", message: event.message ?? "unknown error" });
114
+ }
115
+ };
116
+ await ModelClient.runStreaming({
117
+ config,
118
+ systemPrompt,
119
+ messages,
120
+ onEvent: handleEvent,
121
+ signal: this.abortController.signal,
122
+ });
123
+ }
124
+ async executeTool(toolName, args, responseSoFar, systemPrompt, messages, config) {
125
+ const result = await this.dispatcher.dispatch(toolName, args);
126
+ const resultText = result.content.map(c => c.text).join("\n");
127
+ this.onEvent({
128
+ type: "tool_done",
129
+ name: toolName,
130
+ result: resultText,
131
+ isError: result.isError ?? false,
132
+ });
133
+ // Feed the result back to the model for a follow-up
134
+ const toolMessages = [
135
+ ...messages.slice(-5),
136
+ { role: "assistant", content: responseSoFar || `(using ${toolName})` },
137
+ { role: "user", content: `Tool "${toolName}" returned:\n${resultText}\n\nSummarize the result or continue.` },
138
+ ];
139
+ let secondResponse = "";
140
+ await ModelClient.runStreaming({
141
+ config,
142
+ systemPrompt,
143
+ messages: toolMessages,
144
+ onEvent: (event) => {
145
+ if (event.type === "text_delta") {
146
+ secondResponse += event.text;
147
+ this.onEvent({ type: "text_delta", text: event.text });
148
+ }
149
+ else if (event.type === "turn_done") {
150
+ if (secondResponse.trim()) {
151
+ this.session.addMessage("assistant", secondResponse.trim());
152
+ }
153
+ this.onEvent({ type: "turn_done" });
154
+ }
155
+ else if (event.type === "error") {
156
+ this.onEvent({ type: "error", message: event.message ?? "unknown error" });
157
+ }
158
+ },
159
+ signal: this.abortController?.signal,
160
+ });
161
+ }
162
+ requiresApproval(toolName) {
163
+ const approvalTools = ["execute_trade", "send_transaction", "sign_message", "deploy_contract"];
164
+ return approvalTools.includes(toolName);
165
+ }
166
+ abort() {
167
+ this.abortController?.abort();
168
+ this.abortController = null;
169
+ }
170
+ }
171
+ //# sourceMappingURL=AgentRunner.js.map
@@ -0,0 +1,30 @@
1
+ /**
2
+ * ModelClient — LLM API client for OpenRouter.
3
+ * Handles streaming and non-streaming calls.
4
+ */
5
+ import type { ModelRegistry } from "../models/ModelRegistry.js";
6
+ export interface ModelConfig {
7
+ model: string;
8
+ apiKey: string;
9
+ baseUrl: string;
10
+ }
11
+ export declare function resolveModelConfig(registry: ModelRegistry): ModelConfig;
12
+ export interface StreamEvent {
13
+ type: "text_delta" | "turn_done" | "error";
14
+ text?: string;
15
+ message?: string;
16
+ }
17
+ export interface ModelClientOptions {
18
+ config: ModelConfig;
19
+ systemPrompt: string;
20
+ messages: Array<{
21
+ role: string;
22
+ content: string;
23
+ }>;
24
+ onEvent: (event: StreamEvent) => void;
25
+ signal?: AbortSignal;
26
+ }
27
+ export declare class ModelClient {
28
+ static runStreaming(options: ModelClientOptions): Promise<void>;
29
+ }
30
+ //# sourceMappingURL=ModelClient.d.ts.map
@@ -0,0 +1,84 @@
1
+ /**
2
+ * ModelClient — LLM API client for OpenRouter.
3
+ * Handles streaming and non-streaming calls.
4
+ */
5
+ import { env } from "../core/EnvLoader.js";
6
+ export function resolveModelConfig(registry) {
7
+ const apiKey = env.get("OPENROUTER_API_KEY");
8
+ const defaultModel = env.get("DEFAULT_MODEL");
9
+ return {
10
+ model: defaultModel || registry.resolvePrimary(),
11
+ apiKey: apiKey || "",
12
+ baseUrl: "https://openrouter.ai/api/v1",
13
+ };
14
+ }
15
+ export class ModelClient {
16
+ static async runStreaming(options) {
17
+ const { config, systemPrompt, messages, onEvent, signal } = options;
18
+ const body = {
19
+ model: config.model,
20
+ messages: [
21
+ { role: "system", content: systemPrompt },
22
+ ...messages,
23
+ ],
24
+ stream: true,
25
+ max_tokens: 4096,
26
+ };
27
+ try {
28
+ const response = await fetch(`${config.baseUrl}/chat/completions`, {
29
+ method: "POST",
30
+ headers: {
31
+ "Content-Type": "application/json",
32
+ Authorization: `Bearer ${config.apiKey}`,
33
+ "HTTP-Referer": "https://aiaiaichain.dev",
34
+ "X-Title": "AIAIAI Chain Agent",
35
+ },
36
+ body: JSON.stringify(body),
37
+ signal,
38
+ });
39
+ if (!response.ok) {
40
+ const err = await response.text().catch(() => "unknown error");
41
+ onEvent({ type: "error", message: `API error ${response.status}: ${err}` });
42
+ return;
43
+ }
44
+ const reader = response.body?.getReader();
45
+ if (!reader) {
46
+ onEvent({ type: "error", message: "No response body" });
47
+ return;
48
+ }
49
+ const decoder = new TextDecoder();
50
+ let buffer = "";
51
+ while (true) {
52
+ const { done, value } = await reader.read();
53
+ if (done)
54
+ break;
55
+ buffer += decoder.decode(value, { stream: true });
56
+ const lines = buffer.split("\n");
57
+ buffer = lines.pop() || "";
58
+ for (const line of lines) {
59
+ const trimmed = line.trim();
60
+ if (!trimmed || trimmed === "data: [DONE]")
61
+ continue;
62
+ if (!trimmed.startsWith("data: "))
63
+ continue;
64
+ try {
65
+ const json = JSON.parse(trimmed.slice(6));
66
+ const delta = json.choices?.[0]?.delta?.content;
67
+ if (delta)
68
+ onEvent({ type: "text_delta", text: delta });
69
+ }
70
+ catch { /* skip malformed lines */ }
71
+ }
72
+ }
73
+ onEvent({ type: "turn_done" });
74
+ }
75
+ catch (e) {
76
+ if (e instanceof Error && e.name === "AbortError") {
77
+ onEvent({ type: "turn_done" });
78
+ return;
79
+ }
80
+ onEvent({ type: "error", message: e instanceof Error ? e.message : String(e) });
81
+ }
82
+ }
83
+ }
84
+ //# sourceMappingURL=ModelClient.js.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * SwarmRouter — parallel sub-agent orchestration.
3
+ * Routes tasks to appropriate model tiers for parallel execution.
4
+ */
5
+ import type { Registry } from "../api/Registry.js";
6
+ import type { SessionContext } from "../api/ExtensionAPI.js";
7
+ export interface SubAgentTask {
8
+ id: string;
9
+ prompt: string;
10
+ tier: "orchestrator" | "analyst" | "worker" | "free";
11
+ }
12
+ export interface SubAgentResult {
13
+ taskId: string;
14
+ output: string;
15
+ error?: string;
16
+ }
17
+ export declare class SwarmRouter {
18
+ private registry;
19
+ constructor(registry: Registry);
20
+ runConcurrent(tasks: SubAgentTask[], _sessionCtx: SessionContext): Promise<SubAgentResult[]>;
21
+ private runSingle;
22
+ }
23
+ //# sourceMappingURL=SwarmRouter.d.ts.map