@freesyntax/notch-cli 0.5.22 → 0.5.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/dist/{chunk-EPSOOCNB.js → chunk-474TAHDN.js} +46 -6
- package/dist/{chunk-JXQ4HZ47.js → chunk-JVFOAPYV.js} +279 -28
- package/dist/{chunk-J66N6AFH.js → chunk-UHK6SI4H.js} +87 -18
- package/dist/{chunk-FZVPGJJW.js → chunk-YNYVQ7ZI.js} +10 -10
- package/dist/{config-set-3IWEVZQ4.js → config-set-5F4VK7IT.js} +3 -2
- package/dist/index.js +540 -212
- package/dist/{model-download-3NDKS3VM.js → model-download-KCQJCEPW.js} +1 -1
- package/dist/{ollama-bench-5V5CCOCQ.js → ollama-bench-JLC5POG3.js} +6 -6
- package/dist/{ollama-launch-P5KBK7AJ.js → ollama-launch-3IKB2A3Z.js} +3 -3
- package/dist/server-GMF4WV67.js +187 -0
- package/dist/{tools-XWKCW4RN.js → tools-ABRZPCEJ.js} +3 -1
- package/package.json +60 -57
- package/dist/server-IGOZHW52.js +0 -1479
package/dist/index.js
CHANGED
|
@@ -3,15 +3,17 @@ import "./chunk-4HPRBCSY.js";
|
|
|
3
3
|
import {
|
|
4
4
|
MCPClient,
|
|
5
5
|
buildToolMap,
|
|
6
|
-
|
|
6
|
+
describeToolSchemas,
|
|
7
|
+
disconnectMCPServers,
|
|
7
8
|
drainTailNotifications,
|
|
9
|
+
initMCPServers,
|
|
8
10
|
listBuiltinAgents,
|
|
9
11
|
nextSubagentId,
|
|
10
12
|
parseMCPConfig,
|
|
11
13
|
pollPendingAgents,
|
|
12
14
|
setCurrentSurface,
|
|
13
15
|
spawnSubagent
|
|
14
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-474TAHDN.js";
|
|
15
17
|
import {
|
|
16
18
|
Rollout,
|
|
17
19
|
generateSessionId,
|
|
@@ -29,13 +31,12 @@ import "./chunk-O6AKZ4OH.js";
|
|
|
29
31
|
import {
|
|
30
32
|
loadConfig,
|
|
31
33
|
persistConfigPatch
|
|
32
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-UHK6SI4H.js";
|
|
33
35
|
import "./chunk-KCAR5DOB.js";
|
|
34
36
|
import {
|
|
35
37
|
ByokMissingApiKeyError,
|
|
36
38
|
ByokMissingBaseUrlError,
|
|
37
39
|
MODEL_CATALOG,
|
|
38
|
-
MODEL_IDS,
|
|
39
40
|
MissingApiKeyError,
|
|
40
41
|
findByokProvider,
|
|
41
42
|
isByokRef,
|
|
@@ -43,9 +44,11 @@ import {
|
|
|
43
44
|
listByokProviders,
|
|
44
45
|
modelSupportsImages,
|
|
45
46
|
parseByokRef,
|
|
47
|
+
recordShadowUsage,
|
|
46
48
|
resolveModel,
|
|
47
49
|
validateConfig
|
|
48
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-JVFOAPYV.js";
|
|
51
|
+
import "./chunk-GFVLHUSS.js";
|
|
49
52
|
import "./chunk-6CZCFY6H.js";
|
|
50
53
|
import "./chunk-6U3ZAGYA.js";
|
|
51
54
|
import "./chunk-FFB7GK3Y.js";
|
|
@@ -618,6 +621,304 @@ function microcompact(messages, opts2 = {}) {
|
|
|
618
621
|
return { messages: out, cleared, savedChars };
|
|
619
622
|
}
|
|
620
623
|
|
|
624
|
+
// src/agent/correction.ts
|
|
625
|
+
var CORRECTION_PRESETS = {
|
|
626
|
+
/** No correction — for capable models (Claude, GPT-4, etc.) */
|
|
627
|
+
disabled: () => ({
|
|
628
|
+
intentGate: false,
|
|
629
|
+
maxCorrectionAttempts: 0,
|
|
630
|
+
useTemplateFormat: false
|
|
631
|
+
}),
|
|
632
|
+
/** Light correction — for mid-tier models (Qwen 14B+, Llama 70B) */
|
|
633
|
+
capable: () => ({
|
|
634
|
+
intentGate: false,
|
|
635
|
+
maxCorrectionAttempts: 1,
|
|
636
|
+
useTemplateFormat: false
|
|
637
|
+
}),
|
|
638
|
+
/** Full correction — for small models (7B-14B) */
|
|
639
|
+
smallModel: () => ({
|
|
640
|
+
intentGate: true,
|
|
641
|
+
maxCorrectionAttempts: 2,
|
|
642
|
+
useTemplateFormat: false
|
|
643
|
+
}),
|
|
644
|
+
/** Maximum scaffolding — for tiny models (sub-7B) */
|
|
645
|
+
tinyModel: () => ({
|
|
646
|
+
intentGate: true,
|
|
647
|
+
maxCorrectionAttempts: 3,
|
|
648
|
+
useTemplateFormat: true
|
|
649
|
+
})
|
|
650
|
+
};
|
|
651
|
+
var INTENT_GATE_PROMPT = `You are a classifier. Given the user's message and the list of available tools, decide: does this message require calling a tool to answer properly?
|
|
652
|
+
|
|
653
|
+
Reply with EXACTLY one word: YES or NO
|
|
654
|
+
|
|
655
|
+
Available tools: {tool_names}
|
|
656
|
+
|
|
657
|
+
User message: {user_message}`;
|
|
658
|
+
async function intentGate(model, userMessage, toolNames) {
|
|
659
|
+
const { generateText: generateText3 } = await import("ai");
|
|
660
|
+
const prompt = INTENT_GATE_PROMPT.replace("{tool_names}", toolNames.join(", ")).replace("{user_message}", userMessage.slice(0, 500));
|
|
661
|
+
try {
|
|
662
|
+
const result = await generateText3({
|
|
663
|
+
model,
|
|
664
|
+
messages: [{ role: "user", content: prompt }],
|
|
665
|
+
maxTokens: 10,
|
|
666
|
+
temperature: 0
|
|
667
|
+
});
|
|
668
|
+
const answer = result.text.trim().toUpperCase();
|
|
669
|
+
if (answer.startsWith("YES")) return "yes";
|
|
670
|
+
if (answer.startsWith("NO")) return "no";
|
|
671
|
+
return "unclear";
|
|
672
|
+
} catch {
|
|
673
|
+
return "unclear";
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
function looksLikeFailedToolCall(text, toolNames) {
|
|
677
|
+
if (!text || text.length < 10) return false;
|
|
678
|
+
let weakSignals = 0;
|
|
679
|
+
let strongSignals = 0;
|
|
680
|
+
const lowerText = text.toLowerCase();
|
|
681
|
+
for (const name of toolNames) {
|
|
682
|
+
if (lowerText.includes(name.toLowerCase())) {
|
|
683
|
+
weakSignals++;
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
const jsonPatterns = [
|
|
688
|
+
/\{\s*"name"\s*:/,
|
|
689
|
+
/\{\s*"tool"\s*:/,
|
|
690
|
+
/\{\s*"function"\s*:/,
|
|
691
|
+
/"arguments"\s*:\s*\{/,
|
|
692
|
+
/"parameters"\s*:\s*\{/
|
|
693
|
+
];
|
|
694
|
+
for (const pat of jsonPatterns) {
|
|
695
|
+
if (pat.test(text)) {
|
|
696
|
+
strongSignals++;
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
const intentPatterns = [
|
|
701
|
+
/\b(?:i'll|let me|i need to|i should|i want to|i will)\s+(?:use|call|invoke|run|execute)\b/i,
|
|
702
|
+
/\b(?:calling|using|invoking|running)\s+(?:the\s+)?(?:tool|function)\b/i
|
|
703
|
+
];
|
|
704
|
+
for (const pat of intentPatterns) {
|
|
705
|
+
if (pat.test(text)) {
|
|
706
|
+
weakSignals++;
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
const xmlPatterns = [
|
|
711
|
+
/<tool_call/i,
|
|
712
|
+
/<function_call/i,
|
|
713
|
+
/<tool_use/i,
|
|
714
|
+
/<\|tool▁call\|>/,
|
|
715
|
+
/<\|plugin\|>/
|
|
716
|
+
];
|
|
717
|
+
for (const pat of xmlPatterns) {
|
|
718
|
+
if (pat.test(text)) {
|
|
719
|
+
strongSignals++;
|
|
720
|
+
break;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
if (/```(?:json|tool)?\s*\n?\s*\{/.test(text)) {
|
|
724
|
+
weakSignals++;
|
|
725
|
+
}
|
|
726
|
+
return strongSignals >= 1 || weakSignals >= 2;
|
|
727
|
+
}
|
|
728
|
+
var CORRECTION_PROMPTS = [
|
|
729
|
+
// Attempt 1: XML format (most models have seen this in training)
|
|
730
|
+
`Your previous response tried to call a tool but the format was wrong.
|
|
731
|
+
|
|
732
|
+
Your output was:
|
|
733
|
+
---
|
|
734
|
+
{raw_output}
|
|
735
|
+
---
|
|
736
|
+
|
|
737
|
+
To call a tool, you MUST use this exact format:
|
|
738
|
+
<tool_call>
|
|
739
|
+
{"name": "tool_name", "arguments": {"param1": "value1"}}
|
|
740
|
+
</tool_call>
|
|
741
|
+
|
|
742
|
+
Available tools: {tool_names}
|
|
743
|
+
|
|
744
|
+
Rewrite your response with the correct tool call format. Output ONLY the tool call, nothing else.`,
|
|
745
|
+
// Attempt 2: bare JSON (simpler)
|
|
746
|
+
`Your output was not a valid tool call. Rewrite it as a single JSON object:
|
|
747
|
+
|
|
748
|
+
{"name": "TOOL_NAME", "arguments": {"key": "value"}}
|
|
749
|
+
|
|
750
|
+
Available tools: {tool_names}
|
|
751
|
+
Your failed output: {raw_output_short}
|
|
752
|
+
|
|
753
|
+
Reply with ONLY the JSON object.`,
|
|
754
|
+
// Attempt 3: ask directly (simplest possible)
|
|
755
|
+
`Which tool do you want to call? Reply with the tool name and arguments as JSON.
|
|
756
|
+
Tools: {tool_names}
|
|
757
|
+
JSON:`
|
|
758
|
+
];
|
|
759
|
+
async function correctionPass(model, rawOutput, toolNames, conversationMessages, maxAttempts) {
|
|
760
|
+
const { generateText: generateText3 } = await import("ai");
|
|
761
|
+
for (let i = 0; i < Math.min(maxAttempts, CORRECTION_PROMPTS.length); i++) {
|
|
762
|
+
const promptTemplate = CORRECTION_PROMPTS[i];
|
|
763
|
+
if (!promptTemplate) continue;
|
|
764
|
+
const prompt = promptTemplate.replace("{raw_output}", rawOutput.slice(0, 1500)).replace("{raw_output_short}", rawOutput.slice(0, 500)).replace("{tool_names}", toolNames.join(", "));
|
|
765
|
+
try {
|
|
766
|
+
const result = await generateText3({
|
|
767
|
+
model,
|
|
768
|
+
messages: [
|
|
769
|
+
...conversationMessages.slice(-4),
|
|
770
|
+
// keep recent context
|
|
771
|
+
{ role: "user", content: prompt }
|
|
772
|
+
],
|
|
773
|
+
maxTokens: 500,
|
|
774
|
+
temperature: 0
|
|
775
|
+
});
|
|
776
|
+
const parsed = extractToolCallsFromText(result.text, toolNames);
|
|
777
|
+
if (parsed.length > 0) return parsed;
|
|
778
|
+
} catch {
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
return null;
|
|
782
|
+
}
|
|
783
|
+
function extractTemplateToolCalls(text, toolNames) {
|
|
784
|
+
const calls = [];
|
|
785
|
+
const toolNameSet = new Set(toolNames.map((n) => n.toLowerCase()));
|
|
786
|
+
const lines = text.split("\n");
|
|
787
|
+
let currentTool = null;
|
|
788
|
+
let currentArgs = {};
|
|
789
|
+
for (const line of lines) {
|
|
790
|
+
const trimmed = line.trim();
|
|
791
|
+
const toolMatch = trimmed.match(/^TOOL:\s*(.+)$/i);
|
|
792
|
+
if (toolMatch && toolMatch[1]) {
|
|
793
|
+
if (currentTool && toolNameSet.has(currentTool.toLowerCase())) {
|
|
794
|
+
calls.push({
|
|
795
|
+
id: `correction-${Date.now()}-${calls.length}`,
|
|
796
|
+
name: currentTool,
|
|
797
|
+
args: currentArgs
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
currentTool = toolMatch[1].trim();
|
|
801
|
+
currentArgs = {};
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
const argMatch = trimmed.match(/^ARG_(\w+):\s*(.+)$/i);
|
|
805
|
+
if (argMatch && currentTool && argMatch[1] && argMatch[2]) {
|
|
806
|
+
const key = argMatch[1];
|
|
807
|
+
const rawValue = argMatch[2].trim();
|
|
808
|
+
currentArgs[key] = coerceValue(rawValue);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
if (currentTool && toolNameSet.has(currentTool.toLowerCase())) {
|
|
812
|
+
calls.push({
|
|
813
|
+
id: `correction-${Date.now()}-${calls.length}`,
|
|
814
|
+
name: currentTool,
|
|
815
|
+
args: currentArgs
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
return calls;
|
|
819
|
+
}
|
|
820
|
+
function coerceValue(raw) {
|
|
821
|
+
if (raw === "true") return true;
|
|
822
|
+
if (raw === "false") return false;
|
|
823
|
+
if (raw === "null") return null;
|
|
824
|
+
const num = Number(raw);
|
|
825
|
+
if (!Number.isNaN(num) && raw !== "") return num;
|
|
826
|
+
if (raw.startsWith("{") && raw.endsWith("}") || raw.startsWith("[") && raw.endsWith("]")) {
|
|
827
|
+
try {
|
|
828
|
+
return JSON.parse(raw);
|
|
829
|
+
} catch {
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
return raw;
|
|
833
|
+
}
|
|
834
|
+
function extractToolCallsFromText(text, toolNames) {
|
|
835
|
+
const toolNameSet = new Set(toolNames.map((n) => n.toLowerCase()));
|
|
836
|
+
const xmlRegex = /<tool_call>\s*([\s\S]*?)\s*<\/tool_call>/gi;
|
|
837
|
+
let match2;
|
|
838
|
+
const xmlCalls = [];
|
|
839
|
+
while ((match2 = xmlRegex.exec(text)) !== null) {
|
|
840
|
+
const captured = match2[1];
|
|
841
|
+
if (captured) {
|
|
842
|
+
const parsed = tryParseToolJson(captured, toolNameSet);
|
|
843
|
+
if (parsed) xmlCalls.push(parsed);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
if (xmlCalls.length > 0) return xmlCalls;
|
|
847
|
+
const codeBlockRegex = /```(?:json|tool)?\s*\n?([\s\S]*?)\n?\s*```/g;
|
|
848
|
+
const codeCalls = [];
|
|
849
|
+
while ((match2 = codeBlockRegex.exec(text)) !== null) {
|
|
850
|
+
const captured = match2[1];
|
|
851
|
+
if (captured) {
|
|
852
|
+
const parsed = tryParseToolJson(captured, toolNameSet);
|
|
853
|
+
if (parsed) codeCalls.push(parsed);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
if (codeCalls.length > 0) return codeCalls;
|
|
857
|
+
const jsonRegex = /\{[^{}]*"name"\s*:\s*"[^"]+"\s*[,}][\s\S]*?\}/g;
|
|
858
|
+
const jsonCalls = [];
|
|
859
|
+
while ((match2 = jsonRegex.exec(text)) !== null) {
|
|
860
|
+
const parsed = tryParseToolJson(match2[0], toolNameSet);
|
|
861
|
+
if (parsed) jsonCalls.push(parsed);
|
|
862
|
+
}
|
|
863
|
+
if (jsonCalls.length > 0) return jsonCalls;
|
|
864
|
+
const templateCalls = extractTemplateToolCalls(text, toolNames);
|
|
865
|
+
if (templateCalls.length > 0) return templateCalls;
|
|
866
|
+
return [];
|
|
867
|
+
}
|
|
868
|
+
function tryParseToolJson(jsonStr, toolNameSet) {
|
|
869
|
+
try {
|
|
870
|
+
const obj = JSON.parse(jsonStr.trim());
|
|
871
|
+
const name = obj.name ?? obj.tool ?? obj.function;
|
|
872
|
+
if (!name || typeof name !== "string") return null;
|
|
873
|
+
if (!toolNameSet.has(name.toLowerCase())) return null;
|
|
874
|
+
const args = obj.arguments ?? obj.parameters ?? obj.args ?? {};
|
|
875
|
+
return {
|
|
876
|
+
id: `correction-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
877
|
+
name,
|
|
878
|
+
args: typeof args === "object" ? args : {}
|
|
879
|
+
};
|
|
880
|
+
} catch {
|
|
881
|
+
return null;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
var CAPABLE_PATTERNS = [
|
|
885
|
+
/claude/i,
|
|
886
|
+
/gpt-4/i,
|
|
887
|
+
/gpt-3\.5/i,
|
|
888
|
+
/o[1-4]/i,
|
|
889
|
+
/gemini-(?:pro|ultra|2)/i,
|
|
890
|
+
/deepseek-(?:v3|r1)/i
|
|
891
|
+
];
|
|
892
|
+
var SMALL_PATTERNS = [
|
|
893
|
+
/qwen.*(?:7b|14b|32b)/i,
|
|
894
|
+
/llama.*(?:8b|13b|70b)/i,
|
|
895
|
+
/mistral.*(?:7b|8x7b)/i,
|
|
896
|
+
/gemma.*(?:7b|9b|27b)/i,
|
|
897
|
+
/codestral/i,
|
|
898
|
+
/deepseek-coder/i
|
|
899
|
+
];
|
|
900
|
+
var TINY_PATTERNS = [
|
|
901
|
+
/qwen.*(?:0\.5b|1\.5b|3b|4b)/i,
|
|
902
|
+
/llama.*(?:1b|3b)/i,
|
|
903
|
+
/gemma.*(?:2b|4b)/i,
|
|
904
|
+
/phi.*(?:1|2|3)/i,
|
|
905
|
+
/tinyllama/i,
|
|
906
|
+
/stablelm/i
|
|
907
|
+
];
|
|
908
|
+
function autoDetectPreset(modelId) {
|
|
909
|
+
if (!modelId) return CORRECTION_PRESETS.capable();
|
|
910
|
+
for (const pat of CAPABLE_PATTERNS) {
|
|
911
|
+
if (pat.test(modelId)) return CORRECTION_PRESETS.disabled();
|
|
912
|
+
}
|
|
913
|
+
for (const pat of TINY_PATTERNS) {
|
|
914
|
+
if (pat.test(modelId)) return CORRECTION_PRESETS.tinyModel();
|
|
915
|
+
}
|
|
916
|
+
for (const pat of SMALL_PATTERNS) {
|
|
917
|
+
if (pat.test(modelId)) return CORRECTION_PRESETS.smallModel();
|
|
918
|
+
}
|
|
919
|
+
return CORRECTION_PRESETS.capable();
|
|
920
|
+
}
|
|
921
|
+
|
|
621
922
|
// src/agent/loop.ts
|
|
622
923
|
function getErrorSignature(toolName, result) {
|
|
623
924
|
return {
|
|
@@ -644,7 +945,18 @@ async function runAgentLoop(messages, config) {
|
|
|
644
945
|
let wasCompressed = false;
|
|
645
946
|
const recentErrors = [];
|
|
646
947
|
const MAX_REPEATED_ERRORS = 3;
|
|
948
|
+
const correctionCfg = config.correction ?? autoDetectPreset(config.modelId ?? "");
|
|
949
|
+
let totalCorrectionAttempts = 0;
|
|
950
|
+
let intentResult = "unclear";
|
|
647
951
|
let history = [...messages];
|
|
952
|
+
const toolNames = Object.keys(tools);
|
|
953
|
+
const templateFormatHint = correctionCfg.useTemplateFormat && toolNames.length > 0 ? [
|
|
954
|
+
"",
|
|
955
|
+
"If native tool calling fails, use this fallback format with no extra prose:",
|
|
956
|
+
"TOOL: <tool name>",
|
|
957
|
+
"ARG_<argument_name>: <argument value>",
|
|
958
|
+
`Available tool names: ${toolNames.join(", ")}`
|
|
959
|
+
].join("\n") : "";
|
|
648
960
|
await config.toolContext.runHook?.("pre-compact", { messageCount: history.length });
|
|
649
961
|
history = await autoCompress(history, config.model, contextWindow, () => {
|
|
650
962
|
wasCompressed = true;
|
|
@@ -653,6 +965,13 @@ async function runAgentLoop(messages, config) {
|
|
|
653
965
|
if (wasCompressed) {
|
|
654
966
|
await config.toolContext.runHook?.("post-compact", { messageCount: history.length });
|
|
655
967
|
}
|
|
968
|
+
if (correctionCfg.intentGate && toolNames.length > 0) {
|
|
969
|
+
const lastUserMsg = [...history].reverse().find((m) => m.role === "user");
|
|
970
|
+
const userText = typeof lastUserMsg?.content === "string" ? lastUserMsg.content : "";
|
|
971
|
+
if (userText) {
|
|
972
|
+
intentResult = await intentGate(config.model, userText, toolNames);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
656
975
|
while (iterations < maxIter) {
|
|
657
976
|
iterations++;
|
|
658
977
|
const mc = microcompact(history, { keepLastN: 6, threshold: 50 });
|
|
@@ -666,7 +985,7 @@ async function runAgentLoop(messages, config) {
|
|
|
666
985
|
let streamUsage = null;
|
|
667
986
|
const result = streamText({
|
|
668
987
|
model: config.model,
|
|
669
|
-
system: config.systemPrompt
|
|
988
|
+
system: `${config.systemPrompt}${templateFormatHint}`,
|
|
670
989
|
messages: history,
|
|
671
990
|
tools,
|
|
672
991
|
maxSteps: 1
|
|
@@ -705,8 +1024,67 @@ async function runAgentLoop(messages, config) {
|
|
|
705
1024
|
if (streamUsage) {
|
|
706
1025
|
totalPromptTokens += streamUsage.promptTokens ?? 0;
|
|
707
1026
|
totalCompletionTokens += streamUsage.completionTokens ?? 0;
|
|
1027
|
+
void recordShadowUsage(
|
|
1028
|
+
config.model,
|
|
1029
|
+
streamUsage
|
|
1030
|
+
);
|
|
708
1031
|
}
|
|
709
1032
|
totalToolCalls += toolCalls.length;
|
|
1033
|
+
if (toolCalls.length === 0 && fullText && correctionCfg.maxCorrectionAttempts > 0 && toolNames.length > 0) {
|
|
1034
|
+
const directExtracted = extractToolCallsFromText(fullText, toolNames);
|
|
1035
|
+
const templateExtracted = correctionCfg.useTemplateFormat ? extractTemplateToolCalls(fullText, toolNames) : [];
|
|
1036
|
+
const shouldCorrect = looksLikeFailedToolCall(fullText, toolNames) || directExtracted.length > 0 || templateExtracted.length > 0 || intentResult === "yes" && totalToolCalls === 0;
|
|
1037
|
+
if (shouldCorrect) {
|
|
1038
|
+
const corrected = directExtracted.length > 0 ? directExtracted : templateExtracted.length > 0 ? templateExtracted : await correctionPass(
|
|
1039
|
+
config.model,
|
|
1040
|
+
fullText,
|
|
1041
|
+
toolNames,
|
|
1042
|
+
history,
|
|
1043
|
+
correctionCfg.maxCorrectionAttempts
|
|
1044
|
+
);
|
|
1045
|
+
totalCorrectionAttempts++;
|
|
1046
|
+
if (corrected && corrected.length > 0) {
|
|
1047
|
+
config.onCorrection?.(totalCorrectionAttempts, true);
|
|
1048
|
+
for (const tc of corrected) {
|
|
1049
|
+
toolCalls.push({
|
|
1050
|
+
toolCallId: tc.id,
|
|
1051
|
+
toolName: tc.name,
|
|
1052
|
+
args: tc.args
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
totalToolCalls += corrected.length;
|
|
1056
|
+
for (const tc of corrected) {
|
|
1057
|
+
const toolDef = tools[tc.name];
|
|
1058
|
+
if (!toolDef) {
|
|
1059
|
+
toolResults.push({
|
|
1060
|
+
toolCallId: tc.id,
|
|
1061
|
+
result: { content: `Tool "${tc.name}" not found.`, isError: true }
|
|
1062
|
+
});
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
1065
|
+
try {
|
|
1066
|
+
const result2 = await toolDef.execute(tc.args);
|
|
1067
|
+
toolResults.push({ toolCallId: tc.id, result: result2 });
|
|
1068
|
+
config.onToolCall?.(tc.name, tc.args);
|
|
1069
|
+
config.onToolResult?.(
|
|
1070
|
+
tc.name,
|
|
1071
|
+
result2?.content ?? String(result2),
|
|
1072
|
+
result2?.isError ?? false
|
|
1073
|
+
);
|
|
1074
|
+
} catch (err) {
|
|
1075
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1076
|
+
toolResults.push({
|
|
1077
|
+
toolCallId: tc.id,
|
|
1078
|
+
result: { content: `Error: ${msg}`, isError: true }
|
|
1079
|
+
});
|
|
1080
|
+
config.onToolResult?.(tc.name, `Error: ${msg}`, true);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
} else {
|
|
1084
|
+
config.onCorrection?.(totalCorrectionAttempts, false);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
710
1088
|
if (toolCalls.length > 0) {
|
|
711
1089
|
let hasRepeatedError = false;
|
|
712
1090
|
for (const tr of toolResults) {
|
|
@@ -772,6 +1150,7 @@ async function runAgentLoop(messages, config) {
|
|
|
772
1150
|
iterations,
|
|
773
1151
|
toolCallCount: totalToolCalls,
|
|
774
1152
|
compressed: wasCompressed,
|
|
1153
|
+
correctionAttempts: totalCorrectionAttempts,
|
|
775
1154
|
usage: {
|
|
776
1155
|
promptTokens: totalPromptTokens,
|
|
777
1156
|
completionTokens: totalCompletionTokens,
|
|
@@ -787,6 +1166,7 @@ async function runAgentLoop(messages, config) {
|
|
|
787
1166
|
iterations,
|
|
788
1167
|
toolCallCount: totalToolCalls,
|
|
789
1168
|
compressed: wasCompressed,
|
|
1169
|
+
correctionAttempts: totalCorrectionAttempts,
|
|
790
1170
|
usage: {
|
|
791
1171
|
promptTokens: totalPromptTokens,
|
|
792
1172
|
completionTokens: totalCompletionTokens,
|
|
@@ -794,7 +1174,7 @@ async function runAgentLoop(messages, config) {
|
|
|
794
1174
|
}
|
|
795
1175
|
};
|
|
796
1176
|
}
|
|
797
|
-
async function buildSystemPrompt(projectRoot, _modelId) {
|
|
1177
|
+
async function buildSystemPrompt(projectRoot, _modelId, toolContext) {
|
|
798
1178
|
const sections = [
|
|
799
1179
|
// =========================================================
|
|
800
1180
|
// CACHEABLE PREFIX — stable across turns, sessions, users.
|
|
@@ -805,7 +1185,7 @@ async function buildSystemPrompt(projectRoot, _modelId) {
|
|
|
805
1185
|
() => [
|
|
806
1186
|
"You are Notch, an expert AI coding assistant built by Driftrail.",
|
|
807
1187
|
"You help developers write, debug, refactor, and understand code.",
|
|
808
|
-
"You have access to tools
|
|
1188
|
+
"You have access to the tools listed in the Tool Schemas section."
|
|
809
1189
|
].join("\n")
|
|
810
1190
|
),
|
|
811
1191
|
safeSection(
|
|
@@ -841,8 +1221,11 @@ async function buildSystemPrompt(projectRoot, _modelId) {
|
|
|
841
1221
|
"- When the user asks for a fix, fix the root cause, not the symptom."
|
|
842
1222
|
].join("\n")
|
|
843
1223
|
),
|
|
844
|
-
|
|
845
|
-
|
|
1224
|
+
DANGEROUS_uncachedSystemPromptSection(
|
|
1225
|
+
"tool-schemas",
|
|
1226
|
+
() => describeToolSchemas(toolContext),
|
|
1227
|
+
"The active tool set depends on coordinator mode, MCP servers, plugins, and permissions for this session."
|
|
1228
|
+
),
|
|
846
1229
|
safeSection("builtin-agents", () => {
|
|
847
1230
|
try {
|
|
848
1231
|
const agents = listBuiltinAgents();
|
|
@@ -1671,8 +2054,7 @@ ${toolContext.cwd}
|
|
|
1671
2054
|
## Repository
|
|
1672
2055
|
${repoContext}
|
|
1673
2056
|
|
|
1674
|
-
|
|
1675
|
-
${describeTools()}
|
|
2057
|
+
${describeToolSchemas(toolContext)}
|
|
1676
2058
|
|
|
1677
2059
|
## Instructions
|
|
1678
2060
|
1. Read relevant files to understand the current state
|
|
@@ -5403,20 +5785,20 @@ async function ensureGitignoreEntries(p, entries, sectionTitle, ctx) {
|
|
|
5403
5785
|
ctx.log(chalk8.green(` Updated .gitignore (+${missing.length})`));
|
|
5404
5786
|
}
|
|
5405
5787
|
var DEFAULT_CONFIG = {
|
|
5406
|
-
model: "
|
|
5788
|
+
model: "openrouter/anthropic/claude-sonnet-4-6",
|
|
5407
5789
|
temperature: 0.3,
|
|
5408
5790
|
maxIterations: 25,
|
|
5409
5791
|
useRepoMap: true,
|
|
5410
5792
|
renderMarkdown: true,
|
|
5411
5793
|
permissionMode: "auto",
|
|
5412
|
-
//
|
|
5413
|
-
// Uncomment `
|
|
5794
|
+
// Provider passthrough — disabled by default.
|
|
5795
|
+
// Uncomment `provider` below (and remove the "_" prefix) to route every
|
|
5414
5796
|
// request to an OpenAI-compatible endpoint (OpenAI / Anthropic /
|
|
5415
|
-
// OpenRouter / Together / Fireworks / Groq / Ollama / vLLM / LM Studio
|
|
5797
|
+
// OpenRouter / Google / DeepSeek / Together / Fireworks / Groq / Ollama / vLLM / LM Studio
|
|
5416
5798
|
// / custom). Providers read their key from the corresponding env var —
|
|
5417
5799
|
// e.g. OPENROUTER_API_KEY, ANTHROPIC_API_KEY, OPENAI_API_KEY.
|
|
5418
5800
|
// Run `notch --list-providers` to see all built-in ids.
|
|
5419
|
-
|
|
5801
|
+
_providerExample: {
|
|
5420
5802
|
provider: "openrouter",
|
|
5421
5803
|
model: "anthropic/claude-sonnet-4-6"
|
|
5422
5804
|
}
|
|
@@ -7766,8 +8148,7 @@ function buildCompleter(cwd) {
|
|
|
7766
8148
|
}
|
|
7767
8149
|
if (line.startsWith("/model ")) {
|
|
7768
8150
|
const partial = line.slice(7);
|
|
7769
|
-
const
|
|
7770
|
-
const allNames = [...MODEL_IDS, ...modelNames];
|
|
8151
|
+
const allNames = listByokProviders().filter((p) => p.defaultModel).map((p) => `${p.id}/${p.defaultModel}`);
|
|
7771
8152
|
const matches = allNames.filter((m) => m.startsWith(partial));
|
|
7772
8153
|
return [matches.map((m) => `/model ${m}`), line];
|
|
7773
8154
|
}
|
|
@@ -7818,10 +8199,9 @@ var SLASH_COMMANDS = [
|
|
|
7818
8199
|
{ name: "/quit", description: "Exit Notch", category: "Core" },
|
|
7819
8200
|
// Model & Status
|
|
7820
8201
|
{ name: "/model", description: "Switch or list models", category: "Model" },
|
|
7821
|
-
{ name: "/model download", description: "Download a Notch model locally", category: "Model" },
|
|
7822
|
-
{ name: "/downloads", description: "Show local model download progress", category: "Model" },
|
|
7823
8202
|
{ name: "/status", description: "Check API endpoint health", category: "Model" },
|
|
7824
|
-
{ name: "/
|
|
8203
|
+
{ name: "/providers", description: "List model providers", category: "Model" },
|
|
8204
|
+
{ name: "/sync-keys", description: "Pull provider keys from freesyntax.dev", category: "Model" },
|
|
7825
8205
|
// Session
|
|
7826
8206
|
{ name: "/save", description: "Save current session", category: "Session" },
|
|
7827
8207
|
{ name: "/sessions", description: "List saved sessions", category: "Session" },
|
|
@@ -8352,22 +8732,21 @@ import fs20 from "fs/promises";
|
|
|
8352
8732
|
import { createRequire as createRequire2 } from "module";
|
|
8353
8733
|
var _require2 = createRequire2(import.meta.url);
|
|
8354
8734
|
var VERSION = _require2("../package.json").version;
|
|
8355
|
-
var modelChoices = MODEL_IDS.join(", ");
|
|
8356
8735
|
if (process.argv[2] === "update") {
|
|
8357
8736
|
await runUpdateCli(process.argv.slice(3));
|
|
8358
8737
|
process.exit(process.exitCode ?? 0);
|
|
8359
8738
|
}
|
|
8360
8739
|
if (process.argv[2] === "ollama") {
|
|
8361
|
-
const { runOllamaCli } = await import("./ollama-launch-
|
|
8740
|
+
const { runOllamaCli } = await import("./ollama-launch-3IKB2A3Z.js");
|
|
8362
8741
|
const code = await runOllamaCli(process.argv.slice(3), process.cwd());
|
|
8363
8742
|
process.exit(code);
|
|
8364
8743
|
}
|
|
8365
8744
|
if (process.argv[2] === "config") {
|
|
8366
|
-
const { runConfigCli } = await import("./config-set-
|
|
8745
|
+
const { runConfigCli } = await import("./config-set-5F4VK7IT.js");
|
|
8367
8746
|
const code = await runConfigCli(process.argv.slice(3), process.cwd());
|
|
8368
8747
|
process.exit(code);
|
|
8369
8748
|
}
|
|
8370
|
-
var program = new Command().name("notch").description("Notch CLI \u2014 AI-powered coding assistant by Driftrail").version(VERSION).argument("[prompt...]", "One-shot prompt (runs once and exits)").option(
|
|
8749
|
+
var program = new Command().name("notch").description("Notch CLI \u2014 AI-powered coding assistant by Driftrail").version(VERSION).argument("[prompt...]", "One-shot prompt (runs once and exits)").option("-m, --model <model>", "Model id, preferably provider/model (for example openrouter/anthropic/claude-sonnet-4-6)").option("--base-url <url>", "Override the provider base URL (for custom OpenAI-compatible endpoints)").option("--api-key <key>", "API key for the active provider (prefer provider env vars such as OPENROUTER_API_KEY)").option("--provider <id>", "Provider id (openai, anthropic, openrouter, google, deepseek, together, fireworks, groq, ollama, lmstudio, vllm, custom).").option("--list-providers", "List built-in providers and their API-key env vars, then exit").option("--no-repo-map", "Disable automatic repository mapping").option("--no-markdown", "Disable markdown rendering in output").option("--max-iterations <n>", "Max tool-call rounds per turn", "25").option("-y, --yes", "Auto-confirm destructive actions").option("--trust", "Trust mode \u2014 auto-allow all tool calls").option("--theme <theme>", `UI color theme (${THEME_IDS.join(", ")})`).option("--resume", "Resume the last session for this project").option("--session <id>", "Resume a specific session by ID").option("--cwd <dir>", "Set working directory").option("--json", "Emit JSONL event stream on stdout (headless/CI mode)").option("--output-schema <file>", "Path to JSON Schema constraining the final structured output").option("--output-last-message <file>", "Write the final assistant message to this file on exit").option("--guardian", "Enable Guardian risk scoring when a provider-backed guardian model is configured").option("--coordinator", "Coordinator mode: top-level agent can only spawn/continue/stop workers (plus read/grep/glob). All real work is delegated.").option("--no-auto-dream", "Disable the background memory-consolidation daemon (default: enabled in REPL)").option("--no-update", "Disable the background update check on launch (equivalent to NOTCH_AUTO_UPDATE=0)").option("--update-channel <name>", "npm dist-tag to follow for updates (latest | next | beta)").option(
|
|
8371
8750
|
"--image <path>",
|
|
8372
8751
|
"Attach an image (file path, URL, or data URL). Repeatable.",
|
|
8373
8752
|
(val, prev) => prev ? [...prev, val] : [val],
|
|
@@ -8377,22 +8756,29 @@ var opts = program.opts();
|
|
|
8377
8756
|
var promptArgs = program.args;
|
|
8378
8757
|
function printByokProviderList() {
|
|
8379
8758
|
const t = theme();
|
|
8380
|
-
console.log(t.dim("\n
|
|
8381
|
-
const
|
|
8759
|
+
console.log(t.dim("\n Model providers (point --provider at any of these):\n"));
|
|
8760
|
+
const providers = listByokProviders();
|
|
8761
|
+
const idWidth = Math.max(12, ...providers.map((p) => p.id.length));
|
|
8762
|
+
const labelWidth = Math.max(20, ...providers.map((p) => p.label.length));
|
|
8763
|
+
const envWidth = Math.max(22, ...providers.map((p) => (p.apiKeyEnv || "(none)").length));
|
|
8764
|
+
const keyWidth = 7;
|
|
8765
|
+
const header = ` ${"id".padEnd(idWidth)} ${"label".padEnd(labelWidth)} ${"env var".padEnd(envWidth)} ${"key".padEnd(keyWidth)} default model`;
|
|
8382
8766
|
console.log(t.dim(header));
|
|
8383
8767
|
console.log(t.dim(` ${"-".repeat(header.length - 2)}`));
|
|
8384
|
-
for (const p of
|
|
8385
|
-
const
|
|
8768
|
+
for (const p of providers) {
|
|
8769
|
+
const keyLabel = p.apiKeyEnv ? process.env[p.apiKeyEnv] ? "set" : p.fallbackApiKey ? "local" : "missing" : "none";
|
|
8770
|
+
const paddedKey = keyLabel.padEnd(keyWidth);
|
|
8771
|
+
const keyPresent = keyLabel === "set" ? t.success(paddedKey) : keyLabel === "missing" ? t.dim(paddedKey) : t.dim(paddedKey);
|
|
8386
8772
|
const envDisplay = p.apiKeyEnv || "(none)";
|
|
8387
8773
|
console.log(
|
|
8388
|
-
` ${t.brand(p.id.padEnd(
|
|
8774
|
+
` ${t.brand(p.id.padEnd(idWidth))} ${p.label.padEnd(labelWidth)} ${envDisplay.padEnd(envWidth)} ${keyPresent} ${t.dim(p.defaultModel || "\u2014")}`
|
|
8389
8775
|
);
|
|
8390
8776
|
}
|
|
8391
8777
|
console.log("");
|
|
8392
8778
|
console.log(t.dim(" Use it like:"));
|
|
8393
8779
|
console.log(t.dim(" export OPENROUTER_API_KEY=sk-or-..."));
|
|
8394
|
-
console.log(t.dim(" notch --provider openrouter --model anthropic/claude-
|
|
8395
|
-
console.log(t.dim(" notch --model openrouter
|
|
8780
|
+
console.log(t.dim(" notch --provider openrouter --model anthropic/claude-sonnet-4-6"));
|
|
8781
|
+
console.log(t.dim(" notch --model openrouter/anthropic/claude-sonnet-4-6 # equivalent"));
|
|
8396
8782
|
console.log(t.dim(" notch --provider custom --base-url http://localhost:8000/v1 --model my-model"));
|
|
8397
8783
|
console.log("");
|
|
8398
8784
|
}
|
|
@@ -8414,7 +8800,8 @@ async function persistByokChoice(projectRoot, providerId, defaultModel) {
|
|
|
8414
8800
|
} catch {
|
|
8415
8801
|
}
|
|
8416
8802
|
const idToPersist = providerId === "__custom__" ? "custom" : providerId;
|
|
8417
|
-
current.
|
|
8803
|
+
current.provider = { provider: idToPersist, model: defaultModel };
|
|
8804
|
+
delete current.byok;
|
|
8418
8805
|
await fs20.writeFile(p, JSON.stringify(current, null, 2) + "\n", "utf-8");
|
|
8419
8806
|
} catch {
|
|
8420
8807
|
}
|
|
@@ -8428,14 +8815,12 @@ function interactiveModelPicker(activeModel) {
|
|
|
8428
8815
|
return new Promise((resolve3) => {
|
|
8429
8816
|
const t = theme();
|
|
8430
8817
|
const rows = [];
|
|
8431
|
-
rows.push({ kind: "notch-header" });
|
|
8432
|
-
for (const id of MODEL_IDS) rows.push({ kind: "notch", id });
|
|
8433
8818
|
rows.push({ kind: "byok-header" });
|
|
8434
8819
|
for (const p of listByokProviders()) rows.push({ kind: "byok", provider: p });
|
|
8435
|
-
const selectableIndexes = rows.map((r, i) => r.kind === "
|
|
8820
|
+
const selectableIndexes = rows.map((r, i) => r.kind === "byok" ? i : -1).filter((i) => i >= 0);
|
|
8436
8821
|
let cursor = selectableIndexes.find((i) => {
|
|
8437
8822
|
const r = rows[i];
|
|
8438
|
-
return r && r.kind === "
|
|
8823
|
+
return r && r.kind === "byok" && typeof activeModel === "string" && isByokRef(activeModel) && parseByokRef(activeModel).provider === r.provider.id;
|
|
8439
8824
|
}) ?? selectableIndexes[0] ?? 0;
|
|
8440
8825
|
const rowCount = rows.length;
|
|
8441
8826
|
const headerLines = 2;
|
|
@@ -8452,46 +8837,31 @@ function interactiveModelPicker(activeModel) {
|
|
|
8452
8837
|
console.log(t.dim(" Select a model (\u2191\u2193 to move, Enter to select, Esc to cancel)\n"));
|
|
8453
8838
|
for (let i = 0; i < rows.length; i++) {
|
|
8454
8839
|
const row = rows[i];
|
|
8455
|
-
if (row.kind === "notch-header") {
|
|
8456
|
-
console.log(` ${t.dim("\u2500\u2500\u2500 Notch models (default) \u2500\u2500\u2500")}`);
|
|
8457
|
-
continue;
|
|
8458
|
-
}
|
|
8459
8840
|
if (row.kind === "byok-header") {
|
|
8460
|
-
console.log(` ${t.dim("
|
|
8841
|
+
console.log(` ${t.dim("--- Providers ---")}`);
|
|
8461
8842
|
continue;
|
|
8462
8843
|
}
|
|
8463
8844
|
const isSelected = i === cursor;
|
|
8464
8845
|
const pointer = isSelected ? t.brand("\u276F") : " ";
|
|
8465
|
-
|
|
8466
|
-
|
|
8467
|
-
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
|
|
8472
|
-
|
|
8473
|
-
|
|
8474
|
-
|
|
8475
|
-
|
|
8476
|
-
|
|
8477
|
-
const dot = isCurrent ? t.success("\u25CF") : " ";
|
|
8478
|
-
const envHit = p.apiKeyEnv ? Boolean(process.env[p.apiKeyEnv]) : false;
|
|
8479
|
-
let syncHit = false;
|
|
8480
|
-
if (!envHit) {
|
|
8481
|
-
try {
|
|
8482
|
-
const { loadSyncedByokKeysSync } = (init_auth(), __toCommonJS(auth_exports));
|
|
8483
|
-
const synced = loadSyncedByokKeysSync();
|
|
8484
|
-
const fromSync = synced?.keys[p.id];
|
|
8485
|
-
syncHit = typeof fromSync === "string" && fromSync.length > 0;
|
|
8486
|
-
} catch {
|
|
8487
|
-
}
|
|
8846
|
+
const p = row.provider;
|
|
8847
|
+
const isCurrent = typeof activeModel === "string" && isByokRef(activeModel) ? parseByokRef(activeModel).provider === p.id : false;
|
|
8848
|
+
const dot = isCurrent ? t.success("\u25CF") : " ";
|
|
8849
|
+
const envHit = p.apiKeyEnv ? Boolean(process.env[p.apiKeyEnv]) : false;
|
|
8850
|
+
let syncHit = false;
|
|
8851
|
+
if (!envHit) {
|
|
8852
|
+
try {
|
|
8853
|
+
const { loadSyncedByokKeysSync } = (init_auth(), __toCommonJS(auth_exports));
|
|
8854
|
+
const synced = loadSyncedByokKeysSync();
|
|
8855
|
+
const fromSync = synced?.keys[p.id];
|
|
8856
|
+
syncHit = typeof fromSync === "string" && fromSync.length > 0;
|
|
8857
|
+
} catch {
|
|
8488
8858
|
}
|
|
8489
|
-
const keyPresent = p.apiKeyEnv ? envHit ? t.success("\u2713") : syncHit ? t.brand("\u2713") : t.dim("\u2717") : t.dim("\u2013");
|
|
8490
|
-
const label = isSelected ? t.bold(p.label) : t.dim(p.label);
|
|
8491
|
-
const envDisplay = t.dim((p.apiKeyEnv || "local").padEnd(22));
|
|
8492
|
-
const defModel = t.dim(p.defaultModel ? p.defaultModel.slice(0, 34) : "\u2014");
|
|
8493
|
-
console.log(` ${pointer} ${dot} ${t.brand(p.id.padEnd(12))} ${label.padEnd(20)} ${envDisplay} ${keyPresent} ${defModel}`);
|
|
8494
8859
|
}
|
|
8860
|
+
const keyPresent = p.apiKeyEnv ? envHit ? t.success("\u2713") : syncHit ? t.brand("\u2713") : t.dim("\u2717") : t.dim("\u2013");
|
|
8861
|
+
const label = isSelected ? t.bold(p.label) : t.dim(p.label);
|
|
8862
|
+
const envDisplay = t.dim((p.apiKeyEnv || "local").padEnd(22));
|
|
8863
|
+
const defModel = t.dim(p.defaultModel ? p.defaultModel.slice(0, 34) : "\u2014");
|
|
8864
|
+
console.log(` ${pointer} ${dot} ${t.brand(p.id.padEnd(12))} ${label.padEnd(20)} ${envDisplay} ${keyPresent} ${defModel}`);
|
|
8495
8865
|
}
|
|
8496
8866
|
};
|
|
8497
8867
|
const render = (first) => {
|
|
@@ -8517,16 +8887,12 @@ function interactiveModelPicker(activeModel) {
|
|
|
8517
8887
|
} else if (s === "\r" || s === "\n") {
|
|
8518
8888
|
const row = rows[cursor];
|
|
8519
8889
|
cleanup();
|
|
8520
|
-
if (!row || row.kind !== "
|
|
8890
|
+
if (!row || row.kind !== "byok") {
|
|
8521
8891
|
resolve3(null);
|
|
8522
8892
|
return;
|
|
8523
8893
|
}
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
} else {
|
|
8527
|
-
const modelRef = `${row.provider.id}:${row.provider.defaultModel}`;
|
|
8528
|
-
resolve3({ kind: "byok", provider: row.provider, modelRef });
|
|
8529
|
-
}
|
|
8894
|
+
const modelRef = `${row.provider.id}/${row.provider.defaultModel}`;
|
|
8895
|
+
resolve3({ kind: "byok", provider: row.provider, modelRef });
|
|
8530
8896
|
} else if (s === "\x1B" || s === "") {
|
|
8531
8897
|
cleanup();
|
|
8532
8898
|
resolve3(null);
|
|
@@ -8543,13 +8909,11 @@ function interactiveModelPicker(activeModel) {
|
|
|
8543
8909
|
function printHelp() {
|
|
8544
8910
|
console.log(chalk30.gray(`
|
|
8545
8911
|
Commands:
|
|
8546
|
-
/model \u2014
|
|
8547
|
-
/model <
|
|
8548
|
-
/
|
|
8549
|
-
/
|
|
8550
|
-
/
|
|
8551
|
-
/providers \u2014 List built-in BYOK providers and whether their keys are set
|
|
8552
|
-
/status \u2014 Check backend health (Notch API or BYOK endpoint)
|
|
8912
|
+
/model \u2014 Pick a provider and default model
|
|
8913
|
+
/model <ref> \u2014 Switch model: /model openrouter/anthropic/claude-sonnet-4-6
|
|
8914
|
+
/sync-keys \u2014 Pull provider keys you added on freesyntax.dev
|
|
8915
|
+
/providers \u2014 List built-in providers and whether their keys are set
|
|
8916
|
+
/status \u2014 Check active provider health
|
|
8553
8917
|
/undo \u2014 Undo last file changes
|
|
8554
8918
|
/usage \u2014 Show token usage + context meter
|
|
8555
8919
|
/cost \u2014 Show estimated session cost
|
|
@@ -8656,16 +9020,16 @@ async function main() {
|
|
|
8656
9020
|
const providerCount = Object.keys(synced.keys).length;
|
|
8657
9021
|
if (providerCount > 0) {
|
|
8658
9022
|
console.log(chalk30.gray(
|
|
8659
|
-
` Synced ${providerCount}
|
|
9023
|
+
` Synced ${providerCount} provider key(s) from freesyntax.dev \u2192 ${(await import("./auth-UAMMP5IJ.js")).getByokSyncPath()}`
|
|
8660
9024
|
));
|
|
8661
9025
|
} else {
|
|
8662
9026
|
console.log(chalk30.gray(
|
|
8663
|
-
` No
|
|
9027
|
+
` No provider keys on your profile yet \u2014 add them at freesyntax.dev/settings/keys then run ${chalk30.white("notch sync-keys")}.`
|
|
8664
9028
|
));
|
|
8665
9029
|
}
|
|
8666
9030
|
} catch (syncErr) {
|
|
8667
9031
|
console.log(chalk30.yellow(
|
|
8668
|
-
` (
|
|
9032
|
+
` (provider key sync skipped: ${syncErr.message.slice(0, 120)})`
|
|
8669
9033
|
));
|
|
8670
9034
|
}
|
|
8671
9035
|
console.log("");
|
|
@@ -8684,7 +9048,7 @@ async function main() {
|
|
|
8684
9048
|
console.log(chalk30.gray("\n Not signed in. Run: notch login\n"));
|
|
8685
9049
|
return;
|
|
8686
9050
|
}
|
|
8687
|
-
const spinner = ora7("Pulling
|
|
9051
|
+
const spinner = ora7("Pulling provider keys from freesyntax.dev...").start();
|
|
8688
9052
|
try {
|
|
8689
9053
|
const { syncByokKeys, getByokSyncPath } = await import("./auth-UAMMP5IJ.js");
|
|
8690
9054
|
const synced = await syncByokKeys(creds.token);
|
|
@@ -8692,7 +9056,7 @@ async function main() {
|
|
|
8692
9056
|
const providers = Object.keys(synced.keys);
|
|
8693
9057
|
if (providers.length === 0) {
|
|
8694
9058
|
console.log(chalk30.gray(`
|
|
8695
|
-
No
|
|
9059
|
+
No provider keys on your profile yet.`));
|
|
8696
9060
|
console.log(chalk30.gray(` Add them at ${chalk30.white("https://freesyntax.dev/settings/keys")} then rerun.
|
|
8697
9061
|
`));
|
|
8698
9062
|
} else {
|
|
@@ -8737,7 +9101,7 @@ async function main() {
|
|
|
8737
9101
|
return;
|
|
8738
9102
|
}
|
|
8739
9103
|
if (promptArgs[0] === "mcp-serve" || promptArgs[0] === "mcp-server") {
|
|
8740
|
-
const { runMcpServer } = await import("./server-
|
|
9104
|
+
const { runMcpServer } = await import("./server-GMF4WV67.js");
|
|
8741
9105
|
await runMcpServer({
|
|
8742
9106
|
cwd: opts.cwd ?? process.cwd(),
|
|
8743
9107
|
version: VERSION,
|
|
@@ -8776,7 +9140,7 @@ async function main() {
|
|
|
8776
9140
|
const providerId = opts.provider === "custom" ? "__custom__" : opts.provider;
|
|
8777
9141
|
const byokInfo = findByokProvider(providerId);
|
|
8778
9142
|
if (!byokInfo) {
|
|
8779
|
-
console.error(chalk30.red(` Unknown
|
|
9143
|
+
console.error(chalk30.red(` Unknown provider: ${opts.provider}`));
|
|
8780
9144
|
console.error(chalk30.gray(` Run notch --list-providers to see built-ins, or pass --provider custom --base-url <url>.`));
|
|
8781
9145
|
process.exit(1);
|
|
8782
9146
|
}
|
|
@@ -8793,7 +9157,7 @@ async function main() {
|
|
|
8793
9157
|
} else if (isByokRef(opts.model)) {
|
|
8794
9158
|
const { provider } = parseByokRef(opts.model);
|
|
8795
9159
|
if (!findByokProvider(provider)) {
|
|
8796
|
-
console.error(chalk30.red(` Unknown
|
|
9160
|
+
console.error(chalk30.red(` Unknown provider in model ref: ${provider}`));
|
|
8797
9161
|
console.error(chalk30.gray(` Run notch --list-providers to see built-ins.`));
|
|
8798
9162
|
process.exit(1);
|
|
8799
9163
|
}
|
|
@@ -8801,8 +9165,8 @@ async function main() {
|
|
|
8801
9165
|
config.models.chat.byokProvider = void 0;
|
|
8802
9166
|
} else {
|
|
8803
9167
|
console.error(chalk30.red(` Unknown model: ${opts.model}`));
|
|
8804
|
-
console.error(chalk30.gray(
|
|
8805
|
-
console.error(chalk30.gray(
|
|
9168
|
+
console.error(chalk30.gray(" Use provider/model, for example openrouter/anthropic/claude-sonnet-4-6."));
|
|
9169
|
+
console.error(chalk30.gray(" Run notch --list-providers to see built-ins."));
|
|
8806
9170
|
process.exit(1);
|
|
8807
9171
|
}
|
|
8808
9172
|
}
|
|
@@ -8856,7 +9220,7 @@ async function main() {
|
|
|
8856
9220
|
if (err instanceof ByokMissingApiKeyError) {
|
|
8857
9221
|
printWordmark(VERSION);
|
|
8858
9222
|
const p = err.provider;
|
|
8859
|
-
console.log(` To use ${p.label}
|
|
9223
|
+
console.log(` To use ${p.label} you need an API key.`);
|
|
8860
9224
|
console.log("");
|
|
8861
9225
|
if (p.apiKeyEnv) {
|
|
8862
9226
|
console.log(" \x1B[1mOption 1:\x1B[0m Set the env var");
|
|
@@ -8883,9 +9247,9 @@ async function main() {
|
|
|
8883
9247
|
console.log(" \x1B[1mOption 3:\x1B[0m Pass it inline");
|
|
8884
9248
|
console.log(" \x1B[33m$ notch --api-key your-key-here\x1B[0m");
|
|
8885
9249
|
console.log("");
|
|
8886
|
-
console.log(" \x1B[1mOr:\x1B[0m
|
|
9250
|
+
console.log(" \x1B[1mOr:\x1B[0m Use any provider key (OpenAI, Anthropic, OpenRouter, ...)");
|
|
8887
9251
|
console.log(" \x1B[33m$ notch --list-providers\x1B[0m");
|
|
8888
|
-
console.log(" \x1B[33m$ notch --
|
|
9252
|
+
console.log(" \x1B[33m$ notch --model openrouter/anthropic/claude-sonnet-4-6\x1B[0m");
|
|
8889
9253
|
console.log("");
|
|
8890
9254
|
console.log(" Get your Notch key at: \x1B[4mhttps://freesyntax.dev/settings\x1B[0m");
|
|
8891
9255
|
console.log("");
|
|
@@ -8896,7 +9260,7 @@ async function main() {
|
|
|
8896
9260
|
const info = activeByok ? {
|
|
8897
9261
|
id: activeModelId,
|
|
8898
9262
|
label: `${activeByok.label}`,
|
|
8899
|
-
size: "
|
|
9263
|
+
size: "provider",
|
|
8900
9264
|
gpu: activeByok.id,
|
|
8901
9265
|
contextWindow: 128e3,
|
|
8902
9266
|
maxOutputTokens: 8192,
|
|
@@ -8976,7 +9340,6 @@ async function main() {
|
|
|
8976
9340
|
spinner.warn("Could not build repo map");
|
|
8977
9341
|
}
|
|
8978
9342
|
}
|
|
8979
|
-
const baseSystemPrompt = await buildSystemPrompt(config.projectRoot, activeModelId);
|
|
8980
9343
|
let outputSchema = null;
|
|
8981
9344
|
if (opts.outputSchema) {
|
|
8982
9345
|
try {
|
|
@@ -8991,19 +9354,6 @@ async function main() {
|
|
|
8991
9354
|
}
|
|
8992
9355
|
}
|
|
8993
9356
|
const coordinatorMode = !!opts.coordinator || isCoordinatorModeEnv();
|
|
8994
|
-
const systemPrompt = coordinatorMode ? [
|
|
8995
|
-
COORDINATOR_SYSTEM_PROMPT,
|
|
8996
|
-
repoMapStr ? `
|
|
8997
|
-
## Repository Map
|
|
8998
|
-
${repoMapStr}` : "",
|
|
8999
|
-
outputSchema ? schemaInstructions(outputSchema) : ""
|
|
9000
|
-
].join("") : [
|
|
9001
|
-
baseSystemPrompt,
|
|
9002
|
-
repoMapStr ? `
|
|
9003
|
-
## Repository Map
|
|
9004
|
-
${repoMapStr}` : "",
|
|
9005
|
-
outputSchema ? schemaInstructions(outputSchema) : ""
|
|
9006
|
-
].join("");
|
|
9007
9357
|
if (coordinatorMode && !jsonMode) {
|
|
9008
9358
|
console.log(
|
|
9009
9359
|
chalk30.green(
|
|
@@ -9029,19 +9379,11 @@ ${repoMapStr}` : "",
|
|
|
9029
9379
|
const branches = /* @__PURE__ */ new Map();
|
|
9030
9380
|
let currentBranch = "main";
|
|
9031
9381
|
const costTracker = new CostTracker();
|
|
9032
|
-
const mcpClients = [];
|
|
9033
9382
|
try {
|
|
9034
9383
|
const configRaw = await fs20.readFile(nodePath2.resolve(config.projectRoot, ".notch.json"), "utf-8").catch(() => "{}");
|
|
9035
|
-
const
|
|
9036
|
-
|
|
9037
|
-
|
|
9038
|
-
const client = new MCPClient(mcpConfig, name);
|
|
9039
|
-
await client.connect();
|
|
9040
|
-
mcpClients.push(client);
|
|
9041
|
-
console.log(chalk30.green(` MCP: Connected to ${name} (${client.tools.length} tools)`));
|
|
9042
|
-
} catch (err) {
|
|
9043
|
-
console.log(chalk30.yellow(` MCP: Could not connect to ${name}: ${err.message}`));
|
|
9044
|
-
}
|
|
9384
|
+
const toolCount = await initMCPServers(JSON.parse(configRaw));
|
|
9385
|
+
if (toolCount > 0) {
|
|
9386
|
+
console.log(chalk30.green(` MCP: Connected (${toolCount} tools)`));
|
|
9045
9387
|
}
|
|
9046
9388
|
} catch {
|
|
9047
9389
|
}
|
|
@@ -9059,13 +9401,16 @@ ${repoMapStr}` : "",
|
|
|
9059
9401
|
if (opts.guardian) {
|
|
9060
9402
|
try {
|
|
9061
9403
|
guardianModel = resolveModel({
|
|
9062
|
-
model:
|
|
9404
|
+
model: process.env.NOTCH_GUARDIAN_MODEL ?? config.models.chat.model,
|
|
9063
9405
|
apiKey: config.models.chat.apiKey,
|
|
9064
9406
|
baseUrl: process.env.NOTCH_GUARDIAN_BASE_URL,
|
|
9065
|
-
headers: config.models.chat.headers
|
|
9407
|
+
headers: config.models.chat.headers,
|
|
9408
|
+
byokProvider: config.models.chat.byokProvider,
|
|
9409
|
+
byokHeaders: config.models.chat.byokHeaders,
|
|
9410
|
+
byokApiShape: config.models.chat.byokApiShape
|
|
9066
9411
|
});
|
|
9067
9412
|
if (!jsonMode) {
|
|
9068
|
-
console.log(chalk30.green(
|
|
9413
|
+
console.log(chalk30.green(` Guardian: enabled (${process.env.NOTCH_GUARDIAN_MODEL ?? config.models.chat.model})`));
|
|
9069
9414
|
}
|
|
9070
9415
|
} catch (err) {
|
|
9071
9416
|
if (!jsonMode) {
|
|
@@ -9110,6 +9455,28 @@ ${repoMapStr}` : "",
|
|
|
9110
9455
|
}
|
|
9111
9456
|
}
|
|
9112
9457
|
};
|
|
9458
|
+
const baseSystemPrompt = await buildSystemPrompt(config.projectRoot, activeModelId, toolCtx);
|
|
9459
|
+
const systemPrompt = coordinatorMode ? [
|
|
9460
|
+
COORDINATOR_SYSTEM_PROMPT,
|
|
9461
|
+
"\n\n",
|
|
9462
|
+
baseSystemPrompt,
|
|
9463
|
+
repoMapStr ? `
|
|
9464
|
+
|
|
9465
|
+
## Repository Map
|
|
9466
|
+
${repoMapStr}` : "",
|
|
9467
|
+
outputSchema ? `
|
|
9468
|
+
|
|
9469
|
+
${schemaInstructions(outputSchema)}` : ""
|
|
9470
|
+
].join("") : [
|
|
9471
|
+
baseSystemPrompt,
|
|
9472
|
+
repoMapStr ? `
|
|
9473
|
+
|
|
9474
|
+
## Repository Map
|
|
9475
|
+
${repoMapStr}` : "",
|
|
9476
|
+
outputSchema ? `
|
|
9477
|
+
|
|
9478
|
+
${schemaInstructions(outputSchema)}` : ""
|
|
9479
|
+
].join("");
|
|
9113
9480
|
await toolCtx.runHook?.("session-start", {});
|
|
9114
9481
|
const stopFileWatcher = startFileWatcher(config.projectRoot, hookConfig, (event, results) => {
|
|
9115
9482
|
for (const r of results) {
|
|
@@ -9181,9 +9548,10 @@ Analyze the above input.`;
|
|
|
9181
9548
|
attachments.push(res);
|
|
9182
9549
|
}
|
|
9183
9550
|
if (attachments.length > 0 && !modelSupportsImages(activeModelId)) {
|
|
9184
|
-
const msg = `Selected model "${activeModelId}"
|
|
9185
|
-
if (jsonMode) events.emit({ type: "
|
|
9186
|
-
else console.
|
|
9551
|
+
const msg = `Selected model "${activeModelId}" does not support image attachments. Switch to a vision-capable model.`;
|
|
9552
|
+
if (jsonMode) events.emit({ type: "error", message: msg });
|
|
9553
|
+
else console.error(chalk30.red(` ${msg}`));
|
|
9554
|
+
process.exit(1);
|
|
9187
9555
|
}
|
|
9188
9556
|
if (attachments.length > 0) {
|
|
9189
9557
|
const imageBlocks = attachments.map(imageToContentBlock);
|
|
@@ -9391,11 +9759,9 @@ Analyze the above input.`;
|
|
|
9391
9759
|
} catch {
|
|
9392
9760
|
}
|
|
9393
9761
|
}
|
|
9394
|
-
|
|
9395
|
-
|
|
9396
|
-
|
|
9397
|
-
} catch {
|
|
9398
|
-
}
|
|
9762
|
+
try {
|
|
9763
|
+
disconnectMCPServers();
|
|
9764
|
+
} catch {
|
|
9399
9765
|
}
|
|
9400
9766
|
slashMenu.cleanup();
|
|
9401
9767
|
stopActiveLoop();
|
|
@@ -9422,35 +9788,6 @@ Analyze the above input.`;
|
|
|
9422
9788
|
const picked = await interactiveModelPicker(activeModelId);
|
|
9423
9789
|
if (!picked) {
|
|
9424
9790
|
console.log(chalk30.gray(" Cancelled\n"));
|
|
9425
|
-
} else if (picked.kind === "notch") {
|
|
9426
|
-
if (picked.id === activeModelId) {
|
|
9427
|
-
console.log(chalk30.gray(` Already using ${MODEL_CATALOG[picked.id].label}
|
|
9428
|
-
`));
|
|
9429
|
-
} else {
|
|
9430
|
-
activeModelId = picked.id;
|
|
9431
|
-
config.models.chat.model = activeModelId;
|
|
9432
|
-
config.models.chat.byokProvider = void 0;
|
|
9433
|
-
try {
|
|
9434
|
-
model = resolveModel(config.models.chat);
|
|
9435
|
-
const switchedInfo = MODEL_CATALOG[picked.id];
|
|
9436
|
-
console.log(chalk30.green(` \u2713 Switched to ${switchedInfo.label} (${switchedInfo.id})`));
|
|
9437
|
-
const shortName = picked.id.replace("notch-", "");
|
|
9438
|
-
const hw = switchedInfo.hardware;
|
|
9439
|
-
console.log(chalk30.gray(
|
|
9440
|
-
` Hosted inference is live. To run locally you need ~${hw.vramGb}GB VRAM (${switchedInfo.hardware.recommendedGpu}) + ${hw.diskGb}GB disk for Q4 weights.`
|
|
9441
|
-
));
|
|
9442
|
-
if (switchedInfo.hfRepo) {
|
|
9443
|
-
console.log(chalk30.gray(` Run ${chalk30.white(`/model download ${shortName}`)} to pull them in the background.
|
|
9444
|
-
`));
|
|
9445
|
-
} else {
|
|
9446
|
-
console.log(chalk30.gray(` Local weights aren't published yet \u2014 sticking with the hosted API.
|
|
9447
|
-
`));
|
|
9448
|
-
}
|
|
9449
|
-
} catch (e) {
|
|
9450
|
-
console.log(chalk30.red(` Failed to switch: ${e.message}
|
|
9451
|
-
`));
|
|
9452
|
-
}
|
|
9453
|
-
}
|
|
9454
9791
|
} else {
|
|
9455
9792
|
activeModelId = picked.modelRef;
|
|
9456
9793
|
config.models.chat.model = picked.modelRef;
|
|
@@ -9474,12 +9811,12 @@ Analyze the above input.`;
|
|
|
9474
9811
|
rl.prompt();
|
|
9475
9812
|
return;
|
|
9476
9813
|
}
|
|
9477
|
-
if (input.startsWith("/model ")) {
|
|
9814
|
+
if (input.startsWith("/model ") && !input.startsWith("/model download")) {
|
|
9478
9815
|
const arg = input.replace("/model ", "").trim();
|
|
9479
9816
|
if (isByokRef(arg)) {
|
|
9480
9817
|
const { provider } = parseByokRef(arg);
|
|
9481
9818
|
if (!findByokProvider(provider)) {
|
|
9482
|
-
console.log(chalk30.red(` Unknown
|
|
9819
|
+
console.log(chalk30.red(` Unknown provider: ${provider}`));
|
|
9483
9820
|
console.log(chalk30.gray(` Run /providers to list built-ins.`));
|
|
9484
9821
|
rl.prompt();
|
|
9485
9822
|
return;
|
|
@@ -9498,6 +9835,29 @@ Analyze the above input.`;
|
|
|
9498
9835
|
`));
|
|
9499
9836
|
} else {
|
|
9500
9837
|
console.log(chalk30.red(` Failed to switch: ${e.message}
|
|
9838
|
+
`));
|
|
9839
|
+
}
|
|
9840
|
+
}
|
|
9841
|
+
rl.prompt();
|
|
9842
|
+
return;
|
|
9843
|
+
}
|
|
9844
|
+
const currentProvider = activeByokProvider(config.models.chat);
|
|
9845
|
+
if (currentProvider) {
|
|
9846
|
+
const providerId = currentProvider.id === "__custom__" ? "custom" : currentProvider.id;
|
|
9847
|
+
const modelRef = `${providerId}/${arg}`;
|
|
9848
|
+
activeModelId = modelRef;
|
|
9849
|
+
config.models.chat.model = modelRef;
|
|
9850
|
+
config.models.chat.byokProvider = void 0;
|
|
9851
|
+
try {
|
|
9852
|
+
model = resolveModel(config.models.chat);
|
|
9853
|
+
console.log(chalk30.green(` Switched to ${currentProvider.label} (${modelRef})
|
|
9854
|
+
`));
|
|
9855
|
+
} catch (e) {
|
|
9856
|
+
if (e instanceof ByokMissingApiKeyError) {
|
|
9857
|
+
console.log(chalk30.yellow(` \u26A0 ${e.message}
|
|
9858
|
+
`));
|
|
9859
|
+
} else {
|
|
9860
|
+
console.log(chalk30.red(` Failed to switch: ${e.message}
|
|
9501
9861
|
`));
|
|
9502
9862
|
}
|
|
9503
9863
|
}
|
|
@@ -9510,8 +9870,8 @@ Analyze the above input.`;
|
|
|
9510
9870
|
}
|
|
9511
9871
|
if (!isValidModel(newModel)) {
|
|
9512
9872
|
console.log(chalk30.red(` Unknown model: ${arg}`));
|
|
9513
|
-
console.log(chalk30.gray(
|
|
9514
|
-
console.log(chalk30.gray(
|
|
9873
|
+
console.log(chalk30.gray(" Use provider/model, for example openrouter/anthropic/claude-sonnet-4-6."));
|
|
9874
|
+
console.log(chalk30.gray(" Run /providers to list built-ins."));
|
|
9515
9875
|
rl.prompt();
|
|
9516
9876
|
return;
|
|
9517
9877
|
}
|
|
@@ -9526,52 +9886,16 @@ Analyze the above input.`;
|
|
|
9526
9886
|
return;
|
|
9527
9887
|
}
|
|
9528
9888
|
if (input.startsWith("/model download")) {
|
|
9529
|
-
|
|
9530
|
-
|
|
9531
|
-
console.log(chalk30.red(" Usage: /model download <pyre|ignis|solace|solace-lite>"));
|
|
9532
|
-
rl.prompt();
|
|
9533
|
-
return;
|
|
9534
|
-
}
|
|
9535
|
-
const normalised = arg.startsWith("notch-") ? arg : `notch-${arg}`;
|
|
9536
|
-
if (!isValidModel(normalised)) {
|
|
9537
|
-
console.log(chalk30.red(` Unknown model: ${arg}`));
|
|
9538
|
-
console.log(chalk30.gray(` Notch models: ${modelChoices}`));
|
|
9539
|
-
rl.prompt();
|
|
9540
|
-
return;
|
|
9541
|
-
}
|
|
9542
|
-
const info2 = MODEL_CATALOG[normalised];
|
|
9543
|
-
const { probeSystemCapabilities, evaluateHardware, renderVerdict, startModelDownload } = await import("./model-download-3NDKS3VM.js");
|
|
9544
|
-
const caps = await probeSystemCapabilities();
|
|
9545
|
-
const verdict = evaluateHardware(info2, caps);
|
|
9546
|
-
console.log(`
|
|
9547
|
-
${renderVerdict(info2, verdict)}
|
|
9548
|
-
`);
|
|
9549
|
-
if (!info2.hfRepo) {
|
|
9550
|
-
console.log(chalk30.yellow(
|
|
9551
|
-
` ${info2.label} isn't published to Hugging Face yet \u2014 the Notch team is still preparing merged weights for public download. Use the hosted API in the meantime (run ${chalk30.white("notch login")} if you haven't already).
|
|
9552
|
-
`
|
|
9553
|
-
));
|
|
9554
|
-
rl.prompt();
|
|
9555
|
-
return;
|
|
9556
|
-
}
|
|
9557
|
-
try {
|
|
9558
|
-
const handle = await startModelDownload(normalised);
|
|
9559
|
-
console.log(chalk30.green(` \u2193 Downloading ${info2.label} in background (pid ${handle.pid}).`));
|
|
9560
|
-
console.log(chalk30.gray(` Cache: ${handle.cacheDir}`));
|
|
9561
|
-
console.log(chalk30.gray(` Poll status with ${chalk30.white("/downloads")} \u2014 the pull keeps running even if you exit the REPL (${info2.hardware.diskGb} GB transfer).
|
|
9562
|
-
`));
|
|
9563
|
-
} catch (err) {
|
|
9564
|
-
console.log(chalk30.red(` Download failed to start: ${err.message}
|
|
9565
|
-
`));
|
|
9566
|
-
}
|
|
9889
|
+
console.log(chalk30.gray(" Direct weight downloads are no longer part of the model picker."));
|
|
9890
|
+
console.log(chalk30.gray(" Run a local provider instead, then select it with /model ollama/<model>, /model lmstudio/<model>, or /model custom/<model>.\n"));
|
|
9567
9891
|
rl.prompt();
|
|
9568
9892
|
return;
|
|
9569
9893
|
}
|
|
9570
9894
|
if (input === "/downloads") {
|
|
9571
|
-
const { listDownloads } = await import("./model-download-
|
|
9895
|
+
const { listDownloads } = await import("./model-download-KCQJCEPW.js");
|
|
9572
9896
|
const active = listDownloads();
|
|
9573
9897
|
if (active.length === 0) {
|
|
9574
|
-
console.log(chalk30.gray(" No downloads started this session
|
|
9898
|
+
console.log(chalk30.gray(" No local downloads started this session.\n"));
|
|
9575
9899
|
} else {
|
|
9576
9900
|
console.log(chalk30.gray("\n Model State Last line"));
|
|
9577
9901
|
for (const h of active) {
|
|
@@ -9594,13 +9918,13 @@ ${renderVerdict(info2, verdict)}
|
|
|
9594
9918
|
rl.prompt();
|
|
9595
9919
|
return;
|
|
9596
9920
|
}
|
|
9597
|
-
const spinner2 = ora7("Pulling
|
|
9921
|
+
const spinner2 = ora7("Pulling provider keys from freesyntax.dev...").start();
|
|
9598
9922
|
try {
|
|
9599
9923
|
const synced = await syncByokKeys(creds.token);
|
|
9600
9924
|
spinner2.stop();
|
|
9601
9925
|
const providers = Object.keys(synced.keys);
|
|
9602
9926
|
if (providers.length === 0) {
|
|
9603
|
-
console.log(chalk30.gray(` No
|
|
9927
|
+
console.log(chalk30.gray(` No provider keys on your profile yet. Add them at https://freesyntax.dev/settings/keys.
|
|
9604
9928
|
`));
|
|
9605
9929
|
} else {
|
|
9606
9930
|
console.log(chalk30.green(` \u2713 Synced ${providers.length} key(s): ${providers.join(", ")}`));
|
|
@@ -10204,9 +10528,10 @@ ${renderVerdict(info2, verdict)}
|
|
|
10204
10528
|
if (replAttachments.length > 0 && !modelSupportsImages(activeModelId)) {
|
|
10205
10529
|
console.warn(
|
|
10206
10530
|
chalk30.yellow(
|
|
10207
|
-
` \u26A0 ${activeModelId}
|
|
10531
|
+
` \u26A0 ${activeModelId} does not support image attachments. Switch with /model; skipping image(s).`
|
|
10208
10532
|
)
|
|
10209
10533
|
);
|
|
10534
|
+
replAttachments.length = 0;
|
|
10210
10535
|
}
|
|
10211
10536
|
const textForRefs = imgRefs.cleanedText || (replAttachments.length > 0 ? "Describe the attached image(s)." : "");
|
|
10212
10537
|
const { cleanInput, references } = await resolveReferences(textForRefs, config.projectRoot);
|
|
@@ -10215,6 +10540,10 @@ ${renderVerdict(info2, verdict)}
|
|
|
10215
10540
|
if (references.length > 0) {
|
|
10216
10541
|
console.log(chalk30.gray(` Injected ${references.length} reference(s)`));
|
|
10217
10542
|
}
|
|
10543
|
+
if (!finalPrompt.trim() && replAttachments.length === 0) {
|
|
10544
|
+
console.warn(chalk30.yellow(" Add a message, or switch to a vision-capable model before sending only images."));
|
|
10545
|
+
return;
|
|
10546
|
+
}
|
|
10218
10547
|
if (replAttachments.length > 0) {
|
|
10219
10548
|
const imageBlocks = replAttachments.map(imageToContentBlock);
|
|
10220
10549
|
messages.push({
|
|
@@ -10390,7 +10719,6 @@ async function handleRalphSubcommand(args, cliOpts) {
|
|
|
10390
10719
|
const config = await loadConfig(cliOpts.cwd ? { projectRoot: cliOpts.cwd } : {});
|
|
10391
10720
|
if (cliOpts.model) config.models.chat.model = cliOpts.model;
|
|
10392
10721
|
const model = resolveModel(config.models.chat);
|
|
10393
|
-
const systemPrompt = await buildSystemPrompt(config.projectRoot, config.models.chat.model);
|
|
10394
10722
|
const toolCtx = {
|
|
10395
10723
|
cwd: config.projectRoot,
|
|
10396
10724
|
requireConfirm: false,
|