@radaros/core 0.3.2 → 0.3.4
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/package.json +1 -1
- package/src/agent/agent.ts +113 -14
- package/src/agent/llm-loop.ts +31 -4
- package/src/agent/types.ts +13 -1
- package/src/index.ts +8 -1
- package/src/logger/logger.ts +19 -2
- package/src/memory/user-memory.ts +211 -0
- package/src/models/providers/anthropic.ts +34 -4
- package/src/models/providers/google.ts +29 -4
- package/src/models/providers/openai.ts +28 -6
- package/src/models/providers/vertex.ts +31 -4
- package/src/models/types.ts +12 -0
- package/src/tools/define-tool.ts +3 -1
- package/src/tools/tool-executor.ts +86 -4
- package/src/tools/types.ts +7 -0
- package/src/utils/retry.ts +56 -0
- package/dist/index.d.ts +0 -1317
- package/dist/index.js +0 -4823
|
@@ -1,17 +1,65 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import type { ToolCall } from "../models/types.js";
|
|
3
3
|
import type { RunContext } from "../agent/run-context.js";
|
|
4
|
-
import type { ToolDef, ToolCallResult } from "./types.js";
|
|
4
|
+
import type { ToolDef, ToolCallResult, ToolResult } from "./types.js";
|
|
5
5
|
|
|
6
6
|
const _require = createRequire(import.meta.url);
|
|
7
7
|
|
|
8
|
+
interface CacheEntry {
|
|
9
|
+
result: string | ToolResult;
|
|
10
|
+
expiresAt: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
8
13
|
export class ToolExecutor {
|
|
9
14
|
private tools: Map<string, ToolDef>;
|
|
10
15
|
private concurrency: number;
|
|
16
|
+
private cache = new Map<string, CacheEntry>();
|
|
17
|
+
private cachedDefs: Array<{
|
|
18
|
+
name: string;
|
|
19
|
+
description: string;
|
|
20
|
+
parameters: Record<string, unknown>;
|
|
21
|
+
}> | null = null;
|
|
11
22
|
|
|
12
23
|
constructor(tools: ToolDef[], concurrency: number = 5) {
|
|
13
24
|
this.tools = new Map(tools.map((t) => [t.name, t]));
|
|
14
25
|
this.concurrency = concurrency;
|
|
26
|
+
this.cachedDefs = this.buildToolDefinitions();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
clearCache(): void {
|
|
30
|
+
this.cache.clear();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private getCacheKey(toolName: string, args: Record<string, unknown>): string {
|
|
34
|
+
const sortedArgs = JSON.stringify(args, Object.keys(args).sort());
|
|
35
|
+
return `${toolName}:${sortedArgs}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private getCached(toolName: string, args: Record<string, unknown>): (string | ToolResult) | undefined {
|
|
39
|
+
const tool = this.tools.get(toolName);
|
|
40
|
+
if (!tool?.cache) return undefined;
|
|
41
|
+
|
|
42
|
+
const key = this.getCacheKey(toolName, args);
|
|
43
|
+
const entry = this.cache.get(key);
|
|
44
|
+
if (!entry) return undefined;
|
|
45
|
+
|
|
46
|
+
if (Date.now() > entry.expiresAt) {
|
|
47
|
+
this.cache.delete(key);
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return entry.result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private setCache(toolName: string, args: Record<string, unknown>, result: string | ToolResult): void {
|
|
55
|
+
const tool = this.tools.get(toolName);
|
|
56
|
+
if (!tool?.cache) return;
|
|
57
|
+
|
|
58
|
+
const key = this.getCacheKey(toolName, args);
|
|
59
|
+
this.cache.set(key, {
|
|
60
|
+
result,
|
|
61
|
+
expiresAt: Date.now() + tool.cache.ttl,
|
|
62
|
+
});
|
|
15
63
|
}
|
|
16
64
|
|
|
17
65
|
async executeAll(
|
|
@@ -66,6 +114,24 @@ export class ToolExecutor {
|
|
|
66
114
|
args: toolCall.arguments,
|
|
67
115
|
});
|
|
68
116
|
|
|
117
|
+
const cachedResult = this.getCached(toolCall.name, toolCall.arguments);
|
|
118
|
+
if (cachedResult !== undefined) {
|
|
119
|
+
const resultContent =
|
|
120
|
+
typeof cachedResult === "string" ? cachedResult : cachedResult.content;
|
|
121
|
+
|
|
122
|
+
ctx.eventBus.emit("tool.result", {
|
|
123
|
+
runId: ctx.runId,
|
|
124
|
+
toolName: toolCall.name,
|
|
125
|
+
result: `[cached] ${resultContent}`,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
toolCallId: toolCall.id,
|
|
130
|
+
toolName: toolCall.name,
|
|
131
|
+
result: cachedResult,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
69
135
|
const parsed = tool.parameters.safeParse(toolCall.arguments);
|
|
70
136
|
if (!parsed.success) {
|
|
71
137
|
const errMsg = `Invalid arguments: ${parsed.error.message}`;
|
|
@@ -89,6 +155,8 @@ export class ToolExecutor {
|
|
|
89
155
|
const resultContent =
|
|
90
156
|
typeof rawResult === "string" ? rawResult : rawResult.content;
|
|
91
157
|
|
|
158
|
+
this.setCache(toolCall.name, toolCall.arguments, rawResult);
|
|
159
|
+
|
|
92
160
|
ctx.eventBus.emit("tool.result", {
|
|
93
161
|
runId: ctx.runId,
|
|
94
162
|
toolName: toolCall.name,
|
|
@@ -106,6 +174,16 @@ export class ToolExecutor {
|
|
|
106
174
|
name: string;
|
|
107
175
|
description: string;
|
|
108
176
|
parameters: Record<string, unknown>;
|
|
177
|
+
}> {
|
|
178
|
+
if (this.cachedDefs) return this.cachedDefs;
|
|
179
|
+
this.cachedDefs = this.buildToolDefinitions();
|
|
180
|
+
return this.cachedDefs;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private buildToolDefinitions(): Array<{
|
|
184
|
+
name: string;
|
|
185
|
+
description: string;
|
|
186
|
+
parameters: Record<string, unknown>;
|
|
109
187
|
}> {
|
|
110
188
|
const { zodToJsonSchema } = _require("zod-to-json-schema");
|
|
111
189
|
const defs: Array<{
|
|
@@ -123,13 +201,17 @@ export class ToolExecutor {
|
|
|
123
201
|
});
|
|
124
202
|
} else {
|
|
125
203
|
const jsonSchema = zodToJsonSchema(tool.parameters, {
|
|
126
|
-
target: "
|
|
127
|
-
|
|
204
|
+
target: "jsonSchema7",
|
|
205
|
+
$refStrategy: "none",
|
|
206
|
+
}) as Record<string, unknown>;
|
|
207
|
+
|
|
208
|
+
delete jsonSchema["$schema"];
|
|
209
|
+
delete jsonSchema["additionalProperties"];
|
|
128
210
|
|
|
129
211
|
defs.push({
|
|
130
212
|
name: tool.name,
|
|
131
213
|
description: tool.description,
|
|
132
|
-
parameters: jsonSchema
|
|
214
|
+
parameters: jsonSchema,
|
|
133
215
|
});
|
|
134
216
|
}
|
|
135
217
|
}
|
package/src/tools/types.ts
CHANGED
|
@@ -12,6 +12,11 @@ export interface ToolResult {
|
|
|
12
12
|
artifacts?: Artifact[];
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
export interface ToolCacheConfig {
|
|
16
|
+
/** Time-to-live in milliseconds. Cached results expire after this duration. */
|
|
17
|
+
ttl: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
15
20
|
export interface ToolDef {
|
|
16
21
|
name: string;
|
|
17
22
|
description: string;
|
|
@@ -19,6 +24,8 @@ export interface ToolDef {
|
|
|
19
24
|
execute: (args: Record<string, unknown>, ctx: RunContext) => Promise<string | ToolResult>;
|
|
20
25
|
/** Raw JSON Schema to send to the LLM, bypassing Zod-to-JSON conversion (used by MCP tools). */
|
|
21
26
|
rawJsonSchema?: Record<string, unknown>;
|
|
27
|
+
/** Enable result caching for this tool. */
|
|
28
|
+
cache?: ToolCacheConfig;
|
|
22
29
|
}
|
|
23
30
|
|
|
24
31
|
export interface ToolCallResult {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export interface RetryConfig {
|
|
2
|
+
maxRetries: number;
|
|
3
|
+
initialDelayMs: number;
|
|
4
|
+
maxDelayMs: number;
|
|
5
|
+
retryableErrors?: (error: unknown) => boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const DEFAULT_CONFIG: RetryConfig = {
|
|
9
|
+
maxRetries: 3,
|
|
10
|
+
initialDelayMs: 500,
|
|
11
|
+
maxDelayMs: 10_000,
|
|
12
|
+
retryableErrors: isRetryableError,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function isRetryableError(error: unknown): boolean {
|
|
16
|
+
if (error && typeof error === "object") {
|
|
17
|
+
const status = (error as any).status ?? (error as any).statusCode;
|
|
18
|
+
if (status === 429 || (status >= 500 && status < 600)) return true;
|
|
19
|
+
|
|
20
|
+
const code = (error as any).code;
|
|
21
|
+
if (code === "ECONNRESET" || code === "ETIMEDOUT" || code === "ENOTFOUND") return true;
|
|
22
|
+
|
|
23
|
+
const msg = (error as any).message;
|
|
24
|
+
if (typeof msg === "string" && /rate.limit|too.many.requests|overloaded/i.test(msg)) return true;
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function sleep(ms: number): Promise<void> {
|
|
30
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function withRetry<T>(
|
|
34
|
+
fn: () => Promise<T>,
|
|
35
|
+
config?: Partial<RetryConfig>
|
|
36
|
+
): Promise<T> {
|
|
37
|
+
const cfg = { ...DEFAULT_CONFIG, ...config };
|
|
38
|
+
let lastError: unknown;
|
|
39
|
+
|
|
40
|
+
for (let attempt = 0; attempt <= cfg.maxRetries; attempt++) {
|
|
41
|
+
try {
|
|
42
|
+
return await fn();
|
|
43
|
+
} catch (error) {
|
|
44
|
+
lastError = error;
|
|
45
|
+
if (attempt >= cfg.maxRetries || !cfg.retryableErrors!(error)) throw error;
|
|
46
|
+
|
|
47
|
+
const delay = Math.min(
|
|
48
|
+
cfg.initialDelayMs * Math.pow(2, attempt) + Math.random() * 200,
|
|
49
|
+
cfg.maxDelayMs
|
|
50
|
+
);
|
|
51
|
+
await sleep(delay);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
throw lastError;
|
|
56
|
+
}
|