@morphllm/morphsdk 0.2.45 → 0.2.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{chunk-TVFGHXPE.js → chunk-3FTAIJBH.js} +4 -4
- package/dist/{chunk-ZRLEAPZV.js → chunk-76DJEQEP.js} +4 -4
- package/dist/{chunk-W3XLPMV3.js → chunk-7HS6YXA3.js} +21 -5
- package/dist/{chunk-W3XLPMV3.js.map → chunk-7HS6YXA3.js.map} +1 -1
- package/dist/chunk-BLXC5R4W.js +82 -0
- package/dist/chunk-BLXC5R4W.js.map +1 -0
- package/dist/chunk-FA6UGPVL.js +105 -0
- package/dist/chunk-FA6UGPVL.js.map +1 -0
- package/dist/{chunk-PEGZVGG4.js → chunk-G4AWE5A2.js} +4 -4
- package/dist/{chunk-OUEJ6XEO.js → chunk-GJU7UOFL.js} +4 -4
- package/dist/{chunk-Q7PDN7TS.js → chunk-GZMUGMOZ.js} +1 -1
- package/dist/{chunk-Q7PDN7TS.js.map → chunk-GZMUGMOZ.js.map} +1 -1
- package/dist/chunk-JYBVRF72.js +1 -0
- package/dist/chunk-OOZSGWSK.js +70 -0
- package/dist/chunk-OOZSGWSK.js.map +1 -0
- package/dist/{chunk-GDR65N2J.js → chunk-OXHGFHEU.js} +53 -26
- package/dist/chunk-OXHGFHEU.js.map +1 -0
- package/dist/{chunk-VBBJGWHY.js → chunk-P2XKFWFD.js} +2 -2
- package/dist/chunk-RAKREIXE.js +76 -0
- package/dist/chunk-RAKREIXE.js.map +1 -0
- package/dist/chunk-SDI2FI6G.js +283 -0
- package/dist/chunk-SDI2FI6G.js.map +1 -0
- package/dist/{chunk-GTOXMAF2.js → chunk-SWQPIKPY.js} +44 -3
- package/dist/chunk-SWQPIKPY.js.map +1 -0
- package/dist/chunk-TJIUA27P.js +94 -0
- package/dist/chunk-TJIUA27P.js.map +1 -0
- package/dist/{chunk-O5DA5V5S.js → chunk-UBX7QYBD.js} +4 -4
- package/dist/{chunk-UYBIKZPM.js → chunk-UCWTZSW5.js} +3 -3
- package/dist/{chunk-X4CQ6D3G.js → chunk-UIZT3KVJ.js} +4 -4
- package/dist/chunk-WETRQJGU.js +129 -0
- package/dist/chunk-WETRQJGU.js.map +1 -0
- package/dist/{chunk-RSLIOCOE.js → chunk-XQIVYQD6.js} +3 -2
- package/dist/chunk-XQIVYQD6.js.map +1 -0
- package/dist/client-BGctTHu9.d.ts +318 -0
- package/dist/client.cjs +1886 -44
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.ts +14 -110
- package/dist/client.js +28 -3
- package/dist/core-DxiUwyBe.d.ts +156 -0
- package/dist/git/client.cjs +52 -25
- package/dist/git/client.cjs.map +1 -1
- package/dist/git/client.d.ts +17 -8
- package/dist/git/client.js +1 -1
- package/dist/git/index.cjs +52 -25
- package/dist/git/index.cjs.map +1 -1
- package/dist/git/index.d.ts +1 -1
- package/dist/git/index.js +2 -2
- package/dist/git/types.cjs.map +1 -1
- package/dist/git/types.d.ts +20 -2
- package/dist/index.cjs +1965 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +47 -5
- package/dist/tools/codebase_search/anthropic.js +2 -2
- package/dist/tools/codebase_search/index.js +9 -9
- package/dist/tools/codebase_search/openai.js +2 -2
- package/dist/tools/codebase_search/vercel.js +2 -2
- package/dist/tools/fastapply/anthropic.js +2 -2
- package/dist/tools/fastapply/index.js +7 -7
- package/dist/tools/fastapply/openai.js +2 -2
- package/dist/tools/fastapply/vercel.js +2 -2
- package/dist/tools/index.js +7 -7
- package/dist/tools/warp_grep/agent/config.cjs +80 -1
- package/dist/tools/warp_grep/agent/config.cjs.map +1 -1
- package/dist/tools/warp_grep/agent/config.js +1 -1
- package/dist/tools/warp_grep/agent/parser.cjs +43 -2
- package/dist/tools/warp_grep/agent/parser.cjs.map +1 -1
- package/dist/tools/warp_grep/agent/parser.js +1 -1
- package/dist/tools/warp_grep/agent/prompt.cjs +89 -45
- package/dist/tools/warp_grep/agent/prompt.cjs.map +1 -1
- package/dist/tools/warp_grep/agent/prompt.d.ts +1 -1
- package/dist/tools/warp_grep/agent/prompt.js +1 -1
- package/dist/tools/warp_grep/agent/runner.cjs +229 -49
- package/dist/tools/warp_grep/agent/runner.cjs.map +1 -1
- package/dist/tools/warp_grep/agent/runner.js +4 -4
- package/dist/tools/warp_grep/agent/types.js +0 -1
- package/dist/tools/warp_grep/anthropic.cjs +313 -84
- package/dist/tools/warp_grep/anthropic.cjs.map +1 -1
- package/dist/tools/warp_grep/anthropic.d.ts +75 -12
- package/dist/tools/warp_grep/anthropic.js +22 -9
- package/dist/tools/warp_grep/index.cjs +417 -127
- package/dist/tools/warp_grep/index.cjs.map +1 -1
- package/dist/tools/warp_grep/index.d.ts +17 -4
- package/dist/tools/warp_grep/index.js +30 -22
- package/dist/tools/warp_grep/openai.cjs +316 -84
- package/dist/tools/warp_grep/openai.cjs.map +1 -1
- package/dist/tools/warp_grep/openai.d.ts +73 -29
- package/dist/tools/warp_grep/openai.js +22 -9
- package/dist/tools/warp_grep/providers/command.cjs +80 -1
- package/dist/tools/warp_grep/providers/command.cjs.map +1 -1
- package/dist/tools/warp_grep/providers/command.js +2 -2
- package/dist/tools/warp_grep/providers/local.cjs +82 -2
- package/dist/tools/warp_grep/providers/local.cjs.map +1 -1
- package/dist/tools/warp_grep/providers/local.js +3 -3
- package/dist/tools/warp_grep/utils/ripgrep.cjs +2 -1
- package/dist/tools/warp_grep/utils/ripgrep.cjs.map +1 -1
- package/dist/tools/warp_grep/utils/ripgrep.js +1 -1
- package/dist/tools/warp_grep/vercel.cjs +293 -58
- package/dist/tools/warp_grep/vercel.cjs.map +1 -1
- package/dist/tools/warp_grep/vercel.d.ts +40 -19
- package/dist/tools/warp_grep/vercel.js +18 -9
- package/package.json +2 -1
- package/dist/chunk-AFEPUNAO.js +0 -15
- package/dist/chunk-AFEPUNAO.js.map +0 -1
- package/dist/chunk-GDR65N2J.js.map +0 -1
- package/dist/chunk-GTOXMAF2.js.map +0 -1
- package/dist/chunk-HKZB23U7.js +0 -85
- package/dist/chunk-HKZB23U7.js.map +0 -1
- package/dist/chunk-IQHKEIQX.js +0 -54
- package/dist/chunk-IQHKEIQX.js.map +0 -1
- package/dist/chunk-JKFVDM62.js +0 -45
- package/dist/chunk-JKFVDM62.js.map +0 -1
- package/dist/chunk-K6FQZZ2E.js +0 -104
- package/dist/chunk-K6FQZZ2E.js.map +0 -1
- package/dist/chunk-KL4YVZRF.js +0 -57
- package/dist/chunk-KL4YVZRF.js.map +0 -1
- package/dist/chunk-RSLIOCOE.js.map +0 -1
- package/dist/chunk-XYPMN4A3.js +0 -1
- /package/dist/{chunk-TVFGHXPE.js.map → chunk-3FTAIJBH.js.map} +0 -0
- /package/dist/{chunk-ZRLEAPZV.js.map → chunk-76DJEQEP.js.map} +0 -0
- /package/dist/{chunk-PEGZVGG4.js.map → chunk-G4AWE5A2.js.map} +0 -0
- /package/dist/{chunk-OUEJ6XEO.js.map → chunk-GJU7UOFL.js.map} +0 -0
- /package/dist/{chunk-XYPMN4A3.js.map → chunk-JYBVRF72.js.map} +0 -0
- /package/dist/{chunk-VBBJGWHY.js.map → chunk-P2XKFWFD.js.map} +0 -0
- /package/dist/{chunk-O5DA5V5S.js.map → chunk-UBX7QYBD.js.map} +0 -0
- /package/dist/{chunk-UYBIKZPM.js.map → chunk-UCWTZSW5.js.map} +0 -0
- /package/dist/{chunk-X4CQ6D3G.js.map → chunk-UIZT3KVJ.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -31,14 +31,20 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
AnthropicRouter: () => AnthropicRouter,
|
|
34
|
+
AnthropicToolFactory: () => AnthropicToolFactory,
|
|
34
35
|
BrowserClient: () => BrowserClient,
|
|
35
36
|
CodebaseSearchClient: () => CodebaseSearchClient,
|
|
37
|
+
CommandExecProvider: () => CommandExecProvider,
|
|
36
38
|
FastApplyClient: () => FastApplyClient,
|
|
37
39
|
GeminiRouter: () => GeminiRouter,
|
|
40
|
+
LocalRipgrepProvider: () => LocalRipgrepProvider,
|
|
38
41
|
MorphClient: () => MorphClient,
|
|
39
42
|
MorphGit: () => MorphGit,
|
|
40
43
|
OpenAIRouter: () => OpenAIRouter,
|
|
41
|
-
|
|
44
|
+
OpenAIToolFactory: () => OpenAIToolFactory,
|
|
45
|
+
RawRouter: () => RawRouter,
|
|
46
|
+
VercelToolFactory: () => VercelToolFactory,
|
|
47
|
+
WarpGrepClient: () => WarpGrepClient
|
|
42
48
|
});
|
|
43
49
|
module.exports = __toCommonJS(index_exports);
|
|
44
50
|
|
|
@@ -898,10 +904,1096 @@ async function checkHealth(config = {}) {
|
|
|
898
904
|
}
|
|
899
905
|
}
|
|
900
906
|
|
|
907
|
+
// tools/warp_grep/agent/config.ts
|
|
908
|
+
var AGENT_CONFIG = {
|
|
909
|
+
// Give the model freedom; failsafe cap to prevent infinite loops
|
|
910
|
+
MAX_ROUNDS: 10,
|
|
911
|
+
TIMEOUT_MS: 3e4
|
|
912
|
+
};
|
|
913
|
+
var BUILTIN_EXCLUDES = [
|
|
914
|
+
// Version control
|
|
915
|
+
".git",
|
|
916
|
+
".svn",
|
|
917
|
+
".hg",
|
|
918
|
+
".bzr",
|
|
919
|
+
// Dependencies
|
|
920
|
+
"node_modules",
|
|
921
|
+
"bower_components",
|
|
922
|
+
".pnpm",
|
|
923
|
+
".yarn",
|
|
924
|
+
"vendor",
|
|
925
|
+
"packages",
|
|
926
|
+
"Pods",
|
|
927
|
+
".bundle",
|
|
928
|
+
// Python
|
|
929
|
+
"__pycache__",
|
|
930
|
+
".pytest_cache",
|
|
931
|
+
".mypy_cache",
|
|
932
|
+
".ruff_cache",
|
|
933
|
+
".venv",
|
|
934
|
+
"venv",
|
|
935
|
+
".tox",
|
|
936
|
+
".nox",
|
|
937
|
+
".eggs",
|
|
938
|
+
"*.egg-info",
|
|
939
|
+
// Build outputs
|
|
940
|
+
"dist",
|
|
941
|
+
"build",
|
|
942
|
+
"out",
|
|
943
|
+
"output",
|
|
944
|
+
"target",
|
|
945
|
+
"_build",
|
|
946
|
+
".next",
|
|
947
|
+
".nuxt",
|
|
948
|
+
".output",
|
|
949
|
+
".vercel",
|
|
950
|
+
".netlify",
|
|
951
|
+
// Cache directories
|
|
952
|
+
".cache",
|
|
953
|
+
".parcel-cache",
|
|
954
|
+
".turbo",
|
|
955
|
+
".nx",
|
|
956
|
+
".gradle",
|
|
957
|
+
// IDE/Editor
|
|
958
|
+
".idea",
|
|
959
|
+
".vscode",
|
|
960
|
+
".vs",
|
|
961
|
+
// Coverage
|
|
962
|
+
"coverage",
|
|
963
|
+
".coverage",
|
|
964
|
+
"htmlcov",
|
|
965
|
+
".nyc_output",
|
|
966
|
+
// Temporary
|
|
967
|
+
"tmp",
|
|
968
|
+
"temp",
|
|
969
|
+
".tmp",
|
|
970
|
+
".temp",
|
|
971
|
+
// Lock files
|
|
972
|
+
"package-lock.json",
|
|
973
|
+
"yarn.lock",
|
|
974
|
+
"pnpm-lock.yaml",
|
|
975
|
+
"bun.lockb",
|
|
976
|
+
"Cargo.lock",
|
|
977
|
+
"Gemfile.lock",
|
|
978
|
+
"poetry.lock",
|
|
979
|
+
// Binary/minified
|
|
980
|
+
"*.min.js",
|
|
981
|
+
"*.min.css",
|
|
982
|
+
"*.bundle.js",
|
|
983
|
+
"*.wasm",
|
|
984
|
+
"*.so",
|
|
985
|
+
"*.dll",
|
|
986
|
+
"*.pyc",
|
|
987
|
+
"*.map",
|
|
988
|
+
"*.js.map",
|
|
989
|
+
// Hidden directories catch-all
|
|
990
|
+
".*"
|
|
991
|
+
];
|
|
992
|
+
var DEFAULT_EXCLUDES = (process.env.MORPH_WARP_GREP_EXCLUDE || "").split(",").map((s) => s.trim()).filter(Boolean).concat(BUILTIN_EXCLUDES);
|
|
993
|
+
var DEFAULT_MODEL = "morph-warp-grep";
|
|
994
|
+
|
|
995
|
+
// tools/warp_grep/agent/prompt.ts
|
|
996
|
+
var SYSTEM_PROMPT = `You are a code search agent. Your task is to find all relevant code for a given query.
|
|
997
|
+
|
|
998
|
+
<workflow>
|
|
999
|
+
You have exactly 4 turns. The 4th turn MUST be a \`finish\` call. Each turn allows up to 8 parallel tool calls.
|
|
1000
|
+
|
|
1001
|
+
- Turn 1: Map the territory OR dive deep (based on query specificity)
|
|
1002
|
+
- Turn 2-3: Refine based on findings
|
|
1003
|
+
- Turn 4: MUST call \`finish\` with all relevant code locations
|
|
1004
|
+
- You MAY call \`finish\` early if confident\u2014but never before at least 1 search turn.
|
|
1005
|
+
|
|
1006
|
+
Remember, if the task feels easy to you, it is strongly desirable to call \`finish\` early using fewer turns, but quality over speed.
|
|
1007
|
+
</workflow>
|
|
1008
|
+
|
|
1009
|
+
<tools>
|
|
1010
|
+
### \`analyse <path> [pattern]\`
|
|
1011
|
+
Directory tree or file search. Shows structure of a path, optionally filtered by regex pattern.
|
|
1012
|
+
- \`path\`: Required. Directory or file path (use \`.\` for repo root)
|
|
1013
|
+
- \`pattern\`: Optional regex to filter results
|
|
1014
|
+
|
|
1015
|
+
Examples:
|
|
1016
|
+
\`\`\`
|
|
1017
|
+
analyse .
|
|
1018
|
+
analyse src/api
|
|
1019
|
+
analyse . ".*\\.ts$"
|
|
1020
|
+
analyse src "test.*"
|
|
1021
|
+
\`\`\`
|
|
1022
|
+
|
|
1023
|
+
### \`read <path>[:start-end]\`
|
|
1024
|
+
Read file contents. Line range is 1-based, inclusive.
|
|
1025
|
+
- Returns numbered lines for easy reference
|
|
1026
|
+
- Omit range to read entire file
|
|
1027
|
+
|
|
1028
|
+
Examples:
|
|
1029
|
+
\`\`\`
|
|
1030
|
+
read src/main.py
|
|
1031
|
+
read src/db/conn.py:10-50
|
|
1032
|
+
read package.json:1-20
|
|
1033
|
+
\`\`\`
|
|
1034
|
+
|
|
1035
|
+
### \`grep '<pattern>' <path>\`
|
|
1036
|
+
Ripgrep search. Finds pattern matches across files.
|
|
1037
|
+
- \`'<pattern>'\`: Required. Regex pattern wrapped in single quotes
|
|
1038
|
+
- \`<path>\`: Required. Directory or file to search (use \`.\` for repo root)
|
|
1039
|
+
|
|
1040
|
+
Examples:
|
|
1041
|
+
\`\`\`
|
|
1042
|
+
grep 'class.*Service' src/
|
|
1043
|
+
grep 'def authenticate' .
|
|
1044
|
+
grep 'import.*from' src/components/
|
|
1045
|
+
grep 'TODO' .
|
|
1046
|
+
\`\`\`
|
|
1047
|
+
|
|
1048
|
+
### \`finish <file1:ranges> [file2:ranges ...]\`
|
|
1049
|
+
Submit final answer with all relevant code locations.
|
|
1050
|
+
- Include generous line ranges\u2014don't be stingy with context
|
|
1051
|
+
- Ranges are comma-separated: \`file.py:10-30,50-60\`
|
|
1052
|
+
- ALWAYS include import statements at the top of files (usually lines 1-20)
|
|
1053
|
+
- If code spans multiple files, include ALL of them
|
|
1054
|
+
- Small files can be returned in full
|
|
1055
|
+
|
|
1056
|
+
Examples:
|
|
1057
|
+
\`\`\`
|
|
1058
|
+
finish src/auth.py:1-15,25-50,75-80 src/models/user.py:1-10,20-45
|
|
1059
|
+
finish src/index.ts:1-100
|
|
1060
|
+
\`\`\`
|
|
1061
|
+
</tools>
|
|
1062
|
+
|
|
1063
|
+
<strategy>
|
|
1064
|
+
**Before your first tool call, classify the query:**
|
|
1065
|
+
|
|
1066
|
+
| Query Type | Turn 1 Strategy | Early Finish? |
|
|
1067
|
+
|------------|-----------------|---------------|
|
|
1068
|
+
| **Specific** (function name, error string, unique identifier) | 8 parallel greps on likely paths | Often by turn 2 |
|
|
1069
|
+
| **Conceptual** (how does X work, where is Y handled) | analyse + 2-3 broad greps | Rarely early |
|
|
1070
|
+
| **Exploratory** (find all tests, list API endpoints) | analyse at multiple depths | Usually needs 3 turns |
|
|
1071
|
+
|
|
1072
|
+
**Parallel call patterns:**
|
|
1073
|
+
- **Shotgun grep**: Same pattern, 8 different directories\u2014fast coverage
|
|
1074
|
+
- **Variant grep**: 8 pattern variations (synonyms, naming conventions)\u2014catches inconsistent codebases
|
|
1075
|
+
- **Funnel**: 1 analyse + 7 greps\u2014orient and search simultaneously
|
|
1076
|
+
- **Deep read**: 8 reads on files you already identified\u2014gather full context fast
|
|
1077
|
+
</strategy>
|
|
1078
|
+
|
|
1079
|
+
<output_format>
|
|
1080
|
+
EVERY response MUST follow this exact format:
|
|
1081
|
+
|
|
1082
|
+
1. First, wrap your reasoning in \`<think>...</think>\` tags containing:
|
|
1083
|
+
- Query classification (specific/conceptual/exploratory)
|
|
1084
|
+
- Confidence estimate (can I finish in 1-2 turns?)
|
|
1085
|
+
- This turn's parallel strategy
|
|
1086
|
+
- What signals would let me finish early?
|
|
1087
|
+
|
|
1088
|
+
2. Then, output tool calls wrapped in \`<tool_call>...</tool_call>\` tags, one per line.
|
|
1089
|
+
|
|
1090
|
+
Example:
|
|
1091
|
+
\`\`\`
|
|
1092
|
+
<think>
|
|
1093
|
+
This is a specific query about authentication. I'll grep for auth-related patterns.
|
|
1094
|
+
High confidence I can finish in 2 turns if I find the auth module.
|
|
1095
|
+
Strategy: Shotgun grep across likely directories.
|
|
1096
|
+
</think>
|
|
1097
|
+
<tool_call>grep 'authenticate' src/</tool_call>
|
|
1098
|
+
<tool_call>grep 'login' src/</tool_call>
|
|
1099
|
+
<tool_call>analyse src/auth</tool_call>
|
|
1100
|
+
\`\`\`
|
|
1101
|
+
|
|
1102
|
+
No commentary outside \`<think>\`. No explanations after tool calls.
|
|
1103
|
+
</output_format>
|
|
1104
|
+
|
|
1105
|
+
<finishing_requirements>
|
|
1106
|
+
When calling \`finish\`:
|
|
1107
|
+
- Include the import section (typically lines 1-20) of each file
|
|
1108
|
+
- Include all function/class definitions that are relevant
|
|
1109
|
+
- Include any type definitions, interfaces, or constants used
|
|
1110
|
+
- Better to over-include than leave the user missing context
|
|
1111
|
+
- If unsure about boundaries, include more rather than less
|
|
1112
|
+
</finishing_requirements>
|
|
1113
|
+
|
|
1114
|
+
Begin your exploration now to find code relevant to the query.`;
|
|
1115
|
+
function getSystemPrompt() {
|
|
1116
|
+
return SYSTEM_PROMPT;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
// tools/warp_grep/agent/parser.ts
|
|
1120
|
+
var LLMResponseParseError = class extends Error {
|
|
1121
|
+
constructor(message) {
|
|
1122
|
+
super(message);
|
|
1123
|
+
this.name = "LLMResponseParseError";
|
|
1124
|
+
}
|
|
1125
|
+
};
|
|
1126
|
+
var VALID_COMMANDS = ["analyse", "grep", "read", "finish"];
|
|
1127
|
+
function preprocessText(text) {
|
|
1128
|
+
let processed = text.replace(/<think>[\s\S]*?<\/think>/gi, "");
|
|
1129
|
+
const openingTagRegex = /<tool_call>|<tool>/gi;
|
|
1130
|
+
const closingTagRegex = /<\/tool_call>|<\/tool>/gi;
|
|
1131
|
+
const openingMatches = processed.match(openingTagRegex) || [];
|
|
1132
|
+
const closingMatches = processed.match(closingTagRegex) || [];
|
|
1133
|
+
if (openingMatches.length > closingMatches.length) {
|
|
1134
|
+
const lastClosingMatch = /<\/tool_call>|<\/tool>/gi;
|
|
1135
|
+
let lastClosingIndex = -1;
|
|
1136
|
+
let match;
|
|
1137
|
+
while ((match = lastClosingMatch.exec(processed)) !== null) {
|
|
1138
|
+
lastClosingIndex = match.index + match[0].length;
|
|
1139
|
+
}
|
|
1140
|
+
if (lastClosingIndex > 0) {
|
|
1141
|
+
processed = processed.slice(0, lastClosingIndex);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
const toolCallLines = [];
|
|
1145
|
+
const toolTagRegex = /<tool_call>([\s\S]*?)<\/tool_call>|<tool>([\s\S]*?)<\/tool>/gi;
|
|
1146
|
+
let tagMatch;
|
|
1147
|
+
while ((tagMatch = toolTagRegex.exec(processed)) !== null) {
|
|
1148
|
+
const content = (tagMatch[1] || tagMatch[2] || "").trim();
|
|
1149
|
+
if (content) {
|
|
1150
|
+
const lines = content.split(/\r?\n/).map((l) => l.trim()).filter((l) => l);
|
|
1151
|
+
toolCallLines.push(...lines);
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
const allLines = processed.split(/\r?\n/).map((l) => l.trim());
|
|
1155
|
+
for (const line of allLines) {
|
|
1156
|
+
if (!line) continue;
|
|
1157
|
+
if (line.startsWith("<")) continue;
|
|
1158
|
+
const firstWord = line.split(/\s/)[0];
|
|
1159
|
+
if (VALID_COMMANDS.includes(firstWord)) {
|
|
1160
|
+
if (!toolCallLines.includes(line)) {
|
|
1161
|
+
toolCallLines.push(line);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
return toolCallLines;
|
|
1166
|
+
}
|
|
1167
|
+
var LLMResponseParser = class {
|
|
1168
|
+
finishSpecSplitRe = /,(?=[^,\s]+:)/;
|
|
1169
|
+
parse(text) {
|
|
1170
|
+
if (typeof text !== "string") {
|
|
1171
|
+
throw new TypeError("Command text must be a string.");
|
|
1172
|
+
}
|
|
1173
|
+
const lines = preprocessText(text);
|
|
1174
|
+
const commands = [];
|
|
1175
|
+
let finishAccumulator = null;
|
|
1176
|
+
lines.forEach((line, idx) => {
|
|
1177
|
+
if (!line || line.startsWith("#")) return;
|
|
1178
|
+
const ctx = { lineNumber: idx + 1, raw: line };
|
|
1179
|
+
const parts = this.splitLine(line, ctx);
|
|
1180
|
+
if (parts.length === 0) return;
|
|
1181
|
+
const cmd = parts[0];
|
|
1182
|
+
switch (cmd) {
|
|
1183
|
+
case "analyse":
|
|
1184
|
+
this.handleAnalyse(parts, ctx, commands);
|
|
1185
|
+
break;
|
|
1186
|
+
case "grep":
|
|
1187
|
+
this.handleGrep(parts, ctx, commands);
|
|
1188
|
+
break;
|
|
1189
|
+
case "read":
|
|
1190
|
+
this.handleRead(parts, ctx, commands);
|
|
1191
|
+
break;
|
|
1192
|
+
case "finish":
|
|
1193
|
+
finishAccumulator = this.handleFinish(parts, ctx, finishAccumulator);
|
|
1194
|
+
break;
|
|
1195
|
+
default:
|
|
1196
|
+
break;
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1199
|
+
if (finishAccumulator) {
|
|
1200
|
+
const map = finishAccumulator;
|
|
1201
|
+
const entries = [...map.entries()];
|
|
1202
|
+
const filesPayload = entries.map(([path4, ranges]) => ({
|
|
1203
|
+
path: path4,
|
|
1204
|
+
lines: [...ranges].sort((a, b) => a[0] - b[0])
|
|
1205
|
+
}));
|
|
1206
|
+
commands.push({ name: "finish", arguments: { files: filesPayload } });
|
|
1207
|
+
}
|
|
1208
|
+
return commands;
|
|
1209
|
+
}
|
|
1210
|
+
splitLine(line, ctx) {
|
|
1211
|
+
try {
|
|
1212
|
+
const parts = [];
|
|
1213
|
+
let current = "";
|
|
1214
|
+
let inSingle = false;
|
|
1215
|
+
for (let i = 0; i < line.length; i++) {
|
|
1216
|
+
const ch = line[i];
|
|
1217
|
+
if (ch === "'" && line[i - 1] !== "\\") {
|
|
1218
|
+
inSingle = !inSingle;
|
|
1219
|
+
current += ch;
|
|
1220
|
+
} else if (!inSingle && /\s/.test(ch)) {
|
|
1221
|
+
if (current) {
|
|
1222
|
+
parts.push(current);
|
|
1223
|
+
current = "";
|
|
1224
|
+
}
|
|
1225
|
+
} else {
|
|
1226
|
+
current += ch;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
if (current) parts.push(current);
|
|
1230
|
+
return parts;
|
|
1231
|
+
} catch {
|
|
1232
|
+
throw new LLMResponseParseError(`Line ${ctx.lineNumber}: Unable to parse line.`);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
handleAnalyse(parts, ctx, commands) {
|
|
1236
|
+
if (parts.length < 2) {
|
|
1237
|
+
throw new LLMResponseParseError(`Line ${ctx.lineNumber}: analyse requires <path>`);
|
|
1238
|
+
}
|
|
1239
|
+
const path4 = parts[1];
|
|
1240
|
+
const pattern = parts[2]?.replace(/^"|"$/g, "") ?? null;
|
|
1241
|
+
commands.push({ name: "analyse", arguments: { path: path4, pattern } });
|
|
1242
|
+
}
|
|
1243
|
+
// no glob tool in MCP
|
|
1244
|
+
handleGrep(parts, ctx, commands) {
|
|
1245
|
+
if (parts.length < 3) {
|
|
1246
|
+
throw new LLMResponseParseError(`Line ${ctx.lineNumber}: grep requires '<pattern>' and <path>`);
|
|
1247
|
+
}
|
|
1248
|
+
const pat = parts[1];
|
|
1249
|
+
if (!pat.startsWith("'") || !pat.endsWith("'")) {
|
|
1250
|
+
throw new LLMResponseParseError(`Line ${ctx.lineNumber}: grep pattern must be single-quoted`);
|
|
1251
|
+
}
|
|
1252
|
+
commands.push({ name: "grep", arguments: { pattern: pat.slice(1, -1), path: parts[2] } });
|
|
1253
|
+
}
|
|
1254
|
+
handleRead(parts, ctx, commands) {
|
|
1255
|
+
if (parts.length < 2) {
|
|
1256
|
+
throw new LLMResponseParseError(`Line ${ctx.lineNumber}: read requires <path> or <path>:<start-end>`);
|
|
1257
|
+
}
|
|
1258
|
+
const spec = parts[1];
|
|
1259
|
+
const rangeIdx = spec.indexOf(":");
|
|
1260
|
+
if (rangeIdx === -1) {
|
|
1261
|
+
commands.push({ name: "read", arguments: { path: spec } });
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
const path4 = spec.slice(0, rangeIdx);
|
|
1265
|
+
const range = spec.slice(rangeIdx + 1);
|
|
1266
|
+
const [s, e] = range.split("-").map((v) => parseInt(v, 10));
|
|
1267
|
+
if (!Number.isFinite(s) || !Number.isFinite(e)) {
|
|
1268
|
+
throw new LLMResponseParseError(`Line ${ctx.lineNumber}: invalid read range '${range}'`);
|
|
1269
|
+
}
|
|
1270
|
+
commands.push({ name: "read", arguments: { path: path4, start: s, end: e } });
|
|
1271
|
+
}
|
|
1272
|
+
handleFinish(parts, ctx, acc) {
|
|
1273
|
+
const map = acc ?? /* @__PURE__ */ new Map();
|
|
1274
|
+
const args = parts.slice(1);
|
|
1275
|
+
for (const token of args) {
|
|
1276
|
+
const [path4, rangesText] = token.split(":", 2);
|
|
1277
|
+
if (!path4 || !rangesText) {
|
|
1278
|
+
throw new LLMResponseParseError(`Line ${ctx.lineNumber}: invalid finish token '${token}'`);
|
|
1279
|
+
}
|
|
1280
|
+
const rangeSpecs = rangesText.split(",").filter(Boolean);
|
|
1281
|
+
for (const spec of rangeSpecs) {
|
|
1282
|
+
const [s, e] = spec.split("-").map((v) => parseInt(v, 10));
|
|
1283
|
+
if (!Number.isFinite(s) || !Number.isFinite(e) || e < s) {
|
|
1284
|
+
throw new LLMResponseParseError(`Line ${ctx.lineNumber}: invalid range '${spec}'`);
|
|
1285
|
+
}
|
|
1286
|
+
const arr = map.get(path4) ?? [];
|
|
1287
|
+
arr.push([s, e]);
|
|
1288
|
+
map.set(path4, arr);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
return map;
|
|
1292
|
+
}
|
|
1293
|
+
};
|
|
1294
|
+
|
|
1295
|
+
// tools/warp_grep/tools/read.ts
|
|
1296
|
+
async function toolRead(provider, args) {
|
|
1297
|
+
const res = await provider.read({ path: args.path, start: args.start, end: args.end });
|
|
1298
|
+
return res.lines.join("\n");
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
// tools/warp_grep/tools/analyse.ts
|
|
1302
|
+
async function toolAnalyse(provider, args) {
|
|
1303
|
+
const list = await provider.analyse({
|
|
1304
|
+
path: args.path,
|
|
1305
|
+
pattern: args.pattern ?? null,
|
|
1306
|
+
maxResults: args.maxResults ?? 100,
|
|
1307
|
+
maxDepth: args.maxDepth ?? 2
|
|
1308
|
+
});
|
|
1309
|
+
if (!list.length) return "empty";
|
|
1310
|
+
return list.map((e) => `${" ".repeat(e.depth)}- ${e.type === "dir" ? "[D]" : "[F]"} ${e.name}`).join("\n");
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
// tools/warp_grep/agent/formatter.ts
|
|
1314
|
+
var ToolOutputFormatter = class {
|
|
1315
|
+
format(toolName, args, output, options = {}) {
|
|
1316
|
+
const name = (toolName ?? "").trim();
|
|
1317
|
+
if (!name) {
|
|
1318
|
+
return "";
|
|
1319
|
+
}
|
|
1320
|
+
const payload = output?.toString?.()?.trim?.() ?? "";
|
|
1321
|
+
const isError = Boolean(options.isError);
|
|
1322
|
+
const safeArgs = args ?? {};
|
|
1323
|
+
if (!payload && !isError) {
|
|
1324
|
+
return "";
|
|
1325
|
+
}
|
|
1326
|
+
switch (name) {
|
|
1327
|
+
case "read":
|
|
1328
|
+
return this.formatRead(safeArgs, payload, isError);
|
|
1329
|
+
case "analyse":
|
|
1330
|
+
return this.formatAnalyse(safeArgs, payload, isError);
|
|
1331
|
+
case "grep":
|
|
1332
|
+
return this.formatGrep(safeArgs, payload, isError);
|
|
1333
|
+
default:
|
|
1334
|
+
return payload ? `<tool_output>
|
|
1335
|
+
${payload}
|
|
1336
|
+
</tool_output>` : "";
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
formatRead(args, payload, isError) {
|
|
1340
|
+
if (isError) {
|
|
1341
|
+
return payload;
|
|
1342
|
+
}
|
|
1343
|
+
const path4 = this.asString(args.path) || "...";
|
|
1344
|
+
return `<file path="${path4}">
|
|
1345
|
+
${payload}
|
|
1346
|
+
</file>`;
|
|
1347
|
+
}
|
|
1348
|
+
formatAnalyse(args, payload, isError) {
|
|
1349
|
+
const path4 = this.asString(args.path) || ".";
|
|
1350
|
+
if (isError) {
|
|
1351
|
+
return `<analyse_results path="${path4}" status="error">
|
|
1352
|
+
${payload}
|
|
1353
|
+
</analyse_results>`;
|
|
1354
|
+
}
|
|
1355
|
+
return `<analyse_results path="${path4}">
|
|
1356
|
+
${payload}
|
|
1357
|
+
</analyse_results>`;
|
|
1358
|
+
}
|
|
1359
|
+
formatGrep(args, payload, isError) {
|
|
1360
|
+
const pattern = this.asString(args.pattern);
|
|
1361
|
+
const path4 = this.asString(args.path);
|
|
1362
|
+
const attributes = [];
|
|
1363
|
+
if (pattern !== void 0) {
|
|
1364
|
+
attributes.push(`pattern="${pattern}"`);
|
|
1365
|
+
}
|
|
1366
|
+
if (path4 !== void 0) {
|
|
1367
|
+
attributes.push(`path="${path4}"`);
|
|
1368
|
+
}
|
|
1369
|
+
if (isError) {
|
|
1370
|
+
attributes.push('status="error"');
|
|
1371
|
+
}
|
|
1372
|
+
const attrText = attributes.length ? ` ${attributes.join(" ")}` : "";
|
|
1373
|
+
return `<grep_output${attrText}>
|
|
1374
|
+
${payload}
|
|
1375
|
+
</grep_output>`;
|
|
1376
|
+
}
|
|
1377
|
+
asString(value) {
|
|
1378
|
+
if (value === null || value === void 0) {
|
|
1379
|
+
return void 0;
|
|
1380
|
+
}
|
|
1381
|
+
return String(value);
|
|
1382
|
+
}
|
|
1383
|
+
};
|
|
1384
|
+
var sharedFormatter = new ToolOutputFormatter();
|
|
1385
|
+
function formatAgentToolOutput(toolName, args, output, options = {}) {
|
|
1386
|
+
return sharedFormatter.format(toolName, args, output, options);
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
// tools/warp_grep/agent/grep_helpers.ts
|
|
1390
|
+
var GrepState = class {
|
|
1391
|
+
seenLines = /* @__PURE__ */ new Set();
|
|
1392
|
+
isNew(path4, lineNumber) {
|
|
1393
|
+
const key = this.makeKey(path4, lineNumber);
|
|
1394
|
+
return !this.seenLines.has(key);
|
|
1395
|
+
}
|
|
1396
|
+
add(path4, lineNumber) {
|
|
1397
|
+
this.seenLines.add(this.makeKey(path4, lineNumber));
|
|
1398
|
+
}
|
|
1399
|
+
makeKey(path4, lineNumber) {
|
|
1400
|
+
return `${path4}:${lineNumber}`;
|
|
1401
|
+
}
|
|
1402
|
+
};
|
|
1403
|
+
var MAX_GREP_OUTPUT_CHARS_PER_TURN = 6e4;
|
|
1404
|
+
function extractMatchFields(payload) {
|
|
1405
|
+
const text = payload.replace(/\r?\n$/, "");
|
|
1406
|
+
if (!text || text.startsWith("[error]")) {
|
|
1407
|
+
return null;
|
|
1408
|
+
}
|
|
1409
|
+
const firstSep = text.indexOf(":");
|
|
1410
|
+
if (firstSep === -1) {
|
|
1411
|
+
return null;
|
|
1412
|
+
}
|
|
1413
|
+
let filePath = text.slice(0, firstSep).trim();
|
|
1414
|
+
if (!filePath) {
|
|
1415
|
+
return null;
|
|
1416
|
+
}
|
|
1417
|
+
if (filePath.startsWith("./") || filePath.startsWith(".\\")) {
|
|
1418
|
+
filePath = filePath.slice(2);
|
|
1419
|
+
}
|
|
1420
|
+
const remainder = text.slice(firstSep + 1);
|
|
1421
|
+
const secondSep = remainder.indexOf(":");
|
|
1422
|
+
if (secondSep === -1) {
|
|
1423
|
+
return null;
|
|
1424
|
+
}
|
|
1425
|
+
const linePart = remainder.slice(0, secondSep);
|
|
1426
|
+
const lineNumber = Number.parseInt(linePart, 10);
|
|
1427
|
+
if (!Number.isInteger(lineNumber) || lineNumber <= 0) {
|
|
1428
|
+
return null;
|
|
1429
|
+
}
|
|
1430
|
+
let contentSegment = remainder.slice(secondSep + 1);
|
|
1431
|
+
const columnSep = contentSegment.indexOf(":");
|
|
1432
|
+
if (columnSep !== -1 && /^\d+$/.test(contentSegment.slice(0, columnSep))) {
|
|
1433
|
+
contentSegment = contentSegment.slice(columnSep + 1);
|
|
1434
|
+
}
|
|
1435
|
+
const content = contentSegment.trim();
|
|
1436
|
+
if (!content) {
|
|
1437
|
+
return null;
|
|
1438
|
+
}
|
|
1439
|
+
return { path: filePath, lineNumber, content };
|
|
1440
|
+
}
|
|
1441
|
+
function parseAndFilterGrepOutput(rawOutput, state) {
|
|
1442
|
+
const matches = [];
|
|
1443
|
+
if (typeof rawOutput !== "string" || !rawOutput.trim()) {
|
|
1444
|
+
return matches;
|
|
1445
|
+
}
|
|
1446
|
+
for (const line of rawOutput.split(/\r?\n/)) {
|
|
1447
|
+
const fields = extractMatchFields(line);
|
|
1448
|
+
if (!fields) {
|
|
1449
|
+
continue;
|
|
1450
|
+
}
|
|
1451
|
+
if (state.isNew(fields.path, fields.lineNumber)) {
|
|
1452
|
+
matches.push(fields);
|
|
1453
|
+
state.add(fields.path, fields.lineNumber);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
return matches;
|
|
1457
|
+
}
|
|
1458
|
+
function truncateOutput(payload, maxChars) {
|
|
1459
|
+
if (payload.length <= maxChars) {
|
|
1460
|
+
return payload;
|
|
1461
|
+
}
|
|
1462
|
+
const note = "... (output truncated)";
|
|
1463
|
+
const available = maxChars - note.length - 1;
|
|
1464
|
+
if (available <= 0) {
|
|
1465
|
+
return note;
|
|
1466
|
+
}
|
|
1467
|
+
if (payload.length <= available) {
|
|
1468
|
+
return `${payload.slice(0, available).replace(/\n$/, "")}
|
|
1469
|
+
${note}`;
|
|
1470
|
+
}
|
|
1471
|
+
const core = payload.slice(0, Math.max(0, available - 1));
|
|
1472
|
+
const trimmed = core.replace(/\n$/, "").replace(/\s+$/, "");
|
|
1473
|
+
const snippet = trimmed ? `${trimmed}\u2026` : "\u2026";
|
|
1474
|
+
return `${snippet}
|
|
1475
|
+
${note}`;
|
|
1476
|
+
}
|
|
1477
|
+
function formatTurnGrepOutput(matches, maxChars = MAX_GREP_OUTPUT_CHARS_PER_TURN) {
|
|
1478
|
+
if (!matches || matches.length === 0) {
|
|
1479
|
+
return "No new matches found.";
|
|
1480
|
+
}
|
|
1481
|
+
const matchesByFile = /* @__PURE__ */ new Map();
|
|
1482
|
+
for (const match of matches) {
|
|
1483
|
+
if (!matchesByFile.has(match.path)) {
|
|
1484
|
+
matchesByFile.set(match.path, []);
|
|
1485
|
+
}
|
|
1486
|
+
matchesByFile.get(match.path).push(match);
|
|
1487
|
+
}
|
|
1488
|
+
const lines = [];
|
|
1489
|
+
const sortedPaths = Array.from(matchesByFile.keys()).sort();
|
|
1490
|
+
sortedPaths.forEach((filePath, index) => {
|
|
1491
|
+
if (index > 0) {
|
|
1492
|
+
lines.push("");
|
|
1493
|
+
}
|
|
1494
|
+
lines.push(filePath);
|
|
1495
|
+
const sortedMatches = matchesByFile.get(filePath).slice().sort((a, b) => a.lineNumber - b.lineNumber);
|
|
1496
|
+
for (const match of sortedMatches) {
|
|
1497
|
+
lines.push(`${match.lineNumber}:${match.content}`);
|
|
1498
|
+
}
|
|
1499
|
+
});
|
|
1500
|
+
return truncateOutput(lines.join("\n"), maxChars);
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
// tools/warp_grep/tools/finish.ts
|
|
1504
|
+
async function readFinishFiles(repoRoot, files, reader) {
|
|
1505
|
+
const out = [];
|
|
1506
|
+
for (const f of files) {
|
|
1507
|
+
const ranges = mergeRanges(f.lines);
|
|
1508
|
+
const chunks = [];
|
|
1509
|
+
for (const [s, e] of ranges) {
|
|
1510
|
+
const lines = await reader(f.path, s, e);
|
|
1511
|
+
chunks.push(lines.join("\n"));
|
|
1512
|
+
}
|
|
1513
|
+
out.push({ path: f.path, ranges, content: chunks.join("\n") });
|
|
1514
|
+
}
|
|
1515
|
+
return out;
|
|
1516
|
+
}
|
|
1517
|
+
function mergeRanges(ranges) {
|
|
1518
|
+
if (!ranges.length) return [];
|
|
1519
|
+
const sorted = [...ranges].sort((a, b) => a[0] - b[0]);
|
|
1520
|
+
const merged = [];
|
|
1521
|
+
let [cs, ce] = sorted[0];
|
|
1522
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
1523
|
+
const [s, e] = sorted[i];
|
|
1524
|
+
if (s <= ce + 1) {
|
|
1525
|
+
ce = Math.max(ce, e);
|
|
1526
|
+
} else {
|
|
1527
|
+
merged.push([cs, ce]);
|
|
1528
|
+
cs = s;
|
|
1529
|
+
ce = e;
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
merged.push([cs, ce]);
|
|
1533
|
+
return merged;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
// tools/warp_grep/agent/runner.ts
|
|
1537
|
+
var import_path2 = __toESM(require("path"), 1);
|
|
1538
|
+
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
1539
|
+
var parser = new LLMResponseParser();
|
|
1540
|
+
async function buildInitialState(repoRoot, query) {
|
|
1541
|
+
try {
|
|
1542
|
+
const entries = await import_promises2.default.readdir(repoRoot, { withFileTypes: true });
|
|
1543
|
+
const dirs = entries.filter((e) => e.isDirectory()).map((d) => d.name).slice(0, 50);
|
|
1544
|
+
const files = entries.filter((e) => e.isFile()).map((f) => f.name).slice(0, 50);
|
|
1545
|
+
const parts = [
|
|
1546
|
+
`<repo_root>${repoRoot}</repo_root>`,
|
|
1547
|
+
`<top_dirs>${dirs.join(", ")}</top_dirs>`,
|
|
1548
|
+
`<top_files>${files.join(", ")}</top_files>`
|
|
1549
|
+
];
|
|
1550
|
+
return parts.join("\n");
|
|
1551
|
+
} catch {
|
|
1552
|
+
return `<repo_root>${repoRoot}</repo_root>`;
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
async function callModel(messages, model, apiKey) {
|
|
1556
|
+
const api = "https://api.morphllm.com/v1/chat/completions";
|
|
1557
|
+
const fetchPromise = fetchWithRetry(
|
|
1558
|
+
api,
|
|
1559
|
+
{
|
|
1560
|
+
method: "POST",
|
|
1561
|
+
headers: {
|
|
1562
|
+
"Content-Type": "application/json",
|
|
1563
|
+
Authorization: `Bearer ${apiKey || process.env.MORPH_API_KEY || ""}`
|
|
1564
|
+
},
|
|
1565
|
+
body: JSON.stringify({
|
|
1566
|
+
model,
|
|
1567
|
+
temperature: 0,
|
|
1568
|
+
max_tokens: 1024,
|
|
1569
|
+
messages
|
|
1570
|
+
})
|
|
1571
|
+
},
|
|
1572
|
+
{}
|
|
1573
|
+
);
|
|
1574
|
+
const resp = await withTimeout(fetchPromise, AGENT_CONFIG.TIMEOUT_MS, "morph-warp-grep request timed out");
|
|
1575
|
+
if (!resp.ok) {
|
|
1576
|
+
const t = await resp.text();
|
|
1577
|
+
throw new Error(`morph-warp-grep error ${resp.status}: ${t}`);
|
|
1578
|
+
}
|
|
1579
|
+
const data = await resp.json();
|
|
1580
|
+
const content = data?.choices?.[0]?.message?.content;
|
|
1581
|
+
if (!content || typeof content !== "string") {
|
|
1582
|
+
throw new Error("Invalid response from model");
|
|
1583
|
+
}
|
|
1584
|
+
return content;
|
|
1585
|
+
}
|
|
1586
|
+
async function runWarpGrep(config) {
|
|
1587
|
+
const repoRoot = import_path2.default.resolve(config.repoRoot || process.cwd());
|
|
1588
|
+
const messages = [];
|
|
1589
|
+
const systemMessage = { role: "system", content: getSystemPrompt() };
|
|
1590
|
+
messages.push(systemMessage);
|
|
1591
|
+
const queryContent = `<query>${config.query}</query>`;
|
|
1592
|
+
messages.push({ role: "user", content: queryContent });
|
|
1593
|
+
const initialState = await buildInitialState(repoRoot, config.query);
|
|
1594
|
+
messages.push({ role: "user", content: initialState });
|
|
1595
|
+
const maxRounds = AGENT_CONFIG.MAX_ROUNDS;
|
|
1596
|
+
const model = config.model || DEFAULT_MODEL;
|
|
1597
|
+
const provider = config.provider;
|
|
1598
|
+
const errors = [];
|
|
1599
|
+
const grepState = new GrepState();
|
|
1600
|
+
let finishMeta;
|
|
1601
|
+
let terminationReason = "terminated";
|
|
1602
|
+
for (let round = 1; round <= maxRounds; round += 1) {
|
|
1603
|
+
const assistantContent = await callModel(messages, model, config.apiKey).catch((e) => {
|
|
1604
|
+
errors.push({ message: e instanceof Error ? e.message : String(e) });
|
|
1605
|
+
return "";
|
|
1606
|
+
});
|
|
1607
|
+
if (!assistantContent) break;
|
|
1608
|
+
messages.push({ role: "assistant", content: assistantContent });
|
|
1609
|
+
let toolCalls = [];
|
|
1610
|
+
try {
|
|
1611
|
+
toolCalls = parser.parse(assistantContent);
|
|
1612
|
+
} catch (e) {
|
|
1613
|
+
errors.push({ message: e instanceof Error ? e.message : String(e) });
|
|
1614
|
+
terminationReason = "terminated";
|
|
1615
|
+
break;
|
|
1616
|
+
}
|
|
1617
|
+
if (toolCalls.length === 0) {
|
|
1618
|
+
errors.push({ message: "No tool calls produced by the model." });
|
|
1619
|
+
terminationReason = "terminated";
|
|
1620
|
+
break;
|
|
1621
|
+
}
|
|
1622
|
+
const finishCalls = toolCalls.filter((c) => c.name === "finish");
|
|
1623
|
+
const grepCalls = toolCalls.filter((c) => c.name === "grep");
|
|
1624
|
+
const analyseCalls = toolCalls.filter((c) => c.name === "analyse");
|
|
1625
|
+
const readCalls = toolCalls.filter((c) => c.name === "read");
|
|
1626
|
+
const formatted = [];
|
|
1627
|
+
const otherPromises = [];
|
|
1628
|
+
for (const c of analyseCalls) {
|
|
1629
|
+
const args = c.arguments ?? {};
|
|
1630
|
+
otherPromises.push(
|
|
1631
|
+
toolAnalyse(provider, args).then(
|
|
1632
|
+
(p) => formatAgentToolOutput("analyse", args, p, { isError: false }),
|
|
1633
|
+
(err) => formatAgentToolOutput("analyse", args, String(err), { isError: true })
|
|
1634
|
+
)
|
|
1635
|
+
);
|
|
1636
|
+
}
|
|
1637
|
+
for (const c of readCalls) {
|
|
1638
|
+
const args = c.arguments ?? {};
|
|
1639
|
+
otherPromises.push(
|
|
1640
|
+
toolRead(provider, args).then(
|
|
1641
|
+
(p) => formatAgentToolOutput("read", args, p, { isError: false }),
|
|
1642
|
+
(err) => formatAgentToolOutput("read", args, String(err), { isError: true })
|
|
1643
|
+
)
|
|
1644
|
+
);
|
|
1645
|
+
}
|
|
1646
|
+
const otherResults = await Promise.all(otherPromises);
|
|
1647
|
+
formatted.push(...otherResults);
|
|
1648
|
+
for (const c of grepCalls) {
|
|
1649
|
+
const args = c.arguments ?? {};
|
|
1650
|
+
try {
|
|
1651
|
+
const grepRes = await provider.grep({ pattern: args.pattern, path: args.path });
|
|
1652
|
+
const rawOutput = Array.isArray(grepRes.lines) ? grepRes.lines.join("\n") : "";
|
|
1653
|
+
const newMatches = parseAndFilterGrepOutput(rawOutput, grepState);
|
|
1654
|
+
let formattedPayload = formatTurnGrepOutput(newMatches);
|
|
1655
|
+
if (formattedPayload === "No new matches found.") {
|
|
1656
|
+
formattedPayload = "no new matches";
|
|
1657
|
+
}
|
|
1658
|
+
formatted.push(formatAgentToolOutput("grep", args, formattedPayload, { isError: false }));
|
|
1659
|
+
} catch (err) {
|
|
1660
|
+
formatted.push(formatAgentToolOutput("grep", args, String(err), { isError: true }));
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
if (formatted.length > 0) {
|
|
1664
|
+
const turnsUsed = round;
|
|
1665
|
+
const turnsRemaining = 4 - turnsUsed;
|
|
1666
|
+
let turnMessage;
|
|
1667
|
+
if (turnsRemaining === 0) {
|
|
1668
|
+
turnMessage = `
|
|
1669
|
+
|
|
1670
|
+
[Turn ${turnsUsed}/4] This is your LAST turn. You MUST call the finish tool now.`;
|
|
1671
|
+
} else if (turnsRemaining === 1) {
|
|
1672
|
+
turnMessage = `
|
|
1673
|
+
|
|
1674
|
+
[Turn ${turnsUsed}/4] You have 1 turn remaining. Next turn you MUST call the finish tool.`;
|
|
1675
|
+
} else {
|
|
1676
|
+
turnMessage = `
|
|
1677
|
+
|
|
1678
|
+
[Turn ${turnsUsed}/4] You have ${turnsRemaining} turns remaining.`;
|
|
1679
|
+
}
|
|
1680
|
+
messages.push({ role: "user", content: formatted.join("\n") + turnMessage });
|
|
1681
|
+
}
|
|
1682
|
+
if (finishCalls.length) {
|
|
1683
|
+
const fc = finishCalls[0];
|
|
1684
|
+
const files = fc.arguments?.files ?? [];
|
|
1685
|
+
finishMeta = { files };
|
|
1686
|
+
terminationReason = "completed";
|
|
1687
|
+
break;
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
if (terminationReason !== "completed" || !finishMeta) {
|
|
1691
|
+
return { terminationReason, messages, errors };
|
|
1692
|
+
}
|
|
1693
|
+
const parts = ["Relevant context found:"];
|
|
1694
|
+
for (const f of finishMeta.files) {
|
|
1695
|
+
const ranges = f.lines.map(([s, e]) => `${s}-${e}`).join(", ");
|
|
1696
|
+
parts.push(`- ${f.path}: ${ranges}`);
|
|
1697
|
+
}
|
|
1698
|
+
const payload = parts.join("\n");
|
|
1699
|
+
const resolved = await readFinishFiles(
|
|
1700
|
+
repoRoot,
|
|
1701
|
+
finishMeta.files,
|
|
1702
|
+
async (p, s, e) => {
|
|
1703
|
+
const rr = await provider.read({ path: p, start: s, end: e });
|
|
1704
|
+
return rr.lines.map((l) => {
|
|
1705
|
+
const idx = l.indexOf("|");
|
|
1706
|
+
return idx >= 0 ? l.slice(idx + 1) : l;
|
|
1707
|
+
});
|
|
1708
|
+
}
|
|
1709
|
+
);
|
|
1710
|
+
return {
|
|
1711
|
+
terminationReason: "completed",
|
|
1712
|
+
messages,
|
|
1713
|
+
finish: { payload, metadata: finishMeta, resolved }
|
|
1714
|
+
};
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
// tools/warp_grep/providers/local.ts
|
|
1718
|
+
var import_promises4 = __toESM(require("fs/promises"), 1);
|
|
1719
|
+
var import_path4 = __toESM(require("path"), 1);
|
|
1720
|
+
|
|
1721
|
+
// tools/warp_grep/utils/ripgrep.ts
|
|
1722
|
+
var import_child_process = require("child_process");
|
|
1723
|
+
var import_ripgrep = require("@vscode/ripgrep");
|
|
1724
|
+
function runRipgrep(args, opts) {
|
|
1725
|
+
return new Promise((resolve2) => {
|
|
1726
|
+
const child = (0, import_child_process.spawn)(import_ripgrep.rgPath, args, {
|
|
1727
|
+
cwd: opts?.cwd,
|
|
1728
|
+
env: { ...process.env, ...opts?.env || {} },
|
|
1729
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1730
|
+
});
|
|
1731
|
+
let stdout = "";
|
|
1732
|
+
let stderr = "";
|
|
1733
|
+
child.stdout.on("data", (d) => stdout += d.toString());
|
|
1734
|
+
child.stderr.on("data", (d) => stderr += d.toString());
|
|
1735
|
+
child.on("close", (code) => {
|
|
1736
|
+
resolve2({ stdout, stderr, exitCode: typeof code === "number" ? code : -1 });
|
|
1737
|
+
});
|
|
1738
|
+
child.on("error", () => {
|
|
1739
|
+
resolve2({ stdout: "", stderr: "Failed to spawn ripgrep (rg). Ensure it is installed.", exitCode: -1 });
|
|
1740
|
+
});
|
|
1741
|
+
});
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
// tools/warp_grep/utils/paths.ts
|
|
1745
|
+
var import_fs = __toESM(require("fs"), 1);
|
|
1746
|
+
var import_path3 = __toESM(require("path"), 1);
|
|
1747
|
+
function resolveUnderRepo(repoRoot, targetPath) {
|
|
1748
|
+
const absRoot = import_path3.default.resolve(repoRoot);
|
|
1749
|
+
const resolved = import_path3.default.resolve(absRoot, targetPath);
|
|
1750
|
+
ensureWithinRepo(absRoot, resolved);
|
|
1751
|
+
return resolved;
|
|
1752
|
+
}
|
|
1753
|
+
function ensureWithinRepo(repoRoot, absTarget) {
|
|
1754
|
+
const rel = import_path3.default.relative(import_path3.default.resolve(repoRoot), import_path3.default.resolve(absTarget));
|
|
1755
|
+
if (rel.startsWith("..") || import_path3.default.isAbsolute(rel)) {
|
|
1756
|
+
throw new Error(`Path outside repository root: ${absTarget}`);
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
function toRepoRelative(repoRoot, absPath) {
|
|
1760
|
+
return import_path3.default.relative(import_path3.default.resolve(repoRoot), import_path3.default.resolve(absPath));
|
|
1761
|
+
}
|
|
1762
|
+
function isSymlink(p) {
|
|
1763
|
+
try {
|
|
1764
|
+
const st = import_fs.default.lstatSync(p);
|
|
1765
|
+
return st.isSymbolicLink();
|
|
1766
|
+
} catch {
|
|
1767
|
+
return false;
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
function isTextualFile(filePath, maxBytes = 2e6) {
|
|
1771
|
+
try {
|
|
1772
|
+
const st = import_fs.default.statSync(filePath);
|
|
1773
|
+
if (!st.isFile()) return false;
|
|
1774
|
+
if (st.size > maxBytes) return false;
|
|
1775
|
+
const fd = import_fs.default.openSync(filePath, "r");
|
|
1776
|
+
const buf = Buffer.alloc(512);
|
|
1777
|
+
const read = import_fs.default.readSync(fd, buf, 0, buf.length, 0);
|
|
1778
|
+
import_fs.default.closeSync(fd);
|
|
1779
|
+
for (let i = 0; i < read; i++) {
|
|
1780
|
+
const c = buf[i];
|
|
1781
|
+
if (c === 0) return false;
|
|
1782
|
+
}
|
|
1783
|
+
return true;
|
|
1784
|
+
} catch {
|
|
1785
|
+
return false;
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
// tools/warp_grep/utils/files.ts
|
|
1790
|
+
var import_promises3 = __toESM(require("fs/promises"), 1);
|
|
1791
|
+
async function readAllLines(filePath) {
|
|
1792
|
+
const content = await import_promises3.default.readFile(filePath, "utf8");
|
|
1793
|
+
return content.split(/\r?\n/);
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
// tools/warp_grep/providers/local.ts
|
|
1797
|
+
var LocalRipgrepProvider = class {
|
|
1798
|
+
constructor(repoRoot, excludes = DEFAULT_EXCLUDES) {
|
|
1799
|
+
this.repoRoot = repoRoot;
|
|
1800
|
+
this.excludes = excludes;
|
|
1801
|
+
}
|
|
1802
|
+
async grep(params) {
|
|
1803
|
+
const abs = resolveUnderRepo(this.repoRoot, params.path);
|
|
1804
|
+
const stat = await import_promises4.default.stat(abs).catch(() => null);
|
|
1805
|
+
if (!stat) return { lines: [] };
|
|
1806
|
+
const targetArg = abs === import_path4.default.resolve(this.repoRoot) ? "." : toRepoRelative(this.repoRoot, abs);
|
|
1807
|
+
const args = [
|
|
1808
|
+
"--no-config",
|
|
1809
|
+
"--no-heading",
|
|
1810
|
+
"--with-filename",
|
|
1811
|
+
"--line-number",
|
|
1812
|
+
"--color=never",
|
|
1813
|
+
"--trim",
|
|
1814
|
+
"--max-columns=400",
|
|
1815
|
+
...this.excludes.flatMap((e) => ["-g", `!${e}`]),
|
|
1816
|
+
params.pattern,
|
|
1817
|
+
targetArg || "."
|
|
1818
|
+
];
|
|
1819
|
+
const res = await runRipgrep(args, { cwd: this.repoRoot });
|
|
1820
|
+
if (res.exitCode === -1) {
|
|
1821
|
+
throw new Error(res.stderr || "ripgrep (rg) execution failed.");
|
|
1822
|
+
}
|
|
1823
|
+
if (res.exitCode !== 0 && res.exitCode !== 1) {
|
|
1824
|
+
throw new Error(res.stderr || `ripgrep failed with code ${res.exitCode}`);
|
|
1825
|
+
}
|
|
1826
|
+
const lines = (res.stdout || "").trim().split(/\r?\n/).filter((l) => l.length > 0);
|
|
1827
|
+
return { lines };
|
|
1828
|
+
}
|
|
1829
|
+
async glob(params) {
|
|
1830
|
+
const abs = resolveUnderRepo(this.repoRoot, params.path);
|
|
1831
|
+
const targetArg = abs === import_path4.default.resolve(this.repoRoot) ? "." : toRepoRelative(this.repoRoot, abs);
|
|
1832
|
+
const args = [
|
|
1833
|
+
"--no-config",
|
|
1834
|
+
"--files",
|
|
1835
|
+
"-g",
|
|
1836
|
+
params.pattern,
|
|
1837
|
+
...this.excludes.flatMap((e) => ["-g", `!${e}`]),
|
|
1838
|
+
targetArg || "."
|
|
1839
|
+
];
|
|
1840
|
+
const res = await runRipgrep(args, { cwd: this.repoRoot });
|
|
1841
|
+
if (res.exitCode === -1) {
|
|
1842
|
+
throw new Error(res.stderr || "ripgrep (rg) execution failed.");
|
|
1843
|
+
}
|
|
1844
|
+
const files = (res.stdout || "").trim().split(/\r?\n/).filter((l) => l.length > 0);
|
|
1845
|
+
return { files };
|
|
1846
|
+
}
|
|
1847
|
+
async read(params) {
|
|
1848
|
+
const abs = resolveUnderRepo(this.repoRoot, params.path);
|
|
1849
|
+
const stat = await import_promises4.default.stat(abs).catch(() => null);
|
|
1850
|
+
if (!stat || !stat.isFile()) {
|
|
1851
|
+
throw new Error(`Path is not a file: ${params.path}`);
|
|
1852
|
+
}
|
|
1853
|
+
if (isSymlink(abs)) {
|
|
1854
|
+
throw new Error(`Refusing to read symlink: ${params.path}`);
|
|
1855
|
+
}
|
|
1856
|
+
if (!isTextualFile(abs)) {
|
|
1857
|
+
throw new Error(`Non-text or too-large file: ${params.path}`);
|
|
1858
|
+
}
|
|
1859
|
+
const lines = await readAllLines(abs);
|
|
1860
|
+
const total = lines.length;
|
|
1861
|
+
const s = params.start ?? 1;
|
|
1862
|
+
const e = Math.min(params.end ?? total, total);
|
|
1863
|
+
if (s > total && total > 0) {
|
|
1864
|
+
throw new Error(`start ${s} exceeds file length (${total})`);
|
|
1865
|
+
}
|
|
1866
|
+
const out = [];
|
|
1867
|
+
for (let i = s; i <= e; i += 1) {
|
|
1868
|
+
const content = lines[i - 1] ?? "";
|
|
1869
|
+
out.push(`${i}|${content}`);
|
|
1870
|
+
}
|
|
1871
|
+
return { lines: out };
|
|
1872
|
+
}
|
|
1873
|
+
async analyse(params) {
|
|
1874
|
+
const abs = resolveUnderRepo(this.repoRoot, params.path);
|
|
1875
|
+
const stat = await import_promises4.default.stat(abs).catch(() => null);
|
|
1876
|
+
if (!stat || !stat.isDirectory()) {
|
|
1877
|
+
return [];
|
|
1878
|
+
}
|
|
1879
|
+
const maxResults = params.maxResults ?? 100;
|
|
1880
|
+
const maxDepth = params.maxDepth ?? 2;
|
|
1881
|
+
const regex = params.pattern ? new RegExp(params.pattern) : null;
|
|
1882
|
+
const results = [];
|
|
1883
|
+
async function walk(dir, depth) {
|
|
1884
|
+
if (depth > maxDepth || results.length >= maxResults) return;
|
|
1885
|
+
const entries = await import_promises4.default.readdir(dir, { withFileTypes: true });
|
|
1886
|
+
for (const entry of entries) {
|
|
1887
|
+
const full = import_path4.default.join(dir, entry.name);
|
|
1888
|
+
const rel = toRepoRelative(abs, full).replace(/^[.][/\\]?/, "");
|
|
1889
|
+
if (DEFAULT_EXCLUDES.some((ex) => rel.split(import_path4.default.sep).includes(ex))) continue;
|
|
1890
|
+
if (regex && !regex.test(entry.name)) continue;
|
|
1891
|
+
if (results.length >= maxResults) break;
|
|
1892
|
+
results.push({
|
|
1893
|
+
name: entry.name,
|
|
1894
|
+
path: toRepoRelative(import_path4.default.resolve(""), full),
|
|
1895
|
+
// relative display
|
|
1896
|
+
type: entry.isDirectory() ? "dir" : "file",
|
|
1897
|
+
depth
|
|
1898
|
+
});
|
|
1899
|
+
if (entry.isDirectory()) {
|
|
1900
|
+
await walk(full, depth + 1);
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
await walk(abs, 0);
|
|
1905
|
+
return results;
|
|
1906
|
+
}
|
|
1907
|
+
};
|
|
1908
|
+
|
|
1909
|
+
// tools/warp_grep/core.ts
|
|
1910
|
+
var WarpGrepClient = class {
|
|
1911
|
+
config;
|
|
1912
|
+
constructor(config = {}) {
|
|
1913
|
+
this.config = {
|
|
1914
|
+
apiKey: config.apiKey,
|
|
1915
|
+
debug: config.debug,
|
|
1916
|
+
timeout: config.timeout,
|
|
1917
|
+
retryConfig: config.retryConfig
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1920
|
+
/**
|
|
1921
|
+
* Execute a code search query
|
|
1922
|
+
*
|
|
1923
|
+
* @param input - Search parameters including query, repoRoot, and optional provider
|
|
1924
|
+
* @returns Search results with relevant code contexts
|
|
1925
|
+
*
|
|
1926
|
+
* @example
|
|
1927
|
+
* ```typescript
|
|
1928
|
+
* const result = await client.execute({
|
|
1929
|
+
* query: 'Find authentication middleware',
|
|
1930
|
+
* repoRoot: '.'
|
|
1931
|
+
* });
|
|
1932
|
+
*
|
|
1933
|
+
* if (result.success) {
|
|
1934
|
+
* for (const ctx of result.contexts) {
|
|
1935
|
+
* console.log(`File: ${ctx.file}`);
|
|
1936
|
+
* console.log(ctx.content);
|
|
1937
|
+
* }
|
|
1938
|
+
* }
|
|
1939
|
+
* ```
|
|
1940
|
+
*/
|
|
1941
|
+
async execute(input) {
|
|
1942
|
+
const provider = input.provider ?? new LocalRipgrepProvider(input.repoRoot, input.excludes);
|
|
1943
|
+
const result = await runWarpGrep({
|
|
1944
|
+
query: input.query,
|
|
1945
|
+
repoRoot: input.repoRoot,
|
|
1946
|
+
provider,
|
|
1947
|
+
excludes: input.excludes,
|
|
1948
|
+
includes: input.includes,
|
|
1949
|
+
debug: input.debug ?? this.config.debug ?? false,
|
|
1950
|
+
apiKey: this.config.apiKey
|
|
1951
|
+
});
|
|
1952
|
+
const finish = result.finish;
|
|
1953
|
+
if (result.terminationReason !== "completed" || !finish?.metadata) {
|
|
1954
|
+
return {
|
|
1955
|
+
success: false,
|
|
1956
|
+
error: "Search did not complete"
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
const contexts = (finish.resolved ?? []).map((r) => ({
|
|
1960
|
+
file: r.path,
|
|
1961
|
+
content: r.content
|
|
1962
|
+
}));
|
|
1963
|
+
return {
|
|
1964
|
+
success: true,
|
|
1965
|
+
contexts,
|
|
1966
|
+
summary: finish.payload
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
};
|
|
1970
|
+
function formatResult(result) {
|
|
1971
|
+
if (!result.success) {
|
|
1972
|
+
return `Search failed: ${result.error}`;
|
|
1973
|
+
}
|
|
1974
|
+
if (!result.contexts || result.contexts.length === 0) {
|
|
1975
|
+
return "No relevant code found. Try rephrasing your query.";
|
|
1976
|
+
}
|
|
1977
|
+
const lines = [];
|
|
1978
|
+
lines.push(`Found ${result.contexts.length} relevant code sections:
|
|
1979
|
+
`);
|
|
1980
|
+
result.contexts.forEach((ctx, i) => {
|
|
1981
|
+
lines.push(`${i + 1}. ${ctx.file}`);
|
|
1982
|
+
lines.push("```");
|
|
1983
|
+
lines.push(ctx.content);
|
|
1984
|
+
lines.push("```");
|
|
1985
|
+
lines.push("");
|
|
1986
|
+
});
|
|
1987
|
+
if (result.summary) {
|
|
1988
|
+
lines.push(`Summary: ${result.summary}`);
|
|
1989
|
+
}
|
|
1990
|
+
return lines.join("\n");
|
|
1991
|
+
}
|
|
1992
|
+
|
|
901
1993
|
// git/client.ts
|
|
902
1994
|
var import_isomorphic_git = __toESM(require("isomorphic-git"), 1);
|
|
903
1995
|
var import_node = __toESM(require("isomorphic-git/http/node"), 1);
|
|
904
|
-
var
|
|
1996
|
+
var import_fs2 = __toESM(require("fs"), 1);
|
|
905
1997
|
var DEFAULT_PROXY_URL = "https://repos.morphllm.com";
|
|
906
1998
|
var MorphGit = class {
|
|
907
1999
|
apiKey;
|
|
@@ -958,12 +2050,12 @@ var MorphGit = class {
|
|
|
958
2050
|
throw new Error(`Failed to create repository: ${error}`);
|
|
959
2051
|
}
|
|
960
2052
|
await import_isomorphic_git.default.init({
|
|
961
|
-
fs:
|
|
2053
|
+
fs: import_fs2.default,
|
|
962
2054
|
dir,
|
|
963
2055
|
defaultBranch
|
|
964
2056
|
});
|
|
965
2057
|
await import_isomorphic_git.default.addRemote({
|
|
966
|
-
fs:
|
|
2058
|
+
fs: import_fs2.default,
|
|
967
2059
|
dir,
|
|
968
2060
|
remote: "origin",
|
|
969
2061
|
url: `${this.proxyUrl}/v1/repos/${repoId}`
|
|
@@ -984,7 +2076,7 @@ var MorphGit = class {
|
|
|
984
2076
|
async clone(options) {
|
|
985
2077
|
const { repoId, dir, branch = "main", depth, singleBranch = true } = options;
|
|
986
2078
|
await import_isomorphic_git.default.clone({
|
|
987
|
-
fs:
|
|
2079
|
+
fs: import_fs2.default,
|
|
988
2080
|
http: import_node.default,
|
|
989
2081
|
dir,
|
|
990
2082
|
url: `${this.proxyUrl}/v1/repos/${repoId}`,
|
|
@@ -1001,42 +2093,65 @@ var MorphGit = class {
|
|
|
1001
2093
|
* ```ts
|
|
1002
2094
|
* await morphGit.push({
|
|
1003
2095
|
* dir: './my-project',
|
|
1004
|
-
* branch: 'main' // Required: explicit branch name
|
|
2096
|
+
* branch: 'main', // Required: explicit branch name
|
|
2097
|
+
* index: true // Optional: generate embeddings (default: true)
|
|
1005
2098
|
* });
|
|
1006
2099
|
* ```
|
|
1007
2100
|
*/
|
|
1008
2101
|
async push(options) {
|
|
1009
|
-
const { dir, remote = "origin", branch, waitForEmbeddings } = options;
|
|
2102
|
+
const { dir, remote = "origin", branch, waitForEmbeddings, index = true } = options;
|
|
1010
2103
|
if (!branch) {
|
|
1011
2104
|
throw new Error(
|
|
1012
2105
|
'branch is required for push operations. Specify the branch explicitly: { dir: "./my-project", branch: "main" }'
|
|
1013
2106
|
);
|
|
1014
2107
|
}
|
|
1015
|
-
|
|
2108
|
+
const commitHash = await import_isomorphic_git.default.resolveRef({ fs: import_fs2.default, dir, ref: "HEAD" });
|
|
1016
2109
|
let repoId;
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
const
|
|
1021
|
-
if (
|
|
1022
|
-
|
|
1023
|
-
if (match) {
|
|
1024
|
-
repoId = match[1];
|
|
1025
|
-
}
|
|
2110
|
+
const remotes = await import_isomorphic_git.default.listRemotes({ fs: import_fs2.default, dir });
|
|
2111
|
+
const originRemote = remotes.find((r) => r.remote === remote);
|
|
2112
|
+
if (originRemote) {
|
|
2113
|
+
const match = originRemote.url.match(/\/repos\/([^\/]+)$/);
|
|
2114
|
+
if (match) {
|
|
2115
|
+
repoId = match[1];
|
|
1026
2116
|
}
|
|
1027
2117
|
}
|
|
1028
2118
|
await import_isomorphic_git.default.push({
|
|
1029
|
-
fs:
|
|
2119
|
+
fs: import_fs2.default,
|
|
1030
2120
|
http: import_node.default,
|
|
1031
2121
|
dir,
|
|
1032
2122
|
remote,
|
|
1033
2123
|
ref: branch,
|
|
1034
2124
|
onAuth: this.getAuthCallback()
|
|
1035
2125
|
});
|
|
1036
|
-
if (
|
|
2126
|
+
if (repoId && commitHash) {
|
|
2127
|
+
await this.configureCommit({ repoId, commitHash, branch, index });
|
|
2128
|
+
}
|
|
2129
|
+
if (waitForEmbeddings && repoId && commitHash && index) {
|
|
1037
2130
|
await this.waitForEmbeddings({ repoId, commitHash });
|
|
1038
2131
|
}
|
|
1039
2132
|
}
|
|
2133
|
+
/**
|
|
2134
|
+
* Configure commit settings on the backend after push.
|
|
2135
|
+
* Sets the index flag to control embedding generation.
|
|
2136
|
+
* @private
|
|
2137
|
+
*/
|
|
2138
|
+
async configureCommit(options) {
|
|
2139
|
+
const { repoId, commitHash, branch, index } = options;
|
|
2140
|
+
const response = await fetch(
|
|
2141
|
+
`${this.proxyUrl}/v1/repos/${repoId}/commits/${commitHash}/config`,
|
|
2142
|
+
{
|
|
2143
|
+
method: "POST",
|
|
2144
|
+
headers: {
|
|
2145
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
2146
|
+
"Content-Type": "application/json"
|
|
2147
|
+
},
|
|
2148
|
+
body: JSON.stringify({ index, branch })
|
|
2149
|
+
}
|
|
2150
|
+
);
|
|
2151
|
+
if (!response.ok) {
|
|
2152
|
+
console.warn(`Failed to configure commit: ${response.status}`);
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
1040
2155
|
/**
|
|
1041
2156
|
* Pull changes from remote repository
|
|
1042
2157
|
*
|
|
@@ -1056,7 +2171,7 @@ var MorphGit = class {
|
|
|
1056
2171
|
);
|
|
1057
2172
|
}
|
|
1058
2173
|
await import_isomorphic_git.default.pull({
|
|
1059
|
-
fs:
|
|
2174
|
+
fs: import_fs2.default,
|
|
1060
2175
|
http: import_node.default,
|
|
1061
2176
|
dir,
|
|
1062
2177
|
remote,
|
|
@@ -1125,7 +2240,7 @@ var MorphGit = class {
|
|
|
1125
2240
|
async add(options) {
|
|
1126
2241
|
const { dir, filepath } = options;
|
|
1127
2242
|
await import_isomorphic_git.default.add({
|
|
1128
|
-
fs:
|
|
2243
|
+
fs: import_fs2.default,
|
|
1129
2244
|
dir,
|
|
1130
2245
|
filepath
|
|
1131
2246
|
});
|
|
@@ -1144,7 +2259,7 @@ var MorphGit = class {
|
|
|
1144
2259
|
async remove(options) {
|
|
1145
2260
|
const { dir, filepath } = options;
|
|
1146
2261
|
await import_isomorphic_git.default.remove({
|
|
1147
|
-
fs:
|
|
2262
|
+
fs: import_fs2.default,
|
|
1148
2263
|
dir,
|
|
1149
2264
|
filepath
|
|
1150
2265
|
});
|
|
@@ -1161,6 +2276,7 @@ var MorphGit = class {
|
|
|
1161
2276
|
* name: 'AI Agent',
|
|
1162
2277
|
* email: 'ai@example.com'
|
|
1163
2278
|
* },
|
|
2279
|
+
* metadata: { issueId: 'PROJ-123', source: 'agent' },
|
|
1164
2280
|
* chatHistory: [
|
|
1165
2281
|
* { role: 'user', content: 'Please add a new feature' },
|
|
1166
2282
|
* { role: 'assistant', content: 'I will add that feature' }
|
|
@@ -1170,28 +2286,30 @@ var MorphGit = class {
|
|
|
1170
2286
|
* ```
|
|
1171
2287
|
*/
|
|
1172
2288
|
async commit(options) {
|
|
1173
|
-
const { dir, message, author, chatHistory, recordingId } = options;
|
|
2289
|
+
const { dir, message, author, metadata, chatHistory, recordingId } = options;
|
|
1174
2290
|
const commitAuthor = author || {
|
|
1175
2291
|
name: "Morph SDK",
|
|
1176
2292
|
email: "sdk@morphllm.com"
|
|
1177
2293
|
};
|
|
1178
2294
|
const sha = await import_isomorphic_git.default.commit({
|
|
1179
|
-
fs:
|
|
2295
|
+
fs: import_fs2.default,
|
|
1180
2296
|
dir,
|
|
1181
2297
|
message,
|
|
1182
2298
|
author: commitAuthor
|
|
1183
2299
|
});
|
|
1184
|
-
if (chatHistory || recordingId) {
|
|
1185
|
-
const
|
|
2300
|
+
if (metadata || chatHistory || recordingId) {
|
|
2301
|
+
const notes = {
|
|
2302
|
+
metadata,
|
|
1186
2303
|
chatHistory,
|
|
1187
|
-
recordingId
|
|
2304
|
+
recordingId,
|
|
2305
|
+
_version: 1
|
|
1188
2306
|
};
|
|
1189
2307
|
await import_isomorphic_git.default.addNote({
|
|
1190
|
-
fs:
|
|
2308
|
+
fs: import_fs2.default,
|
|
1191
2309
|
dir,
|
|
1192
2310
|
ref: "refs/notes/morph-metadata",
|
|
1193
2311
|
oid: sha,
|
|
1194
|
-
note: JSON.stringify(
|
|
2312
|
+
note: JSON.stringify(notes, null, 2),
|
|
1195
2313
|
author: commitAuthor
|
|
1196
2314
|
});
|
|
1197
2315
|
}
|
|
@@ -1215,7 +2333,7 @@ var MorphGit = class {
|
|
|
1215
2333
|
throw new Error("filepath is required for status check");
|
|
1216
2334
|
}
|
|
1217
2335
|
const status = await import_isomorphic_git.default.status({
|
|
1218
|
-
fs:
|
|
2336
|
+
fs: import_fs2.default,
|
|
1219
2337
|
dir,
|
|
1220
2338
|
filepath
|
|
1221
2339
|
});
|
|
@@ -1235,7 +2353,7 @@ var MorphGit = class {
|
|
|
1235
2353
|
async log(options) {
|
|
1236
2354
|
const { dir, depth, ref } = options;
|
|
1237
2355
|
const commits = await import_isomorphic_git.default.log({
|
|
1238
|
-
fs:
|
|
2356
|
+
fs: import_fs2.default,
|
|
1239
2357
|
dir,
|
|
1240
2358
|
depth,
|
|
1241
2359
|
ref
|
|
@@ -1256,7 +2374,7 @@ var MorphGit = class {
|
|
|
1256
2374
|
async checkout(options) {
|
|
1257
2375
|
const { dir, ref } = options;
|
|
1258
2376
|
await import_isomorphic_git.default.checkout({
|
|
1259
|
-
fs:
|
|
2377
|
+
fs: import_fs2.default,
|
|
1260
2378
|
dir,
|
|
1261
2379
|
ref
|
|
1262
2380
|
});
|
|
@@ -1276,7 +2394,7 @@ var MorphGit = class {
|
|
|
1276
2394
|
async branch(options) {
|
|
1277
2395
|
const { dir, name, checkout = false } = options;
|
|
1278
2396
|
await import_isomorphic_git.default.branch({
|
|
1279
|
-
fs:
|
|
2397
|
+
fs: import_fs2.default,
|
|
1280
2398
|
dir,
|
|
1281
2399
|
ref: name,
|
|
1282
2400
|
checkout
|
|
@@ -1295,7 +2413,7 @@ var MorphGit = class {
|
|
|
1295
2413
|
async listBranches(options) {
|
|
1296
2414
|
const { dir } = options;
|
|
1297
2415
|
const branches = await import_isomorphic_git.default.listBranches({
|
|
1298
|
-
fs:
|
|
2416
|
+
fs: import_fs2.default,
|
|
1299
2417
|
dir
|
|
1300
2418
|
});
|
|
1301
2419
|
return branches;
|
|
@@ -1313,7 +2431,7 @@ var MorphGit = class {
|
|
|
1313
2431
|
async currentBranch(options) {
|
|
1314
2432
|
const { dir } = options;
|
|
1315
2433
|
const branch = await import_isomorphic_git.default.currentBranch({
|
|
1316
|
-
fs:
|
|
2434
|
+
fs: import_fs2.default,
|
|
1317
2435
|
dir
|
|
1318
2436
|
});
|
|
1319
2437
|
return branch || void 0;
|
|
@@ -1331,7 +2449,7 @@ var MorphGit = class {
|
|
|
1331
2449
|
async statusMatrix(options) {
|
|
1332
2450
|
const { dir } = options;
|
|
1333
2451
|
const matrix = await import_isomorphic_git.default.statusMatrix({
|
|
1334
|
-
fs:
|
|
2452
|
+
fs: import_fs2.default,
|
|
1335
2453
|
dir
|
|
1336
2454
|
});
|
|
1337
2455
|
return matrix.map(([filepath, HEADStatus, workdirStatus, stageStatus]) => {
|
|
@@ -1373,38 +2491,39 @@ var MorphGit = class {
|
|
|
1373
2491
|
async resolveRef(options) {
|
|
1374
2492
|
const { dir, ref } = options;
|
|
1375
2493
|
const oid = await import_isomorphic_git.default.resolveRef({
|
|
1376
|
-
fs:
|
|
2494
|
+
fs: import_fs2.default,
|
|
1377
2495
|
dir,
|
|
1378
2496
|
ref
|
|
1379
2497
|
});
|
|
1380
2498
|
return oid;
|
|
1381
2499
|
}
|
|
1382
2500
|
/**
|
|
1383
|
-
* Get metadata
|
|
2501
|
+
* Get notes (metadata, chat history, recording ID) attached to a commit
|
|
1384
2502
|
*
|
|
1385
2503
|
* @example
|
|
1386
2504
|
* ```ts
|
|
1387
|
-
* const
|
|
2505
|
+
* const notes = await morphGit.getCommitMetadata({
|
|
1388
2506
|
* dir: './my-project',
|
|
1389
2507
|
* commitSha: 'abc123...'
|
|
1390
2508
|
* });
|
|
1391
2509
|
*
|
|
1392
|
-
* if (
|
|
1393
|
-
* console.log('
|
|
1394
|
-
* console.log('
|
|
2510
|
+
* if (notes) {
|
|
2511
|
+
* console.log('Metadata:', notes.metadata);
|
|
2512
|
+
* console.log('Chat history:', notes.chatHistory);
|
|
2513
|
+
* console.log('Recording ID:', notes.recordingId);
|
|
1395
2514
|
* }
|
|
1396
2515
|
* ```
|
|
1397
2516
|
*/
|
|
1398
2517
|
async getCommitMetadata(options) {
|
|
1399
2518
|
try {
|
|
1400
2519
|
const note = await import_isomorphic_git.default.readNote({
|
|
1401
|
-
fs:
|
|
2520
|
+
fs: import_fs2.default,
|
|
1402
2521
|
dir: options.dir,
|
|
1403
2522
|
ref: "refs/notes/morph-metadata",
|
|
1404
2523
|
oid: options.commitSha
|
|
1405
2524
|
});
|
|
1406
|
-
const
|
|
1407
|
-
return
|
|
2525
|
+
const notes = JSON.parse(new TextDecoder().decode(note));
|
|
2526
|
+
return notes;
|
|
1408
2527
|
} catch (err) {
|
|
1409
2528
|
return null;
|
|
1410
2529
|
}
|
|
@@ -1615,6 +2734,718 @@ var RawRouter = class extends BaseRouter {
|
|
|
1615
2734
|
}
|
|
1616
2735
|
};
|
|
1617
2736
|
|
|
2737
|
+
// tools/warp_grep/prompts.ts
|
|
2738
|
+
var WARP_GREP_DESCRIPTION = "A fast and accurate tool that can search for all relevant context in a codebase. You must use this tool to save time and avoid context pollution.";
|
|
2739
|
+
|
|
2740
|
+
// tools/warp_grep/openai.ts
|
|
2741
|
+
var TOOL_PARAMETERS = {
|
|
2742
|
+
type: "object",
|
|
2743
|
+
properties: {
|
|
2744
|
+
query: { type: "string", description: "Free-form repository question" }
|
|
2745
|
+
},
|
|
2746
|
+
required: ["query"]
|
|
2747
|
+
};
|
|
2748
|
+
async function execute(input, config) {
|
|
2749
|
+
const parsed = typeof input === "string" ? JSON.parse(input) : input;
|
|
2750
|
+
const provider = config.provider ?? new LocalRipgrepProvider(config.repoRoot, config.excludes);
|
|
2751
|
+
const result = await runWarpGrep({
|
|
2752
|
+
query: parsed.query,
|
|
2753
|
+
repoRoot: config.repoRoot,
|
|
2754
|
+
provider,
|
|
2755
|
+
excludes: config.excludes,
|
|
2756
|
+
includes: config.includes,
|
|
2757
|
+
debug: config.debug ?? false,
|
|
2758
|
+
apiKey: config.apiKey
|
|
2759
|
+
});
|
|
2760
|
+
const finish = result.finish;
|
|
2761
|
+
if (result.terminationReason !== "completed" || !finish?.metadata) {
|
|
2762
|
+
return { success: false, error: "Search did not complete" };
|
|
2763
|
+
}
|
|
2764
|
+
const contexts = (finish.resolved ?? []).map((r) => ({
|
|
2765
|
+
file: r.path,
|
|
2766
|
+
content: r.content
|
|
2767
|
+
}));
|
|
2768
|
+
return { success: true, contexts, summary: finish.payload };
|
|
2769
|
+
}
|
|
2770
|
+
function createMorphWarpGrepTool(config) {
|
|
2771
|
+
const tool4 = {
|
|
2772
|
+
type: "function",
|
|
2773
|
+
function: {
|
|
2774
|
+
name: "morph-warp-grep",
|
|
2775
|
+
description: config.description ?? WARP_GREP_DESCRIPTION,
|
|
2776
|
+
parameters: TOOL_PARAMETERS
|
|
2777
|
+
}
|
|
2778
|
+
};
|
|
2779
|
+
return Object.assign(tool4, {
|
|
2780
|
+
execute: async (input) => {
|
|
2781
|
+
return execute(input, config);
|
|
2782
|
+
},
|
|
2783
|
+
formatResult: (result) => {
|
|
2784
|
+
return formatResult(result);
|
|
2785
|
+
},
|
|
2786
|
+
getSystemPrompt: () => {
|
|
2787
|
+
return getSystemPrompt();
|
|
2788
|
+
}
|
|
2789
|
+
});
|
|
2790
|
+
}
|
|
2791
|
+
|
|
2792
|
+
// tools/codebase_search/prompts.ts
|
|
2793
|
+
var CODEBASE_SEARCH_DESCRIPTION = `Semantic search that finds code by meaning, not exact text.
|
|
2794
|
+
|
|
2795
|
+
Use this to explore unfamiliar codebases or ask "how/where/what" questions:
|
|
2796
|
+
- "How does X work?" - Find implementation details
|
|
2797
|
+
- "Where is Y handled?" - Locate specific functionality
|
|
2798
|
+
- "What happens when Z?" - Understand flow
|
|
2799
|
+
|
|
2800
|
+
The tool uses two-stage retrieval (embedding similarity + reranking) to find the most semantically relevant code chunks.
|
|
2801
|
+
|
|
2802
|
+
Returns code chunks with file paths, line ranges, and full content ranked by relevance.`;
|
|
2803
|
+
var CODEBASE_SEARCH_SYSTEM_PROMPT = `You have access to the codebase_search tool that performs semantic code search.
|
|
2804
|
+
|
|
2805
|
+
When searching:
|
|
2806
|
+
- Use natural language queries describing what you're looking for
|
|
2807
|
+
- Be specific about functionality, not variable names
|
|
2808
|
+
- Use target_directories to narrow search if you know the area
|
|
2809
|
+
- Results are ranked by relevance (rerank score is most important)
|
|
2810
|
+
|
|
2811
|
+
The tool returns:
|
|
2812
|
+
- File paths with symbol names (e.g. "src/auth.ts::AuthService@L1-L17")
|
|
2813
|
+
- Line ranges for precise navigation
|
|
2814
|
+
- Full code content for each match
|
|
2815
|
+
- Dual relevance scores: embedding similarity + rerank score
|
|
2816
|
+
|
|
2817
|
+
Use results to understand code or answer questions. The content is provided in full - avoid re-reading unless you need more context.`;
|
|
2818
|
+
|
|
2819
|
+
// tools/codebase_search/openai.ts
|
|
2820
|
+
function createCodebaseSearchTool(config) {
|
|
2821
|
+
const toolDefinition = {
|
|
2822
|
+
type: "function",
|
|
2823
|
+
function: {
|
|
2824
|
+
name: "codebase_search",
|
|
2825
|
+
description: CODEBASE_SEARCH_DESCRIPTION,
|
|
2826
|
+
parameters: {
|
|
2827
|
+
type: "object",
|
|
2828
|
+
properties: {
|
|
2829
|
+
query: {
|
|
2830
|
+
type: "string",
|
|
2831
|
+
description: 'A complete question about what you want to understand. Ask as if talking to a colleague: "How does X work?", "What happens when Y?", "Where is Z handled?"'
|
|
2832
|
+
},
|
|
2833
|
+
target_directories: {
|
|
2834
|
+
type: "array",
|
|
2835
|
+
items: { type: "string" },
|
|
2836
|
+
description: "Prefix directory paths to limit search scope (single directory only, no glob patterns). Use [] to search entire repo."
|
|
2837
|
+
},
|
|
2838
|
+
explanation: {
|
|
2839
|
+
type: "string",
|
|
2840
|
+
description: "One sentence explanation as to why this tool is being used, and how it contributes to the goal."
|
|
2841
|
+
},
|
|
2842
|
+
limit: {
|
|
2843
|
+
type: "number",
|
|
2844
|
+
description: "Maximum results to return (default: 10)"
|
|
2845
|
+
}
|
|
2846
|
+
},
|
|
2847
|
+
required: ["query", "target_directories", "explanation"]
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
};
|
|
2851
|
+
return Object.assign(toolDefinition, {
|
|
2852
|
+
execute: async (input) => {
|
|
2853
|
+
const parsedInput = typeof input === "string" ? JSON.parse(input) : input;
|
|
2854
|
+
return executeCodebaseSearch(parsedInput, config);
|
|
2855
|
+
},
|
|
2856
|
+
formatResult: (result) => {
|
|
2857
|
+
return formatResult2(result);
|
|
2858
|
+
},
|
|
2859
|
+
getSystemPrompt: () => {
|
|
2860
|
+
return CODEBASE_SEARCH_SYSTEM_PROMPT;
|
|
2861
|
+
}
|
|
2862
|
+
});
|
|
2863
|
+
}
|
|
2864
|
+
function formatResult2(result) {
|
|
2865
|
+
if (!result.success) {
|
|
2866
|
+
return `Search failed: ${result.error}`;
|
|
2867
|
+
}
|
|
2868
|
+
if (result.results.length === 0) {
|
|
2869
|
+
return "No matching code found. Try rephrasing your query or removing directory filters.";
|
|
2870
|
+
}
|
|
2871
|
+
const lines = [];
|
|
2872
|
+
lines.push(`Found ${result.results.length} relevant code sections (${result.stats.searchTimeMs}ms):
|
|
2873
|
+
`);
|
|
2874
|
+
result.results.forEach((r, i) => {
|
|
2875
|
+
const relevance = (r.rerankScore * 100).toFixed(1);
|
|
2876
|
+
lines.push(`${i + 1}. ${r.filepath} (${relevance}% relevant)`);
|
|
2877
|
+
lines.push(` Symbol: ${r.symbolPath}`);
|
|
2878
|
+
lines.push(` Language: ${r.language}`);
|
|
2879
|
+
lines.push(` Lines: ${r.startLine}-${r.endLine}`);
|
|
2880
|
+
lines.push(` Code:`);
|
|
2881
|
+
const codeLines = r.content.split("\n");
|
|
2882
|
+
codeLines.slice(0, Math.min(codeLines.length, 20)).forEach((line) => {
|
|
2883
|
+
lines.push(` ${line}`);
|
|
2884
|
+
});
|
|
2885
|
+
if (codeLines.length > 20) {
|
|
2886
|
+
lines.push(` ... (${codeLines.length - 20} more lines)`);
|
|
2887
|
+
}
|
|
2888
|
+
lines.push("");
|
|
2889
|
+
});
|
|
2890
|
+
return lines.join("\n");
|
|
2891
|
+
}
|
|
2892
|
+
|
|
2893
|
+
// tools/fastapply/prompts.ts
|
|
2894
|
+
var EDIT_FILE_TOOL_DESCRIPTION = `Use this tool to make an edit to an existing file.
|
|
2895
|
+
|
|
2896
|
+
This will be read by a less intelligent model, which will quickly apply the edit. You should make it clear what the edit is, while also minimizing the unchanged code you write.
|
|
2897
|
+
|
|
2898
|
+
When writing the edit, you should specify each edit in sequence, with the special comment // ... existing code ... to represent unchanged code in between edited lines.
|
|
2899
|
+
|
|
2900
|
+
For example:
|
|
2901
|
+
|
|
2902
|
+
// ... existing code ...
|
|
2903
|
+
FIRST_EDIT
|
|
2904
|
+
// ... existing code ...
|
|
2905
|
+
SECOND_EDIT
|
|
2906
|
+
// ... existing code ...
|
|
2907
|
+
THIRD_EDIT
|
|
2908
|
+
// ... existing code ...
|
|
2909
|
+
|
|
2910
|
+
You should still bias towards repeating as few lines of the original file as possible to convey the change.
|
|
2911
|
+
But, each edit should contain minimally sufficient context of unchanged lines around the code you're editing to resolve ambiguity.
|
|
2912
|
+
|
|
2913
|
+
DO NOT omit spans of pre-existing code (or comments) without using the // ... existing code ... comment to indicate its absence. If you omit the existing code comment, the model may inadvertently delete these lines.
|
|
2914
|
+
|
|
2915
|
+
If you plan on deleting a section, you must provide context before and after to delete it.
|
|
2916
|
+
|
|
2917
|
+
Make sure it is clear what the edit should be, and where it should be applied.
|
|
2918
|
+
Make edits to a file in a single edit_file call instead of multiple edit_file calls to the same file. The apply model can handle many distinct edits at once.`;
|
|
2919
|
+
var EDIT_FILE_SYSTEM_PROMPT = `When the user is asking for edits to their code, use the edit_file tool to highlight the changes necessary and add comments to indicate where unchanged code has been skipped. For example:
|
|
2920
|
+
|
|
2921
|
+
// ... existing code ...
|
|
2922
|
+
{{ edit_1 }}
|
|
2923
|
+
// ... existing code ...
|
|
2924
|
+
{{ edit_2 }}
|
|
2925
|
+
// ... existing code ...
|
|
2926
|
+
|
|
2927
|
+
Often this will mean that the start/end of the file will be skipped, but that's okay! Rewrite the entire file ONLY if specifically requested. Always provide a brief explanation of the updates, unless the user specifically requests only the code.
|
|
2928
|
+
|
|
2929
|
+
These edit codeblocks are also read by a less intelligent language model, colloquially called the apply model, to update the file. To help specify the edit to the apply model, you will be very careful when generating the codeblock to not introduce ambiguity. You will specify all unchanged regions (code and comments) of the file with "// ... existing code ..." comment markers. This will ensure the apply model will not delete existing unchanged code or comments when editing the file.`;
|
|
2930
|
+
|
|
2931
|
+
// tools/fastapply/openai.ts
|
|
2932
|
+
var editFileTool = {
|
|
2933
|
+
type: "function",
|
|
2934
|
+
function: {
|
|
2935
|
+
name: "edit_file",
|
|
2936
|
+
description: EDIT_FILE_TOOL_DESCRIPTION,
|
|
2937
|
+
parameters: {
|
|
2938
|
+
type: "object",
|
|
2939
|
+
properties: {
|
|
2940
|
+
target_filepath: {
|
|
2941
|
+
type: "string",
|
|
2942
|
+
description: "The path of the target file to modify"
|
|
2943
|
+
},
|
|
2944
|
+
instructions: {
|
|
2945
|
+
type: "string",
|
|
2946
|
+
description: "A single sentence describing what you are changing (first person)"
|
|
2947
|
+
},
|
|
2948
|
+
code_edit: {
|
|
2949
|
+
type: "string",
|
|
2950
|
+
description: "The lazy edit with // ... existing code ... markers"
|
|
2951
|
+
}
|
|
2952
|
+
},
|
|
2953
|
+
required: ["target_filepath", "instructions", "code_edit"]
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
};
|
|
2957
|
+
async function execute2(input, config) {
|
|
2958
|
+
return executeEditFile(input, config);
|
|
2959
|
+
}
|
|
2960
|
+
function getSystemPrompt2() {
|
|
2961
|
+
return EDIT_FILE_SYSTEM_PROMPT;
|
|
2962
|
+
}
|
|
2963
|
+
function formatResult3(result) {
|
|
2964
|
+
if (!result.success) {
|
|
2965
|
+
return `Error editing file: ${result.error}`;
|
|
2966
|
+
}
|
|
2967
|
+
const { changes } = result;
|
|
2968
|
+
const summary = [
|
|
2969
|
+
changes.linesAdded && `+${changes.linesAdded} lines`,
|
|
2970
|
+
changes.linesRemoved && `-${changes.linesRemoved} lines`,
|
|
2971
|
+
changes.linesModified && `~${changes.linesModified} lines modified`
|
|
2972
|
+
].filter(Boolean).join(", ");
|
|
2973
|
+
if (result.udiff) {
|
|
2974
|
+
return `Successfully applied changes to ${result.filepath}:
|
|
2975
|
+
|
|
2976
|
+
${result.udiff}
|
|
2977
|
+
|
|
2978
|
+
Summary: ${summary}`;
|
|
2979
|
+
}
|
|
2980
|
+
return `Successfully applied changes to ${result.filepath}. ${summary}`;
|
|
2981
|
+
}
|
|
2982
|
+
function createEditFileTool(config = {}) {
|
|
2983
|
+
const toolDef = {
|
|
2984
|
+
...editFileTool,
|
|
2985
|
+
...config.description && {
|
|
2986
|
+
function: {
|
|
2987
|
+
...editFileTool.function,
|
|
2988
|
+
description: config.description
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
};
|
|
2992
|
+
return Object.assign({}, toolDef, {
|
|
2993
|
+
execute: async (input) => {
|
|
2994
|
+
const parsedInput = typeof input === "string" ? JSON.parse(input) : input;
|
|
2995
|
+
return execute2(parsedInput, config);
|
|
2996
|
+
},
|
|
2997
|
+
formatResult: (result) => {
|
|
2998
|
+
return formatResult3(result);
|
|
2999
|
+
},
|
|
3000
|
+
getSystemPrompt: () => {
|
|
3001
|
+
return getSystemPrompt2();
|
|
3002
|
+
}
|
|
3003
|
+
});
|
|
3004
|
+
}
|
|
3005
|
+
|
|
3006
|
+
// factories/openai.ts
|
|
3007
|
+
var OpenAIToolFactory = class {
|
|
3008
|
+
constructor(config) {
|
|
3009
|
+
this.config = config;
|
|
3010
|
+
}
|
|
3011
|
+
/**
|
|
3012
|
+
* Create an OpenAI-compatible warp grep tool
|
|
3013
|
+
*
|
|
3014
|
+
* @param toolConfig - Tool configuration (apiKey inherited from MorphClient)
|
|
3015
|
+
* @returns OpenAI ChatCompletionTool with execute and formatResult methods
|
|
3016
|
+
*/
|
|
3017
|
+
createWarpGrepTool(toolConfig) {
|
|
3018
|
+
return createMorphWarpGrepTool({
|
|
3019
|
+
...toolConfig,
|
|
3020
|
+
apiKey: this.config.apiKey
|
|
3021
|
+
});
|
|
3022
|
+
}
|
|
3023
|
+
/**
|
|
3024
|
+
* Create an OpenAI-compatible codebase search tool
|
|
3025
|
+
*
|
|
3026
|
+
* @param toolConfig - Tool configuration with repoId (apiKey inherited from MorphClient)
|
|
3027
|
+
* @returns OpenAI ChatCompletionTool with execute and formatResult methods
|
|
3028
|
+
*/
|
|
3029
|
+
createCodebaseSearchTool(toolConfig) {
|
|
3030
|
+
return createCodebaseSearchTool({
|
|
3031
|
+
...toolConfig,
|
|
3032
|
+
apiKey: this.config.apiKey
|
|
3033
|
+
});
|
|
3034
|
+
}
|
|
3035
|
+
/**
|
|
3036
|
+
* Create an OpenAI-compatible edit file tool
|
|
3037
|
+
*
|
|
3038
|
+
* @param toolConfig - Tool configuration (morphApiKey inherited from MorphClient)
|
|
3039
|
+
* @returns OpenAI ChatCompletionTool with execute and formatResult methods
|
|
3040
|
+
*/
|
|
3041
|
+
createEditFileTool(toolConfig = {}) {
|
|
3042
|
+
return createEditFileTool({
|
|
3043
|
+
...toolConfig,
|
|
3044
|
+
morphApiKey: this.config.apiKey
|
|
3045
|
+
});
|
|
3046
|
+
}
|
|
3047
|
+
};
|
|
3048
|
+
|
|
3049
|
+
// tools/warp_grep/anthropic.ts
|
|
3050
|
+
var INPUT_SCHEMA = {
|
|
3051
|
+
type: "object",
|
|
3052
|
+
properties: {
|
|
3053
|
+
query: { type: "string", description: "Free-form repository question" }
|
|
3054
|
+
},
|
|
3055
|
+
required: ["query"]
|
|
3056
|
+
};
|
|
3057
|
+
async function execute3(input, config) {
|
|
3058
|
+
const parsed = typeof input === "string" ? JSON.parse(input) : input;
|
|
3059
|
+
const provider = config.provider ?? new LocalRipgrepProvider(config.repoRoot, config.excludes);
|
|
3060
|
+
const result = await runWarpGrep({
|
|
3061
|
+
query: parsed.query,
|
|
3062
|
+
repoRoot: config.repoRoot,
|
|
3063
|
+
provider,
|
|
3064
|
+
excludes: config.excludes,
|
|
3065
|
+
includes: config.includes,
|
|
3066
|
+
debug: config.debug ?? false,
|
|
3067
|
+
apiKey: config.apiKey
|
|
3068
|
+
});
|
|
3069
|
+
const finish = result.finish;
|
|
3070
|
+
if (result.terminationReason !== "completed" || !finish?.metadata) {
|
|
3071
|
+
return { success: false, error: "Search did not complete" };
|
|
3072
|
+
}
|
|
3073
|
+
const contexts = (finish.resolved ?? []).map((r) => ({
|
|
3074
|
+
file: r.path,
|
|
3075
|
+
content: r.content
|
|
3076
|
+
}));
|
|
3077
|
+
return { success: true, contexts, summary: finish.payload };
|
|
3078
|
+
}
|
|
3079
|
+
function createMorphWarpGrepTool2(config) {
|
|
3080
|
+
const tool4 = {
|
|
3081
|
+
name: "morph-warp-grep",
|
|
3082
|
+
description: config.description ?? WARP_GREP_DESCRIPTION,
|
|
3083
|
+
input_schema: INPUT_SCHEMA
|
|
3084
|
+
};
|
|
3085
|
+
return Object.assign(tool4, {
|
|
3086
|
+
execute: async (input) => {
|
|
3087
|
+
return execute3(input, config);
|
|
3088
|
+
},
|
|
3089
|
+
formatResult: (result) => {
|
|
3090
|
+
return formatResult(result);
|
|
3091
|
+
},
|
|
3092
|
+
getSystemPrompt: () => {
|
|
3093
|
+
return getSystemPrompt();
|
|
3094
|
+
}
|
|
3095
|
+
});
|
|
3096
|
+
}
|
|
3097
|
+
|
|
3098
|
+
// tools/codebase_search/anthropic.ts
|
|
3099
|
+
function createCodebaseSearchTool2(config) {
|
|
3100
|
+
const toolDefinition = {
|
|
3101
|
+
name: "codebase_search",
|
|
3102
|
+
description: CODEBASE_SEARCH_DESCRIPTION,
|
|
3103
|
+
input_schema: {
|
|
3104
|
+
type: "object",
|
|
3105
|
+
properties: {
|
|
3106
|
+
explanation: {
|
|
3107
|
+
type: "string",
|
|
3108
|
+
description: "One sentence explanation as to why this tool is being used, and how it contributes to the goal."
|
|
3109
|
+
},
|
|
3110
|
+
query: {
|
|
3111
|
+
type: "string",
|
|
3112
|
+
description: 'A complete question about what you want to understand. Ask as if talking to a colleague: "How does X work?", "What happens when Y?", "Where is Z handled?"'
|
|
3113
|
+
},
|
|
3114
|
+
target_directories: {
|
|
3115
|
+
type: "array",
|
|
3116
|
+
items: { type: "string" },
|
|
3117
|
+
description: "Prefix directory paths to limit search scope (single directory only, no glob patterns). Use [] to search entire repo."
|
|
3118
|
+
},
|
|
3119
|
+
limit: {
|
|
3120
|
+
type: "number",
|
|
3121
|
+
description: "Maximum results to return (default: 10)"
|
|
3122
|
+
}
|
|
3123
|
+
},
|
|
3124
|
+
required: ["query", "target_directories", "explanation"]
|
|
3125
|
+
},
|
|
3126
|
+
cache_control: { type: "ephemeral" }
|
|
3127
|
+
};
|
|
3128
|
+
return Object.assign(toolDefinition, {
|
|
3129
|
+
execute: async (input) => {
|
|
3130
|
+
return executeCodebaseSearch(input, config);
|
|
3131
|
+
},
|
|
3132
|
+
formatResult: (result) => {
|
|
3133
|
+
return formatResult4(result);
|
|
3134
|
+
},
|
|
3135
|
+
getSystemPrompt: () => {
|
|
3136
|
+
return CODEBASE_SEARCH_SYSTEM_PROMPT;
|
|
3137
|
+
}
|
|
3138
|
+
});
|
|
3139
|
+
}
|
|
3140
|
+
function formatResult4(result) {
|
|
3141
|
+
if (!result.success) {
|
|
3142
|
+
return `Search failed: ${result.error}`;
|
|
3143
|
+
}
|
|
3144
|
+
if (result.results.length === 0) {
|
|
3145
|
+
return "No matching code found. Try rephrasing your query or broadening the search scope.";
|
|
3146
|
+
}
|
|
3147
|
+
const lines = [];
|
|
3148
|
+
lines.push(`Found ${result.results.length} relevant code sections (searched ${result.stats.candidatesRetrieved} candidates in ${result.stats.searchTimeMs}ms):
|
|
3149
|
+
`);
|
|
3150
|
+
result.results.forEach((r, i) => {
|
|
3151
|
+
const relevance = (r.rerankScore * 100).toFixed(1);
|
|
3152
|
+
lines.push(`${i + 1}. ${r.filepath} (${relevance}% relevant)`);
|
|
3153
|
+
lines.push(` Symbol: ${r.symbolPath}`);
|
|
3154
|
+
lines.push(` Language: ${r.language}`);
|
|
3155
|
+
lines.push(` Lines: ${r.startLine}-${r.endLine}`);
|
|
3156
|
+
lines.push(` Code:`);
|
|
3157
|
+
const codeLines = r.content.split("\n");
|
|
3158
|
+
codeLines.slice(0, Math.min(codeLines.length, 20)).forEach((line) => {
|
|
3159
|
+
lines.push(` ${line}`);
|
|
3160
|
+
});
|
|
3161
|
+
if (codeLines.length > 20) {
|
|
3162
|
+
lines.push(` ... (${codeLines.length - 20} more lines)`);
|
|
3163
|
+
}
|
|
3164
|
+
lines.push("");
|
|
3165
|
+
});
|
|
3166
|
+
return lines.join("\n");
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
// tools/fastapply/anthropic.ts
|
|
3170
|
+
var editFileTool2 = {
|
|
3171
|
+
name: "edit_file",
|
|
3172
|
+
description: EDIT_FILE_TOOL_DESCRIPTION,
|
|
3173
|
+
input_schema: {
|
|
3174
|
+
type: "object",
|
|
3175
|
+
properties: {
|
|
3176
|
+
target_filepath: {
|
|
3177
|
+
type: "string",
|
|
3178
|
+
description: "The path of the target file to modify"
|
|
3179
|
+
},
|
|
3180
|
+
instructions: {
|
|
3181
|
+
type: "string",
|
|
3182
|
+
description: "A single sentence describing what you are changing (first person)"
|
|
3183
|
+
},
|
|
3184
|
+
code_edit: {
|
|
3185
|
+
type: "string",
|
|
3186
|
+
description: "The lazy edit with // ... existing code ... markers"
|
|
3187
|
+
}
|
|
3188
|
+
},
|
|
3189
|
+
required: ["target_filepath", "instructions", "code_edit"]
|
|
3190
|
+
}
|
|
3191
|
+
};
|
|
3192
|
+
function formatResult5(result) {
|
|
3193
|
+
if (!result.success) {
|
|
3194
|
+
return `Error editing file: ${result.error}`;
|
|
3195
|
+
}
|
|
3196
|
+
const { changes } = result;
|
|
3197
|
+
const summary = [
|
|
3198
|
+
changes.linesAdded && `+${changes.linesAdded} lines`,
|
|
3199
|
+
changes.linesRemoved && `-${changes.linesRemoved} lines`,
|
|
3200
|
+
changes.linesModified && `~${changes.linesModified} lines modified`
|
|
3201
|
+
].filter(Boolean).join(", ");
|
|
3202
|
+
if (result.udiff) {
|
|
3203
|
+
return `Successfully applied changes to ${result.filepath}:
|
|
3204
|
+
|
|
3205
|
+
${result.udiff}
|
|
3206
|
+
|
|
3207
|
+
Summary: ${summary}`;
|
|
3208
|
+
}
|
|
3209
|
+
return `Successfully applied changes to ${result.filepath}. ${summary}`;
|
|
3210
|
+
}
|
|
3211
|
+
function createEditFileTool2(config = {}) {
|
|
3212
|
+
const toolDef = {
|
|
3213
|
+
...editFileTool2,
|
|
3214
|
+
...config.description && { description: config.description }
|
|
3215
|
+
};
|
|
3216
|
+
return Object.assign({}, toolDef, {
|
|
3217
|
+
execute: async (input) => {
|
|
3218
|
+
return executeEditFile(input, config);
|
|
3219
|
+
},
|
|
3220
|
+
formatResult: (result) => {
|
|
3221
|
+
return formatResult5(result);
|
|
3222
|
+
},
|
|
3223
|
+
getSystemPrompt: () => {
|
|
3224
|
+
return EDIT_FILE_SYSTEM_PROMPT;
|
|
3225
|
+
}
|
|
3226
|
+
});
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3229
|
+
// factories/anthropic.ts
|
|
3230
|
+
var AnthropicToolFactory = class {
|
|
3231
|
+
constructor(config) {
|
|
3232
|
+
this.config = config;
|
|
3233
|
+
}
|
|
3234
|
+
/**
|
|
3235
|
+
* Create an Anthropic-compatible warp grep tool
|
|
3236
|
+
*
|
|
3237
|
+
* @param toolConfig - Tool configuration (apiKey inherited from MorphClient)
|
|
3238
|
+
* @returns Anthropic Tool with execute and formatResult methods
|
|
3239
|
+
*/
|
|
3240
|
+
createWarpGrepTool(toolConfig) {
|
|
3241
|
+
return createMorphWarpGrepTool2({
|
|
3242
|
+
...toolConfig,
|
|
3243
|
+
apiKey: this.config.apiKey
|
|
3244
|
+
});
|
|
3245
|
+
}
|
|
3246
|
+
/**
|
|
3247
|
+
* Create an Anthropic-compatible codebase search tool
|
|
3248
|
+
*
|
|
3249
|
+
* @param toolConfig - Tool configuration with repoId (apiKey inherited from MorphClient)
|
|
3250
|
+
* @returns Anthropic Tool with execute and formatResult methods
|
|
3251
|
+
*/
|
|
3252
|
+
createCodebaseSearchTool(toolConfig) {
|
|
3253
|
+
return createCodebaseSearchTool2({
|
|
3254
|
+
...toolConfig,
|
|
3255
|
+
apiKey: this.config.apiKey
|
|
3256
|
+
});
|
|
3257
|
+
}
|
|
3258
|
+
/**
|
|
3259
|
+
* Create an Anthropic-compatible edit file tool
|
|
3260
|
+
*
|
|
3261
|
+
* @param toolConfig - Tool configuration (morphApiKey inherited from MorphClient)
|
|
3262
|
+
* @returns Anthropic Tool with execute and formatResult methods
|
|
3263
|
+
*/
|
|
3264
|
+
createEditFileTool(toolConfig = {}) {
|
|
3265
|
+
return createEditFileTool2({
|
|
3266
|
+
...toolConfig,
|
|
3267
|
+
morphApiKey: this.config.apiKey
|
|
3268
|
+
});
|
|
3269
|
+
}
|
|
3270
|
+
};
|
|
3271
|
+
|
|
3272
|
+
// tools/warp_grep/vercel.ts
|
|
3273
|
+
var import_ai = require("ai");
|
|
3274
|
+
var import_zod = require("zod");
|
|
3275
|
+
var warpGrepSchema = import_zod.z.object({
|
|
3276
|
+
query: import_zod.z.string().describe("Free-form repository question")
|
|
3277
|
+
});
|
|
3278
|
+
function createMorphWarpGrepTool3(config) {
|
|
3279
|
+
return (0, import_ai.tool)({
|
|
3280
|
+
description: config.description ?? WARP_GREP_DESCRIPTION,
|
|
3281
|
+
inputSchema: warpGrepSchema,
|
|
3282
|
+
execute: async (params) => {
|
|
3283
|
+
const provider = config.provider ?? new LocalRipgrepProvider(config.repoRoot, config.excludes);
|
|
3284
|
+
const result = await runWarpGrep({
|
|
3285
|
+
query: params.query,
|
|
3286
|
+
repoRoot: config.repoRoot,
|
|
3287
|
+
provider,
|
|
3288
|
+
excludes: config.excludes,
|
|
3289
|
+
includes: config.includes,
|
|
3290
|
+
debug: config.debug ?? false,
|
|
3291
|
+
apiKey: config.apiKey
|
|
3292
|
+
});
|
|
3293
|
+
const finish = result.finish;
|
|
3294
|
+
if (result.terminationReason !== "completed" || !finish?.metadata) {
|
|
3295
|
+
return { success: false, error: "Search did not complete" };
|
|
3296
|
+
}
|
|
3297
|
+
const contexts = (finish.resolved ?? []).map((r) => ({
|
|
3298
|
+
file: r.path,
|
|
3299
|
+
content: r.content
|
|
3300
|
+
}));
|
|
3301
|
+
return { success: true, contexts, summary: finish.payload };
|
|
3302
|
+
}
|
|
3303
|
+
});
|
|
3304
|
+
}
|
|
3305
|
+
|
|
3306
|
+
// tools/codebase_search/vercel.ts
|
|
3307
|
+
var import_ai2 = require("ai");
|
|
3308
|
+
var import_zod2 = require("zod");
|
|
3309
|
+
function createCodebaseSearchTool3(config) {
|
|
3310
|
+
const schema = import_zod2.z.object({
|
|
3311
|
+
query: import_zod2.z.string().describe('A complete question about what you want to understand. Ask as if talking to a colleague: "How does X work?", "What happens when Y?", "Where is Z handled?"'),
|
|
3312
|
+
target_directories: import_zod2.z.array(import_zod2.z.string()).describe("Prefix directory paths to limit search scope (single directory only, no glob patterns). Use [] to search entire repo."),
|
|
3313
|
+
explanation: import_zod2.z.string().describe("One sentence explanation as to why this tool is being used, and how it contributes to the goal."),
|
|
3314
|
+
limit: import_zod2.z.number().optional().describe("Max results to return (default: 10)")
|
|
3315
|
+
});
|
|
3316
|
+
return (0, import_ai2.tool)({
|
|
3317
|
+
description: CODEBASE_SEARCH_DESCRIPTION,
|
|
3318
|
+
inputSchema: schema,
|
|
3319
|
+
execute: async (params) => {
|
|
3320
|
+
const { query, target_directories, explanation, limit } = params;
|
|
3321
|
+
const result = await executeCodebaseSearch(
|
|
3322
|
+
{ query, target_directories, explanation, limit },
|
|
3323
|
+
config
|
|
3324
|
+
);
|
|
3325
|
+
if (!result.success) {
|
|
3326
|
+
return {
|
|
3327
|
+
error: result.error,
|
|
3328
|
+
results: []
|
|
3329
|
+
};
|
|
3330
|
+
}
|
|
3331
|
+
return {
|
|
3332
|
+
found: result.results.length,
|
|
3333
|
+
searchTime: `${result.stats.searchTimeMs}ms`,
|
|
3334
|
+
results: result.results.map((r) => ({
|
|
3335
|
+
file: r.filepath,
|
|
3336
|
+
symbol: r.symbolPath,
|
|
3337
|
+
lines: `${r.startLine}-${r.endLine}`,
|
|
3338
|
+
language: r.language,
|
|
3339
|
+
relevance: `${(r.rerankScore * 100).toFixed(1)}%`,
|
|
3340
|
+
code: r.content
|
|
3341
|
+
}))
|
|
3342
|
+
};
|
|
3343
|
+
}
|
|
3344
|
+
});
|
|
3345
|
+
}
|
|
3346
|
+
|
|
3347
|
+
// tools/fastapply/vercel.ts
|
|
3348
|
+
var import_ai3 = require("ai");
|
|
3349
|
+
var import_zod3 = require("zod");
|
|
3350
|
+
var editFileSchema = import_zod3.z.object({
|
|
3351
|
+
target_filepath: import_zod3.z.string().describe("The path of the target file to modify"),
|
|
3352
|
+
instructions: import_zod3.z.string().describe("A single sentence describing what you are changing (first person)"),
|
|
3353
|
+
code_edit: import_zod3.z.string().describe("The lazy edit with // ... existing code ... markers")
|
|
3354
|
+
});
|
|
3355
|
+
var editFileTool3 = (0, import_ai3.tool)({
|
|
3356
|
+
description: EDIT_FILE_TOOL_DESCRIPTION,
|
|
3357
|
+
inputSchema: editFileSchema,
|
|
3358
|
+
execute: async (params) => {
|
|
3359
|
+
const result = await executeEditFile({
|
|
3360
|
+
target_filepath: params.target_filepath,
|
|
3361
|
+
instructions: params.instructions,
|
|
3362
|
+
code_edit: params.code_edit
|
|
3363
|
+
});
|
|
3364
|
+
if (!result.success) {
|
|
3365
|
+
throw new Error(`Failed to edit file: ${result.error}`);
|
|
3366
|
+
}
|
|
3367
|
+
return {
|
|
3368
|
+
success: true,
|
|
3369
|
+
filepath: result.filepath,
|
|
3370
|
+
changes: result.changes,
|
|
3371
|
+
udiff: result.udiff
|
|
3372
|
+
};
|
|
3373
|
+
}
|
|
3374
|
+
});
|
|
3375
|
+
function createEditFileTool3(config = {}) {
|
|
3376
|
+
const schema = import_zod3.z.object({
|
|
3377
|
+
target_filepath: import_zod3.z.string().describe("The path of the target file to modify"),
|
|
3378
|
+
instructions: import_zod3.z.string().describe("A single sentence describing what you are changing (first person)"),
|
|
3379
|
+
code_edit: import_zod3.z.string().describe("The lazy edit with // ... existing code ... markers")
|
|
3380
|
+
});
|
|
3381
|
+
return (0, import_ai3.tool)({
|
|
3382
|
+
description: config.description || EDIT_FILE_TOOL_DESCRIPTION,
|
|
3383
|
+
inputSchema: schema,
|
|
3384
|
+
execute: async (params) => {
|
|
3385
|
+
const result = await executeEditFile(
|
|
3386
|
+
{
|
|
3387
|
+
target_filepath: params.target_filepath,
|
|
3388
|
+
instructions: params.instructions,
|
|
3389
|
+
code_edit: params.code_edit
|
|
3390
|
+
},
|
|
3391
|
+
config
|
|
3392
|
+
);
|
|
3393
|
+
if (!result.success) {
|
|
3394
|
+
throw new Error(`Failed to edit file: ${result.error}`);
|
|
3395
|
+
}
|
|
3396
|
+
return {
|
|
3397
|
+
success: true,
|
|
3398
|
+
filepath: result.filepath,
|
|
3399
|
+
changes: result.changes,
|
|
3400
|
+
udiff: result.udiff
|
|
3401
|
+
};
|
|
3402
|
+
}
|
|
3403
|
+
});
|
|
3404
|
+
}
|
|
3405
|
+
|
|
3406
|
+
// factories/vercel.ts
|
|
3407
|
+
var VercelToolFactory = class {
|
|
3408
|
+
constructor(config) {
|
|
3409
|
+
this.config = config;
|
|
3410
|
+
}
|
|
3411
|
+
/**
|
|
3412
|
+
* Create a Vercel AI SDK-compatible warp grep tool
|
|
3413
|
+
*
|
|
3414
|
+
* @param toolConfig - Tool configuration (apiKey inherited from MorphClient)
|
|
3415
|
+
* @returns Vercel AI SDK tool
|
|
3416
|
+
*/
|
|
3417
|
+
createWarpGrepTool(toolConfig) {
|
|
3418
|
+
return createMorphWarpGrepTool3({
|
|
3419
|
+
...toolConfig,
|
|
3420
|
+
apiKey: this.config.apiKey
|
|
3421
|
+
});
|
|
3422
|
+
}
|
|
3423
|
+
/**
|
|
3424
|
+
* Create a Vercel AI SDK-compatible codebase search tool
|
|
3425
|
+
*
|
|
3426
|
+
* @param toolConfig - Tool configuration with repoId (apiKey inherited from MorphClient)
|
|
3427
|
+
* @returns Vercel AI SDK tool
|
|
3428
|
+
*/
|
|
3429
|
+
createCodebaseSearchTool(toolConfig) {
|
|
3430
|
+
return createCodebaseSearchTool3({
|
|
3431
|
+
...toolConfig,
|
|
3432
|
+
apiKey: this.config.apiKey
|
|
3433
|
+
});
|
|
3434
|
+
}
|
|
3435
|
+
/**
|
|
3436
|
+
* Create a Vercel AI SDK-compatible edit file tool
|
|
3437
|
+
*
|
|
3438
|
+
* @param toolConfig - Tool configuration (morphApiKey inherited from MorphClient)
|
|
3439
|
+
* @returns Vercel AI SDK tool
|
|
3440
|
+
*/
|
|
3441
|
+
createEditFileTool(toolConfig = {}) {
|
|
3442
|
+
return createEditFileTool3({
|
|
3443
|
+
...toolConfig,
|
|
3444
|
+
morphApiKey: this.config.apiKey
|
|
3445
|
+
});
|
|
3446
|
+
}
|
|
3447
|
+
};
|
|
3448
|
+
|
|
1618
3449
|
// client.ts
|
|
1619
3450
|
var MorphClient = class {
|
|
1620
3451
|
/** Client configuration */
|
|
@@ -1623,12 +3454,20 @@ var MorphClient = class {
|
|
|
1623
3454
|
fastApply;
|
|
1624
3455
|
/** CodebaseSearch tool for semantic code search */
|
|
1625
3456
|
codebaseSearch;
|
|
3457
|
+
/** WarpGrep tool for fast code search using ripgrep */
|
|
3458
|
+
warpGrep;
|
|
1626
3459
|
/** Browser tool for AI-powered browser automation */
|
|
1627
3460
|
browser;
|
|
1628
3461
|
/** Git tool for version control operations */
|
|
1629
3462
|
git;
|
|
1630
3463
|
/** Model routers for intelligent model selection */
|
|
1631
3464
|
routers;
|
|
3465
|
+
/** OpenAI-compatible tool factories */
|
|
3466
|
+
openai;
|
|
3467
|
+
/** Anthropic-compatible tool factories */
|
|
3468
|
+
anthropic;
|
|
3469
|
+
/** Vercel AI SDK tool factories */
|
|
3470
|
+
vercel;
|
|
1632
3471
|
/**
|
|
1633
3472
|
* Create a new Morph SDK client
|
|
1634
3473
|
*
|
|
@@ -1657,6 +3496,12 @@ var MorphClient = class {
|
|
|
1657
3496
|
timeout: config.timeout,
|
|
1658
3497
|
retryConfig: config.retryConfig
|
|
1659
3498
|
});
|
|
3499
|
+
this.warpGrep = new WarpGrepClient({
|
|
3500
|
+
apiKey: config.apiKey,
|
|
3501
|
+
debug: config.debug,
|
|
3502
|
+
timeout: config.timeout,
|
|
3503
|
+
retryConfig: config.retryConfig
|
|
3504
|
+
});
|
|
1660
3505
|
this.browser = new BrowserClient({
|
|
1661
3506
|
apiKey: config.apiKey,
|
|
1662
3507
|
debug: config.debug,
|
|
@@ -1693,18 +3538,92 @@ var MorphClient = class {
|
|
|
1693
3538
|
retryConfig: config.retryConfig
|
|
1694
3539
|
})
|
|
1695
3540
|
};
|
|
3541
|
+
this.openai = new OpenAIToolFactory(config);
|
|
3542
|
+
this.anthropic = new AnthropicToolFactory(config);
|
|
3543
|
+
this.vercel = new VercelToolFactory(config);
|
|
3544
|
+
}
|
|
3545
|
+
};
|
|
3546
|
+
|
|
3547
|
+
// tools/warp_grep/providers/command.ts
|
|
3548
|
+
var CommandExecProvider = class {
|
|
3549
|
+
constructor(opts) {
|
|
3550
|
+
this.opts = opts;
|
|
3551
|
+
}
|
|
3552
|
+
map(path4) {
|
|
3553
|
+
return this.opts.pathMap ? this.opts.pathMap(path4) : path4;
|
|
3554
|
+
}
|
|
3555
|
+
async grep(params) {
|
|
3556
|
+
const remotePath = this.map(params.path);
|
|
3557
|
+
const args = [
|
|
3558
|
+
"--no-config",
|
|
3559
|
+
"--no-heading",
|
|
3560
|
+
"--with-filename",
|
|
3561
|
+
"--line-number",
|
|
3562
|
+
"--color=never",
|
|
3563
|
+
"--trim",
|
|
3564
|
+
"--max-columns=400",
|
|
3565
|
+
...(this.opts.excludes ?? DEFAULT_EXCLUDES).flatMap((e) => ["-g", `!${e}`]),
|
|
3566
|
+
params.pattern,
|
|
3567
|
+
remotePath || "."
|
|
3568
|
+
];
|
|
3569
|
+
const res = await this.opts.run("rg", args, { cwd: this.opts.cwd, env: this.opts.env });
|
|
3570
|
+
if (res.exitCode === -1) throw new Error(res.stderr || "ripgrep execution failed");
|
|
3571
|
+
if (res.exitCode !== 0 && res.exitCode !== 1) throw new Error(res.stderr || `ripgrep failed (${res.exitCode})`);
|
|
3572
|
+
const lines = (res.stdout || "").trim().split(/\r?\n/).filter((l) => l.length > 0);
|
|
3573
|
+
return { lines };
|
|
3574
|
+
}
|
|
3575
|
+
async glob(params) {
|
|
3576
|
+
const remotePath = this.map(params.path);
|
|
3577
|
+
const args = [
|
|
3578
|
+
"--no-config",
|
|
3579
|
+
"--files",
|
|
3580
|
+
"-g",
|
|
3581
|
+
params.pattern,
|
|
3582
|
+
...(this.opts.excludes ?? DEFAULT_EXCLUDES).flatMap((e) => ["-g", `!${e}`]),
|
|
3583
|
+
remotePath || "."
|
|
3584
|
+
];
|
|
3585
|
+
const res = await this.opts.run("rg", args, { cwd: this.opts.cwd, env: this.opts.env });
|
|
3586
|
+
if (res.exitCode === -1) throw new Error(res.stderr || "ripgrep execution failed");
|
|
3587
|
+
const files = (res.stdout || "").trim().split(/\r?\n/).filter((l) => l.length > 0);
|
|
3588
|
+
return { files };
|
|
3589
|
+
}
|
|
3590
|
+
async read(params) {
|
|
3591
|
+
const remotePath = this.map(params.path);
|
|
3592
|
+
const rc = this.opts.readCommand ? this.opts.readCommand(remotePath, params.start, params.end) : { cmd: "sed", args: ["-n", `${params.start ?? 1},${params.end ?? 1e6}p`, remotePath] };
|
|
3593
|
+
const res = await this.opts.run(rc.cmd, rc.args, { cwd: this.opts.cwd, env: this.opts.env });
|
|
3594
|
+
if (res.exitCode !== 0) throw new Error(res.stderr || `read failed (${res.exitCode})`);
|
|
3595
|
+
const text = res.stdout || "";
|
|
3596
|
+
const lines = text.split(/\r?\n/).map((line, idx) => `${(params.start ?? 1) + idx}|${line}`);
|
|
3597
|
+
return { lines: lines.filter((l) => l !== `${(params.start ?? 1) + (lines.length - 1)}|`) };
|
|
3598
|
+
}
|
|
3599
|
+
async analyse(params) {
|
|
3600
|
+
const target = this.map(params.path);
|
|
3601
|
+
const pattern = params.pattern ?? "*";
|
|
3602
|
+
const files = await this.glob({ pattern, path: target }).catch(() => ({ files: [] }));
|
|
3603
|
+
return files.files.slice(0, params.maxResults ?? 100).map((f) => ({
|
|
3604
|
+
name: f.split("/").pop() || f,
|
|
3605
|
+
path: f,
|
|
3606
|
+
type: f.endsWith("/") ? "dir" : "file",
|
|
3607
|
+
depth: 0
|
|
3608
|
+
}));
|
|
1696
3609
|
}
|
|
1697
3610
|
};
|
|
1698
3611
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1699
3612
|
0 && (module.exports = {
|
|
1700
3613
|
AnthropicRouter,
|
|
3614
|
+
AnthropicToolFactory,
|
|
1701
3615
|
BrowserClient,
|
|
1702
3616
|
CodebaseSearchClient,
|
|
3617
|
+
CommandExecProvider,
|
|
1703
3618
|
FastApplyClient,
|
|
1704
3619
|
GeminiRouter,
|
|
3620
|
+
LocalRipgrepProvider,
|
|
1705
3621
|
MorphClient,
|
|
1706
3622
|
MorphGit,
|
|
1707
3623
|
OpenAIRouter,
|
|
1708
|
-
|
|
3624
|
+
OpenAIToolFactory,
|
|
3625
|
+
RawRouter,
|
|
3626
|
+
VercelToolFactory,
|
|
3627
|
+
WarpGrepClient
|
|
1709
3628
|
});
|
|
1710
3629
|
//# sourceMappingURL=index.cjs.map
|