apteva 0.1.8 → 0.2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apteva",
3
- "version": "0.1.8",
3
+ "version": "0.2.1",
4
4
  "description": "Run AI agents locally. Multi-provider support for Claude, GPT, Gemini, Llama, and more.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -63,5 +63,12 @@
63
63
  "@apteva/apteva-kit": "^0.1.102",
64
64
  "react": "^18.2.0",
65
65
  "react-dom": "^18.2.0"
66
+ },
67
+ "optionalDependencies": {
68
+ "@apteva/agent-darwin-arm64": "^0.2.0",
69
+ "@apteva/agent-darwin-x64": "^0.2.0",
70
+ "@apteva/agent-linux-x64": "^0.2.0",
71
+ "@apteva/agent-linux-arm64": "^0.2.0",
72
+ "@apteva/agent-win32-x64": "^0.2.0"
66
73
  }
67
74
  }
package/src/binary.ts CHANGED
@@ -3,9 +3,10 @@ import { existsSync, mkdirSync, chmodSync } from "fs";
3
3
 
4
4
  // Binary configuration
5
5
  const BINARY_BASE_URL = "https://github.com/apteva/agent/releases/latest/download";
6
- const DOWNLOAD_TIMEOUT = 60000; // 60 seconds
6
+ const CONNECT_TIMEOUT = 15000; // 15 seconds for initial connection
7
+ const DOWNLOAD_TIMEOUT = 120000; // 120 seconds for full download
7
8
  const MAX_RETRIES = 3;
8
- const RETRY_DELAY = 2000; // 2 seconds
9
+ const RETRY_DELAY = 1000; // 1 second between retries
9
10
 
10
11
  // ANSI colors for console output
