feat: 重构为 Monorepo 架构并实现 HTTP Server
架构变更: - 采用 pnpm workspaces 实现 Monorepo 结构 - 将现有代码迁移到 packages/core - 新增 packages/server HTTP 服务层 Server 功能: - REST API: 会话管理、工具管理、配置管理 - WebSocket: 实时双向通信支持 - SSE: 服务端事件推送 - Hono + Bun 作为运行时 API 端点: - GET/POST /api/sessions - 会话 CRUD - GET/POST /api/sessions/:id/messages - 消息管理 - GET /api/sessions/:id/events - SSE 事件流 - WS /api/ws/:sessionId - WebSocket 连接 - GET/POST /api/tools - 工具管理 - GET/PUT /api/config - 配置管理
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 图数据结构
|
||||
* 用于 PageRank 算法
|
||||
*/
|
||||
|
||||
import type { GraphEdge } from '../types.js';
|
||||
|
||||
export class Graph {
|
||||
/** 邻接表:from -> edges[] */
|
||||
private outEdges: Map<string, GraphEdge[]> = new Map();
|
||||
/** 反向邻接表:to -> edges[] */
|
||||
private inEdges: Map<string, GraphEdge[]> = new Map();
|
||||
/** 所有节点 */
|
||||
private nodes: Set<string> = new Set();
|
||||
|
||||
/**
|
||||
* 添加边
|
||||
*/
|
||||
addEdge(edge: GraphEdge): void {
|
||||
this.nodes.add(edge.from);
|
||||
this.nodes.add(edge.to);
|
||||
|
||||
// 出边
|
||||
if (!this.outEdges.has(edge.from)) {
|
||||
this.outEdges.set(edge.from, []);
|
||||
}
|
||||
this.outEdges.get(edge.from)!.push(edge);
|
||||
|
||||
// 入边
|
||||
if (!this.inEdges.has(edge.to)) {
|
||||
this.inEdges.set(edge.to, []);
|
||||
}
|
||||
this.inEdges.get(edge.to)!.push(edge);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有节点
|
||||
*/
|
||||
getNodes(): string[] {
|
||||
return Array.from(this.nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取节点的出边
|
||||
*/
|
||||
getOutEdges(node: string): GraphEdge[] {
|
||||
return this.outEdges.get(node) || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取节点的入边
|
||||
*/
|
||||
getInEdges(node: string): GraphEdge[] {
|
||||
return this.inEdges.get(node) || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取节点的出度(考虑权重)
|
||||
*/
|
||||
getOutDegree(node: string): number {
|
||||
const edges = this.outEdges.get(node) || [];
|
||||
return edges.reduce((sum, e) => sum + e.weight, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取节点的入度(考虑权重)
|
||||
*/
|
||||
getInDegree(node: string): number {
|
||||
const edges = this.inEdges.get(node) || [];
|
||||
return edges.reduce((sum, e) => sum + e.weight, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取边数量
|
||||
*/
|
||||
getEdgeCount(): number {
|
||||
let count = 0;
|
||||
for (const edges of this.outEdges.values()) {
|
||||
count += edges.length;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空图
|
||||
*/
|
||||
clear(): void {
|
||||
this.outEdges.clear();
|
||||
this.inEdges.clear();
|
||||
this.nodes.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为空
|
||||
*/
|
||||
isEmpty(): boolean {
|
||||
return this.nodes.size === 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* 排序模块导出
|
||||
*/
|
||||
|
||||
export { Graph } from './graph.js';
|
||||
export { pagerank, distributeRanksToDefinitions } from './pagerank.js';
|
||||
export type { PageRankOptions } from './pagerank.js';
|
||||
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* PageRank 算法实现
|
||||
* 基于 Aider 的实现,用于代码符号相关性排序
|
||||
*/
|
||||
|
||||
import { Graph } from './graph.js';
|
||||
|
||||
export interface PageRankOptions {
|
||||
/** 阻尼系数 (默认 0.85) */
|
||||
damping?: number;
|
||||
/** 最大迭代次数 (默认 100) */
|
||||
iterations?: number;
|
||||
/** 收敛阈值 (默认 1e-6) */
|
||||
tolerance?: number;
|
||||
/** 个性化向量:节点 -> 初始权重 */
|
||||
personalization?: Map<string, number>;
|
||||
}
|
||||
|
||||
/**
|
||||
* PageRank 算法
|
||||
*
|
||||
* @param graph - 图结构
|
||||
* @param options - 算法选项
|
||||
* @returns 节点排名 Map<节点, 排名值>
|
||||
*/
|
||||
export function pagerank(
|
||||
graph: Graph,
|
||||
options: PageRankOptions = {}
|
||||
): Map<string, number> {
|
||||
const {
|
||||
damping = 0.85,
|
||||
iterations = 100,
|
||||
tolerance = 1e-6,
|
||||
personalization,
|
||||
} = options;
|
||||
|
||||
const nodes = graph.getNodes();
|
||||
const n = nodes.length;
|
||||
|
||||
if (n === 0) {
|
||||
return new Map();
|
||||
}
|
||||
|
||||
// 初始化排名
|
||||
let ranks = new Map<string, number>();
|
||||
const baseRank = 1 / n;
|
||||
|
||||
// 处理个性化向量
|
||||
let persVector = new Map<string, number>();
|
||||
if (personalization && personalization.size > 0) {
|
||||
// 归一化个性化向量
|
||||
const total = Array.from(personalization.values()).reduce((a, b) => a + b, 0);
|
||||
if (total > 0) {
|
||||
for (const [node, value] of personalization) {
|
||||
persVector.set(node, value / total);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 均匀分布
|
||||
for (const node of nodes) {
|
||||
persVector.set(node, baseRank);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始排名 = 个性化向量
|
||||
for (const node of nodes) {
|
||||
ranks.set(node, persVector.get(node) || baseRank);
|
||||
}
|
||||
|
||||
// 迭代计算
|
||||
for (let iter = 0; iter < iterations; iter++) {
|
||||
const newRanks = new Map<string, number>();
|
||||
let diff = 0;
|
||||
|
||||
// 计算悬挂节点的贡献(没有出边的节点)
|
||||
let danglingSum = 0;
|
||||
for (const node of nodes) {
|
||||
const outEdges = graph.getOutEdges(node);
|
||||
if (outEdges.length === 0) {
|
||||
danglingSum += ranks.get(node) || 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (const node of nodes) {
|
||||
// 基础分数:(1 - damping) * 个性化 + damping * 悬挂贡献
|
||||
let rank =
|
||||
(1 - damping) * (persVector.get(node) || baseRank) +
|
||||
(damping * danglingSum) / n;
|
||||
|
||||
// 收集入边贡献
|
||||
const inEdges = graph.getInEdges(node);
|
||||
for (const edge of inEdges) {
|
||||
const sourceRank = ranks.get(edge.from) || 0;
|
||||
const outDegree = graph.getOutDegree(edge.from);
|
||||
|
||||
if (outDegree > 0) {
|
||||
// 边权重占源节点总出度的比例
|
||||
rank += damping * sourceRank * (edge.weight / outDegree);
|
||||
}
|
||||
}
|
||||
|
||||
newRanks.set(node, rank);
|
||||
diff += Math.abs(rank - (ranks.get(node) || 0));
|
||||
}
|
||||
|
||||
ranks = newRanks;
|
||||
|
||||
// 检查收敛
|
||||
if (diff < tolerance) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ranks;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 PageRank 排名分配到定义上
|
||||
* 按照 Aider 的方式:将源节点的排名按边权重比例分配给目标定义
|
||||
*
|
||||
* @param graph - 图结构
|
||||
* @param nodeRanks - 节点 PageRank 排名
|
||||
* @returns 定义排名 Map<"file:ident", rank>
|
||||
*/
|
||||
export function distributeRanksToDefinitions(
|
||||
graph: Graph,
|
||||
nodeRanks: Map<string, number>
|
||||
): Map<string, number> {
|
||||
const definitionRanks = new Map<string, number>();
|
||||
|
||||
for (const src of graph.getNodes()) {
|
||||
const srcRank = nodeRanks.get(src) || 0;
|
||||
const outEdges = graph.getOutEdges(src);
|
||||
const totalWeight = outEdges.reduce((sum, e) => sum + e.weight, 0);
|
||||
|
||||
if (totalWeight === 0) continue;
|
||||
|
||||
for (const edge of outEdges) {
|
||||
const edgeRank = (srcRank * edge.weight) / totalWeight;
|
||||
const key = `${edge.to}:${edge.ident}`;
|
||||
definitionRanks.set(key, (definitionRanks.get(key) || 0) + edgeRank);
|
||||
}
|
||||
}
|
||||
|
||||
return definitionRanks;
|
||||
}
|
||||
Reference in New Issue
Block a user