@morphllm/morphsdk 0.2.44 → 0.2.46

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