@teammy/server 0.1.13 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +955 -627
- package/dist/native-pi-entry.js +211 -6
- package/dist/project-pi-entry.js +219 -11
- package/dist/web/downloads/teammy-skill.zip +0 -0
- package/package.json +1 -1
package/dist/native-pi-entry.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// apps/server/ai/native-pi-entry.ts
|
|
2
|
-
import { InMemoryCredentialStore, InMemoryModelsStore, Type as
|
|
2
|
+
import { InMemoryCredentialStore, InMemoryModelsStore, Type as Type3 } from "@earendil-works/pi-ai";
|
|
3
3
|
import { createAssistantMessageEventStream } from "@earendil-works/pi-ai/utils/event-stream";
|
|
4
4
|
import {
|
|
5
5
|
createAgentSession,
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
SessionManager,
|
|
9
9
|
SettingsManager
|
|
10
10
|
} from "@earendil-works/pi-coding-agent";
|
|
11
|
-
import { z as
|
|
11
|
+
import { z as z4 } from "zod";
|
|
12
12
|
|
|
13
13
|
// apps/server/ai/agent-channel.ts
|
|
14
14
|
import { createInterface } from "node:readline";
|
|
@@ -60,6 +60,20 @@ import { createModels, createProvider, Type } from "@earendil-works/pi-ai";
|
|
|
60
60
|
import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
|
|
61
61
|
import { openAIResponsesApi } from "@earendil-works/pi-ai/api/openai-responses.lazy";
|
|
62
62
|
import { anthropicMessagesApi } from "@earendil-works/pi-ai/api/anthropic-messages.lazy";
|
|
63
|
+
|
|
64
|
+
// apps/server/errors.ts
|
|
65
|
+
var HubError = class extends Error {
|
|
66
|
+
constructor(statusCode, code, message) {
|
|
67
|
+
super(message);
|
|
68
|
+
this.statusCode = statusCode;
|
|
69
|
+
this.code = code;
|
|
70
|
+
this.name = "HubError";
|
|
71
|
+
}
|
|
72
|
+
statusCode;
|
|
73
|
+
code;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// apps/server/ai/models.ts
|
|
63
77
|
function modelAccess(profile, key, reasoning = false) {
|
|
64
78
|
const model = {
|
|
65
79
|
id: profile.model,
|
|
@@ -109,6 +123,196 @@ function loadAgentSkills(directory = `${agentResourcesPath}/skills`) {
|
|
|
109
123
|
return existsSync(directory) ? loadSkillsFromDir({ dir: directory, source: "teammy-deployment" }) : { skills: [], diagnostics: [] };
|
|
110
124
|
}
|
|
111
125
|
|
|
126
|
+
// apps/server/ai/context-content.ts
|
|
127
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
128
|
+
import { link, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
129
|
+
import { join } from "node:path";
|
|
130
|
+
import { z as z2 } from "zod";
|
|
131
|
+
var CONTENT_BYTES = 32 * 1024;
|
|
132
|
+
var CONTENT_LINES = 500;
|
|
133
|
+
var json = (value) => JSON.stringify(value) ?? "null";
|
|
134
|
+
function contentFits(value, bytes = CONTENT_BYTES) {
|
|
135
|
+
const text = json(value);
|
|
136
|
+
return Buffer.byteLength(text) <= bytes && (text.match(/\\n|\n/g)?.length ?? 0) < CONTENT_LINES;
|
|
137
|
+
}
|
|
138
|
+
var ReadToolResultSchema = z2.object({
|
|
139
|
+
id: z2.string().regex(/^[a-f0-9]{64}$/),
|
|
140
|
+
path: z2.array(z2.union([z2.string().max(512), z2.number().int().nonnegative()])).max(32).refine((path) => Buffer.byteLength(JSON.stringify(path)) <= 4096, "path \u8FC7\u957F").default([]),
|
|
141
|
+
offset: z2.number().int().nonnegative().default(0),
|
|
142
|
+
limit: z2.number().int().positive().max(16e3).optional()
|
|
143
|
+
}).strict();
|
|
144
|
+
var kind = (value) => value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
|
|
145
|
+
var length = (value) => typeof value === "string" || Array.isArray(value) ? value.length : value && typeof value === "object" ? Object.keys(value).length : 1;
|
|
146
|
+
function textPrefix(text, end) {
|
|
147
|
+
if (end < text.length && /[\uD800-\uDBFF]/.test(text[end - 1] ?? "")) end--;
|
|
148
|
+
return text.slice(0, end);
|
|
149
|
+
}
|
|
150
|
+
function overview(value) {
|
|
151
|
+
if (contentFits(value, 1024)) return value;
|
|
152
|
+
if (typeof value === "string")
|
|
153
|
+
return { kind: "string", length: value.length, preview: textPrefix(value, 300) };
|
|
154
|
+
if (Array.isArray(value)) return { kind: "array", length: value.length };
|
|
155
|
+
if (value && typeof value === "object")
|
|
156
|
+
return {
|
|
157
|
+
kind: "object",
|
|
158
|
+
fields: Object.entries(value).slice(0, 12).map(([key, item]) => ({
|
|
159
|
+
key: textPrefix(key, 80),
|
|
160
|
+
kind: kind(item),
|
|
161
|
+
length: length(item),
|
|
162
|
+
...typeof item === "string" ? { preview: textPrefix(item, 120) } : {}
|
|
163
|
+
})),
|
|
164
|
+
totalFields: Object.keys(value).length
|
|
165
|
+
};
|
|
166
|
+
return value;
|
|
167
|
+
}
|
|
168
|
+
var ContextContent = class {
|
|
169
|
+
constructor(directory) {
|
|
170
|
+
this.directory = directory;
|
|
171
|
+
}
|
|
172
|
+
directory;
|
|
173
|
+
async present(value, source) {
|
|
174
|
+
if (contentFits(value)) return value;
|
|
175
|
+
const body = json({ source, value }), id = createHash("sha256").update(body).digest("hex");
|
|
176
|
+
await mkdir(this.directory, { recursive: true, mode: 448 });
|
|
177
|
+
const temporary = join(this.directory, randomUUID() + ".tmp");
|
|
178
|
+
try {
|
|
179
|
+
await writeFile(temporary, body, { flag: "wx", mode: 384 });
|
|
180
|
+
try {
|
|
181
|
+
await link(temporary, join(this.directory, id + ".json"));
|
|
182
|
+
} catch (error) {
|
|
183
|
+
if (error.code !== "EEXIST") throw error;
|
|
184
|
+
}
|
|
185
|
+
} finally {
|
|
186
|
+
await rm(temporary, { force: true });
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
folded: true,
|
|
190
|
+
note: "\u5185\u5BB9\u8FC7\u957F\uFF0C\u5DF2\u6298\u53E0\uFF1B\u4E0B\u65B9\u4EC5\u4E3A\u6982\u89C8\uFF0C\u4E0D\u80FD\u89C6\u4E3A\u5DF2\u8BFB\u5168\u6587\u3002\u7528 read_tool_result \u6309 path \u548C offset \u8BFB\u53D6\u539F\u59CB\u7ED3\u679C\uFF0C\u4E0D\u5FC5\u91CD\u590D\u6267\u884C\u539F\u5DE5\u5177\u3002",
|
|
191
|
+
source,
|
|
192
|
+
id,
|
|
193
|
+
bytes: Buffer.byteLength(json(value)),
|
|
194
|
+
overview: overview(value),
|
|
195
|
+
read: { tool: "read_tool_result", arguments: { id } }
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
async input(text) {
|
|
199
|
+
const value = await this.present(text, "\u672C\u8F6E\u8F93\u5165");
|
|
200
|
+
return typeof value === "string" ? value : "\u672C\u8F6E\u8F93\u5165\u8F83\u957F\uFF0C\u539F\u6587\u5DF2\u5B8C\u6574\u4FDD\u5B58\u3002\u5148\u5206\u6BB5\u8BFB\u53D6\uFF0C\u786E\u8BA4\u7528\u6237\u7684\u76EE\u6807\u3001\u6761\u4EF6\u548C\u9650\u5236\u540E\u518D\u884C\u52A8\uFF1B\u6982\u89C8\u4E0D\u662F\u5168\u90E8\u8F93\u5165\u3002\n" + json(value);
|
|
201
|
+
}
|
|
202
|
+
async read(input) {
|
|
203
|
+
const { id, path, offset, limit } = ReadToolResultSchema.parse(input);
|
|
204
|
+
let body;
|
|
205
|
+
try {
|
|
206
|
+
body = await readFile(join(this.directory, id + ".json"), "utf8");
|
|
207
|
+
} catch {
|
|
208
|
+
throw new HubError(
|
|
209
|
+
404,
|
|
210
|
+
"TOOL_RESULT_NOT_FOUND",
|
|
211
|
+
"\u6B64\u7ED3\u679C\u4E0D\u5728\u5F53\u524D\u4F1A\u8BDD\u6216\u5DF2\u4E0D\u53EF\u7528\uFF0C\u8BF7\u91CD\u65B0\u67E5\u8BE2\u539F\u59CB\u8D44\u6599\u3002"
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
if (createHash("sha256").update(body).digest("hex") !== id)
|
|
215
|
+
throw new HubError(409, "TOOL_RESULT_CHANGED", "\u7ED3\u679C\u5FEB\u7167\u5DF2\u6539\u53D8\uFF0C\u8BF7\u91CD\u65B0\u67E5\u8BE2\u539F\u59CB\u8D44\u6599\u3002");
|
|
216
|
+
const saved = JSON.parse(body);
|
|
217
|
+
let value = saved.value;
|
|
218
|
+
for (const key of path) {
|
|
219
|
+
if (!value || typeof value !== "object" || !Object.hasOwn(value, key))
|
|
220
|
+
throw new HubError(400, "TOOL_RESULT_PATH", "\u7ED3\u679C\u4E2D\u4E0D\u5B58\u5728\u8BE5 path\u3002");
|
|
221
|
+
value = value[key];
|
|
222
|
+
}
|
|
223
|
+
const total = length(value);
|
|
224
|
+
if (offset > total) throw new HubError(400, "TOOL_RESULT_OFFSET", "offset \u8D85\u51FA\u7ED3\u679C\u8303\u56F4\u3002");
|
|
225
|
+
const base = { id, source: saved.source, path, kind: kind(value), total, offset };
|
|
226
|
+
if (typeof value === "string") {
|
|
227
|
+
if (/[\uDC00-\uDFFF]/.test(value[offset] ?? "") && /[\uD800-\uDBFF]/.test(value[offset - 1] ?? ""))
|
|
228
|
+
throw new HubError(400, "TOOL_RESULT_OFFSET", "\u8BF7\u6CBF\u7528\u4E0A\u6B21\u8FD4\u56DE\u7684 nextOffset\u3002");
|
|
229
|
+
const tail = value.slice(offset);
|
|
230
|
+
let lo = 0, hi = Math.min(tail.length, limit ?? 16e3);
|
|
231
|
+
while (lo < hi) {
|
|
232
|
+
const mid = Math.ceil((lo + hi) / 2);
|
|
233
|
+
if (contentFits({ ...base, text: textPrefix(tail, mid), nextOffset: offset + mid }))
|
|
234
|
+
lo = mid;
|
|
235
|
+
else hi = mid - 1;
|
|
236
|
+
}
|
|
237
|
+
let text = textPrefix(tail, lo);
|
|
238
|
+
if (!text && tail) text = textPrefix(tail, 2);
|
|
239
|
+
return {
|
|
240
|
+
...base,
|
|
241
|
+
text,
|
|
242
|
+
nextOffset: offset + text.length < total ? offset + text.length : null
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
if (!value || typeof value !== "object") return { ...base, value, nextOffset: null };
|
|
246
|
+
const entries = [];
|
|
247
|
+
const keys = Array.isArray(value) ? value.map((_, i) => i) : Object.keys(value);
|
|
248
|
+
for (const key of keys.slice(offset, offset + Math.min(limit ?? 50, 100))) {
|
|
249
|
+
const item = value[key];
|
|
250
|
+
const folded = !contentFits(item, 2048);
|
|
251
|
+
const entry = { path: [...path, key], folded, value: folded ? overview(item) : item };
|
|
252
|
+
if (!contentFits({
|
|
253
|
+
...base,
|
|
254
|
+
entries: [...entries, entry],
|
|
255
|
+
nextOffset: offset + entries.length + 1
|
|
256
|
+
}))
|
|
257
|
+
break;
|
|
258
|
+
entries.push(entry);
|
|
259
|
+
}
|
|
260
|
+
if (!entries.length && offset < total)
|
|
261
|
+
throw new HubError(413, "TOOL_RESULT_KEY_TOO_LONG", "\u7ED3\u679C\u5B57\u6BB5\u540D\u8FC7\u957F\uFF0C\u65E0\u6CD5\u751F\u6210\u5B89\u5168\u7684\u5206\u9875\u3002");
|
|
262
|
+
return {
|
|
263
|
+
...base,
|
|
264
|
+
entries,
|
|
265
|
+
nextOffset: offset + entries.length < total ? offset + entries.length : null
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
// apps/server/ai/content-tools.ts
|
|
271
|
+
import { Type as Type2 } from "@earendil-works/pi-ai";
|
|
272
|
+
import { z as z3 } from "zod";
|
|
273
|
+
var readToolResultDescription = "\u8BFB\u53D6\u5F53\u524D\u8303\u56F4\u5185\u5DF2\u6298\u53E0\u7684\u539F\u59CB\u7ED3\u679C\uFF0C\u4E0D\u91CD\u8DD1\u539F\u5DE5\u5177\u3002\u5148\u7528 id \u67E5\u770B\u5B57\u6BB5/\u6761\u76EE\uFF1B\u6CBF\u8FD4\u56DE\u7684 path \u8BFB\u53D6\u5177\u4F53\u5B57\u6BB5\u6216\u6587\u4EF6\u5DEE\u5F02\uFF0CnextOffset \u975E\u7A7A\u65F6\u7EE7\u7EED\u3002folded=true \u7684\u6761\u76EE\u53EA\u663E\u793A\u6982\u89C8\uFF0C\u4E0D\u80FD\u5F53\u4F5C\u5B8C\u6574\u6B63\u6587\uFF1B\u5B57\u7B26\u4E32 offset \u4E3A\u8FD4\u56DE\u7684 UTF-16 \u4F4D\u7F6E\u3002\u5FEB\u7167\u56FA\u5B9A\u5728\u539F\u67E5\u8BE2\u7248\u672C\u3002";
|
|
274
|
+
function contentTools(content, tools) {
|
|
275
|
+
return [
|
|
276
|
+
...tools.map((tool) => ({
|
|
277
|
+
...tool,
|
|
278
|
+
async execute(...args) {
|
|
279
|
+
const result = await tool.execute(...args);
|
|
280
|
+
const blocks = [];
|
|
281
|
+
for (const block of result.content) {
|
|
282
|
+
if (block.type !== "text") {
|
|
283
|
+
blocks.push(block);
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
let value;
|
|
287
|
+
try {
|
|
288
|
+
value = JSON.parse(block.text);
|
|
289
|
+
} catch {
|
|
290
|
+
value = block.text;
|
|
291
|
+
}
|
|
292
|
+
const bounded = await content.present(value, tool.name);
|
|
293
|
+
blocks.push({
|
|
294
|
+
type: "text",
|
|
295
|
+
text: bounded === value && contentFits(block.text) ? block.text : JSON.stringify(bounded)
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
return { content: blocks, details: {} };
|
|
299
|
+
}
|
|
300
|
+
})),
|
|
301
|
+
{
|
|
302
|
+
name: "read_tool_result",
|
|
303
|
+
label: "\u8BFB\u53D6\u6298\u53E0\u5185\u5BB9",
|
|
304
|
+
description: readToolResultDescription,
|
|
305
|
+
parameters: Type2.Unsafe(z3.toJSONSchema(ReadToolResultSchema, { io: "input" })),
|
|
306
|
+
async execute(_id, input) {
|
|
307
|
+
return {
|
|
308
|
+
content: [{ type: "text", text: JSON.stringify(await content.read(input)) }],
|
|
309
|
+
details: {}
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
];
|
|
314
|
+
}
|
|
315
|
+
|
|
112
316
|
// apps/server/ai/native-pi-entry.ts
|
|
113
317
|
var channel = new AgentChannel(process.stdin, process.stdout);
|
|
114
318
|
var sequence = 0;
|
|
@@ -123,6 +327,7 @@ function rpc(request) {
|
|
|
123
327
|
async function run(config) {
|
|
124
328
|
const { model, runtime } = config;
|
|
125
329
|
const cwd = "/workspaces", agentDir = "/tmp/pi";
|
|
330
|
+
const content = new ContextContent("/tmp/teammy-content");
|
|
126
331
|
const modelRuntime = await ModelRuntime.create({
|
|
127
332
|
credentials: new InMemoryCredentialStore(),
|
|
128
333
|
modelsStore: new InMemoryModelsStore(),
|
|
@@ -163,7 +368,7 @@ async function run(config) {
|
|
|
163
368
|
const query = {
|
|
164
369
|
name: "query_tasks",
|
|
165
370
|
...runtime.toolText.query_tasks,
|
|
166
|
-
parameters:
|
|
371
|
+
parameters: Type3.Unsafe(z4.toJSONSchema(TaskQuerySchema)),
|
|
167
372
|
async execute(_id, params) {
|
|
168
373
|
const value = await rpc({ method: "query_tasks", input: TaskQuerySchema.parse(params) });
|
|
169
374
|
return { content: [{ type: "text", text: JSON.stringify(value) }], details: {} };
|
|
@@ -182,8 +387,8 @@ async function run(config) {
|
|
|
182
387
|
retry: { enabled: false, maxRetries: 0 },
|
|
183
388
|
cacheWarming: "off"
|
|
184
389
|
}),
|
|
185
|
-
tools: runtime.config.tools,
|
|
186
|
-
customTools: runtime.config.tools.includes("query_tasks") ? [query] : []
|
|
390
|
+
tools: [...runtime.config.tools, "read_tool_result"],
|
|
391
|
+
customTools: contentTools(content, runtime.config.tools.includes("query_tasks") ? [query] : [])
|
|
187
392
|
});
|
|
188
393
|
session.agent.streamFunction = (_model, context) => {
|
|
189
394
|
const stream = createAssistantMessageEventStream();
|
|
@@ -227,7 +432,7 @@ async function run(config) {
|
|
|
227
432
|
});
|
|
228
433
|
});
|
|
229
434
|
try {
|
|
230
|
-
await session.prompt(config.input);
|
|
435
|
+
await session.prompt(await content.input(config.input));
|
|
231
436
|
const last = session.messages.filter((message) => message.role === "assistant").at(-1);
|
|
232
437
|
if (!last || last.stopReason === "error" || last.stopReason === "aborted")
|
|
233
438
|
throw new Error("Analysis failed.");
|
package/dist/project-pi-entry.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// apps/server/agents/pi-entry.ts
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { existsSync as existsSync2 } from "node:fs";
|
|
4
|
-
import { mkdir, readFile } from "node:fs/promises";
|
|
5
|
-
import { join, basename } from "node:path";
|
|
6
|
-
import { InMemoryCredentialStore, InMemoryModelsStore, Type as
|
|
4
|
+
import { mkdir as mkdir2, readFile as readFile2 } from "node:fs/promises";
|
|
5
|
+
import { join as join2, basename } from "node:path";
|
|
6
|
+
import { InMemoryCredentialStore, InMemoryModelsStore, Type as Type3 } from "@earendil-works/pi-ai";
|
|
7
7
|
import { createAssistantMessageEventStream } from "@earendil-works/pi-ai/utils/event-stream";
|
|
8
8
|
import {
|
|
9
9
|
createAgentSession,
|
|
@@ -54,6 +54,20 @@ import { createModels, createProvider, Type } from "@earendil-works/pi-ai";
|
|
|
54
54
|
import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
|
|
55
55
|
import { openAIResponsesApi } from "@earendil-works/pi-ai/api/openai-responses.lazy";
|
|
56
56
|
import { anthropicMessagesApi } from "@earendil-works/pi-ai/api/anthropic-messages.lazy";
|
|
57
|
+
|
|
58
|
+
// apps/server/errors.ts
|
|
59
|
+
var HubError = class extends Error {
|
|
60
|
+
constructor(statusCode, code, message) {
|
|
61
|
+
super(message);
|
|
62
|
+
this.statusCode = statusCode;
|
|
63
|
+
this.code = code;
|
|
64
|
+
this.name = "HubError";
|
|
65
|
+
}
|
|
66
|
+
statusCode;
|
|
67
|
+
code;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// apps/server/ai/models.ts
|
|
57
71
|
function modelAccess(profile, key, reasoning = false) {
|
|
58
72
|
const model = {
|
|
59
73
|
id: profile.model,
|
|
@@ -103,6 +117,196 @@ function loadAgentSkills(directory = `${agentResourcesPath}/skills`) {
|
|
|
103
117
|
return existsSync(directory) ? loadSkillsFromDir({ dir: directory, source: "teammy-deployment" }) : { skills: [], diagnostics: [] };
|
|
104
118
|
}
|
|
105
119
|
|
|
120
|
+
// apps/server/ai/context-content.ts
|
|
121
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
122
|
+
import { link, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
123
|
+
import { join } from "node:path";
|
|
124
|
+
import { z } from "zod";
|
|
125
|
+
var CONTENT_BYTES = 32 * 1024;
|
|
126
|
+
var CONTENT_LINES = 500;
|
|
127
|
+
var json = (value) => JSON.stringify(value) ?? "null";
|
|
128
|
+
function contentFits(value, bytes = CONTENT_BYTES) {
|
|
129
|
+
const text = json(value);
|
|
130
|
+
return Buffer.byteLength(text) <= bytes && (text.match(/\\n|\n/g)?.length ?? 0) < CONTENT_LINES;
|
|
131
|
+
}
|
|
132
|
+
var ReadToolResultSchema = z.object({
|
|
133
|
+
id: z.string().regex(/^[a-f0-9]{64}$/),
|
|
134
|
+
path: z.array(z.union([z.string().max(512), z.number().int().nonnegative()])).max(32).refine((path) => Buffer.byteLength(JSON.stringify(path)) <= 4096, "path \u8FC7\u957F").default([]),
|
|
135
|
+
offset: z.number().int().nonnegative().default(0),
|
|
136
|
+
limit: z.number().int().positive().max(16e3).optional()
|
|
137
|
+
}).strict();
|
|
138
|
+
var kind = (value) => value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
|
|
139
|
+
var length = (value) => typeof value === "string" || Array.isArray(value) ? value.length : value && typeof value === "object" ? Object.keys(value).length : 1;
|
|
140
|
+
function textPrefix(text, end) {
|
|
141
|
+
if (end < text.length && /[\uD800-\uDBFF]/.test(text[end - 1] ?? "")) end--;
|
|
142
|
+
return text.slice(0, end);
|
|
143
|
+
}
|
|
144
|
+
function overview(value) {
|
|
145
|
+
if (contentFits(value, 1024)) return value;
|
|
146
|
+
if (typeof value === "string")
|
|
147
|
+
return { kind: "string", length: value.length, preview: textPrefix(value, 300) };
|
|
148
|
+
if (Array.isArray(value)) return { kind: "array", length: value.length };
|
|
149
|
+
if (value && typeof value === "object")
|
|
150
|
+
return {
|
|
151
|
+
kind: "object",
|
|
152
|
+
fields: Object.entries(value).slice(0, 12).map(([key, item]) => ({
|
|
153
|
+
key: textPrefix(key, 80),
|
|
154
|
+
kind: kind(item),
|
|
155
|
+
length: length(item),
|
|
156
|
+
...typeof item === "string" ? { preview: textPrefix(item, 120) } : {}
|
|
157
|
+
})),
|
|
158
|
+
totalFields: Object.keys(value).length
|
|
159
|
+
};
|
|
160
|
+
return value;
|
|
161
|
+
}
|
|
162
|
+
var ContextContent = class {
|
|
163
|
+
constructor(directory) {
|
|
164
|
+
this.directory = directory;
|
|
165
|
+
}
|
|
166
|
+
directory;
|
|
167
|
+
async present(value, source) {
|
|
168
|
+
if (contentFits(value)) return value;
|
|
169
|
+
const body = json({ source, value }), id = createHash("sha256").update(body).digest("hex");
|
|
170
|
+
await mkdir(this.directory, { recursive: true, mode: 448 });
|
|
171
|
+
const temporary = join(this.directory, randomUUID() + ".tmp");
|
|
172
|
+
try {
|
|
173
|
+
await writeFile(temporary, body, { flag: "wx", mode: 384 });
|
|
174
|
+
try {
|
|
175
|
+
await link(temporary, join(this.directory, id + ".json"));
|
|
176
|
+
} catch (error) {
|
|
177
|
+
if (error.code !== "EEXIST") throw error;
|
|
178
|
+
}
|
|
179
|
+
} finally {
|
|
180
|
+
await rm(temporary, { force: true });
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
folded: true,
|
|
184
|
+
note: "\u5185\u5BB9\u8FC7\u957F\uFF0C\u5DF2\u6298\u53E0\uFF1B\u4E0B\u65B9\u4EC5\u4E3A\u6982\u89C8\uFF0C\u4E0D\u80FD\u89C6\u4E3A\u5DF2\u8BFB\u5168\u6587\u3002\u7528 read_tool_result \u6309 path \u548C offset \u8BFB\u53D6\u539F\u59CB\u7ED3\u679C\uFF0C\u4E0D\u5FC5\u91CD\u590D\u6267\u884C\u539F\u5DE5\u5177\u3002",
|
|
185
|
+
source,
|
|
186
|
+
id,
|
|
187
|
+
bytes: Buffer.byteLength(json(value)),
|
|
188
|
+
overview: overview(value),
|
|
189
|
+
read: { tool: "read_tool_result", arguments: { id } }
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
async input(text) {
|
|
193
|
+
const value = await this.present(text, "\u672C\u8F6E\u8F93\u5165");
|
|
194
|
+
return typeof value === "string" ? value : "\u672C\u8F6E\u8F93\u5165\u8F83\u957F\uFF0C\u539F\u6587\u5DF2\u5B8C\u6574\u4FDD\u5B58\u3002\u5148\u5206\u6BB5\u8BFB\u53D6\uFF0C\u786E\u8BA4\u7528\u6237\u7684\u76EE\u6807\u3001\u6761\u4EF6\u548C\u9650\u5236\u540E\u518D\u884C\u52A8\uFF1B\u6982\u89C8\u4E0D\u662F\u5168\u90E8\u8F93\u5165\u3002\n" + json(value);
|
|
195
|
+
}
|
|
196
|
+
async read(input) {
|
|
197
|
+
const { id, path, offset, limit } = ReadToolResultSchema.parse(input);
|
|
198
|
+
let body;
|
|
199
|
+
try {
|
|
200
|
+
body = await readFile(join(this.directory, id + ".json"), "utf8");
|
|
201
|
+
} catch {
|
|
202
|
+
throw new HubError(
|
|
203
|
+
404,
|
|
204
|
+
"TOOL_RESULT_NOT_FOUND",
|
|
205
|
+
"\u6B64\u7ED3\u679C\u4E0D\u5728\u5F53\u524D\u4F1A\u8BDD\u6216\u5DF2\u4E0D\u53EF\u7528\uFF0C\u8BF7\u91CD\u65B0\u67E5\u8BE2\u539F\u59CB\u8D44\u6599\u3002"
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
if (createHash("sha256").update(body).digest("hex") !== id)
|
|
209
|
+
throw new HubError(409, "TOOL_RESULT_CHANGED", "\u7ED3\u679C\u5FEB\u7167\u5DF2\u6539\u53D8\uFF0C\u8BF7\u91CD\u65B0\u67E5\u8BE2\u539F\u59CB\u8D44\u6599\u3002");
|
|
210
|
+
const saved = JSON.parse(body);
|
|
211
|
+
let value = saved.value;
|
|
212
|
+
for (const key of path) {
|
|
213
|
+
if (!value || typeof value !== "object" || !Object.hasOwn(value, key))
|
|
214
|
+
throw new HubError(400, "TOOL_RESULT_PATH", "\u7ED3\u679C\u4E2D\u4E0D\u5B58\u5728\u8BE5 path\u3002");
|
|
215
|
+
value = value[key];
|
|
216
|
+
}
|
|
217
|
+
const total = length(value);
|
|
218
|
+
if (offset > total) throw new HubError(400, "TOOL_RESULT_OFFSET", "offset \u8D85\u51FA\u7ED3\u679C\u8303\u56F4\u3002");
|
|
219
|
+
const base = { id, source: saved.source, path, kind: kind(value), total, offset };
|
|
220
|
+
if (typeof value === "string") {
|
|
221
|
+
if (/[\uDC00-\uDFFF]/.test(value[offset] ?? "") && /[\uD800-\uDBFF]/.test(value[offset - 1] ?? ""))
|
|
222
|
+
throw new HubError(400, "TOOL_RESULT_OFFSET", "\u8BF7\u6CBF\u7528\u4E0A\u6B21\u8FD4\u56DE\u7684 nextOffset\u3002");
|
|
223
|
+
const tail = value.slice(offset);
|
|
224
|
+
let lo = 0, hi = Math.min(tail.length, limit ?? 16e3);
|
|
225
|
+
while (lo < hi) {
|
|
226
|
+
const mid = Math.ceil((lo + hi) / 2);
|
|
227
|
+
if (contentFits({ ...base, text: textPrefix(tail, mid), nextOffset: offset + mid }))
|
|
228
|
+
lo = mid;
|
|
229
|
+
else hi = mid - 1;
|
|
230
|
+
}
|
|
231
|
+
let text = textPrefix(tail, lo);
|
|
232
|
+
if (!text && tail) text = textPrefix(tail, 2);
|
|
233
|
+
return {
|
|
234
|
+
...base,
|
|
235
|
+
text,
|
|
236
|
+
nextOffset: offset + text.length < total ? offset + text.length : null
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
if (!value || typeof value !== "object") return { ...base, value, nextOffset: null };
|
|
240
|
+
const entries = [];
|
|
241
|
+
const keys = Array.isArray(value) ? value.map((_, i) => i) : Object.keys(value);
|
|
242
|
+
for (const key of keys.slice(offset, offset + Math.min(limit ?? 50, 100))) {
|
|
243
|
+
const item = value[key];
|
|
244
|
+
const folded = !contentFits(item, 2048);
|
|
245
|
+
const entry = { path: [...path, key], folded, value: folded ? overview(item) : item };
|
|
246
|
+
if (!contentFits({
|
|
247
|
+
...base,
|
|
248
|
+
entries: [...entries, entry],
|
|
249
|
+
nextOffset: offset + entries.length + 1
|
|
250
|
+
}))
|
|
251
|
+
break;
|
|
252
|
+
entries.push(entry);
|
|
253
|
+
}
|
|
254
|
+
if (!entries.length && offset < total)
|
|
255
|
+
throw new HubError(413, "TOOL_RESULT_KEY_TOO_LONG", "\u7ED3\u679C\u5B57\u6BB5\u540D\u8FC7\u957F\uFF0C\u65E0\u6CD5\u751F\u6210\u5B89\u5168\u7684\u5206\u9875\u3002");
|
|
256
|
+
return {
|
|
257
|
+
...base,
|
|
258
|
+
entries,
|
|
259
|
+
nextOffset: offset + entries.length < total ? offset + entries.length : null
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
// apps/server/ai/content-tools.ts
|
|
265
|
+
import { Type as Type2 } from "@earendil-works/pi-ai";
|
|
266
|
+
import { z as z2 } from "zod";
|
|
267
|
+
var readToolResultDescription = "\u8BFB\u53D6\u5F53\u524D\u8303\u56F4\u5185\u5DF2\u6298\u53E0\u7684\u539F\u59CB\u7ED3\u679C\uFF0C\u4E0D\u91CD\u8DD1\u539F\u5DE5\u5177\u3002\u5148\u7528 id \u67E5\u770B\u5B57\u6BB5/\u6761\u76EE\uFF1B\u6CBF\u8FD4\u56DE\u7684 path \u8BFB\u53D6\u5177\u4F53\u5B57\u6BB5\u6216\u6587\u4EF6\u5DEE\u5F02\uFF0CnextOffset \u975E\u7A7A\u65F6\u7EE7\u7EED\u3002folded=true \u7684\u6761\u76EE\u53EA\u663E\u793A\u6982\u89C8\uFF0C\u4E0D\u80FD\u5F53\u4F5C\u5B8C\u6574\u6B63\u6587\uFF1B\u5B57\u7B26\u4E32 offset \u4E3A\u8FD4\u56DE\u7684 UTF-16 \u4F4D\u7F6E\u3002\u5FEB\u7167\u56FA\u5B9A\u5728\u539F\u67E5\u8BE2\u7248\u672C\u3002";
|
|
268
|
+
function contentTools(content, tools) {
|
|
269
|
+
return [
|
|
270
|
+
...tools.map((tool) => ({
|
|
271
|
+
...tool,
|
|
272
|
+
async execute(...args) {
|
|
273
|
+
const result = await tool.execute(...args);
|
|
274
|
+
const blocks = [];
|
|
275
|
+
for (const block of result.content) {
|
|
276
|
+
if (block.type !== "text") {
|
|
277
|
+
blocks.push(block);
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
let value;
|
|
281
|
+
try {
|
|
282
|
+
value = JSON.parse(block.text);
|
|
283
|
+
} catch {
|
|
284
|
+
value = block.text;
|
|
285
|
+
}
|
|
286
|
+
const bounded = await content.present(value, tool.name);
|
|
287
|
+
blocks.push({
|
|
288
|
+
type: "text",
|
|
289
|
+
text: bounded === value && contentFits(block.text) ? block.text : JSON.stringify(bounded)
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
return { content: blocks, details: {} };
|
|
293
|
+
}
|
|
294
|
+
})),
|
|
295
|
+
{
|
|
296
|
+
name: "read_tool_result",
|
|
297
|
+
label: "\u8BFB\u53D6\u6298\u53E0\u5185\u5BB9",
|
|
298
|
+
description: readToolResultDescription,
|
|
299
|
+
parameters: Type2.Unsafe(z2.toJSONSchema(ReadToolResultSchema, { io: "input" })),
|
|
300
|
+
async execute(_id, input) {
|
|
301
|
+
return {
|
|
302
|
+
content: [{ type: "text", text: JSON.stringify(await content.read(input)) }],
|
|
303
|
+
details: {}
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
];
|
|
308
|
+
}
|
|
309
|
+
|
|
106
310
|
// apps/server/agents/pi-entry.ts
|
|
107
311
|
async function piChild() {
|
|
108
312
|
const channel = new AgentChannel(process.stdin, process.stdout);
|
|
@@ -128,7 +332,7 @@ async function piChild() {
|
|
|
128
332
|
]);
|
|
129
333
|
if (session && key === configKey) return;
|
|
130
334
|
session?.dispose();
|
|
131
|
-
await
|
|
335
|
+
await mkdir2(input.sessionDir, { recursive: true });
|
|
132
336
|
manager = input.transient ? SessionManager.inMemory(input.cwd ?? "/project") : input.sessionFile && existsSync2(input.sessionFile) ? SessionManager.open(input.sessionFile, input.sessionDir, input.cwd ?? "/project") : SessionManager.create(input.cwd ?? "/project", input.sessionDir);
|
|
133
337
|
const modelRuntime = await ModelRuntime.create({
|
|
134
338
|
credentials: new InMemoryCredentialStore(),
|
|
@@ -170,23 +374,24 @@ async function piChild() {
|
|
|
170
374
|
reload: async () => {
|
|
171
375
|
}
|
|
172
376
|
};
|
|
377
|
+
const content = new ContextContent(join2(input.sessionDir, "content"));
|
|
173
378
|
const customTools = input.tools.map((tool) => ({
|
|
174
379
|
name: tool.name,
|
|
175
380
|
label: tool.label,
|
|
176
381
|
description: tool.description,
|
|
177
|
-
parameters:
|
|
382
|
+
parameters: Type3.Unsafe(tool.schema),
|
|
178
383
|
execute: async (_id, params) => {
|
|
179
384
|
let input2 = params;
|
|
180
385
|
if (tool.name === "save_material") {
|
|
181
386
|
const source = params;
|
|
182
387
|
input2 = {
|
|
183
388
|
...source,
|
|
184
|
-
...source.contentFile ? { _content: await
|
|
389
|
+
...source.contentFile ? { _content: await readFile2(source.contentFile, "utf8") } : {},
|
|
185
390
|
...source.originals ? {
|
|
186
391
|
_files: await Promise.all(
|
|
187
392
|
source.originals.map(async (file) => ({
|
|
188
393
|
name: file.name ?? basename(file.path),
|
|
189
|
-
base64: (await
|
|
394
|
+
base64: (await readFile2(file.path)).toString("base64")
|
|
190
395
|
}))
|
|
191
396
|
)
|
|
192
397
|
} : {}
|
|
@@ -198,7 +403,7 @@ async function piChild() {
|
|
|
198
403
|
}));
|
|
199
404
|
const created = await createAgentSession({
|
|
200
405
|
cwd: input.cwd ?? "/project",
|
|
201
|
-
agentDir:
|
|
406
|
+
agentDir: join2(input.sessionDir, "agent"),
|
|
202
407
|
modelRuntime,
|
|
203
408
|
model: input.model,
|
|
204
409
|
thinkingLevel: "off",
|
|
@@ -216,9 +421,10 @@ async function piChild() {
|
|
|
216
421
|
"find",
|
|
217
422
|
"ls",
|
|
218
423
|
...input.cwd === "/work" ? ["write", "edit"] : [],
|
|
219
|
-
...input.tools.map((tool) => tool.name)
|
|
424
|
+
...input.tools.map((tool) => tool.name),
|
|
425
|
+
"read_tool_result"
|
|
220
426
|
],
|
|
221
|
-
customTools
|
|
427
|
+
customTools: contentTools(content, customTools)
|
|
222
428
|
});
|
|
223
429
|
session = created.session;
|
|
224
430
|
configKey = key;
|
|
@@ -298,8 +504,10 @@ async function piChild() {
|
|
|
298
504
|
});
|
|
299
505
|
try {
|
|
300
506
|
await native.prompt(
|
|
301
|
-
|
|
507
|
+
await new ContextContent(join2(input.sessionDir, "content")).input(
|
|
508
|
+
interrupted ? `\u6B64\u524D\u8FD9\u4E00\u8F6E\u6267\u884C\u4E2D\u65AD\u6216\u672A\u6210\u529F\uFF0C\u8BF7\u7ED3\u5408\u5DF2\u4FDD\u5B58\u7684\u4E0A\u4E0B\u6587\u7EE7\u7EED\u3002\u5DF2\u63D0\u4EA4\u64CD\u4F5C\u7684\u56DE\u6267\u4ECD\u6709\u6548\uFF0C\u4E0D\u8981\u91CD\u590D\u53D1\u9001\uFF1B\u6682\u5B58\u56DE\u7B54\u548C\u5B8C\u6210\u62A5\u544A\u5C1A\u672A\u63D0\u4EA4\uFF0C\u8BF7\u91CD\u65B0\u6838\u5BF9\u540E\u7ED9\u51FA\u672C\u8F6E\u7ED3\u8BBA\u3002\u539F\u8F93\u5165\uFF1A
|
|
302
509
|
${input.text}` : input.text
|
|
510
|
+
)
|
|
303
511
|
);
|
|
304
512
|
await native.waitForIdle();
|
|
305
513
|
const last = native.messages.filter((message) => message.role === "assistant").at(-1);
|
|
Binary file
|