@vibe-forge/core 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-forge/core",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "imports": {
5
5
  "#~/*.js": {
6
6
  "__vibe-forge__": {
@@ -22,6 +22,15 @@
22
22
  "require": "./dist/index.js"
23
23
  }
24
24
  },
25
+ "./channel": {
26
+ "__vibe-forge__": {
27
+ "default": "./src/channel.ts"
28
+ },
29
+ "default": {
30
+ "import": "./dist/channel.mjs",
31
+ "require": "./dist/channel.js"
32
+ }
33
+ },
25
34
  "./utils/*": {
26
35
  "__vibe-forge__": {
27
36
  "default": "./src/utils/*.ts"
@@ -40,6 +49,15 @@
40
49
  "require": "./dist/controllers/task/index.js"
41
50
  }
42
51
  },
52
+ "./controllers/benchmark": {
53
+ "__vibe-forge__": {
54
+ "default": "./src/controllers/benchmark/index.ts"
55
+ },
56
+ "default": {
57
+ "import": "./dist/controllers/benchmark/index.mjs",
58
+ "require": "./dist/controllers/benchmark/index.js"
59
+ }
60
+ },
43
61
  "./schema": {
44
62
  "__vibe-forge__": {
45
63
  "default": "./src/schema.ts"
@@ -10,7 +10,7 @@ export type AdapterOutputEvent =
10
10
  | { type: 'init'; data: SessionInitInfo }
11
11
  | { type: 'summary'; data: SessionSummaryInfo }
12
12
  | { type: 'message'; data: ChatMessage }
13
- | { type: 'exit'; data: { exitCode: number | null; stderr?: string } }
13
+ | { type: 'exit'; data: { exitCode?: number; stderr?: string } }
14
14
  | { type: 'stop'; data?: ChatMessage }
15
15
 
16
16
  export type SessionInfo =
@@ -66,6 +66,7 @@ export interface AdapterQueryOptions {
66
66
 
67
67
  systemPrompt?: string
68
68
  appendSystemPrompt?: boolean
69
+ permissionMode?: 'default' | 'acceptEdits' | 'plan' | 'dontAsk' | 'bypassPermissions'
69
70
 
70
71
  mcpServers?: {
71
72
  include?: string[]
@@ -85,6 +86,10 @@ export interface AdapterQueryOptions {
85
86
  onEvent: (event: AdapterOutputEvent) => void
86
87
  }
87
88
 
89
+ export interface AdapterInitOptions {
90
+ force?: boolean
91
+ }
92
+
88
93
  export interface AdapterSession {
89
94
  kill: () => void
90
95
  emit: (event: AdapterEvent) => void
@@ -92,6 +97,10 @@ export interface AdapterSession {
92
97
  }
93
98
 
94
99
  export interface Adapter {
100
+ init?: (
101
+ ctx: AdapterCtx,
102
+ options: AdapterInitOptions
103
+ ) => Promise<void>
95
104
  query: (
96
105
  ctx: AdapterCtx,
97
106
  options: AdapterQueryOptions
package/src/channel.ts ADDED
@@ -0,0 +1,75 @@
1
+ import { z } from 'zod'
2
+
3
+ export interface ChannelMap {}
4
+
5
+ export type ChannelType = keyof ChannelMap
6
+
7
+ export const channelBaseSchema = z.object({
8
+ type: z.string().min(1).describe('频道类型'),
9
+ title: z.string().optional().describe('频道标题'),
10
+ description: z.string().optional().describe('频道说明'),
11
+ enabled: z.boolean().optional().describe('是否启用'),
12
+ admins: z.array(z.string()).optional().describe('频道管理员账号')
13
+ })
14
+
15
+ export type ChannelBaseConfig = z.infer<typeof channelBaseSchema>
16
+
17
+ export type ChannelConfigByType<K extends ChannelType> = ChannelBaseConfig & { type: K } & ChannelMap[K]
18
+
19
+ export type ChannelConfig = {
20
+ [K in ChannelType]: ChannelConfigByType<K>
21
+ }[ChannelType]
22
+
23
+ export interface ChannelConnection<TMessage> {
24
+ sendMessage: (message: TMessage) => Promise<void>
25
+ startReceiving?: (options: {
26
+ handlers: ChannelEventHandlers
27
+ }) => Promise<void>
28
+ close?: () => Promise<void>
29
+ }
30
+
31
+ export interface ChannelInboundEvent {
32
+ channelType: string
33
+ sessionType: string
34
+ channelId: string
35
+ senderId?: string
36
+ messageId?: string
37
+ text?: string
38
+ replyTo?: {
39
+ receiveId: string
40
+ receiveIdType: string
41
+ }
42
+ ack?: () => Promise<void>
43
+ unack?: () => Promise<void>
44
+ raw: unknown
45
+ }
46
+
47
+ export type ChannelEventHandler<TPayload = unknown> = (payload: TPayload) => void | Promise<void>
48
+
49
+ export interface ChannelEventHandlers {
50
+ message?: ChannelEventHandler<ChannelInboundEvent>
51
+ [event: string]: ChannelEventHandler | ChannelEventHandler<ChannelInboundEvent> | undefined
52
+ }
53
+
54
+ export interface ChannelDescriptor<
55
+ TConfigSchema extends z.ZodTypeAny = z.ZodTypeAny,
56
+ TMessageSchema extends z.ZodTypeAny = z.ZodTypeAny,
57
+ > {
58
+ type: string
59
+ label: string
60
+ description?: string
61
+ configSchema: TConfigSchema
62
+ messageSchema: TMessageSchema
63
+ }
64
+
65
+ export const defineChannelDescriptor = <TConfigSchema extends z.ZodTypeAny, TMessageSchema extends z.ZodTypeAny>(
66
+ descriptor: ChannelDescriptor<TConfigSchema, TMessageSchema>
67
+ ) => descriptor
68
+
69
+ export const defineChannel = <TConfigSchema extends z.ZodTypeAny, TMessageSchema extends z.ZodTypeAny>(
70
+ descriptor: ChannelDescriptor<TConfigSchema, TMessageSchema>
71
+ ) => descriptor
72
+
73
+ export const defineChannelConnection = <TConfigSchema extends z.ZodTypeAny, TMessageSchema extends z.ZodTypeAny>(
74
+ connect: (config: z.infer<TConfigSchema>) => Promise<ChannelConnection<z.infer<TMessageSchema>>>
75
+ ) => connect
@@ -0,0 +1,122 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { readFile } from 'node:fs/promises'
3
+ import { resolve } from 'node:path'
4
+ import process from 'node:process'
5
+
6
+ import { load } from 'js-yaml'
7
+
8
+ import type { AdapterMap, Config } from './types'
9
+
10
+ const loadJSConfig = async (paths: string[]) => {
11
+ for (const path of paths) {
12
+ try {
13
+ const configPath = resolve(process.cwd(), path)
14
+ if (!existsSync(configPath)) {
15
+ continue
16
+ }
17
+ // eslint-disable-next-line ts/no-require-imports
18
+ return (require(configPath)?.default ?? {}) as Config
19
+ } catch (e) {
20
+ console.error(`Failed to load config file ${path}: ${e}`)
21
+ }
22
+ }
23
+ }
24
+
25
+ const loadJSONConfig = async (paths: string[], jsonVariables: Record<string, string | null | undefined>) => {
26
+ for (const path of paths) {
27
+ try {
28
+ const configPath = resolve(process.cwd(), path)
29
+ if (!existsSync(configPath)) {
30
+ continue
31
+ }
32
+ const configContent = await readFile(configPath, 'utf-8')
33
+ const configResolvedContent = configContent
34
+ .replace(/\$\{(\w+)\}/g, (_, key) => jsonVariables[key] ?? `$\{${key}}`)
35
+ return JSON.parse(configResolvedContent) as Config
36
+ } catch (e) {
37
+ console.error(`Failed to load config file ${path}: ${e}`)
38
+ }
39
+ }
40
+ }
41
+
42
+ const loadYAMLConfig = async (paths: string[], jsonVariables: Record<string, string | null | undefined>) => {
43
+ for (const path of paths) {
44
+ try {
45
+ const configPath = resolve(process.cwd(), path)
46
+ if (!existsSync(configPath)) {
47
+ continue
48
+ }
49
+ const configContent = await readFile(configPath, 'utf-8')
50
+ const configResolvedContent = configContent
51
+ .replace(/\$\{(\w+)\}/g, (_, key) => jsonVariables[key] ?? `$\{${key}}`)
52
+ return load(configResolvedContent) as Config
53
+ } catch (e) {
54
+ console.error(`Failed to load config file ${path}: ${e}`)
55
+ }
56
+ }
57
+ }
58
+
59
+ let configCache: Promise<readonly [Config | undefined, Config | undefined]> | null = null
60
+
61
+ export const resetConfigCache = () => {
62
+ configCache = null
63
+ }
64
+
65
+ export const loadConfig = (options: {
66
+ jsonVariables?: Record<string, string | null | undefined>
67
+ }) => {
68
+ if (configCache) {
69
+ return configCache
70
+ }
71
+
72
+ configCache = (async () =>
73
+ [
74
+ await loadJSONConfig(
75
+ [
76
+ './.ai.config.json',
77
+ './infra/.ai.config.json'
78
+ ],
79
+ options.jsonVariables ?? {}
80
+ ) ??
81
+ await loadYAMLConfig(
82
+ [
83
+ './.ai.config.yaml',
84
+ './.ai.config.yml',
85
+ './infra/.ai.config.yaml',
86
+ './infra/.ai.config.yml'
87
+ ],
88
+ options.jsonVariables ?? {}
89
+ ),
90
+ await loadJSONConfig(
91
+ [
92
+ './.ai.dev.config.json',
93
+ './infra/.ai.dev.config.json'
94
+ ],
95
+ options.jsonVariables ?? {}
96
+ ) ??
97
+ await loadYAMLConfig(
98
+ [
99
+ './.ai.dev.config.yaml',
100
+ './.ai.dev.config.yml',
101
+ './infra/.ai.dev.config.yaml',
102
+ './infra/.ai.dev.config.yml'
103
+ ],
104
+ options.jsonVariables ?? {}
105
+ )
106
+ ] as const)()
107
+ return configCache
108
+ }
109
+
110
+ export const loadAdapterConfig = async <
111
+ K extends keyof AdapterMap,
112
+ >(
113
+ name: K,
114
+ options: { jsonVariables?: Record<string, string> }
115
+ ) => {
116
+ const [projectConfig, userConfig] = await loadConfig(options)
117
+ return {
118
+ ...(projectConfig?.adapters?.[name] ?? {}),
119
+ ...(userConfig?.adapters?.[name] ?? {})
120
+ } as unknown as NonNullable<Config['adapters']>[K]
121
+ }
122
+
@@ -0,0 +1,262 @@
1
+ import type { ChannelConfig } from '../channels'
2
+ import type { PluginConfig } from '../hooks'
3
+
4
+ export interface AdapterMap {}
5
+
6
+ export interface ModelServiceConfig {
7
+ /**
8
+ * 模型服务展示标题
9
+ */
10
+ title?: string
11
+ /**
12
+ * 模型服务展示描述
13
+ */
14
+ description?: string
15
+ /**
16
+ * 模型服务 API 基础 URL
17
+ */
18
+ apiBaseUrl: string
19
+ /**
20
+ * 模型服务 API 密钥
21
+ */
22
+ apiKey: string
23
+ /**
24
+ * 模型服务支持的模型列表
25
+ */
26
+ models?: string[]
27
+ /**
28
+ * 模型服务支持的模型别名
29
+ */
30
+ modelsAlias?: Record<string, string[]>
31
+ /**
32
+ * 拓展配置,由下游自行消费
33
+ */
34
+ extra?: Record<string, unknown>
35
+ }
36
+
37
+ export interface RecommendedModelConfig {
38
+ service?: string
39
+ model: string
40
+ title?: string
41
+ description?: string
42
+ placement?: 'modelSelector'
43
+ }
44
+
45
+ export type LanguageCode = 'zh' | 'en'
46
+
47
+ export type NotificationTrigger = 'completed' | 'failed' | 'terminated' | 'waiting_input'
48
+
49
+ export interface NotificationEventConfig {
50
+ title?: string
51
+ description?: string
52
+ disabled?: boolean
53
+ sound?: string
54
+ }
55
+
56
+ export interface NotificationConfig {
57
+ disabled?: boolean
58
+ volume?: number
59
+ events?: Partial<Record<NotificationTrigger, NotificationEventConfig>>
60
+ }
61
+
62
+ export interface Config {
63
+ /**
64
+ * 配置目录
65
+ */
66
+ baseDir?: string
67
+ /**
68
+ * 适配器配置
69
+ */
70
+ adapters?: Partial<AdapterMap>
71
+ /**
72
+ * 默认适配器名称
73
+ */
74
+ defaultAdapter?: keyof AdapterMap
75
+ /**
76
+ * 模型服务配置
77
+ */
78
+ modelServices?: Record<string, ModelServiceConfig>
79
+ /**
80
+ * 频道配置
81
+ */
82
+ channels?: Record<string, ChannelConfig>
83
+ /**
84
+ * 默认模型服务名称
85
+ */
86
+ defaultModelService?: string
87
+ /**
88
+ * 默认模型名称
89
+ */
90
+ defaultModel?: string
91
+ recommendedModels?: RecommendedModelConfig[]
92
+ interfaceLanguage?: LanguageCode
93
+ modelLanguage?: LanguageCode
94
+ /**
95
+ * MCP 服务器配置
96
+ */
97
+ mcpServers?: Record<
98
+ string,
99
+ & {
100
+ /**
101
+ * 是否启用
102
+ */
103
+ enabled?: boolean
104
+ /**
105
+ * 环境变量配置
106
+ */
107
+ env?: Record<string, string>
108
+ }
109
+ & (
110
+ | {
111
+ type?: undefined
112
+ command: string
113
+ args: string[]
114
+ }
115
+ | {
116
+ type: 'sse'
117
+ url: string
118
+ headers: Record<string, string>
119
+ }
120
+ | {
121
+ type: 'http'
122
+ url: string
123
+ headers?: Record<string, string>
124
+ }
125
+ )
126
+ >
127
+ /**
128
+ * 默认启用的 MCP 服务器列表
129
+ */
130
+ defaultIncludeMcpServers?: string[]
131
+ /**
132
+ * 默认禁用的 MCP 服务器列表
133
+ */
134
+ defaultExcludeMcpServers?: string[]
135
+ noDefaultVibeForgeMcpServer?: boolean
136
+ /**
137
+ * 权限配置
138
+ */
139
+ permissions?: {
140
+ allow?: string[]
141
+ deny?: string[]
142
+ ask?: string[]
143
+ defaultMode?: 'default' | 'acceptEdits' | 'plan' | 'dontAsk' | 'bypassPermissions'
144
+ }
145
+ /**
146
+ * 环境变量配置
147
+ */
148
+ env?: Record<string, string>
149
+ /**
150
+ * 公告配置
151
+ */
152
+ announcements?: string[]
153
+ /**
154
+ * 快捷键配置
155
+ */
156
+ shortcuts?: {
157
+ newSession?: string
158
+ openConfig?: string
159
+ }
160
+ notifications?: NotificationConfig
161
+ /**
162
+ * 会话配置
163
+ */
164
+ conversation?: {
165
+ /**
166
+ * 对话风格
167
+ * - `friendly`: 友好的对话风格,适合用户与助手交互
168
+ * - `programmatic`: 程序化的对话风格,适合助手执行任务
169
+ */
170
+ style?: 'friendly' | 'programmatic'
171
+ /**
172
+ * 自定义对话风格。通过指定提示词约束对话风格。
173
+ */
174
+ customInstructions?: string
175
+ }
176
+ /**
177
+ * 插件配置
178
+ */
179
+ plugins?: PluginConfig
180
+ enabledPlugins?: Record<string, boolean>
181
+ extraKnownMarketplaces?: Record<
182
+ string,
183
+ {
184
+ source:
185
+ | {
186
+ source: 'github'
187
+ repo: string
188
+ }
189
+ | {
190
+ source: 'git'
191
+ url: string
192
+ }
193
+ | {
194
+ source: 'directory'
195
+ path: string
196
+ }
197
+ }
198
+ >
199
+ }
200
+
201
+ export interface AboutInfo {
202
+ version?: string
203
+ lastReleaseAt?: string
204
+ urls?: {
205
+ repo?: string
206
+ docs?: string
207
+ contact?: string
208
+ issues?: string
209
+ releases?: string
210
+ }
211
+ }
212
+
213
+ export interface ConfigSection {
214
+ general?: {
215
+ baseDir?: Config['baseDir']
216
+ defaultAdapter?: Config['defaultAdapter']
217
+ defaultModelService?: Config['defaultModelService']
218
+ defaultModel?: Config['defaultModel']
219
+ recommendedModels?: Config['recommendedModels']
220
+ interfaceLanguage?: Config['interfaceLanguage']
221
+ modelLanguage?: Config['modelLanguage']
222
+ announcements?: Config['announcements']
223
+ permissions?: Config['permissions']
224
+ env?: Config['env']
225
+ notifications?: Config['notifications']
226
+ }
227
+ conversation?: Config['conversation']
228
+ modelServices?: Config['modelServices']
229
+ channels?: Config['channels']
230
+ adapters?: Config['adapters']
231
+ plugins?: {
232
+ plugins?: Config['plugins']
233
+ enabledPlugins?: Config['enabledPlugins']
234
+ extraKnownMarketplaces?: Config['extraKnownMarketplaces']
235
+ }
236
+ mcp?: {
237
+ mcpServers?: Config['mcpServers']
238
+ defaultIncludeMcpServers?: Config['defaultIncludeMcpServers']
239
+ defaultExcludeMcpServers?: Config['defaultExcludeMcpServers']
240
+ noDefaultVibeForgeMcpServer?: Config['noDefaultVibeForgeMcpServer']
241
+ }
242
+ shortcuts?: Config['shortcuts']
243
+ }
244
+
245
+ export interface ConfigResponse {
246
+ sources?: {
247
+ project?: ConfigSection
248
+ user?: ConfigSection
249
+ merged?: ConfigSection
250
+ }
251
+ meta?: {
252
+ workspaceFolder?: string
253
+ configPresent?: {
254
+ project?: boolean
255
+ user?: boolean
256
+ }
257
+ experiments?: Record<string, unknown>
258
+ about?: AboutInfo
259
+ }
260
+ }
261
+
262
+ export const defineConfig = (config: Config) => config