apteva 0.1.7 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -1
- package/src/binary.ts +175 -56
- package/src/server.ts +18 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apteva",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
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
|
|
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 =
|
|
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
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
|
139
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
65
140
|
clearTimeout(timeoutId);
|
|
66
|
-
return
|
|
141
|
+
return arrayBuffer;
|
|
67
142
|
} catch (err) {
|
|
68
143
|
clearTimeout(timeoutId);
|
|
69
144
|
throw err;
|
|
70
145
|
}
|
|
71
146
|
}
|
|
72
147
|
|
|
73
|
-
//
|
|
74
|
-
export async function ensureBinary(binDir: string, silent = false): Promise<{
|
|
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,15 +171,19 @@ export async function ensureBinary(binDir: string, silent = false): Promise<{ su
|
|
|
79
171
|
mkdirSync(binDir, { recursive: true });
|
|
80
172
|
}
|
|
81
173
|
|
|
82
|
-
// Check if already
|
|
174
|
+
// Check if already downloaded
|
|
83
175
|
if (existsSync(binaryPath)) {
|
|
84
|
-
return {
|
|
176
|
+
return {
|
|
177
|
+
success: true,
|
|
178
|
+
path: binaryPath,
|
|
179
|
+
downloaded: false,
|
|
180
|
+
source: "cached"
|
|
181
|
+
};
|
|
85
182
|
}
|
|
86
183
|
|
|
184
|
+
// Need to download - show message
|
|
87
185
|
const url = getDownloadUrl();
|
|
88
|
-
const filename = getBinaryFilename();
|
|
89
186
|
|
|
90
|
-
// Show downloading message (server.ts already printed "Binary ")
|
|
91
187
|
if (!silent) {
|
|
92
188
|
process.stdout.write(`${c.orange}downloading...${c.reset}`);
|
|
93
189
|
}
|
|
@@ -97,47 +193,41 @@ export async function ensureBinary(binDir: string, silent = false): Promise<{ su
|
|
|
97
193
|
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
98
194
|
try {
|
|
99
195
|
if (!silent && attempt > 1) {
|
|
100
|
-
|
|
101
|
-
process.stdout.write(`\r Binary ${c.orange}retry ${attempt}/${MAX_RETRIES}...${c.reset} `);
|
|
196
|
+
process.stdout.write(`\r Agent ${c.orange}retry ${attempt}/${MAX_RETRIES}...${c.reset} `);
|
|
102
197
|
}
|
|
103
198
|
|
|
104
|
-
const
|
|
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
|
-
}
|
|
199
|
+
const arrayBuffer = await downloadWithTimeout(url);
|
|
200
|
+
await Bun.write(binaryPath, arrayBuffer);
|
|
131
201
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
202
|
+
// Make executable on Unix systems
|
|
203
|
+
if (process.platform !== "win32") {
|
|
204
|
+
chmodSync(binaryPath, 0o755);
|
|
205
|
+
}
|
|
137
206
|
|
|
138
|
-
|
|
207
|
+
// Show success
|
|
208
|
+
if (!silent) {
|
|
209
|
+
const sizeMB = (arrayBuffer.byteLength / 1024 / 1024).toFixed(1);
|
|
210
|
+
console.log(`\r${c.green}ready${c.reset} ${c.gray}(${sizeMB}MB downloaded)${c.reset} `);
|
|
139
211
|
}
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
success: true,
|
|
215
|
+
path: binaryPath,
|
|
216
|
+
downloaded: true,
|
|
217
|
+
source: "download"
|
|
218
|
+
};
|
|
140
219
|
} catch (err: any) {
|
|
220
|
+
if (err.message?.startsWith("HTTP 404")) {
|
|
221
|
+
if (!silent) {
|
|
222
|
+
console.log(`\r${c.red}not found${c.reset} `);
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
success: false,
|
|
226
|
+
path: binaryPath,
|
|
227
|
+
error: `Binary not found at ${url}`,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
141
231
|
lastError = err.name === "AbortError" ? "timeout" : String(err.message || err);
|
|
142
232
|
if (attempt < MAX_RETRIES) {
|
|
143
233
|
await sleep(RETRY_DELAY);
|
|
@@ -166,16 +256,45 @@ export function getBinaryStatus(binDir: string): {
|
|
|
166
256
|
downloadUrl: string;
|
|
167
257
|
platform: string;
|
|
168
258
|
arch: string;
|
|
259
|
+
source?: "npm" | "download" | "none";
|
|
169
260
|
} {
|
|
170
261
|
const { platform, arch } = getPlatformInfo();
|
|
171
|
-
|
|
262
|
+
|
|
263
|
+
// Check npm first
|
|
264
|
+
const npmBinary = findNpmBinary();
|
|
265
|
+
if (npmBinary) {
|
|
266
|
+
return {
|
|
267
|
+
exists: true,
|
|
268
|
+
path: npmBinary,
|
|
269
|
+
filename: getBinaryFilename(),
|
|
270
|
+
downloadUrl: getDownloadUrl(),
|
|
271
|
+
platform,
|
|
272
|
+
arch,
|
|
273
|
+
source: "npm",
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Check downloaded
|
|
278
|
+
const downloadedPath = getBinaryPath(binDir);
|
|
279
|
+
if (existsSync(downloadedPath)) {
|
|
280
|
+
return {
|
|
281
|
+
exists: true,
|
|
282
|
+
path: downloadedPath,
|
|
283
|
+
filename: getBinaryFilename(),
|
|
284
|
+
downloadUrl: getDownloadUrl(),
|
|
285
|
+
platform,
|
|
286
|
+
arch,
|
|
287
|
+
source: "download",
|
|
288
|
+
};
|
|
289
|
+
}
|
|
172
290
|
|
|
173
291
|
return {
|
|
174
|
-
exists:
|
|
175
|
-
path,
|
|
292
|
+
exists: false,
|
|
293
|
+
path: downloadedPath,
|
|
176
294
|
filename: getBinaryFilename(),
|
|
177
295
|
downloadUrl: getDownloadUrl(),
|
|
178
296
|
platform,
|
|
179
297
|
arch,
|
|
298
|
+
source: "none",
|
|
180
299
|
};
|
|
181
300
|
}
|
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
|
|
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 };
|
|
@@ -74,12 +84,12 @@ console.log(`
|
|
|
74
84
|
`);
|
|
75
85
|
|
|
76
86
|
// Check binary - ensureBinary handles progress output when downloading
|
|
77
|
-
process.stdout.write(` ${c.darkGray}
|
|
87
|
+
process.stdout.write(` ${c.darkGray}Agent${c.reset} `);
|
|
78
88
|
const binaryResult = await ensureBinary(BIN_DIR);
|
|
79
89
|
// ensureBinary prints its own status when downloading/failing
|
|
80
90
|
// We only need to print "ready" if binary already existed
|
|
81
91
|
if (binaryResult.success && !binaryResult.downloaded) {
|
|
82
|
-
console.log(`${c.gray}ready${c.reset}`);
|
|
92
|
+
console.log(`${c.gray}binary ready${c.reset}`);
|
|
83
93
|
}
|
|
84
94
|
|
|
85
95
|
// Check database
|
|
@@ -93,7 +103,8 @@ console.log(`${c.gray}${configuredProviders.length} configured${c.reset}`);
|
|
|
93
103
|
|
|
94
104
|
const server = Bun.serve({
|
|
95
105
|
port: PORT,
|
|
96
|
-
|
|
106
|
+
hostname: "0.0.0.0", // Listen on all interfaces
|
|
107
|
+
development: false, // Suppress "Started server" message
|
|
97
108
|
|
|
98
109
|
async fetch(req: Request): Promise<Response> {
|
|
99
110
|
const url = new URL(req.url);
|
|
@@ -132,4 +143,4 @@ console.log(`
|
|
|
132
143
|
${c.darkGray}Click link or Cmd/Ctrl+C to copy${c.reset}
|
|
133
144
|
`);
|
|
134
145
|
|
|
135
|
-
export default server
|
|
146
|
+
// Note: Don't use "export default server" - it causes Bun to print "Started server" message
|