create-walle 0.9.21 → 0.9.23
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/README.md +27 -5
- package/package.json +2 -2
- package/template/CLAUDE.md +2 -2
- package/template/LICENSE +1 -1
- package/template/bin/ctm-dev-cleanup.js +24 -3
- package/template/bin/ctm-launch.sh +13 -0
- package/template/bin/dev.sh +156 -18
- package/template/bin/node-bin.sh +84 -0
- package/template/bin/pin-node.sh +51 -0
- package/template/claude-task-manager/api-prompts.js +1203 -182
- package/template/claude-task-manager/api-reviews.js +109 -15
- package/template/claude-task-manager/approval-agent.js +1360 -280
- package/template/claude-task-manager/bin/restart-ctm.sh +64 -23
- package/template/claude-task-manager/bin/storage-migration-supervisor.js +338 -0
- package/template/claude-task-manager/db.js +4417 -295
- package/template/claude-task-manager/docs/app-update-refresh-protocol.md +69 -0
- package/template/claude-task-manager/docs/approval-ai-refinement.md +138 -0
- package/template/claude-task-manager/docs/approval-rescue-loop.md +74 -0
- package/template/claude-task-manager/docs/codex-operational-warning-health.md +107 -0
- package/template/claude-task-manager/docs/codex-resume-state-guard-design.md +17 -12
- package/template/claude-task-manager/docs/codex-terminal-render-controller-handoff.md +311 -0
- package/template/claude-task-manager/docs/coding-agent-hooks-architecture.md +418 -0
- package/template/claude-task-manager/docs/conversation-import-freshness.md +20 -0
- package/template/claude-task-manager/docs/google-workspace-auth-health.md +77 -0
- package/template/claude-task-manager/docs/image-paste-ux.md +13 -0
- package/template/claude-task-manager/docs/ipad-web-preview.md +88 -0
- package/template/claude-task-manager/docs/main-loop-offload-architecture.md +66 -0
- package/template/claude-task-manager/docs/microsoft-dev-tunnel-phone-access-design.md +274 -519
- package/template/claude-task-manager/docs/mobile-live-streaming.md +27 -5
- package/template/claude-task-manager/docs/mobile-remote-submission-lifecycle.md +69 -0
- package/template/claude-task-manager/docs/phone-access-design.md +53 -15
- package/template/claude-task-manager/docs/phone-passkey-identity.md +122 -0
- package/template/claude-task-manager/docs/phone-setup.md +3 -0
- package/template/claude-task-manager/docs/prompt-editing-tree-design.md +25 -1
- package/template/claude-task-manager/docs/remote-desktop-access-design.md +268 -0
- package/template/claude-task-manager/docs/restart-lifecycle-architecture.md +95 -0
- package/template/claude-task-manager/docs/runtime-work-control-plane.md +53 -0
- package/template/claude-task-manager/docs/session-interactive-wait-surfaces.md +38 -0
- package/template/claude-task-manager/docs/session-needs-you-dismissal.md +84 -0
- package/template/claude-task-manager/docs/session-render-state-management-design.md +91 -3
- package/template/claude-task-manager/docs/session-standup-command-center-design.md +25 -1
- package/template/claude-task-manager/docs/session-title-authority.md +32 -0
- package/template/claude-task-manager/docs/session-workspace-binding.md +33 -0
- package/template/claude-task-manager/docs/skill-intent-resolution-design.md +72 -0
- package/template/claude-task-manager/docs/walle-mcp-supervisor-health.md +86 -0
- package/template/claude-task-manager/docs/walle-relay-phone-access-design.md +24 -15
- package/template/claude-task-manager/docs/walle-session-history-hydration.md +114 -0
- package/template/claude-task-manager/docs/walle-session-input-queue.md +104 -0
- package/template/claude-task-manager/docs/walle-session-model-catalog.md +90 -0
- package/template/claude-task-manager/docs/walle-session-model-preferences.md +15 -6
- package/template/claude-task-manager/git-utils.js +897 -27
- package/template/claude-task-manager/lib/agent-capabilities.js +33 -0
- package/template/claude-task-manager/lib/agent-cli-cache.js +37 -7
- package/template/claude-task-manager/lib/agent-hooks-installer.js +26 -2
- package/template/claude-task-manager/lib/agent-presets.js +17 -1
- package/template/claude-task-manager/lib/all-sessions-query.js +108 -0
- package/template/claude-task-manager/lib/approval-ai-refinement.js +488 -0
- package/template/claude-task-manager/lib/approval-self-adapt.js +168 -0
- package/template/claude-task-manager/lib/async-semaphore.js +44 -0
- package/template/claude-task-manager/lib/auth-context.js +5 -0
- package/template/claude-task-manager/lib/auth-rate-limit.js +47 -4
- package/template/claude-task-manager/lib/auth-rules.js +29 -2
- package/template/claude-task-manager/lib/auto-approval-verifier.js +129 -16
- package/template/claude-task-manager/lib/background-llm.js +144 -17
- package/template/claude-task-manager/lib/branch-inventory.js +212 -0
- package/template/claude-task-manager/lib/claude-desktop-sessions.js +15 -3
- package/template/claude-task-manager/lib/coalesce-sync-frames.js +151 -0
- package/template/claude-task-manager/lib/codex-launch-health.js +762 -0
- package/template/claude-task-manager/lib/codex-transcript-pager.js +51 -0
- package/template/claude-task-manager/lib/codex-zst.js +124 -0
- package/template/claude-task-manager/lib/coding-agent-models.js +233 -30
- package/template/claude-task-manager/lib/connection-health.js +232 -0
- package/template/claude-task-manager/lib/conversation-blob-parser.js +42 -0
- package/template/claude-task-manager/lib/conversation-tail-merge.js +89 -26
- package/template/claude-task-manager/lib/ctm-session-context-api.js +39 -10
- package/template/claude-task-manager/lib/cursor-conversation-store.js +354 -0
- package/template/claude-task-manager/lib/db-owner-worker-client.js +315 -0
- package/template/claude-task-manager/lib/document-review.js +141 -6
- package/template/claude-task-manager/lib/escalation-review.js +152 -0
- package/template/claude-task-manager/lib/graceful-shutdown.js +159 -0
- package/template/claude-task-manager/lib/headless-term-service.js +678 -0
- package/template/claude-task-manager/lib/heavy-worker-fallback.js +38 -0
- package/template/claude-task-manager/lib/jsonl-conversation-parser.js +542 -0
- package/template/claude-task-manager/lib/jsonl-range-reader.js +112 -0
- package/template/claude-task-manager/lib/main-db-census.js +216 -0
- package/template/claude-task-manager/lib/message-pagination.js +106 -4
- package/template/claude-task-manager/lib/microsoft-dev-tunnel-setup.js +750 -26
- package/template/claude-task-manager/lib/mobile-auth-api.js +274 -7
- package/template/claude-task-manager/lib/mobile-auth-store.js +592 -10
- package/template/claude-task-manager/lib/mobile-notification-dispatcher.js +15 -0
- package/template/claude-task-manager/lib/model-overview-brain-fallback.js +311 -0
- package/template/claude-task-manager/lib/model-overview-cache.js +141 -0
- package/template/claude-task-manager/lib/models-health-routing-notice.js +126 -0
- package/template/claude-task-manager/lib/node-pin-guard.js +93 -0
- package/template/claude-task-manager/lib/perf-tracker.js +242 -6
- package/template/claude-task-manager/lib/permission-match.js +76 -0
- package/template/claude-task-manager/lib/permission-sync.js +133 -20
- package/template/claude-task-manager/lib/process-title.js +35 -0
- package/template/claude-task-manager/lib/prompt-executions-query.js +25 -0
- package/template/claude-task-manager/lib/prompt-index-disk-cache.js +44 -0
- package/template/claude-task-manager/lib/prompt-intent.js +132 -0
- package/template/claude-task-manager/lib/provider-user-context.js +34 -0
- package/template/claude-task-manager/lib/read-pool-client.js +313 -0
- package/template/claude-task-manager/lib/readpool-breaker.js +31 -0
- package/template/claude-task-manager/lib/recent-sessions-breaker.js +12 -0
- package/template/claude-task-manager/lib/remote-feedback-client.js +72 -0
- package/template/claude-task-manager/lib/remote-relay-protocol.js +37 -4
- package/template/claude-task-manager/lib/remote-relay-store.js +159 -0
- package/template/claude-task-manager/lib/remote-submission-observer.js +278 -0
- package/template/claude-task-manager/lib/restart-guard.js +109 -0
- package/template/claude-task-manager/lib/restore-interruption-detector.js +439 -0
- package/template/claude-task-manager/lib/restore-policy.js +13 -0
- package/template/claude-task-manager/lib/restore-resume-batch.js +74 -0
- package/template/claude-task-manager/lib/restore-runtime.js +68 -0
- package/template/claude-task-manager/lib/restore-storm.js +34 -0
- package/template/claude-task-manager/lib/resume-cwd.js +36 -0
- package/template/claude-task-manager/lib/resume-preflight.js +313 -0
- package/template/claude-task-manager/lib/runtime-work-registry.js +444 -0
- package/template/claude-task-manager/lib/sanitize-openai-auth.js +31 -0
- package/template/claude-task-manager/lib/scheduler.js +21 -1
- package/template/claude-task-manager/lib/scrollback-snapshot-store.js +159 -0
- package/template/claude-task-manager/lib/serial-task-queue.js +64 -0
- package/template/claude-task-manager/lib/server-listeners.js +239 -0
- package/template/claude-task-manager/lib/session-capture.js +42 -7
- package/template/claude-task-manager/lib/session-content-backfill.js +131 -0
- package/template/claude-task-manager/lib/session-history.js +388 -43
- package/template/claude-task-manager/lib/session-host-manager.js +287 -0
- package/template/claude-task-manager/lib/session-image-refs.js +209 -0
- package/template/claude-task-manager/lib/session-jobs.js +399 -59
- package/template/claude-task-manager/lib/session-prompt-index.js +137 -0
- package/template/claude-task-manager/lib/session-restore.js +53 -0
- package/template/claude-task-manager/lib/session-standup.js +123 -23
- package/template/claude-task-manager/lib/session-state-bus.js +14 -0
- package/template/claude-task-manager/lib/session-stream.js +64 -16
- package/template/claude-task-manager/lib/session-timeline-summary.js +260 -0
- package/template/claude-task-manager/lib/session-token-usage.js +494 -0
- package/template/claude-task-manager/lib/session-workspace-binding.js +356 -0
- package/template/claude-task-manager/lib/setup-network-config.js +9 -0
- package/template/claude-task-manager/lib/size-cap.js +45 -0
- package/template/claude-task-manager/lib/size-cap.test.js +62 -0
- package/template/claude-task-manager/lib/skill-autocomplete.js +180 -1
- package/template/claude-task-manager/lib/skill-intent-resolver.js +304 -0
- package/template/claude-task-manager/lib/sqlite-driver.js +19 -3
- package/template/claude-task-manager/lib/standup-attention.js +7 -3
- package/template/claude-task-manager/lib/status-authority.js +39 -0
- package/template/claude-task-manager/lib/status-hooks.js +4 -0
- package/template/claude-task-manager/lib/storage-migration.js +235 -0
- package/template/claude-task-manager/lib/structured-capture.js +298 -0
- package/template/claude-task-manager/lib/sync-io-census.js +163 -0
- package/template/claude-task-manager/lib/tailscale-setup.js +6 -0
- package/template/claude-task-manager/lib/terminal-activity-evidence.js +33 -0
- package/template/claude-task-manager/lib/terminal-choice.js +364 -0
- package/template/claude-task-manager/lib/terminal-control-sanitize.js +17 -0
- package/template/claude-task-manager/lib/terminal-fingerprint.js +48 -0
- package/template/claude-task-manager/lib/terminal-output-flush.js +84 -0
- package/template/claude-task-manager/lib/timeline-order.js +122 -0
- package/template/claude-task-manager/lib/transcript-store.js +348 -43
- package/template/claude-task-manager/lib/transport-security.js +84 -1
- package/template/claude-task-manager/lib/wait-state.js +184 -0
- package/template/claude-task-manager/lib/walle-client.js +47 -5
- package/template/claude-task-manager/lib/walle-ctm-history.js +564 -4
- package/template/claude-task-manager/lib/walle-external-actions.js +135 -16
- package/template/claude-task-manager/lib/walle-history-hydration.js +46 -0
- package/template/claude-task-manager/lib/walle-native-health.js +403 -0
- package/template/claude-task-manager/lib/walle-repair.js +701 -0
- package/template/claude-task-manager/lib/walle-session-cache.js +109 -0
- package/template/claude-task-manager/lib/walle-session-context.js +57 -21
- package/template/claude-task-manager/lib/walle-session-model-catalog.js +34 -0
- package/template/claude-task-manager/lib/walle-supervisor.js +539 -63
- package/template/claude-task-manager/lib/walle-transcript.js +52 -0
- package/template/claude-task-manager/lib/worktree-active-sync.js +11 -7
- package/template/claude-task-manager/lib/worktree-cwd.js +32 -1
- package/template/claude-task-manager/package.json +1 -1
- package/template/claude-task-manager/prompt-harvest.js +89 -66
- package/template/claude-task-manager/providers/claude-code.js +51 -3
- package/template/claude-task-manager/providers/cursor.js +140 -45
- package/template/claude-task-manager/public/css/reviews.css +551 -61
- package/template/claude-task-manager/public/css/setup.css +191 -0
- package/template/claude-task-manager/public/css/walle-session.css +865 -10
- package/template/claude-task-manager/public/css/walle.css +154 -0
- package/template/claude-task-manager/public/designs/ai-providers-consolidation-v2.html +830 -0
- package/template/claude-task-manager/public/index.html +18516 -2058
- package/template/claude-task-manager/public/ipad.html +363 -0
- package/template/claude-task-manager/public/js/document-review-links.js +301 -0
- package/template/claude-task-manager/public/js/image-normalize.js +69 -36
- package/template/claude-task-manager/public/js/message-renderer.js +1265 -77
- package/template/claude-task-manager/public/js/prompts.js +66 -29
- package/template/claude-task-manager/public/js/reviews.js +901 -133
- package/template/claude-task-manager/public/js/session-activity-utils.js +11 -1
- package/template/claude-task-manager/public/js/session-search-utils.js +94 -10
- package/template/claude-task-manager/public/js/session-status-precedence.js +23 -5
- package/template/claude-task-manager/public/js/setup.js +1273 -176
- package/template/claude-task-manager/public/js/stream-view.js +691 -73
- package/template/claude-task-manager/public/js/terminal-reconciler.js +210 -0
- package/template/claude-task-manager/public/js/walle-session.js +2455 -158
- package/template/claude-task-manager/public/js/walle.js +455 -28
- package/template/claude-task-manager/public/m/app.css +2909 -262
- package/template/claude-task-manager/public/m/app.js +6601 -398
- package/template/claude-task-manager/public/m/claim.html +224 -17
- package/template/claude-task-manager/public/m/index.html +117 -21
- package/template/claude-task-manager/public/m/sw.js +3 -1
- package/template/claude-task-manager/public/manifest.json +2 -2
- package/template/claude-task-manager/public/prompts.html +30 -14
- package/template/claude-task-manager/queue-engine.js +507 -28
- package/template/claude-task-manager/scripts/repair-claude-session-images.js +27 -8
- package/template/claude-task-manager/server.js +14341 -2197
- package/template/claude-task-manager/session-integrity.js +160 -18
- package/template/claude-task-manager/session-search-ranking.js +1 -0
- package/template/claude-task-manager/session-utils.js +25 -5
- package/template/claude-task-manager/workers/approval-blocklist.js +96 -6
- package/template/claude-task-manager/workers/approval-widget-validator.js +14 -8
- package/template/claude-task-manager/workers/conversation-import-worker.js +11 -50
- package/template/claude-task-manager/workers/db-owner-worker.js +386 -0
- package/template/claude-task-manager/workers/harvest-worker.js +9 -55
- package/template/claude-task-manager/workers/headless-term-worker.js +9 -530
- package/template/claude-task-manager/workers/read-pool-worker.js +387 -0
- package/template/claude-task-manager/workers/scrollback-worker.js +11 -72
- package/template/claude-task-manager/workers/session-host-process.js +146 -0
- package/template/claude-task-manager/workers/session-integrity-worker.js +10 -54
- package/template/claude-task-manager/workers/state-detectors/base.js +18 -1
- package/template/claude-task-manager/workers/state-detectors/claude-code.js +182 -9
- package/template/claude-task-manager/workers/state-detectors/codex.js +150 -2
- package/template/claude-task-manager/workers/state-detectors/cursor.js +127 -0
- package/template/claude-task-manager/workers/state-detectors/gemini.js +21 -0
- package/template/claude-task-manager/workers/state-detectors/index.js +29 -0
- package/template/claude-task-manager/workers/state-detectors/opencode.js +103 -0
- package/template/docs/design/markdown-review-pane.md +206 -0
- package/template/docs/designs/2026-05-17-portkey-gateway-provider-ux.md +129 -38
- package/template/docs/designs/2026-05-20-mobile-worktree-finish-command.md +27 -0
- package/template/docs/designs/2026-05-22-ai-configuration-consolidation.md +248 -0
- package/template/docs/designs/ai-configuration-consolidation-mock.html +812 -0
- package/template/docs/private-memory-and-pii-policy.md +69 -0
- package/template/package.json +2 -1
- package/template/scripts/check-private-data.js +201 -0
- package/template/shared/sqlite-owner-guard.js +30 -0
- package/template/shared/sqlite-owner-write-queue.js +225 -0
- package/template/shared/sqlite-storage-policy.js +111 -0
- package/template/shared/sqlite-write-lock.js +428 -0
- package/template/wall-e/agent-runners/claude-code.js +5 -0
- package/template/wall-e/agent.js +166 -22
- package/template/wall-e/api-walle.js +524 -70
- package/template/wall-e/auth/provider-flows.js +11 -1
- package/template/wall-e/bin/walle-mcp-stdio.js +341 -17
- package/template/wall-e/brain.js +1614 -141
- package/template/wall-e/chat/attachment-blocks.js +96 -0
- package/template/wall-e/chat/attachments.js +2 -1
- package/template/wall-e/chat/capability-resolver.js +7 -7
- package/template/wall-e/chat/context-messages.js +28 -0
- package/template/wall-e/chat/conversation-frame.js +630 -0
- package/template/wall-e/chat/provider-messages.js +125 -0
- package/template/wall-e/chat.js +1002 -233
- package/template/wall-e/coding/acceptance-contract.js +170 -0
- package/template/wall-e/coding/acp-adapter.js +1 -1
- package/template/wall-e/coding/agent-catalog.js +3 -0
- package/template/wall-e/coding/artifact-store.js +93 -0
- package/template/wall-e/coding/capability-router.js +120 -0
- package/template/wall-e/coding/coding-run-controller.js +423 -0
- package/template/wall-e/coding/compaction-service.js +157 -12
- package/template/wall-e/coding/frontend-verification.js +258 -0
- package/template/wall-e/coding/lifecycle-hooks.js +75 -0
- package/template/wall-e/coding/local-preview-contract.js +157 -0
- package/template/wall-e/coding/permission-service.js +57 -13
- package/template/wall-e/coding/prompt-bundle.js +19 -1
- package/template/wall-e/coding/prompt-section-registry.js +227 -0
- package/template/wall-e/coding/provider-compat.js +15 -0
- package/template/wall-e/coding/runtime-events.js +224 -0
- package/template/wall-e/coding/runtime-mode.js +3 -0
- package/template/wall-e/coding/side-git-snapshot.js +160 -4
- package/template/wall-e/coding/snapshot-service.js +143 -1
- package/template/wall-e/coding/stream-processor.js +388 -34
- package/template/wall-e/coding/task-tool.js +141 -4
- package/template/wall-e/coding/tool-execution-controller.js +365 -0
- package/template/wall-e/coding/tool-registry.js +43 -5
- package/template/wall-e/coding/user-hooks.js +217 -0
- package/template/wall-e/coding-orchestrator.js +1330 -221
- package/template/wall-e/coding-prompts.js +20 -4
- package/template/wall-e/context/context-builder.js +15 -2
- package/template/wall-e/decision/confidence.js +1 -1
- package/template/wall-e/docs/coding-acceptance-contract.md +41 -0
- package/template/wall-e/docs/external-action-controller.md +26 -6
- package/template/wall-e/docs/telemetry-lifecycle.md +8 -2
- package/template/wall-e/embeddings.js +591 -53
- package/template/wall-e/external-action-controller.js +12 -0
- package/template/wall-e/http/auth.js +1 -0
- package/template/wall-e/http/chat-api.js +46 -11
- package/template/wall-e/http/model-admin.js +836 -34
- package/template/wall-e/lib/boot-profile.js +88 -0
- package/template/wall-e/lib/event-loop-monitor.js +93 -0
- package/template/wall-e/lib/service-health.js +194 -0
- package/template/wall-e/llm/anthropic.js +130 -5
- package/template/wall-e/llm/client.js +266 -63
- package/template/wall-e/llm/default-fallback.js +382 -0
- package/template/wall-e/llm/health.js +19 -0
- package/template/wall-e/llm/message-guard.js +78 -0
- package/template/wall-e/llm/model-catalog.js +252 -1
- package/template/wall-e/llm/openai.js +26 -4
- package/template/wall-e/llm/portkey-sync.js +654 -0
- package/template/wall-e/llm/provider-error.js +30 -2
- package/template/wall-e/llm/registry.js +5 -1
- package/template/wall-e/llm/request-compat.js +67 -0
- package/template/wall-e/loops/backfill.js +79 -23
- package/template/wall-e/loops/brain-optimize.js +67 -0
- package/template/wall-e/loops/ingest.js +25 -10
- package/template/wall-e/loops/question-digest.js +160 -0
- package/template/wall-e/loops/reflect.js +6 -4
- package/template/wall-e/loops/think.js +39 -12
- package/template/wall-e/mcp-server.js +318 -36
- package/template/wall-e/memory/ctm-context-client.js +52 -14
- package/template/wall-e/memory/ctm-operational-context.js +237 -0
- package/template/wall-e/memory/ctm-prompt-executions-client.js +128 -0
- package/template/wall-e/memory/ctm-session-context.js +111 -63
- package/template/wall-e/prompts/coding/deepseek.txt +3 -0
- package/template/wall-e/prompts/coding/gemini.txt +6 -0
- package/template/wall-e/prompts/coding/gpt.txt +6 -0
- package/template/wall-e/prompts/coding/local.txt +7 -0
- package/template/wall-e/runtime/decision-hooks.js +115 -0
- package/template/wall-e/runtime/devbox-gateway.js +82 -8
- package/template/wall-e/runtime/prompt-manifest.js +86 -0
- package/template/wall-e/runtime/tool-executor.js +269 -0
- package/template/wall-e/runtime/tool-result-envelope.js +138 -0
- package/template/wall-e/runtime/transcript-projection.js +60 -0
- package/template/wall-e/runtime/walle-runtime.js +224 -0
- package/template/wall-e/scripts/db-optimize/migrate.js +162 -0
- package/template/wall-e/scripts/db-optimize/recall-eval.js +117 -0
- package/template/wall-e/server.js +15 -0
- package/template/wall-e/session-files.js +9 -0
- package/template/wall-e/skills/_bundled/google-calendar/run.js +1 -1
- package/template/wall-e/skills/_bundled/gws-workspace/run.js +1 -1
- package/template/wall-e/skills/_bundled/slack-mentions/run.js +76 -6
- package/template/wall-e/skills/claude-code-reader.js +7 -3
- package/template/wall-e/skills/script-skill-runner.js +10 -0
- package/template/wall-e/skills/skill-planner.js +38 -0
- package/template/wall-e/tools/builtin-middleware.js +19 -9
- package/template/wall-e/tools/local-tools.js +1428 -16
- package/template/wall-e/tools/permission-checker.js +73 -5
- package/template/wall-e/tools/question-manager.js +117 -7
- package/template/wall-e/training/harvester.js +12 -28
- package/template/wall-e/training/replay.js +25 -80
- package/template/website/index.html +10 -10
- package/template/wall-e/eval/ab-test.js +0 -203
- package/template/wall-e/eval/agent-runner.js +0 -772
- package/template/wall-e/eval/agent-scorer.js +0 -461
- package/template/wall-e/eval/aggregator.js +0 -414
- package/template/wall-e/eval/allowed-test-commands.js +0 -34
- package/template/wall-e/eval/benchmark-generator.js +0 -113
- package/template/wall-e/eval/benchmarks/chat-eval.json +0 -1662
- package/template/wall-e/eval/benchmarks/chat.json +0 -82
- package/template/wall-e/eval/benchmarks/coding-agent-real.json +0 -1
- package/template/wall-e/eval/benchmarks/coding-agent.json +0 -1581
- package/template/wall-e/eval/benchmarks/coding.json +0 -122
- package/template/wall-e/eval/benchmarks/memory-retrieval.json +0 -234
- package/template/wall-e/eval/benchmarks/reasoning.json +0 -82
- package/template/wall-e/eval/benchmarks/swebench-lite-30.json +0 -212
- package/template/wall-e/eval/benchmarks.js +0 -669
- package/template/wall-e/eval/cc-replay.js +0 -719
- package/template/wall-e/eval/chat-eval.js +0 -525
- package/template/wall-e/eval/check-keys.js +0 -15
- package/template/wall-e/eval/check-providers.js +0 -42
- package/template/wall-e/eval/codex-cli-baseline.js +0 -669
- package/template/wall-e/eval/coding-agent-real.js +0 -570
- package/template/wall-e/eval/context-compactor.js +0 -251
- package/template/wall-e/eval/debug-agent003.js +0 -68
- package/template/wall-e/eval/diagnostics.js +0 -216
- package/template/wall-e/eval/eval-orchestrator.js +0 -642
- package/template/wall-e/eval/evaluate.js +0 -202
- package/template/wall-e/eval/evaluator.js +0 -373
- package/template/wall-e/eval/exporter.js +0 -212
- package/template/wall-e/eval/fixtures/express-basic/package.json +0 -9
- package/template/wall-e/eval/fixtures/express-basic/server.js +0 -115
- package/template/wall-e/eval/fixtures/express-basic/test.js +0 -83
- package/template/wall-e/eval/fixtures/express-buggy/package.json +0 -9
- package/template/wall-e/eval/fixtures/express-buggy/server.js +0 -113
- package/template/wall-e/eval/fixtures/express-buggy/test.js +0 -83
- package/template/wall-e/eval/fixtures/express-buggy-items/package.json +0 -9
- package/template/wall-e/eval/fixtures/express-buggy-items/server.js +0 -112
- package/template/wall-e/eval/fixtures/express-buggy-items/test.js +0 -83
- package/template/wall-e/eval/fixtures/express-buggy-search/package.json +0 -9
- package/template/wall-e/eval/fixtures/express-buggy-search/server.js +0 -121
- package/template/wall-e/eval/fixtures/express-buggy-search/test.js +0 -83
- package/template/wall-e/eval/fixtures/express-rename-data/data.js +0 -34
- package/template/wall-e/eval/fixtures/express-rename-data/package.json +0 -9
- package/template/wall-e/eval/fixtures/express-rename-data/server.js +0 -97
- package/template/wall-e/eval/fixtures/express-rename-data/test.js +0 -88
- package/template/wall-e/eval/fixtures/express-xss/package.json +0 -12
- package/template/wall-e/eval/fixtures/express-xss/server.js +0 -90
- package/template/wall-e/eval/fixtures/express-xss/test.js +0 -67
- package/template/wall-e/eval/fixtures/express-xss/views/profile.ejs +0 -9
- package/template/wall-e/eval/fixtures/fullstack-app/config/default.js +0 -9
- package/template/wall-e/eval/fixtures/fullstack-app/config/test.js +0 -13
- package/template/wall-e/eval/fixtures/fullstack-app/package.json +0 -11
- package/template/wall-e/eval/fixtures/fullstack-app/public/css/style.css +0 -137
- package/template/wall-e/eval/fixtures/fullstack-app/public/index.html +0 -46
- package/template/wall-e/eval/fixtures/fullstack-app/public/js/app.js +0 -121
- package/template/wall-e/eval/fixtures/fullstack-app/public/js/auth.js +0 -71
- package/template/wall-e/eval/fixtures/fullstack-app/public/js/items.js +0 -80
- package/template/wall-e/eval/fixtures/fullstack-app/public/js/users.js +0 -46
- package/template/wall-e/eval/fixtures/fullstack-app/public/login.html +0 -45
- package/template/wall-e/eval/fixtures/fullstack-app/public/register.html +0 -38
- package/template/wall-e/eval/fixtures/fullstack-app/scripts/migrate.js +0 -23
- package/template/wall-e/eval/fixtures/fullstack-app/scripts/seed.js +0 -46
- package/template/wall-e/eval/fixtures/fullstack-app/server/db.js +0 -99
- package/template/wall-e/eval/fixtures/fullstack-app/server/index.js +0 -94
- package/template/wall-e/eval/fixtures/fullstack-app/server/middleware/auth.js +0 -19
- package/template/wall-e/eval/fixtures/fullstack-app/server/middleware/logger.js +0 -19
- package/template/wall-e/eval/fixtures/fullstack-app/server/router.js +0 -50
- package/template/wall-e/eval/fixtures/fullstack-app/server/routes/auth.js +0 -69
- package/template/wall-e/eval/fixtures/fullstack-app/server/routes/health.js +0 -23
- package/template/wall-e/eval/fixtures/fullstack-app/server/routes/items.js +0 -88
- package/template/wall-e/eval/fixtures/fullstack-app/server/routes/users.js +0 -75
- package/template/wall-e/eval/fixtures/fullstack-app/server/test.js +0 -198
- package/template/wall-e/eval/fixtures/fullstack-app/server/utils/response.js +0 -34
- package/template/wall-e/eval/fixtures/fullstack-app/server/utils/validate.js +0 -26
- package/template/wall-e/eval/fixtures/fullstack-app/server.js +0 -8
- package/template/wall-e/eval/fixtures/fullstack-app/test.js +0 -12
- package/template/wall-e/eval/fixtures/monorepo-basic/package.json +0 -8
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/data.js +0 -58
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/middleware.js +0 -46
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/package.json +0 -8
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/routes.js +0 -64
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/server.js +0 -56
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/test.js +0 -116
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/commands.js +0 -61
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/index.js +0 -62
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/output.js +0 -43
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/package.json +0 -11
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/test.js +0 -44
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/formatters.js +0 -43
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/index.js +0 -12
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/package.json +0 -5
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/test.js +0 -55
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/validators.js +0 -29
- package/template/wall-e/eval/fixtures/monorepo-basic/test.js +0 -46
- package/template/wall-e/eval/fixtures/node-cli/index.js +0 -78
- package/template/wall-e/eval/fixtures/node-cli/package.json +0 -10
- package/template/wall-e/eval/fixtures/node-cli/test.js +0 -57
- package/template/wall-e/eval/fixtures/node-typed/package.json +0 -8
- package/template/wall-e/eval/fixtures/node-typed/src/handlers.js +0 -31
- package/template/wall-e/eval/fixtures/node-typed/src/utils.js +0 -33
- package/template/wall-e/eval/fixtures/node-typed/test.js +0 -36
- package/template/wall-e/eval/fixtures/python-flask/app.py +0 -14
- package/template/wall-e/eval/fixtures/python-flask/requirements.txt +0 -2
- package/template/wall-e/eval/fixtures/python-flask/test_app.py +0 -25
- package/template/wall-e/eval/fixtures/wall-e-subset/brain.js +0 -105
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/aggregator.js +0 -101
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/benchmarks/chat.json +0 -20
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/benchmarks/coding.json +0 -32
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/benchmarks.js +0 -64
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/fixtures/simple-project/package.json +0 -6
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/fixtures/simple-project/server.js +0 -31
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/fixtures/simple-project/test.js +0 -18
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/fixtures/simple-project/utils.js +0 -34
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/runner.js +0 -104
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/scorer.js +0 -73
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/test.js +0 -134
- package/template/wall-e/eval/fixtures/wall-e-subset/llm/client.js +0 -99
- package/template/wall-e/eval/fixtures/wall-e-subset/llm/providers.js +0 -63
- package/template/wall-e/eval/fixtures/wall-e-subset/llm/test.js +0 -70
- package/template/wall-e/eval/fixtures/wall-e-subset/package.json +0 -10
- package/template/wall-e/eval/fixtures/wall-e-subset/test.js +0 -86
- package/template/wall-e/eval/harvester.js +0 -685
- package/template/wall-e/eval/head-to-head.js +0 -388
- package/template/wall-e/eval/humaneval-adapter.js +0 -321
- package/template/wall-e/eval/list-models.js +0 -31
- package/template/wall-e/eval/livecodebench-adapter.js +0 -291
- package/template/wall-e/eval/mail-integration.js +0 -443
- package/template/wall-e/eval/manifest.js +0 -186
- package/template/wall-e/eval/meta-harness/adapters/coding-agent.js +0 -57
- package/template/wall-e/eval/meta-harness/bootstrap-snapshot.js +0 -149
- package/template/wall-e/eval/meta-harness/candidate-store.js +0 -117
- package/template/wall-e/eval/meta-harness/cli.js +0 -86
- package/template/wall-e/eval/meta-harness/domain-spec.js +0 -154
- package/template/wall-e/eval/meta-harness/domains/coding-agent.domain.json +0 -84
- package/template/wall-e/eval/meta-harness/examples/env-bootstrap-candidate.js +0 -29
- package/template/wall-e/eval/meta-harness/experience-store.js +0 -174
- package/template/wall-e/eval/meta-harness/frontier.js +0 -96
- package/template/wall-e/eval/meta-harness/harness-interface.js +0 -90
- package/template/wall-e/eval/meta-harness/leakage-guard.js +0 -80
- package/template/wall-e/eval/meta-harness/optimizer.js +0 -207
- package/template/wall-e/eval/meta-harness/proposer-runner.js +0 -110
- package/template/wall-e/eval/meta-harness/reporting.js +0 -58
- package/template/wall-e/eval/meta-harness/telemetry.js +0 -27
- package/template/wall-e/eval/meta-harness/validation.js +0 -81
- package/template/wall-e/eval/promoter.js +0 -228
- package/template/wall-e/eval/provider-normalizer.js +0 -33
- package/template/wall-e/eval/replay.js +0 -395
- package/template/wall-e/eval/run-agent-benchmarks.js +0 -386
- package/template/wall-e/eval/run-codex-cli-baseline.js +0 -177
- package/template/wall-e/eval/run-coding-agent-real.js +0 -187
- package/template/wall-e/eval/run-eval.js +0 -435
- package/template/wall-e/eval/run-model-comparison.js +0 -142
- package/template/wall-e/eval/session-evaluator.js +0 -187
- package/template/wall-e/eval/session-miner.js +0 -207
- package/template/wall-e/eval/session-retrieval-benchmark.js +0 -150
- package/template/wall-e/eval/session-transcripts.js +0 -509
- package/template/wall-e/eval/shadow.js +0 -161
- package/template/wall-e/eval/swebench-adapter.js +0 -345
- package/template/wall-e/eval/swebench-docker.js +0 -192
- package/template/wall-e/eval/train.py +0 -320
- package/template/wall-e/eval/trainer.js +0 -232
- package/template/wall-e/eval/weekly-eval-loop.js +0 -241
|
@@ -3,12 +3,30 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const http = require('http');
|
|
6
|
-
const { execFile
|
|
6
|
+
const { execFile } = require('child_process');
|
|
7
7
|
const { jsonResponse: _jsonResponseDataFirst } = require('./api-utils');
|
|
8
|
+
const {
|
|
9
|
+
isPortkeyGatewayCredentialRow,
|
|
10
|
+
isPortkeyProviderRow,
|
|
11
|
+
syncPortkeyGatewayModels,
|
|
12
|
+
} = require('../llm/portkey-sync');
|
|
13
|
+
const {
|
|
14
|
+
buildPortkeyHeaders,
|
|
15
|
+
defaultPortkeyBaseUrl,
|
|
16
|
+
} = require('../llm/portkey');
|
|
17
|
+
const { getDevboxClaudePortkeyGateway } = require('../runtime/devbox-gateway');
|
|
8
18
|
|
|
9
19
|
let brain = null;
|
|
10
20
|
try { brain = require('../brain'); } catch {}
|
|
11
21
|
|
|
22
|
+
const SETUP_PROVIDER_TYPES = ['anthropic', 'openai', 'google', 'ollama', 'deepseek', 'moonshot'];
|
|
23
|
+
const PORTKEY_GATEWAY_PROVIDER_TYPES = ['anthropic', 'deepseek', 'google', 'moonshot', 'openai'];
|
|
24
|
+
const SHELL_ENV_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
25
|
+
const SHELL_ENV_LOOKUP_TIMEOUT_MS = 5000;
|
|
26
|
+
const _shellEnvCache = new Map();
|
|
27
|
+
const _shellEnvInflight = new Map();
|
|
28
|
+
let _shellEnvExecFile = execFile;
|
|
29
|
+
|
|
12
30
|
function getBrain() {
|
|
13
31
|
if (!brain) return null;
|
|
14
32
|
try { brain.getDb(); } catch {
|
|
@@ -44,6 +62,101 @@ function getShellRcSource(shell) {
|
|
|
44
62
|
return '';
|
|
45
63
|
}
|
|
46
64
|
|
|
65
|
+
function isSupportedEnvName(name) {
|
|
66
|
+
return /^[A-Z_][A-Z0-9_]*$/.test(String(name || ''));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function normalizeShellEnvValue(value) {
|
|
70
|
+
const str = String(value || '').trim();
|
|
71
|
+
return str && str.length > 3 ? str : null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function _cachedShellEnvValue(name, nowMs = Date.now()) {
|
|
75
|
+
const cached = _shellEnvCache.get(name);
|
|
76
|
+
if (!cached) return null;
|
|
77
|
+
if (nowMs - cached.checkedAt > SHELL_ENV_CACHE_TTL_MS) return null;
|
|
78
|
+
return cached.value || null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function _setShellEnvCache(name, value, nowMs = Date.now()) {
|
|
82
|
+
_shellEnvCache.set(name, { value: normalizeShellEnvValue(value), checkedAt: nowMs });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function _shellEnvLookupScript(names, shell) {
|
|
86
|
+
const refs = names.map((name) => `"$${name}"`).join(' ');
|
|
87
|
+
return `${getShellRcSource(shell)} printf '%s\\0' ${refs}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function _readShellEnvValues(names, options = {}) {
|
|
91
|
+
const uniqueNames = Array.from(new Set((names || []).filter(isSupportedEnvName)));
|
|
92
|
+
const out = new Map();
|
|
93
|
+
const nowMs = options.nowMs || Date.now();
|
|
94
|
+
const missing = [];
|
|
95
|
+
for (const name of uniqueNames) {
|
|
96
|
+
const direct = normalizeShellEnvValue(process.env[name]);
|
|
97
|
+
if (direct) {
|
|
98
|
+
out.set(name, direct);
|
|
99
|
+
_setShellEnvCache(name, direct, nowMs);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const cached = options.ignoreCache ? null : _cachedShellEnvValue(name, nowMs);
|
|
103
|
+
if (cached) {
|
|
104
|
+
out.set(name, cached);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
missing.push(name);
|
|
108
|
+
}
|
|
109
|
+
if (!missing.length) return Promise.resolve(out);
|
|
110
|
+
|
|
111
|
+
const shell = process.env.SHELL || '/bin/zsh';
|
|
112
|
+
const script = _shellEnvLookupScript(missing, shell);
|
|
113
|
+
const timeoutMs = options.timeoutMs || SHELL_ENV_LOOKUP_TIMEOUT_MS;
|
|
114
|
+
const inflightKey = `${shell}\n${timeoutMs}\n${missing.join('\0')}`;
|
|
115
|
+
const inflight = _shellEnvInflight.get(inflightKey);
|
|
116
|
+
if (inflight) {
|
|
117
|
+
return inflight.then((missingValues) => {
|
|
118
|
+
for (const [name, value] of missingValues.entries()) {
|
|
119
|
+
if (value) out.set(name, value);
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
const lookup = new Promise((resolve) => {
|
|
125
|
+
_shellEnvExecFile(shell, ['-lc', script], {
|
|
126
|
+
encoding: 'utf8',
|
|
127
|
+
timeout: timeoutMs,
|
|
128
|
+
maxBuffer: 128 * 1024,
|
|
129
|
+
}, (err, stdout) => {
|
|
130
|
+
const missingValues = new Map();
|
|
131
|
+
if (err) {
|
|
132
|
+
for (const name of missing) _setShellEnvCache(name, null, nowMs);
|
|
133
|
+
resolve(missingValues);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const values = String(stdout || '').split('\0');
|
|
137
|
+
missing.forEach((name, index) => {
|
|
138
|
+
const value = normalizeShellEnvValue(values[index] || '');
|
|
139
|
+
_setShellEnvCache(name, value, nowMs);
|
|
140
|
+
if (value) missingValues.set(name, value);
|
|
141
|
+
});
|
|
142
|
+
resolve(missingValues);
|
|
143
|
+
});
|
|
144
|
+
}).finally(() => {
|
|
145
|
+
_shellEnvInflight.delete(inflightKey);
|
|
146
|
+
});
|
|
147
|
+
_shellEnvInflight.set(inflightKey, lookup);
|
|
148
|
+
return lookup.then((missingValues) => {
|
|
149
|
+
for (const [name, value] of missingValues.entries()) out.set(name, value);
|
|
150
|
+
return out;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function getShellEnvValue(name, options = {}) {
|
|
155
|
+
if (!isSupportedEnvName(name)) return null;
|
|
156
|
+
const values = await _readShellEnvValues([name], options);
|
|
157
|
+
return values.get(name) || null;
|
|
158
|
+
}
|
|
159
|
+
|
|
47
160
|
function readSmallJsonBody(req, maxBytes = 65536) {
|
|
48
161
|
return new Promise((resolve, reject) => {
|
|
49
162
|
const chunks = [];
|
|
@@ -72,28 +185,431 @@ function parseCustomHeaders(value) {
|
|
|
72
185
|
return undefined;
|
|
73
186
|
}
|
|
74
187
|
|
|
75
|
-
function
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
188
|
+
function isSqliteCatalogReadError(err) {
|
|
189
|
+
return !!err && (
|
|
190
|
+
err.code === 'SQLITE_CORRUPT'
|
|
191
|
+
|| /database disk image is malformed|malformed database schema|btree/i.test(err.message || '')
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function addCatalogWarning(warnings, where, err, extra = {}) {
|
|
196
|
+
if (!warnings) return;
|
|
197
|
+
const warning = {
|
|
198
|
+
code: 'model_registry_unavailable',
|
|
199
|
+
where,
|
|
200
|
+
message: err && err.message ? err.message : String(err || 'Model registry read failed'),
|
|
201
|
+
...extra,
|
|
202
|
+
};
|
|
203
|
+
const key = JSON.stringify([warning.code, warning.where, warning.provider_id || '', warning.message]);
|
|
204
|
+
if (!warnings.some((item) => item._key === key)) warnings.push({ ...warning, _key: key });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function publicCatalogWarnings(warnings) {
|
|
208
|
+
return (warnings || []).map(({ _key, ...warning }) => warning);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function catalogStatusPayload(warnings) {
|
|
212
|
+
return warnings && warnings.length
|
|
213
|
+
? {
|
|
214
|
+
catalog_status: 'partial',
|
|
215
|
+
catalog_unavailable: true,
|
|
216
|
+
catalog_warnings: publicCatalogWarnings(warnings),
|
|
217
|
+
}
|
|
218
|
+
: { catalog_status: 'ok' };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function safeGetProviderWithKey(brainApi, provider, warnings) {
|
|
79
222
|
try {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return result && result.length > 3 ? result : null;
|
|
86
|
-
} catch {
|
|
87
|
-
return null;
|
|
223
|
+
return brainApi.getModelProviderWithKey(provider.id);
|
|
224
|
+
} catch (err) {
|
|
225
|
+
if (!isSqliteCatalogReadError(err)) throw err;
|
|
226
|
+
addCatalogWarning(warnings, 'model_provider_key', err, { provider_id: provider.id });
|
|
227
|
+
return provider;
|
|
88
228
|
}
|
|
89
229
|
}
|
|
90
230
|
|
|
91
|
-
function
|
|
92
|
-
|
|
231
|
+
function safeProviderRoutePolicy(brainApi, type, warnings) {
|
|
232
|
+
if (typeof brainApi.getProviderRoutePolicy !== 'function') return 'auto';
|
|
233
|
+
try {
|
|
234
|
+
return brainApi.getProviderRoutePolicy(type);
|
|
235
|
+
} catch (err) {
|
|
236
|
+
if (!isSqliteCatalogReadError(err)) throw err;
|
|
237
|
+
addCatalogWarning(warnings, 'provider_route_policy', err, { provider_type: type });
|
|
238
|
+
return 'auto';
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function safeListModelsByProvider(brainApi, providerId, warnings) {
|
|
243
|
+
try {
|
|
244
|
+
return { ok: true, models: brainApi.listModelsByProvider(providerId) || [] };
|
|
245
|
+
} catch (err) {
|
|
246
|
+
if (!isSqliteCatalogReadError(err)) throw err;
|
|
247
|
+
addCatalogWarning(warnings, 'models_by_provider', err, { provider_id: providerId });
|
|
248
|
+
return { ok: false, models: [], error: err };
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function safeModelCountsByProvider(brainApi, warnings) {
|
|
253
|
+
try {
|
|
254
|
+
if (typeof brainApi.listModelCountsByProvider !== 'function') {
|
|
255
|
+
return { ok: false, counts: new Map(), error: null };
|
|
256
|
+
}
|
|
257
|
+
const rows = brainApi.listModelCountsByProvider();
|
|
258
|
+
const counts = new Map();
|
|
259
|
+
for (const row of rows || []) {
|
|
260
|
+
counts.set(String(row.provider_id || ''), Number(row.model_count || 0));
|
|
261
|
+
}
|
|
262
|
+
return { ok: true, counts };
|
|
263
|
+
} catch (err) {
|
|
264
|
+
if (!isSqliteCatalogReadError(err)) throw err;
|
|
265
|
+
addCatalogWarning(warnings, 'model_counts_by_provider', err);
|
|
266
|
+
return { ok: false, counts: new Map(), error: err };
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function isPortkeyCatalogProviderRoute(provider = {}, models = []) {
|
|
271
|
+
const id = String(provider.id || provider.provider_id || '').toLowerCase();
|
|
272
|
+
const name = String(provider.name || '').toLowerCase();
|
|
273
|
+
if (id.includes('-portkey-catalog-') || /\bvia\s+portkey\s+catalog\b/.test(name)) return true;
|
|
274
|
+
if (!Array.isArray(models) || !models.length) return false;
|
|
275
|
+
return models.every((model) => {
|
|
276
|
+
const source = String(model.source || '').toLowerCase();
|
|
277
|
+
const status = String(model.verification_status || model.verificationStatus || '').toLowerCase();
|
|
278
|
+
return source === 'portkey_catalog' || status === 'catalog';
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function providerCatalogSortRank(provider = {}) {
|
|
283
|
+
return provider.is_catalog === true || provider.catalog_only === true ? 1 : 0;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function getProviderWithMeta(brainApi, provider, options = {}) {
|
|
287
|
+
const warnings = options.warnings || null;
|
|
288
|
+
const full = Object.prototype.hasOwnProperty.call(options, 'full')
|
|
289
|
+
? options.full
|
|
290
|
+
: safeGetProviderWithKey(brainApi, provider, warnings);
|
|
291
|
+
const routePolicy = safeProviderRoutePolicy(brainApi, provider.type, warnings);
|
|
292
|
+
const connectionKind = isPortkeyProviderRow(full || provider) ? 'portkey' : (provider.auth_method && provider.auth_method !== 'api_key' ? provider.auth_method : 'direct');
|
|
293
|
+
const modelRead = options.modelRead || null;
|
|
294
|
+
const isCatalog = isPortkeyCatalogProviderRoute(provider, modelRead && modelRead.ok ? modelRead.models : []);
|
|
295
|
+
const {
|
|
296
|
+
api_key_encrypted: _apiKeyEncrypted,
|
|
297
|
+
apiKeyEncrypted: _apiKeyEncryptedCamel,
|
|
298
|
+
custom_headers: _customHeaders,
|
|
299
|
+
customHeaders: _customHeadersCamel,
|
|
300
|
+
...safeProvider
|
|
301
|
+
} = provider || {};
|
|
93
302
|
return {
|
|
94
|
-
...
|
|
303
|
+
...safeProvider,
|
|
95
304
|
has_key: !!(full && full.api_key_encrypted),
|
|
96
305
|
is_default_instance: provider.id.endsWith('-default') || provider.id.endsWith('-auto'),
|
|
306
|
+
connection_kind: connectionKind,
|
|
307
|
+
connection_source: isCatalog ? 'portkey_catalog' : connectionKind,
|
|
308
|
+
is_catalog: isCatalog,
|
|
309
|
+
catalog_only: isCatalog,
|
|
310
|
+
route_policy: routePolicy,
|
|
311
|
+
model_catalog_status: options.modelCatalogStatus || 'ok',
|
|
312
|
+
...(Number.isFinite(Number(options.modelCount)) ? { model_count: Number(options.modelCount) } : {}),
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function _portkeyLastErrorMap(lastError) {
|
|
317
|
+
const out = new Map();
|
|
318
|
+
for (const part of String(lastError || '').split(/;\s*/)) {
|
|
319
|
+
if (!part) continue;
|
|
320
|
+
const idx = part.indexOf(':');
|
|
321
|
+
if (idx <= 0) continue;
|
|
322
|
+
const routeId = part.slice(0, idx).trim();
|
|
323
|
+
const message = part.slice(idx + 1).trim();
|
|
324
|
+
if (routeId && message) out.set(routeId, message);
|
|
325
|
+
}
|
|
326
|
+
return out;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function getVerifiedPortkeyProviderTypes(brainApi, warnings = null) {
|
|
330
|
+
const providers = brainApi.listModelProviders()
|
|
331
|
+
.map((provider) => safeGetProviderWithKey(brainApi, provider, warnings) || provider)
|
|
332
|
+
.filter(isPortkeyProviderRow);
|
|
333
|
+
const countsRead = safeModelCountsByProvider(brainApi, warnings);
|
|
334
|
+
const supported = new Set(
|
|
335
|
+
providers
|
|
336
|
+
.filter((provider) => {
|
|
337
|
+
if (countsRead.ok) return (countsRead.counts.get(String(provider.id || '')) || 0) > 0;
|
|
338
|
+
const modelRead = safeListModelsByProvider(brainApi, provider.id, warnings);
|
|
339
|
+
return modelRead.ok && modelRead.models.length > 0;
|
|
340
|
+
})
|
|
341
|
+
.map((provider) => provider.type)
|
|
342
|
+
);
|
|
343
|
+
const known = PORTKEY_GATEWAY_PROVIDER_TYPES.filter((type) => supported.has(type));
|
|
344
|
+
const custom = Array.from(supported).filter((type) => !PORTKEY_GATEWAY_PROVIDER_TYPES.includes(type)).sort();
|
|
345
|
+
return known.concat(custom);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function buildGatewaySummary(brainApi, warnings = null) {
|
|
349
|
+
const providers = brainApi.listModelProviders().map((provider) => {
|
|
350
|
+
const full = safeGetProviderWithKey(brainApi, provider, warnings) || provider;
|
|
351
|
+
return { ...provider, ...full };
|
|
352
|
+
});
|
|
353
|
+
const allPortkeyRoutes = providers.filter(isPortkeyProviderRow);
|
|
354
|
+
const gatewayCredentials = providers.filter(isPortkeyGatewayCredentialRow);
|
|
355
|
+
const credentialRows = gatewayCredentials.length ? gatewayCredentials : allPortkeyRoutes.slice(0, 1);
|
|
356
|
+
const routeModelReads = new Map();
|
|
357
|
+
const readRouteModels = (route) => {
|
|
358
|
+
if (!routeModelReads.has(route.id)) {
|
|
359
|
+
routeModelReads.set(route.id, safeListModelsByProvider(brainApi, route.id, warnings));
|
|
360
|
+
}
|
|
361
|
+
return routeModelReads.get(route.id);
|
|
362
|
+
};
|
|
363
|
+
const portkeyRoutes = allPortkeyRoutes.filter((route) => {
|
|
364
|
+
const modelRead = readRouteModels(route);
|
|
365
|
+
return modelRead.ok && modelRead.models.length > 0;
|
|
366
|
+
});
|
|
367
|
+
const configuredTypeSet = new Set(portkeyRoutes.map((route) => route.type).filter(Boolean));
|
|
368
|
+
const providerTypes = PORTKEY_GATEWAY_PROVIDER_TYPES
|
|
369
|
+
.filter((type) => configuredTypeSet.has(type))
|
|
370
|
+
.concat(Array.from(configuredTypeSet).filter((type) => !PORTKEY_GATEWAY_PROVIDER_TYPES.includes(type)).sort());
|
|
371
|
+
const lastSuccess = brainApi.getKv?.('model_gateway_sync:portkey:last_success_at') || '';
|
|
372
|
+
const lastRun = brainApi.getKv?.('model_gateway_sync:portkey:last_run_at') || '';
|
|
373
|
+
const lastError = brainApi.getKv?.('model_gateway_sync:portkey:last_error') || '';
|
|
374
|
+
const routeErrors = _portkeyLastErrorMap(lastError);
|
|
375
|
+
const routes = portkeyRoutes.map((route) => {
|
|
376
|
+
const models = readRouteModels(route).models;
|
|
377
|
+
const modelCount = models.length;
|
|
378
|
+
const catalogModelCount = models.filter((model) => model.source === 'portkey_catalog').length;
|
|
379
|
+
const gatewayModelCount = Math.max(0, modelCount - catalogModelCount);
|
|
380
|
+
const supported = modelCount > 0;
|
|
381
|
+
return {
|
|
382
|
+
id: route.id,
|
|
383
|
+
name: route.name,
|
|
384
|
+
type: route.type,
|
|
385
|
+
enabled: route.enabled,
|
|
386
|
+
model_count: modelCount,
|
|
387
|
+
catalog_model_count: catalogModelCount,
|
|
388
|
+
gateway_model_count: gatewayModelCount,
|
|
389
|
+
supported,
|
|
390
|
+
support_status: supported ? (gatewayModelCount > 0 ? 'supported' : 'catalog') : (routeErrors.has(route.id) ? 'error' : 'unverified'),
|
|
391
|
+
sync_error: routeErrors.get(route.id) || null,
|
|
392
|
+
route_policy: safeProviderRoutePolicy(brainApi, route.type, warnings),
|
|
393
|
+
};
|
|
394
|
+
});
|
|
395
|
+
const routesByType = new Map();
|
|
396
|
+
for (const route of routes) {
|
|
397
|
+
if (!routesByType.has(route.type)) routesByType.set(route.type, []);
|
|
398
|
+
routesByType.get(route.type).push(route);
|
|
399
|
+
}
|
|
400
|
+
const supportedProviderTypes = providerTypes
|
|
401
|
+
.filter((type) => (routesByType.get(type) || []).some((route) => route.supported));
|
|
402
|
+
const supportedProviders = providerTypes.map((type) => {
|
|
403
|
+
const typeRoutes = routesByType.get(type) || [];
|
|
404
|
+
const supportedRoute = typeRoutes.find((route) => route.supported);
|
|
405
|
+
const errorRoute = typeRoutes.find((route) => route.support_status === 'error');
|
|
406
|
+
const route = supportedRoute || errorRoute || typeRoutes[0] || null;
|
|
407
|
+
const supported = Boolean(supportedRoute);
|
|
408
|
+
const supportStatus = supported ? 'supported' : (errorRoute ? 'error' : (typeRoutes.length ? 'unverified' : 'not_configured'));
|
|
409
|
+
return {
|
|
410
|
+
type,
|
|
411
|
+
name: _providerDisplayName(type),
|
|
412
|
+
configured: typeRoutes.length > 0,
|
|
413
|
+
supported,
|
|
414
|
+
support_status: supportStatus,
|
|
415
|
+
route_id: route ? route.id : null,
|
|
416
|
+
model_count: typeRoutes.reduce((sum, item) => sum + item.model_count, 0),
|
|
417
|
+
catalog_model_count: typeRoutes.reduce((sum, item) => sum + (item.catalog_model_count || 0), 0),
|
|
418
|
+
gateway_model_count: typeRoutes.reduce((sum, item) => sum + (item.gateway_model_count || 0), 0),
|
|
419
|
+
sync_error: errorRoute ? errorRoute.sync_error : null,
|
|
420
|
+
route_policy: safeProviderRoutePolicy(brainApi, type, warnings),
|
|
421
|
+
};
|
|
422
|
+
});
|
|
423
|
+
const policies = {};
|
|
424
|
+
for (const type of providerTypes) {
|
|
425
|
+
policies[type] = safeProviderRoutePolicy(brainApi, type, warnings);
|
|
426
|
+
}
|
|
427
|
+
const totalModelCount = routes.reduce((sum, route) => sum + route.model_count, 0);
|
|
428
|
+
const totalCatalogModelCount = routes.reduce((sum, route) => sum + (route.catalog_model_count || 0), 0);
|
|
429
|
+
const totalGatewayModelCount = routes.reduce((sum, route) => sum + (route.gateway_model_count || 0), 0);
|
|
430
|
+
const visibleLastError = totalModelCount > 0 ? '' : lastError;
|
|
431
|
+
return [{
|
|
432
|
+
type: 'portkey',
|
|
433
|
+
name: 'Portkey Gateway',
|
|
434
|
+
enabled: credentialRows.some((row) => row.enabled !== 0) || portkeyRoutes.some((route) => route.enabled !== 0),
|
|
435
|
+
configured: credentialRows.length > 0 || allPortkeyRoutes.length > 0,
|
|
436
|
+
credential_count: credentialRows.length,
|
|
437
|
+
route_count: routes.length,
|
|
438
|
+
provider_count: providerTypes.length,
|
|
439
|
+
verified_provider_count: supportedProviderTypes.length,
|
|
440
|
+
model_count: totalModelCount,
|
|
441
|
+
catalog_model_count: totalCatalogModelCount,
|
|
442
|
+
gateway_model_count: totalGatewayModelCount,
|
|
443
|
+
provider_types: providerTypes,
|
|
444
|
+
configured_provider_types: providerTypes,
|
|
445
|
+
supported_provider_types: supportedProviderTypes,
|
|
446
|
+
supported_providers: supportedProviders,
|
|
447
|
+
routes,
|
|
448
|
+
policies,
|
|
449
|
+
last_success_at: lastSuccess,
|
|
450
|
+
last_run_at: lastRun,
|
|
451
|
+
last_error: visibleLastError,
|
|
452
|
+
last_warning: visibleLastError ? '' : lastError,
|
|
453
|
+
next_sync_hint: 'hourly',
|
|
454
|
+
}];
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function _slugFromLabel(label) {
|
|
458
|
+
return String(label || '')
|
|
459
|
+
.trim()
|
|
460
|
+
.toLowerCase()
|
|
461
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
462
|
+
.replace(/^-|-$/g, '')
|
|
463
|
+
.slice(0, 40);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function _providerDisplayName(type) {
|
|
467
|
+
switch (type) {
|
|
468
|
+
case 'anthropic': return 'Anthropic';
|
|
469
|
+
case 'openai': return 'OpenAI';
|
|
470
|
+
case 'google': return 'Google Gemini';
|
|
471
|
+
case 'deepseek': return 'DeepSeek';
|
|
472
|
+
case 'moonshot': return 'Moonshot / Kimi';
|
|
473
|
+
default:
|
|
474
|
+
return String(type || 'Provider')
|
|
475
|
+
.split(/[-_]+/)
|
|
476
|
+
.filter(Boolean)
|
|
477
|
+
.map((part) => part.length <= 3 ? part.toUpperCase() : part.charAt(0).toUpperCase() + part.slice(1))
|
|
478
|
+
.join(' ') || 'Provider';
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function _sanitizeSetupProviderType(value) {
|
|
483
|
+
const sanitized = typeof value === 'string'
|
|
484
|
+
? value.toLowerCase().replace(/[^a-z]/g, '').slice(0, 20)
|
|
485
|
+
: '';
|
|
486
|
+
if (['kimi', 'kimik', 'kimik2', 'moonshotai', 'moonshotkimi'].includes(sanitized)) return 'moonshot';
|
|
487
|
+
return sanitized;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function _sanitizeSetupModel(value) {
|
|
491
|
+
return typeof value === 'string'
|
|
492
|
+
? value.replace(/[\r\n\s]/g, '').slice(0, 100)
|
|
493
|
+
: '';
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function handleLocalModelOwnerWrite(pathname, { method = 'POST', body = null } = {}) {
|
|
497
|
+
const brainApi = getBrain();
|
|
498
|
+
if (!brainApi) throw new Error('Wall-E brain not available');
|
|
499
|
+
const p = String(pathname || '');
|
|
500
|
+
const m = String(method || 'POST').toUpperCase();
|
|
501
|
+
const payload = body && typeof body === 'object' ? body : {};
|
|
502
|
+
|
|
503
|
+
if (p === '/api/wall-e/setup/provider' && m === 'POST') {
|
|
504
|
+
const type = _sanitizeSetupProviderType(payload.type);
|
|
505
|
+
if (!type) throw new Error('Invalid provider type');
|
|
506
|
+
if (!SETUP_PROVIDER_TYPES.includes(type)) {
|
|
507
|
+
throw new Error('Invalid provider type. Must be one of: ' + SETUP_PROVIDER_TYPES.join(', '));
|
|
508
|
+
}
|
|
509
|
+
const hasBaseUrl = Object.prototype.hasOwnProperty.call(payload, 'base_url')
|
|
510
|
+
|| Object.prototype.hasOwnProperty.call(payload, 'baseUrl');
|
|
511
|
+
const result = brainApi.saveSetupProvider({
|
|
512
|
+
id: payload.id || `${type}-default`,
|
|
513
|
+
name: payload.name || _providerDisplayName(type),
|
|
514
|
+
type,
|
|
515
|
+
baseUrl: hasBaseUrl ? (payload.base_url || payload.baseUrl || null) : undefined,
|
|
516
|
+
apiKeyEncrypted: typeof payload.api_key === 'string'
|
|
517
|
+
? payload.api_key.replace(/[\r\n\s]/g, '').slice(0, 200)
|
|
518
|
+
: (payload.apiKeyEncrypted || null),
|
|
519
|
+
customHeaders: payload.custom_headers || payload.customHeaders || null,
|
|
520
|
+
enabled: payload.enabled !== false,
|
|
521
|
+
model: _sanitizeSetupModel(payload.model),
|
|
522
|
+
setDefault: Boolean(payload.set_default),
|
|
523
|
+
authMethod: payload.auth_method || null,
|
|
524
|
+
});
|
|
525
|
+
return { ok: true, authority: 'wall-e-local-model-admin', owner_write: 'wall-e-local-model-admin', ...result };
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
if (p === '/api/wall-e/setup/default-provider' && m === 'POST') {
|
|
529
|
+
const type = _sanitizeSetupProviderType(payload.type);
|
|
530
|
+
if (!type) throw new Error('Invalid provider type');
|
|
531
|
+
if (!SETUP_PROVIDER_TYPES.includes(type)) {
|
|
532
|
+
throw new Error('Invalid provider type. Must be one of: ' + SETUP_PROVIDER_TYPES.join(', '));
|
|
533
|
+
}
|
|
534
|
+
const result = brainApi.setDefaultProviderSelection({
|
|
535
|
+
type,
|
|
536
|
+
requestedModel: _sanitizeSetupModel(payload.requested_model ?? payload.requestedModel ?? payload.model),
|
|
537
|
+
targetModel: _sanitizeSetupModel(payload.target_model ?? payload.targetModel ?? payload.model),
|
|
538
|
+
});
|
|
539
|
+
return { ok: true, authority: 'wall-e-local-model-admin', owner_write: 'wall-e-local-model-admin', ...result };
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const providerDelete = p.match(/^\/api\/wall-e\/setup\/provider\/([a-z0-9_-]+)$/);
|
|
543
|
+
if (providerDelete && m === 'DELETE') {
|
|
544
|
+
const type = _sanitizeSetupProviderType(providerDelete[1]);
|
|
545
|
+
if (!type) throw new Error('Invalid provider type');
|
|
546
|
+
const result = brainApi.disableModelProviderByType(type);
|
|
547
|
+
return { ok: true, authority: 'wall-e-local-model-admin', owner_write: 'wall-e-local-model-admin', ...result };
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
throw new Error(`No local model owner write exists for ${m} ${p}`);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function _portkeyRuntimeBaseUrl(baseUrl, type) {
|
|
554
|
+
const normalized = String(baseUrl || defaultPortkeyBaseUrl('openai')).trim().replace(/\/+$/, '');
|
|
555
|
+
if (type === 'anthropic' && /^https:\/\/api\.portkey\.ai\/v1$/i.test(normalized)) {
|
|
556
|
+
return defaultPortkeyBaseUrl('anthropic');
|
|
557
|
+
}
|
|
558
|
+
return normalized || defaultPortkeyBaseUrl(type);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function _resolveDevboxPortkeyGateway(body = {}, apiKey = '') {
|
|
562
|
+
const source = String(body.source || body.credentialSource || body.detectedSource || '').toLowerCase();
|
|
563
|
+
const wantsDevbox = source.includes('devbox') || body.useDevboxHeaders === true;
|
|
564
|
+
const devboxGateway = getDevboxClaudePortkeyGateway();
|
|
565
|
+
if (!devboxGateway) return null;
|
|
566
|
+
if (wantsDevbox) return devboxGateway;
|
|
567
|
+
return apiKey && apiKey === devboxGateway.apiKey ? devboxGateway : null;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function upsertPortkeyGateway(brainApi, body = {}) {
|
|
571
|
+
let apiKey = String(body.apiKey || body.apiKeyEncrypted || '').trim();
|
|
572
|
+
const devboxGateway = _resolveDevboxPortkeyGateway(body, apiKey);
|
|
573
|
+
if (!apiKey && devboxGateway) apiKey = devboxGateway.apiKey;
|
|
574
|
+
if (!apiKey) throw new Error('Portkey API key required');
|
|
575
|
+
const label = String(body.label || (devboxGateway && body.source ? devboxGateway.label : '')).trim();
|
|
576
|
+
const slug = _slugFromLabel(label);
|
|
577
|
+
const suffix = slug ? `portkey-${slug}` : 'portkey';
|
|
578
|
+
const baseUrl = String((devboxGateway && devboxGateway.baseUrl) || body.baseUrl || body.gatewayUrl || defaultPortkeyBaseUrl('openai')).trim() || defaultPortkeyBaseUrl('openai');
|
|
579
|
+
const bodyHeaders = parseCustomHeaders(body.customHeaders || body.custom_headers) || {};
|
|
580
|
+
const seedHeaders = Object.assign({}, devboxGateway ? devboxGateway.customHeaders : {}, bodyHeaders);
|
|
581
|
+
const customHeaders = JSON.stringify(buildPortkeyHeaders({
|
|
582
|
+
apiKey,
|
|
583
|
+
configId: body.configId,
|
|
584
|
+
virtualKey: body.virtualKey,
|
|
585
|
+
headers: seedHeaders,
|
|
586
|
+
}));
|
|
587
|
+
let removedLegacyRoutes = 0;
|
|
588
|
+
for (const provider of brainApi.listModelProviders?.() || []) {
|
|
589
|
+
const full = brainApi.getModelProviderWithKey?.(provider.id) || provider;
|
|
590
|
+
if (isPortkeyProviderRow(full) && (brainApi.listModelsByProvider(full.id) || []).length === 0) {
|
|
591
|
+
removedLegacyRoutes += brainApi.deleteModelProvider(full.id) || 0;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
const credential = {
|
|
595
|
+
id: `portkey-gateway-${slug || 'default'}`,
|
|
596
|
+
name: `Portkey Gateway${label ? ` (${label})` : ''}`,
|
|
597
|
+
type: 'portkey_gateway',
|
|
598
|
+
baseUrl: _portkeyRuntimeBaseUrl(baseUrl, 'openai'),
|
|
599
|
+
apiKeyEncrypted: apiKey,
|
|
600
|
+
customHeaders,
|
|
601
|
+
enabled: 1,
|
|
602
|
+
};
|
|
603
|
+
brainApi.upsertModelProvider(credential);
|
|
604
|
+
brainApi.setKv?.('model_gateway:portkey:label', label);
|
|
605
|
+
brainApi.setKv?.('model_gateway:portkey:base_url', baseUrl);
|
|
606
|
+
if (devboxGateway) brainApi.setKv?.('model_gateway:portkey:source', devboxGateway.source);
|
|
607
|
+
return {
|
|
608
|
+
ok: true,
|
|
609
|
+
gateway: 'portkey',
|
|
610
|
+
provider: { id: credential.id, type: credential.type, name: credential.name },
|
|
611
|
+
source: devboxGateway ? devboxGateway.source : (body.source || ''),
|
|
612
|
+
removed_legacy_routes: removedLegacyRoutes,
|
|
97
613
|
};
|
|
98
614
|
}
|
|
99
615
|
|
|
@@ -147,16 +663,168 @@ async function handleModelAdminApi(req, res, url) {
|
|
|
147
663
|
if (p === '/api/models/providers' && m === 'GET') {
|
|
148
664
|
if (!brainApi) return jsonResponse(res, 500, { error: 'Wall-E brain not available' });
|
|
149
665
|
try {
|
|
150
|
-
const
|
|
666
|
+
const warnings = [];
|
|
667
|
+
const modelCountsByProvider = safeModelCountsByProvider(brainApi, warnings);
|
|
668
|
+
const providerRows = brainApi.listModelProviders()
|
|
669
|
+
.map((provider, index) => {
|
|
670
|
+
const full = safeGetProviderWithKey(brainApi, provider, warnings) || provider;
|
|
671
|
+
const isPortkey = isPortkeyProviderRow(full);
|
|
672
|
+
const providerId = String(full.id || provider.id || '');
|
|
673
|
+
const modelCount = modelCountsByProvider.ok
|
|
674
|
+
? (modelCountsByProvider.counts.get(providerId) || 0)
|
|
675
|
+
: null;
|
|
676
|
+
return { provider, full, isPortkey, modelCount, index };
|
|
677
|
+
})
|
|
678
|
+
.filter(({ full, isPortkey, modelCount }) => {
|
|
679
|
+
if (isPortkeyGatewayCredentialRow(full)) return false;
|
|
680
|
+
if (isPortkey && modelCountsByProvider.ok && modelCount === 0) return false;
|
|
681
|
+
return true;
|
|
682
|
+
})
|
|
683
|
+
.map(({ provider, full, modelCount, index }) => ({
|
|
684
|
+
provider: getProviderWithMeta(brainApi, provider, {
|
|
685
|
+
full,
|
|
686
|
+
modelCount: modelCountsByProvider.ok ? modelCount : undefined,
|
|
687
|
+
warnings,
|
|
688
|
+
modelCatalogStatus: warnings.length ? 'partial' : 'ok',
|
|
689
|
+
}),
|
|
690
|
+
index,
|
|
691
|
+
}));
|
|
692
|
+
providerRows.sort((a, b) => {
|
|
693
|
+
const rank = providerCatalogSortRank(a.provider) - providerCatalogSortRank(b.provider);
|
|
694
|
+
if (rank !== 0) return rank;
|
|
695
|
+
return a.index - b.index;
|
|
696
|
+
});
|
|
697
|
+
const providers = providerRows.map((row) => row.provider);
|
|
151
698
|
const typeCounts = {};
|
|
152
699
|
for (const provider of providers) typeCounts[provider.type] = (typeCounts[provider.type] || 0) + 1;
|
|
153
700
|
for (const provider of providers) provider.instance_count = typeCounts[provider.type];
|
|
154
|
-
return jsonResponse(res, 200, { providers });
|
|
701
|
+
return jsonResponse(res, 200, { providers, ...catalogStatusPayload(warnings) });
|
|
702
|
+
} catch (e) {
|
|
703
|
+
return jsonResponse(res, 500, { error: e.message });
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (p === '/api/models/gateways' && m === 'GET') {
|
|
708
|
+
if (!brainApi) return jsonResponse(res, 500, { error: 'Wall-E brain not available' });
|
|
709
|
+
try {
|
|
710
|
+
const warnings = [];
|
|
711
|
+
return jsonResponse(res, 200, { gateways: buildGatewaySummary(brainApi, warnings), ...catalogStatusPayload(warnings) });
|
|
155
712
|
} catch (e) {
|
|
156
713
|
return jsonResponse(res, 500, { error: e.message });
|
|
157
714
|
}
|
|
158
715
|
}
|
|
159
716
|
|
|
717
|
+
if (p === '/api/models/provider-route-policy' && m === 'POST') {
|
|
718
|
+
if (!brainApi) return jsonResponse(res, 500, { error: 'Wall-E brain not available' });
|
|
719
|
+
try {
|
|
720
|
+
const body = await readSmallJsonBody(req);
|
|
721
|
+
const type = String(body.type || '').trim().toLowerCase();
|
|
722
|
+
const policy = String(body.policy || 'auto').trim().toLowerCase();
|
|
723
|
+
const warnings = [];
|
|
724
|
+
if (policy === 'portkey' && !getVerifiedPortkeyProviderTypes(brainApi, warnings).includes(type)) {
|
|
725
|
+
if (warnings.length) {
|
|
726
|
+
return jsonResponse(res, 409, {
|
|
727
|
+
error: `${_providerDisplayName(type)} could not be verified by the Portkey gateway because the model catalog is partially unavailable. Repair or resync the model catalog, then try again.`,
|
|
728
|
+
code: 'model_registry_unavailable',
|
|
729
|
+
type,
|
|
730
|
+
...catalogStatusPayload(warnings),
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
return jsonResponse(res, 409, {
|
|
734
|
+
error: `${_providerDisplayName(type)} is not verified by the Portkey gateway yet. Fix the Portkey sync error, then sync models before selecting Portkey.`,
|
|
735
|
+
code: 'portkey_provider_not_verified',
|
|
736
|
+
type,
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
const result = brainApi.setProviderRoutePolicy({ type: body.type, policy: body.policy });
|
|
740
|
+
return jsonResponse(res, 200, { ok: true, ...result });
|
|
741
|
+
} catch (e) {
|
|
742
|
+
return jsonResponse(res, 400, { error: e.message });
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
if (p === '/api/models/portkey/sync' && m === 'POST') {
|
|
747
|
+
if (!brainApi) return jsonResponse(res, 500, { error: 'Wall-E brain not available' });
|
|
748
|
+
try {
|
|
749
|
+
const body = await readSmallJsonBody(req);
|
|
750
|
+
const result = await syncPortkeyGatewayModels({ brainApi, providerId: body.provider_id || body.providerId || null });
|
|
751
|
+
return jsonResponse(res, result.errors.length ? 207 : 200, { ok: result.errors.length === 0, ...result });
|
|
752
|
+
} catch (e) {
|
|
753
|
+
return jsonResponse(res, 400, { error: e.message });
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
if (p === '/api/models/gateways/portkey' && m === 'POST') {
|
|
758
|
+
if (!brainApi) return jsonResponse(res, 500, { error: 'Wall-E brain not available' });
|
|
759
|
+
try {
|
|
760
|
+
const body = await readSmallJsonBody(req);
|
|
761
|
+
return jsonResponse(res, 200, upsertPortkeyGateway(brainApi, body));
|
|
762
|
+
} catch (e) {
|
|
763
|
+
return jsonResponse(res, 400, { error: e.message });
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
if (p === '/api/models/portkey/apply-all' && m === 'POST') {
|
|
768
|
+
if (!brainApi) return jsonResponse(res, 500, { error: 'Wall-E brain not available' });
|
|
769
|
+
try {
|
|
770
|
+
const providers = brainApi.listModelProviders()
|
|
771
|
+
.map((provider) => brainApi.getModelProviderWithKey(provider.id) || provider)
|
|
772
|
+
.filter(isPortkeyProviderRow);
|
|
773
|
+
const types = Array.from(new Set(providers.map((provider) => provider.type))).sort();
|
|
774
|
+
const supportedTypes = getVerifiedPortkeyProviderTypes(brainApi);
|
|
775
|
+
const supportedSet = new Set(supportedTypes);
|
|
776
|
+
const skippedTypes = types.filter((type) => !supportedSet.has(type));
|
|
777
|
+
for (const type of supportedTypes) brainApi.setProviderRoutePolicy({ type, policy: 'portkey' });
|
|
778
|
+
for (const type of skippedTypes) {
|
|
779
|
+
if (brainApi.getProviderRoutePolicy?.(type) === 'portkey') {
|
|
780
|
+
brainApi.setProviderRoutePolicy({ type, policy: 'auto' });
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
const payload = { ok: supportedTypes.length > 0, gateway: 'portkey', provider_types: supportedTypes, skipped_provider_types: skippedTypes };
|
|
784
|
+
return jsonResponse(res, supportedTypes.length > 0 ? 200 : 409, supportedTypes.length > 0 ? payload : {
|
|
785
|
+
...payload,
|
|
786
|
+
error: 'No Portkey providers are verified yet. Fix the Portkey sync error, then sync models before applying Portkey.',
|
|
787
|
+
code: 'no_verified_portkey_providers',
|
|
788
|
+
});
|
|
789
|
+
} catch (e) {
|
|
790
|
+
return jsonResponse(res, 400, { error: e.message });
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
if (p === '/api/models/portkey/disable-default' && m === 'POST') {
|
|
795
|
+
if (!brainApi) return jsonResponse(res, 500, { error: 'Wall-E brain not available' });
|
|
796
|
+
try {
|
|
797
|
+
const providers = brainApi.listModelProviders()
|
|
798
|
+
.map((provider) => brainApi.getModelProviderWithKey(provider.id) || provider)
|
|
799
|
+
.filter(isPortkeyProviderRow);
|
|
800
|
+
const types = Array.from(new Set(providers.map((provider) => provider.type))).sort();
|
|
801
|
+
for (const type of types) brainApi.setProviderRoutePolicy({ type, policy: 'auto' });
|
|
802
|
+
return jsonResponse(res, 200, { ok: true, gateway: 'portkey', provider_types: types });
|
|
803
|
+
} catch (e) {
|
|
804
|
+
return jsonResponse(res, 400, { error: e.message });
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
if (p === '/api/models/gateways/portkey' && m === 'DELETE') {
|
|
809
|
+
if (!brainApi) return jsonResponse(res, 500, { error: 'Wall-E brain not available' });
|
|
810
|
+
try {
|
|
811
|
+
const providers = brainApi.listModelProviders()
|
|
812
|
+
.map((provider) => brainApi.getModelProviderWithKey(provider.id) || provider)
|
|
813
|
+
.filter((provider) => isPortkeyGatewayCredentialRow(provider) || isPortkeyProviderRow(provider));
|
|
814
|
+
const types = Array.from(new Set(providers.filter(isPortkeyProviderRow).map((provider) => provider.type)));
|
|
815
|
+
let deletedProviders = 0;
|
|
816
|
+
let deletedModels = 0;
|
|
817
|
+
for (const provider of providers) {
|
|
818
|
+
deletedModels += brainApi.deleteModelRegistryByProvider(provider.id) || 0;
|
|
819
|
+
deletedProviders += brainApi.deleteModelProvider(provider.id) || 0;
|
|
820
|
+
}
|
|
821
|
+
for (const type of types) brainApi.setProviderRoutePolicy({ type, policy: 'auto' });
|
|
822
|
+
return jsonResponse(res, 200, { ok: true, gateway: 'portkey', deleted_providers: deletedProviders, deleted_models: deletedModels });
|
|
823
|
+
} catch (e) {
|
|
824
|
+
return jsonResponse(res, 400, { error: e.message });
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
160
828
|
if (p === '/api/models/providers' && m === 'POST') {
|
|
161
829
|
if (!brainApi) return jsonResponse(res, 500, { error: 'Wall-E brain not available' });
|
|
162
830
|
try {
|
|
@@ -195,8 +863,82 @@ async function handleModelAdminApi(req, res, url) {
|
|
|
195
863
|
if (!brainApi) return jsonResponse(res, 500, { error: 'Wall-E brain not available' });
|
|
196
864
|
try {
|
|
197
865
|
const providerId = url.searchParams.get('provider_id');
|
|
198
|
-
const
|
|
199
|
-
|
|
866
|
+
const warnings = [];
|
|
867
|
+
if (providerId) {
|
|
868
|
+
const modelRead = safeListModelsByProvider(brainApi, providerId, warnings);
|
|
869
|
+
return jsonResponse(res, 200, { models: modelRead.models, ...catalogStatusPayload(warnings) });
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
const providers = brainApi.listModelProviders();
|
|
873
|
+
const models = [];
|
|
874
|
+
for (const provider of providers) {
|
|
875
|
+
const modelRead = safeListModelsByProvider(brainApi, provider.id, warnings);
|
|
876
|
+
for (const model of modelRead.models) {
|
|
877
|
+
models.push({
|
|
878
|
+
...model,
|
|
879
|
+
provider_name: provider.name,
|
|
880
|
+
provider_type: provider.type,
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
models.sort((a, b) => String(a.provider_name || '').localeCompare(String(b.provider_name || ''))
|
|
885
|
+
|| String(a.display_name || '').localeCompare(String(b.display_name || '')));
|
|
886
|
+
return jsonResponse(res, 200, { models, ...catalogStatusPayload(warnings) });
|
|
887
|
+
} catch (e) {
|
|
888
|
+
return jsonResponse(res, 500, { error: e.message });
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
if (p === '/api/models/coding-catalog' && m === 'GET') {
|
|
893
|
+
if (!brainApi) return jsonResponse(res, 500, { error: 'Wall-E brain not available' });
|
|
894
|
+
try {
|
|
895
|
+
const warnings = [];
|
|
896
|
+
const providerId = url.searchParams.get('provider_id');
|
|
897
|
+
const includeLive = url.searchParams.get('live') === '1' || url.searchParams.get('live') === 'true';
|
|
898
|
+
const providers = brainApi.listModelProviders()
|
|
899
|
+
.filter((provider) => provider.enabled !== 0 && provider.enabled !== false)
|
|
900
|
+
.filter((provider) => !providerId || provider.id === providerId);
|
|
901
|
+
const providerRows = [];
|
|
902
|
+
const models = [];
|
|
903
|
+
for (const provider of providers) {
|
|
904
|
+
const modelRead = safeListModelsByProvider(brainApi, provider.id, warnings);
|
|
905
|
+
providerRows.push(getProviderWithMeta(brainApi, provider, {
|
|
906
|
+
warnings,
|
|
907
|
+
modelRead,
|
|
908
|
+
modelCatalogStatus: modelRead.ok ? 'ok' : 'partial',
|
|
909
|
+
modelCount: modelRead.models.length,
|
|
910
|
+
}));
|
|
911
|
+
for (const model of modelRead.models) {
|
|
912
|
+
models.push({
|
|
913
|
+
...model,
|
|
914
|
+
provider_name: provider.name,
|
|
915
|
+
provider_type: provider.type,
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
let liveAvailability = null;
|
|
920
|
+
if (includeLive && providers.length > 0) {
|
|
921
|
+
try {
|
|
922
|
+
const { createClient } = require('../llm/client');
|
|
923
|
+
const { listCodingModelAvailability } = require('../llm/coding-availability');
|
|
924
|
+
liveAvailability = await listCodingModelAvailability({
|
|
925
|
+
providers,
|
|
926
|
+
getProviderWithKey: (id) => brainApi.getModelProviderWithKey(id),
|
|
927
|
+
createClient,
|
|
928
|
+
});
|
|
929
|
+
} catch (err) {
|
|
930
|
+
addCatalogWarning(warnings, 'coding_catalog_live_probe', err);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
const { buildSessionModelCatalog } = require('../llm/model-catalog');
|
|
934
|
+
return jsonResponse(res, 200, {
|
|
935
|
+
...buildSessionModelCatalog({
|
|
936
|
+
providers: providerRows,
|
|
937
|
+
models,
|
|
938
|
+
liveAvailability,
|
|
939
|
+
}),
|
|
940
|
+
...catalogStatusPayload(warnings),
|
|
941
|
+
});
|
|
200
942
|
} catch (e) {
|
|
201
943
|
return jsonResponse(res, 500, { error: e.message });
|
|
202
944
|
}
|
|
@@ -270,6 +1012,32 @@ async function handleModelAdminApi(req, res, url) {
|
|
|
270
1012
|
}
|
|
271
1013
|
}
|
|
272
1014
|
|
|
1015
|
+
if (p === '/api/models/usage-ledger' && m === 'GET') {
|
|
1016
|
+
if (!brainApi) return jsonResponse(res, 500, { error: 'Wall-E brain not available' });
|
|
1017
|
+
try {
|
|
1018
|
+
const filters = {
|
|
1019
|
+
sessionId: url.searchParams.get('session_id') || url.searchParams.get('sessionId') || '',
|
|
1020
|
+
providerType: url.searchParams.get('provider') || url.searchParams.get('provider_type') || '',
|
|
1021
|
+
providerId: url.searchParams.get('provider_id') || '',
|
|
1022
|
+
modelId: url.searchParams.get('model') || url.searchParams.get('model_id') || '',
|
|
1023
|
+
modelRegistryId: url.searchParams.get('model_registry_id') || '',
|
|
1024
|
+
source: url.searchParams.get('source') || '',
|
|
1025
|
+
since: url.searchParams.get('since') || '',
|
|
1026
|
+
until: url.searchParams.get('until') || '',
|
|
1027
|
+
limit: url.searchParams.get('limit') || '',
|
|
1028
|
+
};
|
|
1029
|
+
const entries = typeof brainApi.listModelUsageLedger === 'function'
|
|
1030
|
+
? brainApi.listModelUsageLedger(filters)
|
|
1031
|
+
: [];
|
|
1032
|
+
const summary = typeof brainApi.summarizeModelUsageLedger === 'function'
|
|
1033
|
+
? brainApi.summarizeModelUsageLedger(filters)
|
|
1034
|
+
: [];
|
|
1035
|
+
return jsonResponse(res, 200, { entries, summary });
|
|
1036
|
+
} catch (e) {
|
|
1037
|
+
return jsonResponse(res, 500, { error: e.message });
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
273
1041
|
if (p === '/api/models/test-connection' && m === 'POST') {
|
|
274
1042
|
try {
|
|
275
1043
|
const body = await readSmallJsonBody(req);
|
|
@@ -512,13 +1280,21 @@ async function handleModelAdminApi(req, res, url) {
|
|
|
512
1280
|
if (p === '/api/models/detect-keys' && m === 'GET') {
|
|
513
1281
|
try {
|
|
514
1282
|
const detected = [];
|
|
515
|
-
const
|
|
1283
|
+
const envValues = await _readShellEnvValues([
|
|
1284
|
+
'ANTHROPIC_API_KEY',
|
|
1285
|
+
'OPENAI_API_KEY',
|
|
1286
|
+
'GOOGLE_API_KEY',
|
|
1287
|
+
'GEMINI_API_KEY',
|
|
1288
|
+
'MOONSHOT_API_KEY',
|
|
1289
|
+
'PORTKEY_API_KEY',
|
|
1290
|
+
], { ignoreCache: url.searchParams.get('refresh') === '1' });
|
|
1291
|
+
const anthropicKey = envValues.get('ANTHROPIC_API_KEY');
|
|
516
1292
|
if (anthropicKey) detected.push({ type: 'anthropic', name: 'Anthropic', source: 'ANTHROPIC_API_KEY', apiKeyMasked: anthropicKey.slice(0, 8) + '...' + anthropicKey.slice(-4) });
|
|
517
|
-
const openaiKey =
|
|
1293
|
+
const openaiKey = envValues.get('OPENAI_API_KEY');
|
|
518
1294
|
if (openaiKey) detected.push({ type: 'openai', name: 'OpenAI', source: 'OPENAI_API_KEY', apiKeyMasked: openaiKey.slice(0, 8) + '...' + openaiKey.slice(-4) });
|
|
519
|
-
const googleKey =
|
|
1295
|
+
const googleKey = envValues.get('GOOGLE_API_KEY') || envValues.get('GEMINI_API_KEY');
|
|
520
1296
|
if (googleKey) {
|
|
521
|
-
const source =
|
|
1297
|
+
const source = envValues.get('GOOGLE_API_KEY') ? 'GOOGLE_API_KEY' : 'GEMINI_API_KEY';
|
|
522
1298
|
detected.push({ type: 'google', name: 'Google Gemini', source, apiKeyMasked: googleKey.slice(0, 8) + '...' + googleKey.slice(-4) });
|
|
523
1299
|
} else {
|
|
524
1300
|
try {
|
|
@@ -531,14 +1307,32 @@ async function handleModelAdminApi(req, res, url) {
|
|
|
531
1307
|
}
|
|
532
1308
|
} catch {}
|
|
533
1309
|
}
|
|
534
|
-
const moonshotKey =
|
|
1310
|
+
const moonshotKey = envValues.get('MOONSHOT_API_KEY');
|
|
535
1311
|
if (moonshotKey) detected.push({ type: 'moonshot', name: 'Moonshot / Kimi', source: 'MOONSHOT_API_KEY', apiKeyMasked: moonshotKey.slice(0, 8) + '...' + moonshotKey.slice(-4) });
|
|
536
1312
|
if (await detectOllama()) {
|
|
537
1313
|
detected.push({ type: 'ollama', name: 'Ollama (Local)', source: 'localhost:11434', apiKeyMasked: 'n/a' });
|
|
538
1314
|
}
|
|
539
1315
|
const gateways = [];
|
|
540
|
-
const
|
|
541
|
-
if (
|
|
1316
|
+
const devboxPortkey = getDevboxClaudePortkeyGateway();
|
|
1317
|
+
if (devboxPortkey) {
|
|
1318
|
+
gateways.push({
|
|
1319
|
+
type: 'portkey',
|
|
1320
|
+
name: 'Portkey gateway',
|
|
1321
|
+
source: devboxPortkey.source,
|
|
1322
|
+
label: devboxPortkey.label,
|
|
1323
|
+
apiKeyMasked: devboxPortkey.apiKeyMasked,
|
|
1324
|
+
baseUrl: devboxPortkey.baseUrl,
|
|
1325
|
+
canAutoConfigure: true,
|
|
1326
|
+
hasProvider: devboxPortkey.hasProvider,
|
|
1327
|
+
hasVirtualKey: devboxPortkey.hasVirtualKey,
|
|
1328
|
+
hasConfig: devboxPortkey.hasConfig,
|
|
1329
|
+
hasMetadata: devboxPortkey.hasMetadata,
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
const portkeyKey = envValues.get('PORTKEY_API_KEY');
|
|
1333
|
+
if (portkeyKey && (!devboxPortkey || portkeyKey !== devboxPortkey.apiKey)) {
|
|
1334
|
+
gateways.push({ type: 'portkey', name: 'Portkey gateway', source: 'PORTKEY_API_KEY', apiKeyMasked: portkeyKey.slice(0, 8) + '...' + portkeyKey.slice(-4) });
|
|
1335
|
+
}
|
|
542
1336
|
const existingTypes = new Set((brainApi ? brainApi.listModelProviders() : []).map((provider) => provider.type));
|
|
543
1337
|
return jsonResponse(res, 200, { detected: detected.filter((item) => !existingTypes.has(item.type)), gateways });
|
|
544
1338
|
} catch (e) {
|
|
@@ -555,10 +1349,11 @@ async function handleModelAdminApi(req, res, url) {
|
|
|
555
1349
|
if (!type) return jsonResponse(res, 400, { error: 'Missing type' });
|
|
556
1350
|
|
|
557
1351
|
const keyMap = {
|
|
558
|
-
anthropic: () => getShellEnvValue('ANTHROPIC_API_KEY'),
|
|
559
|
-
openai: () => getShellEnvValue('OPENAI_API_KEY'),
|
|
560
|
-
google: () => {
|
|
561
|
-
const
|
|
1352
|
+
anthropic: () => getShellEnvValue('ANTHROPIC_API_KEY', { ignoreCache: true }),
|
|
1353
|
+
openai: () => getShellEnvValue('OPENAI_API_KEY', { ignoreCache: true }),
|
|
1354
|
+
google: async () => {
|
|
1355
|
+
const envValues = await _readShellEnvValues(['GOOGLE_API_KEY', 'GEMINI_API_KEY'], { ignoreCache: true });
|
|
1356
|
+
const envKey = envValues.get('GOOGLE_API_KEY') || envValues.get('GEMINI_API_KEY');
|
|
562
1357
|
if (envKey) return envKey;
|
|
563
1358
|
try {
|
|
564
1359
|
const creds = JSON.parse(fs.readFileSync(path.join(process.env.HOME, '.gemini', 'oauth_creds.json'), 'utf8'));
|
|
@@ -567,13 +1362,13 @@ async function handleModelAdminApi(req, res, url) {
|
|
|
567
1362
|
return null;
|
|
568
1363
|
}
|
|
569
1364
|
},
|
|
570
|
-
deepseek: () => getShellEnvValue('DEEPSEEK_API_KEY'),
|
|
571
|
-
moonshot: () => getShellEnvValue('MOONSHOT_API_KEY'),
|
|
1365
|
+
deepseek: () => getShellEnvValue('DEEPSEEK_API_KEY', { ignoreCache: true }),
|
|
1366
|
+
moonshot: () => getShellEnvValue('MOONSHOT_API_KEY', { ignoreCache: true }),
|
|
572
1367
|
ollama: () => null,
|
|
573
1368
|
};
|
|
574
1369
|
|
|
575
1370
|
const nameMap = { anthropic: 'Anthropic', openai: 'OpenAI', google: 'Google Gemini', deepseek: 'DeepSeek', moonshot: 'Moonshot / Kimi', ollama: 'Ollama (Local)' };
|
|
576
|
-
const apiKey = keyMap[type] ? keyMap[type]() : null;
|
|
1371
|
+
const apiKey = keyMap[type] ? await keyMap[type]() : null;
|
|
577
1372
|
if (!apiKey && type !== 'ollama') return jsonResponse(res, 400, { error: `No API key found in environment for ${type}` });
|
|
578
1373
|
|
|
579
1374
|
const existingProviders = brainApi.listModelProviders().filter((provider) => provider.type === type);
|
|
@@ -625,4 +1420,11 @@ async function handleModelAdminApi(req, res, url) {
|
|
|
625
1420
|
|
|
626
1421
|
module.exports = {
|
|
627
1422
|
handleModelAdminApi,
|
|
1423
|
+
handleLocalModelOwnerWrite,
|
|
1424
|
+
_test: {
|
|
1425
|
+
_readShellEnvValues,
|
|
1426
|
+
_shellEnvLookupScript,
|
|
1427
|
+
clearShellEnvCache() { _shellEnvCache.clear(); _shellEnvInflight.clear(); },
|
|
1428
|
+
setShellEnvExecFile(fn) { _shellEnvExecFile = typeof fn === 'function' ? fn : execFile; },
|
|
1429
|
+
},
|
|
628
1430
|
};
|