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.37",
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 import('@anthropic-ai/claude-agent-sdk');
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 import('@opencode-ai/sdk');
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 titleEndpoints() {
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 && PROXY_TOKEN) {
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: PROXY_TOKEN,
184
+ token: proxyToken,
173
185
  model: gatewayTitleModel(TITLE_MODEL),
186
+ usesProxyToken: true,
174
187
  });
175
188
  }
176
- if (OPENAI_PROXY_URL && PROXY_TOKEN) {
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: PROXY_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
- import('ai'),
200
- import('@ai-sdk/gateway'),
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 requestTitle(endpoint, text, controller.signal);
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);