@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,219 @@
|
|
|
1
|
+
export type ChannelName = "feishu" | "wechat" | "dashboard";
|
|
2
|
+
|
|
3
|
+
export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
4
|
+
|
|
5
|
+
export type AgentMode = "mock" | "pi";
|
|
6
|
+
|
|
7
|
+
export interface BotMessage {
|
|
8
|
+
// A channel package must normalize platform-specific payloads into this shape
|
|
9
|
+
// before handing control to the app runtime.
|
|
10
|
+
channel: ChannelName;
|
|
11
|
+
conversationId: string;
|
|
12
|
+
senderId: string;
|
|
13
|
+
messageId: string;
|
|
14
|
+
text: string;
|
|
15
|
+
isDirectMessage: boolean;
|
|
16
|
+
mentions: string[];
|
|
17
|
+
threadId?: string;
|
|
18
|
+
raw?: unknown;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface BotReply {
|
|
22
|
+
text: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type ChannelReply = string | BotReply | null | void;
|
|
26
|
+
|
|
27
|
+
export interface ChannelStreamHandlers {
|
|
28
|
+
onMeta?(meta: { sessionKey: string }): void;
|
|
29
|
+
onDelta?(delta: string): void;
|
|
30
|
+
onError?(error: string): void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ChannelHandler {
|
|
34
|
+
onMessage?(message: BotMessage): Promise<ChannelReply> | ChannelReply;
|
|
35
|
+
onStreamMessage?(
|
|
36
|
+
message: BotMessage,
|
|
37
|
+
handlers: ChannelStreamHandlers
|
|
38
|
+
): Promise<ChannelReply> | ChannelReply;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ChannelInstance {
|
|
42
|
+
name: ChannelName;
|
|
43
|
+
start(): Promise<void>;
|
|
44
|
+
stop(): Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ChannelTransport<TOutgoingMessage> {
|
|
48
|
+
start?(): Promise<void> | void;
|
|
49
|
+
stop?(): Promise<void> | void;
|
|
50
|
+
send?(message: TOutgoingMessage): Promise<void> | void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface RoutingGateConfig {
|
|
54
|
+
requireMention?: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface SessionStore<TValue> {
|
|
58
|
+
get(key: string): TValue | undefined;
|
|
59
|
+
set(key: string, value: TValue): TValue;
|
|
60
|
+
has(key: string): boolean;
|
|
61
|
+
delete(key: string): boolean;
|
|
62
|
+
clear(): void;
|
|
63
|
+
entries(): Array<[string, TValue]>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface AgentRuntime {
|
|
67
|
+
// App projects own the agent runtime on purpose. We keep it out of channel/core
|
|
68
|
+
// so future work can use raw pi-coding-agent APIs directly.
|
|
69
|
+
run(message: BotMessage): Promise<string>;
|
|
70
|
+
dispose(): Promise<void>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface AgentRuntimeConfig {
|
|
74
|
+
mode: AgentMode;
|
|
75
|
+
provider?: string;
|
|
76
|
+
model?: string;
|
|
77
|
+
configPath?: string;
|
|
78
|
+
thinkingLevel?: ThinkingLevel;
|
|
79
|
+
baseUrl?: string;
|
|
80
|
+
cwd?: string;
|
|
81
|
+
agentDir?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface BotChannelConfig {
|
|
85
|
+
enabled: boolean;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface FeishuBotChannelConfig extends BotChannelConfig {
|
|
89
|
+
appId?: string;
|
|
90
|
+
appSecret?: string;
|
|
91
|
+
domain?: string;
|
|
92
|
+
encryptKey?: string;
|
|
93
|
+
verificationToken?: string;
|
|
94
|
+
thinkingReaction?: {
|
|
95
|
+
enabled?: boolean;
|
|
96
|
+
emojiType?: string;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface WechatBotChannelConfig extends BotChannelConfig {
|
|
101
|
+
implementation?: "mock";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface BotRoutingConfig {
|
|
105
|
+
feishuGroupRequireMention: boolean;
|
|
106
|
+
wechatGroupRequireMention: boolean;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface BotAppConfig {
|
|
110
|
+
appName: string;
|
|
111
|
+
configRoot?: Record<string, unknown>;
|
|
112
|
+
agent: AgentRuntimeConfig;
|
|
113
|
+
routing: BotRoutingConfig;
|
|
114
|
+
channels: {
|
|
115
|
+
feishu?: FeishuBotChannelConfig;
|
|
116
|
+
wechat?: WechatBotChannelConfig;
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function normalizeReply(reply: ChannelReply): BotReply | null {
|
|
121
|
+
if (reply == null) return null;
|
|
122
|
+
if (typeof reply === "string") return { text: reply };
|
|
123
|
+
if (typeof reply === "object" && typeof reply.text === "string") {
|
|
124
|
+
return { text: reply.text };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
throw new TypeError("Reply must be a string, an object with a text field, or null.");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function assertBotMessage(message: unknown): asserts message is BotMessage {
|
|
131
|
+
if (!message || typeof message !== "object") {
|
|
132
|
+
throw new TypeError("BotMessage must be an object.");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const requiredFields: Array<keyof BotMessage> = [
|
|
136
|
+
"channel",
|
|
137
|
+
"conversationId",
|
|
138
|
+
"senderId",
|
|
139
|
+
"messageId",
|
|
140
|
+
"text",
|
|
141
|
+
"isDirectMessage",
|
|
142
|
+
"mentions"
|
|
143
|
+
];
|
|
144
|
+
|
|
145
|
+
for (const field of requiredFields) {
|
|
146
|
+
if (!(field in message)) {
|
|
147
|
+
throw new TypeError(`BotMessage is missing required field: ${field}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function getSessionKey(message: BotMessage): string {
|
|
153
|
+
assertBotMessage(message);
|
|
154
|
+
|
|
155
|
+
// Session routing is deterministic and host-controlled.
|
|
156
|
+
// The model never decides where a message should be threaded.
|
|
157
|
+
if (message.isDirectMessage) {
|
|
158
|
+
return `${message.channel}:dm:${message.senderId}`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (message.threadId) {
|
|
162
|
+
return `${message.channel}:thread:${message.conversationId}:${message.threadId}`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return `${message.channel}:group:${message.conversationId}`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function shouldHandleGroupMessage(
|
|
169
|
+
message: BotMessage,
|
|
170
|
+
routing: RoutingGateConfig = {}
|
|
171
|
+
): boolean {
|
|
172
|
+
assertBotMessage(message);
|
|
173
|
+
|
|
174
|
+
// Mention gating lives in the host/channel layer, not in the model prompt.
|
|
175
|
+
if (message.isDirectMessage) return true;
|
|
176
|
+
if (!routing.requireMention) return true;
|
|
177
|
+
|
|
178
|
+
return Array.isArray(message.mentions) && message.mentions.length > 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function createInMemorySessionStore<TValue>(): SessionStore<TValue> {
|
|
182
|
+
const sessions = new Map<string, TValue>();
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
get(key) {
|
|
186
|
+
return sessions.get(key);
|
|
187
|
+
},
|
|
188
|
+
set(key, value) {
|
|
189
|
+
sessions.set(key, value);
|
|
190
|
+
return value;
|
|
191
|
+
},
|
|
192
|
+
has(key) {
|
|
193
|
+
return sessions.has(key);
|
|
194
|
+
},
|
|
195
|
+
delete(key) {
|
|
196
|
+
return sessions.delete(key);
|
|
197
|
+
},
|
|
198
|
+
clear() {
|
|
199
|
+
sessions.clear();
|
|
200
|
+
},
|
|
201
|
+
entries() {
|
|
202
|
+
return Array.from(sessions.entries());
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function createBotMessage(overrides: Partial<BotMessage> = {}): BotMessage {
|
|
208
|
+
return {
|
|
209
|
+
channel: overrides.channel ?? "feishu",
|
|
210
|
+
conversationId: overrides.conversationId ?? "conversation-1",
|
|
211
|
+
senderId: overrides.senderId ?? "user-1",
|
|
212
|
+
messageId: overrides.messageId ?? `msg-${Date.now()}`,
|
|
213
|
+
text: overrides.text ?? "",
|
|
214
|
+
isDirectMessage: overrides.isDirectMessage ?? true,
|
|
215
|
+
mentions: overrides.mentions ?? [],
|
|
216
|
+
threadId: overrides.threadId,
|
|
217
|
+
raw: overrides.raw
|
|
218
|
+
};
|
|
219
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getConfigStore,
|
|
3
|
+
type ConfigSource,
|
|
4
|
+
} from "../config-store.js";
|
|
5
|
+
import { updateString, updateBool } from "../../config.js";
|
|
6
|
+
|
|
7
|
+
export type DashboardChannelsResponse = {
|
|
8
|
+
routing: {
|
|
9
|
+
feishuGroupRequireMention: boolean;
|
|
10
|
+
wechatGroupRequireMention: boolean;
|
|
11
|
+
};
|
|
12
|
+
feishu: {
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
requireMention?: boolean;
|
|
15
|
+
appId?: string;
|
|
16
|
+
domain?: string;
|
|
17
|
+
encryptKey?: string;
|
|
18
|
+
verificationToken?: string;
|
|
19
|
+
appSecret?: string;
|
|
20
|
+
thinkingReaction?: {
|
|
21
|
+
enabled?: boolean;
|
|
22
|
+
emojiType?: string;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
wechat: {
|
|
26
|
+
enabled: boolean;
|
|
27
|
+
requireMention?: boolean;
|
|
28
|
+
implementation?: string;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type DashboardChannelsSaveRequest = {
|
|
33
|
+
channels: DashboardChannelsResponse;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export function readChannelsResponse(args: ConfigSource): DashboardChannelsResponse {
|
|
37
|
+
const root = getConfigStore(args).read();
|
|
38
|
+
const cfg = root as {
|
|
39
|
+
channels?: { feishu?: Record<string, unknown>; wechat?: Record<string, unknown> };
|
|
40
|
+
};
|
|
41
|
+
const feishuRaw = cfg.channels?.feishu ?? {};
|
|
42
|
+
const wechatRaw = cfg.channels?.wechat ?? {};
|
|
43
|
+
|
|
44
|
+
const feishuEnabled = Boolean((feishuRaw as { enabled?: unknown }).enabled);
|
|
45
|
+
const wechatEnabled = Boolean((wechatRaw as { enabled?: unknown }).enabled);
|
|
46
|
+
const feishuRequireMention = Boolean((feishuRaw as { requireMention?: unknown }).requireMention);
|
|
47
|
+
const wechatRequireMention = Boolean((wechatRaw as { requireMention?: unknown }).requireMention);
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
routing: {
|
|
51
|
+
feishuGroupRequireMention: feishuRequireMention,
|
|
52
|
+
wechatGroupRequireMention: wechatRequireMention,
|
|
53
|
+
},
|
|
54
|
+
feishu: {
|
|
55
|
+
enabled: feishuEnabled,
|
|
56
|
+
requireMention: feishuRequireMention,
|
|
57
|
+
appId: (feishuRaw as { appId?: string }).appId,
|
|
58
|
+
domain: (feishuRaw as { domain?: string }).domain,
|
|
59
|
+
appSecret: (feishuRaw as { appSecret?: string }).appSecret,
|
|
60
|
+
encryptKey: (feishuRaw as { encryptKey?: string }).encryptKey,
|
|
61
|
+
verificationToken: (feishuRaw as { verificationToken?: string }).verificationToken,
|
|
62
|
+
thinkingReaction: (feishuRaw as { thinkingReaction?: unknown }).thinkingReaction as
|
|
63
|
+
| { enabled?: boolean; emojiType?: string }
|
|
64
|
+
| undefined,
|
|
65
|
+
},
|
|
66
|
+
wechat: {
|
|
67
|
+
enabled: wechatEnabled,
|
|
68
|
+
requireMention: wechatRequireMention,
|
|
69
|
+
implementation: (wechatRaw as { implementation?: string }).implementation,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function saveChannelsRequest(args: ConfigSource & { body: DashboardChannelsSaveRequest }) {
|
|
75
|
+
const { channels } = args.body;
|
|
76
|
+
const configStore = getConfigStore(args);
|
|
77
|
+
const root = configStore.read();
|
|
78
|
+
|
|
79
|
+
updateBool(root, ["channels", "feishu", "enabled"], channels.feishu.enabled);
|
|
80
|
+
updateBool(root, ["channels", "feishu", "requireMention"], channels.feishu.requireMention);
|
|
81
|
+
updateString(root, ["channels", "feishu", "appId"], channels.feishu.appId);
|
|
82
|
+
updateString(root, ["channels", "feishu", "domain"], channels.feishu.domain);
|
|
83
|
+
updateString(root, ["channels", "feishu", "appSecret"], channels.feishu.appSecret);
|
|
84
|
+
updateString(root, ["channels", "feishu", "encryptKey"], channels.feishu.encryptKey);
|
|
85
|
+
updateString(root, ["channels", "feishu", "verificationToken"], channels.feishu.verificationToken);
|
|
86
|
+
if (channels.feishu.thinkingReaction) {
|
|
87
|
+
updateBool(
|
|
88
|
+
root,
|
|
89
|
+
["channels", "feishu", "thinkingReaction", "enabled"],
|
|
90
|
+
channels.feishu.thinkingReaction.enabled,
|
|
91
|
+
);
|
|
92
|
+
updateString(
|
|
93
|
+
root,
|
|
94
|
+
["channels", "feishu", "thinkingReaction", "emojiType"],
|
|
95
|
+
channels.feishu.thinkingReaction.emojiType,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
updateBool(root, ["channels", "wechat", "enabled"], channels.wechat.enabled);
|
|
100
|
+
updateBool(root, ["channels", "wechat", "requireMention"], channels.wechat.requireMention);
|
|
101
|
+
updateString(root, ["channels", "wechat", "implementation"], channels.wechat.implementation);
|
|
102
|
+
|
|
103
|
+
configStore.write(root);
|
|
104
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getConfigStore,
|
|
3
|
+
type ConfigSource,
|
|
4
|
+
} from "../config-store.js";
|
|
5
|
+
import { ensureObject, readString, updateString } from "../../config.js";
|
|
6
|
+
|
|
7
|
+
export type DashboardModelOption = {
|
|
8
|
+
value: string;
|
|
9
|
+
label: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type DashboardModelsResponse = {
|
|
13
|
+
defaultModel: string;
|
|
14
|
+
options: DashboardModelOption[];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type DashboardModelsSaveRequest = {
|
|
18
|
+
models: {
|
|
19
|
+
defaultModel: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export class ValidationError extends Error {
|
|
24
|
+
statusCode = 400 as const;
|
|
25
|
+
constructor(message: string) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = "ValidationError";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function validateDefaultModel(args: { defaultModel: string; allowed: Set<string> }) {
|
|
32
|
+
if (typeof args.defaultModel !== "string") {
|
|
33
|
+
throw new ValidationError("Invalid payload.");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const defaultModel = args.defaultModel.trim();
|
|
37
|
+
if (!defaultModel) return;
|
|
38
|
+
|
|
39
|
+
const parts = defaultModel.split("/");
|
|
40
|
+
if (parts.length !== 2) {
|
|
41
|
+
throw new ValidationError(`Invalid defaultModel: "${args.defaultModel}". Expected "provider/model".`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const providerId = parts[0] ?? "";
|
|
45
|
+
const modelId = parts[1] ?? "";
|
|
46
|
+
const idPattern = /^[A-Za-z0-9_.-]+$/;
|
|
47
|
+
if (!idPattern.test(providerId) || !idPattern.test(modelId)) {
|
|
48
|
+
throw new ValidationError(`Invalid defaultModel: "${args.defaultModel}". Expected "provider/model".`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!args.allowed.has(defaultModel)) {
|
|
52
|
+
throw new ValidationError(`Invalid defaultModel: "${args.defaultModel}" is not in the configured model list.`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function readModelsResponse(args: ConfigSource): DashboardModelsResponse {
|
|
57
|
+
const root = getConfigStore(args).read();
|
|
58
|
+
const agents = ensureObject(root.agents);
|
|
59
|
+
const defaults = ensureObject(agents.defaults);
|
|
60
|
+
const defaultModel = readString(ensureObject(defaults.model).primary);
|
|
61
|
+
const providersRoot = ensureObject(ensureObject(root.models).providers);
|
|
62
|
+
|
|
63
|
+
const options = Object.entries(providersRoot).flatMap(([providerId, providerValue]) => {
|
|
64
|
+
const provider = ensureObject(providerValue);
|
|
65
|
+
const rawModels = Array.isArray(provider.models) ? provider.models : [];
|
|
66
|
+
return rawModels.flatMap((rawModel) => {
|
|
67
|
+
const model = ensureObject(rawModel);
|
|
68
|
+
const id = readString(model.id);
|
|
69
|
+
if (!id) {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
const name = readString(model.name) || id;
|
|
73
|
+
return [
|
|
74
|
+
{
|
|
75
|
+
value: `${providerId}/${id}`,
|
|
76
|
+
label: `${providerId} / ${name}`,
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
defaultModel,
|
|
84
|
+
options,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function saveModelsRequest(args: ConfigSource & { body: DashboardModelsSaveRequest }) {
|
|
89
|
+
const configStore = getConfigStore(args);
|
|
90
|
+
const root = configStore.read();
|
|
91
|
+
const current = readModelsResponse({ configStore });
|
|
92
|
+
const allowed = new Set(current.options.map((option) => option.value));
|
|
93
|
+
validateDefaultModel({ defaultModel: args.body.models.defaultModel, allowed });
|
|
94
|
+
|
|
95
|
+
updateString(root, ["agents", "defaults", "model", "primary"], args.body.models.defaultModel);
|
|
96
|
+
|
|
97
|
+
configStore.write(root);
|
|
98
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type DashboardOverviewResponse = {
|
|
2
|
+
appName: string;
|
|
3
|
+
botStatus: string;
|
|
4
|
+
dashboardUrl: string;
|
|
5
|
+
agentMode: string;
|
|
6
|
+
workspaceDir: string;
|
|
7
|
+
agentDir: string;
|
|
8
|
+
enabledChannels: Array<{ id: string; enabled: boolean }>;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function buildOverviewResponse(args: {
|
|
12
|
+
dashboardUrl: string;
|
|
13
|
+
dashboardStarted: boolean;
|
|
14
|
+
appName: string;
|
|
15
|
+
agentMode: string;
|
|
16
|
+
workspaceDir: string;
|
|
17
|
+
agentDir: string;
|
|
18
|
+
enabledChannels: { feishu: boolean; wechat: boolean };
|
|
19
|
+
}): DashboardOverviewResponse {
|
|
20
|
+
const { dashboardUrl, dashboardStarted } = args;
|
|
21
|
+
return {
|
|
22
|
+
appName: args.appName,
|
|
23
|
+
botStatus: dashboardStarted ? "running" : "stopped",
|
|
24
|
+
dashboardUrl,
|
|
25
|
+
agentMode: args.agentMode,
|
|
26
|
+
workspaceDir: args.workspaceDir || "Not set",
|
|
27
|
+
agentDir: args.agentDir || "Not set",
|
|
28
|
+
enabledChannels: [
|
|
29
|
+
{ id: "feishu", enabled: Boolean(args.enabledChannels.feishu) },
|
|
30
|
+
{ id: "wechat", enabled: Boolean(args.enabledChannels.wechat) },
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
export interface ConfigStore {
|
|
4
|
+
read(): Record<string, unknown>;
|
|
5
|
+
write(root: Record<string, unknown>): void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type ConfigSource =
|
|
9
|
+
| {
|
|
10
|
+
configPath: string;
|
|
11
|
+
configStore?: never;
|
|
12
|
+
}
|
|
13
|
+
| {
|
|
14
|
+
configPath?: never;
|
|
15
|
+
configStore: ConfigStore;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function cloneConfig(root: Record<string, unknown>): Record<string, unknown> {
|
|
19
|
+
return JSON.parse(JSON.stringify(root)) as Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function ensureConfigRoot(value: unknown, source: string): Record<string, unknown> {
|
|
23
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
24
|
+
throw new Error(`Invalid config.json shape at ${source}`);
|
|
25
|
+
}
|
|
26
|
+
return value as Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function createFileConfigStore(configPath: string): ConfigStore {
|
|
30
|
+
return {
|
|
31
|
+
read() {
|
|
32
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
33
|
+
return ensureConfigRoot(JSON.parse(raw) as unknown, configPath);
|
|
34
|
+
},
|
|
35
|
+
write(root) {
|
|
36
|
+
writeFileSync(configPath, `${JSON.stringify(root, null, 2)}\n`, "utf-8");
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function createMemoryConfigStore(
|
|
42
|
+
initialRoot: Record<string, unknown>,
|
|
43
|
+
): ConfigStore & { snapshot(): Record<string, unknown> } {
|
|
44
|
+
let current = cloneConfig(initialRoot);
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
read() {
|
|
48
|
+
return cloneConfig(current);
|
|
49
|
+
},
|
|
50
|
+
write(root) {
|
|
51
|
+
current = cloneConfig(root);
|
|
52
|
+
},
|
|
53
|
+
snapshot() {
|
|
54
|
+
return cloneConfig(current);
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function getConfigStore(source: ConfigSource): ConfigStore {
|
|
60
|
+
if (source.configStore) {
|
|
61
|
+
return source.configStore;
|
|
62
|
+
}
|
|
63
|
+
return createFileConfigStore(source.configPath);
|
|
64
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { BotAppConfig } from "../core.js";
|
|
2
|
+
import { createDashboardServer } from "./server.js";
|
|
3
|
+
import type { DashboardServer } from "./types.js";
|
|
4
|
+
import type { PiAgentRuntime } from "../agent.js";
|
|
5
|
+
import {
|
|
6
|
+
createFileConfigStore,
|
|
7
|
+
createMemoryConfigStore,
|
|
8
|
+
type ConfigStore,
|
|
9
|
+
} from "./config-store.js";
|
|
10
|
+
|
|
11
|
+
function createFallbackConfigRoot(botConfig: BotAppConfig): Record<string, unknown> {
|
|
12
|
+
return {
|
|
13
|
+
agents: {
|
|
14
|
+
defaults: {
|
|
15
|
+
model: {
|
|
16
|
+
primary:
|
|
17
|
+
botConfig.agent.provider && botConfig.agent.model
|
|
18
|
+
? `${botConfig.agent.provider}/${botConfig.agent.model}`
|
|
19
|
+
: ""
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
models: {
|
|
24
|
+
providers: {}
|
|
25
|
+
},
|
|
26
|
+
channels: {
|
|
27
|
+
feishu: {
|
|
28
|
+
enabled: botConfig.channels.feishu?.enabled ?? false,
|
|
29
|
+
requireMention: botConfig.routing.feishuGroupRequireMention,
|
|
30
|
+
appId: botConfig.channels.feishu?.appId,
|
|
31
|
+
appSecret: botConfig.channels.feishu?.appSecret,
|
|
32
|
+
domain: botConfig.channels.feishu?.domain,
|
|
33
|
+
encryptKey: botConfig.channels.feishu?.encryptKey,
|
|
34
|
+
verificationToken: botConfig.channels.feishu?.verificationToken,
|
|
35
|
+
thinkingReaction: {
|
|
36
|
+
enabled: botConfig.channels.feishu?.thinkingReaction?.enabled ?? true,
|
|
37
|
+
emojiType: botConfig.channels.feishu?.thinkingReaction?.emojiType
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
wechat: {
|
|
41
|
+
enabled: botConfig.channels.wechat?.enabled ?? false,
|
|
42
|
+
requireMention: botConfig.routing.wechatGroupRequireMention
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function createDashboard(args: {
|
|
49
|
+
botConfig: BotAppConfig;
|
|
50
|
+
channels: { feishu?: unknown; wechat?: unknown };
|
|
51
|
+
agentRuntime: PiAgentRuntime;
|
|
52
|
+
configStore?: ConfigStore;
|
|
53
|
+
}): DashboardServer {
|
|
54
|
+
const configStore =
|
|
55
|
+
args.configStore ??
|
|
56
|
+
(args.botConfig.agent.configPath
|
|
57
|
+
? createFileConfigStore(args.botConfig.agent.configPath)
|
|
58
|
+
: createMemoryConfigStore(args.botConfig.configRoot ?? createFallbackConfigRoot(args.botConfig)));
|
|
59
|
+
|
|
60
|
+
return createDashboardServer({
|
|
61
|
+
runtime: {
|
|
62
|
+
appName: args.botConfig.appName,
|
|
63
|
+
agentMode: args.botConfig.agent.mode,
|
|
64
|
+
workspaceDir: args.botConfig.agent.cwd ?? "",
|
|
65
|
+
agentDir: args.botConfig.agent.agentDir ?? "",
|
|
66
|
+
enabledChannels: {
|
|
67
|
+
feishu: Boolean(args.channels.feishu),
|
|
68
|
+
wechat: Boolean(args.channels.wechat),
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
agentRuntime: args.agentRuntime,
|
|
72
|
+
configStore,
|
|
73
|
+
});
|
|
74
|
+
}
|