@radaros/core 0.3.2 → 0.3.3
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 +49 -0
- package/src/agent/llm-loop.ts +18 -0
- package/src/agent/types.ts +8 -1
- package/src/index.ts +4 -1
- package/src/logger/logger.ts +19 -2
- package/src/memory/user-memory.ts +191 -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 +63 -1
- package/src/tools/types.ts +7 -0
- package/dist/index.d.ts +0 -1317
- package/dist/index.js +0 -4823
|
@@ -72,8 +72,12 @@ export class OpenAIProvider implements ModelProvider {
|
|
|
72
72
|
messages: this.toOpenAIMessages(messages),
|
|
73
73
|
};
|
|
74
74
|
|
|
75
|
-
if (options?.
|
|
76
|
-
params.
|
|
75
|
+
if (options?.reasoning?.enabled) {
|
|
76
|
+
params.reasoning_effort = options.reasoning.effort ?? "medium";
|
|
77
|
+
} else {
|
|
78
|
+
if (options?.temperature !== undefined)
|
|
79
|
+
params.temperature = options.temperature;
|
|
80
|
+
}
|
|
77
81
|
if (options?.maxTokens !== undefined)
|
|
78
82
|
params.max_tokens = options.maxTokens;
|
|
79
83
|
if (options?.topP !== undefined) params.top_p = options.topP;
|
|
@@ -98,8 +102,12 @@ export class OpenAIProvider implements ModelProvider {
|
|
|
98
102
|
stream: true,
|
|
99
103
|
};
|
|
100
104
|
|
|
101
|
-
if (options?.
|
|
102
|
-
params.
|
|
105
|
+
if (options?.reasoning?.enabled) {
|
|
106
|
+
params.reasoning_effort = options.reasoning.effort ?? "medium";
|
|
107
|
+
} else {
|
|
108
|
+
if (options?.temperature !== undefined)
|
|
109
|
+
params.temperature = options.temperature;
|
|
110
|
+
}
|
|
103
111
|
if (options?.maxTokens !== undefined)
|
|
104
112
|
params.max_tokens = options.maxTokens;
|
|
105
113
|
if (options?.topP !== undefined) params.top_p = options.topP;
|
|
@@ -165,11 +173,16 @@ export class OpenAIProvider implements ModelProvider {
|
|
|
165
173
|
}
|
|
166
174
|
}
|
|
167
175
|
|
|
176
|
+
if (delta?.reasoning_content) {
|
|
177
|
+
yield { type: "thinking", text: delta.reasoning_content };
|
|
178
|
+
}
|
|
179
|
+
|
|
168
180
|
if (choice.finish_reason) {
|
|
169
181
|
for (const [, tc] of activeToolCalls) {
|
|
170
182
|
yield { type: "tool_call_end", toolCallId: tc.id };
|
|
171
183
|
}
|
|
172
184
|
|
|
185
|
+
const reasoningTkns = chunk.usage?.completion_tokens_details?.reasoning_tokens ?? 0;
|
|
173
186
|
yield {
|
|
174
187
|
type: "finish",
|
|
175
188
|
finishReason:
|
|
@@ -181,6 +194,7 @@ export class OpenAIProvider implements ModelProvider {
|
|
|
181
194
|
promptTokens: chunk.usage.prompt_tokens ?? 0,
|
|
182
195
|
completionTokens: chunk.usage.completion_tokens ?? 0,
|
|
183
196
|
totalTokens: chunk.usage.total_tokens ?? 0,
|
|
197
|
+
...(reasoningTkns > 0 ? { reasoningTokens: reasoningTkns } : {}),
|
|
184
198
|
}
|
|
185
199
|
: undefined,
|
|
186
200
|
};
|
|
@@ -287,7 +301,7 @@ export class OpenAIProvider implements ModelProvider {
|
|
|
287
301
|
}));
|
|
288
302
|
}
|
|
289
303
|
|
|
290
|
-
private normalizeResponse(response: any): ModelResponse {
|
|
304
|
+
private normalizeResponse(response: any): ModelResponse & { thinking?: string } {
|
|
291
305
|
const choice = response.choices[0];
|
|
292
306
|
const msg = choice.message;
|
|
293
307
|
|
|
@@ -297,10 +311,12 @@ export class OpenAIProvider implements ModelProvider {
|
|
|
297
311
|
arguments: JSON.parse(tc.function.arguments || "{}"),
|
|
298
312
|
}));
|
|
299
313
|
|
|
314
|
+
const reasoningTokens = response.usage?.completion_tokens_details?.reasoning_tokens ?? 0;
|
|
300
315
|
const usage: TokenUsage = {
|
|
301
316
|
promptTokens: response.usage?.prompt_tokens ?? 0,
|
|
302
317
|
completionTokens: response.usage?.completion_tokens ?? 0,
|
|
303
318
|
totalTokens: response.usage?.total_tokens ?? 0,
|
|
319
|
+
...(reasoningTokens > 0 ? { reasoningTokens } : {}),
|
|
304
320
|
};
|
|
305
321
|
|
|
306
322
|
let finishReason: ModelResponse["finishReason"] = "stop";
|
|
@@ -309,7 +325,7 @@ export class OpenAIProvider implements ModelProvider {
|
|
|
309
325
|
else if (choice.finish_reason === "content_filter")
|
|
310
326
|
finishReason = "content_filter";
|
|
311
327
|
|
|
312
|
-
|
|
328
|
+
const result: ModelResponse & { thinking?: string } = {
|
|
313
329
|
message: {
|
|
314
330
|
role: "assistant",
|
|
315
331
|
content: msg.content ?? null,
|
|
@@ -319,5 +335,11 @@ export class OpenAIProvider implements ModelProvider {
|
|
|
319
335
|
finishReason,
|
|
320
336
|
raw: response,
|
|
321
337
|
};
|
|
338
|
+
|
|
339
|
+
if (msg.reasoning_content) {
|
|
340
|
+
result.thinking = msg.reasoning_content;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return result;
|
|
322
344
|
}
|
|
323
345
|
}
|
|
@@ -108,6 +108,12 @@ export class VertexAIProvider implements ModelProvider {
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
if (options?.reasoning?.enabled) {
|
|
112
|
+
config.thinkingConfig = {
|
|
113
|
+
thinkingBudget: options.reasoning.budgetTokens ?? 10000,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
111
117
|
const params: Record<string, unknown> = {
|
|
112
118
|
model: this.modelId,
|
|
113
119
|
contents,
|
|
@@ -140,6 +146,12 @@ export class VertexAIProvider implements ModelProvider {
|
|
|
140
146
|
if (options?.topP !== undefined) config.topP = options.topP;
|
|
141
147
|
if (options?.stop) config.stopSequences = options.stop;
|
|
142
148
|
|
|
149
|
+
if (options?.reasoning?.enabled) {
|
|
150
|
+
config.thinkingConfig = {
|
|
151
|
+
thinkingBudget: options.reasoning.budgetTokens ?? 10000,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
143
155
|
const params: Record<string, unknown> = {
|
|
144
156
|
model: this.modelId,
|
|
145
157
|
contents,
|
|
@@ -163,7 +175,9 @@ export class VertexAIProvider implements ModelProvider {
|
|
|
163
175
|
if (!candidate?.content?.parts) continue;
|
|
164
176
|
|
|
165
177
|
for (const part of candidate.content.parts) {
|
|
166
|
-
if (part.
|
|
178
|
+
if (part.thought) {
|
|
179
|
+
yield { type: "thinking", text: part.text ?? "" };
|
|
180
|
+
} else if (part.text) {
|
|
167
181
|
yield { type: "text", text: part.text };
|
|
168
182
|
}
|
|
169
183
|
|
|
@@ -354,16 +368,21 @@ export class VertexAIProvider implements ModelProvider {
|
|
|
354
368
|
return cleaned;
|
|
355
369
|
}
|
|
356
370
|
|
|
357
|
-
private normalizeResponse(response: any): ModelResponse {
|
|
371
|
+
private normalizeResponse(response: any): ModelResponse & { thinking?: string } {
|
|
358
372
|
const candidate = response.candidates?.[0];
|
|
359
373
|
const parts = candidate?.content?.parts ?? [];
|
|
360
374
|
|
|
361
375
|
let textContent = "";
|
|
376
|
+
let thinkingContent = "";
|
|
362
377
|
const toolCalls: ToolCall[] = [];
|
|
363
378
|
let toolCallCounter = 0;
|
|
364
379
|
|
|
365
380
|
for (const part of parts) {
|
|
366
|
-
if (part.
|
|
381
|
+
if (part.thought && part.text) {
|
|
382
|
+
thinkingContent += part.text;
|
|
383
|
+
} else if (part.text) {
|
|
384
|
+
textContent += part.text;
|
|
385
|
+
}
|
|
367
386
|
if (part.functionCall) {
|
|
368
387
|
toolCalls.push({
|
|
369
388
|
id: `vertex_tc_${toolCallCounter++}`,
|
|
@@ -373,10 +392,12 @@ export class VertexAIProvider implements ModelProvider {
|
|
|
373
392
|
}
|
|
374
393
|
}
|
|
375
394
|
|
|
395
|
+
const thinkingTokens = response.usageMetadata?.thoughtsTokenCount ?? 0;
|
|
376
396
|
const usage: TokenUsage = {
|
|
377
397
|
promptTokens: response.usageMetadata?.promptTokenCount ?? 0,
|
|
378
398
|
completionTokens: response.usageMetadata?.candidatesTokenCount ?? 0,
|
|
379
399
|
totalTokens: response.usageMetadata?.totalTokenCount ?? 0,
|
|
400
|
+
...(thinkingTokens > 0 ? { reasoningTokens: thinkingTokens } : {}),
|
|
380
401
|
};
|
|
381
402
|
|
|
382
403
|
let finishReason: ModelResponse["finishReason"] = "stop";
|
|
@@ -386,7 +407,7 @@ export class VertexAIProvider implements ModelProvider {
|
|
|
386
407
|
else if (candidate?.finishReason === "SAFETY")
|
|
387
408
|
finishReason = "content_filter";
|
|
388
409
|
|
|
389
|
-
|
|
410
|
+
const result: ModelResponse & { thinking?: string } = {
|
|
390
411
|
message: {
|
|
391
412
|
role: "assistant",
|
|
392
413
|
content: textContent || null,
|
|
@@ -396,5 +417,11 @@ export class VertexAIProvider implements ModelProvider {
|
|
|
396
417
|
finishReason,
|
|
397
418
|
raw: response,
|
|
398
419
|
};
|
|
420
|
+
|
|
421
|
+
if (thinkingContent) {
|
|
422
|
+
result.thinking = thinkingContent;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return result;
|
|
399
426
|
}
|
|
400
427
|
}
|
package/src/models/types.ts
CHANGED
|
@@ -64,6 +64,7 @@ export interface TokenUsage {
|
|
|
64
64
|
promptTokens: number;
|
|
65
65
|
completionTokens: number;
|
|
66
66
|
totalTokens: number;
|
|
67
|
+
reasoningTokens?: number;
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
// ── Model response ────────────────────────────────────────────────────────
|
|
@@ -77,6 +78,7 @@ export interface ModelResponse {
|
|
|
77
78
|
|
|
78
79
|
export type StreamChunk =
|
|
79
80
|
| { type: "text"; text: string }
|
|
81
|
+
| { type: "thinking"; text: string }
|
|
80
82
|
| { type: "tool_call_start"; toolCall: { id: string; name: string } }
|
|
81
83
|
| { type: "tool_call_delta"; toolCallId: string; argumentsDelta: string }
|
|
82
84
|
| { type: "tool_call_end"; toolCallId: string }
|
|
@@ -84,6 +86,14 @@ export type StreamChunk =
|
|
|
84
86
|
|
|
85
87
|
// ── Model config ──────────────────────────────────────────────────────────
|
|
86
88
|
|
|
89
|
+
export interface ReasoningConfig {
|
|
90
|
+
enabled: boolean;
|
|
91
|
+
/** Reasoning effort for OpenAI o-series models. */
|
|
92
|
+
effort?: "low" | "medium" | "high";
|
|
93
|
+
/** Token budget for thinking (Anthropic / Gemini). */
|
|
94
|
+
budgetTokens?: number;
|
|
95
|
+
}
|
|
96
|
+
|
|
87
97
|
export interface ModelConfig {
|
|
88
98
|
temperature?: number;
|
|
89
99
|
maxTokens?: number;
|
|
@@ -92,6 +102,8 @@ export interface ModelConfig {
|
|
|
92
102
|
responseFormat?: "text" | "json" | { type: "json_schema"; schema: Record<string, unknown>; name?: string };
|
|
93
103
|
/** Per-request API key override. When provided, the provider uses this key instead of the one set at construction. */
|
|
94
104
|
apiKey?: string;
|
|
105
|
+
/** Enable extended thinking / reasoning. */
|
|
106
|
+
reasoning?: ReasoningConfig;
|
|
95
107
|
}
|
|
96
108
|
|
|
97
109
|
// ── Helpers ───────────────────────────────────────────────────────────────
|
package/src/tools/define-tool.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { z } from "zod";
|
|
2
2
|
import type { RunContext } from "../agent/run-context.js";
|
|
3
|
-
import type { ToolDef, ToolResult } from "./types.js";
|
|
3
|
+
import type { ToolDef, ToolResult, ToolCacheConfig } from "./types.js";
|
|
4
4
|
|
|
5
5
|
export function defineTool<T extends z.ZodObject<any>>(config: {
|
|
6
6
|
name: string;
|
|
@@ -10,11 +10,13 @@ export function defineTool<T extends z.ZodObject<any>>(config: {
|
|
|
10
10
|
args: z.infer<T>,
|
|
11
11
|
ctx: RunContext
|
|
12
12
|
) => Promise<string | ToolResult>;
|
|
13
|
+
cache?: ToolCacheConfig;
|
|
13
14
|
}): ToolDef {
|
|
14
15
|
return {
|
|
15
16
|
name: config.name,
|
|
16
17
|
description: config.description,
|
|
17
18
|
parameters: config.parameters,
|
|
18
19
|
execute: config.execute as ToolDef["execute"],
|
|
20
|
+
cache: config.cache,
|
|
19
21
|
};
|
|
20
22
|
}
|
|
@@ -1,19 +1,61 @@
|
|
|
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>();
|
|
11
17
|
|
|
12
18
|
constructor(tools: ToolDef[], concurrency: number = 5) {
|
|
13
19
|
this.tools = new Map(tools.map((t) => [t.name, t]));
|
|
14
20
|
this.concurrency = concurrency;
|
|
15
21
|
}
|
|
16
22
|
|
|
23
|
+
clearCache(): void {
|
|
24
|
+
this.cache.clear();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private getCacheKey(toolName: string, args: Record<string, unknown>): string {
|
|
28
|
+
const sortedArgs = JSON.stringify(args, Object.keys(args).sort());
|
|
29
|
+
return `${toolName}:${sortedArgs}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private getCached(toolName: string, args: Record<string, unknown>): (string | ToolResult) | undefined {
|
|
33
|
+
const tool = this.tools.get(toolName);
|
|
34
|
+
if (!tool?.cache) return undefined;
|
|
35
|
+
|
|
36
|
+
const key = this.getCacheKey(toolName, args);
|
|
37
|
+
const entry = this.cache.get(key);
|
|
38
|
+
if (!entry) return undefined;
|
|
39
|
+
|
|
40
|
+
if (Date.now() > entry.expiresAt) {
|
|
41
|
+
this.cache.delete(key);
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return entry.result;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private setCache(toolName: string, args: Record<string, unknown>, result: string | ToolResult): void {
|
|
49
|
+
const tool = this.tools.get(toolName);
|
|
50
|
+
if (!tool?.cache) return;
|
|
51
|
+
|
|
52
|
+
const key = this.getCacheKey(toolName, args);
|
|
53
|
+
this.cache.set(key, {
|
|
54
|
+
result,
|
|
55
|
+
expiresAt: Date.now() + tool.cache.ttl,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
17
59
|
async executeAll(
|
|
18
60
|
toolCalls: ToolCall[],
|
|
19
61
|
ctx: RunContext
|
|
@@ -66,6 +108,24 @@ export class ToolExecutor {
|
|
|
66
108
|
args: toolCall.arguments,
|
|
67
109
|
});
|
|
68
110
|
|
|
111
|
+
const cachedResult = this.getCached(toolCall.name, toolCall.arguments);
|
|
112
|
+
if (cachedResult !== undefined) {
|
|
113
|
+
const resultContent =
|
|
114
|
+
typeof cachedResult === "string" ? cachedResult : cachedResult.content;
|
|
115
|
+
|
|
116
|
+
ctx.eventBus.emit("tool.result", {
|
|
117
|
+
runId: ctx.runId,
|
|
118
|
+
toolName: toolCall.name,
|
|
119
|
+
result: `[cached] ${resultContent}`,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
toolCallId: toolCall.id,
|
|
124
|
+
toolName: toolCall.name,
|
|
125
|
+
result: cachedResult,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
69
129
|
const parsed = tool.parameters.safeParse(toolCall.arguments);
|
|
70
130
|
if (!parsed.success) {
|
|
71
131
|
const errMsg = `Invalid arguments: ${parsed.error.message}`;
|
|
@@ -89,6 +149,8 @@ export class ToolExecutor {
|
|
|
89
149
|
const resultContent =
|
|
90
150
|
typeof rawResult === "string" ? rawResult : rawResult.content;
|
|
91
151
|
|
|
152
|
+
this.setCache(toolCall.name, toolCall.arguments, rawResult);
|
|
153
|
+
|
|
92
154
|
ctx.eventBus.emit("tool.result", {
|
|
93
155
|
runId: ctx.runId,
|
|
94
156
|
toolName: toolCall.name,
|
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 {
|