@tanstack/ai-code-mode-snippets 0.3.14

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 (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +208 -0
  3. package/dist/esm/code-mode-with-snippets.d.ts +65 -0
  4. package/dist/esm/code-mode-with-snippets.js +145 -0
  5. package/dist/esm/code-mode-with-snippets.js.map +1 -0
  6. package/dist/esm/create-snippet-management-tools.d.ts +40 -0
  7. package/dist/esm/create-snippet-management-tools.js +173 -0
  8. package/dist/esm/create-snippet-management-tools.js.map +1 -0
  9. package/dist/esm/create-snippets-system-prompt.d.ts +22 -0
  10. package/dist/esm/create-snippets-system-prompt.js +234 -0
  11. package/dist/esm/create-snippets-system-prompt.js.map +1 -0
  12. package/dist/esm/generate-snippet-types.d.ts +7 -0
  13. package/dist/esm/generate-snippet-types.js +79 -0
  14. package/dist/esm/generate-snippet-types.js.map +1 -0
  15. package/dist/esm/index.d.ts +14 -0
  16. package/dist/esm/index.js +10 -0
  17. package/dist/esm/select-relevant-snippets.d.ts +29 -0
  18. package/dist/esm/select-relevant-snippets.js +56 -0
  19. package/dist/esm/select-relevant-snippets.js.map +1 -0
  20. package/dist/esm/snippets-to-bindings.d.ts +34 -0
  21. package/dist/esm/snippets-to-bindings.js +74 -0
  22. package/dist/esm/snippets-to-bindings.js.map +1 -0
  23. package/dist/esm/snippets-to-tools.d.ts +74 -0
  24. package/dist/esm/snippets-to-tools.js +147 -0
  25. package/dist/esm/snippets-to-tools.js.map +1 -0
  26. package/dist/esm/storage/file-storage.d.ts +27 -0
  27. package/dist/esm/storage/file-storage.js +155 -0
  28. package/dist/esm/storage/file-storage.js.map +1 -0
  29. package/dist/esm/storage/index.d.ts +3 -0
  30. package/dist/esm/storage/index.js +3 -0
  31. package/dist/esm/storage/memory-storage.d.ts +17 -0
  32. package/dist/esm/storage/memory-storage.js +100 -0
  33. package/dist/esm/storage/memory-storage.js.map +1 -0
  34. package/dist/esm/trust-strategies.d.ts +50 -0
  35. package/dist/esm/trust-strategies.js +73 -0
  36. package/dist/esm/trust-strategies.js.map +1 -0
  37. package/dist/esm/types.d.ts +216 -0
  38. package/package.json +92 -0
  39. package/src/code-mode-with-snippets.ts +210 -0
  40. package/src/create-snippet-management-tools.ts +298 -0
  41. package/src/create-snippets-system-prompt.ts +289 -0
  42. package/src/generate-snippet-types.ts +162 -0
  43. package/src/index.ts +60 -0
  44. package/src/select-relevant-snippets.ts +136 -0
  45. package/src/snippets-to-bindings.ts +135 -0
  46. package/src/snippets-to-tools.ts +325 -0
  47. package/src/storage/file-storage.ts +275 -0
  48. package/src/storage/index.ts +6 -0
  49. package/src/storage/memory-storage.ts +172 -0
  50. package/src/trust-strategies.ts +142 -0
  51. package/src/types.ts +289 -0
@@ -0,0 +1,74 @@
1
+ //#region src/snippets-to-bindings.ts
2
+ /**
3
+ * Convert snippets to sandbox bindings with the snippet_ prefix.
4
+ * Snippets become callable functions inside the sandbox.
5
+ */
6
+ function snippetsToBindings({ snippets, context, executeInSandbox, storage }) {
7
+ const bindings = {};
8
+ for (const snippet of snippets) {
9
+ const bindingName = `snippet_${snippet.name}`;
10
+ bindings[bindingName] = {
11
+ name: bindingName,
12
+ description: snippet.description,
13
+ inputSchema: snippet.inputSchema,
14
+ outputSchema: snippet.outputSchema,
15
+ execute: async (input) => {
16
+ const startTime = Date.now();
17
+ context?.emitCustomEvent("code_mode:snippet_call", {
18
+ snippet: snippet.name,
19
+ input,
20
+ timestamp: startTime
21
+ });
22
+ try {
23
+ const result = await executeInSandbox(`
24
+ const input = ${JSON.stringify(input)};
25
+ ${snippet.code}
26
+ `, input);
27
+ const duration = Date.now() - startTime;
28
+ context?.emitCustomEvent("code_mode:snippet_result", {
29
+ snippet: snippet.name,
30
+ result,
31
+ duration,
32
+ timestamp: Date.now()
33
+ });
34
+ storage.updateStats(snippet.name, true).catch(() => {});
35
+ return result;
36
+ } catch (error) {
37
+ const duration = Date.now() - startTime;
38
+ context?.emitCustomEvent("code_mode:snippet_error", {
39
+ snippet: snippet.name,
40
+ error: error instanceof Error ? error.message : String(error),
41
+ duration,
42
+ timestamp: Date.now()
43
+ });
44
+ storage.updateStats(snippet.name, false).catch(() => {});
45
+ throw error;
46
+ }
47
+ }
48
+ };
49
+ }
50
+ return bindings;
51
+ }
52
+ /**
53
+ * Create a simple binding record for snippets without full sandbox execution.
54
+ * This is used when snippets are being documented in the system prompt
55
+ * but not yet being executed.
56
+ */
57
+ function snippetsToSimpleBindings(snippets) {
58
+ const bindings = {};
59
+ for (const snippet of snippets) {
60
+ const bindingName = `snippet_${snippet.name}`;
61
+ bindings[bindingName] = {
62
+ name: bindingName,
63
+ description: snippet.description,
64
+ inputSchema: snippet.inputSchema,
65
+ outputSchema: snippet.outputSchema,
66
+ execute: () => Promise.reject(/* @__PURE__ */ new Error(`Snippet ${snippet.name} is not available for execution in this context`))
67
+ };
68
+ }
69
+ return bindings;
70
+ }
71
+ //#endregion
72
+ export { snippetsToBindings, snippetsToSimpleBindings };
73
+
74
+ //# sourceMappingURL=snippets-to-bindings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snippets-to-bindings.js","names":[],"sources":["../../src/snippets-to-bindings.ts"],"sourcesContent":["import type { ToolExecutionContext } from '@tanstack/ai'\nimport type { ToolBinding } from '@tanstack/ai-code-mode'\nimport type { Snippet, SnippetStorage } from './types'\n\ninterface SnippetsToBindingsOptions {\n /**\n * Snippets to convert to bindings\n */\n snippets: Array<Snippet>\n\n /**\n * Tool execution context for emitting custom events\n */\n context?: ToolExecutionContext\n\n /**\n * Function to execute snippet code in the sandbox\n * The snippet code receives `input` as a variable\n */\n executeInSandbox: (code: string, input: unknown) => Promise<unknown>\n\n /**\n * Storage for updating execution stats\n */\n storage: SnippetStorage\n}\n\n/**\n * Convert snippets to sandbox bindings with the snippet_ prefix.\n * Snippets become callable functions inside the sandbox.\n */\nexport function snippetsToBindings({\n snippets,\n context,\n executeInSandbox,\n storage,\n}: SnippetsToBindingsOptions): Record<string, ToolBinding> {\n const bindings: Record<string, ToolBinding> = {}\n\n for (const snippet of snippets) {\n const bindingName = `snippet_${snippet.name}`\n\n bindings[bindingName] = {\n name: bindingName,\n description: snippet.description,\n inputSchema: snippet.inputSchema,\n outputSchema: snippet.outputSchema,\n execute: async (input: unknown) => {\n const startTime = Date.now()\n\n // Emit snippet call event\n context?.emitCustomEvent('code_mode:snippet_call', {\n snippet: snippet.name,\n input,\n timestamp: startTime,\n })\n\n try {\n // Wrap the snippet code to receive input as a variable\n const wrappedCode = `\n const input = ${JSON.stringify(input)};\n ${snippet.code}\n `\n\n const result = await executeInSandbox(wrappedCode, input)\n const duration = Date.now() - startTime\n\n // Emit success event\n context?.emitCustomEvent('code_mode:snippet_result', {\n snippet: snippet.name,\n result,\n duration,\n timestamp: Date.now(),\n })\n\n // Update stats (async, don't await to not block)\n storage.updateStats(snippet.name, true).catch(() => {\n // Silently ignore stats update failures\n })\n\n return result\n } catch (error) {\n const duration = Date.now() - startTime\n\n // Emit error event\n context?.emitCustomEvent('code_mode:snippet_error', {\n snippet: snippet.name,\n error: error instanceof Error ? error.message : String(error),\n duration,\n timestamp: Date.now(),\n })\n\n // Update stats (async, don't await)\n storage.updateStats(snippet.name, false).catch(() => {\n // Silently ignore stats update failures\n })\n\n throw error\n }\n },\n }\n }\n\n return bindings\n}\n\n/**\n * Create a simple binding record for snippets without full sandbox execution.\n * This is used when snippets are being documented in the system prompt\n * but not yet being executed.\n */\nexport function snippetsToSimpleBindings(\n snippets: Array<Snippet>,\n): Record<string, ToolBinding> {\n const bindings: Record<string, ToolBinding> = {}\n\n for (const snippet of snippets) {\n const bindingName = `snippet_${snippet.name}`\n\n bindings[bindingName] = {\n name: bindingName,\n description: snippet.description,\n inputSchema: snippet.inputSchema,\n outputSchema: snippet.outputSchema,\n execute: () =>\n Promise.reject(\n new Error(\n `Snippet ${snippet.name} is not available for execution in this context`,\n ),\n ),\n }\n }\n\n return bindings\n}\n"],"mappings":";;;;;AA+BA,SAAgB,mBAAmB,EACjC,UACA,SACA,kBACA,WACyD;CACzD,MAAM,WAAwC,CAAC;CAE/C,KAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,cAAc,WAAW,QAAQ;EAEvC,SAAS,eAAe;GACtB,MAAM;GACN,aAAa,QAAQ;GACrB,aAAa,QAAQ;GACrB,cAAc,QAAQ;GACtB,SAAS,OAAO,UAAmB;IACjC,MAAM,YAAY,KAAK,IAAI;IAG3B,SAAS,gBAAgB,0BAA0B;KACjD,SAAS,QAAQ;KACjB;KACA,WAAW;IACb,CAAC;IAED,IAAI;KAOF,MAAM,SAAS,MAAM,iBAAiB;4BAJpB,KAAK,UAAU,KAAK,EAAE;cACpC,QAAQ,KAAK;aAGkC,KAAK;KACxD,MAAM,WAAW,KAAK,IAAI,IAAI;KAG9B,SAAS,gBAAgB,4BAA4B;MACnD,SAAS,QAAQ;MACjB;MACA;MACA,WAAW,KAAK,IAAI;KACtB,CAAC;KAGD,QAAQ,YAAY,QAAQ,MAAM,IAAI,CAAC,CAAC,YAAY,CAEpD,CAAC;KAED,OAAO;IACT,SAAS,OAAO;KACd,MAAM,WAAW,KAAK,IAAI,IAAI;KAG9B,SAAS,gBAAgB,2BAA2B;MAClD,SAAS,QAAQ;MACjB,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;MAC5D;MACA,WAAW,KAAK,IAAI;KACtB,CAAC;KAGD,QAAQ,YAAY,QAAQ,MAAM,KAAK,CAAC,CAAC,YAAY,CAErD,CAAC;KAED,MAAM;IACR;GACF;EACF;CACF;CAEA,OAAO;AACT;;;;;;AAOA,SAAgB,yBACd,UAC6B;CAC7B,MAAM,WAAwC,CAAC;CAE/C,KAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,cAAc,WAAW,QAAQ;EAEvC,SAAS,eAAe;GACtB,MAAM;GACN,aAAa,QAAQ;GACrB,aAAa,QAAQ;GACrB,cAAc,QAAQ;GACtB,eACE,QAAQ,uBACN,IAAI,MACF,WAAW,QAAQ,KAAK,gDAC1B,CACF;EACJ;CACF;CAEA,OAAO;AACT"}
@@ -0,0 +1,74 @@
1
+ import { SchemaInput, ServerTool } from '@tanstack/ai';
2
+ import { CodeModeTool, IsolateDriver, ToolBinding } from '@tanstack/ai-code-mode';
3
+ import { Snippet, SnippetStorage } from './types.js';
4
+ /**
5
+ * Options for converting a single snippet to a tool
6
+ */
7
+ export interface SnippetToToolOptions {
8
+ /**
9
+ * The snippet to convert
10
+ */
11
+ snippet: Snippet;
12
+ /**
13
+ * Isolate driver for executing snippet code
14
+ */
15
+ driver: IsolateDriver;
16
+ /**
17
+ * Pre-computed bindings for external_* functions
18
+ */
19
+ bindings: Record<string, ToolBinding>;
20
+ /**
21
+ * Storage for updating execution stats
22
+ */
23
+ storage: SnippetStorage;
24
+ /**
25
+ * Timeout for snippet execution in ms
26
+ * @default 30000
27
+ */
28
+ timeout?: number;
29
+ /**
30
+ * Memory limit in bytes
31
+ * @default 128
32
+ */
33
+ memoryLimit?: number;
34
+ }
35
+ interface SnippetsToToolsOptions {
36
+ /**
37
+ * Snippets to convert to tools
38
+ */
39
+ snippets: Array<Snippet>;
40
+ /**
41
+ * Isolate driver for executing snippet code
42
+ */
43
+ driver: IsolateDriver;
44
+ /**
45
+ * Original tools that become external_* bindings
46
+ * (so snippets can call external_* functions)
47
+ */
48
+ tools: Array<CodeModeTool>;
49
+ /**
50
+ * Storage for updating execution stats
51
+ */
52
+ storage: SnippetStorage;
53
+ /**
54
+ * Timeout for snippet execution in ms
55
+ * @default 30000
56
+ */
57
+ timeout?: number;
58
+ /**
59
+ * Memory limit in bytes
60
+ * @default 128
61
+ */
62
+ memoryLimit?: number;
63
+ }
64
+ /**
65
+ * Convert a single snippet to a ServerTool that the LLM can call directly.
66
+ * The snippet executes its code in the sandbox with access to external_* bindings.
67
+ */
68
+ export declare function snippetToTool({ snippet, driver, bindings, storage, timeout, memoryLimit, }: SnippetToToolOptions): ServerTool<SchemaInput, SchemaInput, string>;
69
+ /**
70
+ * Convert multiple snippets to ServerTools that the LLM can call directly.
71
+ * Snippets become real tools that execute their code in the sandbox.
72
+ */
73
+ export declare function snippetsToTools({ snippets, driver, tools, storage, timeout, memoryLimit, }: SnippetsToToolsOptions): Array<ServerTool<SchemaInput, SchemaInput, string>>;
74
+ export {};
@@ -0,0 +1,147 @@
1
+ import { createEventAwareBindings, stripTypeScript, toolsToBindings } from "@tanstack/ai-code-mode";
2
+ import { toolDefinition } from "@tanstack/ai";
3
+ import { z } from "zod";
4
+ //#region src/snippets-to-tools.ts
5
+ /**
6
+ * Convert JSON Schema to Zod schema.
7
+ * This is a simplified converter that handles common cases.
8
+ */
9
+ function jsonSchemaToZod(schema) {
10
+ const type = schema.type;
11
+ if (type === "string") {
12
+ let zodString = z.string();
13
+ if (schema.description) zodString = zodString.describe(schema.description);
14
+ return zodString;
15
+ }
16
+ if (type === "number" || type === "integer") {
17
+ let zodNum = z.number();
18
+ if (schema.description) zodNum = zodNum.describe(schema.description);
19
+ return zodNum;
20
+ }
21
+ if (type === "boolean") {
22
+ let zodBool = z.boolean();
23
+ if (schema.description) zodBool = zodBool.describe(schema.description);
24
+ return zodBool;
25
+ }
26
+ if (type === "array") {
27
+ const items = schema.items;
28
+ if (items) return z.array(jsonSchemaToZod(items));
29
+ return z.array(z.unknown());
30
+ }
31
+ if (type === "object") {
32
+ const properties = schema.properties;
33
+ const required = schema.required ?? [];
34
+ if (properties) {
35
+ const shape = {};
36
+ for (const [key, propSchema] of Object.entries(properties)) {
37
+ let zodProp = jsonSchemaToZod(propSchema);
38
+ if (!required.includes(key)) zodProp = zodProp.optional();
39
+ shape[key] = zodProp;
40
+ }
41
+ return z.object(shape);
42
+ }
43
+ return z.record(z.string(), z.unknown());
44
+ }
45
+ return z.unknown();
46
+ }
47
+ /**
48
+ * Convert a single snippet to a ServerTool that the LLM can call directly.
49
+ * The snippet executes its code in the sandbox with access to external_* bindings.
50
+ */
51
+ function snippetToTool({ snippet, driver, bindings, storage, timeout = 3e4, memoryLimit = 128 }) {
52
+ const inputSchema = jsonSchemaToZod(snippet.inputSchema);
53
+ const outputSchema = jsonSchemaToZod(snippet.outputSchema);
54
+ return toolDefinition({
55
+ name: snippet.name,
56
+ description: `[SNIPPET] ${snippet.description}`,
57
+ inputSchema,
58
+ outputSchema
59
+ }).server(async (input, context) => {
60
+ const startTime = Date.now();
61
+ const emitCustomEvent = context?.emitCustomEvent || (() => {});
62
+ emitCustomEvent("code_mode:snippet_call", {
63
+ snippet: snippet.name,
64
+ input,
65
+ timestamp: startTime
66
+ });
67
+ let isolateContext = null;
68
+ try {
69
+ console.log(`[Snippet:${snippet.name}] Starting execution with input:`, JSON.stringify(input).substring(0, 200));
70
+ const wrappedCode = `
71
+ const input = ${JSON.stringify(input)};
72
+ ${snippet.code}
73
+ `;
74
+ console.log(`[Snippet:${snippet.name}] Wrapped code (first 500 chars):`, wrappedCode.substring(0, 500));
75
+ const strippedCode = await stripTypeScript(wrappedCode);
76
+ console.log(`[Snippet:${snippet.name}] Stripped code (first 500 chars):`, strippedCode.substring(0, 500));
77
+ const eventAwareBindings = createEventAwareBindings(bindings, emitCustomEvent);
78
+ console.log(`[Snippet:${snippet.name}] Event-aware bindings:`, Object.keys(eventAwareBindings));
79
+ console.log(`[Snippet:${snippet.name}] Creating sandbox context...`);
80
+ isolateContext = await driver.createContext({
81
+ bindings: eventAwareBindings,
82
+ timeout,
83
+ memoryLimit
84
+ });
85
+ console.log(`[Snippet:${snippet.name}] Sandbox context created`);
86
+ console.log(`[Snippet:${snippet.name}] Executing code...`);
87
+ const executionResult = await isolateContext.execute(strippedCode);
88
+ console.log(`[Snippet:${snippet.name}] Execution result:`, {
89
+ success: executionResult.success,
90
+ hasValue: "value" in executionResult,
91
+ error: executionResult.error,
92
+ logs: executionResult.logs
93
+ });
94
+ const duration = Date.now() - startTime;
95
+ if (!executionResult.success) {
96
+ console.error(`[Snippet:${snippet.name}] Execution failed:`, executionResult.error);
97
+ throw new Error(executionResult.error?.message || "Snippet execution failed");
98
+ }
99
+ emitCustomEvent("code_mode:snippet_result", {
100
+ snippet: snippet.name,
101
+ result: executionResult.value,
102
+ duration,
103
+ timestamp: Date.now()
104
+ });
105
+ storage.updateStats(snippet.name, true).catch(() => {});
106
+ return executionResult.value;
107
+ } catch (error) {
108
+ const duration = Date.now() - startTime;
109
+ console.error(`[Snippet:${snippet.name}] CAUGHT ERROR:`, {
110
+ message: error instanceof Error ? error.message : String(error),
111
+ stack: error instanceof Error ? error.stack : void 0,
112
+ duration
113
+ });
114
+ emitCustomEvent("code_mode:snippet_error", {
115
+ snippet: snippet.name,
116
+ error: error instanceof Error ? error.message : String(error),
117
+ duration,
118
+ timestamp: Date.now()
119
+ });
120
+ storage.updateStats(snippet.name, false).catch(() => {});
121
+ throw error;
122
+ } finally {
123
+ if (isolateContext) await isolateContext.dispose();
124
+ }
125
+ });
126
+ }
127
+ /**
128
+ * Convert multiple snippets to ServerTools that the LLM can call directly.
129
+ * Snippets become real tools that execute their code in the sandbox.
130
+ */
131
+ function snippetsToTools({ snippets, driver, tools, storage, timeout = 3e4, memoryLimit = 128 }) {
132
+ console.log("[SnippetsToTools] Creating bindings from tools:", tools.map((t) => t.name));
133
+ const bindings = toolsToBindings(tools, "external_");
134
+ console.log("[SnippetsToTools] Created bindings:", Object.keys(bindings));
135
+ return snippets.map((snippet) => snippetToTool({
136
+ snippet,
137
+ driver,
138
+ bindings,
139
+ storage,
140
+ timeout,
141
+ memoryLimit
142
+ }));
143
+ }
144
+ //#endregion
145
+ export { snippetToTool, snippetsToTools };
146
+
147
+ //# sourceMappingURL=snippets-to-tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snippets-to-tools.js","names":[],"sources":["../../src/snippets-to-tools.ts"],"sourcesContent":["import { toolDefinition } from '@tanstack/ai'\nimport {\n createEventAwareBindings,\n stripTypeScript,\n toolsToBindings,\n} from '@tanstack/ai-code-mode'\nimport { z } from 'zod'\nimport type {\n SchemaInput,\n ServerTool,\n ToolExecutionContext,\n} from '@tanstack/ai'\nimport type {\n CodeModeTool,\n IsolateDriver,\n ToolBinding,\n} from '@tanstack/ai-code-mode'\nimport type { Snippet, SnippetStorage } from './types'\n\n/**\n * Options for converting a single snippet to a tool\n */\nexport interface SnippetToToolOptions {\n /**\n * The snippet to convert\n */\n snippet: Snippet\n\n /**\n * Isolate driver for executing snippet code\n */\n driver: IsolateDriver\n\n /**\n * Pre-computed bindings for external_* functions\n */\n bindings: Record<string, ToolBinding>\n\n /**\n * Storage for updating execution stats\n */\n storage: SnippetStorage\n\n /**\n * Timeout for snippet execution in ms\n * @default 30000\n */\n timeout?: number\n\n /**\n * Memory limit in bytes\n * @default 128\n */\n memoryLimit?: number\n}\n\ninterface SnippetsToToolsOptions {\n /**\n * Snippets to convert to tools\n */\n snippets: Array<Snippet>\n\n /**\n * Isolate driver for executing snippet code\n */\n driver: IsolateDriver\n\n /**\n * Original tools that become external_* bindings\n * (so snippets can call external_* functions)\n */\n tools: Array<CodeModeTool>\n\n /**\n * Storage for updating execution stats\n */\n storage: SnippetStorage\n\n /**\n * Timeout for snippet execution in ms\n * @default 30000\n */\n timeout?: number\n\n /**\n * Memory limit in bytes\n * @default 128\n */\n memoryLimit?: number\n}\n\n/**\n * Convert JSON Schema to Zod schema.\n * This is a simplified converter that handles common cases.\n */\nfunction jsonSchemaToZod(schema: Record<string, unknown>): z.ZodType {\n const type = schema.type as string\n\n if (type === 'string') {\n let zodString = z.string()\n if (schema.description) {\n zodString = zodString.describe(schema.description as string)\n }\n return zodString\n }\n if (type === 'number' || type === 'integer') {\n let zodNum = z.number()\n if (schema.description) {\n zodNum = zodNum.describe(schema.description as string)\n }\n return zodNum\n }\n if (type === 'boolean') {\n let zodBool = z.boolean()\n if (schema.description) {\n zodBool = zodBool.describe(schema.description as string)\n }\n return zodBool\n }\n if (type === 'array') {\n const items = schema.items as Record<string, unknown> | undefined\n if (items) {\n return z.array(jsonSchemaToZod(items))\n }\n return z.array(z.unknown())\n }\n if (type === 'object') {\n const properties = schema.properties as\n | Record<string, Record<string, unknown>>\n | undefined\n const required = (schema.required as Array<string> | undefined) ?? []\n\n if (properties) {\n const shape: Record<string, z.ZodType> = {}\n for (const [key, propSchema] of Object.entries(properties)) {\n let zodProp = jsonSchemaToZod(propSchema)\n if (!required.includes(key)) {\n zodProp = zodProp.optional()\n }\n shape[key] = zodProp\n }\n return z.object(shape)\n }\n return z.record(z.string(), z.unknown())\n }\n\n // Fallback\n return z.unknown()\n}\n\n/**\n * Convert a single snippet to a ServerTool that the LLM can call directly.\n * The snippet executes its code in the sandbox with access to external_* bindings.\n */\nexport function snippetToTool({\n snippet,\n driver,\n bindings,\n storage,\n timeout = 30000,\n memoryLimit = 128,\n}: SnippetToToolOptions): ServerTool<SchemaInput, SchemaInput, string> {\n // Generate input and output schemas from JSON Schema\n const inputSchema = jsonSchemaToZod(snippet.inputSchema)\n const outputSchema = jsonSchemaToZod(snippet.outputSchema)\n\n return toolDefinition({\n name: snippet.name,\n description: `[SNIPPET] ${snippet.description}`,\n inputSchema,\n outputSchema,\n }).server(async (input: unknown, context?: ToolExecutionContext) => {\n const startTime = Date.now()\n const emitCustomEvent = context?.emitCustomEvent || (() => {})\n\n // Emit snippet call event\n emitCustomEvent('code_mode:snippet_call', {\n snippet: snippet.name,\n input,\n timestamp: startTime,\n })\n\n let isolateContext = null\n\n try {\n console.log(\n `[Snippet:${snippet.name}] Starting execution with input:`,\n JSON.stringify(input).substring(0, 200),\n )\n\n // Wrap the snippet code to receive input as a variable\n const wrappedCode = `\n const input = ${JSON.stringify(input)};\n ${snippet.code}\n `\n console.log(\n `[Snippet:${snippet.name}] Wrapped code (first 500 chars):`,\n wrappedCode.substring(0, 500),\n )\n\n // Strip TypeScript to JavaScript\n const strippedCode = await stripTypeScript(wrappedCode)\n console.log(\n `[Snippet:${snippet.name}] Stripped code (first 500 chars):`,\n strippedCode.substring(0, 500),\n )\n\n // Create event-aware bindings\n const eventAwareBindings = createEventAwareBindings(\n bindings,\n emitCustomEvent,\n )\n console.log(\n `[Snippet:${snippet.name}] Event-aware bindings:`,\n Object.keys(eventAwareBindings),\n )\n\n // Create sandbox context\n console.log(`[Snippet:${snippet.name}] Creating sandbox context...`)\n isolateContext = await driver.createContext({\n bindings: eventAwareBindings,\n timeout,\n memoryLimit,\n })\n console.log(`[Snippet:${snippet.name}] Sandbox context created`)\n\n // Execute the code\n console.log(`[Snippet:${snippet.name}] Executing code...`)\n const executionResult = await isolateContext.execute(strippedCode)\n console.log(`[Snippet:${snippet.name}] Execution result:`, {\n success: executionResult.success,\n hasValue: 'value' in executionResult,\n error: executionResult.error,\n logs: executionResult.logs,\n })\n\n const duration = Date.now() - startTime\n\n if (!executionResult.success) {\n console.error(\n `[Snippet:${snippet.name}] Execution failed:`,\n executionResult.error,\n )\n throw new Error(\n executionResult.error?.message || 'Snippet execution failed',\n )\n }\n\n // Emit success event\n emitCustomEvent('code_mode:snippet_result', {\n snippet: snippet.name,\n result: executionResult.value,\n duration,\n timestamp: Date.now(),\n })\n\n // Update stats (async, don't await to not block)\n storage.updateStats(snippet.name, true).catch(() => {\n // Silently ignore stats update failures\n })\n\n return executionResult.value\n } catch (error) {\n const duration = Date.now() - startTime\n console.error(`[Snippet:${snippet.name}] CAUGHT ERROR:`, {\n message: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n duration,\n })\n\n // Emit error event\n emitCustomEvent('code_mode:snippet_error', {\n snippet: snippet.name,\n error: error instanceof Error ? error.message : String(error),\n duration,\n timestamp: Date.now(),\n })\n\n // Update stats (async, don't await)\n storage.updateStats(snippet.name, false).catch(() => {\n // Silently ignore stats update failures\n })\n\n throw error\n } finally {\n if (isolateContext) {\n await isolateContext.dispose()\n }\n }\n })\n}\n\n/**\n * Convert multiple snippets to ServerTools that the LLM can call directly.\n * Snippets become real tools that execute their code in the sandbox.\n */\nexport function snippetsToTools({\n snippets,\n driver,\n tools,\n storage,\n timeout = 30000,\n memoryLimit = 128,\n}: SnippetsToToolsOptions): Array<\n ServerTool<SchemaInput, SchemaInput, string>\n> {\n // Pre-compute bindings from tools (these are shared across all snippet executions)\n console.log(\n '[SnippetsToTools] Creating bindings from tools:',\n tools.map((t) => t.name),\n )\n const bindings = toolsToBindings(tools, 'external_')\n console.log('[SnippetsToTools] Created bindings:', Object.keys(bindings))\n\n return snippets.map((snippet) =>\n snippetToTool({\n snippet,\n driver,\n bindings,\n storage,\n timeout,\n memoryLimit,\n }),\n )\n}\n"],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import { SnippetStorage } from '../types.js';
2
+ import { TrustStrategy } from '../trust-strategies.js';
3
+ export interface FileSnippetStorageOptions {
4
+ /**
5
+ * Directory path for storing snippets
6
+ */
7
+ directory: string;
8
+ /**
9
+ * Trust strategy for determining snippet trust levels
10
+ * @default createDefaultTrustStrategy()
11
+ */
12
+ trustStrategy?: TrustStrategy;
13
+ }
14
+ /**
15
+ * File-system based snippet storage
16
+ *
17
+ * Directory structure:
18
+ * .snippets/
19
+ * _index.json # Fast catalog loading
20
+ * fetch_github_stats/
21
+ * meta.json # Metadata (description, schemas, hints, stats)
22
+ * code.ts # The actual TypeScript code
23
+ * deploy_to_prod/
24
+ * meta.json
25
+ * code.ts
26
+ */
27
+ export declare function createFileSnippetStorage(directoryOrOptions: string | FileSnippetStorageOptions): SnippetStorage;
@@ -0,0 +1,155 @@
1
+ import { createDefaultTrustStrategy } from "../trust-strategies.js";
2
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { existsSync } from "node:fs";
5
+ //#region src/storage/file-storage.ts
6
+ /**
7
+ * File-system based snippet storage
8
+ *
9
+ * Directory structure:
10
+ * .snippets/
11
+ * _index.json # Fast catalog loading
12
+ * fetch_github_stats/
13
+ * meta.json # Metadata (description, schemas, hints, stats)
14
+ * code.ts # The actual TypeScript code
15
+ * deploy_to_prod/
16
+ * meta.json
17
+ * code.ts
18
+ */
19
+ function createFileSnippetStorage(directoryOrOptions) {
20
+ const { directory, trustStrategy = createDefaultTrustStrategy() } = typeof directoryOrOptions === "string" ? { directory: directoryOrOptions } : directoryOrOptions;
21
+ const indexPath = join(directory, "_index.json");
22
+ console.log("[FileSnippetStorage] Initialized with directory:", directory);
23
+ const SAFE_SNIPPET_NAME = /^[A-Za-z0-9_-]+$/;
24
+ function isSafeSnippetName(name) {
25
+ return typeof name === "string" && SAFE_SNIPPET_NAME.test(name);
26
+ }
27
+ function assertSafeSnippetName(name) {
28
+ if (!isSafeSnippetName(name)) throw new Error(`Invalid snippet name ${JSON.stringify(name)}: names must match /^[A-Za-z0-9_-]+$/ (no path separators or traversal).`);
29
+ }
30
+ async function ensureDirectory() {
31
+ if (!existsSync(directory)) {
32
+ console.log("[FileSnippetStorage] Creating directory:", directory);
33
+ await mkdir(directory, { recursive: true });
34
+ }
35
+ }
36
+ async function loadIndex() {
37
+ await ensureDirectory();
38
+ if (!existsSync(indexPath)) return [];
39
+ const content = await readFile(indexPath, "utf-8");
40
+ return JSON.parse(content);
41
+ }
42
+ async function loadAll() {
43
+ const index = await loadIndex();
44
+ const snippets = [];
45
+ for (const entry of index) {
46
+ const snippet = await get(entry.name);
47
+ if (snippet) snippets.push(snippet);
48
+ }
49
+ return snippets;
50
+ }
51
+ async function saveIndex(index) {
52
+ await writeFile(indexPath, JSON.stringify(index, null, 2));
53
+ }
54
+ async function get(name) {
55
+ if (!isSafeSnippetName(name)) return null;
56
+ const snippetDir = join(directory, name);
57
+ const metaPath = join(snippetDir, "meta.json");
58
+ const codePath = join(snippetDir, "code.ts");
59
+ if (!existsSync(metaPath)) return null;
60
+ const [metaContent, code] = await Promise.all([readFile(metaPath, "utf-8"), readFile(codePath, "utf-8")]);
61
+ return {
62
+ ...JSON.parse(metaContent),
63
+ code
64
+ };
65
+ }
66
+ async function save(snippet) {
67
+ assertSafeSnippetName(snippet.name);
68
+ await ensureDirectory();
69
+ const snippetDir = join(directory, snippet.name);
70
+ const metaPath = join(snippetDir, "meta.json");
71
+ const codePath = join(snippetDir, "code.ts");
72
+ const now = (/* @__PURE__ */ new Date()).toISOString();
73
+ const existing = await get(snippet.name);
74
+ const fullSnippet = {
75
+ ...snippet,
76
+ createdAt: existing?.createdAt ?? now,
77
+ updatedAt: now
78
+ };
79
+ const { code, ...meta } = fullSnippet;
80
+ await mkdir(snippetDir, { recursive: true });
81
+ await Promise.all([writeFile(metaPath, JSON.stringify(meta, null, 2)), writeFile(codePath, code)]);
82
+ const index = await loadIndex();
83
+ const indexEntry = {
84
+ id: fullSnippet.id,
85
+ name: fullSnippet.name,
86
+ description: fullSnippet.description,
87
+ usageHints: fullSnippet.usageHints,
88
+ trustLevel: fullSnippet.trustLevel
89
+ };
90
+ const existingIdx = index.findIndex((s) => s.name === snippet.name);
91
+ if (existingIdx >= 0) index[existingIdx] = indexEntry;
92
+ else index.push(indexEntry);
93
+ await saveIndex(index);
94
+ return fullSnippet;
95
+ }
96
+ async function deleteSnippet(name) {
97
+ assertSafeSnippetName(name);
98
+ const snippetDir = join(directory, name);
99
+ if (!existsSync(snippetDir)) return false;
100
+ await rm(snippetDir, { recursive: true });
101
+ await saveIndex((await loadIndex()).filter((s) => s.name !== name));
102
+ return true;
103
+ }
104
+ async function search(query, searchOptions = {}) {
105
+ const { limit = 5 } = searchOptions;
106
+ const index = await loadIndex();
107
+ const terms = query.toLowerCase().split(/\s+/);
108
+ return index.map((snippet) => {
109
+ let score = 0;
110
+ const searchText = [
111
+ snippet.name,
112
+ snippet.description,
113
+ ...snippet.usageHints
114
+ ].join(" ").toLowerCase();
115
+ for (const term of terms) {
116
+ if (searchText.includes(term)) score += 1;
117
+ if (snippet.name.toLowerCase().includes(term)) score += 2;
118
+ }
119
+ return {
120
+ snippet,
121
+ score
122
+ };
123
+ }).filter((s) => s.score > 0).sort((a, b) => b.score - a.score).slice(0, limit).map((s) => s.snippet);
124
+ }
125
+ async function updateStats(name, success) {
126
+ const snippet = await get(name);
127
+ if (!snippet) return;
128
+ const { executions, successRate } = snippet.stats;
129
+ const newExecutions = executions + 1;
130
+ const newStats = {
131
+ executions: newExecutions,
132
+ successRate: (successRate * executions + (success ? 1 : 0)) / newExecutions
133
+ };
134
+ const newTrustLevel = trustStrategy.calculateTrustLevel(snippet.trustLevel, newStats);
135
+ await save({
136
+ ...snippet,
137
+ stats: newStats,
138
+ trustLevel: newTrustLevel
139
+ });
140
+ }
141
+ return {
142
+ loadIndex,
143
+ loadAll,
144
+ get,
145
+ save,
146
+ delete: deleteSnippet,
147
+ search,
148
+ updateStats,
149
+ trustStrategy
150
+ };
151
+ }
152
+ //#endregion
153
+ export { createFileSnippetStorage };
154
+
155
+ //# sourceMappingURL=file-storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-storage.js","names":[],"sources":["../../../src/storage/file-storage.ts"],"sourcesContent":["import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { existsSync } from 'node:fs'\nimport { createDefaultTrustStrategy } from '../trust-strategies'\nimport type {\n Snippet,\n SnippetIndexEntry,\n SnippetSearchOptions,\n SnippetStorage,\n} from '../types'\nimport type { TrustStrategy } from '../trust-strategies'\n\nexport interface FileSnippetStorageOptions {\n /**\n * Directory path for storing snippets\n */\n directory: string\n\n /**\n * Trust strategy for determining snippet trust levels\n * @default createDefaultTrustStrategy()\n */\n trustStrategy?: TrustStrategy\n}\n\n/**\n * File-system based snippet storage\n *\n * Directory structure:\n * .snippets/\n * _index.json # Fast catalog loading\n * fetch_github_stats/\n * meta.json # Metadata (description, schemas, hints, stats)\n * code.ts # The actual TypeScript code\n * deploy_to_prod/\n * meta.json\n * code.ts\n */\nexport function createFileSnippetStorage(\n directoryOrOptions: string | FileSnippetStorageOptions,\n): SnippetStorage {\n const options =\n typeof directoryOrOptions === 'string'\n ? { directory: directoryOrOptions }\n : directoryOrOptions\n\n const { directory, trustStrategy = createDefaultTrustStrategy() } = options\n const indexPath = join(directory, '_index.json')\n\n console.log('[FileSnippetStorage] Initialized with directory:', directory)\n\n // Snippet names are used both as on-disk directory segments and as\n // `snippet_<name>` sandbox tool names, so they must be a single safe\n // identifier segment. Rejecting anything else keeps an LLM-supplied name\n // (e.g. `../../etc`) from escaping `directory` during read/write/delete.\n const SAFE_SNIPPET_NAME = /^[A-Za-z0-9_-]+$/\n\n function isSafeSnippetName(name: string): boolean {\n return typeof name === 'string' && SAFE_SNIPPET_NAME.test(name)\n }\n\n function assertSafeSnippetName(name: string): void {\n if (!isSafeSnippetName(name)) {\n throw new Error(\n `Invalid snippet name ${JSON.stringify(\n name,\n )}: names must match /^[A-Za-z0-9_-]+$/ (no path separators or traversal).`,\n )\n }\n }\n\n async function ensureDirectory(): Promise<void> {\n if (!existsSync(directory)) {\n console.log('[FileSnippetStorage] Creating directory:', directory)\n await mkdir(directory, { recursive: true })\n }\n }\n\n async function loadIndex(): Promise<Array<SnippetIndexEntry>> {\n await ensureDirectory()\n\n if (!existsSync(indexPath)) {\n return []\n }\n\n const content = await readFile(indexPath, 'utf-8')\n return JSON.parse(content) as Array<SnippetIndexEntry>\n }\n\n async function loadAll(): Promise<Array<Snippet>> {\n const index = await loadIndex()\n const snippets: Array<Snippet> = []\n\n for (const entry of index) {\n const snippet = await get(entry.name)\n if (snippet) {\n snippets.push(snippet)\n }\n }\n\n return snippets\n }\n\n async function saveIndex(index: Array<SnippetIndexEntry>): Promise<void> {\n await writeFile(indexPath, JSON.stringify(index, null, 2))\n }\n\n async function get(name: string): Promise<Snippet | null> {\n // Treat an unsafe name as \"not found\" rather than reading outside `directory`.\n if (!isSafeSnippetName(name)) {\n return null\n }\n\n const snippetDir = join(directory, name)\n const metaPath = join(snippetDir, 'meta.json')\n const codePath = join(snippetDir, 'code.ts')\n\n if (!existsSync(metaPath)) {\n return null\n }\n\n const [metaContent, code] = await Promise.all([\n readFile(metaPath, 'utf-8'),\n readFile(codePath, 'utf-8'),\n ])\n\n const meta = JSON.parse(metaContent) as Omit<Snippet, 'code'>\n return { ...meta, code }\n }\n\n async function save(\n snippet: Omit<Snippet, 'createdAt' | 'updatedAt'>,\n ): Promise<Snippet> {\n assertSafeSnippetName(snippet.name)\n await ensureDirectory()\n\n const snippetDir = join(directory, snippet.name)\n const metaPath = join(snippetDir, 'meta.json')\n const codePath = join(snippetDir, 'code.ts')\n\n const now = new Date().toISOString()\n const existing = await get(snippet.name)\n\n const fullSnippet: Snippet = {\n ...snippet,\n createdAt: existing?.createdAt ?? now,\n updatedAt: now,\n }\n\n // Separate code from metadata\n const { code, ...meta } = fullSnippet\n\n // Write snippet files\n await mkdir(snippetDir, { recursive: true })\n await Promise.all([\n writeFile(metaPath, JSON.stringify(meta, null, 2)),\n writeFile(codePath, code),\n ])\n\n // Update index\n const index = await loadIndex()\n const indexEntry: SnippetIndexEntry = {\n id: fullSnippet.id,\n name: fullSnippet.name,\n description: fullSnippet.description,\n usageHints: fullSnippet.usageHints,\n trustLevel: fullSnippet.trustLevel,\n }\n\n const existingIdx = index.findIndex((s) => s.name === snippet.name)\n if (existingIdx >= 0) {\n index[existingIdx] = indexEntry\n } else {\n index.push(indexEntry)\n }\n await saveIndex(index)\n\n return fullSnippet\n }\n\n async function deleteSnippet(name: string): Promise<boolean> {\n assertSafeSnippetName(name)\n\n const snippetDir = join(directory, name)\n\n if (!existsSync(snippetDir)) {\n return false\n }\n\n await rm(snippetDir, { recursive: true })\n\n // Update index\n const index = await loadIndex()\n const filtered = index.filter((s) => s.name !== name)\n await saveIndex(filtered)\n\n return true\n }\n\n async function search(\n query: string,\n searchOptions: SnippetSearchOptions = {},\n ): Promise<Array<SnippetIndexEntry>> {\n const { limit = 5 } = searchOptions\n const index = await loadIndex()\n\n // Simple text matching - can be replaced with embeddings\n const queryLower = query.toLowerCase()\n const terms = queryLower.split(/\\s+/)\n\n const scored = index.map((snippet) => {\n let score = 0\n const searchText = [\n snippet.name,\n snippet.description,\n ...snippet.usageHints,\n ]\n .join(' ')\n .toLowerCase()\n\n for (const term of terms) {\n if (searchText.includes(term)) {\n score += 1\n }\n // Boost exact name matches\n if (snippet.name.toLowerCase().includes(term)) {\n score += 2\n }\n }\n\n return { snippet, score }\n })\n\n return scored\n .filter((s) => s.score > 0)\n .sort((a, b) => b.score - a.score)\n .slice(0, limit)\n .map((s) => s.snippet)\n }\n\n async function updateStats(name: string, success: boolean): Promise<void> {\n const snippet = await get(name)\n if (!snippet) return\n\n const { executions, successRate } = snippet.stats\n const newExecutions = executions + 1\n const newSuccessRate =\n (successRate * executions + (success ? 1 : 0)) / newExecutions\n\n const newStats = { executions: newExecutions, successRate: newSuccessRate }\n\n // Use trust strategy to calculate new trust level\n const newTrustLevel = trustStrategy.calculateTrustLevel(\n snippet.trustLevel,\n newStats,\n )\n\n await save({\n ...snippet,\n stats: newStats,\n trustLevel: newTrustLevel,\n })\n }\n\n return {\n loadIndex,\n loadAll,\n get,\n save,\n delete: deleteSnippet,\n search,\n updateStats,\n trustStrategy,\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAsCA,SAAgB,yBACd,oBACgB;CAMhB,MAAM,EAAE,WAAW,gBAAgB,2BAA2B,MAJ5D,OAAO,uBAAuB,WAC1B,EAAE,WAAW,mBAAmB,IAChC;CAGN,MAAM,YAAY,KAAK,WAAW,aAAa;CAE/C,QAAQ,IAAI,oDAAoD,SAAS;CAMzE,MAAM,oBAAoB;CAE1B,SAAS,kBAAkB,MAAuB;EAChD,OAAO,OAAO,SAAS,YAAY,kBAAkB,KAAK,IAAI;CAChE;CAEA,SAAS,sBAAsB,MAAoB;EACjD,IAAI,CAAC,kBAAkB,IAAI,GACzB,MAAM,IAAI,MACR,wBAAwB,KAAK,UAC3B,IACF,EAAE,yEACJ;CAEJ;CAEA,eAAe,kBAAiC;EAC9C,IAAI,CAAC,WAAW,SAAS,GAAG;GAC1B,QAAQ,IAAI,4CAA4C,SAAS;GACjE,MAAM,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;EAC5C;CACF;CAEA,eAAe,YAA+C;EAC5D,MAAM,gBAAgB;EAEtB,IAAI,CAAC,WAAW,SAAS,GACvB,OAAO,CAAC;EAGV,MAAM,UAAU,MAAM,SAAS,WAAW,OAAO;EACjD,OAAO,KAAK,MAAM,OAAO;CAC3B;CAEA,eAAe,UAAmC;EAChD,MAAM,QAAQ,MAAM,UAAU;EAC9B,MAAM,WAA2B,CAAC;EAElC,KAAK,MAAM,SAAS,OAAO;GACzB,MAAM,UAAU,MAAM,IAAI,MAAM,IAAI;GACpC,IAAI,SACF,SAAS,KAAK,OAAO;EAEzB;EAEA,OAAO;CACT;CAEA,eAAe,UAAU,OAAgD;EACvE,MAAM,UAAU,WAAW,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;CAC3D;CAEA,eAAe,IAAI,MAAuC;EAExD,IAAI,CAAC,kBAAkB,IAAI,GACzB,OAAO;EAGT,MAAM,aAAa,KAAK,WAAW,IAAI;EACvC,MAAM,WAAW,KAAK,YAAY,WAAW;EAC7C,MAAM,WAAW,KAAK,YAAY,SAAS;EAE3C,IAAI,CAAC,WAAW,QAAQ,GACtB,OAAO;EAGT,MAAM,CAAC,aAAa,QAAQ,MAAM,QAAQ,IAAI,CAC5C,SAAS,UAAU,OAAO,GAC1B,SAAS,UAAU,OAAO,CAC5B,CAAC;EAGD,OAAO;GAAE,GADI,KAAK,MAAM,WACZ;GAAM;EAAK;CACzB;CAEA,eAAe,KACb,SACkB;EAClB,sBAAsB,QAAQ,IAAI;EAClC,MAAM,gBAAgB;EAEtB,MAAM,aAAa,KAAK,WAAW,QAAQ,IAAI;EAC/C,MAAM,WAAW,KAAK,YAAY,WAAW;EAC7C,MAAM,WAAW,KAAK,YAAY,SAAS;EAE3C,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EACnC,MAAM,WAAW,MAAM,IAAI,QAAQ,IAAI;EAEvC,MAAM,cAAuB;GAC3B,GAAG;GACH,WAAW,UAAU,aAAa;GAClC,WAAW;EACb;EAGA,MAAM,EAAE,MAAM,GAAG,SAAS;EAG1B,MAAM,MAAM,YAAY,EAAE,WAAW,KAAK,CAAC;EAC3C,MAAM,QAAQ,IAAI,CAChB,UAAU,UAAU,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC,GACjD,UAAU,UAAU,IAAI,CAC1B,CAAC;EAGD,MAAM,QAAQ,MAAM,UAAU;EAC9B,MAAM,aAAgC;GACpC,IAAI,YAAY;GAChB,MAAM,YAAY;GAClB,aAAa,YAAY;GACzB,YAAY,YAAY;GACxB,YAAY,YAAY;EAC1B;EAEA,MAAM,cAAc,MAAM,WAAW,MAAM,EAAE,SAAS,QAAQ,IAAI;EAClE,IAAI,eAAe,GACjB,MAAM,eAAe;OAErB,MAAM,KAAK,UAAU;EAEvB,MAAM,UAAU,KAAK;EAErB,OAAO;CACT;CAEA,eAAe,cAAc,MAAgC;EAC3D,sBAAsB,IAAI;EAE1B,MAAM,aAAa,KAAK,WAAW,IAAI;EAEvC,IAAI,CAAC,WAAW,UAAU,GACxB,OAAO;EAGT,MAAM,GAAG,YAAY,EAAE,WAAW,KAAK,CAAC;EAKxC,MAAM,WADW,MADG,UAAU,EAAA,CACP,QAAQ,MAAM,EAAE,SAAS,IAChC,CAAQ;EAExB,OAAO;CACT;CAEA,eAAe,OACb,OACA,gBAAsC,CAAC,GACJ;EACnC,MAAM,EAAE,QAAQ,MAAM;EACtB,MAAM,QAAQ,MAAM,UAAU;EAI9B,MAAM,QADa,MAAM,YACX,CAAA,CAAW,MAAM,KAAK;EAyBpC,OAvBe,MAAM,KAAK,YAAY;GACpC,IAAI,QAAQ;GACZ,MAAM,aAAa;IACjB,QAAQ;IACR,QAAQ;IACR,GAAG,QAAQ;GACb,CAAC,CACE,KAAK,GAAG,CAAC,CACT,YAAY;GAEf,KAAK,MAAM,QAAQ,OAAO;IACxB,IAAI,WAAW,SAAS,IAAI,GAC1B,SAAS;IAGX,IAAI,QAAQ,KAAK,YAAY,CAAC,CAAC,SAAS,IAAI,GAC1C,SAAS;GAEb;GAEA,OAAO;IAAE;IAAS;GAAM;EAC1B,CAEO,CAAA,CACJ,QAAQ,MAAM,EAAE,QAAQ,CAAC,CAAC,CAC1B,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CACjC,MAAM,GAAG,KAAK,CAAC,CACf,KAAK,MAAM,EAAE,OAAO;CACzB;CAEA,eAAe,YAAY,MAAc,SAAiC;EACxE,MAAM,UAAU,MAAM,IAAI,IAAI;EAC9B,IAAI,CAAC,SAAS;EAEd,MAAM,EAAE,YAAY,gBAAgB,QAAQ;EAC5C,MAAM,gBAAgB,aAAa;EAInC,MAAM,WAAW;GAAE,YAAY;GAAe,cAF3C,cAAc,cAAc,UAAU,IAAI,MAAM;EAEuB;EAG1E,MAAM,gBAAgB,cAAc,oBAClC,QAAQ,YACR,QACF;EAEA,MAAM,KAAK;GACT,GAAG;GACH,OAAO;GACP,YAAY;EACd,CAAC;CACH;CAEA,OAAO;EACL;EACA;EACA;EACA;EACA,QAAQ;EACR;EACA;EACA;CACF;AACF"}
@@ -0,0 +1,3 @@
1
+ export { createFileSnippetStorage } from './file-storage.js';
2
+ export { createMemorySnippetStorage } from './memory-storage.js';
3
+ export type { SnippetStorage, Snippet, SnippetIndexEntry } from '../types.js';
@@ -0,0 +1,3 @@
1
+ import { createMemorySnippetStorage } from "./memory-storage.js";
2
+ import { createFileSnippetStorage } from "./file-storage.js";
3
+ export { createFileSnippetStorage, createMemorySnippetStorage };
@@ -0,0 +1,17 @@
1
+ import { Snippet, SnippetStorage } from '../types.js';
2
+ import { TrustStrategy } from '../trust-strategies.js';
3
+ export interface MemorySnippetStorageOptions {
4
+ /**
5
+ * Initial snippets to populate the storage with
6
+ */
7
+ initialSnippets?: Array<Snippet>;
8
+ /**
9
+ * Trust strategy for determining snippet trust levels
10
+ * @default createDefaultTrustStrategy()
11
+ */
12
+ trustStrategy?: TrustStrategy;
13
+ }
14
+ /**
15
+ * In-memory snippet storage for testing and demos
16
+ */
17
+ export declare function createMemorySnippetStorage(optionsOrSnippets?: MemorySnippetStorageOptions | Array<Snippet>): SnippetStorage;
@@ -0,0 +1,100 @@
1
+ import { createDefaultTrustStrategy } from "../trust-strategies.js";
2
+ //#region src/storage/memory-storage.ts
3
+ /**
4
+ * In-memory snippet storage for testing and demos
5
+ */
6
+ function createMemorySnippetStorage(optionsOrSnippets = []) {
7
+ const { initialSnippets = [], trustStrategy = createDefaultTrustStrategy() } = Array.isArray(optionsOrSnippets) ? { initialSnippets: optionsOrSnippets } : optionsOrSnippets;
8
+ const snippets = /* @__PURE__ */ new Map();
9
+ for (const snippet of initialSnippets) snippets.set(snippet.name, snippet);
10
+ function loadIndex() {
11
+ return Promise.resolve(Array.from(snippets.values()).map((snippet) => ({
12
+ id: snippet.id,
13
+ name: snippet.name,
14
+ description: snippet.description,
15
+ usageHints: snippet.usageHints,
16
+ trustLevel: snippet.trustLevel
17
+ })));
18
+ }
19
+ function loadAll() {
20
+ return Promise.resolve(Array.from(snippets.values()));
21
+ }
22
+ function get(name) {
23
+ return Promise.resolve(snippets.get(name) ?? null);
24
+ }
25
+ function save(snippet) {
26
+ const now = (/* @__PURE__ */ new Date()).toISOString();
27
+ const existing = snippets.get(snippet.name);
28
+ const fullSnippet = {
29
+ ...snippet,
30
+ createdAt: existing?.createdAt ?? now,
31
+ updatedAt: now
32
+ };
33
+ snippets.set(snippet.name, fullSnippet);
34
+ return Promise.resolve(fullSnippet);
35
+ }
36
+ function deleteSnippet(name) {
37
+ if (!snippets.has(name)) return Promise.resolve(false);
38
+ snippets.delete(name);
39
+ return Promise.resolve(true);
40
+ }
41
+ function search(query, searchOptions = {}) {
42
+ const { limit = 5 } = searchOptions;
43
+ const terms = query.toLowerCase().split(/\s+/);
44
+ const scored = Array.from(snippets.values()).map((snippet) => {
45
+ let score = 0;
46
+ const searchText = [
47
+ snippet.name,
48
+ snippet.description,
49
+ ...snippet.usageHints
50
+ ].join(" ").toLowerCase();
51
+ for (const term of terms) {
52
+ if (searchText.includes(term)) score += 1;
53
+ if (snippet.name.toLowerCase().includes(term)) score += 2;
54
+ }
55
+ return {
56
+ snippet,
57
+ score
58
+ };
59
+ });
60
+ return Promise.resolve(scored.filter((s) => s.score > 0).sort((a, b) => b.score - a.score).slice(0, limit).map((s) => ({
61
+ id: s.snippet.id,
62
+ name: s.snippet.name,
63
+ description: s.snippet.description,
64
+ usageHints: s.snippet.usageHints,
65
+ trustLevel: s.snippet.trustLevel
66
+ })));
67
+ }
68
+ function updateStats(name, success) {
69
+ const snippet = snippets.get(name);
70
+ if (!snippet) return Promise.resolve();
71
+ const { executions, successRate } = snippet.stats;
72
+ const newExecutions = executions + 1;
73
+ const newStats = {
74
+ executions: newExecutions,
75
+ successRate: (successRate * executions + (success ? 1 : 0)) / newExecutions
76
+ };
77
+ const newTrustLevel = trustStrategy.calculateTrustLevel(snippet.trustLevel, newStats);
78
+ snippets.set(name, {
79
+ ...snippet,
80
+ stats: newStats,
81
+ trustLevel: newTrustLevel,
82
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
83
+ });
84
+ return Promise.resolve();
85
+ }
86
+ return {
87
+ loadIndex,
88
+ loadAll,
89
+ get,
90
+ save,
91
+ delete: deleteSnippet,
92
+ search,
93
+ updateStats,
94
+ trustStrategy
95
+ };
96
+ }
97
+ //#endregion
98
+ export { createMemorySnippetStorage };
99
+
100
+ //# sourceMappingURL=memory-storage.js.map