tarsk 0.4.13 → 0.4.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/index.js +643 -60
- package/dist/public/assets/{account-view-CXpwWuJZ.js → account-view-bgfCh9fD.js} +1 -1
- package/dist/public/assets/api-tXifOYo2.js +1 -0
- package/dist/public/assets/browser-tab-BqBW_AK-.js +1 -0
- package/dist/public/assets/context-menu-CmaVJQOJ.js +1 -0
- package/dist/public/assets/conversation-history-view-BU-YAZS_.js +1 -0
- package/dist/public/assets/dialogs-config-bLEt-2lj.js +46 -0
- package/dist/public/assets/diff-view-DjfyQW_j.js +3 -0
- package/dist/public/assets/{explorer-tab-view-DC8arYcw.js → explorer-tab-view-DRpNcRXM.js} +2 -2
- package/dist/public/assets/explorer-tree-z3yBekT5.js +1 -0
- package/dist/public/assets/explorer-view-CjevBGrc.js +1 -0
- package/dist/public/assets/history-view-ETKpXvpZ.js +1 -0
- package/dist/public/assets/index-CQrq9F6C.js +65 -0
- package/dist/public/assets/index-Dwos0qd5.css +1 -0
- package/dist/public/assets/onboarding-Dvaoo4rL.js +1 -0
- package/dist/public/assets/onboarding-dialog-D7LOFzso.js +1 -0
- package/dist/public/assets/{project-settings-view-CZPKYKCu.js → project-settings-view-CBW-nuLi.js} +1 -1
- package/dist/public/assets/provider-details-view-DJXhGdNI.js +1 -0
- package/dist/public/assets/{providers-sidebar-DSrnVDNy.js → providers-sidebar-Cn7XvmQ-.js} +1 -1
- package/dist/public/assets/react-vendor-CUw-HIQD.js +17 -0
- package/dist/public/assets/{settings-view-D6FAPBQP.js → settings-view-TH1bX0FR.js} +2 -2
- package/dist/public/assets/{store-BuY90jLM.js → store-CDLJd-q_.js} +1 -1
- package/dist/public/assets/{terminal-panel-Zyr6xth3.js → terminal-panel-BRdTXLkg.js} +1 -1
- package/dist/public/assets/{textarea-CxhrnOOz.js → textarea-DtvDY2H_.js} +1 -1
- package/dist/public/assets/todos-view-BHuGeR5Y.js +1 -0
- package/dist/public/assets/{utils-7CkwsQJ0.js → utils-BxHvPw5k.js} +1 -1
- package/dist/public/index.html +9 -9
- package/package.json +2 -2
- package/dist/public/assets/api-BfUK32d1.js +0 -1
- package/dist/public/assets/browser-tab-BdzIB5R4.js +0 -1
- package/dist/public/assets/context-menu-CC9JLx5B.js +0 -1
- package/dist/public/assets/conversation-history-view-C6iUsZjA.js +0 -1
- package/dist/public/assets/dialogs-config-Ofxg1Bn0.js +0 -46
- package/dist/public/assets/diff-view-C-Kz7R_T.js +0 -3
- package/dist/public/assets/explorer-tree-BwLCrdfR.js +0 -1
- package/dist/public/assets/explorer-view-CPCrOqXp.js +0 -1
- package/dist/public/assets/history-view-CAVl9GFy.js +0 -1
- package/dist/public/assets/index-BVLzVjoR.css +0 -1
- package/dist/public/assets/index-DIWoaAFy.js +0 -30
- package/dist/public/assets/onboarding-DTzcWokE.js +0 -1
- package/dist/public/assets/onboarding-dialog-BQvu_Nac.js +0 -1
- package/dist/public/assets/provider-details-view-BBvKe6Am.js +0 -1
- package/dist/public/assets/react-vendor-9yNfIRPs.js +0 -17
- package/dist/public/assets/todos-view-yiH-gaUK.js +0 -1
package/dist/index.js
CHANGED
|
@@ -663,7 +663,7 @@ function isValidPackageManager(value) {
|
|
|
663
663
|
|
|
664
664
|
// src/server.ts
|
|
665
665
|
import fs3 from "fs";
|
|
666
|
-
import { Hono as
|
|
666
|
+
import { Hono as Hono21 } from "hono";
|
|
667
667
|
import { cors } from "hono/cors";
|
|
668
668
|
import open3 from "open";
|
|
669
669
|
import path5 from "path";
|
|
@@ -671,6 +671,7 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
671
671
|
|
|
672
672
|
// src/agent/agent.executor.ts
|
|
673
673
|
import { Agent as Agent2 } from "@mariozechner/pi-agent-core";
|
|
674
|
+
import { streamSimple } from "@mariozechner/pi-ai";
|
|
674
675
|
import { resolve as resolve2, isAbsolute as isAbsolute2 } from "path";
|
|
675
676
|
|
|
676
677
|
// src/tools/bash.ts
|
|
@@ -3285,6 +3286,8 @@ async function createMCPTools(projectPath) {
|
|
|
3285
3286
|
|
|
3286
3287
|
// src/tools/generate-image.ts
|
|
3287
3288
|
import { Type as Type13 } from "@sinclair/typebox";
|
|
3289
|
+
import { mkdir as fsMkdir2, writeFile as fsWriteFile3 } from "fs/promises";
|
|
3290
|
+
import { dirname as dirname2 } from "path";
|
|
3288
3291
|
|
|
3289
3292
|
// src/features/models/models-catalog.ts
|
|
3290
3293
|
init_database();
|
|
@@ -3610,6 +3613,29 @@ async function generateWithOpenRouter(apiKey, model, prompt, options = {}) {
|
|
|
3610
3613
|
};
|
|
3611
3614
|
}
|
|
3612
3615
|
|
|
3616
|
+
// src/features/image/image-search.client.ts
|
|
3617
|
+
var IMAGE_SEARCH_API = "https://api.webnative.dev/images";
|
|
3618
|
+
async function searchImage(query, size, signal) {
|
|
3619
|
+
const url = new URL(IMAGE_SEARCH_API);
|
|
3620
|
+
url.searchParams.set("query", query);
|
|
3621
|
+
if (size) {
|
|
3622
|
+
url.searchParams.set("size", size);
|
|
3623
|
+
}
|
|
3624
|
+
const response = await fetch(url.toString(), { signal });
|
|
3625
|
+
if (!response.ok) {
|
|
3626
|
+
throw new Error(`Image search failed (${response.status}): ${await response.text()}`);
|
|
3627
|
+
}
|
|
3628
|
+
const contentType = response.headers.get("content-type") ?? "image/jpeg";
|
|
3629
|
+
const buffer = await response.arrayBuffer();
|
|
3630
|
+
const bytes = new Uint8Array(buffer);
|
|
3631
|
+
let binary = "";
|
|
3632
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
3633
|
+
binary += String.fromCharCode(bytes[i]);
|
|
3634
|
+
}
|
|
3635
|
+
const imageData = btoa(binary);
|
|
3636
|
+
return { imageData, contentType };
|
|
3637
|
+
}
|
|
3638
|
+
|
|
3613
3639
|
// src/tools/generate-image.ts
|
|
3614
3640
|
var generateImageSchema = Type13.Object({
|
|
3615
3641
|
prompt: Type13.String({ description: "Text description of the image to generate" }),
|
|
@@ -3617,16 +3643,19 @@ var generateImageSchema = Type13.Object({
|
|
|
3617
3643
|
Type13.String({
|
|
3618
3644
|
description: "Image model name or ID to use (e.g. 'dall-e-3', 'gpt-image-1', 'google/gemini-2.5-flash-image'). If omitted the first enabled image model is used."
|
|
3619
3645
|
})
|
|
3620
|
-
)
|
|
3646
|
+
),
|
|
3647
|
+
save_to_file: Type13.String({
|
|
3648
|
+
description: "File path (relative to the project root) to save the generated image to, e.g. 'assets/logo.png' or 'docs/diagram.png'. The file and any parent directories will be created automatically. The image is also returned as data so it can be displayed."
|
|
3649
|
+
})
|
|
3621
3650
|
});
|
|
3622
3651
|
function createGenerateImageTool(options) {
|
|
3623
|
-
const { metadataManager } = options;
|
|
3652
|
+
const { metadataManager, cwd } = options;
|
|
3624
3653
|
return {
|
|
3625
3654
|
name: "generate_image",
|
|
3626
3655
|
label: "generate_image",
|
|
3627
|
-
description: "Generate an image from a text prompt. Returns a URL or base64 data for the generated image. Use this when the user asks you to create, draw, design, or generate an image.",
|
|
3656
|
+
description: "Generate an image from a text prompt. Returns a URL or base64 data for the generated image. Use this when the user asks you to create, draw, design, or generate an image. Use the save_to_file parameter to save the image directly to a file in the project (e.g. 'assets/hero.png').",
|
|
3628
3657
|
parameters: generateImageSchema,
|
|
3629
|
-
execute: async (_toolCallId, { prompt, model: requestedModel }, signal) => {
|
|
3658
|
+
execute: async (_toolCallId, { prompt, model: requestedModel, save_to_file }, signal) => {
|
|
3630
3659
|
return withAbortSignal(signal, async (isAborted) => {
|
|
3631
3660
|
const modelManager = new ModelManager(metadataManager);
|
|
3632
3661
|
const enabledByProvider = await modelManager.getAllEnabledImageModels();
|
|
@@ -3639,14 +3668,59 @@ function createGenerateImageTool(options) {
|
|
|
3639
3668
|
}
|
|
3640
3669
|
}
|
|
3641
3670
|
if (imageModels.length === 0) {
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3671
|
+
console.log(
|
|
3672
|
+
`[generate_image] No image models enabled \u2014 falling back to image search for: "${prompt.substring(0, 100)}"`
|
|
3673
|
+
);
|
|
3674
|
+
let imageData2;
|
|
3675
|
+
let contentType;
|
|
3676
|
+
try {
|
|
3677
|
+
const result = await searchImage(prompt, void 0, signal);
|
|
3678
|
+
imageData2 = result.imageData;
|
|
3679
|
+
contentType = result.contentType;
|
|
3680
|
+
} catch (err) {
|
|
3681
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3682
|
+
return {
|
|
3683
|
+
content: [
|
|
3684
|
+
{
|
|
3685
|
+
type: "text",
|
|
3686
|
+
text: `No image models are enabled and the image search fallback also failed: ${message}`
|
|
3687
|
+
}
|
|
3688
|
+
],
|
|
3689
|
+
details: void 0
|
|
3690
|
+
};
|
|
3691
|
+
}
|
|
3692
|
+
if (isAborted()) return { content: [], details: void 0 };
|
|
3693
|
+
let savedPath2;
|
|
3694
|
+
if (cwd) {
|
|
3695
|
+
try {
|
|
3696
|
+
const absolutePath = resolveToCwd(save_to_file, cwd);
|
|
3697
|
+
validatePathWithinCwd(absolutePath, cwd);
|
|
3698
|
+
const binary = atob(imageData2);
|
|
3699
|
+
const bytes = new Uint8Array(binary.length);
|
|
3700
|
+
for (let i = 0; i < binary.length; i++) {
|
|
3701
|
+
bytes[i] = binary.charCodeAt(i);
|
|
3647
3702
|
}
|
|
3648
|
-
|
|
3649
|
-
|
|
3703
|
+
await fsMkdir2(dirname2(absolutePath), { recursive: true });
|
|
3704
|
+
await fsWriteFile3(absolutePath, bytes);
|
|
3705
|
+
savedPath2 = save_to_file;
|
|
3706
|
+
} catch (saveError) {
|
|
3707
|
+
const errMsg = saveError instanceof Error ? saveError.message : String(saveError);
|
|
3708
|
+
return {
|
|
3709
|
+
content: [
|
|
3710
|
+
{
|
|
3711
|
+
type: "text",
|
|
3712
|
+
text: `Stock image found but failed to save to "${save_to_file}": ${errMsg}`
|
|
3713
|
+
}
|
|
3714
|
+
],
|
|
3715
|
+
details: { imageData: imageData2, contentType }
|
|
3716
|
+
};
|
|
3717
|
+
}
|
|
3718
|
+
}
|
|
3719
|
+
const fallbackPayload = { imageData: imageData2, contentType };
|
|
3720
|
+
if (savedPath2) fallbackPayload.savedPath = savedPath2;
|
|
3721
|
+
return {
|
|
3722
|
+
content: [{ type: "text", text: JSON.stringify(fallbackPayload) }],
|
|
3723
|
+
details: { imageData: imageData2, contentType }
|
|
3650
3724
|
};
|
|
3651
3725
|
}
|
|
3652
3726
|
let selectedModel = imageModels[0];
|
|
@@ -3766,12 +3840,52 @@ function createGenerateImageTool(options) {
|
|
|
3766
3840
|
imageData = imageResult.b64_json;
|
|
3767
3841
|
}
|
|
3768
3842
|
if (isAborted()) return { content: [], details: void 0 };
|
|
3843
|
+
let savedPath;
|
|
3844
|
+
if (cwd) {
|
|
3845
|
+
try {
|
|
3846
|
+
const absolutePath = resolveToCwd(save_to_file, cwd);
|
|
3847
|
+
validatePathWithinCwd(absolutePath, cwd);
|
|
3848
|
+
let imageBytes;
|
|
3849
|
+
if (imageData) {
|
|
3850
|
+
const binary = atob(imageData);
|
|
3851
|
+
imageBytes = new Uint8Array(binary.length);
|
|
3852
|
+
for (let i = 0; i < binary.length; i++) {
|
|
3853
|
+
imageBytes[i] = binary.charCodeAt(i);
|
|
3854
|
+
}
|
|
3855
|
+
} else if (imageUrl) {
|
|
3856
|
+
const res = await fetch(imageUrl);
|
|
3857
|
+
if (!res.ok) {
|
|
3858
|
+
throw new Error(`Failed to fetch image from URL: ${res.status}`);
|
|
3859
|
+
}
|
|
3860
|
+
imageBytes = new Uint8Array(await res.arrayBuffer());
|
|
3861
|
+
} else {
|
|
3862
|
+
throw new Error("No image data or URL available to save");
|
|
3863
|
+
}
|
|
3864
|
+
await fsMkdir2(dirname2(absolutePath), { recursive: true });
|
|
3865
|
+
await fsWriteFile3(absolutePath, imageBytes);
|
|
3866
|
+
savedPath = save_to_file;
|
|
3867
|
+
console.log(`[generate_image] Saved image to ${absolutePath}`);
|
|
3868
|
+
} catch (saveError) {
|
|
3869
|
+
console.error("[generate_image] Failed to save image to file:", saveError);
|
|
3870
|
+
const errMsg = saveError instanceof Error ? saveError.message : String(saveError);
|
|
3871
|
+
return {
|
|
3872
|
+
content: [
|
|
3873
|
+
{
|
|
3874
|
+
type: "text",
|
|
3875
|
+
text: `Image was generated but failed to save to "${save_to_file}": ${errMsg}`
|
|
3876
|
+
}
|
|
3877
|
+
],
|
|
3878
|
+
details: { imageUrl, imageData, model: selectedModel.id }
|
|
3879
|
+
};
|
|
3880
|
+
}
|
|
3881
|
+
}
|
|
3769
3882
|
const resultPayload = {
|
|
3770
3883
|
model: `${selectedModel.provider}/${selectedModel.id}`,
|
|
3771
3884
|
imageUrl,
|
|
3772
3885
|
imageData
|
|
3773
3886
|
};
|
|
3774
3887
|
if (text) resultPayload.text = text;
|
|
3888
|
+
if (savedPath) resultPayload.savedPath = savedPath;
|
|
3775
3889
|
return {
|
|
3776
3890
|
content: [
|
|
3777
3891
|
{
|
|
@@ -3786,9 +3900,288 @@ function createGenerateImageTool(options) {
|
|
|
3786
3900
|
};
|
|
3787
3901
|
}
|
|
3788
3902
|
|
|
3903
|
+
// src/tools/find-images.ts
|
|
3904
|
+
import { Type as Type14 } from "@sinclair/typebox";
|
|
3905
|
+
import { mkdir as fsMkdir3, writeFile as fsWriteFile4 } from "fs/promises";
|
|
3906
|
+
import { dirname as dirname3 } from "path";
|
|
3907
|
+
var findImagesSchema = Type14.Object({
|
|
3908
|
+
query: Type14.String({ description: "Search term for the image (e.g. 'sunset over mountains')" }),
|
|
3909
|
+
size: Type14.Optional(
|
|
3910
|
+
Type14.Union(
|
|
3911
|
+
[
|
|
3912
|
+
Type14.Literal("original"),
|
|
3913
|
+
Type14.Literal("large2x"),
|
|
3914
|
+
Type14.Literal("large"),
|
|
3915
|
+
Type14.Literal("medium"),
|
|
3916
|
+
Type14.Literal("small"),
|
|
3917
|
+
Type14.Literal("portrait"),
|
|
3918
|
+
Type14.Literal("landscape"),
|
|
3919
|
+
Type14.Literal("tiny")
|
|
3920
|
+
],
|
|
3921
|
+
{
|
|
3922
|
+
description: "Image size variant: original, large2x, large, medium (default), small, portrait, landscape, tiny"
|
|
3923
|
+
}
|
|
3924
|
+
)
|
|
3925
|
+
),
|
|
3926
|
+
save_to_file: Type14.Optional(
|
|
3927
|
+
Type14.String({
|
|
3928
|
+
description: "File path relative to the project root to save the image to (e.g. 'assets/hero.jpg'). Parent directories are created automatically. If omitted the image is returned as base64 data only."
|
|
3929
|
+
})
|
|
3930
|
+
)
|
|
3931
|
+
});
|
|
3932
|
+
function createFindImagesTool(cwd) {
|
|
3933
|
+
return {
|
|
3934
|
+
name: "find_images",
|
|
3935
|
+
label: "find_images",
|
|
3936
|
+
description: "Find a stock image matching a search query. Returns the image as base64 data and optionally saves it to a file. Use this when the user needs an image and the user has not requested it be generated.",
|
|
3937
|
+
parameters: findImagesSchema,
|
|
3938
|
+
execute: async (_toolCallId, { query, size, save_to_file }, signal) => {
|
|
3939
|
+
return withAbortSignal(signal, async (isAborted) => {
|
|
3940
|
+
console.log(
|
|
3941
|
+
`[find_images] Searching for image \u2014 query: "${query}"${size ? `, size: ${size}` : ""}`
|
|
3942
|
+
);
|
|
3943
|
+
let imageData;
|
|
3944
|
+
let contentType;
|
|
3945
|
+
try {
|
|
3946
|
+
const result = await searchImage(query, size, signal);
|
|
3947
|
+
imageData = result.imageData;
|
|
3948
|
+
contentType = result.contentType;
|
|
3949
|
+
} catch (err) {
|
|
3950
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3951
|
+
return {
|
|
3952
|
+
content: [{ type: "text", text: `Image search failed: ${message}` }],
|
|
3953
|
+
details: void 0
|
|
3954
|
+
};
|
|
3955
|
+
}
|
|
3956
|
+
if (isAborted()) return { content: [], details: void 0 };
|
|
3957
|
+
let savedPath;
|
|
3958
|
+
if (cwd && save_to_file) {
|
|
3959
|
+
try {
|
|
3960
|
+
const absolutePath = resolveToCwd(save_to_file, cwd);
|
|
3961
|
+
validatePathWithinCwd(absolutePath, cwd);
|
|
3962
|
+
const binary = atob(imageData);
|
|
3963
|
+
const bytes = new Uint8Array(binary.length);
|
|
3964
|
+
for (let i = 0; i < binary.length; i++) {
|
|
3965
|
+
bytes[i] = binary.charCodeAt(i);
|
|
3966
|
+
}
|
|
3967
|
+
await fsMkdir3(dirname3(absolutePath), { recursive: true });
|
|
3968
|
+
await fsWriteFile4(absolutePath, bytes);
|
|
3969
|
+
savedPath = save_to_file;
|
|
3970
|
+
console.log(`[find_images] Saved image to ${absolutePath}`);
|
|
3971
|
+
} catch (saveError) {
|
|
3972
|
+
const errMsg = saveError instanceof Error ? saveError.message : String(saveError);
|
|
3973
|
+
return {
|
|
3974
|
+
content: [
|
|
3975
|
+
{
|
|
3976
|
+
type: "text",
|
|
3977
|
+
text: `Image was found but failed to save to "${save_to_file}": ${errMsg}`
|
|
3978
|
+
}
|
|
3979
|
+
],
|
|
3980
|
+
details: { imageData, contentType }
|
|
3981
|
+
};
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
const resultPayload = { imageData, contentType };
|
|
3985
|
+
if (savedPath) resultPayload.savedPath = savedPath;
|
|
3986
|
+
return {
|
|
3987
|
+
content: [{ type: "text", text: JSON.stringify(resultPayload) }],
|
|
3988
|
+
details: { imageData, contentType }
|
|
3989
|
+
};
|
|
3990
|
+
});
|
|
3991
|
+
}
|
|
3992
|
+
};
|
|
3993
|
+
}
|
|
3994
|
+
|
|
3995
|
+
// src/tools/execute-browser-js.ts
|
|
3996
|
+
import { Type as Type15 } from "@sinclair/typebox";
|
|
3997
|
+
var runWebWorkerSchema = Type15.Object({
|
|
3998
|
+
intention: Type15.String({
|
|
3999
|
+
description: "A short description of what this code execution is trying to accomplish."
|
|
4000
|
+
}),
|
|
4001
|
+
code: Type15.String({
|
|
4002
|
+
description: "The javascript code to execute in browser context(not nodejs). The environment provides an async function `callTool(toolName, params)` which allows calling any of the other available tools by name. callTool always returns `{ text: string | null, error: string | null }`. On success `text` contains the result and `error` is null. On failure `error` contains the error message and `text` is null. Example: `const r = await callTool('read', { path: 'foo.ts' }); if (r.error) return r.error; return r.text;`."
|
|
4003
|
+
})
|
|
4004
|
+
});
|
|
4005
|
+
var pendingTasks = /* @__PURE__ */ new Map();
|
|
4006
|
+
function submitWebWorkerResult(toolCallId, result) {
|
|
4007
|
+
const pending = pendingTasks.get(toolCallId);
|
|
4008
|
+
if (!pending) return false;
|
|
4009
|
+
pending.resolve(result);
|
|
4010
|
+
pendingTasks.delete(toolCallId);
|
|
4011
|
+
return true;
|
|
4012
|
+
}
|
|
4013
|
+
function cancelPendingWebWorker(toolCallId) {
|
|
4014
|
+
const pending = pendingTasks.get(toolCallId);
|
|
4015
|
+
if (!pending) return false;
|
|
4016
|
+
pending.reject(new Error("Web worker execution cancelled"));
|
|
4017
|
+
pendingTasks.delete(toolCallId);
|
|
4018
|
+
return true;
|
|
4019
|
+
}
|
|
4020
|
+
function buildDescription(tools) {
|
|
4021
|
+
let desc = "Execute javascript code in a web worker (not nodejs). Use this when running JS code is the easiest way to accomplish the goal, such as data transformations, calculations, or orchestrating multiple tool calls programmatically. Returns the result of the execution. You can call other tools by using `await callTool('toolName', { arg: 'value' })`. callTool always returns `{ text: string | null, error: string | null }`. On success `text` contains the result and `error` is null. On failure `error` contains the error message and `text` is null. Example: `const r = await callTool('read', { path: 'foo.ts' }); if (r.error) return r.error; return r.text;`. Write the most compact code possible: no comments, short variable names, minimal whitespace.";
|
|
4022
|
+
if (tools && tools.length > 0) {
|
|
4023
|
+
const signatures = tools.map((t) => `${t.name}: ${JSON.stringify(t.parameters?.properties ?? {})}`).join("\n");
|
|
4024
|
+
desc += `
|
|
4025
|
+
|
|
4026
|
+
Available tools:
|
|
4027
|
+
${signatures}`;
|
|
4028
|
+
}
|
|
4029
|
+
return desc;
|
|
4030
|
+
}
|
|
4031
|
+
function createRunWebWorkerTool(options) {
|
|
4032
|
+
return {
|
|
4033
|
+
name: "run_js",
|
|
4034
|
+
label: "run_js",
|
|
4035
|
+
description: buildDescription(options?.tools),
|
|
4036
|
+
parameters: runWebWorkerSchema,
|
|
4037
|
+
execute: async (toolCallId, { intention: _intention, code }, signal) => {
|
|
4038
|
+
console.log(`[ai] run_js: executing script of length ${code.length}`);
|
|
4039
|
+
if (signal?.aborted) {
|
|
4040
|
+
throw new Error("Operation aborted");
|
|
4041
|
+
}
|
|
4042
|
+
const result = await new Promise((resolve6, reject) => {
|
|
4043
|
+
pendingTasks.set(toolCallId, {
|
|
4044
|
+
resolve: resolve6,
|
|
4045
|
+
reject,
|
|
4046
|
+
code
|
|
4047
|
+
});
|
|
4048
|
+
const onAbort = () => {
|
|
4049
|
+
cancelPendingWebWorker(toolCallId);
|
|
4050
|
+
};
|
|
4051
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
4052
|
+
});
|
|
4053
|
+
return {
|
|
4054
|
+
content: [
|
|
4055
|
+
{
|
|
4056
|
+
type: "text",
|
|
4057
|
+
text: typeof result === "string" ? result : JSON.stringify(result, null, 2) || "undefined"
|
|
4058
|
+
}
|
|
4059
|
+
],
|
|
4060
|
+
details: void 0
|
|
4061
|
+
};
|
|
4062
|
+
}
|
|
4063
|
+
};
|
|
4064
|
+
}
|
|
4065
|
+
var runWebWorkerTool = createRunWebWorkerTool();
|
|
4066
|
+
|
|
4067
|
+
// src/tools/tool-search.ts
|
|
4068
|
+
import { Type as Type16 } from "@sinclair/typebox";
|
|
4069
|
+
var toolSearchSchema = Type16.Object({
|
|
4070
|
+
query: Type16.String({
|
|
4071
|
+
description: 'Search query to find tools. Use "select:name1,name2" to fetch exact tools by name, or keywords to search by description.'
|
|
4072
|
+
}),
|
|
4073
|
+
max_results: Type16.Optional(
|
|
4074
|
+
Type16.Number({
|
|
4075
|
+
description: "Maximum number of results to return (default: 5)"
|
|
4076
|
+
})
|
|
4077
|
+
)
|
|
4078
|
+
});
|
|
4079
|
+
function formatToolSchema(tool) {
|
|
4080
|
+
const schema = JSON.stringify(tool.parameters, null, 2);
|
|
4081
|
+
return `<function>{"name": "${tool.name}", "description": ${JSON.stringify(tool.description)}, "parameters": ${schema}}</function>`;
|
|
4082
|
+
}
|
|
4083
|
+
function parseQuery(query) {
|
|
4084
|
+
const trimmed = query.trim();
|
|
4085
|
+
if (trimmed.startsWith("select:")) {
|
|
4086
|
+
const names = trimmed.slice(7).split(",").map((n) => n.trim().toLowerCase()).filter(Boolean);
|
|
4087
|
+
return { mode: "select", names };
|
|
4088
|
+
}
|
|
4089
|
+
if (trimmed.startsWith("+")) {
|
|
4090
|
+
const parts = trimmed.slice(1).trim().split(/\s+/);
|
|
4091
|
+
const requiredName = parts[0]?.toLowerCase();
|
|
4092
|
+
const terms2 = parts.slice(1).map((t) => t.toLowerCase());
|
|
4093
|
+
return { mode: "search", terms: terms2, requiredName };
|
|
4094
|
+
}
|
|
4095
|
+
const terms = trimmed.toLowerCase().split(/\s+/).filter(Boolean);
|
|
4096
|
+
return { mode: "search", terms };
|
|
4097
|
+
}
|
|
4098
|
+
function scoreTool(tool, terms) {
|
|
4099
|
+
if (terms.length === 0) return 1;
|
|
4100
|
+
const name = tool.name.toLowerCase();
|
|
4101
|
+
const label = ("label" in tool ? tool.label : "").toLowerCase();
|
|
4102
|
+
const desc = tool.description.toLowerCase();
|
|
4103
|
+
let score = 0;
|
|
4104
|
+
for (const term of terms) {
|
|
4105
|
+
if (name === term) score += 10;
|
|
4106
|
+
else if (name.includes(term)) score += 5;
|
|
4107
|
+
if (label.includes(term)) score += 3;
|
|
4108
|
+
if (desc.includes(term)) score += 1;
|
|
4109
|
+
}
|
|
4110
|
+
return score;
|
|
4111
|
+
}
|
|
4112
|
+
function createToolSearchTool(options) {
|
|
4113
|
+
const { liveTools, deferredTools } = options;
|
|
4114
|
+
const activated = /* @__PURE__ */ new Set();
|
|
4115
|
+
return {
|
|
4116
|
+
name: "tool_search",
|
|
4117
|
+
label: "tool_search",
|
|
4118
|
+
description: 'Search for and activate deferred tools. Returns full schema definitions so you can call them. Use "select:name1,name2" to fetch exact tools, or keywords to search by description.',
|
|
4119
|
+
parameters: toolSearchSchema,
|
|
4120
|
+
execute: async (_toolCallId, input) => {
|
|
4121
|
+
const maxResults = input.max_results ?? 5;
|
|
4122
|
+
const parsed = parseQuery(input.query);
|
|
4123
|
+
let matched = [];
|
|
4124
|
+
if (parsed.mode === "select") {
|
|
4125
|
+
for (const name of parsed.names) {
|
|
4126
|
+
const tool = deferredTools.get(name);
|
|
4127
|
+
if (tool) matched.push(tool);
|
|
4128
|
+
}
|
|
4129
|
+
} else {
|
|
4130
|
+
const candidates = [];
|
|
4131
|
+
for (const tool of deferredTools.values()) {
|
|
4132
|
+
if (parsed.requiredName) {
|
|
4133
|
+
const name = tool.name.toLowerCase();
|
|
4134
|
+
if (!name.includes(parsed.requiredName)) continue;
|
|
4135
|
+
}
|
|
4136
|
+
const score = scoreTool(tool, parsed.terms);
|
|
4137
|
+
if (score > 0) {
|
|
4138
|
+
candidates.push({ tool, score });
|
|
4139
|
+
}
|
|
4140
|
+
}
|
|
4141
|
+
candidates.sort((a, b) => b.score - a.score);
|
|
4142
|
+
matched = candidates.slice(0, maxResults).map((c) => c.tool);
|
|
4143
|
+
}
|
|
4144
|
+
if (matched.length === 0) {
|
|
4145
|
+
const available = Array.from(deferredTools.keys()).join(", ");
|
|
4146
|
+
return {
|
|
4147
|
+
content: [
|
|
4148
|
+
{
|
|
4149
|
+
type: "text",
|
|
4150
|
+
text: `No tools matched query "${input.query}". Available deferred tools: ${available || "none"}`
|
|
4151
|
+
}
|
|
4152
|
+
],
|
|
4153
|
+
details: void 0
|
|
4154
|
+
};
|
|
4155
|
+
}
|
|
4156
|
+
for (const tool of matched) {
|
|
4157
|
+
if (!activated.has(tool.name)) {
|
|
4158
|
+
activated.add(tool.name);
|
|
4159
|
+
liveTools.push(tool);
|
|
4160
|
+
}
|
|
4161
|
+
}
|
|
4162
|
+
const schemaBlocks = matched.map(formatToolSchema).join("\n");
|
|
4163
|
+
const text = `Found ${matched.length} tool(s). They are now available for use.
|
|
4164
|
+
|
|
4165
|
+
<functions>
|
|
4166
|
+
${schemaBlocks}
|
|
4167
|
+
</functions>`;
|
|
4168
|
+
return {
|
|
4169
|
+
content: [{ type: "text", text }],
|
|
4170
|
+
details: void 0
|
|
4171
|
+
};
|
|
4172
|
+
}
|
|
4173
|
+
};
|
|
4174
|
+
}
|
|
4175
|
+
function formatDeferredToolsList(deferredTools) {
|
|
4176
|
+
if (deferredTools.size === 0) return "";
|
|
4177
|
+
const names = Array.from(deferredTools.keys()).join(", ");
|
|
4178
|
+
return "\n\n# Deferred Tools\n\nThe following tools are available but deferred. Use the `tool_search` tool to fetch their full definitions before calling them:\n" + names;
|
|
4179
|
+
}
|
|
4180
|
+
|
|
3789
4181
|
// src/tools/index.ts
|
|
3790
4182
|
async function createCodingTools(cwd, options) {
|
|
3791
|
-
const
|
|
4183
|
+
const deferOption = options?.deferTools ?? true;
|
|
4184
|
+
const coreTools = [
|
|
3792
4185
|
createReadTool(cwd, options?.read),
|
|
3793
4186
|
createBashTool(cwd, options?.bash),
|
|
3794
4187
|
createEditTool(cwd),
|
|
@@ -3796,30 +4189,81 @@ async function createCodingTools(cwd, options) {
|
|
|
3796
4189
|
createAskUserTool(),
|
|
3797
4190
|
createTodoTool(options?.threadId ?? ""),
|
|
3798
4191
|
createFindTool(cwd),
|
|
3799
|
-
createAstGrepTool(cwd)
|
|
4192
|
+
createAstGrepTool(cwd),
|
|
4193
|
+
createRunWebWorkerTool()
|
|
3800
4194
|
];
|
|
4195
|
+
const optionalTools = [];
|
|
3801
4196
|
if (options?.metadataManager) {
|
|
3802
|
-
|
|
4197
|
+
optionalTools.push(createGenerateImageTool({ metadataManager: options.metadataManager, cwd }));
|
|
3803
4198
|
}
|
|
3804
|
-
|
|
4199
|
+
optionalTools.push(createFindImagesTool(cwd));
|
|
3805
4200
|
if (options?.skills && options.skills.length > 0) {
|
|
3806
|
-
|
|
4201
|
+
optionalTools.push(
|
|
3807
4202
|
createSkillScriptTool(options.skills, cwd),
|
|
3808
4203
|
createSkillReferenceTool(options.skills)
|
|
3809
|
-
|
|
3810
|
-
toolsWithSkills.push(...skillTools);
|
|
4204
|
+
);
|
|
3811
4205
|
}
|
|
4206
|
+
let mcpTools = [];
|
|
3812
4207
|
try {
|
|
3813
|
-
|
|
3814
|
-
toolsWithSkills.push(...mcpTools);
|
|
4208
|
+
mcpTools = await createMCPTools(cwd);
|
|
3815
4209
|
} catch (error) {
|
|
3816
4210
|
console.warn(`[Tools] Failed to load MCP tools for ${cwd}:`, error);
|
|
3817
4211
|
}
|
|
3818
|
-
|
|
4212
|
+
const deferredTools = /* @__PURE__ */ new Map();
|
|
4213
|
+
if (deferOption === false) {
|
|
4214
|
+
coreTools.push(...optionalTools, ...mcpTools);
|
|
4215
|
+
} else if (deferOption === true) {
|
|
4216
|
+
for (const tool of [...mcpTools, ...optionalTools]) {
|
|
4217
|
+
deferredTools.set(tool.name, tool);
|
|
4218
|
+
}
|
|
4219
|
+
} else {
|
|
4220
|
+
const deferSet = new Set(deferOption.map((n) => n.toLowerCase()));
|
|
4221
|
+
for (const tool of [...optionalTools, ...mcpTools]) {
|
|
4222
|
+
if (deferSet.has(tool.name.toLowerCase())) {
|
|
4223
|
+
deferredTools.set(tool.name, tool);
|
|
4224
|
+
} else {
|
|
4225
|
+
coreTools.push(tool);
|
|
4226
|
+
}
|
|
4227
|
+
}
|
|
4228
|
+
}
|
|
4229
|
+
const tools = [...coreTools];
|
|
4230
|
+
if (deferredTools.size > 0) {
|
|
4231
|
+
const toolSearch = createToolSearchTool({
|
|
4232
|
+
liveTools: tools,
|
|
4233
|
+
deferredTools
|
|
4234
|
+
});
|
|
4235
|
+
tools.push(toolSearch);
|
|
4236
|
+
console.log(
|
|
4237
|
+
`[Tools] ${deferredTools.size} tool(s) deferred behind tool_search: ${Array.from(deferredTools.keys()).join(", ")}`
|
|
4238
|
+
);
|
|
4239
|
+
}
|
|
4240
|
+
return { tools, deferredToolNames: deferredTools };
|
|
4241
|
+
}
|
|
4242
|
+
function createAllTools(cwd, options) {
|
|
4243
|
+
const tools = {
|
|
4244
|
+
read: createReadTool(cwd, options?.read),
|
|
4245
|
+
bash: createBashTool(cwd, options?.bash),
|
|
4246
|
+
edit: createEditTool(cwd),
|
|
4247
|
+
write: createWriteTool(cwd),
|
|
4248
|
+
grep: createGrepTool(cwd),
|
|
4249
|
+
ast_grep: createAstGrepTool(cwd),
|
|
4250
|
+
find: createFindTool(cwd),
|
|
4251
|
+
ask_user: createAskUserTool(),
|
|
4252
|
+
ls: createLsTool(cwd),
|
|
4253
|
+
run_js: createRunWebWorkerTool()
|
|
4254
|
+
};
|
|
4255
|
+
if (options?.metadataManager) {
|
|
4256
|
+
tools.generate_image = createGenerateImageTool({
|
|
4257
|
+
metadataManager: options.metadataManager,
|
|
4258
|
+
cwd
|
|
4259
|
+
});
|
|
4260
|
+
}
|
|
4261
|
+
tools.find_images = createFindImagesTool(cwd);
|
|
4262
|
+
return tools;
|
|
3819
4263
|
}
|
|
3820
4264
|
|
|
3821
4265
|
// src/tools/agent-tool.ts
|
|
3822
|
-
import { Type as
|
|
4266
|
+
import { Type as Type17 } from "@sinclair/typebox";
|
|
3823
4267
|
|
|
3824
4268
|
// src/agent/agent.subagent-executor.ts
|
|
3825
4269
|
import { Agent } from "@mariozechner/pi-agent-core";
|
|
@@ -4090,6 +4534,7 @@ async function executeSubagent(options) {
|
|
|
4090
4534
|
model: resolvedModel,
|
|
4091
4535
|
tools
|
|
4092
4536
|
},
|
|
4537
|
+
streamFn: cachedStreamFn,
|
|
4093
4538
|
getApiKey: async () => apiKey
|
|
4094
4539
|
});
|
|
4095
4540
|
const eventQueue = new EventQueue();
|
|
@@ -4166,15 +4611,15 @@ async function executeSubagent(options) {
|
|
|
4166
4611
|
}
|
|
4167
4612
|
|
|
4168
4613
|
// src/tools/agent-tool.ts
|
|
4169
|
-
var agentToolSchema =
|
|
4170
|
-
prompt:
|
|
4171
|
-
agentName:
|
|
4172
|
-
|
|
4614
|
+
var agentToolSchema = Type17.Object({
|
|
4615
|
+
prompt: Type17.String({ description: "The task or question for the subagent to work on" }),
|
|
4616
|
+
agentName: Type17.Optional(
|
|
4617
|
+
Type17.String({
|
|
4173
4618
|
description: "Name of a defined agent (from AGENT.md) to use. If omitted, a general-purpose subagent is created."
|
|
4174
4619
|
})
|
|
4175
4620
|
),
|
|
4176
|
-
description:
|
|
4177
|
-
|
|
4621
|
+
description: Type17.Optional(
|
|
4622
|
+
Type17.String({
|
|
4178
4623
|
description: "A short (3-5 word) label describing what the subagent will do"
|
|
4179
4624
|
})
|
|
4180
4625
|
)
|
|
@@ -4880,7 +5325,7 @@ function formatAgentsSection(agents) {
|
|
|
4880
5325
|
);
|
|
4881
5326
|
return section;
|
|
4882
5327
|
}
|
|
4883
|
-
async function loadAgentSystemPrompt(threadPath, tools, skills, planMode, agents) {
|
|
5328
|
+
async function loadAgentSystemPrompt(threadPath, tools, skills, planMode, agents, deferredTools) {
|
|
4884
5329
|
let prompt = buildDefaultPrompt(tools);
|
|
4885
5330
|
const agentsContent = loadDeveloperContext(threadPath);
|
|
4886
5331
|
if (agentsContent) {
|
|
@@ -4909,15 +5354,50 @@ async function loadAgentSystemPrompt(threadPath, tools, skills, planMode, agents
|
|
|
4909
5354
|
if (agentsSection) {
|
|
4910
5355
|
prompt += agentsSection;
|
|
4911
5356
|
}
|
|
5357
|
+
if (deferredTools && deferredTools.size > 0) {
|
|
5358
|
+
prompt += formatDeferredToolsList(deferredTools);
|
|
5359
|
+
}
|
|
4912
5360
|
return prompt;
|
|
4913
5361
|
}
|
|
4914
5362
|
|
|
4915
5363
|
// src/agent/agent.executor.ts
|
|
5364
|
+
var cachedStreamFn = (model, context, options) => {
|
|
5365
|
+
return streamSimple(model, context, { ...options, cacheRetention: "long" });
|
|
5366
|
+
};
|
|
5367
|
+
function buildHistoryMessages(history) {
|
|
5368
|
+
const messages = [];
|
|
5369
|
+
const now = Date.now();
|
|
5370
|
+
for (const exchange of history) {
|
|
5371
|
+
messages.push({
|
|
5372
|
+
role: "user",
|
|
5373
|
+
content: exchange.userMessage,
|
|
5374
|
+
timestamp: now
|
|
5375
|
+
});
|
|
5376
|
+
messages.push({
|
|
5377
|
+
role: "assistant",
|
|
5378
|
+
content: [{ type: "text", text: exchange.assistantResponse }],
|
|
5379
|
+
api: "",
|
|
5380
|
+
provider: "",
|
|
5381
|
+
model: "",
|
|
5382
|
+
usage: {
|
|
5383
|
+
input: 0,
|
|
5384
|
+
output: 0,
|
|
5385
|
+
cacheRead: 0,
|
|
5386
|
+
cacheWrite: 0,
|
|
5387
|
+
totalTokens: 0,
|
|
5388
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
|
|
5389
|
+
},
|
|
5390
|
+
stopReason: "stop",
|
|
5391
|
+
timestamp: now
|
|
5392
|
+
});
|
|
5393
|
+
}
|
|
5394
|
+
return messages;
|
|
5395
|
+
}
|
|
4916
5396
|
var PiExecutorImpl = class {
|
|
4917
5397
|
constructor(metadataManager) {
|
|
4918
5398
|
this.metadataManager = metadataManager;
|
|
4919
5399
|
}
|
|
4920
|
-
async *execute(userPrompt, context, signal) {
|
|
5400
|
+
async *execute(userPrompt, context, signal, conversationHistory) {
|
|
4921
5401
|
try {
|
|
4922
5402
|
const providerName = context.provider;
|
|
4923
5403
|
if (!providerName) {
|
|
@@ -4970,7 +5450,7 @@ var PiExecutorImpl = class {
|
|
|
4970
5450
|
api: resolvedModel.api,
|
|
4971
5451
|
provider: resolvedModel.provider
|
|
4972
5452
|
});
|
|
4973
|
-
const tools = await createCodingTools(cwd, {
|
|
5453
|
+
const { tools, deferredToolNames } = await createCodingTools(cwd, {
|
|
4974
5454
|
skills: context.skills,
|
|
4975
5455
|
threadId: context.threadId,
|
|
4976
5456
|
metadataManager: this.metadataManager
|
|
@@ -4980,7 +5460,8 @@ var PiExecutorImpl = class {
|
|
|
4980
5460
|
tools,
|
|
4981
5461
|
context.skills,
|
|
4982
5462
|
context.planMode,
|
|
4983
|
-
context.agents
|
|
5463
|
+
context.agents,
|
|
5464
|
+
deferredToolNames
|
|
4984
5465
|
);
|
|
4985
5466
|
console.log(
|
|
4986
5467
|
"[ai] System prompt loaded from thread path",
|
|
@@ -5002,12 +5483,15 @@ var PiExecutorImpl = class {
|
|
|
5002
5483
|
});
|
|
5003
5484
|
tools.push(agentToolInstance);
|
|
5004
5485
|
}
|
|
5486
|
+
const historyMessages = buildHistoryMessages(conversationHistory ?? []);
|
|
5005
5487
|
const agent = new Agent2({
|
|
5006
5488
|
initialState: {
|
|
5007
5489
|
systemPrompt,
|
|
5008
5490
|
model: resolvedModel,
|
|
5009
|
-
tools
|
|
5491
|
+
tools,
|
|
5492
|
+
messages: historyMessages
|
|
5010
5493
|
},
|
|
5494
|
+
streamFn: cachedStreamFn,
|
|
5011
5495
|
getApiKey: async () => apiKey
|
|
5012
5496
|
});
|
|
5013
5497
|
let done = false;
|
|
@@ -5894,18 +6378,6 @@ function streamAsyncGenerator(c, generator, options) {
|
|
|
5894
6378
|
}
|
|
5895
6379
|
|
|
5896
6380
|
// src/features/conversations/conversations.content.ts
|
|
5897
|
-
var DEFAULT_MAX_CONTEXT_MESSAGES = 10;
|
|
5898
|
-
function formatConversationContext(messages, maxMessages = DEFAULT_MAX_CONTEXT_MESSAGES) {
|
|
5899
|
-
const completed = messages.filter((m) => m.response != null);
|
|
5900
|
-
const recent = completed.slice(-maxMessages);
|
|
5901
|
-
if (recent.length === 0) return "";
|
|
5902
|
-
const lines = ["Previous conversation:"];
|
|
5903
|
-
for (const m of recent) {
|
|
5904
|
-
lines.push(`User: ${m.request.message}`);
|
|
5905
|
-
lines.push(`Assistant: ${m.response.content}`);
|
|
5906
|
-
}
|
|
5907
|
-
return lines.join("\n");
|
|
5908
|
-
}
|
|
5909
6381
|
var extractTextBlocksFromContent = (content) => {
|
|
5910
6382
|
const trimmed = content.trim();
|
|
5911
6383
|
if (!trimmed.startsWith("[") && !trimmed.startsWith("{")) {
|
|
@@ -6505,11 +6977,10 @@ async function postChatMessage(c, threadManager, agentExecutor, conversationMana
|
|
|
6505
6977
|
const priorMessages = (history?.messages ?? []).filter(
|
|
6506
6978
|
(m) => m.id !== messageId && m.response != null
|
|
6507
6979
|
);
|
|
6508
|
-
const
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
User: ${content}` : content;
|
|
6980
|
+
const conversationHistory = priorMessages.slice(-10).map((m) => ({
|
|
6981
|
+
userMessage: m.request.message,
|
|
6982
|
+
assistantResponse: m.response.content
|
|
6983
|
+
}));
|
|
6513
6984
|
const abortController = new AbortController();
|
|
6514
6985
|
const requestSignal = c.req.raw.signal;
|
|
6515
6986
|
if (requestSignal.aborted) {
|
|
@@ -6530,9 +7001,10 @@ User: ${content}` : content;
|
|
|
6530
7001
|
let fullContent = "";
|
|
6531
7002
|
try {
|
|
6532
7003
|
for await (const event of agentExecutor.execute(
|
|
6533
|
-
|
|
7004
|
+
content,
|
|
6534
7005
|
context,
|
|
6535
|
-
abortController.signal
|
|
7006
|
+
abortController.signal,
|
|
7007
|
+
conversationHistory
|
|
6536
7008
|
)) {
|
|
6537
7009
|
capturedEvents.push(event);
|
|
6538
7010
|
if (event.type === "message" && event.content) {
|
|
@@ -14455,6 +14927,8 @@ async function handleGetThreadMessages(c, threadManager, conversationManager) {
|
|
|
14455
14927
|
if (!message.response) {
|
|
14456
14928
|
return [userMessage];
|
|
14457
14929
|
}
|
|
14930
|
+
const tokenCount = message.inputTokens || message.outputTokens ? (message.inputTokens ?? 0) + (message.outputTokens ?? 0) : void 0;
|
|
14931
|
+
const processingTimeMs = message.timestamp && message.response.completedAt ? new Date(message.response.completedAt).getTime() - new Date(message.timestamp).getTime() : void 0;
|
|
14458
14932
|
const agentMessage = {
|
|
14459
14933
|
messageId: `agent-${message.id}`,
|
|
14460
14934
|
sender: "agent",
|
|
@@ -14465,6 +14939,12 @@ async function handleGetThreadMessages(c, threadManager, conversationManager) {
|
|
|
14465
14939
|
contentType: "markdown",
|
|
14466
14940
|
timestamp: message.response.completedAt,
|
|
14467
14941
|
model: message.request.model,
|
|
14942
|
+
...(tokenCount || processingTimeMs) && {
|
|
14943
|
+
metadata: {
|
|
14944
|
+
...tokenCount && { tokenCount },
|
|
14945
|
+
...processingTimeMs && processingTimeMs > 0 && { processingTimeMs }
|
|
14946
|
+
}
|
|
14947
|
+
},
|
|
14468
14948
|
...message.response.imageUrl && { generatedImageUrl: message.response.imageUrl }
|
|
14469
14949
|
};
|
|
14470
14950
|
return [userMessage, agentMessage];
|
|
@@ -14973,7 +15453,7 @@ import { existsSync as existsSync16 } from "fs";
|
|
|
14973
15453
|
|
|
14974
15454
|
// src/features/git/git-download-folder.ts
|
|
14975
15455
|
import { mkdir as mkdir7, writeFile as writeFile5 } from "fs/promises";
|
|
14976
|
-
import { join as join24, dirname as
|
|
15456
|
+
import { join as join24, dirname as dirname4 } from "path";
|
|
14977
15457
|
async function downloadGithubFolder(repoUrl, srcPath, destPath, options = {}) {
|
|
14978
15458
|
const { token, ref } = options;
|
|
14979
15459
|
const match = repoUrl.replace(/\.git$/, "").match(/github\.com\/([^/]+)\/([^/]+)/);
|
|
@@ -15010,7 +15490,7 @@ async function downloadGithubFolder(repoUrl, srcPath, destPath, options = {}) {
|
|
|
15010
15490
|
files.map(async (file) => {
|
|
15011
15491
|
const relativePath = file.path.slice(prefix.length);
|
|
15012
15492
|
const localPath = join24(destPath, relativePath);
|
|
15013
|
-
await mkdir7(
|
|
15493
|
+
await mkdir7(dirname4(localPath), { recursive: true });
|
|
15014
15494
|
const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${refParam}/${file.path}`;
|
|
15015
15495
|
const fileRes = await fetch(rawUrl, { headers });
|
|
15016
15496
|
if (!fileRes.ok) {
|
|
@@ -15646,7 +16126,7 @@ function createThreadRoutes(threadManager, gitManager, conversationManager) {
|
|
|
15646
16126
|
// src/features/git/git.manager.ts
|
|
15647
16127
|
init_utils();
|
|
15648
16128
|
import { mkdir as mkdir9 } from "fs/promises";
|
|
15649
|
-
import { dirname as
|
|
16129
|
+
import { dirname as dirname5 } from "path";
|
|
15650
16130
|
var GitManagerImpl = class {
|
|
15651
16131
|
/**
|
|
15652
16132
|
* Regular expressions for validating different git URL formats
|
|
@@ -15716,7 +16196,7 @@ var GitManagerImpl = class {
|
|
|
15716
16196
|
return;
|
|
15717
16197
|
}
|
|
15718
16198
|
try {
|
|
15719
|
-
const parentDir =
|
|
16199
|
+
const parentDir = dirname5(targetPath);
|
|
15720
16200
|
await mkdir9(parentDir, { recursive: true });
|
|
15721
16201
|
const gitProcess = spawnProcess("git", ["clone", "--progress", gitUrl, targetPath]);
|
|
15722
16202
|
const eventQueue = [];
|
|
@@ -18511,7 +18991,7 @@ function createUpdateRoutes(updater) {
|
|
|
18511
18991
|
// src/features/mcp/mcp.routes.ts
|
|
18512
18992
|
import { Hono as Hono13 } from "hono";
|
|
18513
18993
|
import { readFile as readFile15, writeFile as writeFile7, mkdir as mkdir10, access as access6 } from "fs/promises";
|
|
18514
|
-
import { join as join28, dirname as
|
|
18994
|
+
import { join as join28, dirname as dirname6 } from "path";
|
|
18515
18995
|
|
|
18516
18996
|
// src/features/mcp/mcp.popular.json
|
|
18517
18997
|
var mcp_popular_default = [
|
|
@@ -18637,7 +19117,7 @@ async function readMCPConfig(projectPath) {
|
|
|
18637
19117
|
async function writeMCPConfig(projectPath, config) {
|
|
18638
19118
|
const existing = await readMCPConfig(projectPath);
|
|
18639
19119
|
const targetPath = existing?.filePath ?? join28(projectPath, ".agents/mcp.json");
|
|
18640
|
-
await mkdir10(
|
|
19120
|
+
await mkdir10(dirname6(targetPath), { recursive: true });
|
|
18641
19121
|
await writeFile7(targetPath, JSON.stringify(config, null, 2), "utf-8");
|
|
18642
19122
|
}
|
|
18643
19123
|
function createMCPRoutes() {
|
|
@@ -19025,9 +19505,35 @@ function createProjectTodosRoutes(metadataManager) {
|
|
|
19025
19505
|
|
|
19026
19506
|
// src/features/account/account.routes.ts
|
|
19027
19507
|
import { Hono as Hono17 } from "hono";
|
|
19508
|
+
|
|
19509
|
+
// src/features/account/account-settings.route.ts
|
|
19510
|
+
init_database();
|
|
19511
|
+
var KEY_USE_TOOLS_THROUGH_CODE = "UseToolsThroughCode";
|
|
19512
|
+
async function getAccountSettings(c) {
|
|
19513
|
+
const db = await getDatabase();
|
|
19514
|
+
const raw = await getState(db, KEY_USE_TOOLS_THROUGH_CODE);
|
|
19515
|
+
return c.json({
|
|
19516
|
+
useToolsThroughCode: raw !== false
|
|
19517
|
+
});
|
|
19518
|
+
}
|
|
19519
|
+
async function updateAccountSettings(c) {
|
|
19520
|
+
const body = await c.req.json();
|
|
19521
|
+
const db = await getDatabase();
|
|
19522
|
+
if (typeof body.useToolsThroughCode === "boolean") {
|
|
19523
|
+
await setState(db, KEY_USE_TOOLS_THROUGH_CODE, body.useToolsThroughCode);
|
|
19524
|
+
}
|
|
19525
|
+
const raw = await getState(db, KEY_USE_TOOLS_THROUGH_CODE);
|
|
19526
|
+
return c.json({
|
|
19527
|
+
useToolsThroughCode: raw !== false
|
|
19528
|
+
});
|
|
19529
|
+
}
|
|
19530
|
+
|
|
19531
|
+
// src/features/account/account.routes.ts
|
|
19028
19532
|
function createAccountRoutes() {
|
|
19029
19533
|
const router = new Hono17();
|
|
19030
19534
|
router.get("/info", (c) => getAccountInfo(c));
|
|
19535
|
+
router.get("/settings", (c) => getAccountSettings(c));
|
|
19536
|
+
router.put("/settings", (c) => updateAccountSettings(c));
|
|
19031
19537
|
return router;
|
|
19032
19538
|
}
|
|
19033
19539
|
|
|
@@ -19311,13 +19817,89 @@ function createImageRoutes(metadataManager, conversationManager, threadManager)
|
|
|
19311
19817
|
return router;
|
|
19312
19818
|
}
|
|
19313
19819
|
|
|
19820
|
+
// src/features/browser-js/browser-js.routes.ts
|
|
19821
|
+
import { Hono as Hono20 } from "hono";
|
|
19822
|
+
function createBrowserJsRoutes(threadManager) {
|
|
19823
|
+
const router = new Hono20();
|
|
19824
|
+
router.post("/respond", async (c) => {
|
|
19825
|
+
try {
|
|
19826
|
+
const body = await c.req.json();
|
|
19827
|
+
const { toolCallId, result } = body;
|
|
19828
|
+
if (!toolCallId || typeof toolCallId !== "string") {
|
|
19829
|
+
return errorResponse(
|
|
19830
|
+
c,
|
|
19831
|
+
ErrorCodes.INVALID_REQUEST,
|
|
19832
|
+
"toolCallId is required and must be a string",
|
|
19833
|
+
400
|
|
19834
|
+
);
|
|
19835
|
+
}
|
|
19836
|
+
const resolved = submitWebWorkerResult(toolCallId, result);
|
|
19837
|
+
if (!resolved) {
|
|
19838
|
+
return errorResponse(
|
|
19839
|
+
c,
|
|
19840
|
+
ErrorCodes.INVALID_REQUEST,
|
|
19841
|
+
"No pending web worker execution found for this toolCallId",
|
|
19842
|
+
404
|
|
19843
|
+
);
|
|
19844
|
+
}
|
|
19845
|
+
return c.json({ success: true });
|
|
19846
|
+
} catch (error) {
|
|
19847
|
+
return errorResponse(
|
|
19848
|
+
c,
|
|
19849
|
+
ErrorCodes.REQUEST_PARSE_ERROR,
|
|
19850
|
+
"Failed to parse request body",
|
|
19851
|
+
400,
|
|
19852
|
+
error instanceof Error ? error.message : String(error)
|
|
19853
|
+
);
|
|
19854
|
+
}
|
|
19855
|
+
});
|
|
19856
|
+
router.post("/execute-tool", async (c) => {
|
|
19857
|
+
try {
|
|
19858
|
+
const body = await c.req.json();
|
|
19859
|
+
const { threadId, toolName, params } = body;
|
|
19860
|
+
if (!threadId || typeof threadId !== "string") {
|
|
19861
|
+
return errorResponse(
|
|
19862
|
+
c,
|
|
19863
|
+
ErrorCodes.INVALID_REQUEST,
|
|
19864
|
+
"threadId is required and must be a string",
|
|
19865
|
+
400
|
|
19866
|
+
);
|
|
19867
|
+
}
|
|
19868
|
+
if (!toolName || typeof toolName !== "string") {
|
|
19869
|
+
return errorResponse(
|
|
19870
|
+
c,
|
|
19871
|
+
ErrorCodes.INVALID_REQUEST,
|
|
19872
|
+
"toolName is required and must be a string",
|
|
19873
|
+
400
|
|
19874
|
+
);
|
|
19875
|
+
}
|
|
19876
|
+
const thread = await threadManager.getThread(threadId);
|
|
19877
|
+
if (!thread) {
|
|
19878
|
+
return errorResponse(c, ErrorCodes.THREAD_NOT_FOUND, "Thread not found", 404);
|
|
19879
|
+
}
|
|
19880
|
+
const tools = createAllTools(thread.path);
|
|
19881
|
+
const tool = tools[toolName];
|
|
19882
|
+
if (!tool) {
|
|
19883
|
+
return errorResponse(c, ErrorCodes.INVALID_REQUEST, `Tool ${toolName} not found`, 404);
|
|
19884
|
+
}
|
|
19885
|
+
const executionId = "web-worker-" + Date.now().toString() + "-" + Math.random().toString(36).substr(2, 9);
|
|
19886
|
+
const result = await tool.execute(executionId, params);
|
|
19887
|
+
return c.json(result);
|
|
19888
|
+
} catch (error) {
|
|
19889
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
19890
|
+
return errorResponse(c, ErrorCodes.REQUEST_PARSE_ERROR, detail, 500, detail);
|
|
19891
|
+
}
|
|
19892
|
+
});
|
|
19893
|
+
return router;
|
|
19894
|
+
}
|
|
19895
|
+
|
|
19314
19896
|
// src/server.ts
|
|
19315
19897
|
var __filename = fileURLToPath2(import.meta.url);
|
|
19316
19898
|
var __dirname = path5.dirname(__filename);
|
|
19317
19899
|
async function startTarskServer(options) {
|
|
19318
19900
|
const { isDebug: isDebug2, publicDir: publicDirOverride } = options;
|
|
19319
19901
|
const port = isDebug2 ? 462 : process.env.PORT ? parseInt(process.env.PORT) : 641;
|
|
19320
|
-
const app = new
|
|
19902
|
+
const app = new Hono21();
|
|
19321
19903
|
app.use("/*", cors());
|
|
19322
19904
|
app.use("/*", async (c, next) => {
|
|
19323
19905
|
if (c.req.path.startsWith("/api/")) {
|
|
@@ -19388,6 +19970,7 @@ async function startTarskServer(options) {
|
|
|
19388
19970
|
app.route("/api/image", createImageRoutes(metadataManager, conversationManager, threadManager));
|
|
19389
19971
|
app.route("/api/scaffold", createScaffoldRoutes(projectManager));
|
|
19390
19972
|
app.route("/api/ask-user", createAskUserRoutes());
|
|
19973
|
+
app.route("/api/browser-js", createBrowserJsRoutes(threadManager));
|
|
19391
19974
|
app.route("/api/update", createUpdateRoutes(options.updater));
|
|
19392
19975
|
createSlashCommandRoutes(app, threadManager);
|
|
19393
19976
|
createRuleRoutes(app, projectManager);
|