@gakim-digital/dexter-bridge 0.5.21 → 0.11.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/README.md +116 -35
- package/package.json +19 -5
- package/src/agent.js +1351 -331
- package/src/agentOutput.js +209 -0
- package/src/api.js +48 -1
- package/src/cli.js +267 -39
- package/src/config.js +30 -7
- package/src/framerAgentTools.js +1108 -0
- package/src/harnessMcpServer.js +240 -0
- package/src/harnessTools.js +548 -0
- package/src/logger.js +1 -1
- package/src/nativeSkills.js +295 -0
- package/src/outcomeWorkspace.js +351 -0
- package/src/protocol.js +239 -0
- package/src/providers/acp.js +241 -0
- package/src/providers/codexAppServer.js +1050 -156
- package/src/providers/codexStructuredOutput.js +243 -16
- package/src/providers/directByok.js +197 -0
- package/src/providers/index.js +33 -7
- package/src/providers/openCode.js +607 -0
- package/src/runtimeProfiles.js +284 -0
- package/src/providers/claudeAgentSdk.js +0 -507
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { createLocalAgentAdapter } from './providers/index.js';
|
|
3
|
+
|
|
4
|
+
const DRIVER_LABELS = {
|
|
5
|
+
'codex-app-server': 'Codex',
|
|
6
|
+
'claude-code-cli': 'Claude Code',
|
|
7
|
+
'direct-byok': 'Local API key',
|
|
8
|
+
opencode: 'OpenCode',
|
|
9
|
+
acp: 'ACP agent',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const APP_BUILDER_RUNTIME_SELECTIONS = ['codex', 'claude-code', 'opencode'];
|
|
13
|
+
|
|
14
|
+
export function normalizeRuntimeSelection(value) {
|
|
15
|
+
const normalized = text(value, 80).toLowerCase();
|
|
16
|
+
return APP_BUILDER_RUNTIME_SELECTIONS.includes(normalized) ? normalized : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function runtimeDriver(selection) {
|
|
20
|
+
if (selection === 'codex') return 'codex-app-server';
|
|
21
|
+
if (selection === 'claude-code') return 'claude-code-cli';
|
|
22
|
+
if (selection === 'opencode') return 'opencode';
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function text(value, maximum = 4096) {
|
|
27
|
+
return typeof value === 'string' && value.trim() ? value.trim().slice(0, maximum) : '';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function stableIdentity(driver, instanceId) {
|
|
31
|
+
return crypto.createHash('sha256').update(`${driver}\0${instanceId}`).digest('hex').slice(0, 32);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function normalizeCapabilities(value = {}) {
|
|
35
|
+
return {
|
|
36
|
+
tools: value.tools !== false,
|
|
37
|
+
vision: value.vision === true,
|
|
38
|
+
structuredOutput: value.structuredOutput !== false,
|
|
39
|
+
sessionResume: value.sessionResume !== false,
|
|
40
|
+
outcomeExecution: value.outcomeExecution === true,
|
|
41
|
+
workspaceSnapshot: value.workspaceSnapshot === true,
|
|
42
|
+
workspaceWrite: value.workspaceWrite === true,
|
|
43
|
+
shell: value.shell === true,
|
|
44
|
+
projectShell: value.projectShell === true,
|
|
45
|
+
preview: value.preview === true,
|
|
46
|
+
browser: value.browser === true,
|
|
47
|
+
dataInspection: value.dataInspection === true,
|
|
48
|
+
verification: value.verification === true,
|
|
49
|
+
standardTools: value.standardTools === true,
|
|
50
|
+
progress: value.progress !== false,
|
|
51
|
+
cancellation: value.cancellation !== false,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function normalizeRuntimeProfile(value, { includeSecret = false } = {}) {
|
|
56
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
57
|
+
const driver = text(value.driver, 80);
|
|
58
|
+
const instanceId = text(value.instanceId || value.id, 160);
|
|
59
|
+
if (!driver || !instanceId) return null;
|
|
60
|
+
const id = text(value.id, 180) || `${driver}:${instanceId}`;
|
|
61
|
+
const models = (Array.isArray(value.models) ? value.models : []).slice(0, 100).flatMap((model) => {
|
|
62
|
+
if (!model || typeof model !== 'object' || Array.isArray(model)) return [];
|
|
63
|
+
const modelId = text(model.id, 255);
|
|
64
|
+
if (!modelId) return [];
|
|
65
|
+
return [
|
|
66
|
+
{
|
|
67
|
+
id: modelId,
|
|
68
|
+
provider: text(model.provider, 80) || text(value.providerId, 80) || driver,
|
|
69
|
+
displayName: text(model.displayName, 160) || modelId,
|
|
70
|
+
invocationName: text(model.invocationName, 255) || modelId,
|
|
71
|
+
costTier: ['$', '$$', '$$$'].includes(model.costTier) ? model.costTier : '$$',
|
|
72
|
+
description: text(model.description, 500),
|
|
73
|
+
capabilities: normalizeCapabilities(model.capabilities || value.capabilities),
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
});
|
|
77
|
+
const profile = {
|
|
78
|
+
id,
|
|
79
|
+
instanceId,
|
|
80
|
+
label: text(value.label, 160) || DRIVER_LABELS[driver] || driver,
|
|
81
|
+
location: 'local',
|
|
82
|
+
driver,
|
|
83
|
+
providerId: text(value.providerId, 80) || driver,
|
|
84
|
+
authState: ['ready', 'required', 'error'].includes(value.authState) ? value.authState : 'ready',
|
|
85
|
+
continuationIdentity: text(value.continuationIdentity, 180) || stableIdentity(driver, instanceId),
|
|
86
|
+
capabilities: normalizeCapabilities(value.capabilities),
|
|
87
|
+
models,
|
|
88
|
+
...(text(value.keyLast4, 16) ? { keyLast4: text(value.keyLast4, 16) } : {}),
|
|
89
|
+
...(text(value.command, 2048) ? { command: text(value.command, 2048) } : {}),
|
|
90
|
+
...(Array.isArray(value.args)
|
|
91
|
+
? {
|
|
92
|
+
args: value.args
|
|
93
|
+
.map((item) => text(item, 1024))
|
|
94
|
+
.filter(Boolean)
|
|
95
|
+
.slice(0, 40),
|
|
96
|
+
}
|
|
97
|
+
: {}),
|
|
98
|
+
...(text(value.cwd, 2048) ? { cwd: text(value.cwd, 2048) } : {}),
|
|
99
|
+
};
|
|
100
|
+
if (includeSecret && value.credentials) profile.credentials = value.credentials;
|
|
101
|
+
return profile;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function publicRuntimeProfile(value) {
|
|
105
|
+
const profile = normalizeRuntimeProfile(value);
|
|
106
|
+
if (!profile) return null;
|
|
107
|
+
const { credentials: _credentials, command: _command, args: _args, cwd: _cwd, ...publicProfile } = profile;
|
|
108
|
+
return publicProfile;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function profileFromAgentCheck(check) {
|
|
112
|
+
if (!check || check.installed === false) return null;
|
|
113
|
+
const driver =
|
|
114
|
+
check.agent === 'codex' ? 'codex-app-server' : check.agent === 'claude-code' ? 'claude-code-cli' : null;
|
|
115
|
+
if (!driver) return null;
|
|
116
|
+
const codexProviderId = check.agent === 'codex' ? text(check.providerId, 80) || 'openai' : null;
|
|
117
|
+
const codexProviderName = check.agent === 'codex' ? text(check.providerName, 160) : '';
|
|
118
|
+
return normalizeRuntimeProfile({
|
|
119
|
+
id: `${check.agent}:default`,
|
|
120
|
+
instanceId: 'default',
|
|
121
|
+
label:
|
|
122
|
+
codexProviderName && (codexProviderId !== 'openai' || check.fallbackProviderUsed)
|
|
123
|
+
? `Codex · ${codexProviderName}${check.fallbackProviderUsed ? ' fallback' : ''}`
|
|
124
|
+
: DRIVER_LABELS[driver],
|
|
125
|
+
driver,
|
|
126
|
+
providerId: check.agent === 'codex' ? codexProviderId : 'anthropic',
|
|
127
|
+
authState: check.signedIn === false ? 'required' : 'ready',
|
|
128
|
+
capabilities: {
|
|
129
|
+
tools: true,
|
|
130
|
+
vision: true,
|
|
131
|
+
structuredOutput: true,
|
|
132
|
+
sessionResume: true,
|
|
133
|
+
outcomeExecution: true,
|
|
134
|
+
workspaceSnapshot: true,
|
|
135
|
+
workspaceWrite: true,
|
|
136
|
+
shell: true,
|
|
137
|
+
projectShell: true,
|
|
138
|
+
preview: true,
|
|
139
|
+
browser: true,
|
|
140
|
+
dataInspection: true,
|
|
141
|
+
verification: true,
|
|
142
|
+
standardTools: true,
|
|
143
|
+
progress: true,
|
|
144
|
+
cancellation: true,
|
|
145
|
+
},
|
|
146
|
+
models: check.modelDetails || [],
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function localByokProfiles(config) {
|
|
151
|
+
return (Array.isArray(config?.localCredentials) ? config.localCredentials : []).flatMap((credential) => {
|
|
152
|
+
const provider = text(credential?.provider, 80).toLowerCase();
|
|
153
|
+
const model = text(credential?.model, 255);
|
|
154
|
+
const instanceId = text(credential?.id, 160);
|
|
155
|
+
if (!provider || !model || !instanceId || !text(credential?.apiKey, 8192)) return [];
|
|
156
|
+
const profile = normalizeRuntimeProfile(
|
|
157
|
+
{
|
|
158
|
+
id: `local-byok:${instanceId}`,
|
|
159
|
+
instanceId,
|
|
160
|
+
label: text(credential.label, 160) || `${provider} local key`,
|
|
161
|
+
driver: 'direct-byok',
|
|
162
|
+
providerId: provider,
|
|
163
|
+
continuationIdentity: text(credential.continuationIdentity, 180) || stableIdentity('direct-byok', instanceId),
|
|
164
|
+
keyLast4: text(credential.apiKey, 8192).slice(-4),
|
|
165
|
+
capabilities: {
|
|
166
|
+
tools: true,
|
|
167
|
+
vision: true,
|
|
168
|
+
structuredOutput: true,
|
|
169
|
+
sessionResume: false,
|
|
170
|
+
},
|
|
171
|
+
models: [
|
|
172
|
+
{
|
|
173
|
+
id: `local-byok:${instanceId}:${model}`,
|
|
174
|
+
provider,
|
|
175
|
+
displayName: model,
|
|
176
|
+
invocationName: model,
|
|
177
|
+
costTier: '$$',
|
|
178
|
+
description: `${provider} model using a key stored on this computer.`,
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
credentials: {
|
|
182
|
+
provider,
|
|
183
|
+
apiKey: credential.apiKey,
|
|
184
|
+
model,
|
|
185
|
+
...(text(credential.baseURL, 2048) ? { baseURL: text(credential.baseURL, 2048) } : {}),
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
{ includeSecret: true },
|
|
189
|
+
);
|
|
190
|
+
return profile ? [profile] : [];
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function configuredAcpProfiles(config) {
|
|
195
|
+
return (Array.isArray(config?.acpProfiles) ? config.acpProfiles : [])
|
|
196
|
+
.map((profile) =>
|
|
197
|
+
normalizeRuntimeProfile({
|
|
198
|
+
...profile,
|
|
199
|
+
id: text(profile?.id, 180) || `acp:${crypto.randomUUID()}`,
|
|
200
|
+
driver: 'acp',
|
|
201
|
+
providerId: text(profile?.providerId, 80) || 'acp',
|
|
202
|
+
}),
|
|
203
|
+
)
|
|
204
|
+
.filter(Boolean);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export async function discoverRuntimeProfiles({
|
|
208
|
+
agentChecks = [],
|
|
209
|
+
config = {},
|
|
210
|
+
env = process.env,
|
|
211
|
+
trace,
|
|
212
|
+
runtimeSelection,
|
|
213
|
+
} = {}) {
|
|
214
|
+
const selectedRuntime = normalizeRuntimeSelection(runtimeSelection);
|
|
215
|
+
const selectedDriver = runtimeDriver(selectedRuntime);
|
|
216
|
+
const profiles = agentChecks
|
|
217
|
+
.map(profileFromAgentCheck)
|
|
218
|
+
.filter(Boolean)
|
|
219
|
+
.filter((profile) => !selectedDriver || profile.driver === selectedDriver);
|
|
220
|
+
if (!selectedRuntime) {
|
|
221
|
+
profiles.push(...localByokProfiles(config));
|
|
222
|
+
profiles.push(...configuredAcpProfiles(config));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (
|
|
226
|
+
(!selectedRuntime || selectedRuntime === 'opencode') &&
|
|
227
|
+
String(env.DEXTER_BRIDGE_OPENCODE_ENABLED || 'true').toLowerCase() !== 'false'
|
|
228
|
+
) {
|
|
229
|
+
const profile = normalizeRuntimeProfile({
|
|
230
|
+
id: 'opencode:default',
|
|
231
|
+
instanceId: 'default',
|
|
232
|
+
label: 'OpenCode',
|
|
233
|
+
driver: 'opencode',
|
|
234
|
+
providerId: 'opencode',
|
|
235
|
+
command: env.DEXTER_BRIDGE_OPENCODE_BIN || 'opencode',
|
|
236
|
+
capabilities: {
|
|
237
|
+
tools: true,
|
|
238
|
+
vision: true,
|
|
239
|
+
structuredOutput: true,
|
|
240
|
+
sessionResume: true,
|
|
241
|
+
outcomeExecution: true,
|
|
242
|
+
workspaceSnapshot: true,
|
|
243
|
+
workspaceWrite: true,
|
|
244
|
+
shell: true,
|
|
245
|
+
projectShell: true,
|
|
246
|
+
preview: true,
|
|
247
|
+
browser: true,
|
|
248
|
+
dataInspection: true,
|
|
249
|
+
verification: true,
|
|
250
|
+
standardTools: true,
|
|
251
|
+
progress: true,
|
|
252
|
+
cancellation: true,
|
|
253
|
+
},
|
|
254
|
+
models: [],
|
|
255
|
+
});
|
|
256
|
+
const adapter = createLocalAgentAdapter(profile, { env, trace });
|
|
257
|
+
try {
|
|
258
|
+
const status = await adapter.detect();
|
|
259
|
+
if (status.ok) {
|
|
260
|
+
const models = await adapter.models().catch(() => []);
|
|
261
|
+
profiles.push(
|
|
262
|
+
normalizeRuntimeProfile({
|
|
263
|
+
...profile,
|
|
264
|
+
authState: status.signedIn === false ? 'required' : 'ready',
|
|
265
|
+
models,
|
|
266
|
+
}),
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
} finally {
|
|
270
|
+
adapter?.close?.();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return profiles.filter(Boolean).filter((profile) => !selectedDriver || profile.driver === selectedDriver);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export function runtimeProfileMap(profiles = []) {
|
|
278
|
+
return new Map(
|
|
279
|
+
profiles
|
|
280
|
+
.map((profile) => normalizeRuntimeProfile(profile, { includeSecret: true }))
|
|
281
|
+
.filter(Boolean)
|
|
282
|
+
.map((profile) => [profile.id, profile]),
|
|
283
|
+
);
|
|
284
|
+
}
|