autosnippet 2.18.0 → 2.19.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/dashboard/dist/assets/{icons-C6kshpB1.js → icons-C7FN32VL.js} +1 -1
- package/dashboard/dist/assets/index-D8dCXLzr.js +129 -0
- package/dashboard/dist/index.html +2 -2
- package/lib/external/ai/AiProvider.js +42 -11
- package/lib/external/ai/providers/ClaudeProvider.js +4 -2
- package/lib/external/ai/providers/GoogleGeminiProvider.js +66 -8
- package/lib/external/ai/providers/OpenAiProvider.js +48 -2
- package/lib/external/mcp/handlers/bootstrap.js +1 -2
- package/lib/http/HttpServer.js +4 -0
- package/lib/http/routes/candidates.js +405 -0
- package/lib/http/routes/search.js +113 -0
- package/lib/infrastructure/vector/Chunker.js +3 -8
- package/lib/infrastructure/vector/JsonVectorAdapter.js +2 -9
- package/lib/service/candidate/SimilarityService.js +7 -35
- package/lib/service/chat/ChatAgent.js +28 -686
- package/lib/service/chat/ContextWindow.js +87 -3
- package/lib/service/chat/ConversationStore.js +3 -4
- package/lib/service/chat/ProjectSemanticMemory.js +9 -14
- package/lib/service/chat/ReasoningLayer.js +10 -54
- package/lib/service/chat/ToolRegistry.js +0 -52
- package/lib/service/chat/tools.js +7 -6
- package/lib/service/cursor/TokenBudget.js +4 -21
- package/lib/service/search/CrossEncoderReranker.js +163 -0
- package/lib/service/search/RetrievalFunnel.js +9 -36
- package/lib/service/skills/SignalCollector.js +28 -28
- package/lib/shared/similarity.js +101 -0
- package/lib/shared/token-utils.js +46 -0
- package/package.json +1 -1
- package/dashboard/dist/assets/index-9byoG7kd.js +0 -129
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>AutoSnippet Dashboard</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-D8dCXLzr.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/yaml-qRaU8Ldn.js">
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/syntax-highlighter-BkDyUteW.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/vendor-Ba1BZjav.js">
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/axios-C0Zqfgkc.js">
|
|
13
|
-
<link rel="modulepreload" crossorigin href="/assets/icons-
|
|
13
|
+
<link rel="modulepreload" crossorigin href="/assets/icons-C7FN32VL.js">
|
|
14
14
|
<link rel="modulepreload" crossorigin href="/assets/react-markdown-Dc1U8Kko.js">
|
|
15
15
|
<link rel="stylesheet" crossorigin href="/assets/index-BDmJqEkA.css">
|
|
16
16
|
</head>
|
|
@@ -113,6 +113,36 @@ export class AiProvider {
|
|
|
113
113
|
return { text, functionCalls: null };
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Structured Output — 请求 AI 返回严格 JSON 格式响应
|
|
118
|
+
*
|
|
119
|
+
* 子类覆盖以利用原生 JSON mode:
|
|
120
|
+
* - Gemini: responseMimeType: 'application/json' + responseSchema
|
|
121
|
+
* - OpenAI: response_format: { type: 'json_object' }
|
|
122
|
+
* - Claude: 无原生支持,使用默认实现 (chat + extractJSON)
|
|
123
|
+
*
|
|
124
|
+
* @param {string} prompt — 完整提示词(应包含返回 JSON 的指令)
|
|
125
|
+
* @param {object} [opts]
|
|
126
|
+
* @param {object} [opts.schema] — JSON Schema(Gemini/OpenAI 的 structured output 用)
|
|
127
|
+
* @param {string} [opts.openChar='{'] — extractJSON 边界起始符(fallback 用)
|
|
128
|
+
* @param {string} [opts.closeChar='}'] — extractJSON 边界终止符
|
|
129
|
+
* @param {number} [opts.temperature=0.3]
|
|
130
|
+
* @param {number} [opts.maxTokens=32768]
|
|
131
|
+
* @param {string} [opts.systemPrompt] — 可选系统指令
|
|
132
|
+
* @returns {Promise<any>} — 解析后的 JSON 对象/数组,解析失败返回 null
|
|
133
|
+
*/
|
|
134
|
+
async chatWithStructuredOutput(prompt, opts = {}) {
|
|
135
|
+
const response = await this.chat(prompt, {
|
|
136
|
+
temperature: opts.temperature ?? 0.3,
|
|
137
|
+
maxTokens: opts.maxTokens ?? 32768,
|
|
138
|
+
systemPrompt: opts.systemPrompt,
|
|
139
|
+
});
|
|
140
|
+
if (!response || response.trim().length === 0) return null;
|
|
141
|
+
const openChar = opts.openChar || '{';
|
|
142
|
+
const closeChar = opts.closeChar || '}';
|
|
143
|
+
return this.extractJSON(response, openChar, closeChar);
|
|
144
|
+
}
|
|
145
|
+
|
|
116
146
|
/**
|
|
117
147
|
* 从源码文件批量提取 Recipe 结构(AI 驱动)
|
|
118
148
|
* 默认实现使用 chat() + 标准提示词;子类可覆盖以使用专用 API
|
|
@@ -124,16 +154,14 @@ export class AiProvider {
|
|
|
124
154
|
*/
|
|
125
155
|
async extractRecipes(targetName, filesContent, options = {}) {
|
|
126
156
|
const prompt = this._buildExtractPrompt(targetName, filesContent, options);
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
157
|
+
const parsed = await this.chatWithStructuredOutput(prompt, {
|
|
158
|
+
openChar: '[',
|
|
159
|
+
closeChar: ']',
|
|
160
|
+
temperature: 0.3,
|
|
161
|
+
maxTokens: 32768,
|
|
162
|
+
});
|
|
133
163
|
if (!Array.isArray(parsed)) {
|
|
134
|
-
|
|
135
|
-
const tail = response.length > 300 ? response.substring(response.length - 300) : response;
|
|
136
|
-
this._log('warn', `[extractRecipes] JSON parse failed for target: ${targetName}, response length: ${response.length}, tail: ${tail}`);
|
|
164
|
+
this._log('warn', `[extractRecipes] structured output parse failed for target: ${targetName}`);
|
|
137
165
|
return [];
|
|
138
166
|
}
|
|
139
167
|
if (parsed.length === 0) {
|
|
@@ -454,8 +482,11 @@ ${files}`;
|
|
|
454
482
|
*/
|
|
455
483
|
async enrichCandidates(candidates) {
|
|
456
484
|
const prompt = this._buildEnrichPrompt(candidates);
|
|
457
|
-
const
|
|
458
|
-
|
|
485
|
+
const parsed = await this.chatWithStructuredOutput(prompt, {
|
|
486
|
+
openChar: '[',
|
|
487
|
+
closeChar: ']',
|
|
488
|
+
temperature: 0.3,
|
|
489
|
+
});
|
|
459
490
|
return Array.isArray(parsed) ? parsed : [];
|
|
460
491
|
}
|
|
461
492
|
|
|
@@ -249,8 +249,10 @@ export class ClaudeProvider extends AiProvider {
|
|
|
249
249
|
|
|
250
250
|
async summarize(code) {
|
|
251
251
|
const prompt = `请对以下代码生成结构化摘要,返回 JSON 格式 {title, description, language, patterns: [], keyAPIs: []}:\n\n${code}`;
|
|
252
|
-
|
|
253
|
-
|
|
252
|
+
return await this.chatWithStructuredOutput(prompt, {
|
|
253
|
+
temperature: 0.3,
|
|
254
|
+
maxTokens: 4096,
|
|
255
|
+
}) || { title: '', description: '' };
|
|
254
256
|
}
|
|
255
257
|
|
|
256
258
|
async embed(_text) {
|
|
@@ -216,7 +216,8 @@ export class GoogleGeminiProvider extends AiProvider {
|
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
/**
|
|
219
|
-
* 清理 JSON Schema 使之兼容 Gemini API 的 OpenAPI
|
|
219
|
+
* 清理 JSON Schema 使之兼容 Gemini API 的 OpenAPI 子集(递归)
|
|
220
|
+
* Gemini API 不支持 default、examples 等 JSON Schema 扩展字段
|
|
220
221
|
*/
|
|
221
222
|
#sanitizeSchemaForGemini(schema) {
|
|
222
223
|
if (!schema || typeof schema !== 'object') {
|
|
@@ -224,20 +225,24 @@ export class GoogleGeminiProvider extends AiProvider {
|
|
|
224
225
|
}
|
|
225
226
|
|
|
226
227
|
const cleaned = { ...schema };
|
|
228
|
+
delete cleaned.default;
|
|
229
|
+
delete cleaned.examples;
|
|
227
230
|
if (!cleaned.type) cleaned.type = 'object';
|
|
228
231
|
|
|
232
|
+
// 递归清理 properties
|
|
229
233
|
if (cleaned.properties) {
|
|
230
234
|
const props = {};
|
|
231
235
|
for (const [key, val] of Object.entries(cleaned.properties)) {
|
|
232
|
-
|
|
233
|
-
delete prop.default;
|
|
234
|
-
delete prop.examples;
|
|
235
|
-
if (!prop.type) prop.type = 'string';
|
|
236
|
-
props[key] = prop;
|
|
236
|
+
props[key] = this.#sanitizeSchemaForGemini(val);
|
|
237
237
|
}
|
|
238
238
|
cleaned.properties = props;
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
+
// 递归清理 items (array 类型)
|
|
242
|
+
if (cleaned.items && typeof cleaned.items === 'object') {
|
|
243
|
+
cleaned.items = this.#sanitizeSchemaForGemini(cleaned.items);
|
|
244
|
+
}
|
|
245
|
+
|
|
241
246
|
return cleaned;
|
|
242
247
|
}
|
|
243
248
|
|
|
@@ -297,8 +302,61 @@ export class GoogleGeminiProvider extends AiProvider {
|
|
|
297
302
|
|
|
298
303
|
async summarize(code) {
|
|
299
304
|
const prompt = `请对以下代码生成结构化摘要,返回 JSON 格式 {title, description, language, patterns: [], keyAPIs: []}:\n\n${code}`;
|
|
300
|
-
|
|
301
|
-
|
|
305
|
+
return await this.chatWithStructuredOutput(prompt, {
|
|
306
|
+
temperature: 0.3,
|
|
307
|
+
maxTokens: 8192,
|
|
308
|
+
}) || { title: '', description: '' };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Structured Output — Gemini 原生 JSON mode
|
|
313
|
+
*
|
|
314
|
+
* 使用 responseMimeType: 'application/json' 强制 Gemini 返回合法 JSON。
|
|
315
|
+
* 可选传入 responseSchema 做编译期校验(Gemini 1.5+ / Gemini 2+)。
|
|
316
|
+
*/
|
|
317
|
+
async chatWithStructuredOutput(prompt, opts = {}) {
|
|
318
|
+
return this._withRetry(async () => {
|
|
319
|
+
const {
|
|
320
|
+
schema,
|
|
321
|
+
temperature = 0.3,
|
|
322
|
+
maxTokens = 32768,
|
|
323
|
+
systemPrompt,
|
|
324
|
+
} = opts;
|
|
325
|
+
|
|
326
|
+
const contents = [{ role: 'user', parts: [{ text: prompt }] }];
|
|
327
|
+
|
|
328
|
+
const generationConfig = {
|
|
329
|
+
temperature,
|
|
330
|
+
maxOutputTokens: maxTokens,
|
|
331
|
+
responseMimeType: 'application/json',
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
// 如果提供了 JSON Schema,注入 responseSchema(Gemini 编译期校验)
|
|
335
|
+
if (schema) {
|
|
336
|
+
generationConfig.responseSchema = this.#sanitizeSchemaForGemini(schema);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const body = { contents, generationConfig };
|
|
340
|
+
|
|
341
|
+
if (systemPrompt) {
|
|
342
|
+
body.systemInstruction = { parts: [{ text: systemPrompt }] };
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const url = `${GEMINI_BASE}/models/${this.model}:generateContent?key=${this.apiKey}`;
|
|
346
|
+
const data = await this._post(url, body);
|
|
347
|
+
const text = data?.candidates?.[0]?.content?.parts?.[0]?.text || '';
|
|
348
|
+
|
|
349
|
+
if (!text) return null;
|
|
350
|
+
|
|
351
|
+
try {
|
|
352
|
+
return JSON.parse(text);
|
|
353
|
+
} catch {
|
|
354
|
+
// Gemini JSON mode 偶尔返回前后有空白的 JSON,尝试 extractJSON 降级
|
|
355
|
+
const openChar = opts.openChar || '{';
|
|
356
|
+
const closeChar = opts.closeChar || '}';
|
|
357
|
+
return this.extractJSON(text, openChar, closeChar);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
302
360
|
}
|
|
303
361
|
|
|
304
362
|
async embed(text) {
|
|
@@ -184,8 +184,54 @@ export class OpenAiProvider extends AiProvider {
|
|
|
184
184
|
|
|
185
185
|
async summarize(code) {
|
|
186
186
|
const prompt = `请对以下代码生成结构化摘要,返回 JSON 格式 {title, description, language, patterns: [], keyAPIs: []}:\n\n${code}`;
|
|
187
|
-
|
|
188
|
-
|
|
187
|
+
return await this.chatWithStructuredOutput(prompt, {
|
|
188
|
+
temperature: 0.3,
|
|
189
|
+
maxTokens: 4096,
|
|
190
|
+
}) || { title: '', description: '' };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Structured Output — OpenAI JSON mode
|
|
195
|
+
*
|
|
196
|
+
* 使用 response_format: { type: 'json_object' } 强制返回合法 JSON。
|
|
197
|
+
* 兼容 DeepSeek / Ollama 等 OpenAI-Compatible API。
|
|
198
|
+
*/
|
|
199
|
+
async chatWithStructuredOutput(prompt, opts = {}) {
|
|
200
|
+
return this._withRetry(async () => {
|
|
201
|
+
const {
|
|
202
|
+
temperature = 0.3,
|
|
203
|
+
maxTokens = 32768,
|
|
204
|
+
systemPrompt,
|
|
205
|
+
} = opts;
|
|
206
|
+
|
|
207
|
+
const messages = [];
|
|
208
|
+
if (systemPrompt) {
|
|
209
|
+
messages.push({ role: 'system', content: systemPrompt });
|
|
210
|
+
}
|
|
211
|
+
messages.push({ role: 'user', content: prompt });
|
|
212
|
+
|
|
213
|
+
const body = {
|
|
214
|
+
model: this.model,
|
|
215
|
+
messages,
|
|
216
|
+
temperature,
|
|
217
|
+
max_tokens: maxTokens,
|
|
218
|
+
response_format: { type: 'json_object' },
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const data = await this._post(`${this.baseUrl}/chat/completions`, body);
|
|
222
|
+
const text = data?.choices?.[0]?.message?.content || '';
|
|
223
|
+
|
|
224
|
+
if (!text) return null;
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
return JSON.parse(text);
|
|
228
|
+
} catch {
|
|
229
|
+
// JSON mode 极少出错,降级到 extractJSON
|
|
230
|
+
const openChar = opts.openChar || '{';
|
|
231
|
+
const closeChar = opts.closeChar || '}';
|
|
232
|
+
return this.extractJSON(text, openChar, closeChar);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
189
235
|
}
|
|
190
236
|
|
|
191
237
|
async embed(text) {
|
|
@@ -693,8 +693,7 @@ ${args.userPrompt ? `用户补充: ${args.userPrompt}` : ''}
|
|
|
693
693
|
"insight": "架构洞察(一句话)"
|
|
694
694
|
}`;
|
|
695
695
|
|
|
696
|
-
const
|
|
697
|
-
const parsed = aiProvider.extractJSON(response, '{', '}');
|
|
696
|
+
const parsed = await aiProvider.chatWithStructuredOutput(prompt, { temperature: 0.3 });
|
|
698
697
|
|
|
699
698
|
if (!parsed) {
|
|
700
699
|
errors.push({ id: entry.id, title: entry.title, error: 'AI returned no valid JSON' });
|
package/lib/http/HttpServer.js
CHANGED
|
@@ -25,6 +25,7 @@ import spmRouter from './routes/spm.js';
|
|
|
25
25
|
import violationsRouter from './routes/violations.js';
|
|
26
26
|
import authRouter from './routes/auth.js';
|
|
27
27
|
import skillsRouter from './routes/skills.js';
|
|
28
|
+
import candidatesRouter from './routes/candidates.js';
|
|
28
29
|
import knowledgeRouter from './routes/knowledge.js';
|
|
29
30
|
import recipesRouter from './routes/recipes.js';
|
|
30
31
|
import wikiRouter from './routes/wiki.js';
|
|
@@ -257,6 +258,9 @@ export class HttpServer {
|
|
|
257
258
|
// Skills 路由
|
|
258
259
|
this.app.use(`${apiPrefix}/skills`, skillsRouter);
|
|
259
260
|
|
|
261
|
+
// Candidates 路由(AI 补齐/润色)
|
|
262
|
+
this.app.use(`${apiPrefix}/candidates`, candidatesRouter);
|
|
263
|
+
|
|
260
264
|
// SPM 路由
|
|
261
265
|
this.app.use(`${apiPrefix}/spm`, spmRouter);
|
|
262
266
|
|