@geminilight/mindos 0.6.63 → 0.6.65
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/_standalone/.mindos-build-version +1 -1
- package/_standalone/.next/BUILD_ID +1 -1
- package/_standalone/.next/app-path-routes-manifest.json +21 -21
- package/_standalone/.next/build-manifest.json +2 -2
- package/_standalone/.next/cache/.previewinfo +1 -1
- package/_standalone/.next/cache/.rscinfo +1 -1
- package/_standalone/.next/cache/config.json +3 -3
- package/_standalone/.next/prerender-manifest.json +3 -3
- package/_standalone/.next/server/app/.well-known/agent-card.json/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/_global-error.html +2 -2
- package/_standalone/.next/server/app/_global-error.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/_standalone/.next/server/app/_not-found/page.js +1 -1
- package/_standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/agents/[agentKey]/page.js +1 -1
- package/_standalone/.next/server/app/agents/[agentKey]/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/agents/[agentKey]/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/agents/page.js +1 -1
- package/_standalone/.next/server/app/agents/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/agents/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/agents/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/delegations/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/discover/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/config/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/detect/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/install/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/registry/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/session/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/agent-activity/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/agents/copy-skill/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/agents/copy-skill/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/agents/custom/detect/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/agents/custom/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/ask/route.js +48 -42
- package/_standalone/.next/server/app/api/ask/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/ask/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/ask-sessions/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/auth/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/backlinks/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/backlinks/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/bootstrap/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/bootstrap/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/changes/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/changes/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/export/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/export/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/extract-pdf/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/file/import/route.js +1 -1
- package/_standalone/.next/server/app/api/file/import/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/file/import/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/file/raw/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/file/raw/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/file/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/file/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/git/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/graph/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/graph/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/inbox/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/inbox/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/init/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/init/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/agents/route.js +1 -1
- package/_standalone/.next/server/app/api/mcp/agents/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/mcp/agents/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/install/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/install-skill/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/restart/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/status/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/uninstall/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/monitoring/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/monitoring/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/recent-files/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/recent-files/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/restart/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/search/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/list-models/route.js +1 -1
- package/_standalone/.next/server/app/api/settings/list-models/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/reset-token/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/route.js +1 -1
- package/_standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/settings/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/test-key/route.js +1 -1
- package/_standalone/.next/server/app/api/settings/test-key/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/check-path/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/check-port/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/generate-token/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/ls/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/route.js +1 -1
- package/_standalone/.next/server/app/api/setup/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/skills/route.js +1 -1
- package/_standalone/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/sync/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/tree-version/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/tree-version/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/uninstall/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/update-check/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/update-status/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/workflows/route.js.nft.json +1 -1
- package/_standalone/.next/server/app/api/workflows/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/changes/page.js +1 -1
- package/_standalone/.next/server/app/changes/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/changes/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/echo/[segment]/page.js +2 -2
- package/_standalone/.next/server/app/echo/[segment]/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/echo/[segment]/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/echo/page.js +1 -1
- package/_standalone/.next/server/app/echo/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/echo/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/explore/page.js +1 -1
- package/_standalone/.next/server/app/explore/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/explore/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/help/page.js +1 -1
- package/_standalone/.next/server/app/help/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/help/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/inbox/history/page.js +1 -1
- package/_standalone/.next/server/app/inbox/history/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/inbox/history/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/login/page.js +1 -1
- package/_standalone/.next/server/app/login/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/page.js +1 -1
- package/_standalone/.next/server/app/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/setup/page.js +2 -2
- package/_standalone/.next/server/app/setup/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/trash/page.js +3 -3
- package/_standalone/.next/server/app/trash/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/trash/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/view/[...path]/page.js +2 -2
- package/_standalone/.next/server/app/view/[...path]/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/view/[...path]/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/wiki/page.js +1 -1
- package/_standalone/.next/server/app/wiki/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/wiki/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app-paths-manifest.json +21 -21
- package/_standalone/.next/server/chunks/122.js +222 -0
- package/_standalone/.next/server/chunks/3113.js +52 -0
- package/_standalone/.next/server/chunks/6539.js +1 -1
- package/_standalone/.next/server/chunks/8388.js +2 -2
- package/_standalone/.next/server/chunks/953.js +3 -3
- package/_standalone/.next/server/chunks/9787.js +2 -0
- package/_standalone/.next/server/pages/500.html +2 -2
- package/_standalone/.next/server/server-reference-manifest.js +1 -1
- package/_standalone/.next/server/server-reference-manifest.json +1 -1
- package/_standalone/.next/static/chunks/1001-99da82ec8d8c136f.js +1 -0
- package/_standalone/.next/static/chunks/5149-4d828886dda479fa.js +1 -0
- package/_standalone/.next/static/chunks/{5581-82e5db227f8e9393.js → 5581-c671163a2fe1b312.js} +2 -2
- package/_standalone/.next/static/chunks/6636-53238eff89503f03.js +6 -0
- package/_standalone/.next/static/chunks/6757-1c1a89720fdda8f0.js +1 -0
- package/_standalone/.next/static/chunks/7129-20e9d2463a9da646.js +1 -0
- package/_standalone/.next/static/chunks/{3674-be69a8b858ceacdd.js → 7294-cac25d97869afadc.js} +1 -1
- package/_standalone/.next/static/chunks/8225-21e5cebc3731ddf0.js +1 -0
- package/_standalone/.next/static/chunks/8520-b51810e66293ceb8.js +22 -0
- package/_standalone/.next/static/chunks/9207-dc9c31b351a2ed78.js +1 -0
- package/_standalone/.next/static/chunks/app/agents/[agentKey]/{page-b0dabe793500383d.js → page-2f5cf97e03dc1cc9.js} +1 -1
- package/_standalone/.next/static/chunks/app/agents/{page-1f1ac330c8177cf6.js → page-50eac58d511dcc6e.js} +1 -1
- package/_standalone/.next/static/chunks/app/echo/[segment]/page-2a00f4686adf3885.js +11 -0
- package/_standalone/.next/static/chunks/app/{layout-50a6b1164ee98ab9.js → layout-2cb7a6602d2e5d5f.js} +62 -58
- package/_standalone/.next/static/chunks/app/{page-73802bd31d7f6c9f.js → page-5ab911b2226f6ff7.js} +1 -1
- package/_standalone/.next/static/chunks/app/setup/page-907b7c57fad2292b.js +1 -0
- package/_standalone/.next/static/chunks/app/trash/page-11a511b065ea84c2.js +1 -0
- package/_standalone/.next/static/chunks/app/view/[...path]/{page-808f39963bf04715.js → page-26e47dd4c533a58c.js} +2 -2
- package/_standalone/.next/static/css/67e7918f5ed7d147.css +1 -0
- package/_standalone/.next/trace +65 -65
- package/_standalone/__tests__/api/ask-attachments.test.ts +194 -0
- package/_standalone/__tests__/api/settings.test.ts +16 -12
- package/_standalone/__tests__/api/setup.test.ts +11 -9
- package/_standalone/__tests__/api/test-key.test.ts +0 -10
- package/_standalone/__tests__/components/UpdateToast.test.ts +344 -0
- package/_standalone/__tests__/core/context.test.ts +48 -426
- package/_standalone/__tests__/lib/pi-skills.test.ts +4 -4
- package/_standalone/__tests__/lib/settings-ai-client.test.ts +32 -12
- package/_standalone/__tests__/setup.ts +5 -5
- package/_standalone/components/ask/AskContent.tsx +70 -40
- package/_standalone/components/ask/AskHeader.tsx +8 -1
- package/_standalone/components/ask/MessageList.tsx +37 -3
- package/_standalone/components/ask/ProviderModelCapsule.tsx +51 -129
- package/_standalone/components/settings/AiTab.tsx +270 -347
- package/_standalone/components/settings/CustomProviderFields.tsx +121 -0
- package/_standalone/components/settings/CustomProvidersCard.tsx +2 -2
- package/_standalone/components/settings/KnowledgeTab.tsx +6 -20
- package/_standalone/components/settings/McpAgentInstall.tsx +7 -2
- package/_standalone/components/settings/Primitives.tsx +48 -104
- package/_standalone/components/settings/ProviderModal.tsx +38 -221
- package/_standalone/components/settings/SettingsContent.tsx +5 -12
- package/_standalone/components/settings/TestButton.tsx +64 -0
- package/_standalone/components/settings/types.ts +3 -12
- package/_standalone/components/settings/useCustomProviderForm.ts +132 -0
- package/_standalone/components/setup/StepAI.tsx +3 -3
- package/_standalone/components/shared/ModelInput.tsx +18 -4
- package/_standalone/components/shared/ProviderSelect.tsx +126 -134
- package/_standalone/hooks/useAskChat.ts +97 -13
- package/_standalone/hooks/useAskPanel.ts +17 -1
- package/_standalone/lib/settings-ai-client.ts +17 -8
- package/_standalone/tsconfig.tsbuildinfo +1 -1
- package/app/app/api/ask/route.ts +124 -44
- package/app/app/api/mcp/agents/route.ts +3 -3
- package/app/app/api/settings/list-models/route.ts +15 -26
- package/app/app/api/settings/route.ts +14 -59
- package/app/app/api/settings/test-key/route.ts +47 -12
- package/app/app/api/setup/route.ts +36 -18
- package/app/app/api/skills/route.ts +1 -1
- package/app/app/layout.tsx +5 -3
- package/app/components/HomeContent.tsx +11 -0
- package/app/components/UpdateToast.tsx +255 -0
- package/app/components/ask/AskContent.tsx +70 -40
- package/app/components/ask/AskHeader.tsx +8 -1
- package/app/components/ask/MessageList.tsx +37 -3
- package/app/components/ask/ProviderModelCapsule.tsx +51 -129
- package/app/components/settings/AiTab.tsx +270 -347
- package/app/components/settings/CustomProviderFields.tsx +121 -0
- package/app/components/settings/CustomProvidersCard.tsx +2 -2
- package/app/components/settings/KnowledgeTab.tsx +6 -20
- package/app/components/settings/McpAgentInstall.tsx +7 -2
- package/app/components/settings/Primitives.tsx +48 -104
- package/app/components/settings/ProviderModal.tsx +38 -221
- package/app/components/settings/SettingsContent.tsx +5 -12
- package/app/components/settings/TestButton.tsx +64 -0
- package/app/components/settings/types.ts +3 -12
- package/app/components/settings/useCustomProviderForm.ts +132 -0
- package/app/components/setup/StepAI.tsx +3 -3
- package/app/components/shared/ModelInput.tsx +18 -4
- package/app/components/shared/ProviderSelect.tsx +126 -134
- package/app/hooks/useAskChat.ts +97 -13
- package/app/hooks/useAskPanel.ts +17 -1
- package/app/lib/agent/context.ts +65 -0
- package/app/lib/agent/providers.ts +25 -0
- package/app/lib/agent/tools.ts +1 -1
- package/app/lib/custom-endpoints.ts +129 -29
- package/app/lib/i18n/modules/settings.ts +20 -0
- package/app/lib/pi-integration/skills.ts +16 -4
- package/app/lib/settings-ai-client.ts +17 -8
- package/app/lib/settings.ts +64 -90
- package/app/lib/types.ts +4 -0
- package/package.json +1 -1
- package/_standalone/.next/server/chunks/530.js +0 -218
- package/_standalone/.next/server/chunks/9007.js +0 -2
- package/_standalone/.next/server/chunks/9137.js +0 -52
- package/_standalone/.next/static/chunks/1309-373ade1b40aea186.js +0 -1
- package/_standalone/.next/static/chunks/3165-9189a38fd9ebf6f2.js +0 -1
- package/_standalone/.next/static/chunks/4587-5d06728133fff222.js +0 -1
- package/_standalone/.next/static/chunks/6261-5ce86db54b19ae46.js +0 -1
- package/_standalone/.next/static/chunks/6636-9bbc90fb3b8731fe.js +0 -6
- package/_standalone/.next/static/chunks/7637-904b0a381dc3ec02.js +0 -1
- package/_standalone/.next/static/chunks/8520-84e607f33c409f91.js +0 -22
- package/_standalone/.next/static/chunks/9207-9a4a1a1ede4f8e6e.js +0 -1
- package/_standalone/.next/static/chunks/app/echo/[segment]/page-bc5e104eb7ae6327.js +0 -11
- package/_standalone/.next/static/chunks/app/setup/page-79acb0baf38184c6.js +0 -1
- package/_standalone/.next/static/chunks/app/trash/page-d040db56863da504.js +0 -1
- package/_standalone/.next/static/css/1287672978833d07.css +0 -1
- package/_standalone/lib/agent/context.ts +0 -403
- /package/_standalone/.next/static/{X86rF8dKEO0InosOw4a2_ → eIlwbGas1iRGonlPyEwj7}/_buildManifest.js +0 -0
- /package/_standalone/.next/static/{X86rF8dKEO0InosOw4a2_ → eIlwbGas1iRGonlPyEwj7}/_ssgManifest.js +0 -0
package/app/app/api/ask/route.ts
CHANGED
|
@@ -24,6 +24,7 @@ import { isProviderId, type ProviderId, toPiProvider } from '@/lib/agent/provide
|
|
|
24
24
|
import { getRequestScopedTools, getOrganizeTools, getChatTools, WRITE_TOOLS, truncate } from '@/lib/agent/tools';
|
|
25
25
|
import { isCustomProviderId, findCustomProvider } from '@/lib/custom-endpoints';
|
|
26
26
|
import { AGENT_SYSTEM_PROMPT, ORGANIZE_SYSTEM_PROMPT, CHAT_SYSTEM_PROMPT } from '@/lib/agent/prompt';
|
|
27
|
+
import { estimateStringTokens, getOllamaContextWindow } from '@/lib/agent/context';
|
|
27
28
|
import type { AskModeApi } from '@/lib/types';
|
|
28
29
|
import { toAgentMessages } from '@/lib/agent/to-agent-messages';
|
|
29
30
|
import { logAgentOp } from '@/lib/agent/log';
|
|
@@ -39,6 +40,48 @@ import type { Message as FrontendMessage } from '@/lib/types';
|
|
|
39
40
|
|
|
40
41
|
const MAX_DIR_FILES = 30;
|
|
41
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Load attached and current files into context parts for the system prompt.
|
|
45
|
+
* Returns the context parts array and a list of file paths that failed to load.
|
|
46
|
+
* Deduplicates files and logs failures with the given mode label.
|
|
47
|
+
*/
|
|
48
|
+
function loadAttachedFileContext(
|
|
49
|
+
attachedFiles: string[] | undefined,
|
|
50
|
+
currentFile: string | undefined,
|
|
51
|
+
mode: string,
|
|
52
|
+
): { contextParts: string[]; failedFiles: string[] } {
|
|
53
|
+
const contextParts: string[] = [];
|
|
54
|
+
const failedFiles: string[] = [];
|
|
55
|
+
const seen = new Set<string>();
|
|
56
|
+
|
|
57
|
+
if (Array.isArray(attachedFiles) && attachedFiles.length > 0) {
|
|
58
|
+
for (const filePath of attachedFiles) {
|
|
59
|
+
if (seen.has(filePath)) continue;
|
|
60
|
+
seen.add(filePath);
|
|
61
|
+
try {
|
|
62
|
+
const content = truncate(getFileContent(filePath));
|
|
63
|
+
contextParts.push(`## Attached: ${filePath}\n\n${content}`);
|
|
64
|
+
} catch (err) {
|
|
65
|
+
console.warn(`[ask] ${mode}: failed to read attached file "${filePath}":`, err instanceof Error ? err.message : err);
|
|
66
|
+
failedFiles.push(filePath);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (currentFile && !seen.has(currentFile)) {
|
|
72
|
+
seen.add(currentFile);
|
|
73
|
+
try {
|
|
74
|
+
const content = truncate(getFileContent(currentFile));
|
|
75
|
+
contextParts.push(`## Current file: ${currentFile}\n\n${content}`);
|
|
76
|
+
} catch (err) {
|
|
77
|
+
console.warn(`[ask] ${mode}: failed to read currentFile "${currentFile}":`, err instanceof Error ? err.message : err);
|
|
78
|
+
failedFiles.push(currentFile);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { contextParts, failedFiles };
|
|
83
|
+
}
|
|
84
|
+
|
|
42
85
|
/** Expand attachedFiles entries: directory paths (trailing /) become individual file paths. */
|
|
43
86
|
function expandAttachedFiles(raw: string[]): string[] {
|
|
44
87
|
const result: string[] = [];
|
|
@@ -722,6 +765,11 @@ export async function POST(req: NextRequest) {
|
|
|
722
765
|
: body.mode === 'chat' ? 'chat'
|
|
723
766
|
: 'agent';
|
|
724
767
|
|
|
768
|
+
// Diagnostic: log attached files so silent failures are visible
|
|
769
|
+
if (Array.isArray(attachedFiles) && attachedFiles.length > 0) {
|
|
770
|
+
console.log(`[ask] mode=${askMode} attachedFiles=${JSON.stringify(attachedFiles)} currentFile=${currentFile ?? 'none'}`);
|
|
771
|
+
}
|
|
772
|
+
|
|
725
773
|
// Read agent config from settings
|
|
726
774
|
const serverSettings = readSettings();
|
|
727
775
|
const agentConfig = serverSettings.agent ?? {};
|
|
@@ -758,7 +806,7 @@ export async function POST(req: NextRequest) {
|
|
|
758
806
|
let systemPrompt: string;
|
|
759
807
|
|
|
760
808
|
if (askMode === 'organize') {
|
|
761
|
-
// Organize mode: minimal prompt — only KB structure + uploaded files
|
|
809
|
+
// Organize mode: minimal prompt — only KB structure + attached/uploaded files
|
|
762
810
|
const promptParts: string[] = [ORGANIZE_SYSTEM_PROMPT];
|
|
763
811
|
|
|
764
812
|
promptParts.push(`---\n\nmind_root=${getMindRoot()}`);
|
|
@@ -769,6 +817,15 @@ export async function POST(req: NextRequest) {
|
|
|
769
817
|
promptParts.push(`---\n\n## Knowledge Base Structure\n\n${bootstrapIndex.content}`);
|
|
770
818
|
}
|
|
771
819
|
|
|
820
|
+
// Include attached KB files (@ mentions) — same pattern as chat/agent modes
|
|
821
|
+
const { contextParts, failedFiles } = loadAttachedFileContext(attachedFiles, currentFile, 'organize');
|
|
822
|
+
if (contextParts.length > 0) {
|
|
823
|
+
promptParts.push(`---\n\nThe user is currently viewing these files:\n\n${contextParts.join('\n\n---\n\n')}`);
|
|
824
|
+
}
|
|
825
|
+
if (failedFiles.length > 0) {
|
|
826
|
+
promptParts.push(`---\n\n⚠️ The following attached files could not be read: ${failedFiles.join(', ')}. Inform the user that these files were not loaded.`);
|
|
827
|
+
}
|
|
828
|
+
|
|
772
829
|
if (uploadedParts.length > 0) {
|
|
773
830
|
promptParts.push(
|
|
774
831
|
`---\n\n## ⚠️ USER-UPLOADED FILES\n\n` +
|
|
@@ -794,28 +851,13 @@ export async function POST(req: NextRequest) {
|
|
|
794
851
|
const now = new Date();
|
|
795
852
|
promptParts.push(`---\n\n## Current Time Context\n- Current UTC Time: ${now.toISOString()}\n- System Local Time: ${new Intl.DateTimeFormat('en-US', { dateStyle: 'full', timeStyle: 'long' }).format(now)}`);
|
|
796
853
|
|
|
797
|
-
const contextParts
|
|
798
|
-
const seen = new Set<string>();
|
|
799
|
-
if (Array.isArray(attachedFiles) && attachedFiles.length > 0) {
|
|
800
|
-
for (const filePath of attachedFiles!) {
|
|
801
|
-
if (seen.has(filePath)) continue;
|
|
802
|
-
seen.add(filePath);
|
|
803
|
-
try {
|
|
804
|
-
const content = truncate(getFileContent(filePath));
|
|
805
|
-
contextParts.push(`## Attached: ${filePath}\n\n${content}`);
|
|
806
|
-
} catch { /* ignore missing files */ }
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
if (currentFile && !seen.has(currentFile)) {
|
|
810
|
-
seen.add(currentFile);
|
|
811
|
-
try {
|
|
812
|
-
const content = truncate(getFileContent(currentFile));
|
|
813
|
-
contextParts.push(`## Current file: ${currentFile}\n\n${content}`);
|
|
814
|
-
} catch { /* ignore */ }
|
|
815
|
-
}
|
|
854
|
+
const { contextParts, failedFiles } = loadAttachedFileContext(attachedFiles, currentFile, 'chat');
|
|
816
855
|
if (contextParts.length > 0) {
|
|
817
856
|
promptParts.push(`---\n\nThe user is currently viewing these files:\n\n${contextParts.join('\n\n---\n\n')}`);
|
|
818
857
|
}
|
|
858
|
+
if (failedFiles.length > 0) {
|
|
859
|
+
promptParts.push(`---\n\n⚠️ The following attached files could not be read: ${failedFiles.join(', ')}. Inform the user that these files were not loaded.`);
|
|
860
|
+
}
|
|
819
861
|
|
|
820
862
|
if (uploadedParts.length > 0) {
|
|
821
863
|
promptParts.push(
|
|
@@ -927,28 +969,7 @@ export async function POST(req: NextRequest) {
|
|
|
927
969
|
if (bootstrap.target_config_json?.ok) initContextBlocks.push(`## bootstrap_target_config_json\n\n${bootstrap.target_config_json.content}`);
|
|
928
970
|
|
|
929
971
|
// Build initial context from attached/current files
|
|
930
|
-
const contextParts
|
|
931
|
-
const seen = new Set<string>();
|
|
932
|
-
const hasAttached = Array.isArray(attachedFiles) && attachedFiles.length > 0;
|
|
933
|
-
|
|
934
|
-
if (hasAttached) {
|
|
935
|
-
for (const filePath of attachedFiles!) {
|
|
936
|
-
if (seen.has(filePath)) continue;
|
|
937
|
-
seen.add(filePath);
|
|
938
|
-
try {
|
|
939
|
-
const content = truncate(getFileContent(filePath));
|
|
940
|
-
contextParts.push(`## Attached: ${filePath}\n\n${content}`);
|
|
941
|
-
} catch { /* ignore missing files */ }
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
if (currentFile && !seen.has(currentFile)) {
|
|
946
|
-
seen.add(currentFile);
|
|
947
|
-
try {
|
|
948
|
-
const content = truncate(getFileContent(currentFile));
|
|
949
|
-
contextParts.push(`## Current file: ${currentFile}\n\n${content}`);
|
|
950
|
-
} catch { /* ignore */ }
|
|
951
|
-
}
|
|
972
|
+
const { contextParts, failedFiles } = loadAttachedFileContext(attachedFiles, currentFile, 'agent');
|
|
952
973
|
|
|
953
974
|
const now = new Date();
|
|
954
975
|
const timeContext = `## Current Time Context
|
|
@@ -973,6 +994,9 @@ export async function POST(req: NextRequest) {
|
|
|
973
994
|
if (contextParts.length > 0) {
|
|
974
995
|
promptParts.push(`---\n\nThe user is currently viewing these files:\n\n${contextParts.join('\n\n---\n\n')}`);
|
|
975
996
|
}
|
|
997
|
+
if (failedFiles.length > 0) {
|
|
998
|
+
promptParts.push(`---\n\n⚠️ The following attached files could not be read: ${failedFiles.join(', ')}. Inform the user that these files were not loaded.`);
|
|
999
|
+
}
|
|
976
1000
|
|
|
977
1001
|
if (uploadedParts.length > 0) {
|
|
978
1002
|
promptParts.push(
|
|
@@ -987,6 +1011,9 @@ export async function POST(req: NextRequest) {
|
|
|
987
1011
|
systemPrompt = promptParts.join('\n\n');
|
|
988
1012
|
}
|
|
989
1013
|
|
|
1014
|
+
// Log system prompt size for diagnosing context truncation issues (e.g. Ollama)
|
|
1015
|
+
console.log(`[ask] mode=${askMode} systemPrompt=${systemPrompt.length} chars (~${Math.ceil(systemPrompt.length / 4)} tokens)`);
|
|
1016
|
+
|
|
990
1017
|
try {
|
|
991
1018
|
let provOverride: ProviderId | undefined;
|
|
992
1019
|
let customProviderConfig: { apiKey: string; model: string; baseUrl: string } | undefined;
|
|
@@ -995,11 +1022,11 @@ export async function POST(req: NextRequest) {
|
|
|
995
1022
|
if (body.providerOverride) {
|
|
996
1023
|
if (isCustomProviderId(body.providerOverride)) {
|
|
997
1024
|
const settings = readSettings();
|
|
998
|
-
const customProvider = findCustomProvider(settings.
|
|
1025
|
+
const customProvider = findCustomProvider(settings.ai.providers ?? [], body.providerOverride);
|
|
999
1026
|
if (!customProvider) {
|
|
1000
1027
|
return apiError(ErrorCodes.INVALID_REQUEST, 'Custom provider not found', 400);
|
|
1001
1028
|
}
|
|
1002
|
-
provOverride = customProvider.
|
|
1029
|
+
provOverride = customProvider.protocol;
|
|
1003
1030
|
customProviderConfig = {
|
|
1004
1031
|
apiKey: customProvider.apiKey,
|
|
1005
1032
|
model: customProvider.model,
|
|
@@ -1022,6 +1049,59 @@ export async function POST(req: NextRequest) {
|
|
|
1022
1049
|
hasImages: hasImages(messages),
|
|
1023
1050
|
});
|
|
1024
1051
|
|
|
1052
|
+
// ── Ollama context window guard ──
|
|
1053
|
+
// Ollama silently truncates input that exceeds the model's actual context window.
|
|
1054
|
+
// Detect this and compact the system prompt to prevent attached files from being dropped.
|
|
1055
|
+
if (provider === 'ollama') {
|
|
1056
|
+
const ollamaBase = baseUrl || 'http://localhost:11434/v1';
|
|
1057
|
+
const actualCtx = await getOllamaContextWindow(ollamaBase, modelName);
|
|
1058
|
+
const promptTokens = estimateStringTokens(systemPrompt);
|
|
1059
|
+
// Reserve ~30% of context for conversation history + model output
|
|
1060
|
+
const maxPromptTokens = actualCtx ? Math.floor(actualCtx * 0.7) : undefined;
|
|
1061
|
+
|
|
1062
|
+
if (actualCtx) {
|
|
1063
|
+
console.log(`[ask] Ollama model="${modelName}" context=${actualCtx} promptTokens=${promptTokens} maxPromptTokens=${maxPromptTokens}`);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
if (maxPromptTokens && promptTokens > maxPromptTokens) {
|
|
1067
|
+
console.warn(`[ask] Ollama context overflow: prompt ${promptTokens} tokens > ${maxPromptTokens} max (${actualCtx} ctx). Compacting...`);
|
|
1068
|
+
// Compact by progressively stripping lower-priority sections from system prompt.
|
|
1069
|
+
// Priority order (keep first, strip last):
|
|
1070
|
+
// 1. Core system prompt (AGENT/CHAT/ORGANIZE base) — must keep
|
|
1071
|
+
// 2. Attached/current file content — user explicitly requested these
|
|
1072
|
+
// 3. KB structure (README.md) — important for navigation
|
|
1073
|
+
// 4. Time context — low priority
|
|
1074
|
+
// 5. SKILL.md + write-supplement — largest sections, can be stripped
|
|
1075
|
+
// 6. bootstrap INSTRUCTION/CONFIG — can be stripped for local models
|
|
1076
|
+
|
|
1077
|
+
// Strategy: strip sections between "---" delimiters from the end,
|
|
1078
|
+
// but preserve sections containing "Attached:" or "Current file:" or "USER-UPLOADED"
|
|
1079
|
+
const sections = systemPrompt.split('\n\n---\n\n');
|
|
1080
|
+
const preserved: string[] = [];
|
|
1081
|
+
let currentTokens = 0;
|
|
1082
|
+
|
|
1083
|
+
for (const section of sections) {
|
|
1084
|
+
const sectionTokens = estimateStringTokens(section);
|
|
1085
|
+
const isAttachment = section.includes('## Attached:') || section.includes('## Current file:') || section.includes('USER-UPLOADED');
|
|
1086
|
+
const isCore = preserved.length === 0; // first section = base system prompt
|
|
1087
|
+
|
|
1088
|
+
if (isCore || isAttachment) {
|
|
1089
|
+
// Always keep core prompt and user attachments
|
|
1090
|
+
preserved.push(section);
|
|
1091
|
+
currentTokens += sectionTokens;
|
|
1092
|
+
} else if (currentTokens + sectionTokens <= maxPromptTokens) {
|
|
1093
|
+
preserved.push(section);
|
|
1094
|
+
currentTokens += sectionTokens;
|
|
1095
|
+
} else {
|
|
1096
|
+
console.log(`[ask] Ollama compact: stripping section (${sectionTokens} tokens): ${section.slice(0, 80)}...`);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
systemPrompt = preserved.join('\n\n---\n\n');
|
|
1101
|
+
console.log(`[ask] Ollama compacted: ${promptTokens} → ${estimateStringTokens(systemPrompt)} tokens`);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1025
1105
|
// Convert frontend messages to AgentMessage[]
|
|
1026
1106
|
const agentMessages = toAgentMessages(messages);
|
|
1027
1107
|
|
|
@@ -20,7 +20,7 @@ import { readSettings } from '@/lib/settings';
|
|
|
20
20
|
import { scanSkillDirs } from '@/lib/pi-integration/skills';
|
|
21
21
|
import { getMindRoot } from '@/lib/fs';
|
|
22
22
|
|
|
23
|
-
function enrichMindOsAgent(agent: Record<string, unknown>) {
|
|
23
|
+
async function enrichMindOsAgent(agent: Record<string, unknown>) {
|
|
24
24
|
agent.present = true;
|
|
25
25
|
agent.installed = true;
|
|
26
26
|
agent.scope = 'builtin';
|
|
@@ -35,7 +35,7 @@ function enrichMindOsAgent(agent: Record<string, unknown>) {
|
|
|
35
35
|
|
|
36
36
|
try {
|
|
37
37
|
const projectRoot = process.env.MINDOS_PROJECT_ROOT || path.resolve(process.cwd(), '..');
|
|
38
|
-
const skills = scanSkillDirs({ projectRoot, mindRoot: getMindRoot() });
|
|
38
|
+
const skills = await scanSkillDirs({ projectRoot, mindRoot: getMindRoot() });
|
|
39
39
|
const enabledSkills = skills.filter(s => s.enabled);
|
|
40
40
|
agent.installedSkillNames = enabledSkills.map(s => s.name);
|
|
41
41
|
agent.installedSkillCount = enabledSkills.length;
|
|
@@ -217,7 +217,7 @@ export async function GET() {
|
|
|
217
217
|
});
|
|
218
218
|
|
|
219
219
|
const mindos = agents.find(a => a.key === 'mindos');
|
|
220
|
-
if (mindos) enrichMindOsAgent(mindos as unknown as Record<string, unknown>);
|
|
220
|
+
if (mindos) await enrichMindOsAgent(mindos as unknown as Record<string, unknown>);
|
|
221
221
|
|
|
222
222
|
// Runtime verification: for agents marked as installed with HTTP endpoint,
|
|
223
223
|
// verify endpoint is reachable (1s timeout to avoid blocking)
|
|
@@ -1,45 +1,34 @@
|
|
|
1
1
|
export const dynamic = 'force-dynamic';
|
|
2
2
|
import { NextRequest, NextResponse } from 'next/server';
|
|
3
3
|
import { getModels as piGetModels } from '@mariozechner/pi-ai';
|
|
4
|
-
import { effectiveAiConfig } from '@/lib/settings';
|
|
4
|
+
import { effectiveAiConfig, readSettings } from '@/lib/settings';
|
|
5
5
|
import { type ProviderId, isProviderId, PROVIDER_PRESETS, toPiProvider, getDefaultBaseUrl } from '@/lib/agent/providers';
|
|
6
|
-
import {
|
|
6
|
+
import { isProviderEntryId, findProvider } from '@/lib/custom-endpoints';
|
|
7
7
|
|
|
8
8
|
const TIMEOUT = 10_000;
|
|
9
9
|
|
|
10
10
|
export async function POST(req: NextRequest) {
|
|
11
11
|
try {
|
|
12
12
|
const body = await req.json();
|
|
13
|
-
const { provider,
|
|
13
|
+
const { provider, apiKey, baseUrl } = body as {
|
|
14
14
|
provider?: string;
|
|
15
|
-
customProviderId?: string;
|
|
16
15
|
apiKey?: string;
|
|
17
16
|
baseUrl?: string;
|
|
18
17
|
};
|
|
19
18
|
|
|
20
|
-
// Handle
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
// Handle provider entry ID (p_*) — look up from unified providers list
|
|
20
|
+
if (provider && isProviderEntryId(provider)) {
|
|
21
|
+
const settings = readSettings();
|
|
22
|
+
const entry = findProvider(settings.ai.providers, provider);
|
|
23
|
+
if (!entry) {
|
|
24
|
+
return NextResponse.json({ ok: false, error: 'Provider not found' }, { status: 404 });
|
|
24
25
|
}
|
|
25
|
-
|
|
26
|
-
// Fetch custom provider from settings
|
|
27
|
-
const settings = await fetch(new URL('/api/settings', req.url), {
|
|
28
|
-
headers: req.headers,
|
|
29
|
-
}).then(r => r.json() as Promise<any>);
|
|
30
|
-
|
|
31
|
-
const customProviders = parseCustomProviders(settings.customProviders);
|
|
32
|
-
const cp = customProviders.find(p => p.id === customProviderId);
|
|
33
|
-
if (!cp) {
|
|
34
|
-
return NextResponse.json({ ok: false, error: 'Custom provider not found' }, { status: 404 });
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Use the custom provider's base provider type to fetch models
|
|
26
|
+
|
|
38
27
|
const ctrl = new AbortController();
|
|
39
28
|
const timer = setTimeout(() => ctrl.abort(), TIMEOUT);
|
|
40
|
-
|
|
29
|
+
|
|
41
30
|
try {
|
|
42
|
-
const models = await fetchModels(
|
|
31
|
+
const models = await fetchModels(entry.protocol, apiKey || entry.apiKey, baseUrl || entry.baseUrl, ctrl.signal);
|
|
43
32
|
return NextResponse.json({ ok: true, models });
|
|
44
33
|
} catch (e: unknown) {
|
|
45
34
|
if (e instanceof Error && e.name === 'AbortError') {
|
|
@@ -51,7 +40,7 @@ export async function POST(req: NextRequest) {
|
|
|
51
40
|
}
|
|
52
41
|
}
|
|
53
42
|
|
|
54
|
-
// Handle built-in
|
|
43
|
+
// Handle built-in protocol ID (openai, anthropic, etc.)
|
|
55
44
|
if (!provider || !isProviderId(provider)) {
|
|
56
45
|
return NextResponse.json({ ok: false, error: 'Invalid provider' }, { status: 400 });
|
|
57
46
|
}
|
|
@@ -64,9 +53,9 @@ export async function POST(req: NextRequest) {
|
|
|
64
53
|
return NextResponse.json({ ok: true, models });
|
|
65
54
|
}
|
|
66
55
|
|
|
67
|
-
const cfg = effectiveAiConfig(
|
|
56
|
+
const cfg = effectiveAiConfig();
|
|
68
57
|
let resolvedKey = apiKey || '';
|
|
69
|
-
if (!resolvedKey
|
|
58
|
+
if (!resolvedKey) {
|
|
70
59
|
resolvedKey = cfg.apiKey;
|
|
71
60
|
}
|
|
72
61
|
|
|
@@ -2,8 +2,8 @@ export const dynamic = 'force-dynamic';
|
|
|
2
2
|
import { NextRequest, NextResponse } from 'next/server';
|
|
3
3
|
import { readSettings, writeSettings, ServerSettings } from '@/lib/settings';
|
|
4
4
|
import { invalidateCache } from '@/lib/fs';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { ALL_PROVIDER_IDS, getApiKeyEnvVar, getApiKeyFromEnv } from '@/lib/agent/providers';
|
|
6
|
+
import { parseProviders } from '@/lib/custom-endpoints';
|
|
7
7
|
|
|
8
8
|
function maskToken(token: string | undefined): string {
|
|
9
9
|
if (!token) return '';
|
|
@@ -35,32 +35,19 @@ export async function GET() {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
const maskedProviders: Record<string, { apiKey: string; model: string; baseUrl?: string }> = {};
|
|
40
|
-
for (const [id, cfg] of Object.entries(settings.ai.providers)) {
|
|
41
|
-
if (!cfg) continue;
|
|
42
|
-
maskedProviders[id] = {
|
|
43
|
-
apiKey: cfg.apiKey ? '***set***' : '',
|
|
44
|
-
model: cfg.model ?? '',
|
|
45
|
-
...(cfg.baseUrl !== undefined ? { baseUrl: cfg.baseUrl } : {}),
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const masked = {
|
|
38
|
+
return NextResponse.json({
|
|
50
39
|
ai: {
|
|
51
|
-
|
|
52
|
-
providers:
|
|
40
|
+
activeProvider: settings.ai.activeProvider,
|
|
41
|
+
providers: settings.ai.providers,
|
|
53
42
|
},
|
|
54
43
|
mindRoot: settings.mindRoot,
|
|
55
|
-
webPassword: settings.webPassword
|
|
44
|
+
webPassword: settings.webPassword ?? '',
|
|
56
45
|
authToken: maskToken(settings.authToken),
|
|
57
46
|
mcpPort: settings.mcpPort ?? 8781,
|
|
58
47
|
agent: settings.agent ?? {},
|
|
59
48
|
envOverrides,
|
|
60
49
|
envValues,
|
|
61
|
-
|
|
62
|
-
};
|
|
63
|
-
return NextResponse.json(masked);
|
|
50
|
+
});
|
|
64
51
|
}
|
|
65
52
|
|
|
66
53
|
export async function POST(req: NextRequest) {
|
|
@@ -68,28 +55,14 @@ export async function POST(req: NextRequest) {
|
|
|
68
55
|
const body = await req.json() as Partial<ServerSettings>;
|
|
69
56
|
const current = readSettings();
|
|
70
57
|
|
|
71
|
-
//
|
|
72
|
-
const
|
|
73
|
-
if (body.ai
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const cur = mergedProviders[id as keyof typeof mergedProviders] ?? { apiKey: '', model: '' };
|
|
77
|
-
mergedProviders[id as keyof typeof mergedProviders] = {
|
|
78
|
-
...cur,
|
|
79
|
-
...incoming,
|
|
80
|
-
apiKey: incoming.apiKey === '***set***'
|
|
81
|
-
? (cur.apiKey ?? '')
|
|
82
|
-
: (incoming.apiKey ?? cur.apiKey ?? ''),
|
|
83
|
-
model: incoming.model ?? cur.model ?? '',
|
|
84
|
-
};
|
|
85
|
-
}
|
|
58
|
+
// Resolve AI config
|
|
59
|
+
const resolvedAi = { ...current.ai };
|
|
60
|
+
if (body.ai) {
|
|
61
|
+
if (body.ai.activeProvider !== undefined) resolvedAi.activeProvider = body.ai.activeProvider;
|
|
62
|
+
if (body.ai.providers !== undefined) resolvedAi.providers = parseProviders(body.ai.providers);
|
|
86
63
|
}
|
|
87
64
|
|
|
88
|
-
|
|
89
|
-
const incomingWebPassword = body.webPassword;
|
|
90
|
-
const resolvedWebPassword = incomingWebPassword === '***set***'
|
|
91
|
-
? current.webPassword
|
|
92
|
-
: (incomingWebPassword ?? current.webPassword);
|
|
65
|
+
const resolvedWebPassword = body.webPassword ?? current.webPassword;
|
|
93
66
|
|
|
94
67
|
// authToken is read-only via POST (use /api/settings/reset-token to regenerate)
|
|
95
68
|
// but allow clearing it by passing empty string
|
|
@@ -110,25 +83,8 @@ export async function POST(req: NextRequest) {
|
|
|
110
83
|
}
|
|
111
84
|
}
|
|
112
85
|
|
|
113
|
-
// Handle customProviders: merge with existing, preserving masked keys
|
|
114
|
-
let resolvedCustomProviders = current.customProviders ?? [];
|
|
115
|
-
if (body.customProviders !== undefined) {
|
|
116
|
-
const incoming = parseCustomProviders(body.customProviders);
|
|
117
|
-
resolvedCustomProviders = incoming.map(cp => {
|
|
118
|
-
// If API key is masked, keep existing key
|
|
119
|
-
if (cp.apiKey === '***set***') {
|
|
120
|
-
const existing = (current.customProviders ?? []).find(e => e.id === cp.id);
|
|
121
|
-
return { ...cp, apiKey: existing?.apiKey ?? '' };
|
|
122
|
-
}
|
|
123
|
-
return cp;
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
|
|
127
86
|
const next: ServerSettings = {
|
|
128
|
-
ai:
|
|
129
|
-
provider: body.ai?.provider ?? current.ai.provider,
|
|
130
|
-
providers: mergedProviders,
|
|
131
|
-
},
|
|
87
|
+
ai: resolvedAi,
|
|
132
88
|
mindRoot: body.mindRoot ?? current.mindRoot,
|
|
133
89
|
agent: body.agent ?? current.agent,
|
|
134
90
|
webPassword: resolvedWebPassword,
|
|
@@ -137,7 +93,6 @@ export async function POST(req: NextRequest) {
|
|
|
137
93
|
mcpPort: typeof body.mcpPort === 'number' ? body.mcpPort : current.mcpPort,
|
|
138
94
|
startMode: body.startMode ?? current.startMode,
|
|
139
95
|
connectionMode: resolvedConnectionMode,
|
|
140
|
-
customProviders: resolvedCustomProviders,
|
|
141
96
|
};
|
|
142
97
|
|
|
143
98
|
writeSettings(next);
|
|
@@ -4,7 +4,7 @@ import { complete } from '@mariozechner/pi-ai';
|
|
|
4
4
|
import { effectiveAiConfig, readBaseUrlCompat, writeSettings, readSettings } from '@/lib/settings';
|
|
5
5
|
import { getModelConfig } from '@/lib/agent/model';
|
|
6
6
|
import { type ProviderId, isProviderId } from '@/lib/agent/providers';
|
|
7
|
-
import {
|
|
7
|
+
import { isProviderEntryId, findProvider } from '@/lib/custom-endpoints';
|
|
8
8
|
|
|
9
9
|
const TIMEOUT = 15_000;
|
|
10
10
|
|
|
@@ -40,26 +40,60 @@ function classifyPiAiError(err: unknown): { code: ErrorCode; error: string } {
|
|
|
40
40
|
export async function POST(req: NextRequest) {
|
|
41
41
|
try {
|
|
42
42
|
const body = await req.json();
|
|
43
|
-
const { provider, apiKey, model, baseUrl } = body as {
|
|
43
|
+
const { provider, apiKey, model, baseUrl, baseProviderId } = body as {
|
|
44
44
|
provider?: string;
|
|
45
45
|
apiKey?: string;
|
|
46
46
|
model?: string;
|
|
47
47
|
baseUrl?: string;
|
|
48
|
+
/** When set, run an inline test using only the supplied params (no settings fallback). */
|
|
49
|
+
baseProviderId?: string;
|
|
48
50
|
};
|
|
49
51
|
|
|
50
|
-
//
|
|
51
|
-
if (
|
|
52
|
+
// Inline test for unsaved custom providers — uses only the supplied params.
|
|
53
|
+
if (baseProviderId && isProviderId(baseProviderId)) {
|
|
54
|
+
if (!apiKey) {
|
|
55
|
+
return NextResponse.json({ ok: false, code: 'auth_error', error: 'No API key configured' });
|
|
56
|
+
}
|
|
57
|
+
if (!model) {
|
|
58
|
+
return NextResponse.json({ ok: false, code: 'unknown', error: 'Model is required' }, { status: 400 });
|
|
59
|
+
}
|
|
60
|
+
const start = Date.now();
|
|
61
|
+
const ctrl = new AbortController();
|
|
62
|
+
const timer = setTimeout(() => ctrl.abort(), TIMEOUT);
|
|
63
|
+
try {
|
|
64
|
+
const { model: piModel } = getModelConfig({
|
|
65
|
+
provider: baseProviderId as ProviderId,
|
|
66
|
+
apiKey,
|
|
67
|
+
model,
|
|
68
|
+
baseUrl: baseUrl || undefined,
|
|
69
|
+
});
|
|
70
|
+
await complete(piModel, {
|
|
71
|
+
messages: [{ role: 'user', content: 'hi', timestamp: Date.now() }],
|
|
72
|
+
}, {
|
|
73
|
+
apiKey,
|
|
74
|
+
signal: ctrl.signal,
|
|
75
|
+
});
|
|
76
|
+
return NextResponse.json({ ok: true, latency: Date.now() - start });
|
|
77
|
+
} catch (e) {
|
|
78
|
+
return NextResponse.json({ ok: false, ...classifyPiAiError(e) });
|
|
79
|
+
} finally {
|
|
80
|
+
clearTimeout(timer);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Support provider entry IDs (p_*) — look up from unified providers list
|
|
85
|
+
if (provider && isProviderEntryId(provider)) {
|
|
52
86
|
const settings = readSettings();
|
|
53
|
-
const
|
|
54
|
-
if (!
|
|
87
|
+
const entry = findProvider(settings.ai.providers, provider);
|
|
88
|
+
if (!entry) {
|
|
55
89
|
return NextResponse.json(
|
|
56
|
-
{ ok: false, code: 'unknown', error: '
|
|
90
|
+
{ ok: false, code: 'unknown', error: 'Provider not found' },
|
|
57
91
|
{ status: 400 },
|
|
58
92
|
);
|
|
59
93
|
}
|
|
60
|
-
const resolvedKey =
|
|
61
|
-
const resolvedModel = model ||
|
|
62
|
-
const resolvedBaseUrl = baseUrl ||
|
|
94
|
+
const resolvedKey = apiKey || entry.apiKey;
|
|
95
|
+
const resolvedModel = model || entry.model;
|
|
96
|
+
const resolvedBaseUrl = baseUrl || entry.baseUrl;
|
|
63
97
|
if (!resolvedKey) {
|
|
64
98
|
return NextResponse.json({ ok: false, code: 'auth_error', error: 'No API key configured' });
|
|
65
99
|
}
|
|
@@ -68,7 +102,7 @@ export async function POST(req: NextRequest) {
|
|
|
68
102
|
const timer = setTimeout(() => ctrl.abort(), TIMEOUT);
|
|
69
103
|
try {
|
|
70
104
|
const { model: piModel } = getModelConfig({
|
|
71
|
-
provider:
|
|
105
|
+
provider: entry.protocol,
|
|
72
106
|
apiKey: resolvedKey,
|
|
73
107
|
model: resolvedModel || undefined,
|
|
74
108
|
baseUrl: resolvedBaseUrl || undefined,
|
|
@@ -87,6 +121,7 @@ export async function POST(req: NextRequest) {
|
|
|
87
121
|
}
|
|
88
122
|
}
|
|
89
123
|
|
|
124
|
+
// Legacy: support raw protocol IDs (openai, anthropic, etc.)
|
|
90
125
|
if (!provider || !isProviderId(provider)) {
|
|
91
126
|
return NextResponse.json(
|
|
92
127
|
{ ok: false, code: 'unknown', error: 'Invalid provider' },
|
|
@@ -96,7 +131,7 @@ export async function POST(req: NextRequest) {
|
|
|
96
131
|
|
|
97
132
|
const cfg = effectiveAiConfig(provider as ProviderId);
|
|
98
133
|
let resolvedKey = apiKey || '';
|
|
99
|
-
if (!resolvedKey
|
|
134
|
+
if (!resolvedKey) {
|
|
100
135
|
resolvedKey = cfg.apiKey;
|
|
101
136
|
}
|
|
102
137
|
|
|
@@ -6,6 +6,7 @@ import { readSettings, writeSettings, ServerSettings } from '@/lib/settings';
|
|
|
6
6
|
import { applyTemplate } from '@/lib/template';
|
|
7
7
|
import { expandSetupPathHome } from './path-utils';
|
|
8
8
|
import { type ProviderId, isProviderId, PROVIDER_PRESETS } from '@/lib/agent/providers';
|
|
9
|
+
import { type Provider, generateProviderId, findProvider } from '@/lib/custom-endpoints';
|
|
9
10
|
|
|
10
11
|
function maskApiKey(key: string): string {
|
|
11
12
|
if (!key || key.length < 6) return key ? '***' : '';
|
|
@@ -20,15 +21,14 @@ export async function GET() {
|
|
|
20
21
|
const defaultMindRoot = s.mindRoot || [home, 'MindOS', 'mind'].join(sep);
|
|
21
22
|
|
|
22
23
|
// Build providerConfigs for frontend (masked keys)
|
|
23
|
-
const providerConfigs
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
24
|
+
const providerConfigs = s.ai.providers.map(p => ({
|
|
25
|
+
id: p.id,
|
|
26
|
+
name: p.name,
|
|
27
|
+
protocol: p.protocol,
|
|
28
|
+
model: p.model,
|
|
29
|
+
baseUrl: p.baseUrl,
|
|
30
|
+
apiKeyMask: maskApiKey(p.apiKey),
|
|
31
|
+
}));
|
|
32
32
|
|
|
33
33
|
return NextResponse.json({
|
|
34
34
|
mindRoot: defaultMindRoot,
|
|
@@ -38,7 +38,7 @@ export async function GET() {
|
|
|
38
38
|
mcpPort: s.mcpPort ?? 8781,
|
|
39
39
|
authToken: s.authToken ?? '',
|
|
40
40
|
webPassword: s.webPassword ?? '',
|
|
41
|
-
|
|
41
|
+
activeProvider: s.ai.activeProvider,
|
|
42
42
|
providerConfigs,
|
|
43
43
|
guideState: s.guideState ?? null,
|
|
44
44
|
});
|
|
@@ -104,23 +104,41 @@ export async function POST(req: NextRequest) {
|
|
|
104
104
|
// configured key with blank just because the user didn't re-enter it.
|
|
105
105
|
let mergedAi = current.ai;
|
|
106
106
|
if (ai) {
|
|
107
|
-
const
|
|
108
|
-
const mergedProviders = { ...current.ai.providers };
|
|
107
|
+
const mergedProviders = [...current.ai.providers];
|
|
109
108
|
|
|
110
109
|
// Merge each provider's config from the incoming payload
|
|
111
110
|
if (ai.providers && typeof ai.providers === 'object') {
|
|
112
111
|
for (const [id, inCfg] of Object.entries(ai.providers as Record<string, any>)) {
|
|
113
112
|
if (!isProviderId(id) || !inCfg) continue;
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
113
|
+
const preset = PROVIDER_PRESETS[id];
|
|
114
|
+
const existingIdx = mergedProviders.findIndex(p => p.protocol === id);
|
|
115
|
+
const existing = existingIdx >= 0 ? mergedProviders[existingIdx] : null;
|
|
116
|
+
|
|
117
|
+
const merged: Provider = {
|
|
118
|
+
id: existing?.id ?? generateProviderId(),
|
|
119
|
+
name: existing?.name ?? preset?.name ?? id,
|
|
120
|
+
protocol: id,
|
|
121
|
+
apiKey: inCfg.apiKey || existing?.apiKey || '',
|
|
122
|
+
model: inCfg.model || existing?.model || preset?.defaultModel || '',
|
|
123
|
+
baseUrl: inCfg.baseUrl !== undefined ? (inCfg.baseUrl || '') : (existing?.baseUrl ?? ''),
|
|
119
124
|
};
|
|
125
|
+
|
|
126
|
+
if (existingIdx >= 0) {
|
|
127
|
+
mergedProviders[existingIdx] = merged;
|
|
128
|
+
} else {
|
|
129
|
+
mergedProviders.push(merged);
|
|
130
|
+
}
|
|
120
131
|
}
|
|
121
132
|
}
|
|
122
133
|
|
|
123
|
-
|
|
134
|
+
// Determine active provider
|
|
135
|
+
let newActiveProvider = current.ai.activeProvider;
|
|
136
|
+
if (ai.provider && isProviderId(ai.provider)) {
|
|
137
|
+
const match = mergedProviders.find(p => p.protocol === ai.provider);
|
|
138
|
+
if (match) newActiveProvider = match.id;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
mergedAi = { activeProvider: newActiveProvider, providers: mergedProviders };
|
|
124
142
|
}
|
|
125
143
|
|
|
126
144
|
const disabledSkills = body.template === 'zh' ? ['mindos'] : ['mindos-zh'];
|