amalgm 0.1.69 → 0.1.71
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 +4 -3
- package/runtime/lib/harnesses.js +47 -37
- package/runtime/scripts/amalgm-mcp/agents/store.js +1 -1
- package/runtime/scripts/amalgm-mcp/agents/talk.js +48 -38
- package/runtime/scripts/amalgm-mcp/lib/computer.js +75 -0
- package/runtime/scripts/amalgm-mcp/lib/prefs.js +38 -28
- package/runtime/scripts/amalgm-mcp/tasks/executor.js +25 -18
- package/runtime/scripts/chat-core/adapters/claude.js +1 -1
- package/runtime/scripts/chat-core/adapters/codex.js +3 -1
- package/runtime/scripts/chat-core/adapters/opencode.js +7 -4
- package/runtime/scripts/chat-core/auth.js +11 -29
- package/runtime/scripts/chat-core/contract.js +44 -32
- package/runtime/scripts/chat-core/engine.js +0 -1
- package/runtime/scripts/chat-core/normalizers/codex.js +29 -6
- package/runtime/scripts/chat-core/parts.js +2 -0
- package/runtime/scripts/chat-core/tests/auth.test.js +50 -44
- package/runtime/scripts/chat-core/tests/engine.test.js +1 -1
- package/runtime/scripts/chat-core/tests/native-binaries.test.js +126 -0
- package/runtime/scripts/chat-core/tests/native-config.test.js +87 -9
- package/runtime/scripts/chat-core/tool-shape.js +4 -2
- package/runtime/scripts/chat-core/tooling/native-binaries.js +20 -3
- package/runtime/scripts/chat-core/tooling/native-config.js +54 -39
- package/runtime/scripts/chat-core/tooling/runtime-home.js +3 -1
- package/runtime/scripts/chat-server/model-catalog.js +10 -5
- package/runtime/scripts/credential-adapter.js +1 -1
- package/runtime/scripts/test-claude-code-models.js +17 -12
|
@@ -9,7 +9,7 @@ const { resolveCredential } = require('./credentials/store');
|
|
|
9
9
|
const AUTH_BY_HARNESS = {
|
|
10
10
|
claude_code: ['amalgm', 'byok', 'provider_auth'],
|
|
11
11
|
codex: ['amalgm', 'byok', 'provider_auth'],
|
|
12
|
-
opencode: ['amalgm', 'byok'],
|
|
12
|
+
opencode: ['amalgm', 'byok', 'provider_auth'],
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
function shellValue(raw) {
|
|
@@ -109,35 +109,15 @@ function providerAuthIdentity(harness) {
|
|
|
109
109
|
return `${harness || 'provider'}:provider-default`;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
function
|
|
113
|
-
|
|
114
|
-
return `amalgm-${fingerprint(userId || process.env.AMALGM_USER_ID || 'local') || 'local'}`;
|
|
115
|
-
}
|
|
116
|
-
if (method === 'byok') {
|
|
117
|
-
return `byok-${tokenFingerprint || 'default'}`;
|
|
118
|
-
}
|
|
119
|
-
if (method === 'provider_auth') {
|
|
120
|
-
return `provider-${fingerprint(providerAuthIdentity(harness)) || 'default'}`;
|
|
121
|
-
}
|
|
122
|
-
return 'default';
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function runtimeHome({ amalgmDir, harness, authProfileId }) {
|
|
126
|
-
return path.join(amalgmDir, 'cli-homes', safePathSegment(harness), safePathSegment(authProfileId));
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function pinnedRuntimeHome(amalgmDir, cliHomePath) {
|
|
130
|
-
if (typeof cliHomePath !== 'string' || !cliHomePath.trim()) return null;
|
|
131
|
-
const root = path.resolve(amalgmDir);
|
|
132
|
-
const resolved = path.resolve(cliHomePath.trim());
|
|
133
|
-
return resolved === root || resolved.startsWith(`${root}${path.sep}`) ? resolved : null;
|
|
112
|
+
function runtimeHome({ amalgmDir, harness }) {
|
|
113
|
+
return path.join(amalgmDir, 'cli-homes', safePathSegment(harness), 'home');
|
|
134
114
|
}
|
|
135
115
|
|
|
136
116
|
function providerAuthFingerprint(harness) {
|
|
137
117
|
return fingerprint(providerAuthIdentity(harness));
|
|
138
118
|
}
|
|
139
119
|
|
|
140
|
-
function authEnvelope({ harness, authMethod, sessionId, localBaseUrl, proxyToken, proxyBaseUrl, amalgmDir, userId, credentialId, modelId
|
|
120
|
+
function authEnvelope({ harness, authMethod, sessionId, localBaseUrl, proxyToken, proxyBaseUrl, amalgmDir, userId, credentialId, modelId }) {
|
|
141
121
|
const method = coerceAuth(harness, authMethod);
|
|
142
122
|
const byok = readByokFile(amalgmDir);
|
|
143
123
|
let baseUrl = null;
|
|
@@ -166,7 +146,6 @@ function authEnvelope({ harness, authMethod, sessionId, localBaseUrl, proxyToken
|
|
|
166
146
|
} else if (method === 'provider_auth') {
|
|
167
147
|
tokenFingerprint = providerAuthFingerprint(harness);
|
|
168
148
|
}
|
|
169
|
-
const profileId = authProfileId || deriveAuthProfileId({ method, harness, userId, tokenFingerprint });
|
|
170
149
|
return {
|
|
171
150
|
method,
|
|
172
151
|
baseUrl,
|
|
@@ -176,10 +155,7 @@ function authEnvelope({ harness, authMethod, sessionId, localBaseUrl, proxyToken
|
|
|
176
155
|
forwardBaseUrl,
|
|
177
156
|
tokenRef,
|
|
178
157
|
tokenFingerprint,
|
|
179
|
-
|
|
180
|
-
runtimeHome:
|
|
181
|
-
pinnedRuntimeHome(amalgmDir, cliHomePath)
|
|
182
|
-
|| runtimeHome({ amalgmDir, harness, authProfileId: profileId }),
|
|
158
|
+
runtimeHome: runtimeHome({ amalgmDir, harness }),
|
|
183
159
|
};
|
|
184
160
|
}
|
|
185
161
|
|
|
@@ -203,6 +179,12 @@ function runtimeEnv(contract, baseEnv = process.env) {
|
|
|
203
179
|
env.CLAUDE_CONFIG_DIR = contract.auth.runtimeHome;
|
|
204
180
|
env.OPENCODE_HOME = contract.auth.runtimeHome;
|
|
205
181
|
env.OPENCODE_CONFIG_DIR = contract.auth.runtimeHome;
|
|
182
|
+
if (contract.harness === 'opencode') {
|
|
183
|
+
env.XDG_CONFIG_HOME = path.join(contract.auth.runtimeHome, '.config');
|
|
184
|
+
env.XDG_DATA_HOME = path.join(contract.auth.runtimeHome, '.local', 'share');
|
|
185
|
+
env.XDG_STATE_HOME = path.join(contract.auth.runtimeHome, '.local', 'state');
|
|
186
|
+
env.XDG_CACHE_HOME = path.join(contract.auth.runtimeHome, '.cache');
|
|
187
|
+
}
|
|
206
188
|
if (contract.harness === 'claude_code' && contract.authMethod === 'provider_auth') {
|
|
207
189
|
delete env.CLAUDE_CONFIG_DIR;
|
|
208
190
|
}
|
|
@@ -28,26 +28,31 @@ function canonicalModel(modelId, harness) {
|
|
|
28
28
|
'haiku': 'anthropic/claude-haiku-4.5',
|
|
29
29
|
'sonnet': 'anthropic/claude-sonnet-4.6',
|
|
30
30
|
'sonnet[1m]': 'anthropic/claude-sonnet-4.6',
|
|
31
|
-
'opus': 'anthropic/claude-opus-4.
|
|
32
|
-
'opus1m': 'anthropic/claude-opus-4.
|
|
33
|
-
'opus[1m]': 'anthropic/claude-opus-4.
|
|
31
|
+
'opus': 'anthropic/claude-opus-4.8',
|
|
32
|
+
'opus1m': 'anthropic/claude-opus-4.8-1m',
|
|
33
|
+
'opus[1m]': 'anthropic/claude-opus-4.8-1m',
|
|
34
34
|
'claude-code-haiku': 'anthropic/claude-haiku-4.5',
|
|
35
35
|
'claude-code-sonnet': 'anthropic/claude-sonnet-4.6',
|
|
36
36
|
'claude-code-sonnet-45': 'anthropic/claude-sonnet-4.6',
|
|
37
|
-
'claude-code-opus': 'anthropic/claude-opus-4.
|
|
38
|
-
'claude-code-opus-45': 'anthropic/claude-opus-4.
|
|
39
|
-
'claude-code-opus-46': 'anthropic/claude-opus-4.
|
|
40
|
-
'claude-code-opus-47': 'anthropic/claude-opus-4.
|
|
41
|
-
'claude-code-opus-
|
|
42
|
-
'claude-code-opus-4
|
|
43
|
-
'claude-code-opus-4
|
|
44
|
-
'claude-code-opus-4
|
|
45
|
-
'claude-code-opus-4
|
|
46
|
-
'claude-code-opus-4
|
|
47
|
-
'claude-code-
|
|
48
|
-
'claude-code-opus-
|
|
49
|
-
'claude-code-opus-4
|
|
50
|
-
'claude-code-
|
|
37
|
+
'claude-code-opus': 'anthropic/claude-opus-4.8',
|
|
38
|
+
'claude-code-opus-45': 'anthropic/claude-opus-4.8',
|
|
39
|
+
'claude-code-opus-46': 'anthropic/claude-opus-4.8',
|
|
40
|
+
'claude-code-opus-47': 'anthropic/claude-opus-4.8',
|
|
41
|
+
'claude-code-opus-48': 'anthropic/claude-opus-4.8',
|
|
42
|
+
'claude-code-opus-4.5': 'anthropic/claude-opus-4.8',
|
|
43
|
+
'claude-code-opus-4-5': 'anthropic/claude-opus-4.8',
|
|
44
|
+
'claude-code-opus-4.6': 'anthropic/claude-opus-4.8',
|
|
45
|
+
'claude-code-opus-4-6': 'anthropic/claude-opus-4.8',
|
|
46
|
+
'claude-code-opus-4.7': 'anthropic/claude-opus-4.8',
|
|
47
|
+
'claude-code-opus-4-7': 'anthropic/claude-opus-4.8',
|
|
48
|
+
'claude-code-opus-4.8': 'anthropic/claude-opus-4.8',
|
|
49
|
+
'claude-code-opus-4-8': 'anthropic/claude-opus-4.8',
|
|
50
|
+
'claude-code-opus1m': 'anthropic/claude-opus-4.8-1m',
|
|
51
|
+
'claude-code-opus-1m': 'anthropic/claude-opus-4.8-1m',
|
|
52
|
+
'claude-code-opus-4.8-1m': 'anthropic/claude-opus-4.8-1m',
|
|
53
|
+
'claude-code-opus-4-8-1m': 'anthropic/claude-opus-4.8-1m',
|
|
54
|
+
'claude-code-opus-4.7-1m': 'anthropic/claude-opus-4.8-1m',
|
|
55
|
+
'claude-code-opus-4-7-1m': 'anthropic/claude-opus-4.8-1m',
|
|
51
56
|
'claude-code-sonnet-4.5': 'anthropic/claude-sonnet-4.6',
|
|
52
57
|
'claude-code-sonnet-4-5': 'anthropic/claude-sonnet-4.6',
|
|
53
58
|
'claude-code-sonnet-4.6': 'anthropic/claude-sonnet-4.6',
|
|
@@ -57,25 +62,32 @@ function canonicalModel(modelId, harness) {
|
|
|
57
62
|
'claude-haiku-4-5': 'anthropic/claude-haiku-4.5',
|
|
58
63
|
'claude-sonnet-4-5': 'anthropic/claude-sonnet-4.6',
|
|
59
64
|
'claude-sonnet-4-6': 'anthropic/claude-sonnet-4.6',
|
|
60
|
-
'claude-opus-4-5': 'anthropic/claude-opus-4.
|
|
61
|
-
'claude-opus-4-6': 'anthropic/claude-opus-4.
|
|
62
|
-
'claude-opus-4-7': 'anthropic/claude-opus-4.
|
|
63
|
-
'claude-opus-4-
|
|
64
|
-
'claude-opus-4
|
|
65
|
+
'claude-opus-4-5': 'anthropic/claude-opus-4.8',
|
|
66
|
+
'claude-opus-4-6': 'anthropic/claude-opus-4.8',
|
|
67
|
+
'claude-opus-4-7': 'anthropic/claude-opus-4.8',
|
|
68
|
+
'claude-opus-4-8': 'anthropic/claude-opus-4.8',
|
|
69
|
+
'claude-opus-4-8-1m': 'anthropic/claude-opus-4.8-1m',
|
|
70
|
+
'claude-opus-4.8-1m': 'anthropic/claude-opus-4.8-1m',
|
|
71
|
+
'claude-opus-4-7-1m': 'anthropic/claude-opus-4.8-1m',
|
|
72
|
+
'claude-opus-4.7-1m': 'anthropic/claude-opus-4.8-1m',
|
|
65
73
|
'anthropic/claude-haiku-4-5': 'anthropic/claude-haiku-4.5',
|
|
66
74
|
'anthropic/claude-haiku-4.5': 'anthropic/claude-haiku-4.5',
|
|
67
75
|
'anthropic/claude-sonnet-4-5': 'anthropic/claude-sonnet-4.6',
|
|
68
76
|
'anthropic/claude-sonnet-4-6': 'anthropic/claude-sonnet-4.6',
|
|
69
77
|
'anthropic/claude-sonnet-4.5': 'anthropic/claude-sonnet-4.6',
|
|
70
78
|
'anthropic/claude-sonnet-4.6': 'anthropic/claude-sonnet-4.6',
|
|
71
|
-
'anthropic/claude-opus-4-5': 'anthropic/claude-opus-4.
|
|
72
|
-
'anthropic/claude-opus-4-6': 'anthropic/claude-opus-4.
|
|
73
|
-
'anthropic/claude-opus-4-7': 'anthropic/claude-opus-4.
|
|
74
|
-
'anthropic/claude-opus-4-
|
|
75
|
-
'anthropic/claude-opus-4
|
|
76
|
-
'anthropic/claude-opus-4
|
|
77
|
-
'anthropic/claude-opus-4.
|
|
78
|
-
'anthropic/claude-opus-4.
|
|
79
|
+
'anthropic/claude-opus-4-5': 'anthropic/claude-opus-4.8',
|
|
80
|
+
'anthropic/claude-opus-4-6': 'anthropic/claude-opus-4.8',
|
|
81
|
+
'anthropic/claude-opus-4-7': 'anthropic/claude-opus-4.8',
|
|
82
|
+
'anthropic/claude-opus-4-8': 'anthropic/claude-opus-4.8',
|
|
83
|
+
'anthropic/claude-opus-4-8-1m': 'anthropic/claude-opus-4.8-1m',
|
|
84
|
+
'anthropic/claude-opus-4-7-1m': 'anthropic/claude-opus-4.8-1m',
|
|
85
|
+
'anthropic/claude-opus-4.5': 'anthropic/claude-opus-4.8',
|
|
86
|
+
'anthropic/claude-opus-4.6': 'anthropic/claude-opus-4.8',
|
|
87
|
+
'anthropic/claude-opus-4.7': 'anthropic/claude-opus-4.8',
|
|
88
|
+
'anthropic/claude-opus-4.8': 'anthropic/claude-opus-4.8',
|
|
89
|
+
'anthropic/claude-opus-4.8-1m': 'anthropic/claude-opus-4.8-1m',
|
|
90
|
+
'anthropic/claude-opus-4.7-1m': 'anthropic/claude-opus-4.8-1m',
|
|
79
91
|
};
|
|
80
92
|
if (aliases[cleaned]) return aliases[cleaned];
|
|
81
93
|
if (cleaned.startsWith('anthropic/')) return cleaned.replace(/-(\d+)-(\d+)(?=$|-)/, '-$1.$2');
|
|
@@ -98,6 +110,8 @@ function cliModelFor({ harness, modelId, cliModel, reasoningEffort }) {
|
|
|
98
110
|
const aliases = {
|
|
99
111
|
'anthropic/claude-haiku-4.5': 'haiku',
|
|
100
112
|
'anthropic/claude-sonnet-4.6': 'sonnet',
|
|
113
|
+
'anthropic/claude-opus-4.8': 'opus',
|
|
114
|
+
'anthropic/claude-opus-4.8-1m': 'opus[1m]',
|
|
101
115
|
'anthropic/claude-opus-4.7': 'opus',
|
|
102
116
|
'anthropic/claude-opus-4.7-1m': 'opus[1m]',
|
|
103
117
|
'haiku': 'haiku',
|
|
@@ -347,8 +361,6 @@ function createContract(payload, options = {}) {
|
|
|
347
361
|
userId,
|
|
348
362
|
credentialId: payload.credentialId || payload.byokCredentialId || null,
|
|
349
363
|
modelId,
|
|
350
|
-
cliHomePath: payload.cliHomePath || payload.cli_home_path || null,
|
|
351
|
-
authProfileId: payload.authProfileId || payload.auth_profile_id || null,
|
|
352
364
|
});
|
|
353
365
|
const contract = {
|
|
354
366
|
sessionId,
|
|
@@ -7,8 +7,12 @@ const { normalizeUsage } = require('../usage');
|
|
|
7
7
|
|
|
8
8
|
const NON_TOOL_ITEM_TYPES = new Set(['agentMessage', 'contextCompaction', 'reasoning', 'userMessage']);
|
|
9
9
|
|
|
10
|
+
function codexToolCallId(item = {}) {
|
|
11
|
+
return item?.id || item?.callId || item?.call_id || item?.toolCallId || item?.tool_call_id;
|
|
12
|
+
}
|
|
13
|
+
|
|
10
14
|
function isCodexToolItem(item = {}) {
|
|
11
|
-
return Boolean(item
|
|
15
|
+
return Boolean(codexToolCallId(item) && item?.type && !NON_TOOL_ITEM_TYPES.has(item.type));
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
function codexToolName(item = {}) {
|
|
@@ -37,7 +41,10 @@ function codexToolInput(item = {}) {
|
|
|
37
41
|
if (item[key] !== undefined) input[key] = item[key];
|
|
38
42
|
}
|
|
39
43
|
if (item.input && typeof item.input === 'object') Object.assign(input, item.input);
|
|
40
|
-
|
|
44
|
+
const args = parseObjectish(item.arguments);
|
|
45
|
+
if (args) Object.assign(input, args);
|
|
46
|
+
const rawInput = parseObjectish(item.rawInput);
|
|
47
|
+
if (rawInput) Object.assign(input, rawInput);
|
|
41
48
|
if (item.action && typeof item.action === 'object') {
|
|
42
49
|
if (useful(item.action.query) || !useful(input.query)) input.query = item.action.query;
|
|
43
50
|
if (useful(item.action.url) || !useful(input.url)) input.url = item.action.url;
|
|
@@ -52,6 +59,18 @@ function useful(value) {
|
|
|
52
59
|
return value !== undefined && value !== null && value !== '';
|
|
53
60
|
}
|
|
54
61
|
|
|
62
|
+
function parseObjectish(value) {
|
|
63
|
+
if (!value) return null;
|
|
64
|
+
if (typeof value === 'object' && !Array.isArray(value)) return value;
|
|
65
|
+
if (typeof value !== 'string') return null;
|
|
66
|
+
try {
|
|
67
|
+
const parsed = JSON.parse(value);
|
|
68
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
55
74
|
function codexToolOutput(item = {}) {
|
|
56
75
|
if (item.status === 'failed' && item.error) return '';
|
|
57
76
|
const direct = item.aggregatedOutput ?? item.output ?? item.result ?? item.diff ?? item.error ?? item.message ?? item.text ?? item.finalText;
|
|
@@ -122,7 +141,7 @@ function codexDelta(msg, p = {}) {
|
|
|
122
141
|
}
|
|
123
142
|
|
|
124
143
|
function codexItemId(p = {}) {
|
|
125
|
-
return p.itemId || p.item
|
|
144
|
+
return p.itemId || codexToolCallId(p.item) || p.id || p.callId || p.call_id || p.toolCallId || p.tool_call_id;
|
|
126
145
|
}
|
|
127
146
|
|
|
128
147
|
function codexTokenTotal(value = {}) {
|
|
@@ -248,11 +267,12 @@ function normalizeCodexNotification(msg, providerSessionId, state) {
|
|
|
248
267
|
})];
|
|
249
268
|
}
|
|
250
269
|
if (method === 'item/started' && isCodexToolItem(p.item)) {
|
|
270
|
+
const toolCallId = codexToolCallId(p.item);
|
|
251
271
|
const toolName = codexToolName(p.item);
|
|
252
272
|
const input = codexToolInput(p.item);
|
|
253
273
|
const detail = codexToolDetail(p.item);
|
|
254
274
|
const metadata = codexToolMetadata(p.item);
|
|
255
|
-
return [toolStarted(
|
|
275
|
+
return [toolStarted(toolCallId, {
|
|
256
276
|
providerSessionId,
|
|
257
277
|
toolName,
|
|
258
278
|
title: codexToolTitle(p.item),
|
|
@@ -265,10 +285,11 @@ function normalizeCodexNotification(msg, providerSessionId, state) {
|
|
|
265
285
|
})];
|
|
266
286
|
}
|
|
267
287
|
if (method === 'item/updated' && isCodexToolItem(p.item)) {
|
|
288
|
+
const toolCallId = codexToolCallId(p.item);
|
|
268
289
|
const input = codexToolInput(p.item);
|
|
269
290
|
const toolName = codexToolName(p.item);
|
|
270
291
|
const metadata = codexToolMetadata(p.item);
|
|
271
|
-
return [toolUpdated(
|
|
292
|
+
return [toolUpdated(toolCallId, {
|
|
272
293
|
providerSessionId,
|
|
273
294
|
toolName,
|
|
274
295
|
title: codexToolTitle(p.item),
|
|
@@ -282,13 +303,14 @@ function normalizeCodexNotification(msg, providerSessionId, state) {
|
|
|
282
303
|
})];
|
|
283
304
|
}
|
|
284
305
|
if (method === 'item/completed' && isCodexToolItem(p.item)) {
|
|
306
|
+
const toolCallId = codexToolCallId(p.item);
|
|
285
307
|
const toolName = codexToolName(p.item);
|
|
286
308
|
const input = codexToolInput(p.item);
|
|
287
309
|
const output = codexToolOutput(p.item);
|
|
288
310
|
const error = codexToolError(p.item);
|
|
289
311
|
const metadata = codexToolMetadata(p.item);
|
|
290
312
|
const detail = codexToolDetail(p.item);
|
|
291
|
-
return [toolCompleted(
|
|
313
|
+
return [toolCompleted(toolCallId, {
|
|
292
314
|
providerSessionId,
|
|
293
315
|
toolName,
|
|
294
316
|
title: codexToolTitle(p.item),
|
|
@@ -384,6 +406,7 @@ module.exports = {
|
|
|
384
406
|
codexToolInput,
|
|
385
407
|
codexToolDetail,
|
|
386
408
|
codexToolError,
|
|
409
|
+
codexToolCallId,
|
|
387
410
|
codexToolName,
|
|
388
411
|
codexToolOutput,
|
|
389
412
|
codexToolTitle,
|
|
@@ -1,37 +1,54 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const assert = require('node:assert/strict');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const os = require('node:os');
|
|
6
|
+
const path = require('node:path');
|
|
4
7
|
const test = require('node:test');
|
|
5
8
|
const { authEnvelope, runtimeEnv } = require('../auth');
|
|
6
9
|
|
|
7
|
-
test('
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
10
|
+
test('codex always uses one canonical CLI home across auth methods and token refreshes', () => {
|
|
11
|
+
const amalgmDir = fs.mkdtempSync(path.join(os.tmpdir(), 'amalgm-auth-test-'));
|
|
12
|
+
fs.writeFileSync(path.join(amalgmDir, '.amalgm-credentials'), 'OPENAI_API_KEY=sk-test\n');
|
|
13
|
+
try {
|
|
14
|
+
const first = authEnvelope({
|
|
15
|
+
harness: 'codex',
|
|
16
|
+
authMethod: 'amalgm',
|
|
17
|
+
sessionId: 'session-one',
|
|
18
|
+
proxyToken: 'proxy-token-one',
|
|
19
|
+
localBaseUrl: 'http://127.0.0.1:8084',
|
|
20
|
+
proxyBaseUrl: 'https://proxy.example.test',
|
|
21
|
+
amalgmDir,
|
|
22
|
+
userId: 'user-123',
|
|
23
|
+
});
|
|
24
|
+
const second = authEnvelope({
|
|
25
|
+
harness: 'codex',
|
|
26
|
+
authMethod: 'provider_auth',
|
|
27
|
+
sessionId: 'session-two',
|
|
28
|
+
proxyToken: 'proxy-token-two',
|
|
29
|
+
localBaseUrl: 'http://127.0.0.1:8084',
|
|
30
|
+
proxyBaseUrl: 'https://proxy.example.test',
|
|
31
|
+
amalgmDir,
|
|
32
|
+
userId: 'user-123',
|
|
33
|
+
});
|
|
34
|
+
const third = authEnvelope({
|
|
35
|
+
harness: 'codex',
|
|
36
|
+
authMethod: 'byok',
|
|
37
|
+
sessionId: 'session-three',
|
|
38
|
+
amalgmDir,
|
|
39
|
+
userId: 'user-123',
|
|
40
|
+
});
|
|
28
41
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
42
|
+
assert.notEqual(first.tokenFingerprint, second.tokenFingerprint);
|
|
43
|
+
assert.equal(first.runtimeHome, second.runtimeHome);
|
|
44
|
+
assert.equal(second.runtimeHome, third.runtimeHome);
|
|
45
|
+
assert.equal(first.runtimeHome, path.join(amalgmDir, 'cli-homes', 'codex', 'home'));
|
|
46
|
+
} finally {
|
|
47
|
+
fs.rmSync(amalgmDir, { recursive: true, force: true });
|
|
48
|
+
}
|
|
32
49
|
});
|
|
33
50
|
|
|
34
|
-
test('opencode
|
|
51
|
+
test('opencode always uses one canonical CLI home across auth methods and token refreshes', () => {
|
|
35
52
|
const first = authEnvelope({
|
|
36
53
|
harness: 'opencode',
|
|
37
54
|
authMethod: 'amalgm',
|
|
@@ -44,7 +61,7 @@ test('opencode amalgm auth uses a pinned CLI home across sessions and proxy toke
|
|
|
44
61
|
});
|
|
45
62
|
const second = authEnvelope({
|
|
46
63
|
harness: 'opencode',
|
|
47
|
-
authMethod: '
|
|
64
|
+
authMethod: 'provider_auth',
|
|
48
65
|
sessionId: 'session-two',
|
|
49
66
|
proxyToken: 'proxy-token-two',
|
|
50
67
|
localBaseUrl: 'http://127.0.0.1:8084',
|
|
@@ -56,13 +73,15 @@ test('opencode amalgm auth uses a pinned CLI home across sessions and proxy toke
|
|
|
56
73
|
|
|
57
74
|
assert.notEqual(first.tokenFingerprint, second.tokenFingerprint);
|
|
58
75
|
assert.equal(first.runtimeHome, second.runtimeHome);
|
|
59
|
-
assert.
|
|
76
|
+
assert.equal(first.runtimeHome, '/tmp/amalgm-test/cli-homes/opencode/home');
|
|
60
77
|
assert.equal(env.HOME, first.runtimeHome);
|
|
61
78
|
assert.equal(env.OPENCODE_HOME, first.runtimeHome);
|
|
62
79
|
assert.equal(env.OPENCODE_CONFIG_DIR, first.runtimeHome);
|
|
80
|
+
assert.equal(env.XDG_CONFIG_HOME, '/tmp/amalgm-test/cli-homes/opencode/home/.config');
|
|
81
|
+
assert.equal(env.XDG_DATA_HOME, '/tmp/amalgm-test/cli-homes/opencode/home/.local/share');
|
|
63
82
|
});
|
|
64
83
|
|
|
65
|
-
test('claude
|
|
84
|
+
test('claude always uses one canonical CLI home', () => {
|
|
66
85
|
const envelope = authEnvelope({
|
|
67
86
|
harness: 'claude_code',
|
|
68
87
|
authMethod: 'provider_auth',
|
|
@@ -70,7 +89,7 @@ test('claude provider auth uses a pinned CLI home with native config copied sepa
|
|
|
70
89
|
amalgmDir: '/tmp/amalgm-test',
|
|
71
90
|
});
|
|
72
91
|
|
|
73
|
-
assert.
|
|
92
|
+
assert.equal(envelope.runtimeHome, '/tmp/amalgm-test/cli-homes/claude_code/home');
|
|
74
93
|
|
|
75
94
|
const env = runtimeEnv({
|
|
76
95
|
harness: 'claude_code',
|
|
@@ -88,19 +107,7 @@ test('claude provider auth uses a pinned CLI home with native config copied sepa
|
|
|
88
107
|
assert.equal(env.ANTHROPIC_API_KEY, undefined);
|
|
89
108
|
});
|
|
90
109
|
|
|
91
|
-
test('
|
|
92
|
-
const envelope = authEnvelope({
|
|
93
|
-
harness: 'codex',
|
|
94
|
-
authMethod: 'provider_auth',
|
|
95
|
-
sessionId: 'session-test',
|
|
96
|
-
amalgmDir: '/tmp/amalgm-test',
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
assert.match(envelope.runtimeHome, /\/tmp\/amalgm-test\/cli-homes\/codex\/provider-[0-9a-f]{16}$/);
|
|
100
|
-
assert.equal(envelope.runtimeHome.includes('/runtime/session-test/'), false);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test('stored CLI home path wins over derived path', () => {
|
|
110
|
+
test('provided CLI home path cannot move an agent off its canonical home', () => {
|
|
104
111
|
const envelope = authEnvelope({
|
|
105
112
|
harness: 'codex',
|
|
106
113
|
authMethod: 'amalgm',
|
|
@@ -114,6 +121,5 @@ test('stored CLI home path wins over derived path', () => {
|
|
|
114
121
|
authProfileId: 'manual-profile',
|
|
115
122
|
});
|
|
116
123
|
|
|
117
|
-
assert.equal(envelope.
|
|
118
|
-
assert.equal(envelope.runtimeHome, '/tmp/amalgm-test/cli-homes/codex/manual-profile');
|
|
124
|
+
assert.equal(envelope.runtimeHome, '/tmp/amalgm-test/cli-homes/codex/home');
|
|
119
125
|
});
|
|
@@ -14,7 +14,7 @@ function payload(overrides = {}) {
|
|
|
14
14
|
userId: 'user-test',
|
|
15
15
|
agentId: 'claude_code',
|
|
16
16
|
authMethod: 'amalgm',
|
|
17
|
-
modelId: 'anthropic/claude-opus-4.
|
|
17
|
+
modelId: 'anthropic/claude-opus-4.8',
|
|
18
18
|
cwd: process.cwd(),
|
|
19
19
|
prompt: 'hello',
|
|
20
20
|
proxyToken: 'proxy-token',
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const assert = require('node:assert/strict');
|
|
4
|
+
const { spawnSync } = require('node:child_process');
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const os = require('node:os');
|
|
7
|
+
const path = require('node:path');
|
|
8
|
+
const test = require('node:test');
|
|
9
|
+
const nativeBinaries = require('../tooling/native-binaries');
|
|
10
|
+
|
|
11
|
+
function packagePath(root, packageName) {
|
|
12
|
+
return path.join(root, ...String(packageName).split('/'));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function writePackage(root, packageName) {
|
|
16
|
+
fs.mkdirSync(root, { recursive: true });
|
|
17
|
+
fs.writeFileSync(path.join(root, 'package.json'), `${JSON.stringify({ name: packageName, version: '0.0.0' }, null, 2)}\n`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function writeExecutable(file) {
|
|
21
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
22
|
+
if (process.platform === 'win32') {
|
|
23
|
+
fs.writeFileSync(file, '@echo off\r\nexit /b 0\r\n');
|
|
24
|
+
} else {
|
|
25
|
+
fs.writeFileSync(file, '#!/bin/sh\nexit 0\n');
|
|
26
|
+
fs.chmodSync(file, 0o755);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function installFakePackagedAgentDeps(resourcesPath) {
|
|
31
|
+
const nodeModules = path.join(resourcesPath, 'app.asar.unpacked', 'node_modules');
|
|
32
|
+
const claudePackage = nativeBinaries.__private.claudeTargetPackage();
|
|
33
|
+
const codexTarget = nativeBinaries.__private.codexTarget();
|
|
34
|
+
const openCodePackage = nativeBinaries.__private.openCodePackageCandidates()[0];
|
|
35
|
+
assert.ok(claudePackage, 'current platform should have a Claude native package');
|
|
36
|
+
assert.ok(codexTarget, 'current platform should have a Codex native package');
|
|
37
|
+
assert.ok(openCodePackage, 'current platform should have an OpenCode native package');
|
|
38
|
+
|
|
39
|
+
writePackage(path.join(nodeModules, '@anthropic-ai', 'claude-agent-sdk'), '@anthropic-ai/claude-agent-sdk');
|
|
40
|
+
writePackage(path.join(nodeModules, '@openai', 'codex'), '@openai/codex');
|
|
41
|
+
writePackage(path.join(nodeModules, 'opencode-ai'), 'opencode-ai');
|
|
42
|
+
|
|
43
|
+
const claudeRoot = packagePath(
|
|
44
|
+
path.join(nodeModules, '@anthropic-ai', 'claude-agent-sdk', 'node_modules'),
|
|
45
|
+
claudePackage,
|
|
46
|
+
);
|
|
47
|
+
writePackage(claudeRoot, claudePackage);
|
|
48
|
+
const claude = path.join(claudeRoot, process.platform === 'win32' ? 'claude.exe' : 'claude');
|
|
49
|
+
writeExecutable(claude);
|
|
50
|
+
|
|
51
|
+
const codexRoot = packagePath(
|
|
52
|
+
path.join(nodeModules, '@openai', 'codex', 'node_modules'),
|
|
53
|
+
codexTarget.packageName,
|
|
54
|
+
);
|
|
55
|
+
writePackage(codexRoot, codexTarget.packageName);
|
|
56
|
+
const codex = path.join(codexRoot, 'vendor', codexTarget.triple, 'codex', codexTarget.binaryName);
|
|
57
|
+
writeExecutable(codex);
|
|
58
|
+
|
|
59
|
+
const openCodeRoot = packagePath(
|
|
60
|
+
path.join(nodeModules, 'opencode-ai', 'node_modules'),
|
|
61
|
+
openCodePackage,
|
|
62
|
+
);
|
|
63
|
+
writePackage(openCodeRoot, openCodePackage);
|
|
64
|
+
const opencode = path.join(openCodeRoot, 'bin', process.platform === 'win32' ? 'opencode.exe' : 'opencode');
|
|
65
|
+
writeExecutable(opencode);
|
|
66
|
+
|
|
67
|
+
return { claude, codex, opencode };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function withFakePackagedResources(fn) {
|
|
71
|
+
const previousResourcesPath = Object.getOwnPropertyDescriptor(process, 'resourcesPath');
|
|
72
|
+
const previousEnv = {
|
|
73
|
+
AMALGM_AGENT_BIN_DIR: process.env.AMALGM_AGENT_BIN_DIR,
|
|
74
|
+
AMALGM_NATIVE_NODE_MODULES: process.env.AMALGM_NATIVE_NODE_MODULES,
|
|
75
|
+
CLAUDE_CODE_BINARY: process.env.CLAUDE_CODE_BINARY,
|
|
76
|
+
PATH: process.env.PATH,
|
|
77
|
+
};
|
|
78
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'amalgm-native-binaries-'));
|
|
79
|
+
const resourcesPath = path.join(root, 'resources');
|
|
80
|
+
const binDir = path.join(root, 'bin');
|
|
81
|
+
try {
|
|
82
|
+
Object.defineProperty(process, 'resourcesPath', {
|
|
83
|
+
value: resourcesPath,
|
|
84
|
+
configurable: true,
|
|
85
|
+
});
|
|
86
|
+
process.env.AMALGM_AGENT_BIN_DIR = binDir;
|
|
87
|
+
process.env.PATH = '';
|
|
88
|
+
delete process.env.AMALGM_NATIVE_NODE_MODULES;
|
|
89
|
+
delete process.env.CLAUDE_CODE_BINARY;
|
|
90
|
+
return fn({ root, resourcesPath, binDir });
|
|
91
|
+
} finally {
|
|
92
|
+
if (previousResourcesPath) {
|
|
93
|
+
Object.defineProperty(process, 'resourcesPath', previousResourcesPath);
|
|
94
|
+
} else {
|
|
95
|
+
delete process.resourcesPath;
|
|
96
|
+
}
|
|
97
|
+
for (const [key, value] of Object.entries(previousEnv)) {
|
|
98
|
+
if (value === undefined) delete process.env[key];
|
|
99
|
+
else process.env[key] = value;
|
|
100
|
+
}
|
|
101
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
test('packaged nested agent dependencies resolve to exact executable paths and spawnable shims', () => {
|
|
106
|
+
withFakePackagedResources(({ resourcesPath, binDir }) => {
|
|
107
|
+
const expected = installFakePackagedAgentDeps(resourcesPath);
|
|
108
|
+
|
|
109
|
+
assert.equal(nativeBinaries.__private.bundledClaudeBinary(), expected.claude);
|
|
110
|
+
assert.equal(nativeBinaries.bundledCodexBinary(), expected.codex);
|
|
111
|
+
assert.equal(nativeBinaries.bundledOpenCodeBinary(), expected.opencode);
|
|
112
|
+
|
|
113
|
+
const result = nativeBinaries.ensureAgentCommandShims({ quiet: true, logger: { log() {} } });
|
|
114
|
+
assert.equal(result.ok, true);
|
|
115
|
+
for (const command of ['claude', 'codex', 'opencode']) {
|
|
116
|
+
const shim = path.join(binDir, process.platform === 'win32' ? `${command}.cmd` : command);
|
|
117
|
+
assert.equal(fs.existsSync(shim), true);
|
|
118
|
+
const spawned = spawnSync(shim, ['--smoke'], {
|
|
119
|
+
stdio: 'ignore',
|
|
120
|
+
shell: process.platform === 'win32',
|
|
121
|
+
windowsHide: true,
|
|
122
|
+
});
|
|
123
|
+
assert.equal(spawned.status, 0, `${command} shim should spawn`);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
});
|