@jheavenknows/bluerouter 1.0.58 → 1.0.59
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/app/.next-cli-build/BUILD_ID +1 -1
- package/app/.next-cli-build/app-path-routes-manifest.json +1 -1
- package/app/.next-cli-build/build-manifest.json +2 -2
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/basic-chat/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/cli-tools/[toolId]/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/cli-tools/[toolId]/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/cli-tools/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/cli-tools/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/combos/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/console-log/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/endpoint/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/endpoint/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/media-providers/[kind]/[id]/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/media-providers/[kind]/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/media-providers/combo/[id]/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/media-providers/web/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/mitm/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/ollama-nodes/page.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/ollama-nodes/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/page.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/profile/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/prompt-logs/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/provider-health/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/providers/[id]/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/providers/new/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/providers/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/proxy-pools/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/quota/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/skills/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/translator/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/usage/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/(dashboard)/dashboard/virtual-combos/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/_global-error.html +1 -1
- package/app/.next-cli-build/server/app/_global-error.rsc +1 -1
- package/app/.next-cli-build/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/_not-found.html +1 -1
- package/app/.next-cli-build/server/app/_not-found.rsc +3 -3
- package/app/.next-cli-build/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/api/auth/ldap/test/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/auth/ldap/users/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/auth/login/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/auth/oidc/callback/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/auth/oidc/start/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/auth/oidc/test/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/auth/status/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/all-statuses/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/antigravity-mitm/alias/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/antigravity-mitm/autostart/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/antigravity-mitm/prompt-log-db/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/antigravity-mitm/root-ca/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/antigravity-mitm/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/copilot-settings/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/cli-tools/cowork-settings/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/combos/[id]/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/combos/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/dashboard/prompt-logs/[id]/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/dashboard/prompt-logs/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/keys/[id]/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/keys/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/mcp/[plugin]/message/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/mcp/[plugin]/sse/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/media-providers/tts/deepgram/voices/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/media-providers/tts/elevenlabs/voices/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/media-providers/tts/inworld/voices/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/media-providers/tts/minimax/voices/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/models/alias/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/models/availability/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/models/custom/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/models/disabled/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/models/route.js +1 -1
- package/app/.next-cli-build/server/app/api/models/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/models/test/route.js +1 -1
- package/app/.next-cli-build/server/app/api/models/test/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/oauth/[provider]/[action]/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/oauth/codex/import-token/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/oauth/cursor/auto-import/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/oauth/cursor/import/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/oauth/gitlab/pat/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/oauth/iflow/cookie/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/oauth/kiro/import/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/oauth/kiro/social-exchange/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/ollama-model-memory/route.js +1 -1
- package/app/.next-cli-build/server/app/api/ollama-nodes/[id]/install/route.js +573 -134
- package/app/.next-cli-build/server/app/api/ollama-nodes/[id]/route.js +1 -1
- package/app/.next-cli-build/server/app/api/pricing/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/provider-nodes/[id]/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/provider-nodes/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/providers/[id]/models/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/providers/[id]/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/providers/[id]/test/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/providers/[id]/test-models/route.js +1 -1
- package/app/.next-cli-build/server/app/api/providers/[id]/test-models/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/providers/client/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/providers/route.js +1 -1
- package/app/.next-cli-build/server/app/api/providers/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/providers/test-batch/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/providers/validate/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/proxy-pools/[id]/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/proxy-pools/[id]/test/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/proxy-pools/cloudflare-deploy/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/proxy-pools/deno-deploy/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/proxy-pools/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/proxy-pools/vercel-deploy/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/settings/database/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/settings/require-login/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/settings/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/translator/console-logs/route.js +1 -1
- package/app/.next-cli-build/server/app/api/translator/console-logs/stream/route.js +1 -1
- package/app/.next-cli-build/server/app/api/translator/send/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/translator/translate/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/tunnel/disable/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/tunnel/enable/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/tunnel/status/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/tunnel/tailscale-check/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/tunnel/tailscale-disable/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/tunnel/tailscale-enable/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/tunnel/tailscale-install/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/tunnel/tailscale-login/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/tunnel/tailscale-start-daemon/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/usage/[connectionId]/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/usage/chart/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/usage/history/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/usage/providers/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/usage/request-details/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/usage/request-logs/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/usage/stats/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/usage/stream/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/api/chat/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/audio/speech/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/audio/transcriptions/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/chat/completions/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/embeddings/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/images/generations/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/messages/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/models/[kind]/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/models/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/responses/compact/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/responses/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/search/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1/web/fetch/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/v1beta/models/[...path]/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/version/route.js +1 -1
- package/app/.next-cli-build/server/app/api/version/shutdown/route.js +1 -1
- package/app/.next-cli-build/server/app/api/version/shutdown/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/version/update/route.js +1 -1
- package/app/.next-cli-build/server/app/api/version/update/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/virtual-combos/[id]/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/api/virtual-combos/route.js.nft.json +1 -1
- package/app/.next-cli-build/server/app/callback/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/callback.html +1 -1
- package/app/.next-cli-build/server/app/callback.rsc +3 -3
- package/app/.next-cli-build/server/app/callback.segments/_full.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/callback.segments/_head.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/callback.segments/_index.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/callback.segments/_tree.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/callback.segments/callback/__PAGE__.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/callback.segments/callback.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/basic-chat.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/basic-chat.rsc +5 -5
- package/app/.next-cli-build/server/app/dashboard/basic-chat.segments/!KGRhc2hib2FyZCk/dashboard/basic-chat/__PAGE__.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/basic-chat.segments/!KGRhc2hib2FyZCk/dashboard/basic-chat.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/basic-chat.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/basic-chat.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/basic-chat.segments/_full.segment.rsc +5 -5
- package/app/.next-cli-build/server/app/dashboard/basic-chat.segments/_head.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/basic-chat.segments/_index.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/dashboard/basic-chat.segments/_tree.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/cli-tools.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/cli-tools.rsc +5 -5
- package/app/.next-cli-build/server/app/dashboard/cli-tools.segments/!KGRhc2hib2FyZCk/dashboard/cli-tools/__PAGE__.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/cli-tools.segments/!KGRhc2hib2FyZCk/dashboard/cli-tools.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/cli-tools.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/cli-tools.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/cli-tools.segments/_full.segment.rsc +5 -5
- package/app/.next-cli-build/server/app/dashboard/cli-tools.segments/_head.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/cli-tools.segments/_index.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/dashboard/cli-tools.segments/_tree.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/combos.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/combos.rsc +5 -5
- package/app/.next-cli-build/server/app/dashboard/combos.segments/!KGRhc2hib2FyZCk/dashboard/combos/__PAGE__.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/combos.segments/!KGRhc2hib2FyZCk/dashboard/combos.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/combos.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/combos.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/combos.segments/_full.segment.rsc +5 -5
- package/app/.next-cli-build/server/app/dashboard/combos.segments/_head.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/combos.segments/_index.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/dashboard/combos.segments/_tree.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/endpoint.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/endpoint.rsc +6 -6
- package/app/.next-cli-build/server/app/dashboard/endpoint.segments/!KGRhc2hib2FyZCk/dashboard/endpoint/__PAGE__.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/dashboard/endpoint.segments/!KGRhc2hib2FyZCk/dashboard/endpoint.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/endpoint.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/endpoint.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/endpoint.segments/_full.segment.rsc +6 -6
- package/app/.next-cli-build/server/app/dashboard/endpoint.segments/_head.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/endpoint.segments/_index.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/dashboard/endpoint.segments/_tree.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/media-providers/web.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/media-providers/web.rsc +5 -5
- package/app/.next-cli-build/server/app/dashboard/media-providers/web.segments/!KGRhc2hib2FyZCk/dashboard/media-providers/web/__PAGE__.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/media-providers/web.segments/!KGRhc2hib2FyZCk/dashboard/media-providers/web.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/media-providers/web.segments/!KGRhc2hib2FyZCk/dashboard/media-providers.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/media-providers/web.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/media-providers/web.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/media-providers/web.segments/_full.segment.rsc +5 -5
- package/app/.next-cli-build/server/app/dashboard/media-providers/web.segments/_head.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/media-providers/web.segments/_index.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/dashboard/media-providers/web.segments/_tree.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/mitm.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/mitm.rsc +5 -5
- package/app/.next-cli-build/server/app/dashboard/mitm.segments/!KGRhc2hib2FyZCk/dashboard/mitm/__PAGE__.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/mitm.segments/!KGRhc2hib2FyZCk/dashboard/mitm.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/mitm.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/mitm.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/mitm.segments/_full.segment.rsc +5 -5
- package/app/.next-cli-build/server/app/dashboard/mitm.segments/_head.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/mitm.segments/_index.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/dashboard/mitm.segments/_tree.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/ollama-nodes.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/ollama-nodes.rsc +5 -5
- package/app/.next-cli-build/server/app/dashboard/ollama-nodes.segments/!KGRhc2hib2FyZCk/dashboard/ollama-nodes/__PAGE__.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/ollama-nodes.segments/!KGRhc2hib2FyZCk/dashboard/ollama-nodes.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/ollama-nodes.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/ollama-nodes.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/ollama-nodes.segments/_full.segment.rsc +5 -5
- package/app/.next-cli-build/server/app/dashboard/ollama-nodes.segments/_head.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/ollama-nodes.segments/_index.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/dashboard/ollama-nodes.segments/_tree.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/profile.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/profile.rsc +4 -4
- package/app/.next-cli-build/server/app/dashboard/profile.segments/!KGRhc2hib2FyZCk/dashboard/profile/__PAGE__.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/profile.segments/!KGRhc2hib2FyZCk/dashboard/profile.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/profile.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/profile.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/profile.segments/_full.segment.rsc +4 -4
- package/app/.next-cli-build/server/app/dashboard/profile.segments/_head.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/profile.segments/_index.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/dashboard/profile.segments/_tree.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/prompt-logs.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/prompt-logs.rsc +4 -4
- package/app/.next-cli-build/server/app/dashboard/prompt-logs.segments/!KGRhc2hib2FyZCk/dashboard/prompt-logs/__PAGE__.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/prompt-logs.segments/!KGRhc2hib2FyZCk/dashboard/prompt-logs.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/prompt-logs.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/prompt-logs.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/prompt-logs.segments/_full.segment.rsc +4 -4
- package/app/.next-cli-build/server/app/dashboard/prompt-logs.segments/_head.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/prompt-logs.segments/_index.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/dashboard/prompt-logs.segments/_tree.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/provider-health.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/provider-health.rsc +4 -4
- package/app/.next-cli-build/server/app/dashboard/provider-health.segments/!KGRhc2hib2FyZCk/dashboard/provider-health/__PAGE__.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/provider-health.segments/!KGRhc2hib2FyZCk/dashboard/provider-health.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/provider-health.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/provider-health.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/provider-health.segments/_full.segment.rsc +4 -4
- package/app/.next-cli-build/server/app/dashboard/provider-health.segments/_head.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/provider-health.segments/_index.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/dashboard/provider-health.segments/_tree.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/providers/new.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/providers/new.rsc +5 -5
- package/app/.next-cli-build/server/app/dashboard/providers/new.segments/!KGRhc2hib2FyZCk/dashboard/providers/new/__PAGE__.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/providers/new.segments/!KGRhc2hib2FyZCk/dashboard/providers/new.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/providers/new.segments/!KGRhc2hib2FyZCk/dashboard/providers.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/providers/new.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/providers/new.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/providers/new.segments/_full.segment.rsc +5 -5
- package/app/.next-cli-build/server/app/dashboard/providers/new.segments/_head.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/providers/new.segments/_index.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/dashboard/providers/new.segments/_tree.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/providers.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/providers.rsc +5 -5
- package/app/.next-cli-build/server/app/dashboard/providers.segments/!KGRhc2hib2FyZCk/dashboard/providers/__PAGE__.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/providers.segments/!KGRhc2hib2FyZCk/dashboard/providers.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/providers.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/providers.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/providers.segments/_full.segment.rsc +5 -5
- package/app/.next-cli-build/server/app/dashboard/providers.segments/_head.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/providers.segments/_index.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/dashboard/providers.segments/_tree.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/proxy-pools.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/proxy-pools.rsc +5 -5
- package/app/.next-cli-build/server/app/dashboard/proxy-pools.segments/!KGRhc2hib2FyZCk/dashboard/proxy-pools/__PAGE__.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/proxy-pools.segments/!KGRhc2hib2FyZCk/dashboard/proxy-pools.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/proxy-pools.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/proxy-pools.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/proxy-pools.segments/_full.segment.rsc +5 -5
- package/app/.next-cli-build/server/app/dashboard/proxy-pools.segments/_head.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/proxy-pools.segments/_index.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/dashboard/proxy-pools.segments/_tree.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/quota.html +2 -2
- package/app/.next-cli-build/server/app/dashboard/quota.rsc +6 -6
- package/app/.next-cli-build/server/app/dashboard/quota.segments/!KGRhc2hib2FyZCk/dashboard/quota/__PAGE__.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/dashboard/quota.segments/!KGRhc2hib2FyZCk/dashboard/quota.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/quota.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/quota.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/quota.segments/_full.segment.rsc +6 -6
- package/app/.next-cli-build/server/app/dashboard/quota.segments/_head.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/quota.segments/_index.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/dashboard/quota.segments/_tree.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/settings/pricing/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/dashboard/settings/pricing.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/settings/pricing.rsc +3 -3
- package/app/.next-cli-build/server/app/dashboard/settings/pricing.segments/_full.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/dashboard/settings/pricing.segments/_head.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/settings/pricing.segments/_index.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/dashboard/settings/pricing.segments/_tree.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/settings/pricing.segments/dashboard/settings/pricing/__PAGE__.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/settings/pricing.segments/dashboard/settings/pricing.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/settings/pricing.segments/dashboard/settings.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/settings/pricing.segments/dashboard.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/skills.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/skills.rsc +5 -5
- package/app/.next-cli-build/server/app/dashboard/skills.segments/!KGRhc2hib2FyZCk/dashboard/skills/__PAGE__.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/skills.segments/!KGRhc2hib2FyZCk/dashboard/skills.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/skills.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/skills.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/skills.segments/_full.segment.rsc +5 -5
- package/app/.next-cli-build/server/app/dashboard/skills.segments/_head.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/skills.segments/_index.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/dashboard/skills.segments/_tree.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/translator.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/translator.rsc +5 -5
- package/app/.next-cli-build/server/app/dashboard/translator.segments/!KGRhc2hib2FyZCk/dashboard/translator/__PAGE__.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/translator.segments/!KGRhc2hib2FyZCk/dashboard/translator.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/translator.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/translator.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/translator.segments/_full.segment.rsc +5 -5
- package/app/.next-cli-build/server/app/dashboard/translator.segments/_head.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/translator.segments/_index.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/dashboard/translator.segments/_tree.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/usage.html +1 -1
- package/app/.next-cli-build/server/app/dashboard/usage.rsc +5 -5
- package/app/.next-cli-build/server/app/dashboard/usage.segments/!KGRhc2hib2FyZCk/dashboard/usage/__PAGE__.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/usage.segments/!KGRhc2hib2FyZCk/dashboard/usage.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/usage.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/usage.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/usage.segments/_full.segment.rsc +5 -5
- package/app/.next-cli-build/server/app/dashboard/usage.segments/_head.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/usage.segments/_index.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/dashboard/usage.segments/_tree.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/virtual-combos.html +2 -2
- package/app/.next-cli-build/server/app/dashboard/virtual-combos.rsc +4 -4
- package/app/.next-cli-build/server/app/dashboard/virtual-combos.segments/!KGRhc2hib2FyZCk/dashboard/virtual-combos/__PAGE__.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/virtual-combos.segments/!KGRhc2hib2FyZCk/dashboard/virtual-combos.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/virtual-combos.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/virtual-combos.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard/virtual-combos.segments/_full.segment.rsc +4 -4
- package/app/.next-cli-build/server/app/dashboard/virtual-combos.segments/_head.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard/virtual-combos.segments/_index.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/dashboard/virtual-combos.segments/_tree.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard.html +1 -1
- package/app/.next-cli-build/server/app/dashboard.rsc +5 -5
- package/app/.next-cli-build/server/app/dashboard.segments/!KGRhc2hib2FyZCk/dashboard/__PAGE__.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
- package/app/.next-cli-build/server/app/dashboard.segments/_full.segment.rsc +5 -5
- package/app/.next-cli-build/server/app/dashboard.segments/_head.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/dashboard.segments/_index.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/dashboard.segments/_tree.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/index.html +1 -1
- package/app/.next-cli-build/server/app/index.rsc +3 -3
- package/app/.next-cli-build/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/index.segments/_full.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/index.segments/_head.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/index.segments/_index.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/index.segments/_tree.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/landing/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/landing.html +1 -1
- package/app/.next-cli-build/server/app/landing.rsc +3 -3
- package/app/.next-cli-build/server/app/landing.segments/_full.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/landing.segments/_head.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/landing.segments/_index.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/landing.segments/_tree.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/landing.segments/landing/__PAGE__.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/landing.segments/landing.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/login/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app/login.html +1 -1
- package/app/.next-cli-build/server/app/login.rsc +3 -3
- package/app/.next-cli-build/server/app/login.segments/_full.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/login.segments/_head.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/login.segments/_index.segment.rsc +3 -3
- package/app/.next-cli-build/server/app/login.segments/_tree.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/login.segments/login/__PAGE__.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/login.segments/login.segment.rsc +1 -1
- package/app/.next-cli-build/server/app/page_client-reference-manifest.js +1 -1
- package/app/.next-cli-build/server/app-paths-manifest.json +1 -1
- package/app/.next-cli-build/server/chunks/2769.js +1 -1
- package/app/.next-cli-build/server/chunks/3110.js +1 -1
- package/app/.next-cli-build/server/chunks/4746.js +1 -1
- package/app/.next-cli-build/server/chunks/7130.js +1 -1
- package/app/.next-cli-build/server/chunks/7153.js +1 -1
- package/app/.next-cli-build/server/chunks/9253.js +1 -1
- package/app/.next-cli-build/server/middleware-build-manifest.js +1 -1
- package/app/.next-cli-build/server/pages/404.html +1 -1
- package/app/.next-cli-build/server/pages/500.html +1 -1
- package/app/.next-cli-build/static/chunks/{1321-333a720ed3de9e5e.js → 1321-41e9eaeb3270b514.js} +1 -1
- package/app/.next-cli-build/static/chunks/app/(dashboard)/dashboard/ollama-nodes/page-c771cf90148ecc62.js +1 -0
- package/app/cli/.build-home/AppData/Roaming/bluerouter/db/data.sqlite-shm +0 -0
- package/app/cli/.build-home/AppData/Roaming/bluerouter/db/data.sqlite-wal +0 -0
- package/app/package.json +1 -1
- package/package.json +1 -1
- package/app/.next-cli-build/static/chunks/app/(dashboard)/dashboard/ollama-nodes/page-3e97343c80613dec.js +0 -1
- /package/app/.next-cli-build/static/{HnTGcqSHEUKReNqlg_AiU → Q9DHPrSO_c4RIRpf5wwP3}/_buildManifest.js +0 -0
- /package/app/.next-cli-build/static/{HnTGcqSHEUKReNqlg_AiU → Q9DHPrSO_c4RIRpf5wwP3}/_ssgManifest.js +0 -0
- /package/app/cli/.build-home/AppData/Roaming/bluerouter/db/backups/{upgrade-1.0.53-to-1.0.54-1.0.54-20260527-190346 → upgrade-1.0.58-to-1.0.59-1.0.59-20260527-233043}/data.sqlite +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
(()=>{var a={};a.id=9902,a.ids=[9902],a.modules={261:a=>{"use strict";a.exports=require("next/dist/shared/lib/router/utils/app-paths")},3295:a=>{"use strict";a.exports=require("next/dist/server/app-render/after-task-async-storage.external.js")},6774:(a,b,c)=>{"use strict";c.d(b,{U1:()=>v,Vs:()=>w,YT:()=>z,iJ:()=>y,op:()=>A,ri:()=>x});var d=c(79748),e=c.n(d),f=c(33873),g=c.n(f),h=c(55511),i=c.n(h),j=c(21820),k=c.n(j),l=c(55013);let m=process.env.DATA_DIR||process.env.BLUEROUTER_DATA_DIR||g().join(k().homedir(),".bluerouter"),n=g().join(m,"ollama-nodes.json");async function o(){await e().mkdir(m,{recursive:!0});try{await e().access(n)}catch{await e().writeFile(n,JSON.stringify({nodes:[]},null,2))}}async function p(){await o();try{let a=await e().readFile(n,"utf8"),b=JSON.parse(a||"{}");return{nodes:Array.isArray(b.nodes)?b.nodes:[]}}catch{return{nodes:[]}}}async function q(a){await e().mkdir(m,{recursive:!0}),await e().writeFile(n,JSON.stringify({nodes:a.nodes||[]},null,2))}function r(){return new Date().toISOString()}function s(a,b={}){if(!a)return b;if("object"==typeof a&&!Array.isArray(a))return a;try{let c=JSON.parse(String(a));return c&&"object"==typeof c&&!Array.isArray(c)?c:b}catch{return b}}function t(a){return a?{...a,sshPassword:void 0,sudoPassword:void 0}:null}function u(a={},b={}){var c;let d=String(a.host??b.host??"").trim(),e=Number(a.agentPort??b.agentPort??19999)||19999,f=Array.isArray(c=a.ollamaPorts??b.ollamaPorts??[11434])?[...new Set(c.map(a=>Number(a)).filter(a=>Number.isFinite(a)&&a>0))]:[...new Set(String(c||"11434").split(/[\s,;]+/).map(a=>Number(a.trim())).filter(a=>Number.isFinite(a)&&a>0))],g=s(a.modelMemoryRequirements??b.modelMemoryRequirements,b.modelMemoryRequirements||{}),h=(0,l.nw)(g,{partial:!0,preserveEnabled:!0}),j=b.id||a.id||i().randomUUID(),k=String(a.agentToken??b.agentToken??i().randomBytes(24).toString("hex")).trim(),m=function(a,b,c={}){let d=new Map((Array.isArray(c.instances)?c.instances:[]).map(a=>[Number(a.port),a])),e=new Map((Array.isArray(a)?a:[]).map(a=>[Number(a.port),a]));return b.map(a=>(function(a={},b={}){var c;let d=Number(a.port??b.port??11434)||11434;return{id:String(a.id??b.id??`ollama-${d}`).trim()||`ollama-${d}`,name:String(a.name??b.name??`Ollama :${d}`).trim()||`Ollama :${d}`,port:d,enabled:a.enabled??b.enabled??!0,gpuMode:["auto","cpu","specific","all"].includes(a.gpuMode??b.gpuMode)?a.gpuMode??b.gpuMode:"auto",gpuIndices:Array.isArray(c=a.gpuIndices??b.gpuIndices??[])?[...new Set(c.map(a=>Number(a)).filter(a=>Number.isInteger(a)&&a>=0))]:[...new Set(String(c||"").split(/[\s,;]+/).map(a=>Number(a)).filter(a=>Number.isInteger(a)&&a>=0))],allowMultiGpuSplit:a.allowMultiGpuSplit??b.allowMultiGpuSplit??!1,tags:Array.isArray(a.tags??b.tags)?(a.tags??b.tags).map(a=>String(a).trim()).filter(Boolean):[],modelMemoryRequirements:(0,l.nw)(s(a.modelMemoryRequirements??b.modelMemoryRequirements,b.modelMemoryRequirements||{}),{partial:!0,preserveEnabled:!0})}})(e.get(Number(a))||{},d.get(Number(a))||{port:a}))}(a.instances??b.instances,f.length?f:[11434],b);return{id:j,name:String(a.name??b.name??d??"Ollama Node").trim()||d||"Ollama Node",host:d,sshPort:Number(a.sshPort??b.sshPort??22)||22,sshUser:String(a.sshUser??b.sshUser??"root").trim()||"root",useSudo:a.useSudo??b.useSudo??!1,agentPort:e,agentToken:k,ollamaPorts:f.length?f:[11434],instances:m,refreshIntervalMs:Math.max(5e3,Number(a.refreshIntervalMs??b.refreshIntervalMs??15e3)||15e3),enabled:a.enabled??b.enabled??!0,ufwAllowFrom:String(a.ufwAllowFrom??b.ufwAllowFrom??"").trim(),useGlobalModelMemory:a.useGlobalModelMemory??b.useGlobalModelMemory??!0,modelMemoryRequirements:h,notes:String(a.notes??b.notes??"").trim(),lastStatus:b.lastStatus||null,lastEvaluation:b.lastEvaluation||null,lastStatusAt:b.lastStatusAt||null,lastError:b.lastError||null,lastInstallAt:b.lastInstallAt||null,lastInstallOk:b.lastInstallOk||!1,lastInstallLog:b.lastInstallLog||"",createdAt:b.createdAt||r(),updatedAt:r()}}async function v(){return(await p()).nodes.map(a=>t(u(a,a)))}async function w(a){let b=(await p()).nodes.find(b=>b.id===a)||null;return b?u(b,b):null}async function x(a){let b=await p(),c=u(a);return b.nodes.push(c),await q(b),t(c)}async function y(a,b){let c=await p(),d=c.nodes.findIndex(b=>b.id===a);if(d<0)return null;let e=u(b,c.nodes[d]);return c.nodes[d]=e,await q(c),t(e)}async function z(a,b){let c=await p(),d=c.nodes.findIndex(b=>b.id===a);return d<0?null:(c.nodes[d]={...c.nodes[d],...b,updatedAt:r()},await q(c),t(u(c.nodes[d],c.nodes[d])))}async function A(a){let b=await p(),c=b.nodes.length;return b.nodes=b.nodes.filter(b=>b.id!==a),await q(b),b.nodes.length!==c}},10846:a=>{"use strict";a.exports=require("next/dist/compiled/next-server/app-page.runtime.prod.js")},21820:a=>{"use strict";a.exports=require("os")},29294:a=>{"use strict";a.exports=require("next/dist/server/app-render/work-async-storage.external.js")},33873:a=>{"use strict";a.exports=require("path")},44870:a=>{"use strict";a.exports=require("next/dist/compiled/next-server/app-route.runtime.prod.js")},55013:(a,b,c)=>{"use strict";c.d(b,{LM:()=>v,LQ:()=>u,MW:()=>s,eY:()=>t,nw:()=>o});var d=c(79748),e=c.n(d),f=c(33873),g=c.n(f),h=c(21820),i=c.n(h);let j=process.env.DATA_DIR||process.env.BLUEROUTER_DATA_DIR||g().join(i().homedir(),".bluerouter"),k=g().join(j,"ollama-model-memory.json"),l={"qwen3:1.7b":{minRamFreeMb:2048,minVramFreeMb:0,loadedRamFreeMb:512,loadedVramFreeMb:0,safetyMarginMb:512,notes:"small text model"},"qwen3:4b":{minRamFreeMb:4096,minVramFreeMb:0,loadedRamFreeMb:1024,loadedVramFreeMb:0,safetyMarginMb:512,notes:"small/medium text model"},"qwen3:8b":{minRamFreeMb:6144,minVramFreeMb:0,loadedRamFreeMb:1024,loadedVramFreeMb:512,safetyMarginMb:1024,notes:"8B text model"},"qwen3:14b":{minRamFreeMb:8192,minVramFreeMb:1e4,loadedRamFreeMb:2048,loadedVramFreeMb:1024,safetyMarginMb:1024,notes:"14B class model"},"qwen3:32b":{minRamFreeMb:4096,minVramFreeMb:16e3,loadedRamFreeMb:2048,loadedVramFreeMb:2048,safetyMarginMb:2048,notes:"32B class model, GPU preferred"},"qwen3-coder:30b":{minRamFreeMb:8192,minVramFreeMb:24e3,loadedRamFreeMb:2048,loadedVramFreeMb:3072,safetyMarginMb:2048,notes:"30B coder"},"qwen3-coder:latest":{minRamFreeMb:8192,minVramFreeMb:24e3,loadedRamFreeMb:2048,loadedVramFreeMb:3072,safetyMarginMb:2048,notes:"qwen3 coder latest, treat as 30B unless overridden"},"qwen2.5-coder:14b":{minRamFreeMb:8192,minVramFreeMb:1e4,loadedRamFreeMb:2048,loadedVramFreeMb:1024,safetyMarginMb:1024,notes:"14B coder"},"qwen2.5vl:7b":{minRamFreeMb:8192,minVramFreeMb:8e3,loadedRamFreeMb:2048,loadedVramFreeMb:1024,safetyMarginMb:1024,notes:"7B vision-language"},"qwen2.5vl:latest":{minRamFreeMb:8192,minVramFreeMb:1e4,loadedRamFreeMb:2048,loadedVramFreeMb:1024,safetyMarginMb:1024,notes:"vision-language latest"},"bge-m3:latest":{minRamFreeMb:4096,minVramFreeMb:0,loadedRamFreeMb:512,loadedVramFreeMb:0,safetyMarginMb:512,notes:"embedding model"},"deepseek-coder:6.7b":{minRamFreeMb:4096,minVramFreeMb:0,loadedRamFreeMb:1024,loadedVramFreeMb:512,safetyMarginMb:1024,notes:"6.7B coder"},"gemma4:e4b":{minRamFreeMb:4096,minVramFreeMb:0,loadedRamFreeMb:1024,loadedVramFreeMb:0,safetyMarginMb:1024,notes:"small Gemma variant"},"gemma:7b":{minRamFreeMb:4096,minVramFreeMb:0,loadedRamFreeMb:1024,loadedVramFreeMb:0,safetyMarginMb:1024,notes:"7B text model"},"glm-4.7-flash:latest":{minRamFreeMb:4096,minVramFreeMb:0,loadedRamFreeMb:1024,loadedVramFreeMb:512,safetyMarginMb:1024,notes:"flash/local text model"},"gpt-oss:20b":{minRamFreeMb:8192,minVramFreeMb:12e3,loadedRamFreeMb:2048,loadedVramFreeMb:2048,safetyMarginMb:2048,notes:"20B class model"},"llama3.1:8b":{minRamFreeMb:6144,minVramFreeMb:0,loadedRamFreeMb:1024,loadedVramFreeMb:512,safetyMarginMb:1024,notes:"8B text model"},"llama3.2-vision:latest":{minRamFreeMb:8192,minVramFreeMb:1e4,loadedRamFreeMb:2048,loadedVramFreeMb:1024,safetyMarginMb:1024,notes:"vision model"},"qwen3-*-translate-f16:*":{minRamFreeMb:8192,minVramFreeMb:16e3,loadedRamFreeMb:2048,loadedVramFreeMb:2048,safetyMarginMb:2048,notes:"custom Qwen3 f16 translate wildcard"},"qwen3-*-translate*:latest":{minRamFreeMb:6144,minVramFreeMb:8e3,loadedRamFreeMb:1024,loadedVramFreeMb:1024,safetyMarginMb:1024,notes:"custom Qwen3 translate wildcard"},"bge-m3:*":{minRamFreeMb:4096,minVramFreeMb:0,loadedRamFreeMb:512,loadedVramFreeMb:0,safetyMarginMb:512,notes:"embedding model wildcard"},"gemma*:e4b":{minRamFreeMb:4096,minVramFreeMb:0,loadedRamFreeMb:1024,loadedVramFreeMb:0,safetyMarginMb:1024,notes:"Gemma E4B wildcard"},"gemma*:7b":{minRamFreeMb:4096,minVramFreeMb:0,loadedRamFreeMb:1024,loadedVramFreeMb:0,safetyMarginMb:1024,notes:"Gemma 7B wildcard"},"*:1.7b":{minRamFreeMb:2048,minVramFreeMb:0,loadedRamFreeMb:512,loadedVramFreeMb:0,safetyMarginMb:512,notes:"generic 1.7B fallback"},"*:4b":{minRamFreeMb:4096,minVramFreeMb:0,loadedRamFreeMb:1024,loadedVramFreeMb:0,safetyMarginMb:512,notes:"generic 4B fallback"},"*:6.7b":{minRamFreeMb:4096,minVramFreeMb:0,loadedRamFreeMb:1024,loadedVramFreeMb:512,safetyMarginMb:1024,notes:"generic 6-7B fallback"},"*:7b":{minRamFreeMb:4096,minVramFreeMb:0,loadedRamFreeMb:1024,loadedVramFreeMb:512,safetyMarginMb:1024,notes:"generic 7B fallback"},"*:8b":{minRamFreeMb:6144,minVramFreeMb:0,loadedRamFreeMb:1024,loadedVramFreeMb:512,safetyMarginMb:1024,notes:"generic 8B fallback"},"*:14b":{minRamFreeMb:8192,minVramFreeMb:1e4,loadedRamFreeMb:2048,loadedVramFreeMb:1024,safetyMarginMb:1024,notes:"generic 14B fallback"},"*:20b":{minRamFreeMb:8192,minVramFreeMb:12e3,loadedRamFreeMb:2048,loadedVramFreeMb:2048,safetyMarginMb:2048,notes:"generic 20B fallback"},"*:30b":{minRamFreeMb:8192,minVramFreeMb:24e3,loadedRamFreeMb:2048,loadedVramFreeMb:3072,safetyMarginMb:2048,notes:"generic 30B fallback"},"*:32b":{minRamFreeMb:4096,minVramFreeMb:16e3,loadedRamFreeMb:2048,loadedVramFreeMb:2048,safetyMarginMb:2048,notes:"generic 32B fallback"}},m=["minRamFreeMb","minVramFreeMb","loadedRamFreeMb","loadedVramFreeMb","safetyMarginMb"];function n(a,b=0){let c=Number(a);return Number.isFinite(c)&&c>=0?c:b}function o(a={},b={}){let c={};if(!a||"object"!=typeof a||Array.isArray(a))return c;for(let[d,e]of Object.entries(a)){let a=String(d||"").trim();if(!a||!e||"object"!=typeof e||Array.isArray(e))continue;let f=function(a,{partial:b=!1,preserveEnabled:c=!1}={}){let d={};for(let c of m)if(b){var e;null!=(e=a?.[c])&&""!==String(e).trim()&&(d[c]=n(a[c],0))}else d[c]=n(a?.[c],0);let f=String(a?.notes||"").trim();return f&&(d.notes=f),c&&a&&Object.prototype.hasOwnProperty.call(a,"enabled")&&(d.enabled=!1!==a.enabled),d}(e,b),g=m.some(a=>Object.prototype.hasOwnProperty.call(f,a)),h=b.preserveEnabled&&Object.prototype.hasOwnProperty.call(f,"enabled");(g||f.notes||h)&&(c[a]=f)}return c}async function p(){await e().mkdir(j,{recursive:!0});try{await e().access(k)}catch{await e().writeFile(k,JSON.stringify({requirements:l,updatedAt:new Date().toISOString()},null,2))}}async function q(){await p();try{let a=await e().readFile(k,"utf8"),b=JSON.parse(a||"{}");return{requirements:function(a={}){return o({...l,...a})}(b.requirements||{}),updatedAt:b.updatedAt||null}}catch{return{requirements:o(l),updatedAt:null}}}async function r(a){await e().mkdir(j,{recursive:!0});let b={requirements:o(a),updatedAt:new Date().toISOString()};return await e().writeFile(k,JSON.stringify(b,null,2)),b}async function s(){return(await q()).requirements}async function t(){return q()}async function u(a){return r(a)}async function v(){return r(l)}},55511:a=>{"use strict";a.exports=require("crypto")},63033:a=>{"use strict";a.exports=require("next/dist/server/app-render/work-unit-async-storage.external.js")},68757:(a,b,c)=>{"use strict";c.r(b),c.d(b,{handler:()=>J,patchFetch:()=>I,routeModule:()=>E,serverHooks:()=>H,workAsyncStorage:()=>F,workUnitAsyncStorage:()=>G});var d={};c.r(d),c.d(d,{POST:()=>D,dynamic:()=>C,runtime:()=>B});var e=c(19225),f=c(84006),g=c(8317),h=c(99373),i=c(34775),j=c(24235),k=c(261),l=c(54365),m=c(90771),n=c(73461),o=c(67798),p=c(92280),q=c(62018),r=c(45696),s=c(47929),t=c(86439),u=c(37527),v=c(23211),w=c(6774),x=c(79646);function y(a){return`'${String(a??"").replace(/'/g,"'\"'\"'")}'`}function z(a,b,{input:c="",timeoutMs:d=12e4}={}){return new Promise(e=>{let f=(0,x.spawn)(a,b,{stdio:["pipe","pipe","pipe"]}),g="",h="",i=!1,j=setTimeout(()=>{i||f.kill("SIGKILL")},d);f.stdout.on("data",a=>{g+=a.toString()}),f.stderr.on("data",a=>{h+=a.toString()}),f.on("close",a=>{i=!0,clearTimeout(j),e({ok:0===a,code:a,stdout:g,stderr:h})}),c&&f.stdin.write(c),f.stdin.end()})}async function A(a,b={}){let c=a.sshUser||"root",d=Number(a.sshPort||22),e=`${c}@${a.host}`,f=function(a,b={}){let c="/opt/bluerouter-ollama-agent",d=(a.ollamaPorts||[11434]).join(","),e=String.raw`#!/usr/bin/env python3
|
|
1
|
+
(()=>{var a={};a.id=9902,a.ids=[9902],a.modules={261:a=>{"use strict";a.exports=require("next/dist/shared/lib/router/utils/app-paths")},3295:a=>{"use strict";a.exports=require("next/dist/server/app-render/after-task-async-storage.external.js")},6774:(a,b,c)=>{"use strict";c.d(b,{U1:()=>v,Vs:()=>w,YT:()=>z,iJ:()=>y,op:()=>A,ri:()=>x});var d=c(79748),e=c.n(d),f=c(33873),g=c.n(f),h=c(55511),i=c.n(h),j=c(21820),k=c.n(j),l=c(55013);let m=process.env.DATA_DIR||process.env.BLUEROUTER_DATA_DIR||g().join(k().homedir(),".bluerouter"),n=g().join(m,"ollama-nodes.json");async function o(){await e().mkdir(m,{recursive:!0});try{await e().access(n)}catch{await e().writeFile(n,JSON.stringify({nodes:[]},null,2))}}async function p(){await o();try{let a=await e().readFile(n,"utf8"),b=JSON.parse(a||"{}");return{nodes:Array.isArray(b.nodes)?b.nodes:[]}}catch{return{nodes:[]}}}async function q(a){await e().mkdir(m,{recursive:!0}),await e().writeFile(n,JSON.stringify({nodes:a.nodes||[]},null,2))}function r(){return new Date().toISOString()}function s(a,b={}){if(!a)return b;if("object"==typeof a&&!Array.isArray(a))return a;try{let c=JSON.parse(String(a));return c&&"object"==typeof c&&!Array.isArray(c)?c:b}catch{return b}}function t(a){return a?{...a,sshPassword:void 0,sudoPassword:void 0}:null}function u(a={},b={}){var c;let d=String(a.host??b.host??"").trim(),e=Number(a.agentPort??b.agentPort??19999)||19999,f=Array.isArray(c=a.ollamaPorts??b.ollamaPorts??[11434])?[...new Set(c.map(a=>Number(a)).filter(a=>Number.isFinite(a)&&a>0))]:[...new Set(String(c||"11434").split(/[\s,;]+/).map(a=>Number(a.trim())).filter(a=>Number.isFinite(a)&&a>0))],g=s(a.modelMemoryRequirements??b.modelMemoryRequirements,b.modelMemoryRequirements||{}),h=(0,l.nw)(g,{partial:!0,preserveEnabled:!0}),j=b.id||a.id||i().randomUUID(),k=String(a.agentToken??b.agentToken??i().randomBytes(24).toString("hex")).trim(),m=function(a,b,c={}){let d=new Map((Array.isArray(c.instances)?c.instances:[]).map(a=>[Number(a.port),a])),e=new Map((Array.isArray(a)?a:[]).map(a=>[Number(a.port),a]));return b.map(a=>(function(a={},b={}){var c;let d=Number(a.port??b.port??11434)||11434;return{id:String(a.id??b.id??`ollama-${d}`).trim()||`ollama-${d}`,name:String(a.name??b.name??`Ollama :${d}`).trim()||`Ollama :${d}`,port:d,enabled:a.enabled??b.enabled??!0,gpuMode:["auto","cpu","specific","all"].includes(a.gpuMode??b.gpuMode)?a.gpuMode??b.gpuMode:"auto",gpuIndices:Array.isArray(c=a.gpuIndices??b.gpuIndices??[])?[...new Set(c.map(a=>Number(a)).filter(a=>Number.isInteger(a)&&a>=0))]:[...new Set(String(c||"").split(/[\s,;]+/).map(a=>Number(a)).filter(a=>Number.isInteger(a)&&a>=0))],allowMultiGpuSplit:a.allowMultiGpuSplit??b.allowMultiGpuSplit??!1,tags:Array.isArray(a.tags??b.tags)?(a.tags??b.tags).map(a=>String(a).trim()).filter(Boolean):[],modelMemoryRequirements:(0,l.nw)(s(a.modelMemoryRequirements??b.modelMemoryRequirements,b.modelMemoryRequirements||{}),{partial:!0,preserveEnabled:!0})}})(e.get(Number(a))||{},d.get(Number(a))||{port:a}))}(a.instances??b.instances,f.length?f:[11434],b);return{id:j,name:String(a.name??b.name??d??"Ollama Node").trim()||d||"Ollama Node",host:d,sshPort:Number(a.sshPort??b.sshPort??22)||22,sshUser:String(a.sshUser??b.sshUser??"root").trim()||"root",useSudo:a.useSudo??b.useSudo??!1,agentPort:e,agentToken:k,ollamaPorts:f.length?f:[11434],instances:m,refreshIntervalMs:Math.max(5e3,Number(a.refreshIntervalMs??b.refreshIntervalMs??15e3)||15e3),enabled:a.enabled??b.enabled??!0,ufwAllowFrom:String(a.ufwAllowFrom??b.ufwAllowFrom??"").trim(),useGlobalModelMemory:a.useGlobalModelMemory??b.useGlobalModelMemory??!0,modelMemoryRequirements:h,notes:String(a.notes??b.notes??"").trim(),lastStatus:b.lastStatus||null,lastEvaluation:b.lastEvaluation||null,lastStatusAt:b.lastStatusAt||null,lastError:b.lastError||null,lastInstallAt:b.lastInstallAt||null,lastInstallOk:b.lastInstallOk||!1,lastInstallLog:b.lastInstallLog||"",createdAt:b.createdAt||r(),updatedAt:r()}}async function v(){return(await p()).nodes.map(a=>t(u(a,a)))}async function w(a){let b=(await p()).nodes.find(b=>b.id===a)||null;return b?u(b,b):null}async function x(a){let b=await p(),c=u(a);return b.nodes.push(c),await q(b),t(c)}async function y(a,b){let c=await p(),d=c.nodes.findIndex(b=>b.id===a);if(d<0)return null;let e=u(b,c.nodes[d]);return c.nodes[d]=e,await q(c),t(e)}async function z(a,b){let c=await p(),d=c.nodes.findIndex(b=>b.id===a);return d<0?null:(c.nodes[d]={...c.nodes[d],...b,updatedAt:r()},await q(c),t(u(c.nodes[d],c.nodes[d])))}async function A(a){let b=await p(),c=b.nodes.length;return b.nodes=b.nodes.filter(b=>b.id!==a),await q(b),b.nodes.length!==c}},10846:a=>{"use strict";a.exports=require("next/dist/compiled/next-server/app-page.runtime.prod.js")},21820:a=>{"use strict";a.exports=require("os")},29294:a=>{"use strict";a.exports=require("next/dist/server/app-render/work-async-storage.external.js")},33873:a=>{"use strict";a.exports=require("path")},44870:a=>{"use strict";a.exports=require("next/dist/compiled/next-server/app-route.runtime.prod.js")},55013:(a,b,c)=>{"use strict";c.d(b,{LM:()=>v,LQ:()=>u,MW:()=>s,dV:()=>l,eY:()=>t,iP:()=>m,nw:()=>o});var d=c(79748),e=c.n(d),f=c(33873),g=c.n(f),h=c(21820),i=c.n(h);let j=process.env.DATA_DIR||process.env.BLUEROUTER_DATA_DIR||g().join(i().homedir(),".bluerouter"),k=g().join(j,"ollama-model-memory.json"),l={"qwen3:1.7b":{minRamFreeMb:2048,minVramFreeMb:0,loadedRamFreeMb:512,loadedVramFreeMb:0,safetyMarginMb:512,notes:"small text model"},"qwen3:4b":{minRamFreeMb:4096,minVramFreeMb:0,loadedRamFreeMb:1024,loadedVramFreeMb:0,safetyMarginMb:512,notes:"small/medium text model"},"qwen3:8b":{minRamFreeMb:6144,minVramFreeMb:0,loadedRamFreeMb:1024,loadedVramFreeMb:512,safetyMarginMb:1024,notes:"8B text model"},"qwen3:14b":{minRamFreeMb:8192,minVramFreeMb:1e4,loadedRamFreeMb:2048,loadedVramFreeMb:1024,safetyMarginMb:1024,notes:"14B class model"},"qwen3:32b":{minRamFreeMb:4096,minVramFreeMb:16e3,loadedRamFreeMb:2048,loadedVramFreeMb:2048,safetyMarginMb:2048,notes:"32B class model, GPU preferred"},"qwen3-coder:30b":{minRamFreeMb:8192,minVramFreeMb:24e3,loadedRamFreeMb:2048,loadedVramFreeMb:3072,safetyMarginMb:2048,notes:"30B coder"},"qwen3-coder:latest":{minRamFreeMb:8192,minVramFreeMb:24e3,loadedRamFreeMb:2048,loadedVramFreeMb:3072,safetyMarginMb:2048,notes:"qwen3 coder latest, treat as 30B unless overridden"},"qwen2.5-coder:14b":{minRamFreeMb:8192,minVramFreeMb:1e4,loadedRamFreeMb:2048,loadedVramFreeMb:1024,safetyMarginMb:1024,notes:"14B coder"},"qwen2.5vl:7b":{minRamFreeMb:8192,minVramFreeMb:8e3,loadedRamFreeMb:2048,loadedVramFreeMb:1024,safetyMarginMb:1024,notes:"7B vision-language"},"qwen2.5vl:latest":{minRamFreeMb:8192,minVramFreeMb:1e4,loadedRamFreeMb:2048,loadedVramFreeMb:1024,safetyMarginMb:1024,notes:"vision-language latest"},"bge-m3:latest":{minRamFreeMb:4096,minVramFreeMb:0,loadedRamFreeMb:512,loadedVramFreeMb:0,safetyMarginMb:512,notes:"embedding model"},"deepseek-coder:6.7b":{minRamFreeMb:4096,minVramFreeMb:0,loadedRamFreeMb:1024,loadedVramFreeMb:512,safetyMarginMb:1024,notes:"6.7B coder"},"gemma4:e4b":{minRamFreeMb:4096,minVramFreeMb:0,loadedRamFreeMb:1024,loadedVramFreeMb:0,safetyMarginMb:1024,notes:"small Gemma variant"},"gemma:7b":{minRamFreeMb:4096,minVramFreeMb:0,loadedRamFreeMb:1024,loadedVramFreeMb:0,safetyMarginMb:1024,notes:"7B text model"},"glm-4.7-flash:latest":{minRamFreeMb:4096,minVramFreeMb:0,loadedRamFreeMb:1024,loadedVramFreeMb:512,safetyMarginMb:1024,notes:"flash/local text model"},"gpt-oss:20b":{minRamFreeMb:8192,minVramFreeMb:12e3,loadedRamFreeMb:2048,loadedVramFreeMb:2048,safetyMarginMb:2048,notes:"20B class model"},"llama3.1:8b":{minRamFreeMb:6144,minVramFreeMb:0,loadedRamFreeMb:1024,loadedVramFreeMb:512,safetyMarginMb:1024,notes:"8B text model"},"llama3.2-vision:latest":{minRamFreeMb:8192,minVramFreeMb:1e4,loadedRamFreeMb:2048,loadedVramFreeMb:1024,safetyMarginMb:1024,notes:"vision model"},"qwen3-*-translate-f16:*":{minRamFreeMb:8192,minVramFreeMb:16e3,loadedRamFreeMb:2048,loadedVramFreeMb:2048,safetyMarginMb:2048,notes:"custom Qwen3 f16 translate wildcard"},"qwen3-*-translate*:latest":{minRamFreeMb:6144,minVramFreeMb:8e3,loadedRamFreeMb:1024,loadedVramFreeMb:1024,safetyMarginMb:1024,notes:"custom Qwen3 translate wildcard"},"bge-m3:*":{minRamFreeMb:4096,minVramFreeMb:0,loadedRamFreeMb:512,loadedVramFreeMb:0,safetyMarginMb:512,notes:"embedding model wildcard"},"gemma*:e4b":{minRamFreeMb:4096,minVramFreeMb:0,loadedRamFreeMb:1024,loadedVramFreeMb:0,safetyMarginMb:1024,notes:"Gemma E4B wildcard"},"gemma*:7b":{minRamFreeMb:4096,minVramFreeMb:0,loadedRamFreeMb:1024,loadedVramFreeMb:0,safetyMarginMb:1024,notes:"Gemma 7B wildcard"},"*:1.7b":{minRamFreeMb:2048,minVramFreeMb:0,loadedRamFreeMb:512,loadedVramFreeMb:0,safetyMarginMb:512,notes:"generic 1.7B fallback"},"*:4b":{minRamFreeMb:4096,minVramFreeMb:0,loadedRamFreeMb:1024,loadedVramFreeMb:0,safetyMarginMb:512,notes:"generic 4B fallback"},"*:6.7b":{minRamFreeMb:4096,minVramFreeMb:0,loadedRamFreeMb:1024,loadedVramFreeMb:512,safetyMarginMb:1024,notes:"generic 6-7B fallback"},"*:7b":{minRamFreeMb:4096,minVramFreeMb:0,loadedRamFreeMb:1024,loadedVramFreeMb:512,safetyMarginMb:1024,notes:"generic 7B fallback"},"*:8b":{minRamFreeMb:6144,minVramFreeMb:0,loadedRamFreeMb:1024,loadedVramFreeMb:512,safetyMarginMb:1024,notes:"generic 8B fallback"},"*:14b":{minRamFreeMb:8192,minVramFreeMb:1e4,loadedRamFreeMb:2048,loadedVramFreeMb:1024,safetyMarginMb:1024,notes:"generic 14B fallback"},"*:20b":{minRamFreeMb:8192,minVramFreeMb:12e3,loadedRamFreeMb:2048,loadedVramFreeMb:2048,safetyMarginMb:2048,notes:"generic 20B fallback"},"*:30b":{minRamFreeMb:8192,minVramFreeMb:24e3,loadedRamFreeMb:2048,loadedVramFreeMb:3072,safetyMarginMb:2048,notes:"generic 30B fallback"},"*:32b":{minRamFreeMb:4096,minVramFreeMb:16e3,loadedRamFreeMb:2048,loadedVramFreeMb:2048,safetyMarginMb:2048,notes:"generic 32B fallback"}},m=["minRamFreeMb","minVramFreeMb","loadedRamFreeMb","loadedVramFreeMb","safetyMarginMb"];function n(a,b=0){let c=Number(a);return Number.isFinite(c)&&c>=0?c:b}function o(a={},b={}){let c={};if(!a||"object"!=typeof a||Array.isArray(a))return c;for(let[d,e]of Object.entries(a)){let a=String(d||"").trim();if(!a||!e||"object"!=typeof e||Array.isArray(e))continue;let f=function(a,{partial:b=!1,preserveEnabled:c=!1}={}){let d={};for(let c of m)if(b){var e;null!=(e=a?.[c])&&""!==String(e).trim()&&(d[c]=n(a[c],0))}else d[c]=n(a?.[c],0);let f=String(a?.notes||"").trim();return f&&(d.notes=f),c&&a&&Object.prototype.hasOwnProperty.call(a,"enabled")&&(d.enabled=!1!==a.enabled),d}(e,b),g=m.some(a=>Object.prototype.hasOwnProperty.call(f,a)),h=b.preserveEnabled&&Object.prototype.hasOwnProperty.call(f,"enabled");(g||f.notes||h)&&(c[a]=f)}return c}async function p(){await e().mkdir(j,{recursive:!0});try{await e().access(k)}catch{await e().writeFile(k,JSON.stringify({requirements:l,updatedAt:new Date().toISOString()},null,2))}}async function q(){await p();try{let a=await e().readFile(k,"utf8"),b=JSON.parse(a||"{}");return{requirements:function(a={}){return o({...l,...a})}(b.requirements||{}),updatedAt:b.updatedAt||null}}catch{return{requirements:o(l),updatedAt:null}}}async function r(a){await e().mkdir(j,{recursive:!0});let b={requirements:o(a),updatedAt:new Date().toISOString()};return await e().writeFile(k,JSON.stringify(b,null,2)),b}async function s(){return(await q()).requirements}async function t(){return q()}async function u(a){return r(a)}async function v(){return r(l)}},55511:a=>{"use strict";a.exports=require("crypto")},63033:a=>{"use strict";a.exports=require("next/dist/server/app-render/work-unit-async-storage.external.js")},68757:(a,b,c)=>{"use strict";c.r(b),c.d(b,{handler:()=>J,patchFetch:()=>I,routeModule:()=>E,serverHooks:()=>H,workAsyncStorage:()=>F,workUnitAsyncStorage:()=>G});var d={};c.r(d),c.d(d,{POST:()=>D,dynamic:()=>C,runtime:()=>B});var e=c(19225),f=c(84006),g=c(8317),h=c(99373),i=c(34775),j=c(24235),k=c(261),l=c(54365),m=c(90771),n=c(73461),o=c(67798),p=c(92280),q=c(62018),r=c(45696),s=c(47929),t=c(86439),u=c(37527),v=c(23211),w=c(6774),x=c(79646);function y(a){return`'${String(a??"").replace(/'/g,"'\"'\"'")}'`}function z(a,b,{input:c="",timeoutMs:d=12e4}={}){return new Promise(e=>{let f=(0,x.spawn)(a,b,{stdio:["pipe","pipe","pipe"]}),g="",h="",i=!1,j=setTimeout(()=>{i||f.kill("SIGKILL")},d);f.stdout.on("data",a=>{g+=a.toString()}),f.stderr.on("data",a=>{h+=a.toString()}),f.on("close",a=>{i=!0,clearTimeout(j),e({ok:0===a,code:a,stdout:g,stderr:h})}),c&&f.stdin.write(c),f.stdin.end()})}async function A(a,b={}){let c=a.sshUser||"root",d=Number(a.sshPort||22),e=`${c}@${a.host}`,f=function(a,b={}){let c="/opt/bluerouter-ollama-agent",d=(a.ollamaPorts||[11434]).join(","),e=String.raw`#!/usr/bin/env python3
|
|
2
2
|
import json
|
|
3
3
|
import os
|
|
4
4
|
import re
|
|
5
|
+
import socket
|
|
5
6
|
import subprocess
|
|
6
7
|
import urllib.request
|
|
7
|
-
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
8
8
|
from datetime import datetime, timezone
|
|
9
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
9
10
|
|
|
10
11
|
PORT = int(os.environ.get("AGENT_PORT", "19999"))
|
|
11
12
|
OLLAMA_HOST = os.environ.get("OLLAMA_HOST", "127.0.0.1")
|
|
@@ -13,13 +14,23 @@ OLLAMA_PORTS = [p.strip() for p in os.environ.get("OLLAMA_PORTS", "11434").split
|
|
|
13
14
|
AGENT_TOKEN = os.environ.get("AGENT_TOKEN", "")
|
|
14
15
|
|
|
15
16
|
|
|
16
|
-
def run_cmd(cmd, timeout=
|
|
17
|
+
def run_cmd(cmd, timeout=4):
|
|
17
18
|
try:
|
|
18
19
|
return subprocess.check_output(cmd, stderr=subprocess.STDOUT, timeout=timeout, text=True).strip()
|
|
19
20
|
except Exception:
|
|
20
21
|
return None
|
|
21
22
|
|
|
22
23
|
|
|
24
|
+
def run_json(cmd, timeout=6):
|
|
25
|
+
out = run_cmd(cmd, timeout=timeout)
|
|
26
|
+
if not out:
|
|
27
|
+
return None
|
|
28
|
+
try:
|
|
29
|
+
return json.loads(out)
|
|
30
|
+
except Exception:
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
|
|
23
34
|
def http_json(url, timeout=3):
|
|
24
35
|
try:
|
|
25
36
|
with urllib.request.urlopen(url, timeout=timeout) as r:
|
|
@@ -28,32 +39,83 @@ def http_json(url, timeout=3):
|
|
|
28
39
|
return None
|
|
29
40
|
|
|
30
41
|
|
|
31
|
-
def
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
def read_file(path):
|
|
43
|
+
try:
|
|
44
|
+
with open(path, "rb") as f:
|
|
45
|
+
return f.read()
|
|
46
|
+
except Exception:
|
|
47
|
+
return b""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def read_text(path):
|
|
51
|
+
try:
|
|
52
|
+
with open(path, "r", encoding="utf-8", errors="ignore") as f:
|
|
53
|
+
return f.read()
|
|
54
|
+
except Exception:
|
|
55
|
+
return ""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def read_cmdline(pid):
|
|
59
|
+
raw = read_file(f"/proc/{pid}/cmdline")
|
|
60
|
+
if not raw:
|
|
61
|
+
return ""
|
|
62
|
+
return raw.replace(b"\x00", b" ").decode("utf-8", errors="ignore").strip()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def read_environ(pid):
|
|
66
|
+
raw = read_file(f"/proc/{pid}/environ")
|
|
67
|
+
env = {}
|
|
68
|
+
if not raw:
|
|
69
|
+
return env
|
|
70
|
+
for item in raw.split(b"\x00"):
|
|
71
|
+
if not item or b"=" not in item:
|
|
43
72
|
continue
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
73
|
+
k, v = item.split(b"=", 1)
|
|
74
|
+
env[k.decode("utf-8", errors="ignore")] = v.decode("utf-8", errors="ignore")
|
|
75
|
+
return env
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def parse_systemd_environment(text):
|
|
79
|
+
# systemctl show -p Environment usually returns: Environment=A=1 B=two C=/x/y
|
|
80
|
+
env = {}
|
|
81
|
+
if not text:
|
|
82
|
+
return env
|
|
83
|
+
if text.startswith("Environment="):
|
|
84
|
+
text = text[len("Environment="):]
|
|
85
|
+
for part in re.findall(r'(?:[^\s"\']+|"[^"]*"|\'[^\']*\')+', text):
|
|
86
|
+
item = part.strip().strip('"').strip("'")
|
|
87
|
+
if "=" not in item:
|
|
48
88
|
continue
|
|
49
|
-
|
|
89
|
+
k, v = item.split("=", 1)
|
|
90
|
+
if k:
|
|
91
|
+
env[k] = v
|
|
92
|
+
return env
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def get_service_from_cgroup(pid):
|
|
96
|
+
text = read_text(f"/proc/{pid}/cgroup")
|
|
97
|
+
m = re.search(r'/system\.slice/([^/]+\.service)', text)
|
|
98
|
+
if m:
|
|
99
|
+
return m.group(1)
|
|
100
|
+
# user services may appear under user.slice/user-1000.slice/.../app.slice/name.service
|
|
101
|
+
m = re.search(r'/([^/]+\.service)(?:$|/)', text)
|
|
102
|
+
if m:
|
|
103
|
+
return m.group(1)
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def get_systemd_env(service):
|
|
108
|
+
if not service:
|
|
109
|
+
return {}
|
|
110
|
+
out = run_cmd(["systemctl", "show", service, "-p", "Environment"], timeout=3)
|
|
111
|
+
return parse_systemd_environment(out or "")
|
|
50
112
|
|
|
51
113
|
|
|
52
114
|
def get_gpu_status():
|
|
53
115
|
out = run_cmd([
|
|
54
116
|
"nvidia-smi",
|
|
55
117
|
"--query-gpu=index,uuid,name,memory.total,memory.used,memory.free,utilization.gpu,temperature.gpu",
|
|
56
|
-
"--format=csv,noheader,nounits"
|
|
118
|
+
"--format=csv,noheader,nounits"
|
|
57
119
|
])
|
|
58
120
|
if not out:
|
|
59
121
|
return []
|
|
@@ -74,105 +136,13 @@ def get_gpu_status():
|
|
|
74
136
|
"vramFreeMb": free,
|
|
75
137
|
"vramUsedPercent": round((used / total) * 100, 2) if total else None,
|
|
76
138
|
"gpuUsagePercent": int(float(util)),
|
|
77
|
-
"temperatureC": int(float(temp))
|
|
139
|
+
"temperatureC": int(float(temp))
|
|
78
140
|
})
|
|
79
141
|
except Exception:
|
|
80
142
|
pass
|
|
81
143
|
return gpus
|
|
82
144
|
|
|
83
145
|
|
|
84
|
-
def get_gpu_processes():
|
|
85
|
-
"""Return process-level GPU usage from nvidia-smi compute apps.
|
|
86
|
-
|
|
87
|
-
This is the key signal used to infer port -> PID -> GPU index. It is still
|
|
88
|
-
process-level, not per-model. If one Ollama process serves multiple models,
|
|
89
|
-
the mapping says which GPUs that port/process uses, not which model uses each GPU.
|
|
90
|
-
"""
|
|
91
|
-
uuid_map = get_gpu_uuid_map()
|
|
92
|
-
out = run_cmd([
|
|
93
|
-
"nvidia-smi",
|
|
94
|
-
"--query-compute-apps=gpu_uuid,pid,process_name,used_memory",
|
|
95
|
-
"--format=csv,noheader,nounits",
|
|
96
|
-
])
|
|
97
|
-
processes = []
|
|
98
|
-
if not out:
|
|
99
|
-
return processes
|
|
100
|
-
for line in out.splitlines():
|
|
101
|
-
parts = [x.strip() for x in line.split(",")]
|
|
102
|
-
if len(parts) < 4:
|
|
103
|
-
continue
|
|
104
|
-
uuid, pid, process_name, used_memory = parts[:4]
|
|
105
|
-
try:
|
|
106
|
-
pid_i = int(float(pid))
|
|
107
|
-
used_mb = int(float(str(used_memory).replace("MiB", "").strip()))
|
|
108
|
-
gpu = uuid_map.get(uuid, {})
|
|
109
|
-
processes.append({
|
|
110
|
-
"gpuUuid": uuid,
|
|
111
|
-
"gpuIndex": gpu.get("index"),
|
|
112
|
-
"gpuName": gpu.get("name"),
|
|
113
|
-
"pid": pid_i,
|
|
114
|
-
"processName": process_name,
|
|
115
|
-
"usedMemoryMb": used_mb,
|
|
116
|
-
})
|
|
117
|
-
except Exception:
|
|
118
|
-
continue
|
|
119
|
-
return processes
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def get_process_name(pid):
|
|
123
|
-
if not pid:
|
|
124
|
-
return None
|
|
125
|
-
out = run_cmd(["ps", "-p", str(pid), "-o", "comm="], timeout=2)
|
|
126
|
-
return out.strip() if out else None
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
def get_listening_pid(port):
|
|
130
|
-
"""Best-effort Linux TCP listener PID detection for a local Ollama port."""
|
|
131
|
-
port_s = str(port)
|
|
132
|
-
|
|
133
|
-
# ss is installed on most modern Linux distributions.
|
|
134
|
-
out = run_cmd(["ss", "-ltnp", f"sport = :{port_s}"], timeout=3)
|
|
135
|
-
if out:
|
|
136
|
-
# users:(("ollama",pid=1234,fd=3))
|
|
137
|
-
m = re.search(r"pid=(\d+)", out)
|
|
138
|
-
if m:
|
|
139
|
-
try:
|
|
140
|
-
return int(m.group(1))
|
|
141
|
-
except Exception:
|
|
142
|
-
pass
|
|
143
|
-
|
|
144
|
-
# Fallback to lsof when available.
|
|
145
|
-
out = run_cmd(["lsof", "-nP", f"-iTCP:{port_s}", "-sTCP:LISTEN", "-t"], timeout=3)
|
|
146
|
-
if out:
|
|
147
|
-
for line in out.splitlines():
|
|
148
|
-
line = line.strip()
|
|
149
|
-
if line.isdigit():
|
|
150
|
-
return int(line)
|
|
151
|
-
|
|
152
|
-
# Last-resort netstat parse.
|
|
153
|
-
out = run_cmd(["netstat", "-ltnp"], timeout=3)
|
|
154
|
-
if out:
|
|
155
|
-
for line in out.splitlines():
|
|
156
|
-
if f":{port_s} " not in line:
|
|
157
|
-
continue
|
|
158
|
-
m = re.search(r"\s(\d+)/", line)
|
|
159
|
-
if m:
|
|
160
|
-
return int(m.group(1))
|
|
161
|
-
return None
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def gpu_binding_for_pid(pid, gpu_processes):
|
|
165
|
-
if not pid:
|
|
166
|
-
return {"gpuIndices": [], "gpuVramUsedMb": 0, "gpuProcesses": []}
|
|
167
|
-
matched = [p for p in gpu_processes if int(p.get("pid") or -1) == int(pid)]
|
|
168
|
-
indices = sorted({p.get("gpuIndex") for p in matched if p.get("gpuIndex") is not None})
|
|
169
|
-
return {
|
|
170
|
-
"gpuIndices": indices,
|
|
171
|
-
"gpuVramUsedMb": sum(int(p.get("usedMemoryMb") or 0) for p in matched),
|
|
172
|
-
"gpuProcesses": matched,
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
176
146
|
def get_ram_status():
|
|
177
147
|
out = run_cmd(["free", "-m"])
|
|
178
148
|
if not out:
|
|
@@ -187,7 +157,7 @@ def get_ram_status():
|
|
|
187
157
|
"usedMb": used,
|
|
188
158
|
"freeMb": free,
|
|
189
159
|
"availableMb": avail,
|
|
190
|
-
"usedPercent": round((used / total) * 100, 2) if total else None
|
|
160
|
+
"usedPercent": round((used / total) * 100, 2) if total else None
|
|
191
161
|
}
|
|
192
162
|
return None
|
|
193
163
|
|
|
@@ -200,10 +170,39 @@ def get_cpu_status():
|
|
|
200
170
|
"load1": load[0],
|
|
201
171
|
"load5": load[1],
|
|
202
172
|
"load15": load[2],
|
|
203
|
-
"load1PercentOfCores": round((load[0] / cores) * 100, 2)
|
|
173
|
+
"load1PercentOfCores": round((load[0] / cores) * 100, 2)
|
|
204
174
|
}
|
|
205
175
|
|
|
206
176
|
|
|
177
|
+
def get_gpu_processes(gpus):
|
|
178
|
+
uuid_to_index = {g.get("uuid"): g.get("index") for g in gpus if g.get("uuid")}
|
|
179
|
+
out = run_cmd([
|
|
180
|
+
"nvidia-smi",
|
|
181
|
+
"--query-compute-apps=gpu_uuid,pid,process_name,used_memory",
|
|
182
|
+
"--format=csv,noheader,nounits"
|
|
183
|
+
])
|
|
184
|
+
processes = []
|
|
185
|
+
if out:
|
|
186
|
+
for line in out.splitlines():
|
|
187
|
+
parts = [x.strip() for x in line.split(",")]
|
|
188
|
+
if len(parts) < 4:
|
|
189
|
+
continue
|
|
190
|
+
gpu_uuid, pid, process_name, used_memory = parts[:4]
|
|
191
|
+
try:
|
|
192
|
+
pid_n = int(float(pid))
|
|
193
|
+
used_n = int(float(str(used_memory).replace("MiB", "").strip()))
|
|
194
|
+
except Exception:
|
|
195
|
+
continue
|
|
196
|
+
processes.append({
|
|
197
|
+
"gpuUuid": gpu_uuid,
|
|
198
|
+
"gpuIndex": uuid_to_index.get(gpu_uuid),
|
|
199
|
+
"pid": pid_n,
|
|
200
|
+
"processName": process_name,
|
|
201
|
+
"usedMemoryMb": used_n
|
|
202
|
+
})
|
|
203
|
+
return processes
|
|
204
|
+
|
|
205
|
+
|
|
207
206
|
def compact_model(item):
|
|
208
207
|
if not isinstance(item, dict):
|
|
209
208
|
return None
|
|
@@ -215,11 +214,401 @@ def compact_model(item):
|
|
|
215
214
|
"size_vram": item.get("size_vram"),
|
|
216
215
|
"expires_at": item.get("expires_at"),
|
|
217
216
|
"context_length": item.get("context_length"),
|
|
218
|
-
"details": item.get("details") or {}
|
|
217
|
+
"details": item.get("details") or {}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def find_listener_pid_by_ss(port):
|
|
222
|
+
candidates = []
|
|
223
|
+
commands = [
|
|
224
|
+
["ss", "-H", "-ltnp", "sport", "=", f":{port}"],
|
|
225
|
+
["ss", "-H", "-ltnp"],
|
|
226
|
+
]
|
|
227
|
+
for cmd in commands:
|
|
228
|
+
out = run_cmd(cmd, timeout=3)
|
|
229
|
+
if not out:
|
|
230
|
+
continue
|
|
231
|
+
for line in out.splitlines():
|
|
232
|
+
if f":{port}" not in line:
|
|
233
|
+
continue
|
|
234
|
+
for pid in re.findall(r'pid=([0-9]+)', line):
|
|
235
|
+
candidates.append({"pid": int(pid), "source": "ss", "line": line})
|
|
236
|
+
return candidates
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def find_listener_pid_by_lsof(port):
|
|
240
|
+
candidates = []
|
|
241
|
+
out = run_cmd(["lsof", "-nP", f"-iTCP:{port}", "-sTCP:LISTEN"], timeout=4)
|
|
242
|
+
if not out:
|
|
243
|
+
return candidates
|
|
244
|
+
for line in out.splitlines()[1:]:
|
|
245
|
+
parts = line.split()
|
|
246
|
+
if len(parts) >= 2 and parts[1].isdigit():
|
|
247
|
+
candidates.append({"pid": int(parts[1]), "source": "lsof", "line": line})
|
|
248
|
+
return candidates
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def find_listener_pid_by_fuser(port):
|
|
252
|
+
candidates = []
|
|
253
|
+
out = run_cmd(["fuser", "-n", "tcp", str(port)], timeout=4)
|
|
254
|
+
if not out:
|
|
255
|
+
return candidates
|
|
256
|
+
for pid in re.findall(r'\b[0-9]+\b', out):
|
|
257
|
+
candidates.append({"pid": int(pid), "source": "fuser", "line": out})
|
|
258
|
+
return candidates
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def tcp_port_hex(port):
|
|
262
|
+
return f"{int(port):04X}"
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def listener_inodes_for_port(port):
|
|
266
|
+
inodes = set()
|
|
267
|
+
want = tcp_port_hex(port)
|
|
268
|
+
for path in ["/proc/net/tcp", "/proc/net/tcp6"]:
|
|
269
|
+
text = read_text(path)
|
|
270
|
+
for line in text.splitlines()[1:]:
|
|
271
|
+
cols = line.split()
|
|
272
|
+
if len(cols) < 10:
|
|
273
|
+
continue
|
|
274
|
+
local = cols[1]
|
|
275
|
+
state = cols[3]
|
|
276
|
+
inode = cols[9]
|
|
277
|
+
if state != "0A":
|
|
278
|
+
continue
|
|
279
|
+
if local.split(":")[-1].upper() == want:
|
|
280
|
+
inodes.add(inode)
|
|
281
|
+
return inodes
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def find_listener_pid_by_proc(port):
|
|
285
|
+
candidates = []
|
|
286
|
+
inodes = listener_inodes_for_port(port)
|
|
287
|
+
if not inodes:
|
|
288
|
+
return candidates
|
|
289
|
+
proc_root = "/proc"
|
|
290
|
+
for pid in os.listdir(proc_root):
|
|
291
|
+
if not pid.isdigit():
|
|
292
|
+
continue
|
|
293
|
+
fd_dir = os.path.join(proc_root, pid, "fd")
|
|
294
|
+
try:
|
|
295
|
+
fds = os.listdir(fd_dir)
|
|
296
|
+
except Exception:
|
|
297
|
+
continue
|
|
298
|
+
for fd in fds:
|
|
299
|
+
try:
|
|
300
|
+
target = os.readlink(os.path.join(fd_dir, fd))
|
|
301
|
+
except Exception:
|
|
302
|
+
continue
|
|
303
|
+
m = re.match(r'socket:\[([0-9]+)\]', target)
|
|
304
|
+
if m and m.group(1) in inodes:
|
|
305
|
+
candidates.append({"pid": int(pid), "source": "proc-net", "line": f"inode={m.group(1)} fd={fd}"})
|
|
306
|
+
break
|
|
307
|
+
return candidates
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def find_listener_pid(port):
|
|
311
|
+
all_candidates = []
|
|
312
|
+
for fn in [find_listener_pid_by_ss, find_listener_pid_by_lsof, find_listener_pid_by_fuser, find_listener_pid_by_proc]:
|
|
313
|
+
try:
|
|
314
|
+
all_candidates.extend(fn(port))
|
|
315
|
+
except Exception:
|
|
316
|
+
pass
|
|
317
|
+
seen = set()
|
|
318
|
+
unique = []
|
|
319
|
+
for c in all_candidates:
|
|
320
|
+
pid = c.get("pid")
|
|
321
|
+
if not pid or pid in seen:
|
|
322
|
+
continue
|
|
323
|
+
seen.add(pid)
|
|
324
|
+
unique.append(c)
|
|
325
|
+
if not unique:
|
|
326
|
+
return None, None, []
|
|
327
|
+
chosen = unique[0]
|
|
328
|
+
return chosen.get("pid"), chosen.get("source"), unique
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def process_tree_pids(root_pid):
|
|
332
|
+
try:
|
|
333
|
+
root_pid = int(root_pid)
|
|
334
|
+
except Exception:
|
|
335
|
+
return []
|
|
336
|
+
out = run_cmd(["ps", "-eo", "pid=,ppid=,cmd="], timeout=4)
|
|
337
|
+
children = {}
|
|
338
|
+
if out:
|
|
339
|
+
for line in out.splitlines():
|
|
340
|
+
parts = line.strip().split(None, 2)
|
|
341
|
+
if len(parts) < 2:
|
|
342
|
+
continue
|
|
343
|
+
try:
|
|
344
|
+
pid = int(parts[0]); ppid = int(parts[1])
|
|
345
|
+
except Exception:
|
|
346
|
+
continue
|
|
347
|
+
children.setdefault(ppid, []).append(pid)
|
|
348
|
+
result = []
|
|
349
|
+
stack = [root_pid]
|
|
350
|
+
seen = set()
|
|
351
|
+
while stack:
|
|
352
|
+
pid = stack.pop()
|
|
353
|
+
if pid in seen:
|
|
354
|
+
continue
|
|
355
|
+
seen.add(pid)
|
|
356
|
+
result.append(pid)
|
|
357
|
+
stack.extend(children.get(pid, []))
|
|
358
|
+
return result
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def docker_ps_rows():
|
|
362
|
+
out = run_cmd(["docker", "ps", "--format", "{{.ID}}\t{{.Names}}\t{{.Ports}}"], timeout=5)
|
|
363
|
+
rows = []
|
|
364
|
+
if not out:
|
|
365
|
+
return rows
|
|
366
|
+
for line in out.splitlines():
|
|
367
|
+
parts = line.split("\t")
|
|
368
|
+
if len(parts) >= 3:
|
|
369
|
+
rows.append({"id": parts[0], "name": parts[1], "ports": parts[2]})
|
|
370
|
+
return rows
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def docker_inspect(cid):
|
|
374
|
+
data = run_json(["docker", "inspect", cid], timeout=6)
|
|
375
|
+
if isinstance(data, list) and data:
|
|
376
|
+
return data[0]
|
|
377
|
+
return None
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def find_docker_container_for_port(port, docker_proxy_info=None):
|
|
381
|
+
# Prefer Docker's authoritative NetworkSettings.Ports mapping.
|
|
382
|
+
for row in docker_ps_rows():
|
|
383
|
+
info = docker_inspect(row["id"])
|
|
384
|
+
if not info:
|
|
385
|
+
continue
|
|
386
|
+
ports = (((info.get("NetworkSettings") or {}).get("Ports")) or {})
|
|
387
|
+
for container_port, host_bindings in ports.items():
|
|
388
|
+
if not str(container_port).startswith(str(port)) and not str(container_port).startswith(str((docker_proxy_info or {}).get("containerPort", ""))):
|
|
389
|
+
pass
|
|
390
|
+
for binding in host_bindings or []:
|
|
391
|
+
if str(binding.get("HostPort")) == str(port):
|
|
392
|
+
return row, info
|
|
393
|
+
# Fallback: match container IP parsed from docker-proxy command.
|
|
394
|
+
container_ip = (docker_proxy_info or {}).get("containerIp")
|
|
395
|
+
if container_ip:
|
|
396
|
+
networks = ((info.get("NetworkSettings") or {}).get("Networks")) or {}
|
|
397
|
+
for net in networks.values():
|
|
398
|
+
if net.get("IPAddress") == container_ip:
|
|
399
|
+
return row, info
|
|
400
|
+
# Fallback: match docker ps formatted Ports text.
|
|
401
|
+
if f":{port}->" in row.get("ports", "") or f"0.0.0.0:{port}" in row.get("ports", "") or f"[::]:{port}" in row.get("ports", ""):
|
|
402
|
+
return row, info
|
|
403
|
+
return None, None
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def parse_docker_proxy_cmd(cmdline):
|
|
407
|
+
if "docker-proxy" not in (cmdline or ""):
|
|
408
|
+
return None
|
|
409
|
+
tokens = cmdline.split()
|
|
410
|
+
out = {"kind": "docker-proxy"}
|
|
411
|
+
for i, token in enumerate(tokens):
|
|
412
|
+
if token in ["-host-port", "--host-port"] and i + 1 < len(tokens):
|
|
413
|
+
out["hostPort"] = tokens[i + 1]
|
|
414
|
+
elif token in ["-container-port", "--container-port"] and i + 1 < len(tokens):
|
|
415
|
+
out["containerPort"] = tokens[i + 1]
|
|
416
|
+
elif token in ["-container-ip", "--container-ip"] and i + 1 < len(tokens):
|
|
417
|
+
out["containerIp"] = tokens[i + 1]
|
|
418
|
+
return out
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def docker_env_and_meta(port, pid, cmdline):
|
|
422
|
+
proxy = parse_docker_proxy_cmd(cmdline)
|
|
423
|
+
if not proxy:
|
|
424
|
+
return None
|
|
425
|
+
row, info = find_docker_container_for_port(port, proxy)
|
|
426
|
+
if not info:
|
|
427
|
+
return {
|
|
428
|
+
"runtimeType": "docker-proxy",
|
|
429
|
+
"dockerProxy": proxy,
|
|
430
|
+
"containerFound": False,
|
|
431
|
+
"env": {},
|
|
432
|
+
"source": "docker-proxy-no-container"
|
|
433
|
+
}
|
|
434
|
+
env_list = ((info.get("Config") or {}).get("Env")) or []
|
|
435
|
+
env = {}
|
|
436
|
+
for item in env_list:
|
|
437
|
+
if "=" in item:
|
|
438
|
+
k, v = item.split("=", 1)
|
|
439
|
+
env[k] = v
|
|
440
|
+
host_config = info.get("HostConfig") or {}
|
|
441
|
+
state = info.get("State") or {}
|
|
442
|
+
labels = (info.get("Config") or {}).get("Labels") or {}
|
|
443
|
+
networks = ((info.get("NetworkSettings") or {}).get("Networks")) or {}
|
|
444
|
+
ips = []
|
|
445
|
+
for net_name, net in networks.items():
|
|
446
|
+
if net.get("IPAddress"):
|
|
447
|
+
ips.append({"network": net_name, "ip": net.get("IPAddress")})
|
|
448
|
+
return {
|
|
449
|
+
"runtimeType": "docker",
|
|
450
|
+
"dockerProxy": proxy,
|
|
451
|
+
"containerFound": True,
|
|
452
|
+
"containerId": (info.get("Id") or row.get("id"))[:12],
|
|
453
|
+
"containerName": (info.get("Name") or row.get("name") or "").lstrip("/"),
|
|
454
|
+
"containerImage": (info.get("Config") or {}).get("Image"),
|
|
455
|
+
"containerPid": state.get("Pid"),
|
|
456
|
+
"containerIps": ips,
|
|
457
|
+
"dockerPorts": row.get("ports"),
|
|
458
|
+
"dockerNetworkMode": host_config.get("NetworkMode"),
|
|
459
|
+
"dockerDeviceRequests": host_config.get("DeviceRequests") or [],
|
|
460
|
+
"dockerLabels": labels,
|
|
461
|
+
"env": env,
|
|
462
|
+
"source": "docker-inspect"
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def parse_visible_devices(value, gpus, source):
|
|
467
|
+
if value is None:
|
|
468
|
+
return None
|
|
469
|
+
raw = str(value).strip()
|
|
470
|
+
lower = raw.lower()
|
|
471
|
+
if raw == "":
|
|
472
|
+
return None
|
|
473
|
+
if lower in ["none", "void", "no", "false", "-1"]:
|
|
474
|
+
return {"indices": [], "mode": "disabled", "source": source, "raw": raw}
|
|
475
|
+
if lower == "all":
|
|
476
|
+
return {"indices": [g["index"] for g in gpus], "mode": "all-visible", "source": source, "raw": raw}
|
|
477
|
+
uuid_to_index = {str(g.get("uuid")): g.get("index") for g in gpus if g.get("uuid")}
|
|
478
|
+
indices = []
|
|
479
|
+
unknown = []
|
|
480
|
+
for item in re.split(r'[,;\s]+', raw):
|
|
481
|
+
if not item:
|
|
482
|
+
continue
|
|
483
|
+
if item.isdigit():
|
|
484
|
+
indices.append(int(item))
|
|
485
|
+
elif item in uuid_to_index:
|
|
486
|
+
indices.append(uuid_to_index[item])
|
|
487
|
+
elif item.startswith("GPU-") and item in uuid_to_index:
|
|
488
|
+
indices.append(uuid_to_index[item])
|
|
489
|
+
elif item.startswith("MIG-"):
|
|
490
|
+
unknown.append(item)
|
|
491
|
+
else:
|
|
492
|
+
unknown.append(item)
|
|
493
|
+
return {
|
|
494
|
+
"indices": sorted(list(set(indices))),
|
|
495
|
+
"mode": "specific" if indices else "unknown",
|
|
496
|
+
"source": source,
|
|
497
|
+
"raw": raw,
|
|
498
|
+
"unknownDevices": unknown
|
|
219
499
|
}
|
|
220
500
|
|
|
221
501
|
|
|
222
|
-
def
|
|
502
|
+
def parse_device_requests(device_requests, gpus):
|
|
503
|
+
if not device_requests:
|
|
504
|
+
return None
|
|
505
|
+
ids = []
|
|
506
|
+
all_gpu = False
|
|
507
|
+
for req in device_requests:
|
|
508
|
+
count = req.get("Count")
|
|
509
|
+
caps = req.get("Capabilities") or []
|
|
510
|
+
driver = req.get("Driver")
|
|
511
|
+
req_ids = req.get("DeviceIDs") or []
|
|
512
|
+
if driver == "nvidia" or any("gpu" in str(x).lower() for cap in caps for x in cap):
|
|
513
|
+
if count == -1 and not req_ids:
|
|
514
|
+
all_gpu = True
|
|
515
|
+
ids.extend(req_ids)
|
|
516
|
+
if all_gpu:
|
|
517
|
+
return {"indices": [g["index"] for g in gpus], "mode": "all-visible", "source": "docker-device-requests:all", "raw": "all"}
|
|
518
|
+
if ids:
|
|
519
|
+
return parse_visible_devices(",".join(ids), gpus, "docker-device-requests")
|
|
520
|
+
return None
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def configured_gpu_from_env(runtime_type, env, gpus, docker_meta=None):
|
|
524
|
+
# Prefer NVIDIA_VISIBLE_DEVICES in Docker, CUDA_VISIBLE_DEVICES for direct/systemd.
|
|
525
|
+
candidates = []
|
|
526
|
+
if runtime_type == "docker":
|
|
527
|
+
candidates = [
|
|
528
|
+
("NVIDIA_VISIBLE_DEVICES", env.get("NVIDIA_VISIBLE_DEVICES")),
|
|
529
|
+
("CUDA_VISIBLE_DEVICES", env.get("CUDA_VISIBLE_DEVICES")),
|
|
530
|
+
]
|
|
531
|
+
req = parse_device_requests((docker_meta or {}).get("dockerDeviceRequests"), gpus)
|
|
532
|
+
if req:
|
|
533
|
+
return req
|
|
534
|
+
else:
|
|
535
|
+
candidates = [
|
|
536
|
+
("CUDA_VISIBLE_DEVICES", env.get("CUDA_VISIBLE_DEVICES")),
|
|
537
|
+
("NVIDIA_VISIBLE_DEVICES", env.get("NVIDIA_VISIBLE_DEVICES")),
|
|
538
|
+
("HIP_VISIBLE_DEVICES", env.get("HIP_VISIBLE_DEVICES")),
|
|
539
|
+
("ROCR_VISIBLE_DEVICES", env.get("ROCR_VISIBLE_DEVICES")),
|
|
540
|
+
]
|
|
541
|
+
for key, value in candidates:
|
|
542
|
+
parsed = parse_visible_devices(value, gpus, f"{runtime_type}-env:{key}")
|
|
543
|
+
if parsed:
|
|
544
|
+
return parsed
|
|
545
|
+
if runtime_type in ["systemd", "direct"]:
|
|
546
|
+
return {"indices": [g["index"] for g in gpus], "mode": "all-visible", "source": "all-visible-no-env", "raw": None}
|
|
547
|
+
if runtime_type == "docker":
|
|
548
|
+
# For Docker, lack of NVIDIA_VISIBLE_DEVICES is less conclusive.
|
|
549
|
+
return {"indices": [], "mode": "unknown", "source": "docker-no-visible-devices-env", "raw": None}
|
|
550
|
+
return {"indices": [], "mode": "unknown", "source": "no-gpu-config", "raw": None}
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def process_metadata(port, pid, gpus):
|
|
554
|
+
if not pid:
|
|
555
|
+
return {"runtimeType": "unknown", "env": {}, "configured": configured_gpu_from_env("unknown", {}, gpus)}
|
|
556
|
+
cmdline = read_cmdline(pid)
|
|
557
|
+
service = get_service_from_cgroup(pid)
|
|
558
|
+
runtime_type = "systemd" if service else "direct"
|
|
559
|
+
docker_meta = docker_env_and_meta(port, pid, cmdline)
|
|
560
|
+
if docker_meta:
|
|
561
|
+
runtime_type = docker_meta.get("runtimeType") or "docker"
|
|
562
|
+
env = docker_meta.get("env") or {}
|
|
563
|
+
configured = configured_gpu_from_env("docker", env, gpus, docker_meta)
|
|
564
|
+
return {
|
|
565
|
+
"runtimeType": runtime_type,
|
|
566
|
+
"processCmdline": cmdline,
|
|
567
|
+
"serviceName": service,
|
|
568
|
+
"env": env,
|
|
569
|
+
"configured": configured,
|
|
570
|
+
**{k: v for k, v in docker_meta.items() if k not in ["env"]}
|
|
571
|
+
}
|
|
572
|
+
env = {}
|
|
573
|
+
env.update(get_systemd_env(service))
|
|
574
|
+
env.update(read_environ(pid))
|
|
575
|
+
configured = configured_gpu_from_env("systemd" if service else "direct", env, gpus)
|
|
576
|
+
return {
|
|
577
|
+
"runtimeType": runtime_type,
|
|
578
|
+
"processCmdline": cmdline,
|
|
579
|
+
"serviceName": service,
|
|
580
|
+
"env": {k: v for k, v in env.items() if re.search(r'OLLAMA|CUDA|NVIDIA|HIP|ROCR|GPU', k, re.I)},
|
|
581
|
+
"configured": configured,
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
def gpu_processes_for_pids(gpu_processes, pids):
|
|
586
|
+
pid_set = set(int(x) for x in pids if x)
|
|
587
|
+
return [p for p in gpu_processes if int(p.get("pid") or -1) in pid_set]
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def infer_gpu_from_ps(ps_models, gpus, configured):
|
|
591
|
+
size_bytes = sum(int(m.get("size_vram") or 0) for m in ps_models or [])
|
|
592
|
+
size_mb = int(size_bytes / (1024 * 1024)) if size_bytes else 0
|
|
593
|
+
if size_mb <= 0:
|
|
594
|
+
return [], 0, None
|
|
595
|
+
configured_indices = configured.get("indices") or [] if configured else []
|
|
596
|
+
if configured_indices:
|
|
597
|
+
return configured_indices, size_mb, "ollama-ps-configured-gpu-inferred"
|
|
598
|
+
if len(gpus) == 1:
|
|
599
|
+
return [gpus[0]["index"]], size_mb, "ollama-ps-single-gpu-inferred"
|
|
600
|
+
# Conservative multi-GPU inference: only infer if one GPU's used memory is close enough.
|
|
601
|
+
candidates = []
|
|
602
|
+
for gpu in gpus:
|
|
603
|
+
used = int(gpu.get("vramUsedMb") or 0)
|
|
604
|
+
if used >= max(512, int(size_mb * 0.45)):
|
|
605
|
+
candidates.append(gpu)
|
|
606
|
+
if len(candidates) == 1:
|
|
607
|
+
return [candidates[0]["index"]], size_mb, "ollama-ps-single-used-gpu-inferred"
|
|
608
|
+
return [], 0, None
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
def get_ollama_instance(port, gpus, gpu_processes):
|
|
223
612
|
base = f"http://{OLLAMA_HOST}:{port}"
|
|
224
613
|
tags = http_json(f"{base}/api/tags")
|
|
225
614
|
ps = http_json(f"{base}/api/ps")
|
|
@@ -242,36 +631,88 @@ def get_ollama_instance(port, gpu_processes):
|
|
|
242
631
|
ps_models.append(cm)
|
|
243
632
|
loaded_models.append(cm["name"])
|
|
244
633
|
|
|
245
|
-
pid =
|
|
246
|
-
|
|
634
|
+
pid, pid_source, pid_candidates = find_listener_pid(port)
|
|
635
|
+
meta = process_metadata(port, pid, gpus)
|
|
636
|
+
pid_tree = process_tree_pids(pid) if pid else []
|
|
637
|
+
# Docker containers may expose a container init PID whose children hold GPU handles.
|
|
638
|
+
container_pid = meta.get("containerPid")
|
|
639
|
+
if container_pid:
|
|
640
|
+
pid_tree = sorted(list(set(pid_tree + process_tree_pids(container_pid))))
|
|
641
|
+
matched_gpu_processes = gpu_processes_for_pids(gpu_processes, pid_tree)
|
|
642
|
+
runtime_indices = sorted(list(set([p.get("gpuIndex") for p in matched_gpu_processes if p.get("gpuIndex") is not None])))
|
|
643
|
+
runtime_used = sum(int(p.get("usedMemoryMb") or 0) for p in matched_gpu_processes)
|
|
644
|
+
runtime_source = "port-pid-nvidia-smi" if runtime_indices else None
|
|
645
|
+
ps_indices, ps_used, ps_source = infer_gpu_from_ps(ps_models, gpus, meta.get("configured") or {})
|
|
646
|
+
|
|
647
|
+
configured = meta.get("configured") or {}
|
|
648
|
+
effective_indices = []
|
|
649
|
+
effective_source = None
|
|
650
|
+
if configured.get("indices"):
|
|
651
|
+
effective_indices = configured.get("indices")
|
|
652
|
+
effective_source = configured.get("source")
|
|
653
|
+
elif runtime_indices:
|
|
654
|
+
effective_indices = runtime_indices
|
|
655
|
+
effective_source = runtime_source
|
|
656
|
+
elif ps_indices:
|
|
657
|
+
effective_indices = ps_indices
|
|
658
|
+
effective_source = ps_source
|
|
659
|
+
|
|
660
|
+
detected_indices = runtime_indices or ps_indices
|
|
661
|
+
detected_used = runtime_used if runtime_indices else ps_used
|
|
662
|
+
detected_source = runtime_source or ps_source or ("port-pid-no-gpu" if pid else None)
|
|
247
663
|
|
|
248
664
|
return {
|
|
249
665
|
"id": f"ollama-{port}",
|
|
250
666
|
"port": int(port),
|
|
251
667
|
"baseUrl": base,
|
|
252
668
|
"alive": tags is not None,
|
|
253
|
-
"pid": pid,
|
|
254
|
-
"processName": get_process_name(pid),
|
|
255
669
|
"models": models,
|
|
256
670
|
"modelDetails": model_details,
|
|
257
671
|
"loadedModels": loaded_models,
|
|
258
672
|
"psModels": ps_models,
|
|
259
673
|
"loadedDigests": sorted(list(set([m.get("digest") for m in ps_models if m.get("digest")]))),
|
|
260
|
-
"
|
|
261
|
-
"
|
|
262
|
-
"
|
|
263
|
-
"
|
|
674
|
+
"pid": pid,
|
|
675
|
+
"pidDetectionSource": pid_source,
|
|
676
|
+
"pidCandidates": pid_candidates,
|
|
677
|
+
"pidTree": pid_tree,
|
|
678
|
+
"runtimeType": meta.get("runtimeType"),
|
|
679
|
+
"serviceName": meta.get("serviceName"),
|
|
680
|
+
"processCmdline": meta.get("processCmdline"),
|
|
681
|
+
"processEnv": meta.get("env"),
|
|
682
|
+
"containerId": meta.get("containerId"),
|
|
683
|
+
"containerName": meta.get("containerName"),
|
|
684
|
+
"containerImage": meta.get("containerImage"),
|
|
685
|
+
"containerPid": meta.get("containerPid"),
|
|
686
|
+
"containerIps": meta.get("containerIps"),
|
|
687
|
+
"dockerNetworkMode": meta.get("dockerNetworkMode"),
|
|
688
|
+
"dockerPorts": meta.get("dockerPorts"),
|
|
689
|
+
"dockerDeviceRequests": meta.get("dockerDeviceRequests"),
|
|
690
|
+
"dockerLabels": meta.get("dockerLabels"),
|
|
691
|
+
"dockerProxy": meta.get("dockerProxy"),
|
|
692
|
+
"configuredGpuIndices": configured.get("indices") or [],
|
|
693
|
+
"configuredGpuMode": configured.get("mode"),
|
|
694
|
+
"configuredGpuSource": configured.get("source"),
|
|
695
|
+
"configuredGpuRaw": configured.get("raw"),
|
|
696
|
+
"configuredGpuUnknownDevices": configured.get("unknownDevices") or [],
|
|
697
|
+
"runtimeGpuIndices": runtime_indices,
|
|
698
|
+
"runtimeGpuVramUsedMb": runtime_used,
|
|
699
|
+
"runtimeGpuProcesses": matched_gpu_processes,
|
|
700
|
+
"detectedGpuIndices": detected_indices,
|
|
701
|
+
"detectedGpuVramUsedMb": detected_used,
|
|
702
|
+
"detectedGpuProcesses": matched_gpu_processes,
|
|
703
|
+
"detectedGpuBindingSource": detected_source,
|
|
704
|
+
"effectiveGpuIndices": effective_indices,
|
|
705
|
+
"effectiveGpuSource": effective_source,
|
|
264
706
|
}
|
|
265
707
|
|
|
266
708
|
|
|
267
709
|
def build_status():
|
|
268
710
|
hostname = run_cmd(["hostname"]) or "unknown"
|
|
269
711
|
gpus = get_gpu_status()
|
|
270
|
-
gpu_processes = get_gpu_processes()
|
|
712
|
+
gpu_processes = get_gpu_processes(gpus)
|
|
271
713
|
ram = get_ram_status()
|
|
272
714
|
cpu = get_cpu_status()
|
|
273
|
-
instances = [get_ollama_instance(p, gpu_processes) for p in OLLAMA_PORTS]
|
|
274
|
-
|
|
715
|
+
instances = [get_ollama_instance(p, gpus, gpu_processes) for p in OLLAMA_PORTS]
|
|
275
716
|
tags = ["ollama", "lan"]
|
|
276
717
|
if gpus:
|
|
277
718
|
tags += ["gpu", "nvidia"]
|
|
@@ -279,11 +720,9 @@ def build_status():
|
|
|
279
720
|
tags.append(f"gpu:{gpu.get('index')}:{str(gpu.get('name','')).lower().replace(' ','-')}")
|
|
280
721
|
else:
|
|
281
722
|
tags.append("cpu")
|
|
282
|
-
|
|
283
723
|
for inst in instances:
|
|
284
724
|
for model in inst.get("models", []):
|
|
285
725
|
tags.append(f"model_support:{model}")
|
|
286
|
-
|
|
287
726
|
best_gpu = max(gpus, key=lambda x: x.get("vramFreeMb", 0)) if gpus else None
|
|
288
727
|
return {
|
|
289
728
|
"ok": True,
|
|
@@ -294,10 +733,10 @@ def build_status():
|
|
|
294
733
|
"score": {
|
|
295
734
|
"gpuFreeVramMb": best_gpu.get("vramFreeMb") if best_gpu else 0,
|
|
296
735
|
"gpuUsagePercent": best_gpu.get("gpuUsagePercent") if best_gpu else None,
|
|
297
|
-
"ramAvailableMb": ram.get("availableMb") if ram else None
|
|
736
|
+
"ramAvailableMb": ram.get("availableMb") if ram else None
|
|
298
737
|
},
|
|
299
738
|
"tags": sorted(set(tags)),
|
|
300
|
-
"updatedAt": datetime.now(timezone.utc).isoformat()
|
|
739
|
+
"updatedAt": datetime.now(timezone.utc).isoformat()
|
|
301
740
|
}
|
|
302
741
|
|
|
303
742
|
|
|
@@ -346,7 +785,7 @@ chmod +x "$AGENT_DIR/agent.py"
|
|
|
346
785
|
cat > /etc/systemd/system/bluerouter-ollama-agent.service <<'SERVICE'
|
|
347
786
|
[Unit]
|
|
348
787
|
Description=BlueRouter Ollama Hardware Status Agent
|
|
349
|
-
After=network.target ollama.service
|
|
788
|
+
After=network.target docker.service ollama.service
|
|
350
789
|
|
|
351
790
|
[Service]
|
|
352
791
|
Type=simple
|