@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.
Files changed (111) hide show
  1. package/dist/conversation-kIkMQdYK.d.cts +105 -0
  2. package/dist/conversation-kIkMQdYK.d.ts +105 -0
  3. package/dist/conversation-store-CAyPuBjk.d.ts +10 -0
  4. package/dist/conversation-store-Cl42jpsA.d.cts +10 -0
  5. package/dist/index.cjs +1630 -0
  6. package/dist/index.cjs.map +1 -0
  7. package/dist/index.d.cts +251 -0
  8. package/dist/index.d.ts +251 -0
  9. package/dist/index.js +1536 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/memory-uBLqrQRY.d.cts +28 -0
  12. package/dist/memory-uBLqrQRY.d.ts +28 -0
  13. package/dist/providers/llm/anthropic.cjs +275 -0
  14. package/dist/providers/llm/anthropic.cjs.map +1 -0
  15. package/dist/providers/llm/anthropic.d.cts +22 -0
  16. package/dist/providers/llm/anthropic.d.ts +22 -0
  17. package/dist/providers/llm/anthropic.js +240 -0
  18. package/dist/providers/llm/anthropic.js.map +1 -0
  19. package/dist/providers/llm/ollama.cjs +195 -0
  20. package/dist/providers/llm/ollama.cjs.map +1 -0
  21. package/dist/providers/llm/ollama.d.cts +23 -0
  22. package/dist/providers/llm/ollama.d.ts +23 -0
  23. package/dist/providers/llm/ollama.js +170 -0
  24. package/dist/providers/llm/ollama.js.map +1 -0
  25. package/dist/providers/llm/openai.cjs +213 -0
  26. package/dist/providers/llm/openai.cjs.map +1 -0
  27. package/dist/providers/llm/openai.d.cts +22 -0
  28. package/dist/providers/llm/openai.d.ts +22 -0
  29. package/dist/providers/llm/openai.js +178 -0
  30. package/dist/providers/llm/openai.js.map +1 -0
  31. package/dist/providers/memory/filesystem.cjs +112 -0
  32. package/dist/providers/memory/filesystem.cjs.map +1 -0
  33. package/dist/providers/memory/filesystem.d.cts +17 -0
  34. package/dist/providers/memory/filesystem.d.ts +17 -0
  35. package/dist/providers/memory/filesystem.js +87 -0
  36. package/dist/providers/memory/filesystem.js.map +1 -0
  37. package/dist/providers/store/filesystem.cjs +87 -0
  38. package/dist/providers/store/filesystem.cjs.map +1 -0
  39. package/dist/providers/store/filesystem.d.cts +14 -0
  40. package/dist/providers/store/filesystem.d.ts +14 -0
  41. package/dist/providers/store/filesystem.js +62 -0
  42. package/dist/providers/store/filesystem.js.map +1 -0
  43. package/dist/providers/thread/memory.cjs +81 -0
  44. package/dist/providers/thread/memory.cjs.map +1 -0
  45. package/dist/providers/thread/memory.d.cts +14 -0
  46. package/dist/providers/thread/memory.d.ts +14 -0
  47. package/dist/providers/thread/memory.js +56 -0
  48. package/dist/providers/thread/memory.js.map +1 -0
  49. package/dist/providers/thread/sqlite.cjs +917 -0
  50. package/dist/providers/thread/sqlite.cjs.map +1 -0
  51. package/dist/providers/thread/sqlite.d.cts +17 -0
  52. package/dist/providers/thread/sqlite.d.ts +17 -0
  53. package/dist/providers/thread/sqlite.js +911 -0
  54. package/dist/providers/thread/sqlite.js.map +1 -0
  55. package/dist/providers/tokenizers/auto.cjs +136 -0
  56. package/dist/providers/tokenizers/auto.cjs.map +1 -0
  57. package/dist/providers/tokenizers/auto.d.cts +24 -0
  58. package/dist/providers/tokenizers/auto.d.ts +24 -0
  59. package/dist/providers/tokenizers/auto.js +107 -0
  60. package/dist/providers/tokenizers/auto.js.map +1 -0
  61. package/dist/streaming-B-P6Fw_k.d.cts +372 -0
  62. package/dist/streaming-BtD23BE0.d.ts +372 -0
  63. package/dist/thread-C2b9xRMJ.d.cts +30 -0
  64. package/dist/thread-C2b9xRMJ.d.ts +30 -0
  65. package/dist/tokenizer-BhG_RGUk.d.cts +13 -0
  66. package/dist/tokenizer-BhG_RGUk.d.ts +13 -0
  67. package/package.json +84 -0
  68. package/src/agent-loop.ts +311 -0
  69. package/src/agent-source.ts +12 -0
  70. package/src/artifact.ts +31 -0
  71. package/src/compaction.ts +75 -0
  72. package/src/context-budget.ts +65 -0
  73. package/src/conversation-store.ts +8 -0
  74. package/src/conversation.ts +42 -0
  75. package/src/dispatch.ts +207 -0
  76. package/src/engine.ts +53 -0
  77. package/src/execution-context.ts +31 -0
  78. package/src/index.ts +37 -0
  79. package/src/interrupt-store.ts +25 -0
  80. package/src/interrupt.ts +55 -0
  81. package/src/llm-router.ts +34 -0
  82. package/src/llm.ts +100 -0
  83. package/src/memory-selector.ts +38 -0
  84. package/src/memory.ts +34 -0
  85. package/src/mode.ts +81 -0
  86. package/src/permissions.ts +104 -0
  87. package/src/protocol.ts +1 -0
  88. package/src/providers/llm/anthropic.ts +298 -0
  89. package/src/providers/llm/ollama.ts +219 -0
  90. package/src/providers/llm/openai.ts +215 -0
  91. package/src/providers/memory/filesystem.ts +99 -0
  92. package/src/providers/store/filesystem.ts +64 -0
  93. package/src/providers/thread/memory.ts +67 -0
  94. package/src/providers/thread/sqlite.ts +147 -0
  95. package/src/providers/tokenizers/auto.ts +26 -0
  96. package/src/providers/tokenizers/byte.ts +27 -0
  97. package/src/providers/tokenizers/tiktoken.ts +91 -0
  98. package/src/reasoning.ts +7 -0
  99. package/src/rule-matcher.ts +32 -0
  100. package/src/runtime.ts +416 -0
  101. package/src/safety.ts +56 -0
  102. package/src/sandbox.ts +23 -0
  103. package/src/session-context.ts +33 -0
  104. package/src/skill-source.ts +21 -0
  105. package/src/streaming.ts +124 -0
  106. package/src/system-prompt.ts +71 -0
  107. package/src/system-reminder.ts +9 -0
  108. package/src/thread.ts +33 -0
  109. package/src/tokenizer.ts +31 -0
  110. package/src/tool.ts +175 -0
  111. package/src/tracing.ts +63 -0
