@brutalist/mcp 0.5.1 → 0.6.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/README.md +63 -63
- package/dist/brutalist-server.d.ts +10 -0
- package/dist/brutalist-server.d.ts.map +1 -1
- package/dist/brutalist-server.js +177 -293
- package/dist/brutalist-server.js.map +1 -1
- package/dist/cli-agents.d.ts +1 -0
- package/dist/cli-agents.d.ts.map +1 -1
- package/dist/cli-agents.js +45 -2
- package/dist/cli-agents.js.map +1 -1
- package/dist/tool-definitions.d.ts +6 -0
- package/dist/tool-definitions.d.ts.map +1 -0
- package/dist/tool-definitions.js +211 -0
- package/dist/tool-definitions.js.map +1 -0
- package/dist/types/brutalist.d.ts +0 -16
- package/dist/types/brutalist.d.ts.map +1 -1
- package/dist/types/tool-config.d.ts +52 -0
- package/dist/types/tool-config.d.ts.map +1 -0
- package/dist/types/tool-config.js +25 -0
- package/dist/types/tool-config.js.map +1 -0
- package/dist/utils/pagination.d.ts +2 -2
- package/dist/utils/pagination.d.ts.map +1 -1
- package/dist/utils/pagination.js +1 -1
- package/dist/utils/pagination.js.map +1 -1
- package/dist/utils/response-cache.d.ts +89 -0
- package/dist/utils/response-cache.d.ts.map +1 -0
- package/dist/utils/response-cache.js +260 -0
- package/dist/utils/response-cache.js.map +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +22 -3
- package/dist/utils.js.map +1 -1
- package/package.json +2 -2
package/dist/brutalist-server.js
CHANGED
|
@@ -6,7 +6,10 @@ import express from "express";
|
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { CLIAgentOrchestrator } from './cli-agents.js';
|
|
8
8
|
import { logger } from './logger.js';
|
|
9
|
-
import {
|
|
9
|
+
import { BASE_ROAST_SCHEMA } from './types/tool-config.js';
|
|
10
|
+
import { TOOL_CONFIGS } from './tool-definitions.js';
|
|
11
|
+
import { extractPaginationParams, parseCursor, PAGINATION_DEFAULTS, ResponseChunker, createPaginationMetadata, formatPaginationStatus, estimateTokenCount } from './utils/pagination.js';
|
|
12
|
+
import { ResponseCache } from './utils/response-cache.js';
|
|
10
13
|
// Use environment variable or fallback to manual version
|
|
11
14
|
const PACKAGE_VERSION = process.env.npm_package_version || "0.4.4";
|
|
12
15
|
export class BrutalistServer {
|
|
@@ -14,6 +17,7 @@ export class BrutalistServer {
|
|
|
14
17
|
config;
|
|
15
18
|
cliOrchestrator;
|
|
16
19
|
httpTransport;
|
|
20
|
+
responseCache;
|
|
17
21
|
constructor(config = {}) {
|
|
18
22
|
this.config = {
|
|
19
23
|
workingDirectory: process.cwd(),
|
|
@@ -25,6 +29,16 @@ export class BrutalistServer {
|
|
|
25
29
|
};
|
|
26
30
|
logger.debug("Initializing CLI Agent Orchestrator");
|
|
27
31
|
this.cliOrchestrator = new CLIAgentOrchestrator();
|
|
32
|
+
// Initialize response cache with configurable TTL
|
|
33
|
+
const cacheTTLHours = parseInt(process.env.BRUTALIST_CACHE_TTL_HOURS || '2', 10);
|
|
34
|
+
this.responseCache = new ResponseCache({
|
|
35
|
+
ttlHours: cacheTTLHours,
|
|
36
|
+
maxEntries: 50,
|
|
37
|
+
maxTotalSizeMB: 500,
|
|
38
|
+
maxEntrySizeMB: 10,
|
|
39
|
+
compressionThresholdMB: 1
|
|
40
|
+
});
|
|
41
|
+
logger.info(`📦 Response cache initialized with ${cacheTTLHours} hour TTL`);
|
|
28
42
|
this.server = new McpServer({
|
|
29
43
|
name: "brutalist-mcp",
|
|
30
44
|
version: PACKAGE_VERSION,
|
|
@@ -148,287 +162,18 @@ export class BrutalistServer {
|
|
|
148
162
|
});
|
|
149
163
|
}
|
|
150
164
|
registerTools() {
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
verbose: z.boolean().optional().describe("Include detailed execution information in output (default: false)"),
|
|
159
|
-
models: z.object({
|
|
160
|
-
claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
|
|
161
|
-
codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
|
|
162
|
-
gemini: z.enum(['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.5-flash-lite']).optional().describe("Gemini model")
|
|
163
|
-
}).optional().describe("Specific models to use for each CLI agent (defaults: codex=gpt-5, gemini=gemini-2.5-flash)"),
|
|
164
|
-
// Pagination parameters for large responses
|
|
165
|
-
offset: z.number().min(0).optional().describe("Character offset for response pagination (default: 0)"),
|
|
166
|
-
limit: z.number().min(1000).max(100000).optional().describe("Maximum characters per response chunk (default: 25000, max: 100000)"),
|
|
167
|
-
cursor: z.string().optional().describe("Pagination cursor from previous response (alternative to offset/limit)")
|
|
168
|
-
}, async (args, extra) => {
|
|
169
|
-
try {
|
|
170
|
-
const systemPrompt = `You are a battle-scarred principal engineer who has debugged production disasters for 15 years. Find security holes, performance bottlenecks, and maintainability nightmares in this codebase. Be brutal about what's broken but specific about what would actually work. Treat this like code that will kill people if it fails.`;
|
|
171
|
-
// Extract progressToken from request metadata for real-time streaming
|
|
172
|
-
const progressToken = extra._meta?.progressToken;
|
|
173
|
-
// Extract pagination parameters
|
|
174
|
-
const paginationParams = extractPaginationParams(args);
|
|
175
|
-
if (args.cursor) {
|
|
176
|
-
const cursorParams = parseCursor(args.cursor);
|
|
177
|
-
Object.assign(paginationParams, cursorParams);
|
|
178
|
-
}
|
|
179
|
-
const result = await this.executeBrutalistAnalysis("codebase", args.targetPath, systemPrompt, args.context, args.workingDirectory, args.enableSandbox, args.preferredCLI, args.verbose, args.models, progressToken);
|
|
180
|
-
return this.formatToolResponse(result, args.verbose, paginationParams);
|
|
181
|
-
}
|
|
182
|
-
catch (error) {
|
|
183
|
-
return this.formatErrorResponse(error);
|
|
184
|
-
}
|
|
185
|
-
});
|
|
186
|
-
// ROAST_FILE_STRUCTURE: Directory hierarchy demolition
|
|
187
|
-
this.server.tool("roast_file_structure", "Deploy brutal AI critics to systematically destroy your file organization. These agents will navigate your actual directory structure and expose every organizational disaster, naming convention failure, and structural nightmare that makes your codebase unmaintainable.", {
|
|
188
|
-
targetPath: z.string().describe("Directory path to analyze"),
|
|
189
|
-
depth: z.number().optional().describe("Maximum directory depth to analyze (default: 3)"),
|
|
190
|
-
context: z.string().optional().describe("Additional context about the project structure"),
|
|
191
|
-
workingDirectory: z.string().optional().describe("Working directory to execute from"),
|
|
192
|
-
preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
|
|
193
|
-
models: z.object({
|
|
194
|
-
claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
|
|
195
|
-
codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
|
|
196
|
-
gemini: z.enum(['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.5-flash-lite']).optional().describe("Gemini model")
|
|
197
|
-
}).optional().describe("Specific models to use for each CLI agent")
|
|
198
|
-
}, async (args) => {
|
|
199
|
-
try {
|
|
200
|
-
const systemPrompt = `You are a brutal file organization critic. Your job is to systematically destroy the given directory structure by finding every organizational disaster, naming convention failure, and structural nightmare that makes codebases unmaintainable. Examine folder hierarchies, file naming patterns, separation of concerns, and overall project organization. Be ruthlessly honest about how poor organization will slow development and confuse developers. But after cataloguing this organizational hellscape, sketch out what sanity would actually look like.`;
|
|
201
|
-
const result = await this.executeBrutalistAnalysis("fileStructure", args.targetPath, systemPrompt, `Project structure analysis (depth: ${args.depth || 3}). ${args.context || ''}`, args.workingDirectory, undefined, // enableSandbox
|
|
202
|
-
args.preferredCLI, undefined, // verbose
|
|
203
|
-
args.models);
|
|
204
|
-
return this.formatToolResponse(result);
|
|
205
|
-
}
|
|
206
|
-
catch (error) {
|
|
207
|
-
return this.formatErrorResponse(error);
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
// ROAST_DEPENDENCIES: Package management demolition
|
|
211
|
-
this.server.tool("roast_dependencies", "Deploy brutal AI critics to systematically destroy your dependency management. These agents will read your actual package files, analyze version conflicts, and expose every security vulnerability and compatibility nightmare in your dependency tree.", {
|
|
212
|
-
targetPath: z.string().describe("Path to package file (package.json, requirements.txt, Cargo.toml, etc.)"),
|
|
213
|
-
includeDevDeps: z.boolean().optional().describe("Include development dependencies in analysis (default: true)"),
|
|
214
|
-
context: z.string().optional().describe("Additional context about the project dependencies"),
|
|
215
|
-
workingDirectory: z.string().optional().describe("Working directory to execute from"),
|
|
216
|
-
preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
|
|
217
|
-
models: z.object({
|
|
218
|
-
claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
|
|
219
|
-
codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
|
|
220
|
-
gemini: z.enum(['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.5-flash-lite']).optional().describe("Gemini model")
|
|
221
|
-
}).optional().describe("Specific models to use for each CLI agent")
|
|
222
|
-
}, async (args) => {
|
|
223
|
-
try {
|
|
224
|
-
const systemPrompt = `You are a brutal dependency management critic. Your job is to systematically destroy the given dependency configuration by finding every security vulnerability, version conflict, compatibility nightmare, and bloat that will cause production failures. Examine package versions, security issues, licensing problems, and dependency tree complexity. Be ruthlessly honest about how poor dependency management will cause security breaches and deployment failures. After exposing this dependency dumpster fire, grudgingly admit what competent dependency management would require.`;
|
|
225
|
-
const result = await this.executeBrutalistAnalysis("dependencies", args.targetPath, systemPrompt, `Dependency analysis (dev deps: ${args.includeDevDeps ?? true}). ${args.context || ''}`, args.workingDirectory, undefined, // enableSandbox
|
|
226
|
-
args.preferredCLI, undefined, // verbose
|
|
227
|
-
args.models);
|
|
228
|
-
return this.formatToolResponse(result);
|
|
229
|
-
}
|
|
230
|
-
catch (error) {
|
|
231
|
-
return this.formatErrorResponse(error);
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
// ROAST_GIT_HISTORY: Version control demolition
|
|
235
|
-
this.server.tool("roast_git_history", "Deploy brutal AI critics to systematically destroy your git history and development practices. These agents will analyze your actual commit history, branching strategy, and code evolution to expose every workflow disaster and collaboration nightmare.", {
|
|
236
|
-
targetPath: z.string().describe("Git repository path to analyze"),
|
|
237
|
-
commitRange: z.string().optional().describe("Commit range to analyze (e.g., 'HEAD~10..HEAD', default: last 20 commits)"),
|
|
238
|
-
context: z.string().optional().describe("Additional context about the development workflow"),
|
|
239
|
-
workingDirectory: z.string().optional().describe("Working directory to execute from"),
|
|
240
|
-
preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
|
|
241
|
-
models: z.object({
|
|
242
|
-
claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
|
|
243
|
-
codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
|
|
244
|
-
gemini: z.enum(['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.5-flash-lite']).optional().describe("Gemini model")
|
|
245
|
-
}).optional().describe("Specific models to use for each CLI agent")
|
|
246
|
-
}, async (args) => {
|
|
247
|
-
try {
|
|
248
|
-
const systemPrompt = `You are a brutal git workflow critic. Your job is to systematically destroy the given git history and development practices by finding every workflow disaster, commit quality issue, and collaboration nightmare. Examine commit messages, branching strategies, merge patterns, and code evolution. Be ruthlessly honest about how poor git practices will cause deployment issues, collaboration failures, and development chaos. When you're done cataloguing this version control wasteland, reluctantly outline what professional git hygiene actually demands.`;
|
|
249
|
-
const result = await this.executeBrutalistAnalysis("gitHistory", args.targetPath, systemPrompt, `Git history analysis (range: ${args.commitRange || 'last 20 commits'}). ${args.context || ''}`, args.workingDirectory, undefined, // enableSandbox
|
|
250
|
-
args.preferredCLI, undefined, // verbose
|
|
251
|
-
args.models);
|
|
252
|
-
return this.formatToolResponse(result);
|
|
253
|
-
}
|
|
254
|
-
catch (error) {
|
|
255
|
-
return this.formatErrorResponse(error);
|
|
256
|
-
}
|
|
257
|
-
});
|
|
258
|
-
// ROAST_TEST_COVERAGE: Testing infrastructure demolition
|
|
259
|
-
this.server.tool("roast_test_coverage", "Deploy brutal AI critics to systematically destroy your testing strategy. These agents will analyze your actual test files, run coverage reports, and expose every testing gap and quality assurance nightmare that will let bugs slip into production.", {
|
|
260
|
-
targetPath: z.string().describe("Path to test directory or test configuration file"),
|
|
261
|
-
runCoverage: z.boolean().optional().describe("Attempt to run coverage analysis (default: true)"),
|
|
262
|
-
context: z.string().optional().describe("Additional context about the testing strategy"),
|
|
263
|
-
workingDirectory: z.string().optional().describe("Working directory to execute from"),
|
|
264
|
-
preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
|
|
265
|
-
models: z.object({
|
|
266
|
-
claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
|
|
267
|
-
codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
|
|
268
|
-
gemini: z.enum(['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.5-flash-lite']).optional().describe("Gemini model")
|
|
269
|
-
}).optional().describe("Specific models to use for each CLI agent")
|
|
270
|
-
}, async (args) => {
|
|
271
|
-
try {
|
|
272
|
-
const systemPrompt = `You are a brutal testing strategy critic. Your job is to systematically destroy the given testing approach by finding every testing gap, quality assurance nightmare, and coverage disaster that will let bugs slip into production. Examine test coverage, test quality, testing patterns, and CI/CD integration. Be ruthlessly honest about how poor testing will cause production failures and user-facing bugs. After dissecting this quality assurance horror show, begrudgingly spell out what it takes to actually catch bugs before users do.`;
|
|
273
|
-
const result = await this.executeBrutalistAnalysis("testCoverage", args.targetPath, systemPrompt, `Test coverage analysis (run coverage: ${args.runCoverage ?? true}). ${args.context || ''}`, args.workingDirectory, undefined, // enableSandbox
|
|
274
|
-
args.preferredCLI, undefined, // verbose
|
|
275
|
-
args.models);
|
|
276
|
-
return this.formatToolResponse(result);
|
|
277
|
-
}
|
|
278
|
-
catch (error) {
|
|
279
|
-
return this.formatErrorResponse(error);
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
// ROAST_IDEA: Any idea destruction
|
|
283
|
-
this.server.tool("roast_idea", "Deploy brutal AI critics to systematically destroy ANY idea - business, technical, creative, or otherwise. These critics understand the gap between imagination and reality, finding where your concept will encounter the immovable forces of the world. They are harsh about delusions but wise about what might actually survive.", {
|
|
284
|
-
idea: z.string().describe("ANY idea to analyze and demolish - business, technical, creative, or otherwise"),
|
|
285
|
-
context: z.string().optional().describe("Additional context about goals, constraints, or background"),
|
|
286
|
-
timeline: z.string().optional().describe("Expected timeline or deadline"),
|
|
287
|
-
resources: z.string().optional().describe("Available resources (budget, team, time, skills)"),
|
|
288
|
-
preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
|
|
289
|
-
models: z.object({
|
|
290
|
-
claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
|
|
291
|
-
codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
|
|
292
|
-
gemini: z.enum(['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.5-flash-lite']).optional().describe("Gemini model")
|
|
293
|
-
}).optional().describe("Specific models to use for each CLI agent")
|
|
294
|
-
}, async (args) => {
|
|
295
|
-
try {
|
|
296
|
-
const systemPrompt = `You are a brutal idea critic who understands the gap between imagination and reality. Your job is to systematically destroy the given idea by finding where it will encounter the immovable forces of the real world. Be ruthlessly honest about why most ideas fail when they meet practical constraints, human nature, physics, logic, or simple implementation reality. After demolishing the delusions, concede what salvage operations might actually work.`;
|
|
297
|
-
const result = await this.executeBrutalistAnalysis("idea", args.idea, systemPrompt, `Context: ${args.context || 'none'}, Timeline: ${args.timeline || 'unspecified'}, Resources: ${args.resources || 'unknown'}`, undefined, // workingDirectory
|
|
298
|
-
undefined, // enableSandbox
|
|
299
|
-
args.preferredCLI, undefined, // verbose
|
|
300
|
-
args.models);
|
|
301
|
-
return this.formatToolResponse(result);
|
|
302
|
-
}
|
|
303
|
-
catch (error) {
|
|
304
|
-
return this.formatErrorResponse(error);
|
|
305
|
-
}
|
|
306
|
-
});
|
|
307
|
-
// ROAST_ARCHITECTURE: System design demolition
|
|
308
|
-
this.server.tool("roast_architecture", "Deploy brutal AI critics to systematically destroy your system architecture. These critics have watched elegant designs collapse under real load, identifying every bottleneck, cost explosion, and scaling failure that will destroy your system. They are ruthless about why this won't survive production.", {
|
|
309
|
-
architecture: z.string().describe("Architecture description, diagram, or design document"),
|
|
310
|
-
scale: z.string().optional().describe("Expected scale/load (users, requests, data)"),
|
|
311
|
-
constraints: z.string().optional().describe("Budget, timeline, or technical constraints"),
|
|
312
|
-
deployment: z.string().optional().describe("Deployment environment and strategy"),
|
|
313
|
-
preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
|
|
314
|
-
models: z.object({
|
|
315
|
-
claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
|
|
316
|
-
codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
|
|
317
|
-
gemini: z.enum(['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.5-flash-lite']).optional().describe("Gemini model")
|
|
318
|
-
}).optional().describe("Specific models to use for each CLI agent")
|
|
319
|
-
}, async (args) => {
|
|
320
|
-
try {
|
|
321
|
-
const systemPrompt = `You are a brutal system architecture critic who has watched elegant designs collapse under real load. Your job is to systematically destroy the given architecture by finding every bottleneck, cost explosion, and scaling failure that will destroy the system in production. Examine scalability, reliability, cost, complexity, and operational challenges. Be ruthlessly honest about why this architecture won't survive production load. After crushing these architectural fantasies, reluctantly sketch what would actually scale without bankrupting the company.`;
|
|
322
|
-
const result = await this.executeBrutalistAnalysis("architecture", args.architecture, systemPrompt, `Scale: ${args.scale || 'unknown'}, Constraints: ${args.constraints || 'none specified'}, Deployment: ${args.deployment || 'unclear'}`, undefined, // workingDirectory
|
|
323
|
-
undefined, // enableSandbox
|
|
324
|
-
args.preferredCLI, undefined, // verbose
|
|
325
|
-
args.models);
|
|
326
|
-
return this.formatToolResponse(result);
|
|
327
|
-
}
|
|
328
|
-
catch (error) {
|
|
329
|
-
return this.formatErrorResponse(error);
|
|
330
|
-
}
|
|
331
|
-
});
|
|
332
|
-
// ROAST_RESEARCH: Academic project demolition
|
|
333
|
-
this.server.tool("roast_research", "Deploy brutal AI critics to systematically demolish your research methodology. These critics are supremely jaded peer reviewers who have rejected thousands of papers and watched countless studies fail to replicate. They find every statistical flaw, sampling bias, and reproducibility nightmare.", {
|
|
334
|
-
research: z.string().describe("Research description, methodology, or paper draft"),
|
|
335
|
-
field: z.string().optional().describe("Research field (ML, systems, theory, etc.)"),
|
|
336
|
-
claims: z.string().optional().describe("Main claims or contributions"),
|
|
337
|
-
data: z.string().optional().describe("Data sources, datasets, or experimental setup"),
|
|
338
|
-
preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
|
|
339
|
-
models: z.object({
|
|
340
|
-
claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
|
|
341
|
-
codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
|
|
342
|
-
gemini: z.enum(['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.5-flash-lite']).optional().describe("Gemini model")
|
|
343
|
-
}).optional().describe("Specific models to use for each CLI agent")
|
|
344
|
-
}, async (args) => {
|
|
345
|
-
try {
|
|
346
|
-
const systemPrompt = `You are a brutal research methodology critic - a supremely jaded peer reviewer who has rejected thousands of papers and watched countless studies fail to replicate. Your job is to systematically demolish the given research by finding every statistical flaw, sampling bias, reproducibility nightmare, and methodological disaster. Be ruthlessly honest about research quality, experimental design, and scientific rigor. After eviscerating this methodological train wreck, grudgingly admit what real science would demand.`;
|
|
347
|
-
const result = await this.executeBrutalistAnalysis("research", args.research, systemPrompt, `Field: ${args.field || 'unspecified'}, Claims: ${args.claims || 'unclear'}, Data: ${args.data || 'not provided'}`, undefined, // workingDirectory
|
|
348
|
-
undefined, // enableSandbox
|
|
349
|
-
args.preferredCLI, undefined, // verbose
|
|
350
|
-
args.models);
|
|
351
|
-
return this.formatToolResponse(result);
|
|
352
|
-
}
|
|
353
|
-
catch (error) {
|
|
354
|
-
return this.formatErrorResponse(error);
|
|
355
|
-
}
|
|
356
|
-
});
|
|
357
|
-
// ROAST_SECURITY: Security-focused attack vector analysis
|
|
358
|
-
this.server.tool("roast_security", "Deploy brutal AI critics to systematically annihilate your security design. These critics are battle-hardened penetration testers who find every authentication bypass, injection vulnerability, privilege escalation path, and social engineering opportunity that real attackers will exploit.", {
|
|
359
|
-
system: z.string().describe("System, application, or security design to analyze"),
|
|
360
|
-
assets: z.string().optional().describe("Critical assets or data to protect"),
|
|
361
|
-
threatModel: z.string().optional().describe("Known threats or attack vectors to consider"),
|
|
362
|
-
compliance: z.string().optional().describe("Compliance requirements (GDPR, HIPAA, etc.)"),
|
|
363
|
-
preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
|
|
364
|
-
models: z.object({
|
|
365
|
-
claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
|
|
366
|
-
codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
|
|
367
|
-
gemini: z.enum(['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.5-flash-lite']).optional().describe("Gemini model")
|
|
368
|
-
}).optional().describe("Specific models to use for each CLI agent")
|
|
369
|
-
}, async (args) => {
|
|
370
|
-
try {
|
|
371
|
-
const systemPrompt = `You are a brutal security critic - a battle-hardened penetration tester who finds every authentication bypass, injection vulnerability, privilege escalation path, and social engineering opportunity that real attackers will exploit. Your job is to systematically annihilate the given security design by finding every weakness that will lead to data breaches, system compromises, and security incidents. Be ruthlessly honest about security flaws and attack vectors. After obliterating these security delusions, begrudgingly outline what actual defense looks like.`;
|
|
372
|
-
const result = await this.executeBrutalistAnalysis("security", args.system, systemPrompt, `Assets: ${args.assets || 'unspecified'}, Threats: ${args.threatModel || 'unknown'}, Compliance: ${args.compliance || 'none specified'}`, undefined, // workingDirectory
|
|
373
|
-
undefined, // enableSandbox
|
|
374
|
-
args.preferredCLI, undefined, // verbose
|
|
375
|
-
args.models);
|
|
376
|
-
return this.formatToolResponse(result);
|
|
377
|
-
}
|
|
378
|
-
catch (error) {
|
|
379
|
-
return this.formatErrorResponse(error);
|
|
380
|
-
}
|
|
381
|
-
});
|
|
382
|
-
// ROAST_PRODUCT: UX and market reality criticism
|
|
383
|
-
this.server.tool("roast_product", "Deploy brutal AI critics to systematically eviscerate your product concept. These critics are product veterans who understand why users really abandon things, finding every usability disaster, adoption barrier, and workflow failure that will drive users away in seconds.", {
|
|
384
|
-
product: z.string().describe("Product description, features, or user experience to analyze"),
|
|
385
|
-
users: z.string().optional().describe("Target users or user personas"),
|
|
386
|
-
competition: z.string().optional().describe("Competitive landscape or alternatives"),
|
|
387
|
-
metrics: z.string().optional().describe("Success metrics or KPIs"),
|
|
388
|
-
preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
|
|
389
|
-
models: z.object({
|
|
390
|
-
claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
|
|
391
|
-
codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
|
|
392
|
-
gemini: z.enum(['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.5-flash-lite']).optional().describe("Gemini model")
|
|
393
|
-
}).optional().describe("Specific models to use for each CLI agent")
|
|
394
|
-
}, async (args) => {
|
|
395
|
-
try {
|
|
396
|
-
const systemPrompt = `You are a brutal product critic - a product veteran who understands why users really abandon things. Your job is to systematically eviscerate the given product concept by finding every usability disaster, adoption barrier, and workflow failure that will drive users away in seconds. Examine user experience, market fit, competitive positioning, and business model viability. Be ruthlessly honest about why most products fail to gain adoption. After torching this product disaster, reluctantly suggest what might actually get users to stick around.`;
|
|
397
|
-
const result = await this.executeBrutalistAnalysis("product", args.product, systemPrompt, `Users: ${args.users || 'unclear'}, Competition: ${args.competition || 'unknown'}, Metrics: ${args.metrics || 'undefined'}`, undefined, // workingDirectory
|
|
398
|
-
undefined, // enableSandbox
|
|
399
|
-
args.preferredCLI, undefined, // verbose
|
|
400
|
-
args.models);
|
|
401
|
-
return this.formatToolResponse(result);
|
|
402
|
-
}
|
|
403
|
-
catch (error) {
|
|
404
|
-
return this.formatErrorResponse(error);
|
|
405
|
-
}
|
|
406
|
-
});
|
|
407
|
-
// ROAST_INFRASTRUCTURE: DevOps and operations demolition
|
|
408
|
-
this.server.tool("roast_infrastructure", "Deploy brutal AI critics to systematically obliterate your infrastructure design. These critics are grizzled site reliability engineers who find every single point of failure, scaling bottleneck, and operational nightmare that will cause outages when you least expect them.", {
|
|
409
|
-
infrastructure: z.string().describe("Infrastructure setup, deployment strategy, or operations plan"),
|
|
410
|
-
scale: z.string().optional().describe("Expected scale and load patterns"),
|
|
411
|
-
budget: z.string().optional().describe("Infrastructure budget or cost constraints"),
|
|
412
|
-
sla: z.string().optional().describe("SLA requirements or uptime targets"),
|
|
413
|
-
preferredCLI: z.enum(["claude", "codex", "gemini"]).optional().describe("Preferred CLI agent to use (default: use all available CLIs)"),
|
|
414
|
-
models: z.object({
|
|
415
|
-
claude: z.string().optional().describe("Claude model: opus, sonnet, or full name like claude-opus-4-1-20250805"),
|
|
416
|
-
codex: z.string().optional().describe("Codex model: gpt-5, gpt-5-codex, o3, o3-mini, o3-pro, o4-mini"),
|
|
417
|
-
gemini: z.enum(['gemini-2.5-flash', 'gemini-2.5-pro', 'gemini-2.5-flash-lite']).optional().describe("Gemini model")
|
|
418
|
-
}).optional().describe("Specific models to use for each CLI agent")
|
|
419
|
-
}, async (args) => {
|
|
420
|
-
try {
|
|
421
|
-
const systemPrompt = `You are a brutal infrastructure critic - a grizzled site reliability engineer who finds every single point of failure, scaling bottleneck, and operational nightmare that will cause outages when you least expect them. Your job is to systematically obliterate the given infrastructure design by finding every weakness that will lead to downtime, cost overruns, and operational disasters. Be ruthlessly honest about infrastructure fragility and operational complexity. After demolishing this infrastructure fever dream, grudgingly map out what actually stays up at 3 AM.`;
|
|
422
|
-
const result = await this.executeBrutalistAnalysis("infrastructure", args.infrastructure, systemPrompt, `Scale: ${args.scale || 'unknown'}, Budget: ${args.budget || 'unlimited?'}, SLA: ${args.sla || 'undefined'}`, undefined, // workingDirectory
|
|
423
|
-
undefined, // enableSandbox
|
|
424
|
-
args.preferredCLI, undefined, // verbose
|
|
425
|
-
args.models);
|
|
426
|
-
return this.formatToolResponse(result);
|
|
427
|
-
}
|
|
428
|
-
catch (error) {
|
|
429
|
-
return this.formatErrorResponse(error);
|
|
430
|
-
}
|
|
165
|
+
// Register all roast tools using unified handler - DRY principle
|
|
166
|
+
TOOL_CONFIGS.forEach(config => {
|
|
167
|
+
const schema = {
|
|
168
|
+
...config.schemaExtensions,
|
|
169
|
+
...BASE_ROAST_SCHEMA
|
|
170
|
+
};
|
|
171
|
+
this.server.tool(config.name, config.description, schema, async (args, extra) => this.handleRoastTool(config, args, extra));
|
|
431
172
|
});
|
|
173
|
+
// Register special tools that don't follow the pattern
|
|
174
|
+
this.registerSpecialTools();
|
|
175
|
+
}
|
|
176
|
+
registerSpecialTools() {
|
|
432
177
|
// ROAST_CLI_DEBATE: Adversarial analysis between different CLI agents
|
|
433
178
|
this.server.tool("roast_cli_debate", "Deploy CLI agents in structured adversarial debate. Agents take opposing positions and systematically challenge each other's reasoning. Perfect for exploring complex topics from multiple perspectives and stress-testing ideas through rigorous intellectual discourse.", {
|
|
434
179
|
targetPath: z.string().describe("Topic, question, or concept to debate (NOT a file path - use natural language)"),
|
|
@@ -479,6 +224,12 @@ export class BrutalistServer {
|
|
|
479
224
|
roster += `**Available CLIs:** ${cliContext.availableCLIs.join(', ') || 'None detected'}\n`;
|
|
480
225
|
roster += `**Current CLI:** ${cliContext.currentCLI || 'Unknown'}\n`;
|
|
481
226
|
roster += `**Smart Routing:** ${cliContext.currentCLI ? `Excludes ${cliContext.currentCLI} for analysis` : 'Uses all available CLIs'}\n\n`;
|
|
227
|
+
roster += "## Pagination Support (NEW in v0.5.2)\n";
|
|
228
|
+
roster += "**All tools now support intelligent pagination:**\n";
|
|
229
|
+
roster += "- Analysis results are cached with 2-hour TTL\n";
|
|
230
|
+
roster += "- Use `analysis_id` from response to paginate without re-running\n";
|
|
231
|
+
roster += "- Smart text chunking preserves readability\n";
|
|
232
|
+
roster += "- Example: `roast_codebase(analysis_id: 'a3f5c2d8', offset: 25000)`\n\n";
|
|
482
233
|
roster += "## Brutalist Philosophy\n";
|
|
483
234
|
roster += "*All tools use CLI agents with brutal system prompts for maximum reality-based criticism.*\n";
|
|
484
235
|
return {
|
|
@@ -490,6 +241,88 @@ export class BrutalistServer {
|
|
|
490
241
|
}
|
|
491
242
|
});
|
|
492
243
|
}
|
|
244
|
+
/**
|
|
245
|
+
* Unified handler for all roast tools - DRY principle
|
|
246
|
+
*/
|
|
247
|
+
async handleRoastTool(config, args, extra) {
|
|
248
|
+
try {
|
|
249
|
+
const progressToken = extra._meta?.progressToken;
|
|
250
|
+
// Extract pagination parameters
|
|
251
|
+
const paginationParams = extractPaginationParams(args);
|
|
252
|
+
if (args.cursor) {
|
|
253
|
+
const cursorParams = parseCursor(args.cursor);
|
|
254
|
+
Object.assign(paginationParams, cursorParams);
|
|
255
|
+
}
|
|
256
|
+
// Check cache if analysis_id provided
|
|
257
|
+
if (args.analysis_id && !args.force_refresh) {
|
|
258
|
+
const cachedContent = await this.responseCache.get(args.analysis_id);
|
|
259
|
+
if (cachedContent) {
|
|
260
|
+
logger.info(`🎯 Using cached result for analysis_id: ${args.analysis_id}`);
|
|
261
|
+
const cachedResult = {
|
|
262
|
+
success: true,
|
|
263
|
+
responses: [{
|
|
264
|
+
agent: 'cached',
|
|
265
|
+
success: true,
|
|
266
|
+
output: cachedContent,
|
|
267
|
+
executionTime: 0
|
|
268
|
+
}]
|
|
269
|
+
};
|
|
270
|
+
return this.formatToolResponse(cachedResult, args.verbose, paginationParams, args.analysis_id);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
// Generate cache key for this request
|
|
274
|
+
const cacheKey = this.responseCache.generateCacheKey(config.cacheKeyFields.reduce((acc, field) => {
|
|
275
|
+
acc.tool = config.name;
|
|
276
|
+
if (args[field] !== undefined)
|
|
277
|
+
acc[field] = args[field];
|
|
278
|
+
return acc;
|
|
279
|
+
}, {}));
|
|
280
|
+
// Check if we have a cached result (unless forcing refresh)
|
|
281
|
+
if (!args.force_refresh) {
|
|
282
|
+
const cachedContent = await this.responseCache.get(cacheKey);
|
|
283
|
+
if (cachedContent) {
|
|
284
|
+
const analysisId = this.responseCache.generateAnalysisId(cacheKey);
|
|
285
|
+
logger.info(`🎯 Cache hit for new request, using analysis_id: ${analysisId}`);
|
|
286
|
+
const cachedResult = {
|
|
287
|
+
success: true,
|
|
288
|
+
responses: [{
|
|
289
|
+
agent: 'cached',
|
|
290
|
+
success: true,
|
|
291
|
+
output: cachedContent,
|
|
292
|
+
executionTime: 0
|
|
293
|
+
}]
|
|
294
|
+
};
|
|
295
|
+
return this.formatToolResponse(cachedResult, args.verbose, paginationParams, analysisId);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
// Build context with custom builder if available
|
|
299
|
+
const context = config.contextBuilder ? config.contextBuilder(args) : args.context;
|
|
300
|
+
// Get the primary argument (targetPath, idea, architecture, etc.)
|
|
301
|
+
const primaryArg = args[config.primaryArgField];
|
|
302
|
+
// Run the analysis
|
|
303
|
+
const result = await this.executeBrutalistAnalysis(config.analysisType, primaryArg, config.systemPrompt, context, args.workingDirectory, args.enableSandbox, args.preferredCLI, args.verbose, args.models, progressToken);
|
|
304
|
+
// Cache the result if successful
|
|
305
|
+
let analysisId;
|
|
306
|
+
if (result.success && result.responses.length > 0) {
|
|
307
|
+
const fullContent = this.extractFullContent(result);
|
|
308
|
+
if (fullContent) {
|
|
309
|
+
const cacheData = config.cacheKeyFields.reduce((acc, field) => {
|
|
310
|
+
acc.tool = config.name;
|
|
311
|
+
if (args[field] !== undefined)
|
|
312
|
+
acc[field] = args[field];
|
|
313
|
+
return acc;
|
|
314
|
+
}, {});
|
|
315
|
+
const { analysisId: newId } = await this.responseCache.set(cacheData, fullContent, cacheKey);
|
|
316
|
+
analysisId = newId;
|
|
317
|
+
logger.info(`✅ Cached analysis result with ID: ${analysisId}`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return this.formatToolResponse(result, args.verbose, paginationParams, analysisId);
|
|
321
|
+
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
return this.formatErrorResponse(error);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
493
326
|
async executeCLIDebate(targetPath, debateRounds, context, workingDirectory, enableSandbox, models) {
|
|
494
327
|
logger.debug("Executing CLI debate", {
|
|
495
328
|
targetPath,
|
|
@@ -743,7 +576,32 @@ Remember: You are ${currentAgent.toUpperCase()}, passionate advocate for ${assig
|
|
|
743
576
|
throw error;
|
|
744
577
|
}
|
|
745
578
|
}
|
|
746
|
-
|
|
579
|
+
/**
|
|
580
|
+
* Extract full content from analysis result for caching
|
|
581
|
+
*/
|
|
582
|
+
extractFullContent(result) {
|
|
583
|
+
if (result.synthesis) {
|
|
584
|
+
return result.synthesis;
|
|
585
|
+
}
|
|
586
|
+
else if (result.responses && result.responses.length > 0) {
|
|
587
|
+
const successfulResponses = result.responses.filter(r => r.success);
|
|
588
|
+
if (successfulResponses.length > 0) {
|
|
589
|
+
let output = `${successfulResponses.length} AI critics have systematically demolished your work.\n\n`;
|
|
590
|
+
successfulResponses.forEach((response, index) => {
|
|
591
|
+
output += `## Critic ${index + 1}: ${response.agent.toUpperCase()}\n`;
|
|
592
|
+
output += `*Execution time: ${response.executionTime}ms*\n\n`;
|
|
593
|
+
output += response.output;
|
|
594
|
+
// Only add separator between critics, not after the last one
|
|
595
|
+
if (index < successfulResponses.length - 1) {
|
|
596
|
+
output += '\n\n---\n\n';
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
return output;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
formatToolResponse(result, verbose = false, paginationParams, analysisId) {
|
|
747
605
|
logger.info(`🔧 DEBUG: formatToolResponse called with synthesis length: ${result.synthesis?.length || 0}`);
|
|
748
606
|
logger.info(`🔧 DEBUG: result.success=${result.success}, responses.length=${result.responses?.length || 0}`);
|
|
749
607
|
logger.info(`🔧 DEBUG: pagination params:`, paginationParams);
|
|
@@ -762,7 +620,7 @@ Remember: You are ${currentAgent.toUpperCase()}, passionate advocate for ${assig
|
|
|
762
620
|
}
|
|
763
621
|
// Handle pagination if params provided and content is substantial
|
|
764
622
|
if (paginationParams && primaryContent) {
|
|
765
|
-
return this.formatPaginatedResponse(primaryContent, paginationParams, result, verbose);
|
|
623
|
+
return this.formatPaginatedResponse(primaryContent, paginationParams, result, verbose, analysisId);
|
|
766
624
|
}
|
|
767
625
|
// Non-paginated response (legacy behavior)
|
|
768
626
|
if (primaryContent) {
|
|
@@ -795,37 +653,63 @@ Remember: You are ${currentAgent.toUpperCase()}, passionate advocate for ${assig
|
|
|
795
653
|
}]
|
|
796
654
|
};
|
|
797
655
|
}
|
|
798
|
-
formatPaginatedResponse(content, paginationParams, result, verbose) {
|
|
656
|
+
formatPaginatedResponse(content, paginationParams, result, verbose, analysisId) {
|
|
799
657
|
// Using imported pagination utilities
|
|
800
658
|
const offset = paginationParams.offset || 0;
|
|
801
659
|
const limit = paginationParams.limit || PAGINATION_DEFAULTS.DEFAULT_LIMIT;
|
|
802
660
|
logger.info(`🔧 DEBUG: Paginating content - offset: ${offset}, limit: ${limit}, total: ${content.length}`);
|
|
803
|
-
//
|
|
804
|
-
const
|
|
805
|
-
const
|
|
661
|
+
// Use ResponseChunker for intelligent boundary detection
|
|
662
|
+
const chunker = new ResponseChunker(limit, 200); // 200 char overlap
|
|
663
|
+
const chunks = chunker.chunkText(content);
|
|
664
|
+
// Find the appropriate chunk based on offset
|
|
665
|
+
let targetChunk = chunks[0]; // Default to first chunk
|
|
666
|
+
let currentOffset = 0;
|
|
667
|
+
for (const chunk of chunks) {
|
|
668
|
+
if (offset >= chunk.startOffset && offset < chunk.endOffset) {
|
|
669
|
+
targetChunk = chunk;
|
|
670
|
+
break;
|
|
671
|
+
}
|
|
672
|
+
currentOffset = chunk.endOffset;
|
|
673
|
+
}
|
|
674
|
+
const chunkContent = targetChunk.content;
|
|
675
|
+
const actualOffset = targetChunk.startOffset;
|
|
676
|
+
const endOffset = targetChunk.endOffset;
|
|
806
677
|
// Create pagination metadata
|
|
807
678
|
const pagination = createPaginationMetadata(content.length, paginationParams, limit);
|
|
808
679
|
const statusLine = formatPaginationStatus(pagination);
|
|
809
680
|
// Estimate token usage for user awareness
|
|
810
|
-
const chunkTokens = estimateTokenCount(
|
|
681
|
+
const chunkTokens = estimateTokenCount(chunkContent);
|
|
811
682
|
const totalTokens = estimateTokenCount(content);
|
|
812
683
|
// Format response with pagination info
|
|
813
684
|
let paginatedText = '';
|
|
814
|
-
// Add pagination header
|
|
685
|
+
// Add pagination header with analysis ID
|
|
815
686
|
paginatedText += `# Brutalist Analysis Results\n\n`;
|
|
816
687
|
paginatedText += `**📊 Pagination Status:** ${statusLine}\n`;
|
|
688
|
+
if (analysisId) {
|
|
689
|
+
paginatedText += `**🔑 Analysis ID:** ${analysisId}\n`;
|
|
690
|
+
}
|
|
817
691
|
paginatedText += `**🔢 Token Estimate:** ~${chunkTokens.toLocaleString()} tokens (chunk) / ~${totalTokens.toLocaleString()} tokens (total)\n\n`;
|
|
818
692
|
if (pagination.hasMore) {
|
|
819
|
-
|
|
693
|
+
if (analysisId) {
|
|
694
|
+
paginatedText += `**⏭️ Continue Reading:** Use \`analysis_id: "${analysisId}", offset: ${endOffset}\`\n\n`;
|
|
695
|
+
}
|
|
696
|
+
else {
|
|
697
|
+
paginatedText += `**⏭️ Continue Reading:** Use \`offset: ${endOffset}\` for next chunk\n\n`;
|
|
698
|
+
}
|
|
820
699
|
}
|
|
821
700
|
paginatedText += `---\n\n`;
|
|
822
701
|
// Add the actual content chunk
|
|
823
|
-
paginatedText +=
|
|
702
|
+
paginatedText += chunkContent;
|
|
824
703
|
// Add footer for continuation
|
|
825
704
|
if (pagination.hasMore) {
|
|
826
705
|
paginatedText += `\n\n---\n\n`;
|
|
827
706
|
paginatedText += `📖 **End of chunk ${pagination.chunkIndex}/${pagination.totalChunks}**\n`;
|
|
828
|
-
|
|
707
|
+
if (analysisId) {
|
|
708
|
+
paginatedText += `🔄 To continue: Include \`analysis_id: "${analysisId}"\` with \`offset: ${endOffset}\` in next request`;
|
|
709
|
+
}
|
|
710
|
+
else {
|
|
711
|
+
paginatedText += `🔄 To continue: Use same tool with \`offset: ${endOffset}\``;
|
|
712
|
+
}
|
|
829
713
|
}
|
|
830
714
|
else {
|
|
831
715
|
paginatedText += `\n\n---\n\n`;
|
|
@@ -840,7 +724,7 @@ Remember: You are ${currentAgent.toUpperCase()}, passionate advocate for ${assig
|
|
|
840
724
|
paginatedText += `- **Selected CLI:** ${result.executionSummary.selectedCLI}\n`;
|
|
841
725
|
}
|
|
842
726
|
}
|
|
843
|
-
logger.info(`🔧 DEBUG: Returning paginated chunk - ${
|
|
727
|
+
logger.info(`🔧 DEBUG: Returning paginated chunk - ${chunkContent.length} chars (${chunkTokens} tokens)`);
|
|
844
728
|
return {
|
|
845
729
|
content: [{
|
|
846
730
|
type: "text",
|