@jussmor/sdk-ai 0.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/dist/conversation-kIkMQdYK.d.cts +105 -0
- package/dist/conversation-kIkMQdYK.d.ts +105 -0
- package/dist/conversation-store-CAyPuBjk.d.ts +10 -0
- package/dist/conversation-store-Cl42jpsA.d.cts +10 -0
- package/dist/index.cjs +1630 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +251 -0
- package/dist/index.d.ts +251 -0
- package/dist/index.js +1536 -0
- package/dist/index.js.map +1 -0
- package/dist/memory-uBLqrQRY.d.cts +28 -0
- package/dist/memory-uBLqrQRY.d.ts +28 -0
- package/dist/providers/llm/anthropic.cjs +275 -0
- package/dist/providers/llm/anthropic.cjs.map +1 -0
- package/dist/providers/llm/anthropic.d.cts +22 -0
- package/dist/providers/llm/anthropic.d.ts +22 -0
- package/dist/providers/llm/anthropic.js +240 -0
- package/dist/providers/llm/anthropic.js.map +1 -0
- package/dist/providers/llm/ollama.cjs +195 -0
- package/dist/providers/llm/ollama.cjs.map +1 -0
- package/dist/providers/llm/ollama.d.cts +23 -0
- package/dist/providers/llm/ollama.d.ts +23 -0
- package/dist/providers/llm/ollama.js +170 -0
- package/dist/providers/llm/ollama.js.map +1 -0
- package/dist/providers/llm/openai.cjs +213 -0
- package/dist/providers/llm/openai.cjs.map +1 -0
- package/dist/providers/llm/openai.d.cts +22 -0
- package/dist/providers/llm/openai.d.ts +22 -0
- package/dist/providers/llm/openai.js +178 -0
- package/dist/providers/llm/openai.js.map +1 -0
- package/dist/providers/memory/filesystem.cjs +112 -0
- package/dist/providers/memory/filesystem.cjs.map +1 -0
- package/dist/providers/memory/filesystem.d.cts +17 -0
- package/dist/providers/memory/filesystem.d.ts +17 -0
- package/dist/providers/memory/filesystem.js +87 -0
- package/dist/providers/memory/filesystem.js.map +1 -0
- package/dist/providers/store/filesystem.cjs +87 -0
- package/dist/providers/store/filesystem.cjs.map +1 -0
- package/dist/providers/store/filesystem.d.cts +14 -0
- package/dist/providers/store/filesystem.d.ts +14 -0
- package/dist/providers/store/filesystem.js +62 -0
- package/dist/providers/store/filesystem.js.map +1 -0
- package/dist/providers/thread/memory.cjs +81 -0
- package/dist/providers/thread/memory.cjs.map +1 -0
- package/dist/providers/thread/memory.d.cts +14 -0
- package/dist/providers/thread/memory.d.ts +14 -0
- package/dist/providers/thread/memory.js +56 -0
- package/dist/providers/thread/memory.js.map +1 -0
- package/dist/providers/thread/sqlite.cjs +917 -0
- package/dist/providers/thread/sqlite.cjs.map +1 -0
- package/dist/providers/thread/sqlite.d.cts +17 -0
- package/dist/providers/thread/sqlite.d.ts +17 -0
- package/dist/providers/thread/sqlite.js +911 -0
- package/dist/providers/thread/sqlite.js.map +1 -0
- package/dist/providers/tokenizers/auto.cjs +136 -0
- package/dist/providers/tokenizers/auto.cjs.map +1 -0
- package/dist/providers/tokenizers/auto.d.cts +24 -0
- package/dist/providers/tokenizers/auto.d.ts +24 -0
- package/dist/providers/tokenizers/auto.js +107 -0
- package/dist/providers/tokenizers/auto.js.map +1 -0
- package/dist/streaming-B-P6Fw_k.d.cts +372 -0
- package/dist/streaming-BtD23BE0.d.ts +372 -0
- package/dist/thread-C2b9xRMJ.d.cts +30 -0
- package/dist/thread-C2b9xRMJ.d.ts +30 -0
- package/dist/tokenizer-BhG_RGUk.d.cts +13 -0
- package/dist/tokenizer-BhG_RGUk.d.ts +13 -0
- package/package.json +84 -0
- package/src/agent-loop.ts +311 -0
- package/src/agent-source.ts +12 -0
- package/src/artifact.ts +31 -0
- package/src/compaction.ts +75 -0
- package/src/context-budget.ts +65 -0
- package/src/conversation-store.ts +8 -0
- package/src/conversation.ts +42 -0
- package/src/dispatch.ts +207 -0
- package/src/engine.ts +53 -0
- package/src/execution-context.ts +31 -0
- package/src/index.ts +37 -0
- package/src/interrupt-store.ts +25 -0
- package/src/interrupt.ts +55 -0
- package/src/llm-router.ts +34 -0
- package/src/llm.ts +100 -0
- package/src/memory-selector.ts +38 -0
- package/src/memory.ts +34 -0
- package/src/mode.ts +81 -0
- package/src/permissions.ts +104 -0
- package/src/protocol.ts +1 -0
- package/src/providers/llm/anthropic.ts +298 -0
- package/src/providers/llm/ollama.ts +219 -0
- package/src/providers/llm/openai.ts +215 -0
- package/src/providers/memory/filesystem.ts +99 -0
- package/src/providers/store/filesystem.ts +64 -0
- package/src/providers/thread/memory.ts +67 -0
- package/src/providers/thread/sqlite.ts +147 -0
- package/src/providers/tokenizers/auto.ts +26 -0
- package/src/providers/tokenizers/byte.ts +27 -0
- package/src/providers/tokenizers/tiktoken.ts +91 -0
- package/src/reasoning.ts +7 -0
- package/src/rule-matcher.ts +32 -0
- package/src/runtime.ts +416 -0
- package/src/safety.ts +56 -0
- package/src/sandbox.ts +23 -0
- package/src/session-context.ts +33 -0
- package/src/skill-source.ts +21 -0
- package/src/streaming.ts +124 -0
- package/src/system-prompt.ts +71 -0
- package/src/system-reminder.ts +9 -0
- package/src/thread.ts +33 -0
- package/src/tokenizer.ts +31 -0
- package/src/tool.ts +175 -0
- package/src/tracing.ts +63 -0
package/src/streaming.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { AgentLoopResult } from "./agent-loop.js";
|
|
2
|
+
import type { Artifact } from "./artifact.js";
|
|
3
|
+
import type { InterruptRequest } from "./interrupt.js";
|
|
4
|
+
import type { ToolCallEntry, ToolResult } from "./dispatch.js";
|
|
5
|
+
import type { LLMProvider, ChatRequest } from "./llm.js";
|
|
6
|
+
|
|
7
|
+
export type StreamEventType =
|
|
8
|
+
| "delta"
|
|
9
|
+
| "thinking"
|
|
10
|
+
| "tool_call"
|
|
11
|
+
| "tool_result"
|
|
12
|
+
| "turn_complete"
|
|
13
|
+
| "agent_result"
|
|
14
|
+
| "interrupt_required"
|
|
15
|
+
| "interrupt_resolved"
|
|
16
|
+
| "artifact_created"
|
|
17
|
+
| "artifact_updated"
|
|
18
|
+
| "compaction"
|
|
19
|
+
| "plan_mode_changed"
|
|
20
|
+
| "done"
|
|
21
|
+
| "error";
|
|
22
|
+
|
|
23
|
+
export interface CompactionEvent {
|
|
24
|
+
messagesDropped: number;
|
|
25
|
+
overflowTokens: number;
|
|
26
|
+
summary?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface PlanModeEvent {
|
|
30
|
+
state: "entered" | "exited";
|
|
31
|
+
plan?: string;
|
|
32
|
+
reason?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface AgentResult {
|
|
36
|
+
agentId: string;
|
|
37
|
+
result: AgentLoopResult;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface StreamEvent {
|
|
41
|
+
type: StreamEventType;
|
|
42
|
+
delta?: string;
|
|
43
|
+
thinking?: string;
|
|
44
|
+
toolCall?: ToolCallEntry;
|
|
45
|
+
toolResult?: ToolResult;
|
|
46
|
+
agentResult?: AgentResult;
|
|
47
|
+
interrupt?: InterruptRequest;
|
|
48
|
+
artifact?: Artifact;
|
|
49
|
+
final?: AgentLoopResult;
|
|
50
|
+
compaction?: CompactionEvent;
|
|
51
|
+
planMode?: PlanModeEvent;
|
|
52
|
+
error?: Error;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface StreamingLLMProvider extends LLMProvider {
|
|
56
|
+
chatStream(
|
|
57
|
+
req: ChatRequest,
|
|
58
|
+
signal?: AbortSignal,
|
|
59
|
+
): AsyncGenerator<StreamEvent>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function collectStream(
|
|
63
|
+
events: AsyncGenerator<StreamEvent>,
|
|
64
|
+
): Promise<{ text: string; final: AgentLoopResult | undefined; error: Error | undefined }> {
|
|
65
|
+
let text = "";
|
|
66
|
+
let final: AgentLoopResult | undefined;
|
|
67
|
+
let error: Error | undefined;
|
|
68
|
+
|
|
69
|
+
for await (const ev of events) {
|
|
70
|
+
if (ev.type === "delta" && ev.delta) text += ev.delta;
|
|
71
|
+
if (ev.type === "done") final = ev.final;
|
|
72
|
+
if (ev.type === "error") error = ev.error;
|
|
73
|
+
}
|
|
74
|
+
return { text, final, error };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function fanOutStream(
|
|
78
|
+
source: AsyncGenerator<StreamEvent>,
|
|
79
|
+
consumers: number,
|
|
80
|
+
): AsyncGenerator<StreamEvent>[] {
|
|
81
|
+
const queues: StreamEvent[][] = Array.from({ length: consumers }, () => []);
|
|
82
|
+
const resolvers: Array<((value: IteratorResult<StreamEvent>) => void) | null> = Array(consumers).fill(null);
|
|
83
|
+
let done = false;
|
|
84
|
+
|
|
85
|
+
void (async () => {
|
|
86
|
+
for await (const ev of source) {
|
|
87
|
+
for (let i = 0; i < consumers; i++) {
|
|
88
|
+
const resolve = resolvers[i];
|
|
89
|
+
if (resolve) {
|
|
90
|
+
resolvers[i] = null;
|
|
91
|
+
resolve({ value: ev, done: false });
|
|
92
|
+
} else {
|
|
93
|
+
queues[i]!.push(ev);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
done = true;
|
|
98
|
+
for (let i = 0; i < consumers; i++) {
|
|
99
|
+
const resolve = resolvers[i];
|
|
100
|
+
if (resolve) {
|
|
101
|
+
resolvers[i] = null;
|
|
102
|
+
resolve({ value: undefined as unknown as StreamEvent, done: true });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
})();
|
|
106
|
+
|
|
107
|
+
return Array.from({ length: consumers }, (_, i) => {
|
|
108
|
+
return (async function* (): AsyncGenerator<StreamEvent> {
|
|
109
|
+
while (true) {
|
|
110
|
+
const queued = queues[i]!.shift();
|
|
111
|
+
if (queued !== undefined) {
|
|
112
|
+
yield queued;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (done) return;
|
|
116
|
+
const ev = await new Promise<IteratorResult<StreamEvent>>((resolve) => {
|
|
117
|
+
resolvers[i] = resolve;
|
|
118
|
+
});
|
|
119
|
+
if (ev.done) return;
|
|
120
|
+
yield ev.value;
|
|
121
|
+
}
|
|
122
|
+
})();
|
|
123
|
+
});
|
|
124
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { Tokenizer } from "./tokenizer.js";
|
|
2
|
+
import { truncateToTokens } from "./tokenizer.js";
|
|
3
|
+
|
|
4
|
+
export type SystemPromptLayer =
|
|
5
|
+
| "core"
|
|
6
|
+
| "behavior"
|
|
7
|
+
| "memory"
|
|
8
|
+
| "session"
|
|
9
|
+
| "mode";
|
|
10
|
+
|
|
11
|
+
const LAYER_ORDER: SystemPromptLayer[] = [
|
|
12
|
+
"core",
|
|
13
|
+
"behavior",
|
|
14
|
+
"memory",
|
|
15
|
+
"session",
|
|
16
|
+
"mode",
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
export class SystemPromptBuilder {
|
|
20
|
+
private layers = new Map<SystemPromptLayer, string>();
|
|
21
|
+
private maxLayerTokens = new Map<SystemPromptLayer, number>();
|
|
22
|
+
|
|
23
|
+
setMaxLayerTokens(layer: SystemPromptLayer, maxTokens: number): void {
|
|
24
|
+
this.maxLayerTokens.set(layer, maxTokens);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
build(): string {
|
|
28
|
+
return this.buildWithBudget();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
buildWithBudget(tok?: Tokenizer): string {
|
|
32
|
+
const parts: string[] = [];
|
|
33
|
+
for (const layer of LAYER_ORDER) {
|
|
34
|
+
let content = (this.layers.get(layer) ?? "").trim();
|
|
35
|
+
if (!content) continue;
|
|
36
|
+
if (tok) {
|
|
37
|
+
const cap = this.maxLayerTokens.get(layer);
|
|
38
|
+
if (cap && cap > 0 && tok.count(content) > cap) {
|
|
39
|
+
content = truncateToTokens(content, cap, tok);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
parts.push(content);
|
|
43
|
+
}
|
|
44
|
+
return parts.join("\n\n");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get(layer: SystemPromptLayer): string {
|
|
48
|
+
return this.layers.get(layer) ?? "";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
has(layer: SystemPromptLayer): boolean {
|
|
52
|
+
return (this.layers.get(layer) ?? "").trim().length > 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
set(layer: SystemPromptLayer, content: string): void {
|
|
56
|
+
this.layers.set(layer, content.trim());
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
append(layer: SystemPromptLayer, content: string): void {
|
|
60
|
+
const existing = this.layers.get(layer) ?? "";
|
|
61
|
+
if (!existing) {
|
|
62
|
+
this.layers.set(layer, content.trim());
|
|
63
|
+
} else {
|
|
64
|
+
this.layers.set(layer, existing + "\n\n" + content.trim());
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
clear(layer: SystemPromptLayer): void {
|
|
69
|
+
this.layers.delete(layer);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function systemReminder(content: string): string {
|
|
2
|
+
const trimmed = content.trim();
|
|
3
|
+
if (!trimmed) return "";
|
|
4
|
+
return `<system-reminder>\n${trimmed}\n</system-reminder>`;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function joinSystemReminders(...reminders: string[]): string {
|
|
8
|
+
return reminders.filter(Boolean).join("\n\n");
|
|
9
|
+
}
|
package/src/thread.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type ThreadStatus = "active" | "completed" | "failed" | "archived";
|
|
2
|
+
|
|
3
|
+
export interface Thread {
|
|
4
|
+
id: string;
|
|
5
|
+
userId?: string;
|
|
6
|
+
projectId?: string;
|
|
7
|
+
modeId?: string;
|
|
8
|
+
status: ThreadStatus;
|
|
9
|
+
parentId?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface Message {
|
|
13
|
+
id?: string;
|
|
14
|
+
fromThreadId: string;
|
|
15
|
+
toThreadId: string;
|
|
16
|
+
content: string;
|
|
17
|
+
sentAt?: Date;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ThreadProvider {
|
|
21
|
+
create(projectId: string, modeId: string, signal?: AbortSignal): Promise<Thread>;
|
|
22
|
+
get(threadId: string, signal?: AbortSignal): Promise<Thread>;
|
|
23
|
+
archive(threadId: string, signal?: AbortSignal): Promise<void>;
|
|
24
|
+
sendMessage(msg: Message, signal?: AbortSignal): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface MultiUserThreadProvider extends ThreadProvider {
|
|
28
|
+
createForUser(userId: string, projectId: string, modeId: string, signal?: AbortSignal): Promise<Thread>;
|
|
29
|
+
getForUser(userId: string, threadId: string, signal?: AbortSignal): Promise<Thread>;
|
|
30
|
+
listByUser(userId: string, status: ThreadStatus, signal?: AbortSignal): Promise<Thread[]>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const ErrThreadAccessDenied = new Error("thread: access denied");
|
package/src/tokenizer.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface Tokenizer {
|
|
2
|
+
count(text: string): number;
|
|
3
|
+
encode(text: string): number[];
|
|
4
|
+
decode(tokens: number[]): string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export class HeuristicTokenizer implements Tokenizer {
|
|
8
|
+
count(text: string): number {
|
|
9
|
+
return Math.ceil(text.length / 4);
|
|
10
|
+
}
|
|
11
|
+
encode(_text: string): number[] {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
decode(_tokens: number[]): string {
|
|
15
|
+
return "";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function truncateToTokens(
|
|
20
|
+
text: string,
|
|
21
|
+
maxTokens: number,
|
|
22
|
+
tok: Tokenizer,
|
|
23
|
+
): string {
|
|
24
|
+
if (tok.count(text) <= maxTokens) return text;
|
|
25
|
+
const tokens = tok.encode(text);
|
|
26
|
+
if (tokens.length > 0 && tokens.length > maxTokens) {
|
|
27
|
+
return tok.decode(tokens.slice(0, maxTokens));
|
|
28
|
+
}
|
|
29
|
+
const limit = Math.min(maxTokens * 4, text.length);
|
|
30
|
+
return text.slice(0, limit);
|
|
31
|
+
}
|
package/src/tool.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import type { ToolDef, ToolFuncParams } from "./llm.js";
|
|
2
|
+
|
|
3
|
+
export type ToolCategory =
|
|
4
|
+
| "workspace"
|
|
5
|
+
| "compute"
|
|
6
|
+
| "data"
|
|
7
|
+
| "web"
|
|
8
|
+
| "planning"
|
|
9
|
+
| "communication"
|
|
10
|
+
| "integrations"
|
|
11
|
+
| "memory"
|
|
12
|
+
| "custom";
|
|
13
|
+
|
|
14
|
+
export type ToolExecuteFunc = (
|
|
15
|
+
ctx: AbortSignal | undefined,
|
|
16
|
+
sandboxId: string,
|
|
17
|
+
args: Record<string, unknown>,
|
|
18
|
+
) => Promise<string>;
|
|
19
|
+
|
|
20
|
+
export type ToolPredicate = (args: Record<string, unknown>) => boolean;
|
|
21
|
+
|
|
22
|
+
export type ToolValidator = (
|
|
23
|
+
ctx: AbortSignal | undefined,
|
|
24
|
+
args: Record<string, unknown>,
|
|
25
|
+
) => Promise<void>;
|
|
26
|
+
|
|
27
|
+
export type PermissionDecision = "allow" | "deny" | "ask_user";
|
|
28
|
+
|
|
29
|
+
export interface PermissionResult {
|
|
30
|
+
decision: PermissionDecision;
|
|
31
|
+
reason?: string;
|
|
32
|
+
updatedArgs?: Record<string, unknown>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type ToolPermissionFn = (
|
|
36
|
+
ctx: AbortSignal | undefined,
|
|
37
|
+
args: Record<string, unknown>,
|
|
38
|
+
) => Promise<PermissionResult>;
|
|
39
|
+
|
|
40
|
+
export type ToolReminderFn = (
|
|
41
|
+
ctx: AbortSignal | undefined,
|
|
42
|
+
) => Promise<string>;
|
|
43
|
+
|
|
44
|
+
export interface Tool {
|
|
45
|
+
name: string;
|
|
46
|
+
description: string;
|
|
47
|
+
category: ToolCategory;
|
|
48
|
+
parameters: ToolFuncParams;
|
|
49
|
+
execute: ToolExecuteFunc;
|
|
50
|
+
aliases?: string[];
|
|
51
|
+
searchHint?: string;
|
|
52
|
+
isReadOnly?: ToolPredicate;
|
|
53
|
+
isConcurrencySafe?: ToolPredicate;
|
|
54
|
+
isDestructive?: ToolPredicate;
|
|
55
|
+
validate?: ToolValidator;
|
|
56
|
+
checkPermissions?: ToolPermissionFn;
|
|
57
|
+
dynamicReminder?: ToolReminderFn;
|
|
58
|
+
hidden?: boolean;
|
|
59
|
+
deferred?: boolean;
|
|
60
|
+
alwaysLoad?: boolean;
|
|
61
|
+
maxResultSizeChars?: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface ToolMatch {
|
|
65
|
+
tool: Tool;
|
|
66
|
+
score: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function toToolDef(t: Tool): ToolDef {
|
|
70
|
+
return {
|
|
71
|
+
type: "function",
|
|
72
|
+
function: {
|
|
73
|
+
name: t.name,
|
|
74
|
+
description: t.description,
|
|
75
|
+
parameters: t.parameters,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function scoreToolMatch(t: Tool, query: string): number {
|
|
81
|
+
let score = 0;
|
|
82
|
+
if (t.name.toLowerCase().includes(query)) score += 0.6;
|
|
83
|
+
if (t.searchHint?.toLowerCase().includes(query)) score += 0.4;
|
|
84
|
+
if (t.description.toLowerCase().includes(query)) score += 0.3;
|
|
85
|
+
if (t.category.toLowerCase().includes(query)) score += 0.1;
|
|
86
|
+
if (t.aliases?.some((a) => a.toLowerCase().includes(query))) score += 0.2;
|
|
87
|
+
return score;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export class ToolRegistry {
|
|
91
|
+
private tools = new Map<string, Tool>();
|
|
92
|
+
private aliases = new Map<string, string>();
|
|
93
|
+
|
|
94
|
+
register(tool: Tool): void {
|
|
95
|
+
this.tools.set(tool.name, tool);
|
|
96
|
+
for (const alias of tool.aliases ?? []) {
|
|
97
|
+
const trimmed = alias.trim();
|
|
98
|
+
if (trimmed && trimmed !== tool.name) {
|
|
99
|
+
this.aliases.set(trimmed, tool.name);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
get(name: string): Tool | undefined {
|
|
105
|
+
return this.tools.get(name) ?? this.tools.get(this.aliases.get(name) ?? "");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
list(): Tool[] {
|
|
109
|
+
return Array.from(this.tools.values());
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
byCategory(category: ToolCategory): Tool[] {
|
|
113
|
+
return this.list().filter((t) => t.category === category);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
names(): string[] {
|
|
117
|
+
return Array.from(this.tools.keys()).sort();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
describeAvailable(): string {
|
|
121
|
+
const tools = this.list().sort((a, b) => a.name.localeCompare(b.name));
|
|
122
|
+
if (tools.length === 0) return "- none";
|
|
123
|
+
const lines = tools
|
|
124
|
+
.filter((t) => t.name.trim())
|
|
125
|
+
.map((t) => {
|
|
126
|
+
const desc = t.description.trim();
|
|
127
|
+
return desc ? `- ${t.name}: ${desc}` : `- ${t.name}`;
|
|
128
|
+
});
|
|
129
|
+
return lines.length === 0 ? "- none" : lines.join("\n");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
toolDefs(): ToolDef[] {
|
|
133
|
+
return this.list()
|
|
134
|
+
.filter((t) => !t.hidden && !t.deferred)
|
|
135
|
+
.map(toToolDef);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async collectDynamicReminders(
|
|
139
|
+
signal?: AbortSignal,
|
|
140
|
+
): Promise<string[]> {
|
|
141
|
+
const tools = this.list()
|
|
142
|
+
.filter((t) => t.dynamicReminder != null)
|
|
143
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
144
|
+
|
|
145
|
+
const results: string[] = [];
|
|
146
|
+
for (const t of tools) {
|
|
147
|
+
const block = await t.dynamicReminder!(signal);
|
|
148
|
+
if (block.trim()) results.push(block);
|
|
149
|
+
}
|
|
150
|
+
return results;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
search(query: string): ToolMatch[] {
|
|
154
|
+
const q = query.toLowerCase().trim();
|
|
155
|
+
if (!q) return [];
|
|
156
|
+
return this.list()
|
|
157
|
+
.map((t) => ({ tool: t, score: scoreToolMatch(t, q) }))
|
|
158
|
+
.filter((m) => m.score > 0)
|
|
159
|
+
.sort((a, b) => b.score - a.score);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
reveal(name: string): void {
|
|
163
|
+
const tool = this.tools.get(name);
|
|
164
|
+
if (!tool) throw new Error(`tool "${name}" not found`);
|
|
165
|
+
tool.hidden = false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
hide(name: string): void {
|
|
169
|
+
const tool = this.tools.get(name);
|
|
170
|
+
if (!tool) throw new Error(`tool "${name}" not found`);
|
|
171
|
+
tool.hidden = true;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export { toToolDef };
|
package/src/tracing.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export interface Span {
|
|
4
|
+
traceId: string;
|
|
5
|
+
spanId: string;
|
|
6
|
+
parentId?: string;
|
|
7
|
+
name: string;
|
|
8
|
+
startedAt: Date;
|
|
9
|
+
finishedAt?: Date;
|
|
10
|
+
attrs?: Record<string, unknown>;
|
|
11
|
+
error?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class Tracer {
|
|
15
|
+
private readonly _traceId: string;
|
|
16
|
+
private spans: Span[] = [];
|
|
17
|
+
private stack: string[] = [];
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
this._traceId = randomUUID();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
traceId(): string {
|
|
24
|
+
return this._traceId;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
startSpan(name: string, attrs?: Record<string, unknown>): Span {
|
|
28
|
+
const span: Span = {
|
|
29
|
+
traceId: this._traceId,
|
|
30
|
+
spanId: randomUUID(),
|
|
31
|
+
parentId: this.stack[this.stack.length - 1],
|
|
32
|
+
name,
|
|
33
|
+
startedAt: new Date(),
|
|
34
|
+
attrs,
|
|
35
|
+
};
|
|
36
|
+
this.spans.push(span);
|
|
37
|
+
this.stack.push(span.spanId);
|
|
38
|
+
return span;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
finishSpan(span: Span, error?: Error): void {
|
|
42
|
+
span.finishedAt = new Date();
|
|
43
|
+
if (error) span.error = error.message;
|
|
44
|
+
const idx = this.stack.lastIndexOf(span.spanId);
|
|
45
|
+
if (idx !== -1) this.stack.splice(idx, 1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
allSpans(): Span[] {
|
|
49
|
+
return this.spans.slice();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface TracedContext {
|
|
54
|
+
__tracer?: Tracer;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function withTracer<T extends TracedContext>(ctx: T, tracer: Tracer): T {
|
|
58
|
+
return { ...ctx, __tracer: tracer };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function tracerFromContext(ctx: TracedContext): Tracer | undefined {
|
|
62
|
+
return ctx.__tracer;
|
|
63
|
+
}
|