@pellux/goodvibes-agent 1.1.7 → 1.3.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/CHANGELOG.md +329 -0
- package/README.md +46 -30
- package/dist/package/{ast-grep-napi.linux-x64-gnu-mkk8xwww.node → ast-grep-napi.linux-x64-gnu-swtppvy9.node} +0 -0
- package/dist/package/{ast-grep-napi.linux-x64-musl-ryqtgdv6.node → ast-grep-napi.linux-x64-musl-ttfcdtap.node} +0 -0
- package/dist/package/main.js +138903 -91477
- package/docs/README.md +14 -7
- package/docs/channels-remote-and-api.md +8 -5
- package/docs/connected-host.md +14 -12
- package/docs/getting-started.md +58 -30
- package/docs/knowledge-artifacts-and-multimodal.md +7 -5
- package/docs/project-planning.md +2 -2
- package/docs/providers-and-routing.md +11 -3
- package/docs/tools-and-commands.md +116 -45
- package/docs/voice-and-live-tts.md +3 -1
- package/package.json +3 -2
- package/release/release-notes.md +142 -5
- package/release/release-readiness.json +124 -94
- package/src/agent/assistant-cockpit.ts +247 -0
- package/src/agent/autonomy-schedule-format.ts +96 -0
- package/src/agent/autonomy-schedule.ts +514 -0
- package/src/agent/channel-delivery-receipts.ts +292 -0
- package/src/agent/competitive-feature-inventory.ts +294 -0
- package/src/agent/document-registry-types.ts +106 -0
- package/src/agent/document-registry.ts +734 -0
- package/src/agent/harness-control.ts +2 -2
- package/src/agent/memory-prompt.ts +2 -2
- package/src/agent/operator-actions.ts +50 -6
- package/src/agent/persona-registry.ts +8 -0
- package/src/agent/project-context-files.ts +273 -0
- package/src/agent/prompt-context-receipts.ts +502 -0
- package/src/agent/reminder-schedule-format.ts +16 -2
- package/src/agent/research-run-registry.ts +582 -0
- package/src/agent/research-source-registry.ts +441 -0
- package/src/agent/routine-registry.ts +19 -3
- package/src/agent/routine-schedule-format.ts +27 -2
- package/src/agent/runtime-profile-starters.ts +7 -0
- package/src/agent/runtime-profile.ts +106 -4
- package/src/agent/schedule-edit-format.ts +129 -0
- package/src/agent/schedule-edit.ts +496 -0
- package/src/agent/schedule-next-routes.ts +42 -0
- package/src/agent/setup-wizard-artifact-receipts.ts +366 -0
- package/src/agent/setup-wizard-checkpoint.ts +140 -0
- package/src/agent/setup-wizard.ts +683 -0
- package/src/agent/skill-registry-types.ts +102 -0
- package/src/agent/skill-registry.ts +81 -107
- package/src/agent/vibe-confirmation-routes.ts +62 -0
- package/src/agent/vibe-file.ts +285 -0
- package/src/cli/agent-knowledge-command.ts +45 -2
- package/src/cli/help.ts +3 -0
- package/src/cli/profiles-command.ts +25 -11
- package/src/config/agent-settings-policy.ts +1 -18
- package/src/input/agent-workspace-activation.ts +53 -0
- package/src/input/agent-workspace-artifact-browser-editor.ts +494 -0
- package/src/input/agent-workspace-artifact-metadata.ts +17 -0
- package/src/input/agent-workspace-basic-command-editors.ts +5 -1
- package/src/input/agent-workspace-categories.ts +153 -155
- package/src/input/agent-workspace-channel-triage.ts +481 -0
- package/src/input/agent-workspace-channels.ts +263 -0
- package/src/input/agent-workspace-command-editor.ts +152 -0
- package/src/input/agent-workspace-context-snapshot.ts +349 -0
- package/src/input/agent-workspace-delegation-editor-submission.ts +8 -0
- package/src/input/agent-workspace-document-editor.ts +502 -0
- package/src/input/agent-workspace-document-ops-editor.ts +523 -0
- package/src/input/agent-workspace-host-category.ts +1 -1
- package/src/input/agent-workspace-local-library-snapshot.ts +167 -0
- package/src/input/agent-workspace-model-compare-editor.ts +376 -0
- package/src/input/agent-workspace-model-compare-prompt-submission.ts +552 -0
- package/src/input/agent-workspace-model-compare-types.ts +82 -0
- package/src/input/agent-workspace-model-compare-utils.ts +35 -0
- package/src/input/agent-workspace-onboarding-categories.ts +141 -0
- package/src/input/agent-workspace-operations-command-editor-submission.ts +53 -0
- package/src/input/agent-workspace-operations-command-editors.ts +22 -0
- package/src/input/agent-workspace-research-report-editor.ts +212 -0
- package/src/input/agent-workspace-research-run-editor.ts +144 -0
- package/src/input/agent-workspace-research-source-editor.ts +167 -0
- package/src/input/agent-workspace-review-packet-snapshot.ts +593 -0
- package/src/input/agent-workspace-review-packet-utils.ts +265 -0
- package/src/input/agent-workspace-settings.ts +313 -30
- package/src/input/agent-workspace-setup-checkpoint-action.ts +127 -0
- package/src/input/agent-workspace-setup-snapshot.ts +217 -0
- package/src/input/agent-workspace-setup.ts +61 -1
- package/src/input/agent-workspace-snapshot.ts +184 -149
- package/src/input/agent-workspace-types.ts +287 -2
- package/src/input/agent-workspace.ts +23 -1
- package/src/input/command-registry.ts +9 -2
- package/src/input/commands/agent-runtime-profile-runtime.ts +28 -13
- package/src/input/commands/channels-runtime.ts +102 -114
- package/src/input/commands/delegation-runtime.ts +87 -24
- package/src/input/commands/knowledge-browser-flags.ts +12 -0
- package/src/input/commands/knowledge.ts +28 -14
- package/src/input/commands/operator-actions-runtime.ts +6 -3
- package/src/input/commands/schedule-runtime.ts +71 -14
- package/src/input/commands/session-content.ts +2 -2
- package/src/input/commands/shell-core.ts +2 -0
- package/src/input/commands/vibe-runtime.ts +140 -0
- package/src/input/commands.ts +2 -0
- package/src/input/connected-host-routes.ts +123 -0
- package/src/input/settings-modal-types.ts +14 -0
- package/src/input/setup-wizard-live-receipts.ts +76 -0
- package/src/main.ts +15 -15
- package/src/panels/qr-panel.ts +2 -2
- package/src/renderer/agent-workspace-context-lines.ts +701 -0
- package/src/renderer/agent-workspace.ts +68 -421
- package/src/renderer/settings-modal-helpers.ts +12 -0
- package/src/renderer/settings-modal.ts +12 -0
- package/src/runtime/bootstrap-command-context.ts +11 -1
- package/src/runtime/bootstrap-command-parts.ts +14 -3
- package/src/runtime/bootstrap-core.ts +39 -1
- package/src/runtime/bootstrap-shell.ts +6 -0
- package/src/runtime/bootstrap.ts +113 -30
- package/src/runtime/connected-host-auth.ts +3 -2
- package/src/runtime/execution-ledger.ts +231 -0
- package/src/runtime/services.ts +88 -10
- package/src/runtime/tool-permission-safety.ts +199 -5
- package/src/shell/session-continuity-hints.ts +39 -0
- package/src/tools/agent-artifacts-tool.ts +606 -0
- package/src/tools/agent-audit-tool.ts +155 -0
- package/src/tools/agent-autonomy-schedule-tool.ts +238 -0
- package/src/tools/agent-autonomy-tool.ts +140 -0
- package/src/tools/agent-channel-send-tool.ts +24 -2
- package/src/tools/agent-channels-tool.ts +140 -0
- package/src/tools/agent-computer-tool.ts +168 -0
- package/src/tools/agent-context-policy.ts +137 -7
- package/src/tools/agent-context-tool.ts +143 -0
- package/src/tools/agent-delegation-tool.ts +128 -0
- package/src/tools/agent-device-tool.ts +240 -0
- package/src/tools/agent-documents-tool.ts +684 -0
- package/src/tools/agent-execution-tool.ts +230 -0
- package/src/tools/agent-harness-agent-orchestration-policy.ts +75 -0
- package/src/tools/agent-harness-agent-orchestration.ts +791 -0
- package/src/tools/agent-harness-autonomy-intake.ts +785 -0
- package/src/tools/agent-harness-autonomy-live-records.ts +742 -0
- package/src/tools/agent-harness-autonomy-queue-types.ts +88 -0
- package/src/tools/agent-harness-autonomy-queue.ts +571 -0
- package/src/tools/agent-harness-autonomy-watcher-read-models.ts +509 -0
- package/src/tools/agent-harness-background-processes-types.ts +35 -0
- package/src/tools/agent-harness-background-processes.ts +798 -0
- package/src/tools/agent-harness-browser-cockpit-route.ts +373 -0
- package/src/tools/agent-harness-browser-control.ts +346 -0
- package/src/tools/agent-harness-browser-pwa-read-models.ts +622 -0
- package/src/tools/agent-harness-channel-metadata.ts +168 -10
- package/src/tools/agent-harness-cli-command-policy.ts +110 -0
- package/src/tools/agent-harness-command-catalog.ts +4 -2
- package/src/tools/agent-harness-command-runner.ts +42 -0
- package/src/tools/agent-harness-connected-host-status.ts +7 -4
- package/src/tools/agent-harness-delegation-posture.ts +234 -19
- package/src/tools/agent-harness-device-live-read-models.ts +366 -0
- package/src/tools/agent-harness-document-ops-reviewer-readiness.ts +234 -0
- package/src/tools/agent-harness-document-ops-types.ts +72 -0
- package/src/tools/agent-harness-document-ops.ts +671 -0
- package/src/tools/agent-harness-execution-history.ts +489 -0
- package/src/tools/agent-harness-execution-posture.ts +385 -0
- package/src/tools/agent-harness-file-recovery.ts +135 -0
- package/src/tools/agent-harness-interactive-runtime-records.ts +421 -0
- package/src/tools/agent-harness-keybinding-metadata.ts +16 -12
- package/src/tools/agent-harness-learning-curator-common.ts +102 -0
- package/src/tools/agent-harness-learning-curator-consolidation.ts +295 -0
- package/src/tools/agent-harness-learning-curator-proposals.ts +606 -0
- package/src/tools/agent-harness-learning-curator-types.ts +151 -0
- package/src/tools/agent-harness-learning-curator.ts +417 -0
- package/src/tools/agent-harness-local-model-benchmarks.ts +269 -0
- package/src/tools/agent-harness-local-model-cookbook.ts +238 -0
- package/src/tools/agent-harness-local-model-endpoints.ts +788 -0
- package/src/tools/agent-harness-local-model-smoke.ts +277 -0
- package/src/tools/agent-harness-local-model-url.ts +78 -0
- package/src/tools/agent-harness-media-posture.ts +223 -2
- package/src/tools/agent-harness-memory-external-providers.ts +796 -0
- package/src/tools/agent-harness-memory-posture.ts +672 -0
- package/src/tools/agent-harness-memory-provider-certification.ts +219 -0
- package/src/tools/agent-harness-memory-refinement.ts +340 -0
- package/src/tools/agent-harness-metadata.ts +100 -168
- package/src/tools/agent-harness-mode-catalog.ts +86 -33
- package/src/tools/agent-harness-model-catalog.ts +162 -0
- package/src/tools/agent-harness-model-provider-health.ts +283 -0
- package/src/tools/agent-harness-model-readiness.ts +432 -0
- package/src/tools/agent-harness-model-routing-types.ts +327 -0
- package/src/tools/agent-harness-model-routing-utils.ts +30 -0
- package/src/tools/agent-harness-model-routing.ts +165 -193
- package/src/tools/agent-harness-operator-methods.ts +115 -133
- package/src/tools/agent-harness-pairing-posture.ts +452 -18
- package/src/tools/agent-harness-personal-ops-certification.ts +116 -0
- package/src/tools/agent-harness-personal-ops-discovery.ts +533 -0
- package/src/tools/agent-harness-personal-ops-intake.ts +385 -0
- package/src/tools/agent-harness-personal-ops-lanes.ts +270 -0
- package/src/tools/agent-harness-personal-ops-operations.ts +225 -0
- package/src/tools/agent-harness-personal-ops-provider-records.ts +358 -0
- package/src/tools/agent-harness-personal-ops-provider-task-records.ts +321 -0
- package/src/tools/agent-harness-personal-ops-records.ts +684 -0
- package/src/tools/agent-harness-personal-ops-runner.ts +637 -0
- package/src/tools/agent-harness-personal-ops-types.ts +240 -0
- package/src/tools/agent-harness-personal-ops.ts +613 -0
- package/src/tools/agent-harness-project-context.ts +144 -0
- package/src/tools/agent-harness-prompt-context.ts +589 -0
- package/src/tools/agent-harness-provider-account-metadata.ts +2 -2
- package/src/tools/agent-harness-release-evidence.ts +9 -7
- package/src/tools/agent-harness-release-readiness.ts +9 -7
- package/src/tools/agent-harness-remote-read-models.ts +541 -0
- package/src/tools/agent-harness-research-briefing.ts +446 -0
- package/src/tools/agent-harness-research-live-read-models.ts +500 -0
- package/src/tools/agent-harness-research-queue.ts +250 -0
- package/src/tools/agent-harness-research-runs.ts +307 -0
- package/src/tools/agent-harness-research-workflow.ts +395 -0
- package/src/tools/agent-harness-security-posture.ts +2 -2
- package/src/tools/agent-harness-service-posture.ts +9 -9
- package/src/tools/agent-harness-session-metadata.ts +3 -3
- package/src/tools/agent-harness-setup-connected-host.ts +501 -0
- package/src/tools/agent-harness-setup-describe.ts +557 -0
- package/src/tools/agent-harness-setup-handoffs.ts +502 -0
- package/src/tools/agent-harness-setup-model-helpers.ts +102 -0
- package/src/tools/agent-harness-setup-plan.ts +253 -0
- package/src/tools/agent-harness-setup-posture-types.ts +218 -0
- package/src/tools/agent-harness-setup-posture-utils.ts +173 -0
- package/src/tools/agent-harness-setup-posture.ts +449 -221
- package/src/tools/agent-harness-setup-smoke.ts +533 -0
- package/src/tools/agent-harness-sudo-posture.ts +114 -0
- package/src/tools/agent-harness-tool-schema.ts +142 -7
- package/src/tools/agent-harness-tool-types.ts +83 -0
- package/src/tools/agent-harness-tool-utils.ts +43 -0
- package/src/tools/agent-harness-tool.ts +319 -370
- package/src/tools/agent-harness-ui-surface-metadata.ts +76 -69
- package/src/tools/agent-harness-ui-surface-types.ts +46 -0
- package/src/tools/agent-harness-vibe-health.ts +105 -0
- package/src/tools/agent-harness-workspace-action-runner.ts +149 -0
- package/src/tools/agent-harness-workspace-actions.ts +114 -8
- package/src/tools/agent-harness-workspace-editor-execution.ts +214 -0
- package/src/tools/agent-harness-workspace-editor-runner.ts +755 -0
- package/src/tools/agent-host-tool.ts +159 -0
- package/src/tools/agent-knowledge-ingest-tool.ts +34 -1
- package/src/tools/agent-learning-consolidation-core.ts +327 -0
- package/src/tools/agent-learning-consolidation-tool.ts +758 -0
- package/src/tools/agent-memory-tool.ts +253 -0
- package/src/tools/agent-model-compare-export.ts +315 -0
- package/src/tools/agent-model-compare-handoff.ts +592 -0
- package/src/tools/agent-model-compare-judgment.ts +633 -0
- package/src/tools/agent-model-compare-run.ts +735 -0
- package/src/tools/agent-model-compare-tool.ts +698 -0
- package/src/tools/agent-model-compare-types.ts +191 -0
- package/src/tools/agent-model-compare-utils.ts +93 -0
- package/src/tools/agent-models-tool.ts +208 -0
- package/src/tools/agent-operator-action-tool.ts +1 -1
- package/src/tools/agent-operator-method-tool.ts +643 -0
- package/src/tools/agent-personal-ops-tool.ts +197 -0
- package/src/tools/agent-policy-explanation.ts +415 -0
- package/src/tools/agent-research-report-tool.ts +608 -0
- package/src/tools/agent-research-runner.ts +367 -0
- package/src/tools/agent-research-runs-tool.ts +434 -0
- package/src/tools/agent-research-sources-tool.ts +443 -0
- package/src/tools/agent-research-tool.ts +515 -0
- package/src/tools/agent-review-packet-presets-core.ts +757 -0
- package/src/tools/agent-review-packet-presets-tool.ts +466 -0
- package/src/tools/agent-review-packet-share-tool.ts +305 -0
- package/src/tools/agent-route-planner-candidates-setup.ts +411 -0
- package/src/tools/agent-route-planner-candidates-surfaces.ts +432 -0
- package/src/tools/agent-route-planner-candidates-work.ts +251 -0
- package/src/tools/agent-route-planner-helpers.ts +667 -0
- package/src/tools/agent-route-planner.ts +185 -0
- package/src/tools/agent-route-tool.ts +105 -0
- package/src/tools/agent-schedule-edit-tool.ts +210 -0
- package/src/tools/agent-schedule-tool.ts +282 -0
- package/src/tools/agent-security-tool.ts +145 -0
- package/src/tools/agent-sessions-tool.ts +122 -0
- package/src/tools/agent-settings-import-tool.ts +104 -0
- package/src/tools/agent-settings-tool.ts +176 -0
- package/src/tools/agent-setup-tool.ts +226 -0
- package/src/tools/agent-support-tool.ts +122 -0
- package/src/tools/agent-terminal-process-tools.ts +167 -0
- package/src/tools/agent-tool-policy-guard-types.ts +77 -0
- package/src/tools/agent-tool-policy-guard.ts +106 -85
- package/src/tools/agent-vibe-tool.ts +297 -0
- package/src/tools/agent-work-plan-tool.ts +265 -6
- package/src/tools/agent-workspace-tool.ts +305 -0
- package/src/tools/artifact-archive.ts +169 -0
- package/src/tools/tool-definition-compaction.ts +36 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,788 @@
|
|
|
1
|
+
import type { CommandContext } from '../input/command-registry.ts';
|
|
2
|
+
import type { LocalModelDetection, LocalModelEndpointSource, LocalModelServerDefaultEndpoint, LocalModelServerDiagnosticsPublication, LocalModelServerEndpoint, LocalModelServerHealthMap, LocalModelServerServingDiagnostics, MutableLocalModelServerEndpoint } from './agent-harness-model-routing-types.ts';
|
|
3
|
+
import { listProviderIds, listProviderRegistryProviders, listRegistryModels, modelDisplayName, modelModelId, modelProviderId, modelRegistryKey, readProviderModels } from './agent-harness-model-catalog.ts';
|
|
4
|
+
import { previewHarnessText } from './agent-harness-text.ts';
|
|
5
|
+
import { readLimit, readRecord, readString } from './agent-harness-model-routing-utils.ts';
|
|
6
|
+
import { extractUrls, isPrivateOrLocalUrl, localStackFor, modelsUrlFor, normalizeLocalBaseUrl, parseUrlCandidate } from './agent-harness-local-model-url.ts';
|
|
7
|
+
|
|
8
|
+
export { cleanUrlCandidate, extractUrls, isPrivateOrLocalHost, isPrivateOrLocalUrl, localStackFor, modelsUrlFor, normalizeLocalBaseUrl, parseUrlCandidate } from './agent-harness-local-model-url.ts';
|
|
9
|
+
|
|
10
|
+
export function localProviderNameFor(providerId: string | null, stack: string | null, fallback: string): string {
|
|
11
|
+
const seed = providerId || stack || fallback;
|
|
12
|
+
const normalized = seed.toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '');
|
|
13
|
+
return normalized || 'local-openai';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function localProviderAddRoute(providerId: string | null, stack: string | null, baseUrl: string): string {
|
|
17
|
+
const name = localProviderNameFor(providerId, stack, 'local-openai');
|
|
18
|
+
return `agent_harness mode:"run_command" command:"/provider add ${name} ${baseUrl} local --yes" confirm:true explicitUserRequest:"Add this local provider after the server is running."`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function localEndpointId(baseUrl: string): string {
|
|
22
|
+
return `local-${baseUrl.toLowerCase().replace(/^https?:\/\//, '').replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '')}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function localEndpointInspectRoute(endpointId: string): string {
|
|
26
|
+
return `agent_harness mode:"model_route" modelRouteId:"${endpointId}"`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function localEndpointSmokeRoute(endpointId?: string): string {
|
|
30
|
+
const target = endpointId ? ` modelRouteId:"${endpointId}"` : '';
|
|
31
|
+
return `models action:"smoke"${target} confirm:true explicitUserRequest:"Check local model servers."`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function localEndpointDiagnostics(endpoint: MutableLocalModelServerEndpoint, providerExists: boolean): NonNullable<LocalModelServerEndpoint['diagnostics']> {
|
|
35
|
+
const stack = endpoint.stack ?? 'OpenAI-compatible';
|
|
36
|
+
return {
|
|
37
|
+
liveProbe: 'not-run',
|
|
38
|
+
successCriteria: [
|
|
39
|
+
'The confirmed smoke command exits 0.',
|
|
40
|
+
'The model-list endpoint returns JSON without credentials in output.',
|
|
41
|
+
'At least one model id is visible before refresh or benchmark.',
|
|
42
|
+
],
|
|
43
|
+
failureTriage: [
|
|
44
|
+
`If connection is refused, start the ${stack} server and load a model before refreshing Agent models.`,
|
|
45
|
+
'If the endpoint returns 404, verify the base URL path; most OpenAI-compatible servers should use /v1.',
|
|
46
|
+
'If the host is 0.0.0.0 or a LAN address, switch Agent to a trusted client URL such as 127.0.0.1 or the intended private host.',
|
|
47
|
+
],
|
|
48
|
+
afterSmoke: providerExists
|
|
49
|
+
? ['Run the refresh route, then run a local benchmark before changing the default route.']
|
|
50
|
+
: ['Add the provider route only after smoke succeeds, then refresh models and run a local benchmark.'],
|
|
51
|
+
policy: 'Diagnostics are read-only criteria and confirmed route hints. Agent probes local model-list endpoints only through models action:"smoke" after explicit confirmation; provider add, refresh, benchmark, and route changes remain separate actions.',
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface LocalModelServingReadModelSource {
|
|
56
|
+
readonly path: string;
|
|
57
|
+
readonly source: unknown;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface LocalModelServingDiagnosticRecord extends LocalModelServerServingDiagnostics {
|
|
61
|
+
readonly baseUrl: string | null;
|
|
62
|
+
readonly modelsUrl: string | null;
|
|
63
|
+
readonly providerId: string | null;
|
|
64
|
+
readonly stack: string | null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface LocalModelServingDiagnosticIndex {
|
|
68
|
+
readonly records: readonly LocalModelServingDiagnosticRecord[];
|
|
69
|
+
readonly publication: Omit<LocalModelServerDiagnosticsPublication, 'matchedEndpointCount'>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const LOCAL_MODEL_SERVING_READ_MODEL_PATHS = [
|
|
73
|
+
'context.platform.readModels.localModelServers',
|
|
74
|
+
'context.platform.readModels.localModelServing',
|
|
75
|
+
'context.platform.readModels.localModelDiagnostics',
|
|
76
|
+
'context.platform.readModels.models.localServers',
|
|
77
|
+
'context.platform.readModels.models.servingDiagnostics',
|
|
78
|
+
'context.platform.readModels.localModels.servingDiagnostics',
|
|
79
|
+
'context.platform.readModels.ollama.servingDiagnostics',
|
|
80
|
+
'context.platform.readModels.llamaCpp.servingDiagnostics',
|
|
81
|
+
'context.platform.readModels.vllm.servingDiagnostics',
|
|
82
|
+
'context.platform.readModels.localAi.servingDiagnostics',
|
|
83
|
+
'context.platform.readModels.openAiCompatible.servingDiagnostics',
|
|
84
|
+
'context.platform.localModelServing',
|
|
85
|
+
'context.clients.operator.models.servingDiagnostics',
|
|
86
|
+
'context.clients.operator.localModelServingDiagnostics',
|
|
87
|
+
] as const;
|
|
88
|
+
|
|
89
|
+
const LOCAL_MODEL_SECRET_PATTERNS: readonly [RegExp, string][] = [
|
|
90
|
+
[/("?\b(?:api[-_]?key|apikey|token|secret|password|passwd|credential|authorization)\b"?\s*:\s*)("[^"]*"|'[^']*'|[^\s,}]+)/gi, '$1"<redacted>"'],
|
|
91
|
+
[/\b([A-Z0-9_]*(?:API[_-]?KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTHORIZATION|BEARER)[A-Z0-9_]*)=("[^"]*"|'[^']*'|[^\s]+)/gi, '$1=<redacted>'],
|
|
92
|
+
[/(\b(?:token|secret|password|passwd|api[-_]?key|apikey|authorization|credential)\s*[:=]\s*)("[^"]*"|'[^']*'|[^\s,}]+)/gi, '$1<redacted>'],
|
|
93
|
+
[/(Authorization:\s*Bearer\s+)[A-Za-z0-9._~+/=-]+/gi, '$1<redacted>'],
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
function redactLocalModelDiagnosticText(value: string): string {
|
|
97
|
+
return LOCAL_MODEL_SECRET_PATTERNS.reduce((text, [pattern, replacement]) => text.replace(pattern, replacement), value);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function readNumber(value: unknown): number | null {
|
|
101
|
+
const parsed = typeof value === 'string' && value.trim() ? Number(value) : value;
|
|
102
|
+
return typeof parsed === 'number' && Number.isFinite(parsed) ? parsed : null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function readBoolean(value: unknown): boolean | null {
|
|
106
|
+
if (typeof value === 'boolean') return value;
|
|
107
|
+
if (typeof value === 'string') {
|
|
108
|
+
const normalized = value.trim().toLowerCase();
|
|
109
|
+
if (['true', 'yes', '1', 'supported', 'enabled'].includes(normalized)) return true;
|
|
110
|
+
if (['false', 'no', '0', 'unsupported', 'disabled'].includes(normalized)) return false;
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function readStringArray(value: unknown): readonly string[] {
|
|
116
|
+
const values = Array.isArray(value) ? value : [];
|
|
117
|
+
return [...new Set(values.map((entry) => {
|
|
118
|
+
if (typeof entry === 'string') return entry.trim();
|
|
119
|
+
const record = readRecord(entry);
|
|
120
|
+
return readString(record.id) || readString(record.name) || readString(record.model) || readString(record.modelId);
|
|
121
|
+
}).filter(Boolean))].slice(0, 12);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function firstString(record: Record<string, unknown>, keys: readonly string[]): string {
|
|
125
|
+
for (const key of keys) {
|
|
126
|
+
const value = readString(record[key]);
|
|
127
|
+
if (value) return value;
|
|
128
|
+
}
|
|
129
|
+
return '';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function firstNumber(record: Record<string, unknown>, keys: readonly string[]): number | null {
|
|
133
|
+
for (const key of keys) {
|
|
134
|
+
const value = readNumber(record[key]);
|
|
135
|
+
if (value !== null) return value;
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function firstBoolean(record: Record<string, unknown>, keys: readonly string[]): boolean | null {
|
|
141
|
+
for (const key of keys) {
|
|
142
|
+
const value = readBoolean(record[key]);
|
|
143
|
+
if (value !== null) return value;
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function isoFromDiagnosticValue(value: unknown): string | null {
|
|
149
|
+
const timestamp = typeof value === 'number' && Number.isFinite(value)
|
|
150
|
+
? value
|
|
151
|
+
: typeof value === 'string' && value.trim()
|
|
152
|
+
? Date.parse(value)
|
|
153
|
+
: Number.NaN;
|
|
154
|
+
return Number.isNaN(timestamp) ? null : new Date(timestamp).toISOString();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function localServingStatus(record: Record<string, unknown>, loadedModelCount: number): LocalModelServerServingDiagnostics['status'] {
|
|
158
|
+
const value = firstString(record, ['status', 'healthStatus', 'state', 'result', 'outcome']).toLowerCase().replace(/[_\s]+/g, '-');
|
|
159
|
+
if (['ready', 'ok', 'healthy', 'running', 'online', 'passed', 'success', 'succeeded', 'available'].includes(value)) return 'ready';
|
|
160
|
+
if (['blocked', 'missing', 'unavailable', 'unreachable', 'offline', 'not-running', 'needs-setup'].includes(value)) return 'blocked';
|
|
161
|
+
if (['degraded', 'attention', 'warning', 'slow', 'resource-pressure', 'limited'].includes(value)) return 'attention';
|
|
162
|
+
if (loadedModelCount > 0) return 'ready';
|
|
163
|
+
return 'unknown';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function localResourcePressure(record: Record<string, unknown>): LocalModelServerServingDiagnostics['resourcePressure'] {
|
|
167
|
+
const value = firstString(record, ['resourcePressure', 'pressure', 'memoryPressure', 'vramPressure']).toLowerCase().replace(/[_\s]+/g, '-');
|
|
168
|
+
if (['low', 'ok', 'normal', 'healthy'].includes(value)) return 'low';
|
|
169
|
+
if (['moderate', 'medium', 'warning', 'attention'].includes(value)) return 'moderate';
|
|
170
|
+
if (['high', 'critical', 'exhausted', 'oom'].includes(value)) return 'high';
|
|
171
|
+
const usage = firstNumber(record, ['memoryUsagePercent', 'memoryPercent', 'vramUsagePercent', 'gpuMemoryPercent']);
|
|
172
|
+
if (usage !== null) return usage >= 90 ? 'high' : usage >= 70 ? 'moderate' : 'low';
|
|
173
|
+
return 'unknown';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function localResourceSummary(record: Record<string, unknown>, pressure: LocalModelServerServingDiagnostics['resourcePressure']): string | undefined {
|
|
177
|
+
const explicit = firstString(record, ['resourceSummary', 'resources', 'resourceDetail']);
|
|
178
|
+
if (explicit) return redactLocalModelDiagnosticText(previewHarnessText(explicit, 180));
|
|
179
|
+
const parts = [
|
|
180
|
+
firstNumber(record, ['memoryUsagePercent', 'memoryPercent']) !== null ? `memory ${firstNumber(record, ['memoryUsagePercent', 'memoryPercent'])}%` : '',
|
|
181
|
+
firstNumber(record, ['vramUsagePercent', 'gpuMemoryPercent']) !== null ? `vram ${firstNumber(record, ['vramUsagePercent', 'gpuMemoryPercent'])}%` : '',
|
|
182
|
+
firstNumber(record, ['cpuPercent', 'cpuUsagePercent']) !== null ? `cpu ${firstNumber(record, ['cpuPercent', 'cpuUsagePercent'])}%` : '',
|
|
183
|
+
].filter(Boolean);
|
|
184
|
+
if (parts.length > 0) return `${pressure} pressure; ${parts.join(', ')}.`;
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function firstStringFromRecords(records: readonly Record<string, unknown>[], keys: readonly string[]): string {
|
|
189
|
+
for (const record of records) {
|
|
190
|
+
const value = firstString(record, keys);
|
|
191
|
+
if (value) return value;
|
|
192
|
+
}
|
|
193
|
+
return '';
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function localServingSchemaStatus(record: Record<string, unknown>): LocalModelServerServingDiagnostics['schemaStatus'] {
|
|
197
|
+
const records = [record, readRecord(record.schema), readRecord(record.contract), readRecord(record.receipt)];
|
|
198
|
+
const explicit = firstStringFromRecords(records, ['schemaStatus', 'receiptSchemaStatus', 'certificationStatus']).toLowerCase().replace(/[_\s]+/g, '-');
|
|
199
|
+
if (['certified', 'valid', 'verified', 'schema-certified'].includes(explicit)) return 'certified';
|
|
200
|
+
const schemaVersion = firstStringFromRecords(records, ['schemaVersion', 'receiptSchemaVersion', 'contractVersion']);
|
|
201
|
+
const provenance = firstStringFromRecords(records, ['methodId', 'sourceTool', 'actionId']);
|
|
202
|
+
const publicationGuarantee = firstStringFromRecords(records, ['publicationGuarantee', 'hostPublicationGuarantee', 'servingPublicationGuarantee']);
|
|
203
|
+
return schemaVersion && provenance && publicationGuarantee ? 'certified' : 'legacy';
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function routeTextFromDiagnosticValue(value: unknown): string {
|
|
207
|
+
const direct = readString(value);
|
|
208
|
+
if (direct) return previewHarnessText(redactLocalModelDiagnosticText(direct), 260);
|
|
209
|
+
const record = readRecord(value);
|
|
210
|
+
const route = firstString(record, ['route', 'modelRoute', 'operatorRoute', 'methodRoute', 'commandRoute']);
|
|
211
|
+
return route ? previewHarnessText(redactLocalModelDiagnosticText(route), 260) : '';
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function diagnosticRouteFromRecord(record: Record<string, unknown>, keys: readonly string[]): string {
|
|
215
|
+
for (const key of keys) {
|
|
216
|
+
const route = routeTextFromDiagnosticValue(record[key]);
|
|
217
|
+
if (route) return route;
|
|
218
|
+
}
|
|
219
|
+
for (const nestedKey of ['routes', 'actions', 'controls', 'operatorRoutes', 'hostRoutes', 'receiptRoutes']) {
|
|
220
|
+
const nested = readRecord(record[nestedKey]);
|
|
221
|
+
for (const key of keys) {
|
|
222
|
+
const route = routeTextFromDiagnosticValue(nested[key]);
|
|
223
|
+
if (route) return route;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return '';
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function diagnosticPublicationGuarantee(record: Record<string, unknown>): string {
|
|
230
|
+
const value = firstStringFromRecords(
|
|
231
|
+
[record, readRecord(record.schema), readRecord(record.contract), readRecord(record.receipt)],
|
|
232
|
+
['publicationGuarantee', 'hostPublicationGuarantee', 'servingPublicationGuarantee'],
|
|
233
|
+
);
|
|
234
|
+
return value ? previewHarnessText(redactLocalModelDiagnosticText(value), 220) : '';
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function localServingProvenance(
|
|
238
|
+
record: Record<string, unknown>,
|
|
239
|
+
startRoute: string,
|
|
240
|
+
repairRoute: string,
|
|
241
|
+
): readonly string[] {
|
|
242
|
+
const explicit = readStringArray(record.provenance);
|
|
243
|
+
const values = [
|
|
244
|
+
...explicit,
|
|
245
|
+
firstStringFromRecords([record, readRecord(record.receipt)], ['methodId']) ? `method ${firstStringFromRecords([record, readRecord(record.receipt)], ['methodId'])}` : '',
|
|
246
|
+
firstString(record, ['actionId']) ? `action ${firstString(record, ['actionId'])}` : '',
|
|
247
|
+
firstString(record, ['sourceTool']) ? `sourceTool ${firstString(record, ['sourceTool'])}` : '',
|
|
248
|
+
startRoute ? `start ${startRoute}` : '',
|
|
249
|
+
repairRoute ? `repair ${repairRoute}` : '',
|
|
250
|
+
];
|
|
251
|
+
return [...new Set(values
|
|
252
|
+
.map((entry) => previewHarnessText(redactLocalModelDiagnosticText(entry), 220))
|
|
253
|
+
.filter(Boolean))]
|
|
254
|
+
.slice(0, 8);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function setupServingReadModelSources(context: CommandContext): readonly LocalModelServingReadModelSource[] {
|
|
258
|
+
const contextRecord = context as unknown as Record<string, unknown>;
|
|
259
|
+
const platform = readRecord(contextRecord.platform);
|
|
260
|
+
const clients = readRecord(contextRecord.clients);
|
|
261
|
+
const readModels = readRecord(platform.readModels);
|
|
262
|
+
const modelReadModels = readRecord(readModels.models);
|
|
263
|
+
const localModels = readRecord(readModels.localModels);
|
|
264
|
+
const ollama = readRecord(readModels.ollama);
|
|
265
|
+
const llamaCpp = readRecord(readModels.llamaCpp);
|
|
266
|
+
const vllm = readRecord(readModels.vllm);
|
|
267
|
+
const localAi = readRecord(readModels.localAi);
|
|
268
|
+
const openAiCompatible = readRecord(readModels.openAiCompatible);
|
|
269
|
+
const operator = readRecord(clients.operator);
|
|
270
|
+
const operatorModels = readRecord(operator.models);
|
|
271
|
+
return [
|
|
272
|
+
{ path: 'context.platform.readModels.localModelServers', source: readModels.localModelServers },
|
|
273
|
+
{ path: 'context.platform.readModels.localModelServing', source: readModels.localModelServing },
|
|
274
|
+
{ path: 'context.platform.readModels.localModelDiagnostics', source: readModels.localModelDiagnostics },
|
|
275
|
+
{ path: 'context.platform.readModels.models.localServers', source: modelReadModels.localServers },
|
|
276
|
+
{ path: 'context.platform.readModels.models.servingDiagnostics', source: modelReadModels.servingDiagnostics },
|
|
277
|
+
{ path: 'context.platform.readModels.localModels.servingDiagnostics', source: localModels.servingDiagnostics },
|
|
278
|
+
{ path: 'context.platform.readModels.ollama.servingDiagnostics', source: ollama.servingDiagnostics },
|
|
279
|
+
{ path: 'context.platform.readModels.llamaCpp.servingDiagnostics', source: llamaCpp.servingDiagnostics },
|
|
280
|
+
{ path: 'context.platform.readModels.vllm.servingDiagnostics', source: vllm.servingDiagnostics },
|
|
281
|
+
{ path: 'context.platform.readModels.localAi.servingDiagnostics', source: localAi.servingDiagnostics },
|
|
282
|
+
{ path: 'context.platform.readModels.openAiCompatible.servingDiagnostics', source: openAiCompatible.servingDiagnostics },
|
|
283
|
+
{ path: 'context.platform.localModelServing', source: platform.localModelServing },
|
|
284
|
+
{ path: 'context.clients.operator.models.servingDiagnostics', source: operatorModels.servingDiagnostics },
|
|
285
|
+
{ path: 'context.clients.operator.localModelServingDiagnostics', source: operator.localModelServingDiagnostics },
|
|
286
|
+
];
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function readServingSnapshot(source: unknown): unknown {
|
|
290
|
+
if (typeof source === 'function') {
|
|
291
|
+
const value = (source as () => unknown)();
|
|
292
|
+
return value instanceof Promise ? null : value;
|
|
293
|
+
}
|
|
294
|
+
const record = readRecord(source);
|
|
295
|
+
for (const methodName of ['getSnapshot', 'snapshot', 'list', 'listDiagnostics', 'listServingDiagnostics', 'listLocalServers', 'listServers', 'readSnapshot']) {
|
|
296
|
+
const method = record[methodName];
|
|
297
|
+
if (typeof method === 'function') {
|
|
298
|
+
const value = (method as () => unknown)();
|
|
299
|
+
return value instanceof Promise ? null : value;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return source;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function diagnosticEntriesFromSnapshot(snapshot: unknown): readonly unknown[] {
|
|
306
|
+
if (Array.isArray(snapshot)) return snapshot;
|
|
307
|
+
if (snapshot instanceof Map) return [...snapshot.entries()].map(([id, entry]) => {
|
|
308
|
+
const entryRecord = readRecord(entry);
|
|
309
|
+
return Object.keys(entryRecord).length > 0 && !readString(entryRecord.id) ? { ...entryRecord, id } : entry;
|
|
310
|
+
});
|
|
311
|
+
const record = readRecord(snapshot);
|
|
312
|
+
for (const key of ['localModelServers', 'localServers', 'servingDiagnostics', 'diagnostics', 'servers', 'endpoints', 'records', 'items', 'entries']) {
|
|
313
|
+
const value = record[key];
|
|
314
|
+
if (value instanceof Map) return [...value.entries()].map(([id, entry]) => {
|
|
315
|
+
const entryRecord = readRecord(entry);
|
|
316
|
+
return Object.keys(entryRecord).length > 0 && !readString(entryRecord.id) ? { ...entryRecord, id } : entry;
|
|
317
|
+
});
|
|
318
|
+
if (Array.isArray(value)) return value;
|
|
319
|
+
const valueRecord = readRecord(value);
|
|
320
|
+
if (Object.keys(valueRecord).length > 0) {
|
|
321
|
+
return Object.entries(valueRecord).map(([id, entry]) => {
|
|
322
|
+
const entryRecord = readRecord(entry);
|
|
323
|
+
return Object.keys(entryRecord).length > 0 && !readString(entryRecord.id) ? { ...entryRecord, id } : entry;
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return Object.keys(record).length > 0 ? [record] : [];
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function isLocalServingDiagnosticRecord(record: Record<string, unknown>): boolean {
|
|
331
|
+
const base = firstString(record, ['baseUrl', 'baseURL', 'endpoint', 'url', 'modelsUrl', 'serverUrl']);
|
|
332
|
+
const providerId = firstString(record, ['providerId', 'provider', 'providerName']);
|
|
333
|
+
if (!base && !providerId) return false;
|
|
334
|
+
return Boolean(
|
|
335
|
+
firstString(record, ['serverVersion', 'version', 'resourcePressure', 'status', 'healthStatus', 'startReceiptId', 'repairReceiptId', 'receiptId'])
|
|
336
|
+
|| readStringArray(record.loadedModels).length > 0
|
|
337
|
+
|| readStringArray(record.models).length > 0
|
|
338
|
+
|| firstNumber(record, ['loadedModelCount', 'modelCount', 'contextWindowTokens', 'memoryUsagePercent', 'vramUsagePercent']) !== null,
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function normalizeServingDiagnosticRecord(value: unknown, source: string): LocalModelServingDiagnosticRecord | null {
|
|
343
|
+
const record = {
|
|
344
|
+
...readRecord(readRecord(value).metadata),
|
|
345
|
+
...readRecord(value),
|
|
346
|
+
};
|
|
347
|
+
if (!isLocalServingDiagnosticRecord(record)) return null;
|
|
348
|
+
const stack = firstString(record, ['stack', 'serverType', 'backend']) || localStackFor(JSON.stringify(record)) || null;
|
|
349
|
+
const rawBaseUrl = firstString(record, ['baseUrl', 'baseURL', 'endpoint', 'url', 'serverUrl']);
|
|
350
|
+
const rawModelsUrl = firstString(record, ['modelsUrl', 'modelListUrl']);
|
|
351
|
+
const baseUrl = rawBaseUrl
|
|
352
|
+
? normalizeLocalBaseUrl(rawBaseUrl, stack)
|
|
353
|
+
: rawModelsUrl
|
|
354
|
+
? normalizeLocalBaseUrl(rawModelsUrl, stack)
|
|
355
|
+
: null;
|
|
356
|
+
const modelsUrl = rawModelsUrl
|
|
357
|
+
? modelsUrlFor(normalizeLocalBaseUrl(rawModelsUrl, stack) ?? rawModelsUrl.replace(/\/models\/?$/i, ''))
|
|
358
|
+
: baseUrl
|
|
359
|
+
? modelsUrlFor(baseUrl)
|
|
360
|
+
: null;
|
|
361
|
+
const loadedModels = readStringArray(record.loadedModels).length > 0
|
|
362
|
+
? readStringArray(record.loadedModels)
|
|
363
|
+
: readStringArray(record.models).length > 0
|
|
364
|
+
? readStringArray(record.models)
|
|
365
|
+
: readStringArray(record.modelIds);
|
|
366
|
+
const loadedModelCount = firstNumber(record, ['loadedModelCount', 'modelCount']) ?? loadedModels.length;
|
|
367
|
+
const status = localServingStatus(record, loadedModelCount);
|
|
368
|
+
const resourcePressure = localResourcePressure(record);
|
|
369
|
+
const summary = redactLocalModelDiagnosticText(firstString(record, ['summary', 'detail', 'message']))
|
|
370
|
+
|| `${providerIdLabel(firstString(record, ['providerId', 'provider', 'providerName']))} local serving diagnostics are ${status}.`;
|
|
371
|
+
const serverVersion = firstString(record, ['serverVersion', 'version']);
|
|
372
|
+
const contextWindowTokens = firstNumber(record, ['contextWindowTokens', 'contextWindow', 'maxContextTokens']);
|
|
373
|
+
const toolSupport = firstBoolean(record, ['toolSupport', 'toolCalling', 'supportsTools', 'supportsToolCalling']);
|
|
374
|
+
const startReceiptId = firstString(record, ['startReceiptId', 'startupReceiptId']);
|
|
375
|
+
const repairReceiptId = firstString(record, ['repairReceiptId', 'restartReceiptId']);
|
|
376
|
+
const receiptStatus = firstString(record, ['receiptStatus', 'receiptOutcome']);
|
|
377
|
+
const schemaStatus = localServingSchemaStatus(record);
|
|
378
|
+
const schemaVersion = firstStringFromRecords(
|
|
379
|
+
[record, readRecord(record.schema), readRecord(record.contract), readRecord(record.receipt)],
|
|
380
|
+
['schemaVersion', 'receiptSchemaVersion', 'contractVersion'],
|
|
381
|
+
);
|
|
382
|
+
const publicationGuarantee = diagnosticPublicationGuarantee(record);
|
|
383
|
+
const publisher = firstString(record, ['publisher', 'publisherId', 'daemonId', 'hostId']);
|
|
384
|
+
const startRoute = diagnosticRouteFromRecord(record, ['startRoute', 'startupRoute', 'startModelServerRoute', 'startServerRoute', 'startActionRoute', 'startCommandRoute']);
|
|
385
|
+
const repairRoute = diagnosticRouteFromRecord(record, ['repairRoute', 'restartRoute', 'restartServerRoute', 'repairModelServerRoute', 'repairActionRoute', 'repairCommandRoute']);
|
|
386
|
+
const provenance = localServingProvenance(record, startRoute, repairRoute);
|
|
387
|
+
const missingSignals = [
|
|
388
|
+
...(schemaStatus === 'certified' ? [] : ['Certified local serving diagnostics schema is not published.']),
|
|
389
|
+
...(serverVersion ? [] : ['Server version is not published.']),
|
|
390
|
+
...(loadedModelCount > 0 ? [] : ['Loaded model detail is not published.']),
|
|
391
|
+
...(contextWindowTokens !== null ? [] : ['Context-window support is not published.']),
|
|
392
|
+
...(toolSupport !== null ? [] : ['Tool-support capability is not published.']),
|
|
393
|
+
...(resourcePressure !== 'unknown' ? [] : ['Resource pressure is not published.']),
|
|
394
|
+
...(startReceiptId || repairReceiptId ? [] : ['Start/repair receipt ids are not published.']),
|
|
395
|
+
...(startRoute || repairRoute ? [] : ['Host-published start/repair execution routes are not published.']),
|
|
396
|
+
];
|
|
397
|
+
return {
|
|
398
|
+
status,
|
|
399
|
+
source,
|
|
400
|
+
summary: previewHarnessText(summary, 220),
|
|
401
|
+
schemaStatus,
|
|
402
|
+
...(schemaVersion ? { schemaVersion: previewHarnessText(redactLocalModelDiagnosticText(schemaVersion), 80) } : {}),
|
|
403
|
+
...(provenance.length > 0 ? { provenance } : {}),
|
|
404
|
+
...(publicationGuarantee ? { publicationGuarantee } : {}),
|
|
405
|
+
...(publisher ? { publisher: previewHarnessText(redactLocalModelDiagnosticText(publisher), 80) } : {}),
|
|
406
|
+
...(serverVersion ? { serverVersion: previewHarnessText(serverVersion, 80) } : {}),
|
|
407
|
+
...(loadedModelCount > 0 ? { loadedModelCount } : {}),
|
|
408
|
+
loadedModels: loadedModels.map((model) => previewHarnessText(model, 96)),
|
|
409
|
+
...(contextWindowTokens !== null ? { contextWindowTokens } : {}),
|
|
410
|
+
...(toolSupport !== null ? { toolSupport } : {}),
|
|
411
|
+
resourcePressure,
|
|
412
|
+
...(localResourceSummary(record, resourcePressure) ? { resourceSummary: localResourceSummary(record, resourcePressure) } : {}),
|
|
413
|
+
lastCheckedAt: isoFromDiagnosticValue(firstString(record, ['lastCheckedAt', 'checkedAt', 'updatedAt', 'timestamp']) || record.lastCheckedAt || record.checkedAt || record.updatedAt || record.timestamp),
|
|
414
|
+
...(startReceiptId ? { startReceiptId } : {}),
|
|
415
|
+
...(repairReceiptId ? { repairReceiptId } : {}),
|
|
416
|
+
...(receiptStatus ? { receiptStatus } : {}),
|
|
417
|
+
...(startRoute ? { startRoute } : {}),
|
|
418
|
+
...(repairRoute ? { repairRoute } : {}),
|
|
419
|
+
inspectRoute: firstString(record, ['inspectRoute', 'modelRoute', 'route']) || 'models action:"local" includeParameters:true',
|
|
420
|
+
missingSignals,
|
|
421
|
+
policy: 'Read-only daemon-published local serving diagnostics. Agent consumes certified version/model/capability/resource/receipt metadata when present, and surfaces start/repair routes only when the host publishes exact confirmed routes; provider edits, refresh, smoke, benchmark, and route changes remain separate visible confirmed actions.',
|
|
422
|
+
baseUrl,
|
|
423
|
+
modelsUrl,
|
|
424
|
+
providerId: firstString(record, ['providerId', 'provider', 'providerName']) || null,
|
|
425
|
+
stack,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function providerIdLabel(providerId: string): string {
|
|
430
|
+
return providerId || 'Detected';
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function localServingDiagnosticsIndex(context: CommandContext): LocalModelServingDiagnosticIndex {
|
|
434
|
+
const records: LocalModelServingDiagnosticRecord[] = [];
|
|
435
|
+
const sourcePaths = new Set<string>();
|
|
436
|
+
let sawSource = false;
|
|
437
|
+
let sawError = false;
|
|
438
|
+
for (const entry of setupServingReadModelSources(context)) {
|
|
439
|
+
if (entry.source === undefined || entry.source === null) continue;
|
|
440
|
+
sawSource = true;
|
|
441
|
+
try {
|
|
442
|
+
const sourceRecords = diagnosticEntriesFromSnapshot(readServingSnapshot(entry.source))
|
|
443
|
+
.flatMap((value) => {
|
|
444
|
+
const record = normalizeServingDiagnosticRecord(value, entry.path);
|
|
445
|
+
return record ? [record] : [];
|
|
446
|
+
});
|
|
447
|
+
if (sourceRecords.length > 0) sourcePaths.add(entry.path);
|
|
448
|
+
records.push(...sourceRecords);
|
|
449
|
+
} catch {
|
|
450
|
+
sawError = true;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
const status = records.length > 0
|
|
454
|
+
? 'published-read-model'
|
|
455
|
+
: sawError
|
|
456
|
+
? 'read-model-error'
|
|
457
|
+
: sawSource
|
|
458
|
+
? 'read-model-empty'
|
|
459
|
+
: 'not-published';
|
|
460
|
+
const missingSignals = records.length > 0
|
|
461
|
+
? []
|
|
462
|
+
: ['No daemon-published local serving diagnostics read model is available to Agent yet.'];
|
|
463
|
+
return {
|
|
464
|
+
records,
|
|
465
|
+
publication: {
|
|
466
|
+
status,
|
|
467
|
+
requiredPaths: LOCAL_MODEL_SERVING_READ_MODEL_PATHS,
|
|
468
|
+
sourcePaths: [...sourcePaths],
|
|
469
|
+
recordCount: records.length,
|
|
470
|
+
missingSignals,
|
|
471
|
+
policy: 'Agent reads local serving diagnostics only from SDK/daemon read models. It does not probe local servers until the user confirms models action:"smoke".',
|
|
472
|
+
},
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function servingDiagnosticForEndpoint(
|
|
477
|
+
records: readonly LocalModelServingDiagnosticRecord[],
|
|
478
|
+
endpoint: MutableLocalModelServerEndpoint,
|
|
479
|
+
): LocalModelServingDiagnosticRecord | null {
|
|
480
|
+
return records.find((record) => record.baseUrl === endpoint.baseUrl || record.modelsUrl === modelsUrlFor(endpoint.baseUrl))
|
|
481
|
+
?? records.find((record) => Boolean(record.providerId && endpoint.providerId && record.providerId === endpoint.providerId))
|
|
482
|
+
?? null;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
export function localModelServerEndpoints(
|
|
486
|
+
context: CommandContext,
|
|
487
|
+
includeParameters: boolean,
|
|
488
|
+
): readonly LocalModelServerEndpoint[] {
|
|
489
|
+
const servingDiagnostics = localServingDiagnosticsIndex(context);
|
|
490
|
+
return collectLocalServerEndpointCandidates(context).map((endpoint) => describeLocalServerEndpoint(
|
|
491
|
+
endpoint,
|
|
492
|
+
includeParameters,
|
|
493
|
+
servingDiagnosticForEndpoint(servingDiagnostics.records, endpoint),
|
|
494
|
+
));
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
export function localModelServerDefaults(): readonly LocalModelServerDefaultEndpoint[] {
|
|
498
|
+
const defaults = [
|
|
499
|
+
{
|
|
500
|
+
id: 'ollama',
|
|
501
|
+
label: 'Ollama',
|
|
502
|
+
stack: 'ollama',
|
|
503
|
+
baseUrl: 'http://127.0.0.1:11434/v1',
|
|
504
|
+
startHint: 'Start Ollama, pull a practical model, then refresh models.',
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
id: 'lm-studio',
|
|
508
|
+
label: 'LM Studio',
|
|
509
|
+
stack: 'openai-compatible',
|
|
510
|
+
baseUrl: 'http://127.0.0.1:1234/v1',
|
|
511
|
+
startHint: 'Start the LM Studio local server and load one model.',
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
id: 'llama-cpp',
|
|
515
|
+
label: 'llama.cpp',
|
|
516
|
+
stack: 'llama.cpp',
|
|
517
|
+
baseUrl: 'http://127.0.0.1:8080/v1',
|
|
518
|
+
startHint: 'Run llama-server with a GGUF model before adding the provider.',
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
id: 'vllm',
|
|
522
|
+
label: 'vLLM',
|
|
523
|
+
stack: 'vllm',
|
|
524
|
+
baseUrl: 'http://127.0.0.1:8000/v1',
|
|
525
|
+
startHint: 'Start the vLLM OpenAI-compatible API server after GPU/driver checks.',
|
|
526
|
+
},
|
|
527
|
+
] as const;
|
|
528
|
+
return defaults.map((entry) => ({
|
|
529
|
+
...entry,
|
|
530
|
+
modelsUrl: modelsUrlFor(entry.baseUrl),
|
|
531
|
+
smokeCommand: `curl -fsS ${modelsUrlFor(entry.baseUrl)}`,
|
|
532
|
+
addProviderRoute: localProviderAddRoute(null, entry.stack, entry.baseUrl),
|
|
533
|
+
}));
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
export function localModelDetection(context: CommandContext): LocalModelDetection {
|
|
537
|
+
const providerIds = new Set<string>();
|
|
538
|
+
const modelRoutes = new Set<string>();
|
|
539
|
+
const stacks = new Set<string>();
|
|
540
|
+
for (const providerId of listProviderIds(context)) {
|
|
541
|
+
const stack = localStackFor(providerId);
|
|
542
|
+
if (!stack) continue;
|
|
543
|
+
providerIds.add(providerId);
|
|
544
|
+
stacks.add(stack);
|
|
545
|
+
}
|
|
546
|
+
for (const model of listRegistryModels(context)) {
|
|
547
|
+
const record = readRecord(model);
|
|
548
|
+
const providerId = modelProviderId(model);
|
|
549
|
+
const registryKey = modelRegistryKey(model);
|
|
550
|
+
const fields = [
|
|
551
|
+
providerId,
|
|
552
|
+
registryKey,
|
|
553
|
+
modelModelId(model),
|
|
554
|
+
modelDisplayName(model),
|
|
555
|
+
readString(record.description),
|
|
556
|
+
readString(record.baseURL),
|
|
557
|
+
readString(record.baseUrl),
|
|
558
|
+
readString(record.serverType),
|
|
559
|
+
JSON.stringify(record.providerEnvVars ?? ''),
|
|
560
|
+
].join('\n');
|
|
561
|
+
const stack = localStackFor(fields);
|
|
562
|
+
if (!stack) continue;
|
|
563
|
+
if (providerId) providerIds.add(providerId);
|
|
564
|
+
if (registryKey) modelRoutes.add(registryKey);
|
|
565
|
+
stacks.add(stack);
|
|
566
|
+
}
|
|
567
|
+
return {
|
|
568
|
+
providerIds: [...providerIds].sort((a, b) => a.localeCompare(b)),
|
|
569
|
+
modelRoutes: [...modelRoutes].sort((a, b) => a.localeCompare(b)),
|
|
570
|
+
stacks: [...stacks].sort((a, b) => a.localeCompare(b)),
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
export function addLocalServerEndpoint(
|
|
575
|
+
endpoints: Map<string, MutableLocalModelServerEndpoint>,
|
|
576
|
+
input: {
|
|
577
|
+
readonly baseUrl: string;
|
|
578
|
+
readonly providerId?: string | null;
|
|
579
|
+
readonly stack?: string | null;
|
|
580
|
+
readonly source: LocalModelEndpointSource;
|
|
581
|
+
readonly sourceDetail: string;
|
|
582
|
+
readonly modelRoutes?: readonly string[];
|
|
583
|
+
readonly notes?: readonly string[];
|
|
584
|
+
},
|
|
585
|
+
): void {
|
|
586
|
+
const normalized = normalizeLocalBaseUrl(input.baseUrl, input.stack);
|
|
587
|
+
if (!normalized) return;
|
|
588
|
+
const stack = input.stack ?? localStackFor(input.baseUrl) ?? localStackFor(`${input.providerId ?? ''}\n${input.sourceDetail}`) ?? 'openai-compatible';
|
|
589
|
+
const existing = endpoints.get(normalized);
|
|
590
|
+
if (existing) {
|
|
591
|
+
if (!existing.providerId && input.providerId) existing.providerId = input.providerId;
|
|
592
|
+
if (!existing.stack && stack) existing.stack = stack;
|
|
593
|
+
existing.sources.add(input.source);
|
|
594
|
+
existing.sourceDetails.add(input.sourceDetail);
|
|
595
|
+
for (const route of input.modelRoutes ?? []) existing.modelRoutes.add(route);
|
|
596
|
+
for (const note of input.notes ?? []) existing.notes.add(note);
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
endpoints.set(normalized, {
|
|
600
|
+
providerId: input.providerId ?? null,
|
|
601
|
+
stack,
|
|
602
|
+
baseUrl: normalized,
|
|
603
|
+
sources: new Set([input.source]),
|
|
604
|
+
sourceDetails: new Set([input.sourceDetail]),
|
|
605
|
+
modelRoutes: new Set(input.modelRoutes ?? []),
|
|
606
|
+
notes: new Set(input.notes ?? []),
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
export function collectLocalServerEndpointCandidates(context: CommandContext): readonly MutableLocalModelServerEndpoint[] {
|
|
611
|
+
const endpoints = new Map<string, MutableLocalModelServerEndpoint>();
|
|
612
|
+
for (const provider of listProviderRegistryProviders(context)) {
|
|
613
|
+
const record = readRecord(provider);
|
|
614
|
+
const providerId = readString(record.name) || readString(record.id) || readString(record.providerId) || readString(record.provider);
|
|
615
|
+
const fields = [
|
|
616
|
+
providerId,
|
|
617
|
+
readString(record.label),
|
|
618
|
+
readString(record.displayName),
|
|
619
|
+
readString(record.description),
|
|
620
|
+
readString(record.baseURL),
|
|
621
|
+
readString(record.baseUrl),
|
|
622
|
+
readString(record.endpoint),
|
|
623
|
+
JSON.stringify(record.serviceNames ?? ''),
|
|
624
|
+
JSON.stringify(record.envVars ?? ''),
|
|
625
|
+
].join('\n');
|
|
626
|
+
const stack = localStackFor(fields);
|
|
627
|
+
const routes = readProviderModels(record.models, providerId);
|
|
628
|
+
const urls = [
|
|
629
|
+
readString(record.baseURL),
|
|
630
|
+
readString(record.baseUrl),
|
|
631
|
+
readString(record.endpoint),
|
|
632
|
+
...extractUrls(fields),
|
|
633
|
+
].filter(Boolean);
|
|
634
|
+
for (const baseUrl of urls) {
|
|
635
|
+
addLocalServerEndpoint(endpoints, {
|
|
636
|
+
baseUrl,
|
|
637
|
+
providerId: providerId || null,
|
|
638
|
+
stack: stack ?? localStackFor(baseUrl),
|
|
639
|
+
source: 'provider-registry',
|
|
640
|
+
sourceDetail: providerId ? `provider:${providerId}` : 'provider-registry',
|
|
641
|
+
modelRoutes: routes,
|
|
642
|
+
notes: providerId ? ['Provider already exists in the registry.'] : [],
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
for (const model of listRegistryModels(context)) {
|
|
648
|
+
const record = readRecord(model);
|
|
649
|
+
const providerId = modelProviderId(model);
|
|
650
|
+
const registryKey = modelRegistryKey(model);
|
|
651
|
+
const fields = [
|
|
652
|
+
providerId,
|
|
653
|
+
registryKey,
|
|
654
|
+
modelModelId(model),
|
|
655
|
+
modelDisplayName(model),
|
|
656
|
+
readString(record.description),
|
|
657
|
+
readString(record.baseURL),
|
|
658
|
+
readString(record.baseUrl),
|
|
659
|
+
readString(record.serverType),
|
|
660
|
+
JSON.stringify(record.providerEnvVars ?? ''),
|
|
661
|
+
].join('\n');
|
|
662
|
+
const stack = localStackFor(fields);
|
|
663
|
+
const urls = [
|
|
664
|
+
readString(record.baseURL),
|
|
665
|
+
readString(record.baseUrl),
|
|
666
|
+
readString(record.endpoint),
|
|
667
|
+
...extractUrls(readString(record.description)),
|
|
668
|
+
].filter(Boolean);
|
|
669
|
+
for (const baseUrl of urls) {
|
|
670
|
+
addLocalServerEndpoint(endpoints, {
|
|
671
|
+
baseUrl,
|
|
672
|
+
providerId: providerId || null,
|
|
673
|
+
stack: stack ?? localStackFor(baseUrl),
|
|
674
|
+
source: 'model-registry',
|
|
675
|
+
sourceDetail: registryKey ? `model:${registryKey}` : 'model-registry',
|
|
676
|
+
modelRoutes: registryKey ? [registryKey] : [],
|
|
677
|
+
notes: registryKey ? ['At least one model route is already registered for this endpoint.'] : [],
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
const envHints: readonly { readonly key: string; readonly stack: string; readonly note: string }[] = [
|
|
683
|
+
{ key: 'OLLAMA_BASE_URL', stack: 'ollama', note: 'Environment override for the Ollama base URL.' },
|
|
684
|
+
{ key: 'OLLAMA_HOST', stack: 'ollama', note: 'Environment override for the Ollama host.' },
|
|
685
|
+
{ key: 'LM_STUDIO_BASE_URL', stack: 'openai-compatible', note: 'Environment override for LM Studio.' },
|
|
686
|
+
{ key: 'OPENAI_COMPATIBLE_BASE_URL', stack: 'openai-compatible', note: 'Environment override for a local OpenAI-compatible server.' },
|
|
687
|
+
{ key: 'OPENAI_COMPAT_BASE_URL', stack: 'openai-compatible', note: 'Environment override for a local OpenAI-compatible server.' },
|
|
688
|
+
{ key: 'VLLM_BASE_URL', stack: 'vllm', note: 'Environment override for vLLM.' },
|
|
689
|
+
{ key: 'LLAMA_CPP_BASE_URL', stack: 'llama.cpp', note: 'Environment override for llama.cpp.' },
|
|
690
|
+
{ key: 'LITELLM_BASE_URL', stack: 'openai-compatible', note: 'Environment override for LiteLLM or a local gateway.' },
|
|
691
|
+
];
|
|
692
|
+
for (const hint of envHints) {
|
|
693
|
+
const value = readString(process.env[hint.key]);
|
|
694
|
+
if (!value) continue;
|
|
695
|
+
addLocalServerEndpoint(endpoints, {
|
|
696
|
+
baseUrl: value,
|
|
697
|
+
stack: hint.stack,
|
|
698
|
+
source: 'environment',
|
|
699
|
+
sourceDetail: `env:${hint.key}`,
|
|
700
|
+
notes: [hint.note],
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
return [...endpoints.values()].sort((left, right) => left.baseUrl.localeCompare(right.baseUrl));
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
export function describeLocalServerEndpoint(
|
|
708
|
+
endpoint: MutableLocalModelServerEndpoint,
|
|
709
|
+
includeParameters = false,
|
|
710
|
+
servingDiagnostics?: LocalModelServerServingDiagnostics | null,
|
|
711
|
+
): LocalModelServerEndpoint {
|
|
712
|
+
const modelsUrl = modelsUrlFor(endpoint.baseUrl);
|
|
713
|
+
const providerExists = Boolean(endpoint.providerId) || endpoint.modelRoutes.size > 0;
|
|
714
|
+
const notes = new Set(endpoint.notes);
|
|
715
|
+
if (endpoint.baseUrl.includes('0.0.0.0')) {
|
|
716
|
+
notes.add('0.0.0.0 is usually a listen address; prefer 127.0.0.1 or a trusted LAN host for the client provider URL.');
|
|
717
|
+
}
|
|
718
|
+
if (!providerExists) {
|
|
719
|
+
notes.add('No registered model route was found for this endpoint yet.');
|
|
720
|
+
}
|
|
721
|
+
const id = localEndpointId(endpoint.baseUrl);
|
|
722
|
+
return {
|
|
723
|
+
kind: 'local-server-endpoint',
|
|
724
|
+
id,
|
|
725
|
+
providerId: endpoint.providerId,
|
|
726
|
+
stack: endpoint.stack,
|
|
727
|
+
baseUrl: endpoint.baseUrl,
|
|
728
|
+
modelsUrl,
|
|
729
|
+
diagnosticStatus: providerExists ? 'registered-route-needs-smoke' : 'needs-provider-after-smoke',
|
|
730
|
+
inspectRoute: localEndpointInspectRoute(id),
|
|
731
|
+
sources: [...endpoint.sources].sort((a, b) => a.localeCompare(b)),
|
|
732
|
+
sourceDetails: [...endpoint.sourceDetails].sort((a, b) => a.localeCompare(b)),
|
|
733
|
+
modelRoutes: [...endpoint.modelRoutes].sort((a, b) => a.localeCompare(b)),
|
|
734
|
+
smokeCommand: `curl -fsS ${modelsUrl}`,
|
|
735
|
+
smokeRoute: localEndpointSmokeRoute(id),
|
|
736
|
+
refreshRoute: 'agent_harness mode:"run_command" command:"/refresh-models" confirm:true explicitUserRequest:"Refresh models after verifying the local server."',
|
|
737
|
+
addProviderRoute: providerExists ? null : localProviderAddRoute(endpoint.providerId, endpoint.stack, endpoint.baseUrl),
|
|
738
|
+
notes: [...notes],
|
|
739
|
+
...(servingDiagnostics ? { servingDiagnostics } : {}),
|
|
740
|
+
...(includeParameters ? { diagnostics: localEndpointDiagnostics(endpoint, providerExists) } : {}),
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
export function localModelServerHealthMap(
|
|
745
|
+
context: CommandContext,
|
|
746
|
+
includeParameters: boolean,
|
|
747
|
+
): LocalModelServerHealthMap {
|
|
748
|
+
const servingDiagnostics = localServingDiagnosticsIndex(context);
|
|
749
|
+
const rawEndpoints = collectLocalServerEndpointCandidates(context);
|
|
750
|
+
const matchedEndpointCount = rawEndpoints
|
|
751
|
+
.filter((endpoint) => Boolean(servingDiagnosticForEndpoint(servingDiagnostics.records, endpoint)))
|
|
752
|
+
.length;
|
|
753
|
+
const endpoints = rawEndpoints.map((endpoint) => describeLocalServerEndpoint(
|
|
754
|
+
endpoint,
|
|
755
|
+
includeParameters,
|
|
756
|
+
servingDiagnosticForEndpoint(servingDiagnostics.records, endpoint),
|
|
757
|
+
));
|
|
758
|
+
const returned = endpoints.slice(0, includeParameters ? 8 : 3);
|
|
759
|
+
const suggestedDefaults = localModelServerDefaults().slice(0, includeParameters ? 4 : 2);
|
|
760
|
+
const first = returned[0];
|
|
761
|
+
const hasMatchedDiagnostics = matchedEndpointCount > 0;
|
|
762
|
+
return {
|
|
763
|
+
status: endpoints.length > 0 ? 'candidate-endpoints' : 'no-local-endpoints',
|
|
764
|
+
liveProbe: 'not-run',
|
|
765
|
+
endpointCount: endpoints.length,
|
|
766
|
+
returnedEndpoints: returned.length,
|
|
767
|
+
endpoints: returned,
|
|
768
|
+
suggestedDefaults,
|
|
769
|
+
daemonDiagnostics: {
|
|
770
|
+
...servingDiagnostics.publication,
|
|
771
|
+
matchedEndpointCount,
|
|
772
|
+
missingSignals: hasMatchedDiagnostics ? [] : servingDiagnostics.publication.missingSignals,
|
|
773
|
+
},
|
|
774
|
+
nextActions: endpoints.length > 0
|
|
775
|
+
? [
|
|
776
|
+
...(hasMatchedDiagnostics ? ['Review published local serving diagnostics before smoke, refresh, benchmark, or provider changes.'] : []),
|
|
777
|
+
`Smoke test ${first?.modelsUrl ?? 'the detected model-list endpoint'} before benchmark or route changes.`,
|
|
778
|
+
'Refresh the model catalog after the local server is running and reachable.',
|
|
779
|
+
'Run the local benchmark comparison before making a local route the default.',
|
|
780
|
+
]
|
|
781
|
+
: [
|
|
782
|
+
'Start one local server from suggestedDefaults, then smoke test its model-list endpoint.',
|
|
783
|
+
'Add or select the provider route only after the server is reachable.',
|
|
784
|
+
'Refresh models and run the local benchmark before changing the default route.',
|
|
785
|
+
],
|
|
786
|
+
policy: 'Read-only local endpoint map. It derives candidate model-list URLs, smoke commands, confirmed route hints, and daemon-published serving diagnostics from registry/env/read-model metadata; it does not probe the network, install servers, download models, add providers, refresh models, benchmark, or change routes.',
|
|
787
|
+
};
|
|
788
|
+
}
|