amalgm 0.1.37 → 0.1.38
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": "amalgm",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.38",
|
|
4
4
|
"description": "Amalgm local computer runtime: login, MCP, chat, events, previews, and tunnels.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"private": false,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"sync-runtime": "node ../../scripts/sync-npm-package-runtime.mjs",
|
|
18
18
|
"prepack": "node ../../scripts/sync-npm-package-runtime.mjs",
|
|
19
19
|
"pack:dry": "npm pack --dry-run",
|
|
20
|
-
"check": "node --check bin/amalgm.js && node --check lib/auth-store.js && node --check lib/cli.js && node --check lib/paths.js && node --check lib/supervisor.js && node --check lib/tunnel-chat.js && node --check lib/tunnel-events.js && node --check runtime/scripts/runtime-auth.js && node --check runtime/scripts/proxy-token-store.js && node --check runtime/scripts/local-gateway.js && node --check runtime/scripts/port-monitor.js && node --check runtime/scripts/fs-watcher.js && node --check runtime/scripts/chat-server.js && node --check runtime/scripts/chat-server/index.js && node --check runtime/scripts/chat-server/config.js && node --check runtime/scripts/chat-core/tooling/native-binaries.js && node --check runtime/scripts/amalgm-mcp/index.js && node --check runtime/scripts/amalgm-mcp/config.js"
|
|
20
|
+
"check": "node --check bin/amalgm.js && node --check lib/auth-store.js && node --check lib/cli.js && node --check lib/paths.js && node --check lib/supervisor.js && node --check lib/tunnel-chat.js && node --check lib/tunnel-events.js && node --check runtime/scripts/runtime-auth.js && node --check runtime/scripts/proxy-token-store.js && node --check runtime/scripts/local-gateway.js && node --check runtime/scripts/port-monitor.js && node --check runtime/scripts/fs-watcher.js && node --check runtime/scripts/chat-server.js && node --check runtime/scripts/chat-server/index.js && node --check runtime/scripts/chat-server/config.js && node --check runtime/scripts/chat-core/tooling/native-binaries.js && node --check runtime/scripts/chat-core/tooling/package-import.js && node --check runtime/scripts/amalgm-mcp/index.js && node --check runtime/scripts/amalgm-mcp/config.js"
|
|
21
21
|
},
|
|
22
22
|
"engines": {
|
|
23
23
|
"node": ">=20"
|
|
@@ -8,6 +8,7 @@ const { normalizeClaudeMessage, usageRecordsFromClaudeResult, usageFromClaude }
|
|
|
8
8
|
const { recordNativeEvent } = require('../recorder');
|
|
9
9
|
const { toClaudeMcpServers } = require('../tooling/mcp-bundle');
|
|
10
10
|
const { bundledClaudeBinary } = require('../tooling/native-binaries');
|
|
11
|
+
const { importPackage } = require('../tooling/package-import');
|
|
11
12
|
const { composeSystemPrompt } = require('../tooling/system-prompt');
|
|
12
13
|
|
|
13
14
|
function redactSecrets(text) {
|
|
@@ -90,7 +91,7 @@ class ClaudeAdapter {
|
|
|
90
91
|
}
|
|
91
92
|
};
|
|
92
93
|
try {
|
|
93
|
-
const sdk = await
|
|
94
|
+
const sdk = await importPackage('@anthropic-ai/claude-agent-sdk');
|
|
94
95
|
const query = sdk.query({
|
|
95
96
|
prompt: promptText(input, contract),
|
|
96
97
|
options: {
|
|
@@ -12,6 +12,7 @@ const { normalizeOpenCodeEvent, normalizeOpenCodePromptResult } = require('../no
|
|
|
12
12
|
const { recordNativeEvent } = require('../recorder');
|
|
13
13
|
const { toOpenCodeMcpConfig } = require('../tooling/mcp-bundle');
|
|
14
14
|
const { bundledOpenCodeBinary, executableExists } = require('../tooling/native-binaries');
|
|
15
|
+
const { importPackage } = require('../tooling/package-import');
|
|
15
16
|
const { composeSystemPrompt } = require('../tooling/system-prompt');
|
|
16
17
|
|
|
17
18
|
function splitModel(model, auth) {
|
|
@@ -204,7 +205,7 @@ function isCompactCommand(text) {
|
|
|
204
205
|
|
|
205
206
|
class OpenCodeAdapter {
|
|
206
207
|
async startInstance(contract) {
|
|
207
|
-
const sdk = await
|
|
208
|
+
const sdk = await importPackage('@opencode-ai/sdk');
|
|
208
209
|
const server = await startOpenCodeServer(contract);
|
|
209
210
|
const client = sdk.createOpencodeClient({ baseUrl: server.url });
|
|
210
211
|
return { client, server, closed: false };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { pathToFileURL } = require('url');
|
|
6
|
+
|
|
7
|
+
function packageParts(specifier) {
|
|
8
|
+
const parts = String(specifier || '').split('/').filter(Boolean);
|
|
9
|
+
if (parts.length === 0) throw new Error('Package specifier is required');
|
|
10
|
+
if (parts[0].startsWith('@')) {
|
|
11
|
+
if (parts.length < 2) throw new Error(`Invalid scoped package specifier: ${specifier}`);
|
|
12
|
+
return {
|
|
13
|
+
packageName: `${parts[0]}/${parts[1]}`,
|
|
14
|
+
subpath: parts.slice(2).join('/'),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
packageName: parts[0],
|
|
19
|
+
subpath: parts.slice(1).join('/'),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function nodePathEntries() {
|
|
24
|
+
return String(process.env.NODE_PATH || '')
|
|
25
|
+
.split(path.delimiter)
|
|
26
|
+
.map((entry) => entry.trim())
|
|
27
|
+
.filter(Boolean);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function ancestorNodeModules() {
|
|
31
|
+
const entries = [];
|
|
32
|
+
let current = __dirname;
|
|
33
|
+
for (;;) {
|
|
34
|
+
entries.push(path.join(current, 'node_modules'));
|
|
35
|
+
const next = path.dirname(current);
|
|
36
|
+
if (next === current) break;
|
|
37
|
+
current = next;
|
|
38
|
+
}
|
|
39
|
+
return entries;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function candidateNodeModules() {
|
|
43
|
+
const resourcesPath = process.resourcesPath || '';
|
|
44
|
+
const entries = [
|
|
45
|
+
...nodePathEntries(),
|
|
46
|
+
...ancestorNodeModules(),
|
|
47
|
+
resourcesPath ? path.join(resourcesPath, 'app.asar', 'node_modules') : '',
|
|
48
|
+
resourcesPath ? path.join(resourcesPath, 'app.asar.unpacked', 'node_modules') : '',
|
|
49
|
+
resourcesPath ? path.join(resourcesPath, 'cli', 'node_modules') : '',
|
|
50
|
+
resourcesPath ? path.join(resourcesPath, 'runtime', 'node_modules') : '',
|
|
51
|
+
].filter(Boolean);
|
|
52
|
+
return Array.from(new Set(entries));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function readPackageJson(packageRoot) {
|
|
56
|
+
try {
|
|
57
|
+
return JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function exportedPath(exportsField, subpath) {
|
|
64
|
+
if (!exportsField) return '';
|
|
65
|
+
const key = subpath ? `./${subpath}` : '.';
|
|
66
|
+
const value = typeof exportsField === 'object' && !Array.isArray(exportsField)
|
|
67
|
+
? exportsField[key]
|
|
68
|
+
: (key === '.' ? exportsField : null);
|
|
69
|
+
if (!value) return '';
|
|
70
|
+
if (typeof value === 'string') return value;
|
|
71
|
+
if (typeof value !== 'object' || Array.isArray(value)) return '';
|
|
72
|
+
for (const condition of ['import', 'module', 'default', 'node', 'require']) {
|
|
73
|
+
if (typeof value[condition] === 'string') return value[condition];
|
|
74
|
+
}
|
|
75
|
+
return '';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function resolvePackageEntry(specifier) {
|
|
79
|
+
const { packageName, subpath } = packageParts(specifier);
|
|
80
|
+
for (const nodeModules of candidateNodeModules()) {
|
|
81
|
+
const packageRoot = path.join(nodeModules, packageName);
|
|
82
|
+
const pkg = readPackageJson(packageRoot);
|
|
83
|
+
if (!pkg) continue;
|
|
84
|
+
|
|
85
|
+
const target = (
|
|
86
|
+
exportedPath(pkg.exports, subpath)
|
|
87
|
+
|| (subpath ? subpath : '')
|
|
88
|
+
|| pkg.module
|
|
89
|
+
|| pkg.main
|
|
90
|
+
|| 'index.js'
|
|
91
|
+
);
|
|
92
|
+
const resolved = path.join(packageRoot, target);
|
|
93
|
+
if (fs.existsSync(resolved)) return resolved;
|
|
94
|
+
}
|
|
95
|
+
return '';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function importPackage(specifier) {
|
|
99
|
+
try {
|
|
100
|
+
return await import(specifier);
|
|
101
|
+
} catch (originalError) {
|
|
102
|
+
const resolved = resolvePackageEntry(specifier);
|
|
103
|
+
if (!resolved) throw originalError;
|
|
104
|
+
return import(pathToFileURL(resolved).href);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = { importPackage, resolvePackageEntry };
|
|
@@ -17,6 +17,7 @@ const {
|
|
|
17
17
|
OPENAI_PROXY_URL,
|
|
18
18
|
AI_GATEWAY_PROXY_URL,
|
|
19
19
|
} = require('./config');
|
|
20
|
+
const { importPackage } = require('../chat-core/tooling/package-import');
|
|
20
21
|
|
|
21
22
|
// ── Supabase primitives ─────────────────────────────────────────────────────
|
|
22
23
|
|
|
@@ -153,8 +154,19 @@ function gatewaySdkBaseUrl(baseUrl) {
|
|
|
153
154
|
return clean.endsWith('/v1/ai') ? clean : `${clean}/v1/ai`;
|
|
154
155
|
}
|
|
155
156
|
|
|
156
|
-
function
|
|
157
|
+
async function currentProxyToken({ forceRefresh = false } = {}) {
|
|
158
|
+
if (!AI_GATEWAY_PROXY_URL && !OPENAI_PROXY_URL) return '';
|
|
159
|
+
try {
|
|
160
|
+
return await ensureFreshProxyToken?.({ force: forceRefresh, logger: console }) || readProxyToken?.() || PROXY_TOKEN || '';
|
|
161
|
+
} catch (err) {
|
|
162
|
+
console.warn('[DB] Proxy token refresh failed for title generation:', err.message);
|
|
163
|
+
return readProxyToken?.() || PROXY_TOKEN || '';
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function titleEndpoints({ forceRefresh = false } = {}) {
|
|
157
168
|
const endpoints = [];
|
|
169
|
+
const proxyToken = await currentProxyToken({ forceRefresh });
|
|
158
170
|
if (AI_GATEWAY_API_KEY_TITLE) {
|
|
159
171
|
endpoints.push({
|
|
160
172
|
name: 'vercel_ai_sdk_gateway',
|
|
@@ -164,22 +176,24 @@ function titleEndpoints() {
|
|
|
164
176
|
model: gatewayTitleModel(TITLE_MODEL),
|
|
165
177
|
});
|
|
166
178
|
}
|
|
167
|
-
if (AI_GATEWAY_PROXY_URL &&
|
|
179
|
+
if (AI_GATEWAY_PROXY_URL && proxyToken) {
|
|
168
180
|
endpoints.push({
|
|
169
181
|
name: 'ai_gateway_proxy',
|
|
170
182
|
kind: 'openai_compat',
|
|
171
183
|
url: `${AI_GATEWAY_PROXY_URL}/chat/completions`,
|
|
172
|
-
token:
|
|
184
|
+
token: proxyToken,
|
|
173
185
|
model: gatewayTitleModel(TITLE_MODEL),
|
|
186
|
+
usesProxyToken: true,
|
|
174
187
|
});
|
|
175
188
|
}
|
|
176
|
-
if (OPENAI_PROXY_URL &&
|
|
189
|
+
if (OPENAI_PROXY_URL && proxyToken) {
|
|
177
190
|
endpoints.push({
|
|
178
191
|
name: 'openai_proxy',
|
|
179
192
|
kind: 'openai_compat',
|
|
180
193
|
url: `${OPENAI_PROXY_URL}/v1/chat/completions`,
|
|
181
|
-
token:
|
|
194
|
+
token: proxyToken,
|
|
182
195
|
model: openAiTitleModel(TITLE_MODEL),
|
|
196
|
+
usesProxyToken: true,
|
|
183
197
|
});
|
|
184
198
|
}
|
|
185
199
|
if (OPENAI_API_KEY_TITLE) {
|
|
@@ -196,8 +210,8 @@ function titleEndpoints() {
|
|
|
196
210
|
|
|
197
211
|
async function requestTitleWithAiSdkGateway(endpoint, prompt, signal) {
|
|
198
212
|
const [{ generateText }, { createGatewayProvider }] = await Promise.all([
|
|
199
|
-
|
|
200
|
-
|
|
213
|
+
importPackage('ai'),
|
|
214
|
+
importPackage('@ai-sdk/gateway'),
|
|
201
215
|
]);
|
|
202
216
|
const gateway = createGatewayProvider({
|
|
203
217
|
apiKey: endpoint.token,
|
|
@@ -240,11 +254,26 @@ async function requestTitle(endpoint, prompt, signal) {
|
|
|
240
254
|
return cleanGeneratedTitle(extractResponseText(await res.json()));
|
|
241
255
|
}
|
|
242
256
|
|
|
257
|
+
function isProxyAuthFailure(error) {
|
|
258
|
+
return /\b(401|403)\b|invalid or expired token|unauthorized/i.test(String(error?.message || error || ''));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async function requestTitleWithProxyRetry(endpoint, prompt, signal) {
|
|
262
|
+
try {
|
|
263
|
+
return await requestTitle(endpoint, prompt, signal);
|
|
264
|
+
} catch (err) {
|
|
265
|
+
if (!endpoint.usesProxyToken || !isProxyAuthFailure(err)) throw err;
|
|
266
|
+
const refreshedToken = await currentProxyToken({ forceRefresh: true });
|
|
267
|
+
if (!refreshedToken || refreshedToken === endpoint.token) throw err;
|
|
268
|
+
return requestTitle({ ...endpoint, token: refreshedToken }, prompt, signal);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
243
272
|
async function generateTitle(prompt) {
|
|
244
273
|
const text = String(prompt || '').trim();
|
|
245
274
|
if (!text) return '';
|
|
246
275
|
|
|
247
|
-
const endpoints = titleEndpoints();
|
|
276
|
+
const endpoints = await titleEndpoints();
|
|
248
277
|
if (endpoints.length === 0) {
|
|
249
278
|
console.warn('[DB] Title generation skipped: no gateway/proxy credentials');
|
|
250
279
|
return '';
|
|
@@ -254,7 +283,7 @@ async function generateTitle(prompt) {
|
|
|
254
283
|
const controller = new AbortController();
|
|
255
284
|
const timeout = setTimeout(() => controller.abort(), TITLE_TIMEOUT_MS);
|
|
256
285
|
try {
|
|
257
|
-
const title = await
|
|
286
|
+
const title = await requestTitleWithProxyRetry(endpoint, text, controller.signal);
|
|
258
287
|
if (title) return title;
|
|
259
288
|
} catch (err) {
|
|
260
289
|
console.warn('[DB] Title generation endpoint failed:', err.message);
|