minimal-agent 0.2.0 → 0.3.0
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 +50 -72
- package/package.json +18 -13
- package/plugins/ralph-wiggum/plugin.js +205 -0
- package/plugins/ralph-wiggum/src/goalState.js +260 -0
- package/plugins/ralph-wiggum/src/{sentinels.ts → sentinels.js} +4 -7
- package/plugins/ralph-wiggum/src/stopHookRunner.js +104 -0
- package/plugins/ralph-wiggum/src/verificationGate.js +202 -0
- package/plugins/workflow-runner/{plugin.ts → plugin.js} +20 -26
- package/plugins/workflow-runner/src/expressions.js +369 -0
- package/plugins/workflow-runner/src/index.js +174 -0
- package/plugins/workflow-runner/src/loader.js +183 -0
- package/plugins/workflow-runner/src/runner.js +290 -0
- package/plugins/workflow-runner/src/stepExecutors/assert.js +28 -0
- package/plugins/workflow-runner/src/stepExecutors/llm.js +44 -0
- package/plugins/workflow-runner/src/stepExecutors/skill.js +103 -0
- package/plugins/workflow-runner/src/stepExecutors/{tool.ts → tool.js} +19 -25
- package/plugins/workflow-runner/src/types.js +59 -0
- package/plugins/workflow-runner/src/{workflowState.ts → workflowState.js} +21 -40
- package/src/bootstrap/cwdArg.js +22 -0
- package/src/bootstrap/workingDir.js +31 -0
- package/src/cli/configWizard.js +272 -0
- package/src/cli/print.js +192 -0
- package/src/config/configFile.js +78 -0
- package/src/config.js +118 -0
- package/src/context/compact.js +357 -0
- package/src/context/microCompactLite.js +151 -0
- package/src/context/persistContext.js +109 -0
- package/src/context/reactiveCompact.js +121 -0
- package/src/context/sessionPath.js +58 -0
- package/src/context/snipCompact.js +112 -0
- package/src/context/tokenCounter.js +66 -0
- package/src/llm/client.js +182 -0
- package/src/loop.js +230 -0
- package/src/main.js +116 -0
- package/src/plugin-sdk.js +24 -0
- package/src/plugins/commandRouter.js +169 -0
- package/src/plugins/hookEngine.js +258 -0
- package/src/plugins/pluginApi.js +23 -0
- package/src/plugins/pluginLoader.js +71 -0
- package/src/plugins/pluginRunner.js +65 -0
- package/src/plugins/transcript.js +171 -0
- package/src/prompts/projectInstructions.js +48 -0
- package/src/prompts/skillList.js +126 -0
- package/src/prompts/system.js +155 -0
- package/src/session/runTurn.js +41 -0
- package/src/session/sessionState.js +19 -0
- package/src/tools/bash/bash.js +352 -0
- package/src/tools/bash/semantics.js +85 -0
- package/src/tools/bash/warnings.js +98 -0
- package/src/tools/edit/edit.js +253 -0
- package/src/tools/edit/multi-edit.js +155 -0
- package/src/tools/glob/glob.js +97 -0
- package/src/tools/grep/grep.js +185 -0
- package/src/tools/grep/rgPath.js +173 -0
- package/src/tools/index.js +94 -0
- package/src/tools/read/read.js +209 -0
- package/src/tools/shared/fileState.js +61 -0
- package/src/tools/shared/fileUtils.js +281 -0
- package/src/tools/shared/schemas.js +16 -0
- package/src/tools/types.js +21 -0
- package/src/tools/webbrowser/browser.js +55 -0
- package/src/tools/webbrowser/webbrowser.js +194 -0
- package/src/tools/webfetch/preapproved.js +267 -0
- package/src/tools/webfetch/webfetch.js +317 -0
- package/src/tools/websearch/websearch.js +161 -0
- package/src/tools/write/write.js +125 -0
- package/src/types/turndown.d.ts +23 -0
- package/src/types.js +16 -0
- package/src/ui/App.js +37 -0
- package/src/ui/InputBox.js +240 -0
- package/src/ui/MessageList.js +28 -0
- package/src/ui/Root.js +70 -0
- package/src/ui/StatusLine.js +41 -0
- package/src/ui/ToolStatus.js +11 -0
- package/src/ui/hooks/useChat.js +234 -0
- package/src/ui/hooks/usePasteHandler.js +137 -0
- package/src/ui/hooks/useTextBuffer.js +55 -0
- package/src/ui/hooks/useTokenUsage.js +30 -0
- package/src/ui/textBuffer.js +217 -0
- package/src/utils/packageRoot.js +37 -0
- package/src/utils/resourcePaths.js +49 -0
- package/src/utils/zodToJson.js +29 -0
- package/dist/main.js +0 -5315
- package/plugins/ralph-wiggum/plugin.ts +0 -275
- package/plugins/ralph-wiggum/scripts/setup-ralph-loop.sh +0 -203
- package/plugins/ralph-wiggum/src/goalState.ts +0 -310
- package/plugins/ralph-wiggum/src/stopHookRunner.ts +0 -136
- package/plugins/ralph-wiggum/src/verificationGate.ts +0 -252
- package/plugins/ralph-wiggum/test/goalState.test.ts +0 -410
- package/plugins/ralph-wiggum/test/verificationGate.test.ts +0 -122
- package/plugins/workflow-runner/src/expressions.ts +0 -371
- package/plugins/workflow-runner/src/index.ts +0 -194
- package/plugins/workflow-runner/src/loader.ts +0 -193
- package/plugins/workflow-runner/src/runner.ts +0 -313
- package/plugins/workflow-runner/src/stepExecutors/assert.ts +0 -30
- package/plugins/workflow-runner/src/stepExecutors/llm.ts +0 -54
- package/plugins/workflow-runner/src/stepExecutors/skill.ts +0 -115
- package/plugins/workflow-runner/src/types.ts +0 -183
- package/plugins/workflow-runner/test/cli.e2e.test.ts +0 -114
- package/plugins/workflow-runner/test/e2e.test.ts +0 -268
- package/plugins/workflow-runner/test/expressions.test.ts +0 -140
- package/plugins/workflow-runner/test/fixtures/cli-e2e.yaml +0 -27
- package/plugins/workflow-runner/test/fixtures/hello-workflow.yaml +0 -49
- package/plugins/workflow-runner/test/graceful.test.ts +0 -139
- package/plugins/workflow-runner/test/loader.test.ts +0 -216
- package/plugins/workflow-runner/test/pluginRunner.isolation.test.ts +0 -230
- package/plugins/workflow-runner/test/runner.test.ts +0 -511
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================
|
|
3
|
+
* src/ui/textBuffer.ts —— 输入框文本缓冲(纯逻辑,无 React)
|
|
4
|
+
* ------------------------------------------------------------
|
|
5
|
+
* 把"光标 + 文本"建模成一对值 { text, cursor }:
|
|
6
|
+
* - text 是 user-perceived 字符串(可含 \n,多行也行)
|
|
7
|
+
* - cursor 是 **code-point 偏移**(非 UTF-16),保证不会切坏 emoji / 中日韩
|
|
8
|
+
*
|
|
9
|
+
* 所有 op 都是纯函数:返回新的 { text, cursor },不改入参。
|
|
10
|
+
* 这样 React 端用 useState 包一下就行;测试也直接对纯函数断言、零依赖。
|
|
11
|
+
*
|
|
12
|
+
* 支持的操作:
|
|
13
|
+
* - 插入:insert / setText / clear
|
|
14
|
+
* - 删除:deleteBefore(Backspace)/ deleteAt(Delete)
|
|
15
|
+
* / killToLineStart(Ctrl+U)/ killToLineEnd(Ctrl+K)
|
|
16
|
+
* / deleteWordBefore(Ctrl+W)
|
|
17
|
+
* - 移动:moveLeft / moveRight / moveWordLeft / moveWordRight
|
|
18
|
+
* / moveLineStart / moveLineEnd
|
|
19
|
+
* / moveBufferStart / moveBufferEnd
|
|
20
|
+
* / moveUp / moveDown(按逻辑行,跨 \n,保持列位)
|
|
21
|
+
*
|
|
22
|
+
* 词边界定义(够用即可,不学完整 vim/emacs):
|
|
23
|
+
* [A-Za-z0-9_] 视为 word char;其他(空格、标点、CJK、emoji)都是 non-word。
|
|
24
|
+
* word-jump 的语义对齐多数 emacs:先吃当前侧的 non-word,再吃 word。
|
|
25
|
+
* ============================================================
|
|
26
|
+
*/
|
|
27
|
+
export const EMPTY_BUFFER = { text: '', cursor: 0 };
|
|
28
|
+
// ---------- 工具:code-point 视角操作字符串 ----------
|
|
29
|
+
/** 把字符串拆成 code-point 数组(Array.from 自动处理 surrogate pair) */
|
|
30
|
+
function toCps(s) {
|
|
31
|
+
return Array.from(s);
|
|
32
|
+
}
|
|
33
|
+
function fromCps(cps) {
|
|
34
|
+
return cps.join('');
|
|
35
|
+
}
|
|
36
|
+
function isWordChar(c) {
|
|
37
|
+
if (!c)
|
|
38
|
+
return false;
|
|
39
|
+
return /[A-Za-z0-9_]/.test(c);
|
|
40
|
+
}
|
|
41
|
+
/** 给定 code-point 数组与一个偏移,返回所在逻辑行的起点(指向 \n 之后或 0) */
|
|
42
|
+
function findLineStart(cps, idx) {
|
|
43
|
+
let i = idx;
|
|
44
|
+
while (i > 0 && cps[i - 1] !== '\n')
|
|
45
|
+
i--;
|
|
46
|
+
return i;
|
|
47
|
+
}
|
|
48
|
+
/** 给定 code-point 数组与一个偏移,返回所在逻辑行的终点(指向 \n 或末尾) */
|
|
49
|
+
function findLineEnd(cps, idx) {
|
|
50
|
+
let i = idx;
|
|
51
|
+
while (i < cps.length && cps[i] !== '\n')
|
|
52
|
+
i++;
|
|
53
|
+
return i;
|
|
54
|
+
}
|
|
55
|
+
/** 把 cursor 钳制到合法范围 */
|
|
56
|
+
function clamp(cps, idx) {
|
|
57
|
+
return Math.max(0, Math.min(cps.length, idx));
|
|
58
|
+
}
|
|
59
|
+
// ---------- 插入 ----------
|
|
60
|
+
export function bufInsert(s, content) {
|
|
61
|
+
if (content.length === 0)
|
|
62
|
+
return s;
|
|
63
|
+
const cps = toCps(s.text);
|
|
64
|
+
const ins = toCps(content);
|
|
65
|
+
cps.splice(s.cursor, 0, ...ins);
|
|
66
|
+
return { text: fromCps(cps), cursor: s.cursor + ins.length };
|
|
67
|
+
}
|
|
68
|
+
export function bufSetText(text) {
|
|
69
|
+
return { text, cursor: toCps(text).length };
|
|
70
|
+
}
|
|
71
|
+
export function bufClear() {
|
|
72
|
+
return EMPTY_BUFFER;
|
|
73
|
+
}
|
|
74
|
+
// ---------- 删除 ----------
|
|
75
|
+
export function bufDeleteBefore(s) {
|
|
76
|
+
if (s.cursor === 0)
|
|
77
|
+
return s;
|
|
78
|
+
const cps = toCps(s.text);
|
|
79
|
+
cps.splice(s.cursor - 1, 1);
|
|
80
|
+
return { text: fromCps(cps), cursor: s.cursor - 1 };
|
|
81
|
+
}
|
|
82
|
+
export function bufDeleteAt(s) {
|
|
83
|
+
const cps = toCps(s.text);
|
|
84
|
+
if (s.cursor >= cps.length)
|
|
85
|
+
return s;
|
|
86
|
+
cps.splice(s.cursor, 1);
|
|
87
|
+
return { text: fromCps(cps), cursor: s.cursor };
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Ctrl+W / M-DEL:先吃光标左侧的非词字符(空格 / 标点),再吃前一个词。
|
|
91
|
+
* 例:光标贴在 "world" 后 → 删 'world',留 "hello "。
|
|
92
|
+
* 光标在 "hello " 之后 → 先吃 3 空格再吃 "hello",留空。
|
|
93
|
+
*/
|
|
94
|
+
export function bufDeleteWordBefore(s) {
|
|
95
|
+
if (s.cursor === 0)
|
|
96
|
+
return s;
|
|
97
|
+
const cps = toCps(s.text);
|
|
98
|
+
let i = s.cursor;
|
|
99
|
+
while (i > 0 && !isWordChar(cps[i - 1]))
|
|
100
|
+
i--;
|
|
101
|
+
while (i > 0 && isWordChar(cps[i - 1]))
|
|
102
|
+
i--;
|
|
103
|
+
cps.splice(i, s.cursor - i);
|
|
104
|
+
return { text: fromCps(cps), cursor: i };
|
|
105
|
+
}
|
|
106
|
+
/** Ctrl+U:从光标删到当前逻辑行行首 */
|
|
107
|
+
export function bufKillToLineStart(s) {
|
|
108
|
+
const cps = toCps(s.text);
|
|
109
|
+
const lineStart = findLineStart(cps, s.cursor);
|
|
110
|
+
if (lineStart === s.cursor)
|
|
111
|
+
return s;
|
|
112
|
+
cps.splice(lineStart, s.cursor - lineStart);
|
|
113
|
+
return { text: fromCps(cps), cursor: lineStart };
|
|
114
|
+
}
|
|
115
|
+
/** Ctrl+K:从光标删到当前逻辑行行末(不删 \n 自身) */
|
|
116
|
+
export function bufKillToLineEnd(s) {
|
|
117
|
+
const cps = toCps(s.text);
|
|
118
|
+
const lineEnd = findLineEnd(cps, s.cursor);
|
|
119
|
+
if (lineEnd === s.cursor)
|
|
120
|
+
return s;
|
|
121
|
+
cps.splice(s.cursor, lineEnd - s.cursor);
|
|
122
|
+
return { text: fromCps(cps), cursor: s.cursor };
|
|
123
|
+
}
|
|
124
|
+
// ---------- 移动 ----------
|
|
125
|
+
export function bufMoveLeft(s) {
|
|
126
|
+
return { text: s.text, cursor: Math.max(0, s.cursor - 1) };
|
|
127
|
+
}
|
|
128
|
+
export function bufMoveRight(s) {
|
|
129
|
+
const len = toCps(s.text).length;
|
|
130
|
+
return { text: s.text, cursor: Math.min(len, s.cursor + 1) };
|
|
131
|
+
}
|
|
132
|
+
export function bufMoveWordLeft(s) {
|
|
133
|
+
const cps = toCps(s.text);
|
|
134
|
+
let i = s.cursor;
|
|
135
|
+
while (i > 0 && !isWordChar(cps[i - 1]))
|
|
136
|
+
i--;
|
|
137
|
+
while (i > 0 && isWordChar(cps[i - 1]))
|
|
138
|
+
i--;
|
|
139
|
+
return { text: s.text, cursor: i };
|
|
140
|
+
}
|
|
141
|
+
export function bufMoveWordRight(s) {
|
|
142
|
+
const cps = toCps(s.text);
|
|
143
|
+
let i = s.cursor;
|
|
144
|
+
while (i < cps.length && !isWordChar(cps[i]))
|
|
145
|
+
i++;
|
|
146
|
+
while (i < cps.length && isWordChar(cps[i]))
|
|
147
|
+
i++;
|
|
148
|
+
return { text: s.text, cursor: i };
|
|
149
|
+
}
|
|
150
|
+
export function bufMoveLineStart(s) {
|
|
151
|
+
const cps = toCps(s.text);
|
|
152
|
+
return { text: s.text, cursor: findLineStart(cps, s.cursor) };
|
|
153
|
+
}
|
|
154
|
+
export function bufMoveLineEnd(s) {
|
|
155
|
+
const cps = toCps(s.text);
|
|
156
|
+
return { text: s.text, cursor: findLineEnd(cps, s.cursor) };
|
|
157
|
+
}
|
|
158
|
+
export function bufMoveBufferStart(s) {
|
|
159
|
+
return { text: s.text, cursor: 0 };
|
|
160
|
+
}
|
|
161
|
+
export function bufMoveBufferEnd(s) {
|
|
162
|
+
return { text: s.text, cursor: toCps(s.text).length };
|
|
163
|
+
}
|
|
164
|
+
/** ↑:上移一个逻辑行,保留列位(目标行短就贴行末) */
|
|
165
|
+
export function bufMoveUp(s) {
|
|
166
|
+
const cps = toCps(s.text);
|
|
167
|
+
const lineStart = findLineStart(cps, s.cursor);
|
|
168
|
+
if (lineStart === 0)
|
|
169
|
+
return s; // 已在首行
|
|
170
|
+
const prevLineEnd = lineStart - 1; // 指向上一行末尾的 \n
|
|
171
|
+
const prevLineStart = findLineStart(cps, prevLineEnd);
|
|
172
|
+
const col = s.cursor - lineStart;
|
|
173
|
+
const prevLineLen = prevLineEnd - prevLineStart;
|
|
174
|
+
return { text: s.text, cursor: prevLineStart + Math.min(col, prevLineLen) };
|
|
175
|
+
}
|
|
176
|
+
/** ↓:下移一个逻辑行,保留列位 */
|
|
177
|
+
export function bufMoveDown(s) {
|
|
178
|
+
const cps = toCps(s.text);
|
|
179
|
+
const lineStart = findLineStart(cps, s.cursor);
|
|
180
|
+
const lineEnd = findLineEnd(cps, s.cursor);
|
|
181
|
+
if (lineEnd === cps.length)
|
|
182
|
+
return s; // 已在末行
|
|
183
|
+
const nextLineStart = lineEnd + 1;
|
|
184
|
+
const nextLineEnd = findLineEnd(cps, nextLineStart);
|
|
185
|
+
const col = s.cursor - lineStart;
|
|
186
|
+
const nextLineLen = nextLineEnd - nextLineStart;
|
|
187
|
+
return { text: s.text, cursor: nextLineStart + Math.min(col, nextLineLen) };
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* 给定一个 BufferState,算出它的可视布局。
|
|
191
|
+
* 渲染端拿到 lines + cursorRow/Col 就能把光标绘制在正确位置。
|
|
192
|
+
*/
|
|
193
|
+
export function layoutBuffer(s) {
|
|
194
|
+
const cps = toCps(s.text);
|
|
195
|
+
const cursor = clamp(cps, s.cursor);
|
|
196
|
+
const lineStart = findLineStart(cps, cursor);
|
|
197
|
+
// 把 cps 按 \n 分行(保持 code-point 视角,避免 surrogate pair 撕裂)
|
|
198
|
+
const lines = [];
|
|
199
|
+
let buf = [];
|
|
200
|
+
let cursorRow = 0;
|
|
201
|
+
let charCount = 0;
|
|
202
|
+
for (const ch of cps) {
|
|
203
|
+
if (ch === '\n') {
|
|
204
|
+
lines.push(fromCps(buf));
|
|
205
|
+
buf = [];
|
|
206
|
+
if (charCount < lineStart)
|
|
207
|
+
cursorRow++;
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
buf.push(ch);
|
|
211
|
+
}
|
|
212
|
+
charCount++;
|
|
213
|
+
}
|
|
214
|
+
lines.push(fromCps(buf));
|
|
215
|
+
const cursorCol = cursor - lineStart;
|
|
216
|
+
return { lines, cursorRow, cursorCol };
|
|
217
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================
|
|
3
|
+
* src/utils/packageRoot.ts —— 定位 minimal-agent 包根目录
|
|
4
|
+
* ------------------------------------------------------------
|
|
5
|
+
* 问题:bundle 后 import.meta.url 指向 dist/main.js,
|
|
6
|
+
* 源码模式则指向 src/<deep>/*.ts,两者深度不同。
|
|
7
|
+
* 固定 `join(__dirname, '..', '..')` 在某一种模式下必然错。
|
|
8
|
+
*
|
|
9
|
+
* 方案:从调用方文件起向上找最近的 package.json,把那一层
|
|
10
|
+
* 当作包根。源码模式(src/prompts、src/tools/grep)、bundle
|
|
11
|
+
* 模式(dist/main.js)、全局安装(node_modules/minimal-agent/dist)
|
|
12
|
+
* 全都自动适配。
|
|
13
|
+
* ============================================================
|
|
14
|
+
*/
|
|
15
|
+
import { existsSync } from 'node:fs';
|
|
16
|
+
import { dirname, resolve } from 'node:path';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
const cache = new Map();
|
|
19
|
+
/** 向上最多翻 8 级;超过仍找不到视为环境异常。 */
|
|
20
|
+
const MAX_DEPTH = 8;
|
|
21
|
+
export function findPackageRoot(metaUrl) {
|
|
22
|
+
const cached = cache.get(metaUrl);
|
|
23
|
+
if (cached !== undefined)
|
|
24
|
+
return cached;
|
|
25
|
+
let dir = dirname(fileURLToPath(metaUrl));
|
|
26
|
+
for (let i = 0; i < MAX_DEPTH; i++) {
|
|
27
|
+
if (existsSync(resolve(dir, 'package.json'))) {
|
|
28
|
+
cache.set(metaUrl, dir);
|
|
29
|
+
return dir;
|
|
30
|
+
}
|
|
31
|
+
const parent = dirname(dir);
|
|
32
|
+
if (parent === dir)
|
|
33
|
+
break;
|
|
34
|
+
dir = parent;
|
|
35
|
+
}
|
|
36
|
+
throw new Error(`packageRoot: 找不到 package.json(起点 ${metaUrl})`);
|
|
37
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================
|
|
3
|
+
* src/utils/resourcePaths.ts —— 资源目录双源解析
|
|
4
|
+
* ------------------------------------------------------------
|
|
5
|
+
* 问题:skills/ 和 plugins/ 这类"资源目录"需要在两种场景下都能找到:
|
|
6
|
+
* - 用户在 minimal-agent 源码仓库里跑(dev):cwd === packageRoot
|
|
7
|
+
* - 用户全局 npm install 后在自己项目里跑:cwd ≠ packageRoot
|
|
8
|
+
* 此时既要看 npm 包自带的资源,也要看用户在自己项目里新加的资源。
|
|
9
|
+
*
|
|
10
|
+
* 方案:返回 [cwd/<name>, packageRoot/<name>],cwd 优先。
|
|
11
|
+
* - 用 existsSync 过滤不存在的目录(保留静默降级)
|
|
12
|
+
* - cwd 与 packageRoot 重合时去重(dev 场景)
|
|
13
|
+
*
|
|
14
|
+
* 消费者(commandRouter / skillList / 各插件 loader)按顺序扫描,先扫到的
|
|
15
|
+
* 同名条目胜出 —— 让用户能在自己项目里覆盖或扩展全局自带资源。
|
|
16
|
+
*
|
|
17
|
+
* ResourceName 故意是开放 string:插件可以自由声明自己的资源名(如
|
|
18
|
+
* workflow-runner 的 'workflows'),框架不预设资源名清单。
|
|
19
|
+
* ============================================================
|
|
20
|
+
*/
|
|
21
|
+
import { existsSync } from 'node:fs';
|
|
22
|
+
import { join, resolve } from 'node:path';
|
|
23
|
+
import { getWorkingDir } from '../bootstrap/workingDir.js';
|
|
24
|
+
import { findPackageRoot } from './packageRoot.js';
|
|
25
|
+
/**
|
|
26
|
+
* 返回资源目录的搜索顺序数组(cwd 优先 + packageRoot fallback)。
|
|
27
|
+
*
|
|
28
|
+
* @param name 资源类别名(也作为子目录名)
|
|
29
|
+
* @param metaUrl 调用方 import.meta.url(用于定位 packageRoot)
|
|
30
|
+
* @returns 存在的搜索路径数组;都不存在则返回空数组
|
|
31
|
+
*/
|
|
32
|
+
export function getResourceSearchPaths(name, metaUrl) {
|
|
33
|
+
const paths = [];
|
|
34
|
+
const cwdPath = resolve(join(getWorkingDir(), name));
|
|
35
|
+
if (existsSync(cwdPath)) {
|
|
36
|
+
paths.push(cwdPath);
|
|
37
|
+
}
|
|
38
|
+
let pkgPath;
|
|
39
|
+
try {
|
|
40
|
+
pkgPath = resolve(join(findPackageRoot(metaUrl), name));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return paths;
|
|
44
|
+
}
|
|
45
|
+
if (pkgPath !== cwdPath && existsSync(pkgPath)) {
|
|
46
|
+
paths.push(pkgPath);
|
|
47
|
+
}
|
|
48
|
+
return paths;
|
|
49
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================
|
|
3
|
+
* src/utils/zodToJson.ts —— Zod schema → JSON Schema 自动派生
|
|
4
|
+
* ------------------------------------------------------------
|
|
5
|
+
* 封装 zod-to-json-schema,统一清理输出格式。
|
|
6
|
+
*
|
|
7
|
+
* 设计原则:
|
|
8
|
+
* - 单一数据源:每个工具只写一份 Zod schema
|
|
9
|
+
* - 自动派生:parameters(给 LLM 看的 JSON Schema)由 Zod 自动生成
|
|
10
|
+
* - 消除冗余:不再需要手写两份重复的参数定义
|
|
11
|
+
* ============================================================
|
|
12
|
+
*/
|
|
13
|
+
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
14
|
+
/**
|
|
15
|
+
* 从 Zod schema 派生出 OpenAI tools[] 兼容的 JSON Schema 对象。
|
|
16
|
+
*
|
|
17
|
+
* 做了以下清理:
|
|
18
|
+
* - 移除 `$schema` 元数据字段(LLM 不需要)
|
|
19
|
+
* - `additionalProperties` 默认 false(防止模型传多余字段)
|
|
20
|
+
*/
|
|
21
|
+
export function toToolParameters(schema) {
|
|
22
|
+
const json = zodToJsonSchema(schema, {
|
|
23
|
+
target: 'openApi3',
|
|
24
|
+
$refStrategy: 'none',
|
|
25
|
+
});
|
|
26
|
+
// 移除 LLM 不需要的元数据
|
|
27
|
+
const { $schema, ...rest } = json;
|
|
28
|
+
return rest;
|
|
29
|
+
}
|