@coze-arch/cli 0.0.18 → 0.0.19-alpha.502ddf
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/lib/__templates__/expo/.coze +1 -0
- package/lib/__templates__/expo/.cozeproj/scripts/validate.sh +8 -0
- package/lib/__templates__/expo/package.json +2 -1
- package/lib/__templates__/nextjs/.coze +1 -0
- package/lib/__templates__/nextjs/package.json +3 -1
- package/lib/__templates__/nextjs/scripts/validate.sh +10 -0
- package/lib/__templates__/nuxt-vue/.coze +1 -0
- package/lib/__templates__/nuxt-vue/app/pages/index.vue +6 -0
- package/lib/__templates__/nuxt-vue/eslint.config.mjs +25 -0
- package/lib/__templates__/nuxt-vue/nuxt.config.ts +2 -2
- package/lib/__templates__/nuxt-vue/package.json +9 -2
- package/lib/__templates__/nuxt-vue/pnpm-lock.yaml +790 -10
- package/lib/__templates__/nuxt-vue/scripts/validate.sh +10 -0
- package/lib/__templates__/pi-agent/.coze +10 -0
- package/lib/__templates__/pi-agent/AGENTS.md +149 -0
- package/lib/__templates__/pi-agent/README.md +218 -0
- package/lib/__templates__/pi-agent/_gitignore +3 -0
- package/lib/__templates__/pi-agent/_npmrc +23 -0
- package/lib/__templates__/pi-agent/bin/pi-bot.ts +8 -0
- package/lib/__templates__/pi-agent/docs/project-overview.md +368 -0
- package/lib/__templates__/pi-agent/docs/user/getting-started.md +46 -0
- package/lib/__templates__/pi-agent/package.json +63 -0
- package/lib/__templates__/pi-agent/pi-resources/SYSTEM.md +15 -0
- package/lib/__templates__/pi-agent/pi-resources/extensions/preference-memory/index.ts +355 -0
- package/lib/__templates__/pi-agent/pi-resources/skills/coze-asr/SKILL.md +30 -0
- package/lib/__templates__/pi-agent/pi-resources/skills/coze-image-gen/SKILL.md +29 -0
- package/lib/__templates__/pi-agent/pi-resources/skills/coze-tts/SKILL.md +57 -0
- package/lib/__templates__/pi-agent/pi-resources/skills/coze-video-gen/SKILL.md +40 -0
- package/lib/__templates__/pi-agent/pnpm-lock.yaml +8282 -0
- package/lib/__templates__/pi-agent/scripts/dev.sh +14 -0
- package/lib/__templates__/pi-agent/scripts/prepare.sh +35 -0
- package/lib/__templates__/pi-agent/src/agent.ts +363 -0
- package/lib/__templates__/pi-agent/src/channels/feishu/index.ts +760 -0
- package/lib/__templates__/pi-agent/src/channels/feishu/streaming-card.ts +297 -0
- package/lib/__templates__/pi-agent/src/channels/wechat/index.ts +171 -0
- package/lib/__templates__/pi-agent/src/cli.ts +117 -0
- package/lib/__templates__/pi-agent/src/config.ts +749 -0
- package/lib/__templates__/pi-agent/src/core.ts +219 -0
- package/lib/__templates__/pi-agent/src/dashboard/api/channels.ts +104 -0
- package/lib/__templates__/pi-agent/src/dashboard/api/models.ts +98 -0
- package/lib/__templates__/pi-agent/src/dashboard/api/overview.ts +33 -0
- package/lib/__templates__/pi-agent/src/dashboard/config-store.ts +64 -0
- package/lib/__templates__/pi-agent/src/dashboard/index.ts +74 -0
- package/lib/__templates__/pi-agent/src/dashboard/server.ts +610 -0
- package/lib/__templates__/pi-agent/src/dashboard/types.ts +25 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/index.html +13 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/postcss.config.cjs +7 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/app-layout.tsx +172 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/page-title.tsx +17 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/alert.tsx +22 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/badge.tsx +25 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/button.tsx +40 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/card.tsx +29 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/input.tsx +18 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/label.tsx +8 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/select.tsx +80 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/separator.tsx +23 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/hooks/use-fetch.ts +32 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/hooks/use-local-storage-state.ts +23 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/main.tsx +24 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/chat-page.tsx +440 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/overview-page.tsx +330 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/services/chat-ws-service.ts +167 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/styles.css +203 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/src/utils/index.ts +11 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/tsconfig.json +13 -0
- package/lib/__templates__/pi-agent/src/dashboard/web/vite.config.ts +17 -0
- package/lib/__templates__/pi-agent/src/index.ts +123 -0
- package/lib/__templates__/pi-agent/src/pi-resources.ts +125 -0
- package/lib/__templates__/pi-agent/src/session-store.ts +223 -0
- package/lib/__templates__/pi-agent/src/tools/common/format-coze-error.ts +12 -0
- package/lib/__templates__/pi-agent/src/tools/index.ts +2 -0
- package/lib/__templates__/pi-agent/src/tools/web-fetch/index.ts +195 -0
- package/lib/__templates__/pi-agent/src/tools/web-search/index.ts +206 -0
- package/lib/__templates__/pi-agent/template.config.js +45 -0
- package/lib/__templates__/pi-agent/tests/cli.test.ts +136 -0
- package/lib/__templates__/pi-agent/tests/config.test.ts +377 -0
- package/lib/__templates__/pi-agent/tests/dashboard-models-api.test.ts +171 -0
- package/lib/__templates__/pi-agent/tests/feishu-channel.test.ts +149 -0
- package/lib/__templates__/pi-agent/tests/feishu-streaming-card.test.ts +15 -0
- package/lib/__templates__/pi-agent/tests/pi-resources.test.ts +73 -0
- package/lib/__templates__/pi-agent/tests/preference-memory.test.ts +43 -0
- package/lib/__templates__/pi-agent/tests/session-store.test.ts +61 -0
- package/lib/__templates__/pi-agent/tests/smoke/run-smoke.ts +275 -0
- package/lib/__templates__/pi-agent/tests/web-fetch.test.ts +157 -0
- package/lib/__templates__/pi-agent/tests/web-search.test.ts +208 -0
- package/lib/__templates__/pi-agent/tsconfig.json +21 -0
- package/lib/__templates__/pi-agent/types/larksuiteoapi-node-sdk.d.ts +113 -0
- package/lib/__templates__/taro/.coze +1 -0
- package/lib/__templates__/taro/.cozeproj/scripts/validate.sh +8 -0
- package/lib/__templates__/taro/package.json +1 -1
- package/lib/__templates__/templates.json +24 -0
- package/lib/__templates__/vite/.coze +1 -0
- package/lib/__templates__/vite/package.json +3 -1
- package/lib/__templates__/vite/scripts/validate.sh +10 -0
- package/lib/cli.js +13 -2
- package/package.json +1 -1
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { type Static, Type } from "@sinclair/typebox";
|
|
2
|
+
import type { ToolDefinition } from "@mariozechner/pi-coding-agent";
|
|
3
|
+
import { FetchClient, type FetchContentItem, type FetchResponse } from "coze-coding-dev-sdk";
|
|
4
|
+
import { formatCozeError } from "../common/format-coze-error.js";
|
|
5
|
+
|
|
6
|
+
type FetchResultLink = {
|
|
7
|
+
title?: string;
|
|
8
|
+
url?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type FetchResultImage = {
|
|
12
|
+
url?: string;
|
|
13
|
+
width?: number;
|
|
14
|
+
height?: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type FetchResultItem = {
|
|
18
|
+
url: string;
|
|
19
|
+
title?: string;
|
|
20
|
+
text: string;
|
|
21
|
+
links: FetchResultLink[];
|
|
22
|
+
images: FetchResultImage[];
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function joinText(items: FetchContentItem[]): string {
|
|
26
|
+
return items
|
|
27
|
+
.filter((item) => item.type === "text" && item.text)
|
|
28
|
+
.map((item) => item.text?.trim() ?? "")
|
|
29
|
+
.filter(Boolean)
|
|
30
|
+
.join("\n\n");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function collectLinks(items: FetchContentItem[]): FetchResultLink[] {
|
|
34
|
+
return items
|
|
35
|
+
.filter((item) => item.type === "link")
|
|
36
|
+
.map((item) => ({
|
|
37
|
+
title: item.text,
|
|
38
|
+
url: item.url,
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function collectImages(items: FetchContentItem[]): FetchResultImage[] {
|
|
43
|
+
return items
|
|
44
|
+
.filter((item) => item.type === "image")
|
|
45
|
+
.map((item) => ({
|
|
46
|
+
url: item.image?.display_url ?? item.image?.image_url,
|
|
47
|
+
width: item.image?.width,
|
|
48
|
+
height: item.image?.height,
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function normalizeFetchedItem(response: FetchResponse, textOnly: boolean): FetchResultItem {
|
|
53
|
+
const content = response.content ?? [];
|
|
54
|
+
return {
|
|
55
|
+
url: response.url ?? "",
|
|
56
|
+
title: response.title,
|
|
57
|
+
text: joinText(content),
|
|
58
|
+
links: textOnly ? [] : collectLinks(content),
|
|
59
|
+
images: textOnly ? [] : collectImages(content),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const MAX_CONCURRENCY = 5;
|
|
64
|
+
|
|
65
|
+
async function fetchContent(
|
|
66
|
+
urls: string[],
|
|
67
|
+
textOnly: boolean,
|
|
68
|
+
): Promise<FetchResultItem[]> {
|
|
69
|
+
const client = new FetchClient();
|
|
70
|
+
const results: FetchResultItem[] = new Array(urls.length);
|
|
71
|
+
let next = 0;
|
|
72
|
+
|
|
73
|
+
async function worker() {
|
|
74
|
+
while (next < urls.length) {
|
|
75
|
+
const idx = next++;
|
|
76
|
+
const response = await client.fetch(urls[idx]);
|
|
77
|
+
results[idx] = normalizeFetchedItem(response, textOnly);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
await Promise.all(Array.from({ length: Math.min(MAX_CONCURRENCY, urls.length) }, () => worker()));
|
|
82
|
+
|
|
83
|
+
return results;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const FetchToolSchema = Type.Object(
|
|
87
|
+
{
|
|
88
|
+
urls: Type.Union(
|
|
89
|
+
[
|
|
90
|
+
Type.String({ description: "Single URL to fetch." }),
|
|
91
|
+
Type.Array(Type.String({ description: "URL to fetch." }), {
|
|
92
|
+
description: "One or more URLs to fetch.",
|
|
93
|
+
minItems: 1,
|
|
94
|
+
}),
|
|
95
|
+
],
|
|
96
|
+
{
|
|
97
|
+
description:
|
|
98
|
+
"URL input. Accepts either a single URL string or an array of URL strings.",
|
|
99
|
+
},
|
|
100
|
+
),
|
|
101
|
+
format: Type.Optional(
|
|
102
|
+
Type.Unsafe<"text" | "markdown" | "json">({
|
|
103
|
+
type: "string",
|
|
104
|
+
enum: ["text", "markdown", "json"],
|
|
105
|
+
description: "Formatting preference for the textual output.",
|
|
106
|
+
}),
|
|
107
|
+
),
|
|
108
|
+
textOnly: Type.Optional(
|
|
109
|
+
Type.Boolean({ description: "Only return extracted text, without images or links." }),
|
|
110
|
+
),
|
|
111
|
+
},
|
|
112
|
+
{ additionalProperties: false },
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
function renderFetchedText(items: FetchResultItem[], format: "text" | "markdown" | "json"): string {
|
|
116
|
+
if (format === "json") {
|
|
117
|
+
return JSON.stringify(items, null, 2);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (format === "markdown") {
|
|
121
|
+
return items
|
|
122
|
+
.map((item) => {
|
|
123
|
+
const lines: string[] = [];
|
|
124
|
+
lines.push(`# ${item.title ?? "Untitled"}`);
|
|
125
|
+
lines.push(`URL: ${item.url}`);
|
|
126
|
+
lines.push("", item.text);
|
|
127
|
+
if (item.links.length > 0) {
|
|
128
|
+
const validLinks = item.links.filter((link) => link.url);
|
|
129
|
+
if (validLinks.length > 0) {
|
|
130
|
+
lines.push("", "## Links");
|
|
131
|
+
for (const link of validLinks) {
|
|
132
|
+
lines.push(`- [${link.title ?? link.url}](${link.url})`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return lines.join("\n");
|
|
137
|
+
})
|
|
138
|
+
.join("\n\n---\n\n");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return items
|
|
142
|
+
.map((item, index) => {
|
|
143
|
+
const lines: string[] = [];
|
|
144
|
+
lines.push(`${index + 1}. ${item.title ?? "Untitled"}`);
|
|
145
|
+
lines.push(` URL: ${item.url}`);
|
|
146
|
+
lines.push(` ${item.text}`);
|
|
147
|
+
if (item.links.length > 0) {
|
|
148
|
+
const validLinks = item.links.filter((link) => link.url);
|
|
149
|
+
if (validLinks.length > 0) {
|
|
150
|
+
lines.push(" Links:");
|
|
151
|
+
for (const link of validLinks) {
|
|
152
|
+
lines.push(` - ${link.title ?? link.url}: ${link.url}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (item.images.length > 0) {
|
|
157
|
+
lines.push(" Images:");
|
|
158
|
+
for (const img of item.images) {
|
|
159
|
+
lines.push(` - ${img.url}${typeof img.width === "number" && typeof img.height === "number" ? ` (${img.width}x${img.height})` : ""}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return lines.join("\n");
|
|
163
|
+
})
|
|
164
|
+
.join("\n\n");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export const cozeWebFetchTool: ToolDefinition = {
|
|
168
|
+
name: "coze_web_fetch",
|
|
169
|
+
label: "Coze Web Fetch",
|
|
170
|
+
description:
|
|
171
|
+
"Fetch and extract structured content from web pages or documents through Coze.",
|
|
172
|
+
parameters: FetchToolSchema,
|
|
173
|
+
async execute(_toolCallId, rawParams) {
|
|
174
|
+
const params = rawParams as Static<typeof FetchToolSchema>;
|
|
175
|
+
const urls = typeof params.urls === "string" ? [params.urls] : params.urls;
|
|
176
|
+
const textOnly = params.textOnly === true;
|
|
177
|
+
console.log("[coze_web_fetch] urls=%s textOnly=%s format=%s", urls.join(", "), textOnly, params.format ?? "text");
|
|
178
|
+
try {
|
|
179
|
+
const items = await fetchContent(urls, textOnly);
|
|
180
|
+
console.log("[coze_web_fetch] done, %d pages fetched", items.length);
|
|
181
|
+
const format = params.format ?? "text";
|
|
182
|
+
return {
|
|
183
|
+
content: [{ type: "text", text: renderFetchedText(items, format) }],
|
|
184
|
+
details: { urls, format, count: items.length },
|
|
185
|
+
};
|
|
186
|
+
} catch (error) {
|
|
187
|
+
const message = formatCozeError(error);
|
|
188
|
+
console.error("[coze_web_fetch] error:", message);
|
|
189
|
+
return {
|
|
190
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
191
|
+
details: { error: true, message },
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
};
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { type Static, Type } from "@sinclair/typebox";
|
|
2
|
+
import type { ToolDefinition } from "@mariozechner/pi-coding-agent";
|
|
3
|
+
import { SearchClient, type ImageItem, type SearchResponse, type WebItem } from "coze-coding-dev-sdk";
|
|
4
|
+
import { formatCozeError } from "../common/format-coze-error.js";
|
|
5
|
+
|
|
6
|
+
type SearchResultItem = {
|
|
7
|
+
title: string;
|
|
8
|
+
url?: string;
|
|
9
|
+
siteName?: string;
|
|
10
|
+
snippet?: string;
|
|
11
|
+
content?: string;
|
|
12
|
+
publishTime?: string;
|
|
13
|
+
imageUrl?: string;
|
|
14
|
+
imageWidth?: number;
|
|
15
|
+
imageHeight?: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type SearchResult = {
|
|
19
|
+
query: string;
|
|
20
|
+
type: "web" | "image";
|
|
21
|
+
summary?: string;
|
|
22
|
+
items: SearchResultItem[];
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function mapWebItem(item: WebItem): SearchResultItem {
|
|
26
|
+
return {
|
|
27
|
+
title: item.title,
|
|
28
|
+
url: item.url,
|
|
29
|
+
siteName: item.site_name,
|
|
30
|
+
snippet: item.snippet,
|
|
31
|
+
content: item.content,
|
|
32
|
+
publishTime: item.publish_time,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function mapImageItem(item: ImageItem): SearchResultItem {
|
|
37
|
+
return {
|
|
38
|
+
title: item.title ?? "Untitled",
|
|
39
|
+
url: item.url,
|
|
40
|
+
siteName: item.site_name,
|
|
41
|
+
publishTime: item.publish_time,
|
|
42
|
+
imageUrl: item.image?.url,
|
|
43
|
+
imageWidth: item.image?.width,
|
|
44
|
+
imageHeight: item.image?.height,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
type SearchInput = {
|
|
49
|
+
query: string;
|
|
50
|
+
type?: "web" | "image";
|
|
51
|
+
count?: number;
|
|
52
|
+
timeRange?: string;
|
|
53
|
+
sites?: string;
|
|
54
|
+
blockHosts?: string;
|
|
55
|
+
needSummary?: boolean;
|
|
56
|
+
needContent?: boolean;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
async function searchWeb(input: SearchInput): Promise<SearchResult> {
|
|
60
|
+
const client = new SearchClient();
|
|
61
|
+
const type = input.type ?? "web";
|
|
62
|
+
const count = input.count ?? 10;
|
|
63
|
+
let response: SearchResponse;
|
|
64
|
+
|
|
65
|
+
if (type === "image") {
|
|
66
|
+
response = await client.imageSearch(input.query, count);
|
|
67
|
+
return {
|
|
68
|
+
query: input.query,
|
|
69
|
+
type,
|
|
70
|
+
items: (response.image_items ?? []).map(mapImageItem),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const hasAdvanced = Boolean(input.timeRange || input.sites || input.blockHosts || input.needContent);
|
|
75
|
+
if (hasAdvanced) {
|
|
76
|
+
response = await client.advancedSearch(input.query, {
|
|
77
|
+
searchType: "web",
|
|
78
|
+
count,
|
|
79
|
+
timeRange: input.timeRange,
|
|
80
|
+
sites: input.sites,
|
|
81
|
+
blockHosts: input.blockHosts,
|
|
82
|
+
needSummary: input.needSummary,
|
|
83
|
+
needContent: input.needContent,
|
|
84
|
+
});
|
|
85
|
+
} else {
|
|
86
|
+
response = await client.webSearch(input.query, count, input.needSummary);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
query: input.query,
|
|
91
|
+
type,
|
|
92
|
+
summary: response.summary,
|
|
93
|
+
items: (response.web_items ?? []).map(mapWebItem),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const SearchToolSchema = Type.Object(
|
|
98
|
+
{
|
|
99
|
+
query: Type.String({ description: "Search query." }),
|
|
100
|
+
type: Type.Optional(
|
|
101
|
+
Type.Unsafe<"web" | "image">({
|
|
102
|
+
type: "string",
|
|
103
|
+
enum: ["web", "image"],
|
|
104
|
+
description: "Search type. Defaults to web.",
|
|
105
|
+
}),
|
|
106
|
+
),
|
|
107
|
+
count: Type.Optional(
|
|
108
|
+
Type.Integer({
|
|
109
|
+
description: "Number of results to return.",
|
|
110
|
+
minimum: 1,
|
|
111
|
+
maximum: 20,
|
|
112
|
+
}),
|
|
113
|
+
),
|
|
114
|
+
timeRange: Type.Optional(
|
|
115
|
+
Type.Unsafe<"1d" | "1w" | "1m">({
|
|
116
|
+
type: "string",
|
|
117
|
+
enum: ["1d", "1w", "1m"],
|
|
118
|
+
description: "Recency filter for web search.",
|
|
119
|
+
}),
|
|
120
|
+
),
|
|
121
|
+
sites: Type.Optional(
|
|
122
|
+
Type.String({ description: "Comma-separated domains to include." }),
|
|
123
|
+
),
|
|
124
|
+
blockHosts: Type.Optional(
|
|
125
|
+
Type.String({ description: "Comma-separated domains to exclude." }),
|
|
126
|
+
),
|
|
127
|
+
needSummary: Type.Optional(
|
|
128
|
+
Type.Boolean({ description: "Whether to include Coze summary output." }),
|
|
129
|
+
),
|
|
130
|
+
needContent: Type.Optional(
|
|
131
|
+
Type.Boolean({ description: "Whether to include extracted page content." }),
|
|
132
|
+
),
|
|
133
|
+
},
|
|
134
|
+
{ additionalProperties: false },
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
function buildSearchText(
|
|
138
|
+
result: SearchResult,
|
|
139
|
+
options: { includeContent: boolean },
|
|
140
|
+
): string {
|
|
141
|
+
const lines = [`Coze web search: ${result.query}`];
|
|
142
|
+
if (result.summary) {
|
|
143
|
+
lines.push("", `Summary: ${result.summary}`);
|
|
144
|
+
}
|
|
145
|
+
lines.push("", `Results (${result.items.length})`);
|
|
146
|
+
for (const [index, item] of result.items.entries()) {
|
|
147
|
+
lines.push(`${index + 1}. ${item.title}`);
|
|
148
|
+
if (item.url) {
|
|
149
|
+
lines.push(` URL: ${item.url}`);
|
|
150
|
+
}
|
|
151
|
+
if (item.imageUrl) {
|
|
152
|
+
lines.push(` Image: ${item.imageUrl}`);
|
|
153
|
+
}
|
|
154
|
+
if (item.siteName) {
|
|
155
|
+
lines.push(` Source: ${item.siteName}`);
|
|
156
|
+
}
|
|
157
|
+
if (item.publishTime) {
|
|
158
|
+
lines.push(` Published: ${item.publishTime}`);
|
|
159
|
+
}
|
|
160
|
+
if (item.snippet) {
|
|
161
|
+
lines.push(` ${item.snippet}`);
|
|
162
|
+
}
|
|
163
|
+
if (options.includeContent && item.content) {
|
|
164
|
+
lines.push(" Content:");
|
|
165
|
+
lines.push(` ${item.content}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return lines.join("\n");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export const cozeWebSearchTool: ToolDefinition = {
|
|
172
|
+
name: "coze_web_search",
|
|
173
|
+
label: "Coze Web Search",
|
|
174
|
+
description:
|
|
175
|
+
"Search the web or images through Coze. Supports summaries, recency filters, and site restrictions.",
|
|
176
|
+
parameters: SearchToolSchema,
|
|
177
|
+
async execute(_toolCallId, rawParams) {
|
|
178
|
+
const params = rawParams as Static<typeof SearchToolSchema>;
|
|
179
|
+
console.log("[coze_web_search] query=%s type=%s count=%s", params.query, params.type ?? "web", params.count ?? 10);
|
|
180
|
+
try {
|
|
181
|
+
const result = await searchWeb(params);
|
|
182
|
+
console.log("[coze_web_search] done, %d results", result.items.length);
|
|
183
|
+
return {
|
|
184
|
+
content: [
|
|
185
|
+
{
|
|
186
|
+
type: "text",
|
|
187
|
+
text: buildSearchText(result, { includeContent: params.needContent === true }),
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
details: {
|
|
191
|
+
query: result.query,
|
|
192
|
+
type: result.type,
|
|
193
|
+
summary: result.summary,
|
|
194
|
+
count: result.items.length,
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
} catch (error) {
|
|
198
|
+
const message = formatCozeError(error);
|
|
199
|
+
console.error("[coze_web_search] error:", message);
|
|
200
|
+
return {
|
|
201
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
202
|
+
details: { error: true, message },
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export const paramsSchema = {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
port: {
|
|
14
|
+
type: 'number',
|
|
15
|
+
default: 5000,
|
|
16
|
+
minimum: 1024,
|
|
17
|
+
maximum: 65535,
|
|
18
|
+
description: 'Dashboard server port',
|
|
19
|
+
},
|
|
20
|
+
workspaceDir: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
default: '/workspace/workspace',
|
|
23
|
+
description: 'Workspace directory path',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
required: [],
|
|
27
|
+
additionalProperties: false,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const config = {
|
|
31
|
+
description:
|
|
32
|
+
'Pi Agent:`coze init ${COZE_WORKSPACE_PATH} --template pi-agent`\n' +
|
|
33
|
+
'- 适用:基于 pi-agent-core 的 AI Agent 应用\n' +
|
|
34
|
+
'- 支持飞书、微信等多渠道接入\n' +
|
|
35
|
+
'- 内置 Dashboard 管理面板\n' +
|
|
36
|
+
'- 使用 TypeScript + Express + Vite',
|
|
37
|
+
paramsSchema,
|
|
38
|
+
|
|
39
|
+
defaultParams: {
|
|
40
|
+
port: 5000,
|
|
41
|
+
workspaceDir: '/workspace/workspace',
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default config;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { createMemoryConfigStore } from "../src/dashboard/config-store.js";
|
|
4
|
+
import { runConfigCommand, type CliContext } from "../src/cli.js";
|
|
5
|
+
|
|
6
|
+
function createTestContext(initial: Record<string, unknown> = {}) {
|
|
7
|
+
const store = createMemoryConfigStore(initial);
|
|
8
|
+
const logs: string[] = [];
|
|
9
|
+
const errors: string[] = [];
|
|
10
|
+
const ctx: CliContext = {
|
|
11
|
+
configStore: store,
|
|
12
|
+
log: (msg) => logs.push(msg),
|
|
13
|
+
error: (msg) => errors.push(msg),
|
|
14
|
+
};
|
|
15
|
+
return { store, logs, errors, ctx };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
test("runConfigCommand returns false for non-config commands", () => {
|
|
19
|
+
const { ctx } = createTestContext();
|
|
20
|
+
assert.equal(runConfigCommand([], ctx), false);
|
|
21
|
+
assert.equal(runConfigCommand(["start"], ctx), false);
|
|
22
|
+
assert.equal(runConfigCommand(["run"], ctx), false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("config set writes a string value at a nested path", () => {
|
|
26
|
+
const { store, logs, ctx } = createTestContext({ channels: { feishu: { appId: "old" } } });
|
|
27
|
+
runConfigCommand(["config", "set", "channels.feishu.appId", "cli_new"], ctx);
|
|
28
|
+
const snapshot = store.snapshot();
|
|
29
|
+
assert.equal((snapshot.channels as Record<string, Record<string, unknown>>).feishu.appId, "cli_new");
|
|
30
|
+
assert.ok(logs[0]?.includes("cli_new"));
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("config set coerces boolean values", () => {
|
|
34
|
+
const { store, ctx } = createTestContext({ channels: { feishu: { enabled: false } } });
|
|
35
|
+
runConfigCommand(["config", "set", "channels.feishu.enabled", "true"], ctx);
|
|
36
|
+
const snapshot = store.snapshot();
|
|
37
|
+
assert.equal((snapshot.channels as Record<string, Record<string, unknown>>).feishu.enabled, true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("config set coerces numeric values", () => {
|
|
41
|
+
const { store, ctx } = createTestContext({ models: { maxTokens: 8192 } });
|
|
42
|
+
runConfigCommand(["config", "set", "models.maxTokens", "16384"], ctx);
|
|
43
|
+
const snapshot = store.snapshot();
|
|
44
|
+
assert.equal((snapshot.models as Record<string, unknown>).maxTokens, 16384);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("config set creates intermediate objects for missing paths", () => {
|
|
48
|
+
const { store, ctx } = createTestContext({});
|
|
49
|
+
runConfigCommand(["config", "set", "channels.feishu.appId", "new-id"], ctx);
|
|
50
|
+
const snapshot = store.snapshot();
|
|
51
|
+
assert.equal(
|
|
52
|
+
(snapshot.channels as Record<string, Record<string, unknown>>).feishu.appId,
|
|
53
|
+
"new-id",
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("config set handles array index paths", () => {
|
|
58
|
+
const { store, ctx } = createTestContext({ items: [{ name: "a" }, { name: "b" }] });
|
|
59
|
+
runConfigCommand(["config", "set", "items.1.name", "updated"], ctx);
|
|
60
|
+
const snapshot = store.snapshot();
|
|
61
|
+
assert.equal((snapshot.items as Array<Record<string, unknown>>)[1]!.name, "updated");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("config get returns a string value", () => {
|
|
65
|
+
const { logs, ctx } = createTestContext({ channels: { feishu: { appId: "test-id" } } });
|
|
66
|
+
runConfigCommand(["config", "get", "channels.feishu.appId"], ctx);
|
|
67
|
+
assert.equal(logs[0], "test-id");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("config get returns a formatted object", () => {
|
|
71
|
+
const { logs, ctx } = createTestContext({ channels: { feishu: { appId: "x", enabled: true } } });
|
|
72
|
+
runConfigCommand(["config", "get", "channels.feishu"], ctx);
|
|
73
|
+
const parsed = JSON.parse(logs[0]!) as Record<string, unknown>;
|
|
74
|
+
assert.equal(parsed.appId, "x");
|
|
75
|
+
assert.equal(parsed.enabled, true);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("config get reports error for missing key", () => {
|
|
79
|
+
const { errors, ctx } = createTestContext({});
|
|
80
|
+
runConfigCommand(["config", "get", "missing.key"], ctx);
|
|
81
|
+
assert.ok(errors[0]?.includes("not found"));
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("config list outputs the full config", () => {
|
|
85
|
+
const original = { channels: { feishu: { enabled: true } } };
|
|
86
|
+
const { logs, ctx } = createTestContext(original);
|
|
87
|
+
runConfigCommand(["config", "list"], ctx);
|
|
88
|
+
const parsed = JSON.parse(logs[0]!) as Record<string, unknown>;
|
|
89
|
+
assert.deepEqual(parsed, original);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("config delete removes a key", () => {
|
|
93
|
+
const { store, logs, ctx } = createTestContext({
|
|
94
|
+
channels: { feishu: { appId: "x", enabled: true } },
|
|
95
|
+
});
|
|
96
|
+
runConfigCommand(["config", "delete", "channels.feishu.appId"], ctx);
|
|
97
|
+
const snapshot = store.snapshot();
|
|
98
|
+
const feishu = (snapshot.channels as Record<string, Record<string, unknown>>).feishu;
|
|
99
|
+
assert.equal("appId" in feishu, false);
|
|
100
|
+
assert.equal(feishu.enabled, true);
|
|
101
|
+
assert.ok(logs[0]?.includes("Deleted"));
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("config delete reports error for missing key", () => {
|
|
105
|
+
const { errors, ctx } = createTestContext({});
|
|
106
|
+
runConfigCommand(["config", "delete", "missing.key"], ctx);
|
|
107
|
+
assert.ok(errors[0]?.includes("not found"));
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("config with no subcommand prints usage", () => {
|
|
111
|
+
const { logs, ctx } = createTestContext();
|
|
112
|
+
const handled = runConfigCommand(["config"], ctx);
|
|
113
|
+
assert.equal(handled, true);
|
|
114
|
+
assert.ok(logs.some((l) => l.includes("Usage")));
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("config with unknown subcommand prints error and usage", () => {
|
|
118
|
+
const { errors, logs, ctx } = createTestContext();
|
|
119
|
+
const handled = runConfigCommand(["config", "badcmd"], ctx);
|
|
120
|
+
assert.equal(handled, true);
|
|
121
|
+
assert.ok(errors[0]?.includes("Unknown"));
|
|
122
|
+
assert.ok(logs.some((l) => l.includes("Usage")));
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("config set with missing arguments prints usage", () => {
|
|
126
|
+
const { errors, ctx } = createTestContext();
|
|
127
|
+
runConfigCommand(["config", "set", "key"], ctx);
|
|
128
|
+
assert.ok(errors[0]?.includes("Usage"));
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("config set coerces null value", () => {
|
|
132
|
+
const { store, ctx } = createTestContext({ key: "value" });
|
|
133
|
+
runConfigCommand(["config", "set", "key", "null"], ctx);
|
|
134
|
+
const snapshot = store.snapshot();
|
|
135
|
+
assert.equal(snapshot.key, null);
|
|
136
|
+
});
|