@pellux/goodvibes-agent 1.0.31 → 1.0.33

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.
Files changed (63) hide show
  1. package/CHANGELOG.md +38 -3
  2. package/README.md +7 -5
  3. package/dist/package/main.js +7346 -2349
  4. package/docs/README.md +2 -2
  5. package/docs/channels-remote-and-api.md +6 -2
  6. package/docs/connected-host.md +26 -5
  7. package/docs/getting-started.md +29 -9
  8. package/docs/knowledge-artifacts-and-multimodal.md +16 -4
  9. package/docs/project-planning.md +2 -2
  10. package/docs/providers-and-routing.md +3 -2
  11. package/docs/release-and-publishing.md +15 -11
  12. package/docs/tools-and-commands.md +20 -8
  13. package/package.json +8 -3
  14. package/release/live-verification/live-verification.json +148 -0
  15. package/release/live-verification/live-verification.md +187 -0
  16. package/release/performance-snapshot.json +57 -0
  17. package/release/release-notes.md +28 -0
  18. package/release/release-readiness.json +581 -0
  19. package/src/cli/agent-knowledge-command.ts +5 -5
  20. package/src/cli/agent-knowledge-format.ts +11 -0
  21. package/src/cli/agent-knowledge-runtime.ts +92 -13
  22. package/src/cli/bundle-command.ts +5 -4
  23. package/src/cli/entrypoint.ts +5 -2
  24. package/src/cli/external-runtime.ts +2 -15
  25. package/src/cli/management.ts +4 -3
  26. package/src/input/commands/guidance-runtime.ts +1 -1
  27. package/src/input/commands/knowledge.ts +2 -2
  28. package/src/runtime/bootstrap.ts +2 -2
  29. package/src/runtime/connected-host-auth.ts +16 -0
  30. package/src/tools/agent-channel-send-tool.ts +5 -7
  31. package/src/tools/agent-harness-channel-metadata.ts +177 -0
  32. package/src/tools/agent-harness-connected-host-status.ts +1 -1
  33. package/src/tools/agent-harness-delegation-posture.ts +216 -0
  34. package/src/tools/agent-harness-keybinding-metadata.ts +57 -22
  35. package/src/tools/agent-harness-mcp-metadata.ts +246 -0
  36. package/src/tools/agent-harness-media-posture.ts +282 -0
  37. package/src/tools/agent-harness-metadata.ts +21 -4
  38. package/src/tools/agent-harness-model-routing.ts +501 -0
  39. package/src/tools/agent-harness-notification-metadata.ts +217 -0
  40. package/src/tools/agent-harness-operator-methods.ts +285 -0
  41. package/src/tools/agent-harness-pairing-posture.ts +265 -0
  42. package/src/tools/agent-harness-provider-account-metadata.ts +203 -0
  43. package/src/tools/agent-harness-release-evidence.ts +364 -0
  44. package/src/tools/agent-harness-release-readiness.ts +298 -0
  45. package/src/tools/agent-harness-security-posture.ts +646 -0
  46. package/src/tools/agent-harness-service-posture.ts +201 -0
  47. package/src/tools/agent-harness-session-metadata.ts +282 -0
  48. package/src/tools/agent-harness-setup-posture.ts +295 -0
  49. package/src/tools/agent-harness-tool-schema.ts +103 -27
  50. package/src/tools/agent-harness-tool.ts +209 -236
  51. package/src/tools/agent-harness-ui-surface-metadata.ts +1 -1
  52. package/src/tools/agent-harness-workspace-actions.ts +232 -0
  53. package/src/tools/agent-knowledge-ingest-tool.ts +6 -8
  54. package/src/tools/agent-knowledge-tool.ts +122 -23
  55. package/src/tools/agent-local-registry-tool.ts +4 -5
  56. package/src/tools/agent-media-generate-tool.ts +4 -6
  57. package/src/tools/agent-notify-tool.ts +5 -6
  58. package/src/tools/agent-operator-action-tool.ts +6 -8
  59. package/src/tools/agent-operator-briefing-tool.ts +3 -4
  60. package/src/tools/agent-reminder-schedule-tool.ts +6 -7
  61. package/src/tools/agent-tool-policy-guard.ts +8 -17
  62. package/src/tools/agent-work-plan-tool.ts +3 -4
  63. package/src/version.ts +2 -2