11
12
  const c = {
@@ -16,12 +17,56 @@ const c = {
16
17
  red: "\x1b[38;5;196m",
17
18
  };
18
19
 
19
- // Determine platform and architecture
20
+ // Map Node.js platform/arch to npm package names
21
+ function getNpmPackageName(): string {
22
+ const platform = process.platform; // darwin, linux, win32
23
+ const arch = process.arch; // x64, arm64
24
+
25
+ // Map to our package naming convention
26
+ const archMap: Record<string, string> = {
27
+ x64: "x64",
28
+ arm64: "arm64",
29
+ };
30
+
31
+ const mappedArch = archMap[arch] || arch;
32
+ return `@apteva/agent-${platform}-${mappedArch}`;
33
+ }
34
+
35
+ // Try to find binary from installed npm package
36
+ function findNpmBinary(): string | null {
37
+ const packageName = getNpmPackageName();
38
+
39
+ try {
40
+ // Try to require the package - it exports the binary path
41
+ const binaryPath = require(packageName);
42
+ if (existsSync(binaryPath)) {
43
+ return binaryPath;
44
+ }
45
+ } catch {
46
+ // Package not installed, fall through
47
+ }
48
+
49
+ // Also try direct path resolution in node_modules
50
+ const possiblePaths = [
51
+ join(import.meta.dir, "../../node_modules", packageName, process.platform === "win32" ? "agent.exe" : "agent"),
52
+ join(process.cwd(), "node_modules", packageName, process.platform === "win32" ? "agent.exe" : "agent"),
53
+ ];
54
+
55
+ for (const p of possiblePaths) {
56
+ if (existsSync(p)) {
57
+ return p;
58
+ }
59
+ }
60
+
61
+ return null;
62
+ }
63
+
64
+ // Determine platform and architecture for download fallback
20
65
  function getPlatformInfo(): { platform: string; arch: string; ext: string } {
21
66
  const platform = process.platform === "win32" ? "windows" : process.platform;
22
67
  let arch = process.arch;
23
68
 
24
- // Normalize architecture names
69
+ // Normalize architecture names for GitHub releases
25
70
  if (arch === "x64") arch = "amd64";
26
71
  if (arch === "arm64") arch = "arm64";
27
72
 
@@ -30,13 +75,13 @@ function getPlatformInfo(): { platform: string; arch: string; ext: string } {
30
75
  return { platform, arch, ext };
31
76
  }
32
77
 
33
- // Get binary filename for current platform
78
+ // Get binary filename for current platform (for download)
34
79
  export function getBinaryFilename(): string {
35
80
  const { platform, arch, ext } = getPlatformInfo();
36
81
  return `agent-${platform}-${arch}${ext}`;
37
82
  }
38
83
 
39
- // Get full binary path
84
+ // Get full binary path in bin directory
40
85
  export function getBinaryPath(binDir: string): string {
41
86
  return join(binDir, getBinaryFilename());
42
87
  }
@@ -47,31 +92,78 @@ function getDownloadUrl(): string {
47
92
  return `${BINARY_BASE_URL}/${filename}`;
48
93
  }
49
94
 
50
- // Check if binary exists
95
+ // Check if binary exists (either from npm or downloaded)
51
96
  export function binaryExists(binDir: string): boolean {
97
+ // First check npm package
98
+ const npmBinary = findNpmBinary();
99
+ if (npmBinary) return true;
100
+
101
+ // Then check downloaded binary
52
102
  return existsSync(getBinaryPath(binDir));
53
103
  }
54
104
 
105
+ // Get the actual binary path (npm or downloaded)
106
+ export function getActualBinaryPath(binDir: string): string | null {
107
+ // First check npm package
108
+ const npmBinary = findNpmBinary();
109
+ if (npmBinary) return npmBinary;
110
+
111
+ // Then check downloaded binary
112
+ const downloadedPath = getBinaryPath(binDir);
113
+ if (existsSync(downloadedPath)) return downloadedPath;
114
+
115
+ return null;
116
+ }
117
+
55
118
  // Helper to delay
56
119
  const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
57
120
 
58
- // Download with timeout
59
- async function fetchWithTimeout(url: string, timeout: number): Promise<Response> {
121
+ // Download file with timeout for both connection and body
122
+ async function downloadWithTimeout(url: string): Promise<ArrayBuffer> {
60
123
  const controller = new AbortController();
61
- const timeoutId = setTimeout(() => controller.abort(), timeout);
124
+
125
+ // Timeout for connection
126
+ let timeoutId = setTimeout(() => controller.abort(), CONNECT_TIMEOUT);
127
+
128
+ const response = await fetch(url, { signal: controller.signal });
129
+ clearTimeout(timeoutId);
130
+
131
+ if (!response.ok) {
132
+ throw new Error(`HTTP ${response.status}`);
133
+ }
134
+
135
+ // Timeout for body download
136
+ timeoutId = setTimeout(() => controller.abort(), DOWNLOAD_TIMEOUT);
62
137
 
63
138
  try {
64
- const response = await fetch(url, { signal: controller.signal });
139
+ const arrayBuffer = await response.arrayBuffer();
65
140
  clearTimeout(timeoutId);
66
- return response;
141
+ return arrayBuffer;
67
142
  } catch (err) {
68
143
  clearTimeout(timeoutId);
69
144
  throw err;
70
145
  }
71
146
  }
72
147
 
73
- // Download binary if missing
74
- export async function ensureBinary(binDir: string, silent = false): Promise<{ success: boolean; path: string; error?: string; downloaded?: boolean }> {
148
+ // Ensure binary exists - check npm first, then download
149
+ export async function ensureBinary(binDir: string, silent = false): Promise<{
150
+ success: boolean;
151
+ path: string;
152
+ error?: string;
153
+ downloaded?: boolean;
154
+ source?: "npm" | "download" | "cached";
155
+ }> {
156
+ // First, check if binary is available from npm package
157
+ const npmBinary = findNpmBinary();
158
+ if (npmBinary) {
159
+ return {
160
+ success: true,
161
+ path: npmBinary,
162
+ downloaded: false,
163
+ source: "npm"
164
+ };
165
+ }
166
+
75
167
  const binaryPath = getBinaryPath(binDir);
76
168
 
77
169
  // Ensure bin directory exists
@@ -79,82 +171,26 @@ export async function ensureBinary(binDir: string, silent = false): Promise<{ su
79
171
  mkdirSync(binDir, { recursive: true });
80
172
  }
81
173
 
82
- // Check if already exists
174
+ // Check if already downloaded
83
175
  if (existsSync(binaryPath)) {
84
- return { success: true, path: binaryPath, downloaded: false };
85
- }
86
-
87
- const url = getDownloadUrl();
88
- const filename = getBinaryFilename();
89
-
90
- // Show downloading message (server.ts already printed "Binary ")
91
- if (!silent) {
92
- process.stdout.write(`${c.orange}downloading...${c.reset}`);
93
- }
94
-
95
- let lastError: string = "";
96
-
97
- for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
98
- try {
99
- if (!silent && attempt > 1) {
100
- // Clear line and show retry status
101
- process.stdout.write(`\r Agent ${c.orange}retry ${attempt}/${MAX_RETRIES}...${c.reset} `);
102
- }
103
-
104
- const response = await fetchWithTimeout(url, DOWNLOAD_TIMEOUT);
105
-
106
- if (!response.ok) {
107
- if (response.status === 404) {
108
- if (!silent) {
109
- console.log(`\r${c.red}not found${c.reset} `);
110
- }
111
- return {
112
- success: false,
113
- path: binaryPath,
114
- error: `Binary not found at ${url}`,
115
- };
116
- }
117
- lastError = `HTTP ${response.status}`;
118
- if (attempt < MAX_RETRIES) {
119
- await sleep(RETRY_DELAY);
120
- continue;
121
- }
122
- } else {
123
- // Download the binary
124
- const arrayBuffer = await response.arrayBuffer();
125
- await Bun.write(binaryPath, arrayBuffer);
126
-
127
- // Make executable on Unix systems
128
- if (process.platform !== "win32") {
129
- chmodSync(binaryPath, 0o755);
130
- }
131
-
132
- // Show success
133
- if (!silent) {
134
- const sizeMB = (arrayBuffer.byteLength / 1024 / 1024).toFixed(1);
135
- console.log(`\r${c.green}binary ready${c.reset} ${c.gray}(${sizeMB}MB downloaded)${c.reset} `);
136
- }
137
-
138
- return { success: true, path: binaryPath, downloaded: true };
139
- }
140
- } catch (err: any) {
141
- lastError = err.name === "AbortError" ? "timeout" : String(err.message || err);
142
- if (attempt < MAX_RETRIES) {
143
- await sleep(RETRY_DELAY);
144
- continue;
145
- }
146
- }
176
+ return {
177
+ success: true,
178
+ path: binaryPath,
179
+ downloaded: false,
180
+ source: "cached"
181
+ };
147
182
  }
148
183
 
149
- // All retries failed
184
+ // No npm package and no cached binary - show error
150
185
  if (!silent) {
151
- console.log(`\r${c.red}failed${c.reset} ${c.gray}(${lastError})${c.reset} `);
186
+ console.log(`${c.red}not found${c.reset}`);
187
+ console.log(`\n Install the agent binary: npm install @apteva/agent-linux-x64`);
152
188
  }
153
189
 
154
190
  return {
155
191
  success: false,
156
192
  path: binaryPath,
157
- error: `Failed after ${MAX_RETRIES} attempts: ${lastError}`,
193
+ error: "Binary not found. Install via: npm install @apteva/agent-<platform>",
158
194
  };
159
195
  }
160
196
 
@@ -166,16 +202,45 @@ export function getBinaryStatus(binDir: string): {
166
202
  downloadUrl: string;
167
203
  platform: string;
168
204
  arch: string;
205
+ source?: "npm" | "download" | "none";
169
206
  } {
170
207
  const { platform, arch } = getPlatformInfo();
171
- const path = getBinaryPath(binDir);
208
+
209
+ // Check npm first
210
+ const npmBinary = findNpmBinary();
211
+ if (npmBinary) {
212
+ return {
213
+ exists: true,
214
+ path: npmBinary,
215
+ filename: getBinaryFilename(),
216
+ downloadUrl: getDownloadUrl(),
217
+ platform,
218
+ arch,
219
+ source: "npm",
220
+ };
221
+ }
222
+
223
+ // Check downloaded
224
+ const downloadedPath = getBinaryPath(binDir);
225
+ if (existsSync(downloadedPath)) {
226
+ return {
227
+ exists: true,
228
+ path: downloadedPath,
229
+ filename: getBinaryFilename(),
230
+ downloadUrl: getDownloadUrl(),
231
+ platform,
232
+ arch,
233
+ source: "download",
234
+ };
235
+ }
172
236
 
173
237
  return {
174
- exists: existsSync(path),
175
- path,
238
+ exists: false,
239
+ path: downloadedPath,
176
240
  filename: getBinaryFilename(),
177
241
  downloadUrl: getDownloadUrl(),
178
242
  platform,
179
243
  arch,
244
+ source: "none",
180
245
  };
181
246
  }
package/src/providers.ts CHANGED
@@ -6,91 +6,61 @@ export const PROVIDERS = {
6
6
  anthropic: {
7
7
  id: "anthropic",
8
8
  name: "Anthropic",
9
- displayName: "Anthropic (Claude)",
9
+ displayName: "Anthropic",
10
10
  envVar: "ANTHROPIC_API_KEY",
11
11
  docsUrl: "https://console.anthropic.com/settings/keys",
12
12
  testEndpoint: "https://api.anthropic.com/v1/messages",
13
13
  models: [
14
- { value: "claude-sonnet-4-20250514", label: "Claude Sonnet 4", recommended: true },
15
- { value: "claude-opus-4-20250514", label: "Claude Opus 4" },
16
- { value: "claude-4-5-sonnet", label: "Claude 4.5 Sonnet" },
17
- { value: "claude-4-5-haiku", label: "Claude 4.5 Haiku (Fast)" },
14
+ { value: "claude-sonnet-4-5", label: "Claude Sonnet 4.5", recommended: true },
15
+ { value: "claude-haiku-4-5", label: "Claude Haiku 4.5 (Fast)" },
18
16
  ],
19
17
  },
20
18
  openai: {
21
19
  id: "openai",
22
20
  name: "OpenAI",
23
- displayName: "OpenAI (GPT)",
21
+ displayName: "OpenAI",
24
22
  envVar: "OPENAI_API_KEY",
25
23
  docsUrl: "https://platform.openai.com/api-keys",
26
24
  testEndpoint: "https://api.openai.com/v1/models",
27
25
  models: [
28
26
  { value: "gpt-4o", label: "GPT-4o", recommended: true },
29
27
  { value: "gpt-4o-mini", label: "GPT-4o Mini (Fast)" },
30
- { value: "gpt-4-turbo", label: "GPT-4 Turbo" },
31
28
  ],
32
29
  },
33
30
  groq: {
34
31
  id: "groq",
35
32
  name: "Groq",
36
- displayName: "Groq (Ultra-fast)",
33
+ displayName: "Groq",
37
34
  envVar: "GROQ_API_KEY",
38
35
  docsUrl: "https://console.groq.com/keys",
39
36
  testEndpoint: "https://api.groq.com/openai/v1/models",
40
37
  models: [
41
38
  { value: "llama-3.3-70b-versatile", label: "Llama 3.3 70B", recommended: true },
42
- { value: "llama-3.1-8b-instant", label: "Llama 3.1 8B (Instant)" },
43
- { value: "mixtral-8x7b-32768", label: "Mixtral 8x7B" },
39
+ { value: "llama-3.1-8b-instant", label: "Llama 3.1 8B (Fast)" },
44
40
  ],
45
41
  },
46
42
  gemini: {
47
43
  id: "gemini",
48
44
  name: "Google",
49
- displayName: "Google (Gemini)",
45
+ displayName: "Google Gemini",
50
46
  envVar: "GEMINI_API_KEY",
51
47
  docsUrl: "https://aistudio.google.com/app/apikey",
52
48
  testEndpoint: "https://generativelanguage.googleapis.com/v1/models",
53
49
  models: [
54
- { value: "gemini-2.0-flash", label: "Gemini 2.0 Flash", recommended: true },
55
- { value: "gemini-1.5-pro", label: "Gemini 1.5 Pro" },
56
- { value: "gemini-1.5-flash", label: "Gemini 1.5 Flash" },
57
- ],
58
- },
59
- fireworks: {
60
- id: "fireworks",
61
- name: "Fireworks",
62
- displayName: "Fireworks AI",
63
- envVar: "FIREWORKS_API_KEY",
64
- docsUrl: "https://fireworks.ai/api-keys",
65
- testEndpoint: "https://api.fireworks.ai/inference/v1/models",
66
- models: [
67
- { value: "accounts/fireworks/models/llama-v3p3-70b-instruct", label: "Llama 3.3 70B", recommended: true },
68
- { value: "accounts/fireworks/models/deepseek-v3", label: "DeepSeek V3" },
69
- { value: "accounts/fireworks/models/qwen2p5-72b-instruct", label: "Qwen 2.5 72B" },
50
+ { value: "gemini-3-pro-preview", label: "Gemini 3 Pro Preview (Latest)", recommended: true },
51
+ { value: "gemini-3-flash-preview", label: "Gemini 3 Flash Preview (Fast)" },
70
52
  ],
71
53
  },
72
54
  xai: {
73
55
  id: "xai",
74
56
  name: "xAI",
75
- displayName: "xAI (Grok)",
57
+ displayName: "xAI Grok",
76
58
  envVar: "XAI_API_KEY",
77
59
  docsUrl: "https://console.x.ai/",
78
60
  testEndpoint: "https://api.x.ai/v1/models",
79
61
  models: [
80
- { value: "grok-2-latest", label: "Grok 2", recommended: true },
81
- { value: "grok-beta", label: "Grok Beta" },
82
- ],
83
- },
84
- moonshot: {
85
- id: "moonshot",
86
- name: "Moonshot",
87
- displayName: "Moonshot AI (Kimi)",
88
- envVar: "MOONSHOT_API_KEY",
89
- docsUrl: "https://platform.moonshot.cn/console/api-keys",
90
- testEndpoint: "https://api.moonshot.cn/v1/models",
91
- models: [
92
- { value: "moonshot-v1-128k", label: "Moonshot V1 128K", recommended: true },
93
- { value: "moonshot-v1-32k", label: "Moonshot V1 32K" },
62
+ { value: "grok-2", label: "Grok 2", recommended: true },
63
+ { value: "grok-2-mini", label: "Grok 2 Mini (Fast)" },
94
64
  ],
95
65
  },
96
66
  together: {
@@ -101,20 +71,32 @@ export const PROVIDERS = {
101
71
  docsUrl: "https://api.together.xyz/settings/api-keys",
102
72
  testEndpoint: "https://api.together.xyz/v1/models",
103
73
  models: [
104
- { value: "meta-llama/Llama-3.3-70B-Instruct-Turbo", label: "Llama 3.3 70B", recommended: true },
105
- { value: "deepseek-ai/DeepSeek-R1", label: "DeepSeek R1" },
106
- { value: "deepseek-ai/DeepSeek-V3", label: "DeepSeek V3" },
74
+ { value: "moonshotai/Kimi-K2.5", label: "Kimi K2.5", recommended: true },
75
+ { value: "moonshotai/Kimi-K2-Thinking", label: "Kimi K2 Thinking (Reasoning)" },
107
76
  ],
108
77
  },
109
- venice: {
110
- id: "venice",
111
- name: "Venice",
112
- displayName: "Venice AI",
113
- envVar: "VENICE_API_KEY",
114
- docsUrl: "https://venice.ai/settings/api",
115
- testEndpoint: "https://api.venice.ai/api/v1/models",
78
+ fireworks: {
79
+ id: "fireworks",
80
+ name: "Fireworks",
81
+ displayName: "Fireworks AI",
82
+ envVar: "FIREWORKS_API_KEY",
83
+ docsUrl: "https://fireworks.ai/api-keys",
84
+ testEndpoint: "https://api.fireworks.ai/inference/v1/models",
85
+ models: [
86
+ { value: "accounts/fireworks/models/kimi-k2p5", label: "Kimi K2.5", recommended: true },
87
+ { value: "accounts/fireworks/models/kimi-k2-thinking", label: "Kimi K2 Thinking (Reasoning)" },
88
+ ],
89
+ },
90
+ moonshot: {
91
+ id: "moonshot",
92
+ name: "Moonshot",
93
+ displayName: "Moonshot AI",
94
+ envVar: "MOONSHOT_API_KEY",
95
+ docsUrl: "https://platform.moonshot.cn/console/api-keys",
96
+ testEndpoint: "https://api.moonshot.cn/v1/models",
116
97
  models: [
117
- { value: "llama-3.3-70b", label: "Llama 3.3 70B", recommended: true },
98
+ { value: "moonshot-v1-128k", label: "Kimi 128K", recommended: true },
99
+ { value: "moonshot-v1-32k", label: "Kimi 32K (Fast)" },
118
100
  ],
119
101
  },
120
102
  } as const;
package/src/routes/api.ts CHANGED
@@ -172,8 +172,8 @@ export async function handleApiRequest(req: Request, path: string): Promise<Resp
172
172
  const proc = spawn({
173
173
  cmd: [BINARY_PATH],
174
174
  env,
175
- stdout: "inherit",
176
- stderr: "inherit",
175
+ stdout: "ignore",
176
+ stderr: "ignore",
177
177
  });
178
178
 
179
179
  agentProcesses.set(agent.id, proc);
package/src/server.ts CHANGED
@@ -3,7 +3,7 @@ import { handleApiRequest } from "./routes/api";
3
3
  import { serveStatic } from "./routes/static";
4
4
  import { join } from "path";
5
5
  import { initDatabase, AgentDB, ProviderKeysDB } from "./db";
6
- import { ensureBinary, getBinaryPath, getBinaryStatus } from "./binary";
6
+ import { ensureBinary, getBinaryPath, getBinaryStatus, getActualBinaryPath } from "./binary";
7
7
 
8
8
  const PORT = parseInt(process.env.PORT || "4280");
9
9
  const DATA_DIR = process.env.DATA_DIR || join(import.meta.dir, "../data");
@@ -31,8 +31,18 @@ AgentDB.resetAllStatus();
31
31
  // In-memory store for running agent processes only
32
32
  export const agentProcesses: Map<string, Subprocess> = new Map();
33
33
 
34
- // Binary path - can be overridden via environment variable
35
- export const BINARY_PATH = process.env.AGENT_BINARY_PATH || getBinaryPath(BIN_DIR);
34
+ // Binary path - can be overridden via environment variable, or found from npm/downloaded
35
+ export function getBinaryPathForAgent(): string {
36
+ // Environment override takes priority
37
+ if (process.env.AGENT_BINARY_PATH) {
38
+ return process.env.AGENT_BINARY_PATH;
39
+ }
40
+ // Otherwise use npm package or downloaded binary
41
+ return getActualBinaryPath(BIN_DIR) || getBinaryPath(BIN_DIR);
42
+ }
43
+
44
+ // Export for legacy compatibility
45
+ export const BINARY_PATH = getBinaryPathForAgent();
36
46
 
37
47
  // Export binary status function for API
38
48
  export { getBinaryStatus, BIN_DIR };
@@ -133,4 +143,4 @@ console.log(`
133
143
  ${c.darkGray}Click link or Cmd/Ctrl+C to copy${c.reset}
134
144
  `);
135
145
 
136
- export default server;
146
+ // Note: Don't use "export default server" - it causes Bun to print "Started server" message