feat: implement Tier 3 features — lane queue, credential redaction, token dashboard, xAI, Voyage AI
- Lane Queue: per-session FIFO queue in gateway replacing reject-when-busy (9 tests) - Credential Redaction: redactConfig() expanded to cover 18+ secret fields (16 tests) - Web UI Token Dashboard: system.tokenUsage endpoint + Usage page with summary cards - xAI (Grok) Provider: OpenAI-compatible client with model pricing - Voyage AI Embeddings: new embedding provider with configurable dimensions (5 tests) - Update gap analysis: 90→95 match (70%→74%), Tier 3 section marked DONE - Update state.json: test count 1001→1034, add tier3_completion entry Total: 1034 tests passing across 85 files, typecheck clean
This commit is contained in:
+54
-4
@@ -114,6 +114,12 @@ export function createClientFromConfig(cfg: ModelConfig): ModelClient {
|
||||
apiKey: cfg.api_key ?? process.env.ZHIPUAI_API_KEY,
|
||||
baseURL: cfg.endpoint ?? 'https://api.z.ai/api/paas/v4',
|
||||
});
|
||||
case 'xai':
|
||||
return new OpenAIClient({
|
||||
model: cfg.model,
|
||||
apiKey: cfg.api_key ?? process.env.XAI_API_KEY,
|
||||
baseURL: cfg.endpoint ?? 'https://api.x.ai/v1',
|
||||
});
|
||||
case 'bedrock':
|
||||
return new BedrockClient({
|
||||
model: cfg.model,
|
||||
@@ -313,6 +319,8 @@ export function createModelRouter(config: Config): ModelRouter {
|
||||
* Create the unified message handler for the channel registry.
|
||||
* Each channel+sender pair gets its own AgentOrchestrator backed by a persistent session.
|
||||
* The orchestrator wraps a NativeAgent and adds delegation to different model tiers.
|
||||
*
|
||||
* Returns both the message handler function and the agents map for usage tracking.
|
||||
*/
|
||||
function createMessageRouter(deps: {
|
||||
sessionManager: SessionManager;
|
||||
@@ -326,7 +334,10 @@ function createMessageRouter(deps: {
|
||||
agentRouter?: AgentRouter;
|
||||
sandboxManager?: SandboxManager;
|
||||
audioConfig?: AudioTranscriptionConfig;
|
||||
}) {
|
||||
}): {
|
||||
handler: (msg: InboundMessage, reply: (response: OutboundMessage) => Promise<void>) => Promise<void>;
|
||||
agents: Map<string, { orchestrator: AgentOrchestrator; collector: OutboundAttachmentCollector }>;
|
||||
} {
|
||||
// Cache agents by session ID + agent config name to avoid recreating on every message
|
||||
const agents = new Map<string, { orchestrator: AgentOrchestrator; collector: OutboundAttachmentCollector }>();
|
||||
|
||||
@@ -444,7 +455,7 @@ function createMessageRouter(deps: {
|
||||
return entry;
|
||||
}
|
||||
|
||||
return async (msg: InboundMessage, reply: (response: OutboundMessage) => Promise<void>): Promise<void> => {
|
||||
const handler = async (msg: InboundMessage, reply: (response: OutboundMessage) => Promise<void>): Promise<void> => {
|
||||
const { orchestrator: agent, collector } = getOrCreateAgent(msg.channel, msg.senderId);
|
||||
|
||||
// Handle special commands
|
||||
@@ -524,6 +535,8 @@ function createMessageRouter(deps: {
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return { handler, agents };
|
||||
}
|
||||
|
||||
export async function startDaemon(config: Config): Promise<DaemonContext> {
|
||||
@@ -780,6 +793,10 @@ export async function startDaemon(config: Config): Promise<DaemonContext> {
|
||||
// Initialize channel registry (created early so the gateway can reference it)
|
||||
const channelRegistry = new ChannelRegistry();
|
||||
|
||||
// Mutable reference to channel agents map — set after createMessageRouter() below.
|
||||
// This allows the gateway's getTokenUsage callback to access channel agent usage data.
|
||||
let channelAgents: Map<string, { orchestrator: AgentOrchestrator; collector: OutboundAttachmentCollector }> | null = null;
|
||||
|
||||
// Initialize gateway WebSocket server
|
||||
const gateway = new GatewayServer({
|
||||
port: config.server.port,
|
||||
@@ -803,6 +820,35 @@ export async function startDaemon(config: Config): Promise<DaemonContext> {
|
||||
// Exit with code 75 (EX_TEMPFAIL) — process supervisor should restart
|
||||
process.exit(75);
|
||||
},
|
||||
getTokenUsage: () => {
|
||||
const results: Array<{
|
||||
sessionId: string;
|
||||
primary: { inputTokens: number; outputTokens: number; calls: number };
|
||||
delegation: Record<string, { inputTokens: number; outputTokens: number; calls: number }>;
|
||||
total: { inputTokens: number; outputTokens: number; calls: number; estimatedCost: number };
|
||||
}> = [];
|
||||
|
||||
// Collect usage from gateway WebSocket sessions (NativeAgent-based)
|
||||
const sessionBridge = gateway.getSessionBridge();
|
||||
for (const entry of sessionBridge.getAllUsage()) {
|
||||
results.push(entry);
|
||||
}
|
||||
|
||||
// Collect usage from channel agents (AgentOrchestrator-based, has full delegation data)
|
||||
if (channelAgents) {
|
||||
for (const [sessionId, { orchestrator }] of channelAgents) {
|
||||
const usage = orchestrator.getUsage();
|
||||
results.push({
|
||||
sessionId,
|
||||
primary: usage.primary,
|
||||
delegation: usage.delegation,
|
||||
total: usage.total,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
},
|
||||
});
|
||||
|
||||
if (config.server.token) {
|
||||
@@ -812,7 +858,7 @@ export async function startDaemon(config: Config): Promise<DaemonContext> {
|
||||
// ── Channel Registry ──────────────────────────────────────────
|
||||
|
||||
// Set up the unified message handler
|
||||
channelRegistry.setMessageHandler(createMessageRouter({
|
||||
const messageRouter = createMessageRouter({
|
||||
sessionManager,
|
||||
modelRouter,
|
||||
systemPrompt,
|
||||
@@ -824,7 +870,11 @@ export async function startDaemon(config: Config): Promise<DaemonContext> {
|
||||
agentRouter,
|
||||
sandboxManager,
|
||||
audioConfig,
|
||||
}));
|
||||
});
|
||||
channelRegistry.setMessageHandler(messageRouter.handler);
|
||||
|
||||
// Wire channel agents into the getTokenUsage callback (late binding)
|
||||
channelAgents = messageRouter.agents;
|
||||
|
||||
// Register Telegram adapter
|
||||
const telegramAdapter = new TelegramAdapter({
|
||||
|
||||
Reference in New Issue
Block a user