@@ -0,0 +1,501 @@
1
+ import type { CommandContext } from '../input/command-registry.ts';
2
+ import { requireProviderApi } from '../input/commands/runtime-services.ts';
3
+
4
+ export interface AgentHarnessModelRoutingArgs {
5
+ readonly modelRouteId?: unknown;
6
+ readonly target?: unknown;
7
+ readonly query?: unknown;
8
+ readonly includeParameters?: unknown;
9
+ readonly limit?: unknown;
10
+ }
11
+
12
+ type ModelRouteResolution =
13
+ | { readonly status: 'found'; readonly route: Record<string, unknown> }
14
+ | { readonly status: 'ambiguous'; readonly input: string; readonly candidates: readonly Record<string, unknown>[] }
15
+ | { readonly status: 'missing_lookup'; readonly usage: string };
16
+
17
+ type ModelRouteLookupSource = 'modelRouteId' | 'target' | 'query';
18
+
19
+ interface ModelCandidate {
20
+ readonly kind: 'model';
21
+ readonly id: string;
22
+ readonly registryKey: string;
23
+ readonly modelId: string;
24
+ readonly providerId: string;
25
+ readonly displayName: string;
26
+ readonly current: boolean;
27
+ readonly contextWindow: number | null;
28
+ readonly reasoningEffort: readonly string[];
29
+ readonly capabilities: unknown;
30
+ readonly pinned: boolean;
31
+ }
32
+
33
+ interface RouteCandidate {
34
+ readonly kind: 'route';
35
+ readonly id: string;
36
+ readonly label: string;
37
+ readonly detail: string;
38
+ readonly currentValue: unknown;
39
+ readonly settingKeys: readonly string[];
40
+ readonly commands: readonly string[];
41
+ readonly uiSurfaces: readonly string[];
42
+ }
43
+
44
+ interface ProviderApiLike {
45
+ readonly getFavorites: () => Promise<unknown>;
46
+ readonly getCurrentModel: () => Promise<unknown>;
47
+ readonly listModels: (options?: { readonly selectableOnly?: boolean }) => Promise<readonly unknown[]>;
48
+ readonly listProviderIds: () => readonly string[];
49
+ }
50
+
51
+ function readString(value: unknown): string {
52
+ return typeof value === 'string' ? value.trim() : '';
53
+ }
54
+
55
+ function readLimit(value: unknown, fallback: number): number {
56
+ const parsed = typeof value === 'string' && value.trim() ? Number(value) : value;
57
+ if (typeof parsed !== 'number' || !Number.isFinite(parsed)) return fallback;
58
+ return Math.max(1, Math.min(500, Math.trunc(parsed)));
59
+ }
60
+
61
+ function readRecord(value: unknown): Record<string, unknown> {
62
+ return value && typeof value === 'object' ? value as Record<string, unknown> : {};
63
+ }
64
+
65
+ function readStringArray(value: unknown): readonly string[] {
66
+ return Array.isArray(value) ? value.map((entry) => readString(entry)).filter(Boolean) : [];
67
+ }
68
+
69
+ function readConfig(context: CommandContext, key: string): unknown {
70
+ try {
71
+ return (context.platform.configManager as { get(settingKey: string): unknown }).get(key);
72
+ } catch {
73
+ return undefined;
74
+ }
75
+ }
76
+
77
+ function contextWindowFor(context: CommandContext, model: unknown): number | null {
78
+ try {
79
+ const registry = context.provider.providerRegistry as { getContextWindowForModel(candidate: unknown): number };
80
+ const value = registry.getContextWindowForModel(model);
81
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
82
+ } catch {
83
+ return null;
84
+ }
85
+ }
86
+
87
+ function modelRegistryKey(model: unknown): string {
88
+ const record = readRecord(model);
89
+ return readString(record.registryKey) || readString(record.id) || readString(record.modelId);
90
+ }
91
+
92
+ function modelProviderId(model: unknown): string {
93
+ const record = readRecord(model);
94
+ return readString(record.providerId) || readString(record.provider);
95
+ }
96
+
97
+ function modelModelId(model: unknown): string {
98
+ const record = readRecord(model);
99
+ return readString(record.modelId) || readString(record.id) || modelRegistryKey(model);
100
+ }
101
+
102
+ function modelDisplayName(model: unknown): string {
103
+ const record = readRecord(model);
104
+ return readString(record.displayName) || readString(record.name) || modelRegistryKey(model);
105
+ }
106
+
107
+ function modelCurrent(model: unknown): boolean {
108
+ const record = readRecord(model);
109
+ return record.current === true;
110
+ }
111
+
112
+ function modelReasoning(model: unknown): readonly string[] {
113
+ const record = readRecord(model);
114
+ return readStringArray(record.reasoningEffort);
115
+ }
116
+
117
+ function modelCapabilities(model: unknown): unknown {
118
+ return readRecord(model).capabilities ?? null;
119
+ }
120
+
121
+ function readProviderApi(context: CommandContext): ProviderApiLike | null {
122
+ try {
123
+ return requireProviderApi(context) as ProviderApiLike;
124
+ } catch {
125
+ return null;
126
+ }
127
+ }
128
+
129
+ async function loadPinnedModelIds(context: CommandContext): Promise<ReadonlySet<string>> {
130
+ const providerApi = readProviderApi(context);
131
+ if (!providerApi) return new Set();
132
+ try {
133
+ const favorites = await providerApi.getFavorites();
134
+ const pinned = readRecord(favorites).pinned;
135
+ if (!Array.isArray(pinned)) return new Set();
136
+ return new Set(pinned.flatMap((entry) => {
137
+ const record = readRecord(entry);
138
+ return [readString(record.registryKey), readString(record.modelId)].filter(Boolean);
139
+ }));
140
+ } catch {
141
+ return new Set();
142
+ }
143
+ }
144
+
145
+ async function loadModels(context: CommandContext): Promise<readonly ModelCandidate[]> {
146
+ const providerApi = readProviderApi(context);
147
+ if (!providerApi) return [];
148
+ const pinned = await loadPinnedModelIds(context);
149
+ const models = await providerApi.listModels({ selectableOnly: true });
150
+ return models.map((model) => {
151
+ const registryKey = modelRegistryKey(model);
152
+ const modelId = modelModelId(model);
153
+ return {
154
+ kind: 'model',
155
+ id: registryKey,
156
+ registryKey,
157
+ modelId,
158
+ providerId: modelProviderId(model),
159
+ displayName: modelDisplayName(model),
160
+ current: modelCurrent(model) || registryKey === context.session.runtime.model,
161
+ contextWindow: contextWindowFor(context, model),
162
+ reasoningEffort: modelReasoning(model),
163
+ capabilities: modelCapabilities(model),
164
+ pinned: pinned.has(registryKey) || pinned.has(modelId),
165
+ };
166
+ });
167
+ }
168
+
169
+ function listProviderIds(context: CommandContext): readonly string[] {
170
+ return readProviderApi(context)?.listProviderIds() ?? [];
171
+ }
172
+
173
+ async function readCurrentModel(context: CommandContext): Promise<unknown> {
174
+ try {
175
+ return await readProviderApi(context)?.getCurrentModel() ?? null;
176
+ } catch {
177
+ return null;
178
+ }
179
+ }
180
+
181
+ function routeCandidates(context: CommandContext): readonly RouteCandidate[] {
182
+ return [
183
+ {
184
+ kind: 'route',
185
+ id: 'main',
186
+ label: 'Main conversation model',
187
+ detail: 'Provider/model route for normal Agent chat turns.',
188
+ currentValue: {
189
+ provider: context.session.runtime.provider,
190
+ model: context.session.runtime.model,
191
+ reasoningEffort: context.session.runtime.reasoningEffort || readConfig(context, 'provider.reasoningEffort') || null,
192
+ },
193
+ settingKeys: ['provider.model', 'provider.reasoningEffort'],
194
+ commands: ['/model', '/provider', '/effort'],
195
+ uiSurfaces: ['model-picker', 'provider-picker', 'reasoning-effort-picker'],
196
+ },
197
+ {
198
+ kind: 'route',
199
+ id: 'embedding-provider',
200
+ label: 'Embedding provider',
201
+ detail: 'Provider used for embedding-backed Agent features.',
202
+ currentValue: readConfig(context, 'provider.embeddingProvider') ?? null,
203
+ settingKeys: ['provider.embeddingProvider'],
204
+ commands: ['/settings provider.embeddingProvider'],
205
+ uiSurfaces: ['settings'],
206
+ },
207
+ {
208
+ kind: 'route',
209
+ id: 'system-prompt',
210
+ label: 'System prompt file',
211
+ detail: 'Agent-owned system prompt file route for the serial conversation.',
212
+ currentValue: readConfig(context, 'provider.systemPromptFile') || null,
213
+ settingKeys: ['provider.systemPromptFile'],
214
+ commands: ['/settings provider.systemPromptFile'],
215
+ uiSurfaces: ['settings'],
216
+ },
217
+ {
218
+ kind: 'route',
219
+ id: 'helper-model',
220
+ label: 'Helper model route',
221
+ detail: 'Optional helper-model route for Agent-owned helper operations.',
222
+ currentValue: {
223
+ enabled: readConfig(context, 'helper.enabled') ?? null,
224
+ provider: readConfig(context, 'helper.globalProvider') ?? null,
225
+ model: readConfig(context, 'helper.globalModel') ?? null,
226
+ },
227
+ settingKeys: ['helper.enabled', 'helper.globalProvider', 'helper.globalModel'],
228
+ commands: ['/settings helper.enabled', '/settings helper.globalProvider', '/settings helper.globalModel'],
229
+ uiSurfaces: ['settings'],
230
+ },
231
+ {
232
+ kind: 'route',
233
+ id: 'tool-llm',
234
+ label: 'Tool LLM route',
235
+ detail: 'Optional provider/model route for tool-assist operations.',
236
+ currentValue: {
237
+ enabled: readConfig(context, 'tools.llmEnabled') ?? null,
238
+ provider: readConfig(context, 'tools.llmProvider') ?? null,
239
+ model: readConfig(context, 'tools.llmModel') ?? null,
240
+ },
241
+ settingKeys: ['tools.llmEnabled', 'tools.llmProvider', 'tools.llmModel'],
242
+ commands: ['/settings tools.llmEnabled', '/settings tools.llmProvider', '/settings tools.llmModel'],
243
+ uiSurfaces: ['settings'],
244
+ },
245
+ {
246
+ kind: 'route',
247
+ id: 'catalog-refresh',
248
+ label: 'Model catalog refresh',
249
+ detail: 'Provider catalog, benchmark, and token-limit refresh route.',
250
+ currentValue: 'available',
251
+ settingKeys: [],
252
+ commands: ['/refresh-models'],
253
+ uiSurfaces: ['model-picker'],
254
+ },
255
+ {
256
+ kind: 'route',
257
+ id: 'pinned-models',
258
+ label: 'Pinned model favorites',
259
+ detail: 'Local model favorites used by the model picker.',
260
+ currentValue: 'see model entries where pinned is true',
261
+ settingKeys: [],
262
+ commands: ['/pin', '/unpin'],
263
+ uiSurfaces: ['model-picker'],
264
+ },
265
+ {
266
+ kind: 'route',
267
+ id: 'custom-providers',
268
+ label: 'Custom provider files',
269
+ detail: 'Agent-local OpenAI-compatible provider add/remove route.',
270
+ currentValue: 'workspace controlled',
271
+ settingKeys: [],
272
+ commands: ['/provider add <name> <baseURL> [apiKey] --yes', '/provider remove <name> --yes'],
273
+ uiSurfaces: ['settings', 'provider-picker'],
274
+ },
275
+ ];
276
+ }
277
+
278
+ function lookupFromArgs(args: AgentHarnessModelRoutingArgs): { readonly source: ModelRouteLookupSource; readonly input: string } | null {
279
+ const modelRouteId = readString(args.modelRouteId);
280
+ if (modelRouteId) return { source: 'modelRouteId', input: modelRouteId };
281
+ const target = readString(args.target);
282
+ if (target) return { source: 'target', input: target };
283
+ const query = readString(args.query);
284
+ return query ? { source: 'query', input: query } : null;
285
+ }
286
+
287
+ function routeSearchText(route: RouteCandidate): string {
288
+ return [
289
+ route.id,
290
+ route.label,
291
+ route.detail,
292
+ ...route.settingKeys,
293
+ ...route.commands,
294
+ ...route.uiSurfaces,
295
+ JSON.stringify(route.currentValue),
296
+ ].join('\n').toLowerCase();
297
+ }
298
+
299
+ function modelSearchText(model: ModelCandidate): string {
300
+ return [
301
+ model.registryKey,
302
+ model.modelId,
303
+ model.providerId,
304
+ model.displayName,
305
+ model.current ? 'current selected active' : '',
306
+ model.pinned ? 'pinned favorite' : '',
307
+ ...model.reasoningEffort,
308
+ JSON.stringify(model.capabilities),
309
+ ].join('\n').toLowerCase();
310
+ }
311
+
312
+ function describeRoute(route: RouteCandidate, options: { readonly includeParameters?: boolean; readonly lookup?: Record<string, unknown> } = {}): Record<string, unknown> {
313
+ return {
314
+ kind: 'route',
315
+ modelRouteId: route.id,
316
+ label: route.label,
317
+ detail: route.detail,
318
+ currentValue: route.currentValue,
319
+ settingKeys: route.settingKeys,
320
+ commands: route.commands,
321
+ uiSurfaces: route.uiSurfaces,
322
+ ...(options.lookup ? { lookup: options.lookup } : {}),
323
+ policy: {
324
+ effect: 'read-only',
325
+ values: 'Model routing posture returns model ids, provider ids, route state, capabilities, and safe setting keys only; provider credentials are never returned.',
326
+ mutation: 'Model/provider selection, catalog refresh, favorites, custom provider edits, and route setting changes stay explicit user-facing picker, settings, workspace, or slash-command flows.',
327
+ },
328
+ ...(options.includeParameters ? {
329
+ modelAccess: {
330
+ inspectRouting: 'agent_harness mode:"model_routing"',
331
+ inspectRoute: 'agent_harness mode:"model_route"',
332
+ settingRead: 'agent_harness mode:"get_setting"',
333
+ settingMutation: 'agent_harness mode:"set_setting" confirm:true explicitUserRequest:"..."',
334
+ openModelPicker: 'agent_harness mode:"open_ui_surface" surfaceId:"model-picker" confirm:true',
335
+ openProviderPicker: 'agent_harness mode:"open_ui_surface" surfaceId:"provider-picker" confirm:true',
336
+ },
337
+ } : {}),
338
+ };
339
+ }
340
+
341
+ function describeModel(model: ModelCandidate, options: { readonly includeParameters?: boolean; readonly lookup?: Record<string, unknown> } = {}): Record<string, unknown> {
342
+ return {
343
+ kind: 'model',
344
+ modelRouteId: model.registryKey,
345
+ registryKey: model.registryKey,
346
+ modelId: model.modelId,
347
+ providerId: model.providerId,
348
+ displayName: model.displayName,
349
+ current: model.current,
350
+ pinned: model.pinned,
351
+ contextWindow: model.contextWindow,
352
+ reasoningEffort: model.reasoningEffort,
353
+ capabilities: model.capabilities,
354
+ ...(options.lookup ? { lookup: options.lookup } : {}),
355
+ policy: {
356
+ effect: 'read-only',
357
+ mutation: 'Selecting this model stays a visible picker, settings, workspace, or slash-command flow with explicit user request.',
358
+ },
359
+ ...(options.includeParameters ? {
360
+ modelAccess: {
361
+ selectModelCommand: `/model ${model.registryKey}`,
362
+ selectProviderCommand: `/provider ${model.providerId}`,
363
+ pinCommand: `/pin ${model.registryKey}`,
364
+ unpinCommand: `/unpin ${model.registryKey}`,
365
+ setMainModel: `agent_harness mode:"set_setting" key:"provider.model" value:"${model.registryKey}" confirm:true`,
366
+ },
367
+ } : {}),
368
+ };
369
+ }
370
+
371
+ function describeCandidate(entry: RouteCandidate | ModelCandidate): Record<string, unknown> {
372
+ if (entry.kind === 'route') {
373
+ return {
374
+ kind: 'route',
375
+ modelRouteId: entry.id,
376
+ label: entry.label,
377
+ };
378
+ }
379
+ return {
380
+ kind: 'model',
381
+ modelRouteId: entry.registryKey,
382
+ providerId: entry.providerId,
383
+ displayName: entry.displayName,
384
+ current: entry.current,
385
+ pinned: entry.pinned,
386
+ };
387
+ }
388
+
389
+ export async function modelRoutingCatalogStatus(context: CommandContext): Promise<Record<string, unknown>> {
390
+ const [models, providerIds] = await Promise.all([
391
+ loadModels(context),
392
+ Promise.resolve(listProviderIds(context)),
393
+ ]);
394
+ return {
395
+ modes: ['model_routing', 'model_route'],
396
+ status: readProviderApi(context) ? 'available' : 'degraded',
397
+ routes: routeCandidates(context).length,
398
+ providers: providerIds.length,
399
+ models: models.length,
400
+ currentModel: context.session.runtime.model,
401
+ currentProvider: context.session.runtime.provider,
402
+ readOnly: true,
403
+ };
404
+ }
405
+
406
+ export async function modelRoutingSummary(context: CommandContext, args: AgentHarnessModelRoutingArgs): Promise<Record<string, unknown>> {
407
+ const query = readString(args.query).toLowerCase();
408
+ const includeParameters = args.includeParameters === true;
409
+ const [models, providerIds, currentModel] = await Promise.all([
410
+ loadModels(context),
411
+ Promise.resolve(listProviderIds(context)),
412
+ readCurrentModel(context),
413
+ ]);
414
+ const routes = routeCandidates(context);
415
+ const filteredRoutes = routes.filter((route) => !query || routeSearchText(route).includes(query));
416
+ const filteredModels = models.filter((model) => !query || modelSearchText(model).includes(query));
417
+ const limit = readLimit(args.limit, 100);
418
+ return {
419
+ status: readProviderApi(context) ? 'available' : 'degraded',
420
+ current: {
421
+ provider: context.session.runtime.provider,
422
+ model: context.session.runtime.model,
423
+ reasoningEffort: context.session.runtime.reasoningEffort || readConfig(context, 'provider.reasoningEffort') || null,
424
+ currentModel: currentModel ? describeModel({
425
+ kind: 'model',
426
+ id: modelRegistryKey(currentModel),
427
+ registryKey: modelRegistryKey(currentModel),
428
+ modelId: modelModelId(currentModel),
429
+ providerId: modelProviderId(currentModel),
430
+ displayName: modelDisplayName(currentModel),
431
+ current: true,
432
+ contextWindow: contextWindowFor(context, currentModel),
433
+ reasoningEffort: modelReasoning(currentModel),
434
+ capabilities: modelCapabilities(currentModel),
435
+ pinned: false,
436
+ }) : null,
437
+ },
438
+ providers: providerIds,
439
+ routes: filteredRoutes.slice(0, limit).map((route) => describeRoute(route, { includeParameters })),
440
+ models: filteredModels.slice(0, limit).map((model) => describeModel(model, { includeParameters })),
441
+ returned: {
442
+ routes: Math.min(filteredRoutes.length, limit),
443
+ models: Math.min(filteredModels.length, limit),
444
+ },
445
+ total: {
446
+ routes: routes.length,
447
+ providers: providerIds.length,
448
+ models: models.length,
449
+ },
450
+ issues: readProviderApi(context) ? [] : [{
451
+ severity: 'warning',
452
+ message: 'Provider API is unavailable in this runtime; static model route posture is still inspectable.',
453
+ }],
454
+ policy: 'Read-only provider/model routing posture. Route changes, model selection, provider add/remove, refresh, pin, and unpin remain visible picker, settings, workspace, or slash-command flows.',
455
+ };
456
+ }
457
+
458
+ export async function describeHarnessModelRoute(context: CommandContext, args: AgentHarnessModelRoutingArgs): Promise<ModelRouteResolution> {
459
+ const lookup = lookupFromArgs(args);
460
+ if (!lookup) {
461
+ return {
462
+ status: 'missing_lookup',
463
+ usage: 'model_route requires modelRouteId, target, or query. Use mode:"model_routing" to inspect route and model ids.',
464
+ };
465
+ }
466
+ const [models] = await Promise.all([loadModels(context)]);
467
+ const routes = routeCandidates(context);
468
+ const normalized = lookup.input.toLowerCase();
469
+ const exactRoute = routes.find((route) => route.id === lookup.input);
470
+ if (exactRoute) return { status: 'found', route: describeRoute(exactRoute, { includeParameters: true, lookup: { ...lookup, resolvedBy: 'route-id' } }) };
471
+ const exactModel = models.find((model) => model.registryKey === lookup.input || model.modelId === lookup.input);
472
+ if (exactModel) return { status: 'found', route: describeModel(exactModel, { includeParameters: true, lookup: { ...lookup, resolvedBy: 'model-id' } }) };
473
+ const insensitiveRoute = routes.find((route) => route.id.toLowerCase() === normalized);
474
+ if (insensitiveRoute) return { status: 'found', route: describeRoute(insensitiveRoute, { includeParameters: true, lookup: { ...lookup, resolvedBy: 'case-insensitive-route-id' } }) };
475
+ const insensitiveModel = models.find((model) => model.registryKey.toLowerCase() === normalized || model.modelId.toLowerCase() === normalized);
476
+ if (insensitiveModel) return { status: 'found', route: describeModel(insensitiveModel, { includeParameters: true, lookup: { ...lookup, resolvedBy: 'case-insensitive-model-id' } }) };
477
+ const searched = [
478
+ ...routes.filter((route) => routeSearchText(route).includes(normalized)),
479
+ ...models.filter((model) => modelSearchText(model).includes(normalized)),
480
+ ];
481
+ if (searched.length === 1) {
482
+ const found = searched[0]!;
483
+ return {
484
+ status: 'found',
485
+ route: found.kind === 'route'
486
+ ? describeRoute(found, { includeParameters: true, lookup: { ...lookup, resolvedBy: 'search' } })
487
+ : describeModel(found, { includeParameters: true, lookup: { ...lookup, resolvedBy: 'search' } }),
488
+ };
489
+ }
490
+ if (searched.length > 1) {
491
+ return {
492
+ status: 'ambiguous',
493
+ input: lookup.input,
494
+ candidates: searched.slice(0, 8).map(describeCandidate),
495
+ };
496
+ }
497
+ return {
498
+ status: 'missing_lookup',
499
+ usage: `Unknown model route ${lookup.input}. Use mode:"model_routing" to inspect route and model ids.`,
500
+ };
501
+ }