@stackmemoryai/stackmemory 1.0.0 → 1.2.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 +66 -270
- package/dist/src/cli/claude-sm.js +9 -0
- package/dist/src/cli/codex-sm.js +9 -0
- package/dist/src/cli/commands/audit.js +134 -0
- package/dist/src/cli/commands/bench.js +252 -0
- package/dist/src/cli/commands/dashboard.js +2 -1
- package/dist/src/cli/commands/stats.js +118 -0
- package/dist/src/cli/index.js +6 -0
- package/dist/src/core/config/feature-flags.js +7 -1
- package/dist/src/core/context/enhanced-rehydration.js +24 -5
- package/dist/src/core/extensions/cerebras-adapter.js +28 -0
- package/dist/src/core/extensions/deepinfra-adapter.js +32 -0
- package/dist/src/core/extensions/provider-adapter.js +33 -240
- package/dist/src/core/models/complexity-scorer.js +154 -0
- package/dist/src/core/models/model-router.js +230 -36
- package/dist/src/core/models/provider-pricing.js +63 -0
- package/dist/src/core/models/sensitive-guard.js +112 -0
- package/dist/src/core/monitoring/feedback-loops.js +88 -0
- package/dist/src/features/sweep/pty-wrapper.js +9 -0
- package/dist/src/hooks/daemon.js +8 -0
- package/dist/src/hooks/graphiti-hooks.js +104 -0
- package/dist/src/hooks/schemas.js +12 -1
- package/dist/src/integrations/anthropic/batch-client.js +256 -0
- package/dist/src/integrations/anthropic/client.js +87 -72
- package/dist/src/integrations/claude-code/subagent-client.js +133 -12
- package/dist/src/integrations/graphiti/client.js +115 -0
- package/dist/src/integrations/graphiti/config.js +17 -0
- package/dist/src/integrations/graphiti/types.js +4 -0
- package/dist/src/integrations/mcp/handlers/index.js +25 -1
- package/dist/src/integrations/mcp/handlers/provider-handlers.js +227 -0
- package/dist/src/integrations/mcp/server.js +207 -1
- package/dist/src/integrations/mcp/tool-definitions.js +38 -1
- package/dist/src/orchestrators/multimodal/baselines.js +128 -0
- package/dist/src/orchestrators/multimodal/constants.js +9 -1
- package/dist/src/orchestrators/multimodal/harness.js +86 -6
- package/dist/src/orchestrators/multimodal/providers.js +113 -2
- package/dist/src/skills/recursive-agent-orchestrator.js +15 -10
- package/dist/src/utils/fuzzy-edit.js +162 -0
- package/package.json +5 -1
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
2
|
+
import { dirname as __pathDirname } from 'path';
|
|
3
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
4
|
+
const __dirname = __pathDirname(__filename);
|
|
5
|
+
import { logger } from "../core/monitoring/logger.js";
|
|
6
|
+
import { GraphitiClient } from "../integrations/graphiti/client.js";
|
|
7
|
+
import { DEFAULT_GRAPHITI_CONFIG } from "../integrations/graphiti/config.js";
|
|
8
|
+
class GraphitiHooks {
|
|
9
|
+
client;
|
|
10
|
+
config;
|
|
11
|
+
constructor(config = {}) {
|
|
12
|
+
this.config = { ...DEFAULT_GRAPHITI_CONFIG, ...config };
|
|
13
|
+
this.client = new GraphitiClient(this.config);
|
|
14
|
+
}
|
|
15
|
+
register(emitter) {
|
|
16
|
+
if (!this.config.enabled) {
|
|
17
|
+
logger.debug("Graphiti hooks disabled");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
emitter.registerHandler("session_start", this.onSessionStart.bind(this));
|
|
21
|
+
emitter.registerHandler("file_change", this.onFileChange.bind(this));
|
|
22
|
+
emitter.registerHandler("session_end", this.onSessionEnd.bind(this));
|
|
23
|
+
logger.info("Graphiti hooks registered", {
|
|
24
|
+
endpoint: this.config.endpoint,
|
|
25
|
+
backend: this.config.backend,
|
|
26
|
+
maxHops: this.config.maxHops
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
async onSessionStart(event) {
|
|
30
|
+
try {
|
|
31
|
+
const status = await this.client.getStatus();
|
|
32
|
+
if (!status.connected) {
|
|
33
|
+
logger.warn("Graphiti not available - operating in degraded mode");
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const episode = {
|
|
37
|
+
type: "session_start",
|
|
38
|
+
content: event.data || {},
|
|
39
|
+
timestamp: Date.now(),
|
|
40
|
+
source: "stackmemory",
|
|
41
|
+
metadata: { severity: "info" }
|
|
42
|
+
};
|
|
43
|
+
await this.client.upsertEpisode(episode);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
logger.debug("Graphiti session_start failed", {
|
|
46
|
+
error: error instanceof Error ? error.message : String(error)
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async onFileChange(event) {
|
|
51
|
+
const fileEvent = event;
|
|
52
|
+
try {
|
|
53
|
+
const episode = {
|
|
54
|
+
type: "file_change",
|
|
55
|
+
content: {
|
|
56
|
+
path: fileEvent.data.path,
|
|
57
|
+
changeType: fileEvent.data.changeType,
|
|
58
|
+
size: typeof fileEvent.data.content === "string" ? fileEvent.data.content.length : void 0
|
|
59
|
+
},
|
|
60
|
+
timestamp: Date.now(),
|
|
61
|
+
source: "stackmemory"
|
|
62
|
+
};
|
|
63
|
+
await this.client.upsertEpisode(episode);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
logger.debug("Graphiti file_change episode failed", {
|
|
66
|
+
error: error instanceof Error ? error.message : String(error)
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async onSessionEnd(event) {
|
|
71
|
+
try {
|
|
72
|
+
const episode = {
|
|
73
|
+
type: "session_end",
|
|
74
|
+
content: event.data || {},
|
|
75
|
+
timestamp: Date.now(),
|
|
76
|
+
source: "stackmemory"
|
|
77
|
+
};
|
|
78
|
+
await this.client.upsertEpisode(episode);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
logger.debug("Graphiti session_end failed", {
|
|
81
|
+
error: error instanceof Error ? error.message : String(error)
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Expose a simple temporal query helper for future MCP tooling
|
|
86
|
+
async buildTemporalContext(query = {}) {
|
|
87
|
+
const now = Date.now();
|
|
88
|
+
const q = {
|
|
89
|
+
query: query.query || void 0,
|
|
90
|
+
entityTypes: query.entityTypes || void 0,
|
|
91
|
+
relationTypes: query.relationTypes || void 0,
|
|
92
|
+
validFrom: query.validFrom ?? now - 1e3 * 60 * 60 * 24 * 30,
|
|
93
|
+
// 30d default
|
|
94
|
+
validTo: query.validTo ?? now,
|
|
95
|
+
maxHops: query.maxHops ?? this.config.maxHops,
|
|
96
|
+
k: query.k ?? 20,
|
|
97
|
+
rerank: query.rerank ?? true
|
|
98
|
+
};
|
|
99
|
+
return this.client.queryTemporal(q);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
export {
|
|
103
|
+
GraphitiHooks
|
|
104
|
+
};
|
|
@@ -16,6 +16,10 @@ const ModelProviderSchema = z.enum([
|
|
|
16
16
|
"qwen",
|
|
17
17
|
"openai",
|
|
18
18
|
"ollama",
|
|
19
|
+
"cerebras",
|
|
20
|
+
"deepinfra",
|
|
21
|
+
"openrouter",
|
|
22
|
+
"anthropic-batch",
|
|
19
23
|
"custom"
|
|
20
24
|
]);
|
|
21
25
|
const ModelConfigSchema = z.object({
|
|
@@ -33,7 +37,10 @@ const ModelRouterConfigSchema = z.object({
|
|
|
33
37
|
plan: ModelProviderSchema.optional(),
|
|
34
38
|
think: ModelProviderSchema.optional(),
|
|
35
39
|
code: ModelProviderSchema.optional(),
|
|
36
|
-
review: ModelProviderSchema.optional()
|
|
40
|
+
review: ModelProviderSchema.optional(),
|
|
41
|
+
linting: ModelProviderSchema.optional(),
|
|
42
|
+
context: ModelProviderSchema.optional(),
|
|
43
|
+
testing: ModelProviderSchema.optional()
|
|
37
44
|
}).optional().default({}),
|
|
38
45
|
fallback: z.object({
|
|
39
46
|
enabled: z.boolean(),
|
|
@@ -49,6 +56,10 @@ const ModelRouterConfigSchema = z.object({
|
|
|
49
56
|
qwen: ModelConfigSchema.optional(),
|
|
50
57
|
openai: ModelConfigSchema.optional(),
|
|
51
58
|
ollama: ModelConfigSchema.optional(),
|
|
59
|
+
cerebras: ModelConfigSchema.optional(),
|
|
60
|
+
deepinfra: ModelConfigSchema.optional(),
|
|
61
|
+
openrouter: ModelConfigSchema.optional(),
|
|
62
|
+
"anthropic-batch": ModelConfigSchema.optional(),
|
|
52
63
|
custom: ModelConfigSchema.optional()
|
|
53
64
|
}).optional().default({}),
|
|
54
65
|
thinkingMode: z.object({
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
2
|
+
import { dirname as __pathDirname } from 'path';
|
|
3
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
4
|
+
const __dirname = __pathDirname(__filename);
|
|
5
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
import { logger } from "../../core/monitoring/logger.js";
|
|
9
|
+
const BATCH_JOBS_PATH = join(homedir(), ".stackmemory", "batch-jobs.json");
|
|
10
|
+
const DEFAULT_POLL_INTERVAL_MS = 3e4;
|
|
11
|
+
const DEFAULT_TIMEOUT_MS = 2 * 60 * 60 * 1e3;
|
|
12
|
+
class AnthropicBatchClient {
|
|
13
|
+
apiKey;
|
|
14
|
+
baseUrl;
|
|
15
|
+
mockMode;
|
|
16
|
+
constructor(config) {
|
|
17
|
+
this.apiKey = config?.apiKey !== void 0 ? config.apiKey : process.env["ANTHROPIC_API_KEY"] || "";
|
|
18
|
+
this.baseUrl = config?.baseUrl || "https://api.anthropic.com";
|
|
19
|
+
this.mockMode = config?.mockMode ?? !this.apiKey;
|
|
20
|
+
if (this.mockMode) {
|
|
21
|
+
logger.warn("AnthropicBatchClient: no API key, using mock mode");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Submit a batch of requests
|
|
26
|
+
*/
|
|
27
|
+
async submit(requests, description) {
|
|
28
|
+
if (this.mockMode) {
|
|
29
|
+
const batchId = `batch_mock_${Date.now()}`;
|
|
30
|
+
this.persistJob({
|
|
31
|
+
batchId,
|
|
32
|
+
status: "ended",
|
|
33
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
34
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
35
|
+
requestCount: requests.length,
|
|
36
|
+
description
|
|
37
|
+
});
|
|
38
|
+
return batchId;
|
|
39
|
+
}
|
|
40
|
+
const response = await fetch(`${this.baseUrl}/v1/messages/batches`, {
|
|
41
|
+
method: "POST",
|
|
42
|
+
headers: {
|
|
43
|
+
"Content-Type": "application/json",
|
|
44
|
+
"x-api-key": this.apiKey,
|
|
45
|
+
"anthropic-version": "2023-06-01"
|
|
46
|
+
},
|
|
47
|
+
body: JSON.stringify({ requests })
|
|
48
|
+
});
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
const errText = await response.text();
|
|
51
|
+
throw new Error(`Batch submit failed: ${response.status} ${errText}`);
|
|
52
|
+
}
|
|
53
|
+
const job = await response.json();
|
|
54
|
+
this.persistJob({
|
|
55
|
+
batchId: job.id,
|
|
56
|
+
status: job.processing_status,
|
|
57
|
+
createdAt: job.created_at,
|
|
58
|
+
requestCount: requests.length,
|
|
59
|
+
description
|
|
60
|
+
});
|
|
61
|
+
logger.info("Batch submitted", { batchId: job.id, count: requests.length });
|
|
62
|
+
return job.id;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Poll batch job status
|
|
66
|
+
*/
|
|
67
|
+
async poll(batchId) {
|
|
68
|
+
if (this.mockMode) {
|
|
69
|
+
return this.mockBatchJob(batchId);
|
|
70
|
+
}
|
|
71
|
+
const response = await fetch(
|
|
72
|
+
`${this.baseUrl}/v1/messages/batches/${batchId}`,
|
|
73
|
+
{
|
|
74
|
+
headers: {
|
|
75
|
+
"x-api-key": this.apiKey,
|
|
76
|
+
"anthropic-version": "2023-06-01"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
const errText = await response.text();
|
|
82
|
+
throw new Error(`Batch poll failed: ${response.status} ${errText}`);
|
|
83
|
+
}
|
|
84
|
+
const job = await response.json();
|
|
85
|
+
this.updateJobStatus(batchId, job.processing_status, job.ended_at);
|
|
86
|
+
return job;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Retrieve batch results
|
|
90
|
+
*/
|
|
91
|
+
async retrieve(batchId) {
|
|
92
|
+
if (this.mockMode) {
|
|
93
|
+
return this.mockBatchResults(batchId);
|
|
94
|
+
}
|
|
95
|
+
const job = await this.poll(batchId);
|
|
96
|
+
if (job.processing_status !== "ended") {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`Batch ${batchId} not finished: ${job.processing_status}`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
if (!job.results_url) {
|
|
102
|
+
throw new Error(`Batch ${batchId} has no results URL`);
|
|
103
|
+
}
|
|
104
|
+
const response = await fetch(job.results_url, {
|
|
105
|
+
headers: {
|
|
106
|
+
"x-api-key": this.apiKey,
|
|
107
|
+
"anthropic-version": "2023-06-01"
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
if (!response.ok) {
|
|
111
|
+
throw new Error(`Batch retrieve failed: ${response.status}`);
|
|
112
|
+
}
|
|
113
|
+
const text = await response.text();
|
|
114
|
+
return text.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Poll until batch completes or times out
|
|
118
|
+
*/
|
|
119
|
+
async waitForCompletion(batchId, timeoutMs = DEFAULT_TIMEOUT_MS, pollIntervalMs = DEFAULT_POLL_INTERVAL_MS) {
|
|
120
|
+
const deadline = Date.now() + timeoutMs;
|
|
121
|
+
while (Date.now() < deadline) {
|
|
122
|
+
const job = await this.poll(batchId);
|
|
123
|
+
if (job.processing_status === "ended") {
|
|
124
|
+
return job;
|
|
125
|
+
}
|
|
126
|
+
const remaining = deadline - Date.now();
|
|
127
|
+
const waitTime = Math.min(pollIntervalMs, remaining);
|
|
128
|
+
if (waitTime <= 0) break;
|
|
129
|
+
await new Promise((resolve) => {
|
|
130
|
+
const timer = setTimeout(() => resolve(), waitTime);
|
|
131
|
+
if (typeof timer === "object" && "unref" in timer) {
|
|
132
|
+
timer.unref();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
throw new Error(`Batch ${batchId} timed out after ${timeoutMs}ms`);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Submit and wait for results (convenience)
|
|
140
|
+
*/
|
|
141
|
+
async submitAndWait(requests, timeoutMs = DEFAULT_TIMEOUT_MS, description) {
|
|
142
|
+
const batchId = await this.submit(requests, description);
|
|
143
|
+
await this.waitForCompletion(batchId, timeoutMs);
|
|
144
|
+
return this.retrieve(batchId);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Cancel a batch job
|
|
148
|
+
*/
|
|
149
|
+
async cancel(batchId) {
|
|
150
|
+
if (this.mockMode) {
|
|
151
|
+
return this.mockBatchJob(batchId, "canceling");
|
|
152
|
+
}
|
|
153
|
+
const response = await fetch(
|
|
154
|
+
`${this.baseUrl}/v1/messages/batches/${batchId}/cancel`,
|
|
155
|
+
{
|
|
156
|
+
method: "POST",
|
|
157
|
+
headers: {
|
|
158
|
+
"x-api-key": this.apiKey,
|
|
159
|
+
"anthropic-version": "2023-06-01"
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
if (!response.ok) {
|
|
164
|
+
throw new Error(`Batch cancel failed: ${response.status}`);
|
|
165
|
+
}
|
|
166
|
+
const job = await response.json();
|
|
167
|
+
this.updateJobStatus(batchId, job.processing_status);
|
|
168
|
+
return job;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* List stored batch jobs
|
|
172
|
+
*/
|
|
173
|
+
listJobs() {
|
|
174
|
+
return this.loadStoredJobs();
|
|
175
|
+
}
|
|
176
|
+
// ── Persistence ───────────────────────────────────────────────────────
|
|
177
|
+
persistJob(job) {
|
|
178
|
+
const jobs = this.loadStoredJobs();
|
|
179
|
+
const existing = jobs.findIndex((j) => j.batchId === job.batchId);
|
|
180
|
+
if (existing >= 0) {
|
|
181
|
+
jobs[existing] = job;
|
|
182
|
+
} else {
|
|
183
|
+
jobs.push(job);
|
|
184
|
+
}
|
|
185
|
+
const trimmed = jobs.slice(-50);
|
|
186
|
+
this.saveStoredJobs(trimmed);
|
|
187
|
+
}
|
|
188
|
+
updateJobStatus(batchId, status, endedAt) {
|
|
189
|
+
const jobs = this.loadStoredJobs();
|
|
190
|
+
const job = jobs.find((j) => j.batchId === batchId);
|
|
191
|
+
if (job) {
|
|
192
|
+
job.status = status;
|
|
193
|
+
if (endedAt) job.endedAt = endedAt;
|
|
194
|
+
this.saveStoredJobs(jobs);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
loadStoredJobs() {
|
|
198
|
+
try {
|
|
199
|
+
if (existsSync(BATCH_JOBS_PATH)) {
|
|
200
|
+
return JSON.parse(readFileSync(BATCH_JOBS_PATH, "utf8"));
|
|
201
|
+
}
|
|
202
|
+
} catch {
|
|
203
|
+
}
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
saveStoredJobs(jobs) {
|
|
207
|
+
try {
|
|
208
|
+
const dir = join(homedir(), ".stackmemory");
|
|
209
|
+
if (!existsSync(dir)) {
|
|
210
|
+
mkdirSync(dir, { recursive: true });
|
|
211
|
+
}
|
|
212
|
+
writeFileSync(BATCH_JOBS_PATH, JSON.stringify(jobs, null, 2));
|
|
213
|
+
} catch {
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// ── Mock mode ─────────────────────────────────────────────────────────
|
|
217
|
+
mockBatchJob(batchId, status = "ended") {
|
|
218
|
+
return {
|
|
219
|
+
id: batchId,
|
|
220
|
+
type: "message_batch",
|
|
221
|
+
processing_status: status,
|
|
222
|
+
request_counts: {
|
|
223
|
+
processing: 0,
|
|
224
|
+
succeeded: 1,
|
|
225
|
+
errored: 0,
|
|
226
|
+
canceled: 0,
|
|
227
|
+
expired: 0
|
|
228
|
+
},
|
|
229
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
230
|
+
ended_at: status === "ended" ? (/* @__PURE__ */ new Date()).toISOString() : void 0
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
mockBatchResults(batchId) {
|
|
234
|
+
const jobs = this.loadStoredJobs();
|
|
235
|
+
const job = jobs.find((j) => j.batchId === batchId);
|
|
236
|
+
const count = job?.requestCount || 1;
|
|
237
|
+
return Array.from({ length: count }, (_, i) => ({
|
|
238
|
+
custom_id: `req_${i}`,
|
|
239
|
+
result: {
|
|
240
|
+
type: "succeeded",
|
|
241
|
+
message: {
|
|
242
|
+
id: `msg_mock_${i}`,
|
|
243
|
+
content: [
|
|
244
|
+
{ type: "text", text: `Mock batch response for request ${i}` }
|
|
245
|
+
],
|
|
246
|
+
model: "claude-sonnet-4-5-20250929",
|
|
247
|
+
stop_reason: "end_turn",
|
|
248
|
+
usage: { input_tokens: 100, output_tokens: 50 }
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
export {
|
|
255
|
+
AnthropicBatchClient
|
|
256
|
+
};
|
|
@@ -3,6 +3,7 @@ import { dirname as __pathDirname } from 'path';
|
|
|
3
3
|
const __filename = __fileURLToPath(import.meta.url);
|
|
4
4
|
const __dirname = __pathDirname(__filename);
|
|
5
5
|
import { logger } from "../../core/monitoring/logger.js";
|
|
6
|
+
import { STRUCTURED_RESPONSE_SUFFIX } from "../../orchestrators/multimodal/constants.js";
|
|
6
7
|
class AnthropicClient {
|
|
7
8
|
apiKey;
|
|
8
9
|
baseURL;
|
|
@@ -31,17 +32,21 @@ class AnthropicClient {
|
|
|
31
32
|
* Send completion request to Claude
|
|
32
33
|
*/
|
|
33
34
|
async complete(request) {
|
|
35
|
+
const enhancedRequest = {
|
|
36
|
+
...request,
|
|
37
|
+
systemPrompt: request.systemPrompt + STRUCTURED_RESPONSE_SUFFIX
|
|
38
|
+
};
|
|
34
39
|
await this.checkRateLimit();
|
|
35
40
|
logger.debug("Sending completion request", {
|
|
36
|
-
model:
|
|
37
|
-
promptLength:
|
|
38
|
-
maxTokens:
|
|
41
|
+
model: enhancedRequest.model,
|
|
42
|
+
promptLength: enhancedRequest.prompt.length,
|
|
43
|
+
maxTokens: enhancedRequest.maxTokens
|
|
39
44
|
});
|
|
40
45
|
if (!this.apiKey) {
|
|
41
|
-
return this.mockComplete(
|
|
46
|
+
return this.mockComplete(enhancedRequest);
|
|
42
47
|
}
|
|
43
48
|
try {
|
|
44
|
-
const response = await this.sendRequest(
|
|
49
|
+
const response = await this.sendRequest(enhancedRequest);
|
|
45
50
|
return response.content;
|
|
46
51
|
} catch (error) {
|
|
47
52
|
logger.error("Anthropic API error", { error });
|
|
@@ -57,7 +62,9 @@ class AnthropicClient {
|
|
|
57
62
|
} catch (error) {
|
|
58
63
|
if (attempt < this.maxRetries) {
|
|
59
64
|
const delay = Math.pow(2, attempt) * 1e3;
|
|
60
|
-
logger.warn(
|
|
65
|
+
logger.warn(
|
|
66
|
+
`Retrying after ${delay}ms (attempt ${attempt}/${this.maxRetries})`
|
|
67
|
+
);
|
|
61
68
|
await this.delay(delay);
|
|
62
69
|
return this.sendRequest(request, attempt + 1);
|
|
63
70
|
}
|
|
@@ -85,44 +92,48 @@ class AnthropicClient {
|
|
|
85
92
|
* Mock response generators
|
|
86
93
|
*/
|
|
87
94
|
mockPlanningResponse(prompt) {
|
|
88
|
-
return JSON.stringify(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
95
|
+
return JSON.stringify(
|
|
96
|
+
{
|
|
97
|
+
plan: {
|
|
98
|
+
type: "sequential",
|
|
99
|
+
tasks: [
|
|
100
|
+
{
|
|
101
|
+
id: "task-1",
|
|
102
|
+
description: "Analyze requirements",
|
|
103
|
+
agent: "context",
|
|
104
|
+
dependencies: []
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: "task-2",
|
|
108
|
+
type: "parallel",
|
|
109
|
+
description: "Implementation phase",
|
|
110
|
+
children: [
|
|
111
|
+
{
|
|
112
|
+
id: "task-2a",
|
|
113
|
+
description: "Write core logic",
|
|
114
|
+
agent: "code",
|
|
115
|
+
dependencies: ["task-1"]
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: "task-2b",
|
|
119
|
+
description: "Write tests",
|
|
120
|
+
agent: "testing",
|
|
121
|
+
dependencies: ["task-1"]
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: "task-3",
|
|
127
|
+
description: "Review and improve",
|
|
128
|
+
agent: "review",
|
|
129
|
+
dependencies: ["task-2"]
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
null,
|
|
135
|
+
2
|
|
136
|
+
);
|
|
126
137
|
}
|
|
127
138
|
mockCodeResponse(prompt) {
|
|
128
139
|
return `
|
|
@@ -166,34 +177,38 @@ describe('validateInput', () => {
|
|
|
166
177
|
`.trim();
|
|
167
178
|
}
|
|
168
179
|
mockReviewResponse(prompt) {
|
|
169
|
-
return JSON.stringify(
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
180
|
+
return JSON.stringify(
|
|
181
|
+
{
|
|
182
|
+
quality: 0.75,
|
|
183
|
+
issues: [
|
|
184
|
+
"Missing error handling in processTask function",
|
|
185
|
+
"No input validation before processing",
|
|
186
|
+
"Tests could cover more edge cases"
|
|
187
|
+
],
|
|
188
|
+
suggestions: [
|
|
189
|
+
"Add try-catch block in processTask",
|
|
190
|
+
"Validate input length and type",
|
|
191
|
+
"Add tests for special characters and long inputs",
|
|
192
|
+
"Consider adding performance tests"
|
|
193
|
+
],
|
|
194
|
+
improvements: [
|
|
195
|
+
{
|
|
196
|
+
file: "implementation.ts",
|
|
197
|
+
line: 3,
|
|
198
|
+
suggestion: "Add input validation",
|
|
199
|
+
priority: "high"
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
file: "tests.ts",
|
|
203
|
+
line: 15,
|
|
204
|
+
suggestion: "Add edge case tests",
|
|
205
|
+
priority: "medium"
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
},
|
|
209
|
+
null,
|
|
210
|
+
2
|
|
211
|
+
);
|
|
197
212
|
}
|
|
198
213
|
/**
|
|
199
214
|
* Create mock response object
|