@@ -0,0 +1,207 @@
1
+ import type { ToolCallEntry } from "./llm.js";
2
+ import type { ToolRegistry } from "./tool.js";
3
+ import type { SandboxDriver } from "./sandbox.js";
4
+ import type { PermissionEngine } from "./permissions.js";
5
+ import type { ChatMessage } from "./llm.js";
6
+
7
+ export type { ToolCallEntry };
8
+
9
+ export interface ToolResult {
10
+ toolCallId: string;
11
+ name: string;
12
+ content: string;
13
+ error?: Error;
14
+ }
15
+
16
+ export class ToolDispatcher {
17
+ private permissions?: PermissionEngine;
18
+
19
+ constructor(
20
+ private tools: ToolRegistry,
21
+ private sandbox?: SandboxDriver,
22
+ ) {}
23
+
24
+ withPermissions(engine: PermissionEngine): this {
25
+ this.permissions = engine;
26
+ return this;
27
+ }
28
+
29
+ async dispatch(
30
+ call: ToolCallEntry,
31
+ sandboxId: string,
32
+ signal?: AbortSignal,
33
+ ): Promise<ToolResult> {
34
+ const tool = this.tools.get(call.name);
35
+ if (!tool) {
36
+ const err = new Error(`tool "${call.name}" not found`);
37
+ return {
38
+ toolCallId: call.id,
39
+ name: call.name,
40
+ content: `error: tool "${call.name}" not found in registry`,
41
+ error: err,
42
+ };
43
+ }
44
+
45
+ let args: Record<string, unknown> = {};
46
+ if (call.arguments && call.arguments !== "{}") {
47
+ try {
48
+ args = JSON.parse(call.arguments) as Record<string, unknown>;
49
+ } catch (e) {
50
+ const err = e instanceof Error ? e : new Error(String(e));
51
+ return {
52
+ toolCallId: call.id,
53
+ name: call.name,
54
+ content: `error: invalid JSON arguments: ${err.message}`,
55
+ error: err,
56
+ };
57
+ }
58
+ }
59
+
60
+ if (tool.validate) {
61
+ try {
62
+ await tool.validate(signal, args);
63
+ } catch (e) {
64
+ const err = e instanceof Error ? e : new Error(String(e));
65
+ return {
66
+ toolCallId: call.id,
67
+ name: call.name,
68
+ content: `error: ${err.message}`,
69
+ error: err,
70
+ };
71
+ }
72
+ }
73
+
74
+ if (this.permissions) {
75
+ const dec = await this.permissions.decide(tool, args, signal);
76
+ if (dec.behavior === "deny" || dec.behavior === "ask") {
77
+ const reason = dec.reason?.trim() || "permission denied";
78
+ const content = `<tool_use_error>The user did not approve this ${tool.name} call. Reason: ${reason}.\n\nThe tool was NOT executed. Do NOT claim success. Do NOT retry the same call. Acknowledge the rejection and ask the user how to proceed (or stop).</tool_use_error>`;
79
+ return {
80
+ toolCallId: call.id,
81
+ name: call.name,
82
+ content,
83
+ error: new Error(reason),
84
+ };
85
+ }
86
+ if (dec.updatedInput) args = dec.updatedInput;
87
+ } else if (tool.checkPermissions) {
88
+ try {
89
+ const result = await tool.checkPermissions(signal, args);
90
+ if (result.decision === "deny" || result.decision === "ask_user") {
91
+ const reason = result.reason?.trim() || "permission denied";
92
+ return {
93
+ toolCallId: call.id,
94
+ name: call.name,
95
+ content: `error: ${reason}`,
96
+ error: new Error(reason),
97
+ };
98
+ }
99
+ if (result.updatedArgs) args = result.updatedArgs as Record<string, unknown>;
100
+ } catch (e) {
101
+ const err = e instanceof Error ? e : new Error(String(e));
102
+ return {
103
+ toolCallId: call.id,
104
+ name: call.name,
105
+ content: `error: permission check failed: ${err.message}`,
106
+ error: err,
107
+ };
108
+ }
109
+ }
110
+
111
+ try {
112
+ const content = await tool.execute(signal, sandboxId, args);
113
+ return { toolCallId: call.id, name: call.name, content };
114
+ } catch (e) {
115
+ const err = e instanceof Error ? e : new Error(String(e));
116
+ return {
117
+ toolCallId: call.id,
118
+ name: call.name,
119
+ content: `error: ${err.message}`,
120
+ error: err,
121
+ };
122
+ }
123
+ }
124
+
125
+ async dispatchAll(
126
+ calls: ToolCallEntry[],
127
+ sandboxId: string,
128
+ signal?: AbortSignal,
129
+ ): Promise<ToolResult[]> {
130
+ const results: ToolResult[] = [];
131
+ for (const call of calls) {
132
+ results.push(await this.dispatch(call, sandboxId, signal));
133
+ }
134
+ return results;
135
+ }
136
+
137
+ async dispatchParallel(
138
+ calls: ToolCallEntry[],
139
+ sandboxId: string,
140
+ signal?: AbortSignal,
141
+ ): Promise<ToolResult[]> {
142
+ if (calls.length === 0) return [];
143
+ if (calls.length === 1) return [await this.dispatch(calls[0]!, sandboxId, signal)];
144
+
145
+ const results = new Array<ToolResult>(calls.length);
146
+
147
+ const safe: Array<{ idx: number; call: ToolCallEntry }> = [];
148
+ const unsafe: Array<{ idx: number; call: ToolCallEntry }> = [];
149
+
150
+ for (let i = 0; i < calls.length; i++) {
151
+ const call = calls[i]!;
152
+ const tool = this.tools.get(call.name);
153
+ let isSafe = false;
154
+ if (tool?.isConcurrencySafe) {
155
+ let args: Record<string, unknown> = {};
156
+ try {
157
+ if (call.arguments && call.arguments !== "{}") {
158
+ args = JSON.parse(call.arguments) as Record<string, unknown>;
159
+ }
160
+ } catch {}
161
+ isSafe = tool.isConcurrencySafe(args);
162
+ }
163
+ if (isSafe) {
164
+ safe.push({ idx: i, call });
165
+ } else {
166
+ unsafe.push({ idx: i, call });
167
+ }
168
+ }
169
+
170
+ await Promise.all(
171
+ safe.map(async ({ idx, call }) => {
172
+ results[idx] = await this.dispatch(call, sandboxId, signal);
173
+ }),
174
+ );
175
+
176
+ for (const { idx, call } of unsafe) {
177
+ results[idx] = await this.dispatch(call, sandboxId, signal);
178
+ }
179
+
180
+ return results as ToolResult[];
181
+ }
182
+ }
183
+
184
+ export function toolResultsToMessages(
185
+ assistantToolCalls: ToolCallEntry[],
186
+ results: ToolResult[],
187
+ ): ChatMessage[] {
188
+ const msgs: ChatMessage[] = [
189
+ { role: "assistant", content: "", toolCalls: assistantToolCalls },
190
+ ];
191
+ for (const r of results) {
192
+ msgs.push({
193
+ role: "tool",
194
+ name: r.name,
195
+ toolCallId: r.toolCallId,
196
+ content: r.content,
197
+ });
198
+ }
199
+ return msgs;
200
+ }
201
+
202
+ export function areIndependent(calls: ToolCallEntry[]): boolean {
203
+ if (calls.length <= 1) return true;
204
+ return !calls.some(
205
+ (c) => c.arguments.includes("${") || c.arguments.includes("{{"),
206
+ );
207
+ }
package/src/engine.ts ADDED
@@ -0,0 +1,53 @@
1
+ import type { LLMProvider } from "./llm.js";
2
+ import type { MemoryProvider } from "./memory.js";
3
+ import type { SandboxDriver } from "./sandbox.js";
4
+ import type { ThreadProvider } from "./thread.js";
5
+ import type { ModeProvider } from "./mode.js";
6
+ import type { ContextBudget } from "./context-budget.js";
7
+ import { defaultContextBudget } from "./context-budget.js";
8
+ import { SystemPromptBuilder } from "./system-prompt.js";
9
+ import { ToolRegistry } from "./tool.js";
10
+
11
+ export interface Engine {
12
+ memory?: MemoryProvider;
13
+ sandbox?: SandboxDriver;
14
+ tools?: ToolRegistry;
15
+ threads?: ThreadProvider;
16
+ modes?: ModeProvider;
17
+ llm?: LLMProvider;
18
+ prompt?: SystemPromptBuilder;
19
+ budget?: ContextBudget;
20
+ }
21
+
22
+ export function newEngine(...opts: Array<(e: Engine) => void>): Engine {
23
+ const e: Engine = {};
24
+ for (const opt of opts) opt(e);
25
+ return e;
26
+ }
27
+
28
+ export function newEngineWithDefaults(windowSize: number): Engine {
29
+ const budget = defaultContextBudget(windowSize);
30
+ return {
31
+ prompt: new SystemPromptBuilder(),
32
+ budget,
33
+ };
34
+ }
35
+
36
+ export const hasMemory = (e: Engine) => e.memory != null;
37
+ export const hasSandbox = (e: Engine) => e.sandbox != null;
38
+ export const hasTools = (e: Engine) => e.tools != null;
39
+ export const hasThreads = (e: Engine) => e.threads != null;
40
+ export const hasModes = (e: Engine) => e.modes != null;
41
+ export const hasLLM = (e: Engine) => e.llm != null;
42
+ export const hasPrompt = (e: Engine) => e.prompt != null;
43
+ export const hasBudget = (e: Engine) => e.budget != null;
44
+
45
+ // Functional options
46
+ export const withMemory = (p: MemoryProvider) => (e: Engine) => { e.memory = p; };
47
+ export const withSandbox = (p: SandboxDriver) => (e: Engine) => { e.sandbox = p; };
48
+ export const withTools = (r: ToolRegistry) => (e: Engine) => { e.tools = r; };
49
+ export const withThreads = (p: ThreadProvider) => (e: Engine) => { e.threads = p; };
50
+ export const withModes = (p: ModeProvider) => (e: Engine) => { e.modes = p; };
51
+ export const withLLM = (p: LLMProvider) => (e: Engine) => { e.llm = p; };
52
+ export const withPrompt = (b: SystemPromptBuilder) => (e: Engine) => { e.prompt = b; };
53
+ export const withBudget = (b: ContextBudget) => (e: Engine) => { e.budget = b; };
@@ -0,0 +1,31 @@
1
+ export type TodoStatus = "pending" | "in_progress" | "completed";
2
+
3
+ export interface Todo {
4
+ id: string;
5
+ content: string;
6
+ activeForm?: string;
7
+ status: TodoStatus;
8
+ }
9
+
10
+ export interface ExecutionContext {
11
+ todos(): Todo[];
12
+ setTodos(todos: Todo[]): void;
13
+ markDone(id: string): void;
14
+ }
15
+
16
+ export class InMemoryExecutionContext implements ExecutionContext {
17
+ private _todos: Todo[] = [];
18
+
19
+ todos(): Todo[] {
20
+ return this._todos.slice();
21
+ }
22
+
23
+ setTodos(todos: Todo[]): void {
24
+ this._todos = todos.slice();
25
+ }
26
+
27
+ markDone(id: string): void {
28
+ const todo = this._todos.find((t) => t.id === id);
29
+ if (todo) todo.status = "completed";
30
+ }
31
+ }
package/src/index.ts ADDED
@@ -0,0 +1,37 @@
1
+ // Core types
2
+ export * from "./protocol.js";
3
+ export * from "./llm.js";
4
+ export * from "./tool.js";
5
+ export * from "./streaming.js";
6
+ export * from "./conversation.js";
7
+ export * from "./conversation-store.js";
8
+ export * from "./mode.js";
9
+ export * from "./memory.js";
10
+ export * from "./thread.js";
11
+ export * from "./sandbox.js";
12
+ export * from "./artifact.js";
13
+ export * from "./interrupt.js";
14
+ export * from "./safety.js";
15
+ export * from "./tracing.js";
16
+ export * from "./reasoning.js";
17
+ export * from "./tokenizer.js";
18
+ export * from "./system-prompt.js";
19
+ export * from "./system-reminder.js";
20
+ export * from "./context-budget.js";
21
+ export * from "./compaction.js";
22
+ export * from "./permissions.js";
23
+ export * from "./session-context.js";
24
+
25
+ export * from "./execution-context.js";
26
+ export * from "./interrupt-store.js";
27
+ export * from "./agent-source.js";
28
+ export * from "./memory-selector.js";
29
+ export * from "./rule-matcher.js";
30
+ export * from "./skill-source.js";
31
+
32
+ // Core components
33
+ export * from "./engine.js";
34
+ export * from "./dispatch.js";
35
+ export * from "./agent-loop.js";
36
+ export * from "./llm-router.js";
37
+ export * from "./runtime.js";
@@ -0,0 +1,25 @@
1
+ import type { InterruptRequest } from "./interrupt.js";
2
+
3
+ export interface InterruptStore {
4
+ put(req: InterruptRequest, signal?: AbortSignal): Promise<void>;
5
+ get(id: string, signal?: AbortSignal): Promise<{ req: InterruptRequest; ok: true } | { ok: false }>;
6
+ delete(id: string, signal?: AbortSignal): Promise<void>;
7
+ }
8
+
9
+ export class InMemoryInterruptStore implements InterruptStore {
10
+ private store = new Map<string, InterruptRequest>();
11
+
12
+ async put(req: InterruptRequest): Promise<void> {
13
+ this.store.set(req.id, req);
14
+ }
15
+
16
+ async get(id: string): Promise<{ req: InterruptRequest; ok: true } | { ok: false }> {
17
+ const req = this.store.get(id);
18
+ if (!req) return { ok: false };
19
+ return { req, ok: true };
20
+ }
21
+
22
+ async delete(id: string): Promise<void> {
23
+ this.store.delete(id);
24
+ }
25
+ }
@@ -0,0 +1,55 @@
1
+ export type InterruptKind = "approval" | "question" | "form_input";
2
+
3
+ export interface FormField {
4
+ name: string;
5
+ label: string;
6
+ type: "text" | "textarea" | "select" | "checkbox";
7
+ options?: string[];
8
+ required?: boolean;
9
+ defaultValue?: string;
10
+ }
11
+
12
+ export interface InterruptRequest {
13
+ id: string;
14
+ kind: InterruptKind;
15
+ title: string;
16
+ description?: string;
17
+ toolName?: string;
18
+ toolArgs?: Record<string, unknown>;
19
+ fields?: FormField[];
20
+ createdAt: Date;
21
+ }
22
+
23
+ export interface InterruptResponse {
24
+ id: string;
25
+ approved?: boolean;
26
+ answer?: unknown;
27
+ modifiedArgs?: string;
28
+ }
29
+
30
+ export type InterruptRequester = (
31
+ req: InterruptRequest,
32
+ signal?: AbortSignal,
33
+ ) => Promise<InterruptResponse>;
34
+
35
+ export class InterruptGate {
36
+ private requester: InterruptRequester;
37
+ private pending = new Map<string, InterruptRequest>();
38
+
39
+ constructor(requester: InterruptRequester) {
40
+ this.requester = requester;
41
+ }
42
+
43
+ async wait(req: InterruptRequest, signal?: AbortSignal): Promise<InterruptResponse> {
44
+ this.pending.set(req.id, req);
45
+ try {
46
+ return await this.requester(req, signal);
47
+ } finally {
48
+ this.pending.delete(req.id);
49
+ }
50
+ }
51
+
52
+ pendingRequests(): InterruptRequest[] {
53
+ return Array.from(this.pending.values());
54
+ }
55
+ }
@@ -0,0 +1,34 @@
1
+ import type { LLMProvider, ChatRequest, ChatResponse } from "./llm.js";
2
+
3
+ export function parseModelRef(ref: string): [string, string] {
4
+ const slash = ref.indexOf("/");
5
+ if (slash === -1) return ["", ref];
6
+ return [ref.slice(0, slash), ref.slice(slash + 1)];
7
+ }
8
+
9
+ export class RoutedLLMProvider implements LLMProvider {
10
+ private routes = new Map<string, LLMProvider>();
11
+ private fallback?: LLMProvider;
12
+
13
+ register(provider: string, llm: LLMProvider): this {
14
+ this.routes.set(provider, llm);
15
+ return this;
16
+ }
17
+
18
+ withFallback(llm: LLMProvider): this {
19
+ this.fallback = llm;
20
+ return this;
21
+ }
22
+
23
+ route(model: string): LLMProvider {
24
+ const [provider] = parseModelRef(model);
25
+ const resolved = provider ? this.routes.get(provider) : undefined;
26
+ if (resolved) return resolved;
27
+ if (this.fallback) return this.fallback;
28
+ throw new Error(`no provider registered for model: ${model}`);
29
+ }
30
+
31
+ async chat(req: ChatRequest, signal?: AbortSignal): Promise<ChatResponse> {
32
+ return this.route(req.model ?? "").chat(req, signal);
33
+ }
34
+ }
package/src/llm.ts ADDED
@@ -0,0 +1,100 @@
1
+ export type Role = "system" | "user" | "assistant" | "tool";
2
+
3
+ export interface ImageContent {
4
+ source?: string;
5
+ mediaType?: string;
6
+ url?: string;
7
+ }
8
+
9
+ export interface DocumentContent {
10
+ source?: string;
11
+ mediaType?: string;
12
+ url?: string;
13
+ title?: string;
14
+ cacheControl?: string;
15
+ }
16
+
17
+ export interface ToolCallEntry {
18
+ id: string;
19
+ name: string;
20
+ arguments: string;
21
+ }
22
+
23
+ export interface ToolChoice {
24
+ type: "auto" | "any" | "tool";
25
+ name?: string;
26
+ disableParallelToolUse?: boolean;
27
+ }
28
+
29
+ export interface ChatMessage {
30
+ role: Role;
31
+ content: string;
32
+ name?: string;
33
+ toolCallId?: string;
34
+ toolCalls?: ToolCallEntry[];
35
+ images?: ImageContent[];
36
+ documents?: DocumentContent[];
37
+ }
38
+
39
+ export interface ToolFuncParams {
40
+ type: "object";
41
+ properties?: Record<string, ToolParam>;
42
+ required?: string[];
43
+ }
44
+
45
+ export interface ToolParam {
46
+ type: string;
47
+ description?: string;
48
+ enum?: string[];
49
+ items?: ToolParam;
50
+ properties?: Record<string, ToolParam>;
51
+ required?: string[];
52
+ }
53
+
54
+ export interface ToolDef {
55
+ type: "function";
56
+ function: {
57
+ name: string;
58
+ description: string;
59
+ parameters: ToolFuncParams;
60
+ };
61
+ }
62
+
63
+ export interface ChatRequest {
64
+ model?: string;
65
+ messages: ChatMessage[];
66
+ tools?: ToolDef[];
67
+ toolChoice?: ToolChoice;
68
+ temperature?: number;
69
+ topP?: number;
70
+ topK?: number;
71
+ maxTokens?: number;
72
+ reasoningEffort?: string;
73
+ thinkingBudget?: number;
74
+ stop?: string[];
75
+ metadata?: Record<string, string>;
76
+ }
77
+
78
+ export interface TokenUsage {
79
+ promptTokens: number;
80
+ completionTokens: number;
81
+ totalTokens: number;
82
+ }
83
+
84
+ export interface ChatResponse {
85
+ content: string;
86
+ reasoning?: string;
87
+ thinkingContent?: string;
88
+ toolCalls?: ToolCallEntry[];
89
+ finishReason: string;
90
+ usage: TokenUsage;
91
+ model?: string;
92
+ }
93
+
94
+ export interface LLMProvider {
95
+ chat(req: ChatRequest, signal?: AbortSignal): Promise<ChatResponse>;
96
+ }
97
+
98
+ export interface ModelRouter {
99
+ route(model: string): LLMProvider;
100
+ }
@@ -0,0 +1,38 @@
1
+ export interface MemoryHeader {
2
+ path: string;
3
+ title?: string;
4
+ description?: string;
5
+ type?: string;
6
+ }
7
+
8
+ export interface MemorySelector {
9
+ select(
10
+ query: string,
11
+ headers: MemoryHeader[],
12
+ limit: number,
13
+ signal?: AbortSignal,
14
+ ): Promise<MemoryHeader[]>;
15
+ }
16
+
17
+ export class KeywordMemorySelector implements MemorySelector {
18
+ async select(
19
+ query: string,
20
+ headers: MemoryHeader[],
21
+ limit: number,
22
+ ): Promise<MemoryHeader[]> {
23
+ const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
24
+ if (!terms.length || !headers.length) return [];
25
+
26
+ const scored = headers.map((h) => {
27
+ const text = `${h.title ?? ""} ${h.description ?? ""} ${h.type ?? ""} ${h.path}`.toLowerCase();
28
+ const score = terms.reduce((acc, t) => acc + (text.includes(t) ? 1 : 0), 0);
29
+ return { h, score };
30
+ });
31
+
32
+ return scored
33
+ .filter((s) => s.score > 0)
34
+ .sort((a, b) => b.score - a.score)
35
+ .slice(0, limit)
36
+ .map((s) => s.h);
37
+ }
38
+ }
package/src/memory.ts ADDED
@@ -0,0 +1,34 @@
1
+ export type Scope = "user" | "project";
2
+
3
+ export interface MemoryEntry {
4
+ path: string;
5
+ scope: Scope;
6
+ content?: string;
7
+ source?: string;
8
+ updatedAt?: number;
9
+ }
10
+
11
+ export interface MemorySearchResult extends MemoryEntry {
12
+ score: number;
13
+ }
14
+
15
+ export interface MemoryProvider {
16
+ view(scope: Scope, path: string, signal?: AbortSignal): Promise<string>;
17
+ create(scope: Scope, path: string, content: string, signal?: AbortSignal): Promise<void>;
18
+ strReplace(scope: Scope, path: string, oldStr: string, newStr: string, signal?: AbortSignal): Promise<void>;
19
+ delete(scope: Scope, path: string, signal?: AbortSignal): Promise<void>;
20
+ rename(scope: Scope, oldPath: string, newPath: string, signal?: AbortSignal): Promise<void>;
21
+ list(scope: Scope, path: string, signal?: AbortSignal): Promise<string[]>;
22
+ search(scope: Scope, query: string, signal?: AbortSignal): Promise<MemoryEntry[]>;
23
+ }
24
+
25
+ export interface MemoryRoot {
26
+ scope: Scope;
27
+ path: string;
28
+ label: string;
29
+ }
30
+
31
+ export const DEFAULT_MEMORY_ROOTS: MemoryRoot[] = [
32
+ { scope: "user", path: "/", label: "User memory" },
33
+ { scope: "project", path: "/", label: "Project memory" },
34
+ ];
package/src/mode.ts ADDED
@@ -0,0 +1,81 @@
1
+ export type BaseMode = "balanced" | "analyst" | "deep_work";
2
+
3
+ export type ToolsMode = "allowlist" | "denylist";
4
+
5
+ export interface ModeMeta {
6
+ id: string;
7
+ name: string;
8
+ baseMode?: string;
9
+ toolsMode?: ToolsMode;
10
+ tools?: string[];
11
+ model?: string;
12
+ reasoningEffort?: string;
13
+ temperature?: string;
14
+ author?: string;
15
+ created?: string;
16
+ }
17
+
18
+ export interface ModelSettings {
19
+ model?: string;
20
+ reasoningEffort?: string;
21
+ temperature?: number;
22
+ }
23
+
24
+ export interface Mode {
25
+ meta: ModeMeta;
26
+ id: string;
27
+ name: string;
28
+ baseModeId?: BaseMode;
29
+ promptContent?: string;
30
+ modelSettings?: ModelSettings;
31
+ toolsMode?: ToolsMode;
32
+ toolsList?: string[];
33
+ }
34
+
35
+ export function isToolAllowed(mode: Mode, toolName: string): boolean {
36
+ if (!mode.toolsMode || !mode.toolsList?.length) return true;
37
+ const listed = mode.toolsList.includes(toolName);
38
+ if (mode.toolsMode === "allowlist") return listed;
39
+ if (mode.toolsMode === "denylist") return !listed;
40
+ return true;
41
+ }
42
+
43
+ export interface ModeProvider {
44
+ get(modeId: string, signal?: AbortSignal): Promise<Mode>;
45
+ list(signal?: AbortSignal): Promise<Mode[]>;
46
+ create(mode: Mode, signal?: AbortSignal): Promise<Mode>;
47
+ builtinModes(): Mode[];
48
+ }
49
+
50
+ export class StaticModeProvider implements ModeProvider {
51
+ private modes: Map<string, Mode>;
52
+ private modeList: Mode[];
53
+
54
+ constructor(modes: Mode[]) {
55
+ this.modes = new Map();
56
+ this.modeList = [];
57
+ for (const mode of modes) {
58
+ if (!mode.id) continue;
59
+ this.modes.set(mode.id, mode);
60
+ this.modeList.push(mode);
61
+ }
62
+ }
63
+
64
+ async get(modeId: string): Promise<Mode> {
65
+ const mode = this.modes.get(modeId);
66
+ if (!mode) throw new Error(`mode not found: ${modeId}`);
67
+ return mode;
68
+ }
69
+
70
+ async list(): Promise<Mode[]> {
71
+ return this.modeList;
72
+ }
73
+
74
+ async create(): Promise<Mode> {
75
+ throw new Error("mode creation not supported by static mode provider");
76
+ }
77
+
78
+ builtinModes(): Mode[] {
79
+ return [];
80
+ }
81
+ }