@gakim-digital/dexter-bridge 0.11.6 → 0.11.9

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.
@@ -71,6 +71,20 @@ function normalizeCapabilities(value = {}) {
71
71
  };
72
72
  }
73
73
 
74
+ function normalizeModelCatalog(value = {}) {
75
+ const source = ['runtime', 'fallback', 'stale', 'error'].includes(value.source)
76
+ ? value.source
77
+ : 'fallback';
78
+ return {
79
+ source,
80
+ refreshedAt: text(value.refreshedAt, 80) || new Date().toISOString(),
81
+ modelCount: Number.isFinite(Number(value.modelCount))
82
+ ? Math.max(0, Math.floor(Number(value.modelCount)))
83
+ : 0,
84
+ ...(text(value.error, 500) ? { error: text(value.error, 500) } : {}),
85
+ };
86
+ }
87
+
74
88
  export function normalizeRuntimeProfile(value, { includeSecret = false } = {}) {
75
89
  if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
76
90
  const driver = text(value.driver, 80);
@@ -110,6 +124,10 @@ export function normalizeRuntimeProfile(value, { includeSecret = false } = {}) {
110
124
  continuationIdentity: text(value.continuationIdentity, 180) || stableIdentity(driver, instanceId),
111
125
  capabilities: normalizeCapabilities(value.capabilities),
112
126
  models,
127
+ modelCatalog: normalizeModelCatalog({
128
+ ...(value.modelCatalog || {}),
129
+ modelCount: models.length,
130
+ }),
113
131
  ...(text(value.keyLast4, 16) ? { keyLast4: text(value.keyLast4, 16) } : {}),
114
132
  ...(text(value.command, 2048) ? { command: text(value.command, 2048) } : {}),
115
133
  ...(Array.isArray(value.args)
@@ -121,6 +139,9 @@ export function normalizeRuntimeProfile(value, { includeSecret = false } = {}) {
121
139
  }
122
140
  : {}),
123
141
  ...(text(value.cwd, 2048) ? { cwd: text(value.cwd, 2048) } : {}),
142
+ ...(normalizeDiagnostics(value.diagnostics)
143
+ ? { diagnostics: normalizeDiagnostics(value.diagnostics) }
144
+ : {}),
124
145
  };
125
146
  if (includeSecret && value.credentials) profile.credentials = value.credentials;
126
147
  return profile;
@@ -133,7 +154,104 @@ export function publicRuntimeProfile(value) {
133
154
  return publicProfile;
134
155
  }
135
156
 
136
- function profileFromAgentCheck(check) {
157
+ const DIAGNOSTIC_LEVELS = new Set(['info', 'warn', 'error']);
158
+
159
+ /**
160
+ * Structured environment checks for one local agent, shaped so the builder
161
+ * can show exactly what works and what to fix (installed, signed in, which
162
+ * credential is billed, and whether the model list came from the agent).
163
+ */
164
+ export function diagnosticsFromAgentCheck(check, env = process.env) {
165
+ const label = check?.label || check?.agent || 'Agent';
166
+ const checks = [];
167
+ const add = (level, code, message, hint) =>
168
+ checks.push({ level, code, message, ...(hint ? { hint } : {}) });
169
+ if (!check || check.installed === false || check.status === 'unavailable') {
170
+ add(
171
+ 'error',
172
+ 'cli_missing',
173
+ check?.error || `${label} is not installed on this computer.`,
174
+ check?.agent === 'codex'
175
+ ? 'Install Codex (npm i -g @openai/codex), then restart the bridge.'
176
+ : 'Install Claude Code (npm i -g @anthropic-ai/claude-code), then restart the bridge.',
177
+ );
178
+ } else {
179
+ add(
180
+ 'info',
181
+ 'cli_detected',
182
+ `${label}${check.version ? ` ${check.version}` : ''} detected.`,
183
+ );
184
+ if (check.signedIn === false) {
185
+ add(
186
+ 'error',
187
+ 'sign_in_required',
188
+ check.error || `${label} is not signed in.`,
189
+ check.agent === 'codex'
190
+ ? 'Run `codex login` in a terminal, then restart the bridge.'
191
+ : 'Run `claude` in a terminal and sign in, then restart the bridge.',
192
+ );
193
+ } else {
194
+ const method = text(check.authMethod || check.authMode, 80);
195
+ add('info', 'signed_in', `Signed in${method ? ` (${method})` : ''}.`);
196
+ if (check.agent === 'claude-code' && text(env?.ANTHROPIC_API_KEY, 8)) {
197
+ add(
198
+ 'warn',
199
+ 'api_key_billing',
200
+ 'ANTHROPIC_API_KEY is set, so Claude Code bills that API key instead of your Claude plan.',
201
+ 'Unset ANTHROPIC_API_KEY in the bridge terminal to use your plan.',
202
+ );
203
+ }
204
+ if (check.agent === 'codex' && check.fallbackProviderUsed) {
205
+ add(
206
+ 'warn',
207
+ 'provider_fallback',
208
+ `Codex is using ${check.providerName || check.providerId || 'a fallback provider'} because the configured provider is not ready.`,
209
+ );
210
+ }
211
+ }
212
+ const modelCount = Array.isArray(check.modelDetails) ? check.modelDetails.length : 0;
213
+ if (check.modelCatalogSource === 'runtime') {
214
+ add('info', 'models_discovered', `${modelCount} model${modelCount === 1 ? '' : 's'} available.`);
215
+ } else if (check.signedIn !== false) {
216
+ add(
217
+ 'warn',
218
+ 'model_catalog_fallback',
219
+ `Could not read the model list from ${label}; showing the default list.`,
220
+ check.modelCatalogError ? text(check.modelCatalogError, 300) : undefined,
221
+ );
222
+ }
223
+ }
224
+ return {
225
+ status: checks.some((item) => item.level === 'error')
226
+ ? 'fail'
227
+ : checks.some((item) => item.level === 'warn')
228
+ ? 'warn'
229
+ : 'pass',
230
+ checks,
231
+ testedAt: new Date().toISOString(),
232
+ };
233
+ }
234
+
235
+ function normalizeDiagnostics(value) {
236
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
237
+ const checks = (Array.isArray(value.checks) ? value.checks : [])
238
+ .slice(0, 12)
239
+ .flatMap((item) => {
240
+ const level = DIAGNOSTIC_LEVELS.has(item?.level) ? item.level : null;
241
+ const code = text(item?.code, 80);
242
+ const message = text(item?.message, 500);
243
+ if (!level || !code || !message) return [];
244
+ const hint = text(item?.hint, 300);
245
+ return [{ level, code, message, ...(hint ? { hint } : {}) }];
246
+ });
247
+ return {
248
+ status: ['pass', 'warn', 'fail'].includes(value.status) ? value.status : 'warn',
249
+ checks,
250
+ testedAt: text(value.testedAt, 40) || new Date().toISOString(),
251
+ };
252
+ }
253
+
254
+ function profileFromAgentCheck(check, env) {
137
255
  if (!check || check.installed === false) return null;
138
256
  const driver =
139
257
  check.agent === 'codex' ? 'codex-app-server' : check.agent === 'claude-code' ? 'claude-code-cli' : null;
@@ -169,6 +287,13 @@ function profileFromAgentCheck(check) {
169
287
  cancellation: true,
170
288
  },
171
289
  models: check.modelDetails || [],
290
+ modelCatalog: {
291
+ source: check.modelCatalogSource || 'fallback',
292
+ refreshedAt: check.modelCatalogRefreshedAt,
293
+ modelCount: check.modelDetails?.length || 0,
294
+ ...(check.modelCatalogError ? { error: check.modelCatalogError } : {}),
295
+ },
296
+ diagnostics: diagnosticsFromAgentCheck(check, env),
172
297
  });
173
298
  }
174
299
 
@@ -239,7 +364,7 @@ export async function discoverRuntimeProfiles({
239
364
  const selectedRuntime = normalizeRuntimeSelection(runtimeSelection);
240
365
  const selectedDriver = runtimeDriver(selectedRuntime);
241
366
  const profiles = agentChecks
242
- .map(profileFromAgentCheck)
367
+ .map((check) => profileFromAgentCheck(check, env))
243
368
  .filter(Boolean)
244
369
  .filter((profile) => !selectedDriver || profile.driver === selectedDriver);
245
370
  if (!selectedRuntime) {