agen-vektor 0.1.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 (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +212 -0
  3. package/dist/agent/agent.js +90 -0
  4. package/dist/agent/context.js +106 -0
  5. package/dist/agent/loop.js +152 -0
  6. package/dist/agent/memory.js +87 -0
  7. package/dist/agent/planner.js +38 -0
  8. package/dist/agent/prompts.js +41 -0
  9. package/dist/agent/session.js +125 -0
  10. package/dist/cli/index.js +304 -0
  11. package/dist/cli/keyboard.js +283 -0
  12. package/dist/config/config.js +92 -0
  13. package/dist/config/credentials.js +127 -0
  14. package/dist/config/models.js +47 -0
  15. package/dist/providers/anthropic.js +276 -0
  16. package/dist/providers/custom.js +24 -0
  17. package/dist/providers/factory.js +58 -0
  18. package/dist/providers/gemini.js +223 -0
  19. package/dist/providers/ollama.js +195 -0
  20. package/dist/providers/openai-compat.js +296 -0
  21. package/dist/providers/openai.js +17 -0
  22. package/dist/providers/openrouter.js +23 -0
  23. package/dist/providers/provider.js +39 -0
  24. package/dist/security/command-policy.js +99 -0
  25. package/dist/security/permissions.js +66 -0
  26. package/dist/tools/filesystem.js +297 -0
  27. package/dist/tools/git.js +66 -0
  28. package/dist/tools/registry.js +39 -0
  29. package/dist/tools/search.js +184 -0
  30. package/dist/tools/shell.js +108 -0
  31. package/dist/tools/web.js +76 -0
  32. package/dist/tui/app.js +913 -0
  33. package/dist/tui/chat.js +76 -0
  34. package/dist/tui/components.js +85 -0
  35. package/dist/tui/diff.js +105 -0
  36. package/dist/tui/input.js +121 -0
  37. package/dist/tui/statusbar.js +32 -0
  38. package/dist/utils/logger.js +96 -0
  39. package/dist/utils/paths.js +90 -0
  40. package/dist/utils/terminal.js +191 -0
  41. package/package.json +56 -0
@@ -0,0 +1,276 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AnthropicProvider = void 0;
4
+ /**
5
+ * Anthropic provider — Messages API with tool use and streaming.
6
+ * https://docs.anthropic.com/en/api/messages
7
+ */
8
+ const provider_1 = require("./provider");
9
+ const credentials_1 = require("../config/credentials");
10
+ const models_1 = require("../config/models");
11
+ const API_URL = 'https://api.anthropic.com/v1/messages';
12
+ const ANTHROPIC_VERSION = '2023-06-01';
13
+ function toAnthropicMessages(messages) {
14
+ const out = [];
15
+ for (const m of messages) {
16
+ if (m.role === 'system')
17
+ continue; // system handled separately
18
+ if (m.role === 'tool') {
19
+ out.push({
20
+ role: 'user',
21
+ content: [
22
+ {
23
+ type: 'tool_result',
24
+ tool_use_id: m.toolCallId,
25
+ content: m.content,
26
+ },
27
+ ],
28
+ });
29
+ continue;
30
+ }
31
+ if (m.toolCalls && m.toolCalls.length > 0) {
32
+ const content = [];
33
+ if (m.content)
34
+ content.push({ type: 'text', text: m.content });
35
+ for (const tc of m.toolCalls) {
36
+ content.push({
37
+ type: 'tool_use',
38
+ id: tc.id,
39
+ name: tc.name,
40
+ input: safeParse(tc.arguments),
41
+ });
42
+ }
43
+ out.push({ role: 'assistant', content });
44
+ continue;
45
+ }
46
+ out.push({ role: m.role, content: m.content });
47
+ }
48
+ return out;
49
+ }
50
+ function toToolDefs(tools) {
51
+ return tools.map((t) => ({
52
+ name: t.name,
53
+ description: t.description,
54
+ input_schema: t.parameters,
55
+ }));
56
+ }
57
+ function safeParse(s) {
58
+ try {
59
+ return JSON.parse(s);
60
+ }
61
+ catch {
62
+ return { _raw: s };
63
+ }
64
+ }
65
+ class AnthropicProvider {
66
+ id = 'anthropic';
67
+ name = 'Anthropic';
68
+ apiKey;
69
+ constructor(apiKey) {
70
+ this.apiKey = apiKey !== undefined ? apiKey : (0, credentials_1.getApiKey)('anthropic');
71
+ }
72
+ capabilities() {
73
+ return { streaming: true, tools: true, contextWindow: 200000 };
74
+ }
75
+ headers() {
76
+ return {
77
+ 'Content-Type': 'application/json',
78
+ 'x-api-key': this.apiKey || '',
79
+ 'anthropic-version': ANTHROPIC_VERSION,
80
+ };
81
+ }
82
+ buildBody(params, stream) {
83
+ const system = params.messages
84
+ .filter((m) => m.role === 'system')
85
+ .map((m) => m.content)
86
+ .join('\n\n');
87
+ const body = {
88
+ model: params.model,
89
+ max_tokens: params.maxTokens || 8192,
90
+ messages: toAnthropicMessages(params.messages),
91
+ stream,
92
+ };
93
+ if (system)
94
+ body.system = system;
95
+ if (params.tools && params.tools.length > 0)
96
+ body.tools = toToolDefs(params.tools);
97
+ if (params.temperature !== undefined)
98
+ body.temperature = params.temperature;
99
+ return body;
100
+ }
101
+ async post(body, signal) {
102
+ const controller = new AbortController();
103
+ const timer = setTimeout(() => controller.abort(), 180_000);
104
+ const onOuterAbort = () => controller.abort();
105
+ if (signal) {
106
+ if (signal.aborted)
107
+ controller.abort();
108
+ else
109
+ signal.addEventListener('abort', onOuterAbort);
110
+ }
111
+ try {
112
+ const res = await fetch(API_URL, {
113
+ method: 'POST',
114
+ headers: this.headers(),
115
+ body: JSON.stringify(body),
116
+ signal: controller.signal,
117
+ });
118
+ const text = await res.text();
119
+ if (!res.ok) {
120
+ const detail = safeJson(text);
121
+ const errObj = (detail && detail.error);
122
+ throw new provider_1.ProviderError(errObj?.message || `HTTP ${res.status}: ${text.slice(0, 300)}`, res.status, res.status >= 500 || res.status === 429);
123
+ }
124
+ return safeJson(text) || {};
125
+ }
126
+ finally {
127
+ clearTimeout(timer);
128
+ if (signal)
129
+ signal.removeEventListener('abort', onOuterAbort);
130
+ }
131
+ }
132
+ async chat(params) {
133
+ const data = await this.post(this.buildBody(params, false), params.signal);
134
+ const content = data.content || [];
135
+ let text = '';
136
+ const toolCalls = [];
137
+ for (const block of content) {
138
+ if (block.type === 'text')
139
+ text += String(block.text || '');
140
+ if (block.type === 'tool_use') {
141
+ toolCalls.push({
142
+ id: String(block.id),
143
+ name: String(block.name),
144
+ arguments: JSON.stringify(block.input ?? {}),
145
+ });
146
+ }
147
+ }
148
+ const usage = data.usage;
149
+ return {
150
+ content: text,
151
+ toolCalls,
152
+ stopReason: String(data.stop_reason || 'end_turn'),
153
+ usage: usage
154
+ ? { inputTokens: usage.input_tokens, outputTokens: usage.output_tokens }
155
+ : undefined,
156
+ model: String(data.model || params.model),
157
+ };
158
+ }
159
+ async *stream(params) {
160
+ const controller = new AbortController();
161
+ const timer = setTimeout(() => controller.abort(), 180_000);
162
+ const onOuterAbort = () => controller.abort();
163
+ if (params.signal) {
164
+ if (params.signal.aborted)
165
+ controller.abort();
166
+ else
167
+ params.signal.addEventListener('abort', onOuterAbort);
168
+ }
169
+ try {
170
+ const res = await fetch(API_URL, {
171
+ method: 'POST',
172
+ headers: this.headers(),
173
+ body: JSON.stringify(this.buildBody(params, true)),
174
+ signal: controller.signal,
175
+ });
176
+ if (!res.ok || !res.body) {
177
+ const text = await res.text().catch(() => '');
178
+ throw new provider_1.ProviderError(`HTTP ${res.status}: ${text.slice(0, 200)}`, res.status, res.status >= 500);
179
+ }
180
+ const reader = res.body.getReader();
181
+ const decoder = new TextDecoder();
182
+ let buffer = '';
183
+ let content = '';
184
+ const toolCalls = [];
185
+ let currentTool = null;
186
+ for (;;) {
187
+ const { done, value } = await reader.read();
188
+ if (done)
189
+ break;
190
+ buffer += decoder.decode(value, { stream: true });
191
+ const lines = buffer.split('\n');
192
+ buffer = lines.pop() || '';
193
+ // Events arrive as pairs: `event: X` then `data: {...}`
194
+ for (let i = 0; i < lines.length; i++) {
195
+ const trimmed = lines[i].trim();
196
+ if (!trimmed.startsWith('event:'))
197
+ continue;
198
+ const evtName = trimmed.slice(6).trim();
199
+ const dataLine = lines[i + 1];
200
+ if (!dataLine)
201
+ continue;
202
+ const json = safeJson(dataLine.replace(/^data:\s*/, '').trim());
203
+ if (!json)
204
+ continue;
205
+ const contentBlock = json.content_block;
206
+ if (evtName === 'content_block_start' && contentBlock?.type === 'tool_use') {
207
+ currentTool = {
208
+ id: String(contentBlock.id || ''),
209
+ name: String(contentBlock.name || ''),
210
+ arguments: '',
211
+ };
212
+ }
213
+ else if (evtName === 'content_block_delta') {
214
+ const delta = json.delta;
215
+ if (delta?.type === 'text_delta' && typeof delta.text === 'string') {
216
+ content += delta.text;
217
+ yield { type: 'delta', delta: delta.text };
218
+ }
219
+ else if (delta?.type === 'input_json_delta' &&
220
+ currentTool &&
221
+ typeof delta.partial_json === 'string') {
222
+ currentTool.arguments += delta.partial_json;
223
+ }
224
+ }
225
+ else if (evtName === 'content_block_stop') {
226
+ if (currentTool && currentTool.name) {
227
+ toolCalls.push(currentTool);
228
+ yield { type: 'tool_call', toolCall: currentTool };
229
+ }
230
+ currentTool = null;
231
+ }
232
+ else if (evtName === 'message_stop') {
233
+ if (currentTool && currentTool.name) {
234
+ toolCalls.push(currentTool);
235
+ yield { type: 'tool_call', toolCall: currentTool };
236
+ }
237
+ currentTool = null;
238
+ yield {
239
+ type: 'done',
240
+ result: {
241
+ content,
242
+ toolCalls,
243
+ stopReason: toolCalls.length > 0 ? 'tool_calls' : 'end_turn',
244
+ model: params.model,
245
+ },
246
+ };
247
+ return;
248
+ }
249
+ }
250
+ }
251
+ if (currentTool && currentTool.name)
252
+ toolCalls.push(currentTool);
253
+ yield {
254
+ type: 'done',
255
+ result: { content, toolCalls, stopReason: 'end_turn', model: params.model },
256
+ };
257
+ }
258
+ finally {
259
+ clearTimeout(timer);
260
+ if (params.signal)
261
+ params.signal.removeEventListener('abort', onOuterAbort);
262
+ }
263
+ }
264
+ async models() {
265
+ return (0, models_1.modelsForProvider)(this.id).map((m) => m.id);
266
+ }
267
+ }
268
+ exports.AnthropicProvider = AnthropicProvider;
269
+ function safeJson(text) {
270
+ try {
271
+ return JSON.parse(text);
272
+ }
273
+ catch {
274
+ return null;
275
+ }
276
+ }
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CustomProvider = void 0;
4
+ const openai_compat_1 = require("./openai-compat");
5
+ /**
6
+ * Custom provider — any OpenAI-compatible endpoint.
7
+ * Other protocols are documented but not fully implemented;
8
+ * the adapter clearly reports its limitation.
9
+ */
10
+ class CustomProvider extends openai_compat_1.OpenAICompatProvider {
11
+ protocol;
12
+ constructor(cfg) {
13
+ super({
14
+ id: 'custom',
15
+ name: cfg.name || 'Custom',
16
+ baseUrl: cfg.baseUrl,
17
+ apiKey: cfg.apiKey !== undefined ? cfg.apiKey : null,
18
+ defaultModel: cfg.model || 'default',
19
+ contextWindow: 128000,
20
+ });
21
+ this.protocol = cfg.protocol;
22
+ }
23
+ }
24
+ exports.CustomProvider = CustomProvider;
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PROVIDER_IDS = void 0;
4
+ exports.providerDisplayNames = providerDisplayNames;
5
+ exports.createProvider = createProvider;
6
+ exports.providerRequiresKey = providerRequiresKey;
7
+ const openai_1 = require("./openai");
8
+ const anthropic_1 = require("./anthropic");
9
+ const gemini_1 = require("./gemini");
10
+ const openrouter_1 = require("./openrouter");
11
+ const ollama_1 = require("./ollama");
12
+ const custom_1 = require("./custom");
13
+ const credentials_1 = require("../config/credentials");
14
+ exports.PROVIDER_IDS = ['openai', 'anthropic', 'gemini', 'openrouter', 'ollama', 'custom'];
15
+ function providerDisplayNames() {
16
+ return [
17
+ { id: 'openai', name: 'OpenAI' },
18
+ { id: 'anthropic', name: 'Anthropic' },
19
+ { id: 'gemini', name: 'Gemini' },
20
+ { id: 'openrouter', name: 'OpenRouter' },
21
+ { id: 'ollama', name: 'Ollama' },
22
+ { id: 'custom', name: 'Custom' },
23
+ ];
24
+ }
25
+ function createProvider(config) {
26
+ switch (config.provider) {
27
+ case 'openai':
28
+ return new openai_1.OpenAIProvider();
29
+ case 'anthropic':
30
+ return new anthropic_1.AnthropicProvider();
31
+ case 'gemini':
32
+ return new gemini_1.GeminiProvider();
33
+ case 'openrouter':
34
+ return new openrouter_1.OpenRouterProvider();
35
+ case 'ollama':
36
+ return new ollama_1.OllamaProvider();
37
+ case 'custom':
38
+ return new custom_1.CustomProvider({
39
+ name: config.customName || 'Custom',
40
+ baseUrl: config.apiUrl || process.env.VECTOR_API_URL || '',
41
+ apiKey: (0, credentials_1.getApiKey)('custom'),
42
+ model: config.model,
43
+ protocol: config.protocol || 'openai',
44
+ });
45
+ default:
46
+ // Unknown provider id: treat as OpenAI-compatible with the given URL
47
+ return new custom_1.CustomProvider({
48
+ name: config.provider,
49
+ baseUrl: config.apiUrl || process.env.VECTOR_API_URL || '',
50
+ apiKey: (0, credentials_1.getApiKey)('custom'),
51
+ model: config.model,
52
+ protocol: 'openai',
53
+ });
54
+ }
55
+ }
56
+ function providerRequiresKey(id) {
57
+ return id !== 'ollama';
58
+ }
@@ -0,0 +1,223 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GeminiProvider = void 0;
4
+ /**
5
+ * Gemini provider — generateContent API with function calling + streaming.
6
+ * https://ai.google.dev/api/generate-content
7
+ */
8
+ const provider_1 = require("./provider");
9
+ const credentials_1 = require("../config/credentials");
10
+ const models_1 = require("../config/models");
11
+ const BASE = 'https://generativelanguage.googleapis.com/v1beta';
12
+ function toGeminiMessages(messages) {
13
+ const out = [];
14
+ let pendingToolResults = [];
15
+ const flush = () => {
16
+ if (pendingToolResults.length > 0) {
17
+ out.push({ role: 'user', parts: pendingToolResults });
18
+ pendingToolResults = [];
19
+ }
20
+ };
21
+ for (const m of messages) {
22
+ if (m.role === 'system')
23
+ continue; // handled via system_instruction
24
+ if (m.role === 'tool') {
25
+ pendingToolResults.push({
26
+ functionResponse: {
27
+ name: m.name,
28
+ response: { result: m.content },
29
+ },
30
+ });
31
+ continue;
32
+ }
33
+ flush();
34
+ const parts = [];
35
+ if (m.content)
36
+ parts.push({ text: m.content });
37
+ if (m.toolCalls && m.toolCalls.length > 0) {
38
+ parts.push({
39
+ functionCall: m.toolCalls.map((tc) => ({
40
+ name: tc.name,
41
+ args: safeParse(tc.arguments),
42
+ })),
43
+ });
44
+ }
45
+ out.push({ role: m.role === 'assistant' ? 'model' : 'user', parts });
46
+ }
47
+ flush();
48
+ return out;
49
+ }
50
+ function toToolDefs(tools) {
51
+ return tools.map((t) => ({
52
+ functionDeclarations: [
53
+ {
54
+ name: t.name,
55
+ description: t.description,
56
+ parameters: t.parameters,
57
+ },
58
+ ],
59
+ }));
60
+ }
61
+ function safeParse(s) {
62
+ try {
63
+ return JSON.parse(s);
64
+ }
65
+ catch {
66
+ return { _raw: s };
67
+ }
68
+ }
69
+ function extractResult(data) {
70
+ const candidates = data.candidates || [];
71
+ const content0 = candidates[0]?.content || {};
72
+ const parts = content0.parts || [];
73
+ let content = '';
74
+ const toolCalls = [];
75
+ for (const part of parts) {
76
+ if (typeof part.text === 'string')
77
+ content += part.text;
78
+ if (part.functionCall) {
79
+ const raw = part.functionCall;
80
+ const fcs = Array.isArray(raw) ? raw : [raw];
81
+ for (const fc of fcs) {
82
+ toolCalls.push({
83
+ id: `gemini_${Math.random().toString(36).slice(2)}`,
84
+ name: String(fc.name || ''),
85
+ arguments: JSON.stringify(fc.args ?? {}),
86
+ });
87
+ }
88
+ }
89
+ }
90
+ return { content, toolCalls };
91
+ }
92
+ class GeminiProvider {
93
+ id = 'gemini';
94
+ name = 'Gemini';
95
+ apiKey;
96
+ constructor(apiKey) {
97
+ this.apiKey = apiKey !== undefined ? apiKey : (0, credentials_1.getApiKey)('gemini');
98
+ }
99
+ capabilities() {
100
+ return { streaming: true, tools: true, contextWindow: 1048576 };
101
+ }
102
+ buildBody(params, stream) {
103
+ const system = params.messages
104
+ .filter((m) => m.role === 'system')
105
+ .map((m) => m.content)
106
+ .join('\n\n');
107
+ const body = {
108
+ contents: toGeminiMessages(params.messages),
109
+ };
110
+ if (system)
111
+ body.systemInstruction = { parts: [{ text: system }] };
112
+ if (params.tools && params.tools.length > 0)
113
+ body.tools = toToolDefs(params.tools);
114
+ if (params.temperature !== undefined)
115
+ body.generationConfig = { temperature: params.temperature };
116
+ if (stream)
117
+ body.generationConfig = { ...body.generationConfig, responseModalities: ['TEXT'] };
118
+ return body;
119
+ }
120
+ url(params, stream) {
121
+ const key = this.apiKey || '';
122
+ const action = stream ? 'streamGenerateContent' : 'generateContent';
123
+ const alt = stream ? '&alt=sse' : '';
124
+ return `${BASE}/models/${params.model}:${action}?key=${encodeURIComponent(key)}${alt}`;
125
+ }
126
+ async post(params, stream) {
127
+ const controller = new AbortController();
128
+ const timer = setTimeout(() => controller.abort(), 180_000);
129
+ const onOuterAbort = () => controller.abort();
130
+ if (params.signal) {
131
+ if (params.signal.aborted)
132
+ controller.abort();
133
+ else
134
+ params.signal.addEventListener('abort', onOuterAbort);
135
+ }
136
+ const res = await fetch(this.url(params, stream), {
137
+ method: 'POST',
138
+ headers: { 'Content-Type': 'application/json' },
139
+ body: JSON.stringify(this.buildBody(params, stream)),
140
+ signal: controller.signal,
141
+ });
142
+ clearTimeout(timer);
143
+ if (params.signal)
144
+ params.signal.removeEventListener('abort', onOuterAbort);
145
+ return { res };
146
+ }
147
+ async chat(params) {
148
+ const { res } = await this.post(params, false);
149
+ const text = await res.text();
150
+ if (!res.ok) {
151
+ throw new provider_1.ProviderError(`Gemini HTTP ${res.status}: ${text.slice(0, 300)}`, res.status, res.status >= 500);
152
+ }
153
+ const data = JSON.parse(text);
154
+ const { content, toolCalls } = extractResult(data);
155
+ return {
156
+ content,
157
+ toolCalls,
158
+ stopReason: toolCalls.length > 0 ? 'tool_calls' : 'stop',
159
+ model: params.model,
160
+ };
161
+ }
162
+ async *stream(params) {
163
+ const { res } = await this.post(params, true);
164
+ if (!res.ok || !res.body) {
165
+ const text = await res.text().catch(() => '');
166
+ throw new provider_1.ProviderError(`Gemini HTTP ${res.status}: ${text.slice(0, 200)}`, res.status, res.status >= 500);
167
+ }
168
+ const reader = res.body.getReader();
169
+ const decoder = new TextDecoder();
170
+ let buffer = '';
171
+ let content = '';
172
+ const toolCalls = [];
173
+ for (;;) {
174
+ const { done, value } = await reader.read();
175
+ if (done)
176
+ break;
177
+ buffer += decoder.decode(value, { stream: true });
178
+ const lines = buffer.split('\n');
179
+ buffer = lines.pop() || '';
180
+ for (const line of lines) {
181
+ const trimmed = line.trim();
182
+ if (!trimmed.startsWith('data:'))
183
+ continue;
184
+ const payload = trimmed.slice(5).trim();
185
+ if (!payload)
186
+ continue;
187
+ const json = safeJson(payload);
188
+ if (!json)
189
+ continue;
190
+ const { content: deltaText, toolCalls: newCalls } = extractResult(json);
191
+ if (deltaText) {
192
+ content += deltaText;
193
+ yield { type: 'delta', delta: deltaText };
194
+ }
195
+ for (const tc of newCalls) {
196
+ toolCalls.push(tc);
197
+ yield { type: 'tool_call', toolCall: tc };
198
+ }
199
+ }
200
+ }
201
+ yield {
202
+ type: 'done',
203
+ result: {
204
+ content,
205
+ toolCalls,
206
+ stopReason: toolCalls.length > 0 ? 'tool_calls' : 'stop',
207
+ model: params.model,
208
+ },
209
+ };
210
+ }
211
+ async models() {
212
+ return (0, models_1.modelsForProvider)(this.id).map((m) => m.id);
213
+ }
214
+ }
215
+ exports.GeminiProvider = GeminiProvider;
216
+ function safeJson(text) {
217
+ try {
218
+ return JSON.parse(text);
219
+ }
220
+ catch {
221
+ return null;
222
+ }
223
+ }