@gakim-digital/dexter-bridge 0.5.20 → 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/src/config.js CHANGED
@@ -17,6 +17,18 @@ export const BRIDGE_BUILD_FINGERPRINT = crypto
17
17
  './cli.js',
18
18
  './config.js',
19
19
  './protocol.js',
20
+ './nativeSkills.js',
21
+ './harnessTools.js',
22
+ './harnessMcpServer.js',
23
+ './framerAgentTools.js',
24
+ './outcomeWorkspace.js',
25
+ './runtimeProfiles.js',
26
+ './providers/index.js',
27
+ './providers/codexAppServer.js',
28
+ './providers/codexStructuredOutput.js',
29
+ './providers/directByok.js',
30
+ './providers/openCode.js',
31
+ './providers/acp.js',
20
32
  ].map((relativePath) => {
21
33
  const url = new URL(relativePath, import.meta.url);
22
34
  return `${relativePath}\u0000${fs.readFileSync(url, 'utf8')}`;
@@ -24,15 +36,34 @@ export const BRIDGE_BUILD_FINGERPRINT = crypto
24
36
  .digest('hex');
25
37
  export const BRIDGE_CAPABILITIES = [
26
38
  'model-turn-v1',
39
+ 'outcome-run-v1',
40
+ 'harness-tools-v1',
41
+ 'framer-agent-v1',
42
+ 'framer-project-bootstrap-v1',
43
+ 'project-shell-v1',
27
44
  'build-fingerprint-v1',
28
45
  'tool-schema-parity-v1',
46
+ 'runtime-profiles-v1',
47
+ 'native-skills-v1',
48
+ 'model-progress-v1',
29
49
  ];
30
- // Codex is the default local agent: driving Claude Code from a user's Claude.ai
31
- // subscription needs prior written approval from Anthropic for commercial use, so
32
- // 'claude-code' only runs when the server offers it (see policy gating in
33
- // framerCompanionModels.ts and docs/dexter-connect-onboarding-plan.md §0).
50
+ export const BRIDGE_PRODUCTS = {
51
+ dexter: {
52
+ id: 'dexter',
53
+ name: 'Dexter',
54
+ connectionScope: 'framer',
55
+ },
56
+ instawebai: {
57
+ id: 'instawebai',
58
+ name: 'InstaWebAI',
59
+ connectionScope: 'app-builder',
60
+ },
61
+ };
62
+ // Codex remains the default local agent. Claude Code is enabled only when the
63
+ // user explicitly connects the locally installed CLI and chooses one of its
64
+ // reported models.
34
65
  export const DEFAULT_BRIDGE_AGENT = 'codex';
35
- export const BRIDGE_AGENTS = ['claude-code', 'codex', 'dry-run'];
66
+ export const BRIDGE_AGENTS = ['claude-code', 'codex', 'opencode', 'dry-run'];
36
67
  export const COMPANION_MODEL_DEFINITIONS = [
37
68
  {
38
69
  id: 'claude-code:fable',
@@ -146,17 +177,34 @@ export function defaultConfigDir(env = process.env) {
146
177
  return env.DEXTER_BRIDGE_CONFIG_DIR || path.join(os.homedir(), '.dexter-bridge');
147
178
  }
148
179
 
180
+ export function normalizeBridgeProduct(value) {
181
+ const id =
182
+ typeof value === 'string'
183
+ ? value.trim().toLowerCase()
184
+ : typeof value?.id === 'string'
185
+ ? value.id.trim().toLowerCase()
186
+ : '';
187
+ const connectionScope =
188
+ value && typeof value === 'object' && typeof value.connectionScope === 'string'
189
+ ? value.connectionScope.trim().toLowerCase()
190
+ : '';
191
+ if (id === 'instawebai' || connectionScope === 'app-builder') {
192
+ return BRIDGE_PRODUCTS.instawebai;
193
+ }
194
+ return BRIDGE_PRODUCTS.dexter;
195
+ }
196
+
149
197
  export function configFilePath(configDir = defaultConfigDir()) {
150
198
  return path.join(configDir, 'config.json');
151
199
  }
152
200
 
153
- export function normalizeApiBaseUrl(value) {
201
+ export function normalizeApiBaseUrl(value, { allowInsecureHttp = false } = {}) {
154
202
  const raw = String(value || DEFAULT_API_BASE_URL).trim();
155
203
  let parsed;
156
204
  try {
157
205
  parsed = new URL(raw);
158
206
  } catch {
159
- throw new Error('Dexter API URL must be a valid absolute URL.');
207
+ throw new Error('Bridge API URL must be a valid absolute URL.');
160
208
  }
161
209
  const hostname = parsed.hostname.replace(/^\[|\]$/g, '').toLowerCase();
162
210
  const loopback =
@@ -165,13 +213,16 @@ export function normalizeApiBaseUrl(value) {
165
213
  || hostname === '127.0.0.1'
166
214
  || hostname === '::1';
167
215
  if (parsed.username || parsed.password) {
168
- throw new Error('Dexter API URL must not contain embedded credentials.');
216
+ throw new Error('Bridge API URL must not contain embedded credentials.');
169
217
  }
170
- if (parsed.protocol !== 'https:' && !(parsed.protocol === 'http:' && loopback)) {
171
- throw new Error('Dexter API URL must use HTTPS. HTTP is allowed only for localhost development.');
218
+ if (
219
+ parsed.protocol !== 'https:'
220
+ && !(parsed.protocol === 'http:' && (loopback || allowInsecureHttp))
221
+ ) {
222
+ throw new Error('Bridge API URL must use HTTPS. HTTP is allowed only for localhost development.');
172
223
  }
173
224
  if (parsed.search || parsed.hash) {
174
- throw new Error('Dexter API URL must not contain a query string or fragment.');
225
+ throw new Error('Bridge API URL must not contain a query string or fragment.');
175
226
  }
176
227
  return parsed.toString().replace(/\/+$/, '');
177
228
  }
@@ -180,6 +231,7 @@ export function normalizeAgentName(value, fallback = DEFAULT_BRIDGE_AGENT) {
180
231
  const raw = String(value || '').trim().toLowerCase();
181
232
  if (raw === 'claude' || raw === 'claude_code' || raw === 'claude-code') return 'claude-code';
182
233
  if (raw === 'openai-codex' || raw === 'openai_codex' || raw === 'codex') return 'codex';
234
+ if (raw === 'open-code' || raw === 'open_code' || raw === 'opencode') return 'opencode';
183
235
  if (raw === 'dryrun' || raw === 'dry_run' || raw === 'dry-run') return 'dry-run';
184
236
  return BRIDGE_AGENTS.includes(fallback) ? fallback : DEFAULT_BRIDGE_AGENT;
185
237
  }
@@ -196,12 +248,27 @@ export function companionModelDefinition(value, agent = DEFAULT_BRIDGE_AGENT) {
196
248
  if (!raw || raw === 'local-companion') {
197
249
  return COMPANION_MODEL_DEFINITIONS.find((model) => model.id === DEFAULT_MODEL_BY_AGENT[normalizedAgent]);
198
250
  }
199
- return (
251
+ const known =
200
252
  candidates.find((model) => model.id.toLowerCase() === raw) ||
201
253
  candidates.find((model) => model.invocationName.toLowerCase() === raw) ||
202
- candidates.find((model) => model.id.toLowerCase() === `${normalizedAgent}:${raw}`) ||
203
- COMPANION_MODEL_DEFINITIONS.find((model) => model.id === DEFAULT_MODEL_BY_AGENT[normalizedAgent])
204
- );
254
+ candidates.find((model) => model.id.toLowerCase() === `${normalizedAgent}:${raw}`);
255
+ if (known) return known;
256
+ const prefix = `${normalizedAgent}:`;
257
+ if (raw.startsWith(prefix)) {
258
+ const invocationName = raw.slice(prefix.length);
259
+ if (/^[a-z0-9][a-z0-9._-]{0,159}$/.test(invocationName)) {
260
+ return {
261
+ id: `${normalizedAgent}:${invocationName}`,
262
+ agent: normalizedAgent,
263
+ provider: normalizedAgent === 'codex' ? 'openai' : normalizedAgent === 'claude-code' ? 'anthropic' : 'companion',
264
+ displayName: invocationName,
265
+ invocationName,
266
+ costTier: '$$',
267
+ description: `${normalizedAgent === 'codex' ? 'Codex' : 'Claude Code'} model reported by the connected bridge.`,
268
+ };
269
+ }
270
+ }
271
+ return COMPANION_MODEL_DEFINITIONS.find((model) => model.id === DEFAULT_MODEL_BY_AGENT[normalizedAgent]);
205
272
  }
206
273
 
207
274
  export function normalizeCompanionModelName(value, agent = DEFAULT_BRIDGE_AGENT) {