@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,172 @@
1
+ import { createDefaultTrustStrategy } from '../trust-strategies'
2
+ import type {
3
+ Snippet,
4
+ SnippetIndexEntry,
5
+ SnippetSearchOptions,
6
+ SnippetStorage,
7
+ } from '../types'
8
+ import type { TrustStrategy } from '../trust-strategies'
9
+
10
+ export interface MemorySnippetStorageOptions {
11
+ /**
12
+ * Initial snippets to populate the storage with
13
+ */
14
+ initialSnippets?: Array<Snippet>
15
+
16
+ /**
17
+ * Trust strategy for determining snippet trust levels
18
+ * @default createDefaultTrustStrategy()
19
+ */
20
+ trustStrategy?: TrustStrategy
21
+ }
22
+
23
+ /**
24
+ * In-memory snippet storage for testing and demos
25
+ */
26
+ export function createMemorySnippetStorage(
27
+ optionsOrSnippets: MemorySnippetStorageOptions | Array<Snippet> = [],
28
+ ): SnippetStorage {
29
+ const options = Array.isArray(optionsOrSnippets)
30
+ ? { initialSnippets: optionsOrSnippets }
31
+ : optionsOrSnippets
32
+
33
+ const { initialSnippets = [], trustStrategy = createDefaultTrustStrategy() } =
34
+ options
35
+
36
+ // Store snippets in a Map for O(1) lookup
37
+ const snippets = new Map<string, Snippet>()
38
+
39
+ // Initialize with any provided snippets
40
+ for (const snippet of initialSnippets) {
41
+ snippets.set(snippet.name, snippet)
42
+ }
43
+
44
+ function loadIndex(): Promise<Array<SnippetIndexEntry>> {
45
+ return Promise.resolve(
46
+ Array.from(snippets.values()).map((snippet) => ({
47
+ id: snippet.id,
48
+ name: snippet.name,
49
+ description: snippet.description,
50
+ usageHints: snippet.usageHints,
51
+ trustLevel: snippet.trustLevel,
52
+ })),
53
+ )
54
+ }
55
+
56
+ function loadAll(): Promise<Array<Snippet>> {
57
+ return Promise.resolve(Array.from(snippets.values()))
58
+ }
59
+
60
+ function get(name: string): Promise<Snippet | null> {
61
+ return Promise.resolve(snippets.get(name) ?? null)
62
+ }
63
+
64
+ function save(
65
+ snippet: Omit<Snippet, 'createdAt' | 'updatedAt'>,
66
+ ): Promise<Snippet> {
67
+ const now = new Date().toISOString()
68
+ const existing = snippets.get(snippet.name)
69
+
70
+ const fullSnippet: Snippet = {
71
+ ...snippet,
72
+ createdAt: existing?.createdAt ?? now,
73
+ updatedAt: now,
74
+ }
75
+
76
+ snippets.set(snippet.name, fullSnippet)
77
+ return Promise.resolve(fullSnippet)
78
+ }
79
+
80
+ function deleteSnippet(name: string): Promise<boolean> {
81
+ if (!snippets.has(name)) {
82
+ return Promise.resolve(false)
83
+ }
84
+ snippets.delete(name)
85
+ return Promise.resolve(true)
86
+ }
87
+
88
+ function search(
89
+ query: string,
90
+ searchOptions: SnippetSearchOptions = {},
91
+ ): Promise<Array<SnippetIndexEntry>> {
92
+ const { limit = 5 } = searchOptions
93
+
94
+ // Simple text matching
95
+ const queryLower = query.toLowerCase()
96
+ const terms = queryLower.split(/\s+/)
97
+
98
+ const scored = Array.from(snippets.values()).map((snippet) => {
99
+ let score = 0
100
+ const searchText = [
101
+ snippet.name,
102
+ snippet.description,
103
+ ...snippet.usageHints,
104
+ ]
105
+ .join(' ')
106
+ .toLowerCase()
107
+
108
+ for (const term of terms) {
109
+ if (searchText.includes(term)) {
110
+ score += 1
111
+ }
112
+ // Boost exact name matches
113
+ if (snippet.name.toLowerCase().includes(term)) {
114
+ score += 2
115
+ }
116
+ }
117
+
118
+ return { snippet, score }
119
+ })
120
+
121
+ return Promise.resolve(
122
+ scored
123
+ .filter((s) => s.score > 0)
124
+ .sort((a, b) => b.score - a.score)
125
+ .slice(0, limit)
126
+ .map((s) => ({
127
+ id: s.snippet.id,
128
+ name: s.snippet.name,
129
+ description: s.snippet.description,
130
+ usageHints: s.snippet.usageHints,
131
+ trustLevel: s.snippet.trustLevel,
132
+ })),
133
+ )
134
+ }
135
+
136
+ function updateStats(name: string, success: boolean): Promise<void> {
137
+ const snippet = snippets.get(name)
138
+ if (!snippet) return Promise.resolve()
139
+
140
+ const { executions, successRate } = snippet.stats
141
+ const newExecutions = executions + 1
142
+ const newSuccessRate =
143
+ (successRate * executions + (success ? 1 : 0)) / newExecutions
144
+
145
+ const newStats = { executions: newExecutions, successRate: newSuccessRate }
146
+
147
+ // Use trust strategy to calculate new trust level
148
+ const newTrustLevel = trustStrategy.calculateTrustLevel(
149
+ snippet.trustLevel,
150
+ newStats,
151
+ )
152
+
153
+ snippets.set(name, {
154
+ ...snippet,
155
+ stats: newStats,
156
+ trustLevel: newTrustLevel,
157
+ updatedAt: new Date().toISOString(),
158
+ })
159
+ return Promise.resolve()
160
+ }
161
+
162
+ return {
163
+ loadIndex,
164
+ loadAll,
165
+ get,
166
+ save,
167
+ delete: deleteSnippet,
168
+ search,
169
+ updateStats,
170
+ trustStrategy,
171
+ }
172
+ }
@@ -0,0 +1,142 @@
1
+ import type { SnippetStats, TrustLevel } from './types'
2
+
3
+ /**
4
+ * Strategy for determining snippet trust levels
5
+ */
6
+ export interface TrustStrategy {
7
+ /**
8
+ * Get the initial trust level for a newly created snippet
9
+ */
10
+ getInitialTrustLevel: () => TrustLevel
11
+
12
+ /**
13
+ * Calculate the new trust level based on execution stats
14
+ */
15
+ calculateTrustLevel: (
16
+ currentLevel: TrustLevel,
17
+ stats: SnippetStats,
18
+ ) => TrustLevel
19
+ }
20
+
21
+ /**
22
+ * Default trust strategy - snippets must earn trust through successful executions
23
+ *
24
+ * - untrusted: New snippet (0 executions)
25
+ * - provisional: 10+ executions with ≥90% success rate
26
+ * - trusted: 100+ executions with ≥95% success rate
27
+ */
28
+ export function createDefaultTrustStrategy(): TrustStrategy {
29
+ return {
30
+ getInitialTrustLevel: () => 'untrusted',
31
+
32
+ calculateTrustLevel: (currentLevel, stats) => {
33
+ const { executions, successRate } = stats
34
+
35
+ if (
36
+ currentLevel === 'untrusted' &&
37
+ executions >= 10 &&
38
+ successRate >= 0.9
39
+ ) {
40
+ return 'provisional'
41
+ }
42
+
43
+ if (
44
+ currentLevel === 'provisional' &&
45
+ executions >= 100 &&
46
+ successRate >= 0.95
47
+ ) {
48
+ return 'trusted'
49
+ }
50
+
51
+ return currentLevel
52
+ },
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Always trusted strategy - snippets are immediately trusted upon creation
58
+ *
59
+ * Use this for development/testing or when you trust the LLM's code generation
60
+ */
61
+ export function createAlwaysTrustedStrategy(): TrustStrategy {
62
+ return {
63
+ getInitialTrustLevel: () => 'trusted',
64
+ calculateTrustLevel: () => 'trusted',
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Relaxed trust strategy - faster trust promotion for development
70
+ *
71
+ * - untrusted: New snippet (0 executions)
72
+ * - provisional: 3+ executions with ≥80% success rate
73
+ * - trusted: 10+ executions with ≥90% success rate
74
+ */
75
+ export function createRelaxedTrustStrategy(): TrustStrategy {
76
+ return {
77
+ getInitialTrustLevel: () => 'untrusted',
78
+
79
+ calculateTrustLevel: (currentLevel, stats) => {
80
+ const { executions, successRate } = stats
81
+
82
+ if (
83
+ currentLevel === 'untrusted' &&
84
+ executions >= 3 &&
85
+ successRate >= 0.8
86
+ ) {
87
+ return 'provisional'
88
+ }
89
+
90
+ if (
91
+ currentLevel === 'provisional' &&
92
+ executions >= 10 &&
93
+ successRate >= 0.9
94
+ ) {
95
+ return 'trusted'
96
+ }
97
+
98
+ return currentLevel
99
+ },
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Custom trust strategy with configurable thresholds
105
+ */
106
+ export function createCustomTrustStrategy(config: {
107
+ initialLevel?: TrustLevel
108
+ provisionalThreshold?: { executions: number; successRate: number }
109
+ trustedThreshold?: { executions: number; successRate: number }
110
+ }): TrustStrategy {
111
+ const {
112
+ initialLevel = 'untrusted',
113
+ provisionalThreshold = { executions: 10, successRate: 0.9 },
114
+ trustedThreshold = { executions: 100, successRate: 0.95 },
115
+ } = config
116
+
117
+ return {
118
+ getInitialTrustLevel: () => initialLevel,
119
+
120
+ calculateTrustLevel: (currentLevel, stats) => {
121
+ const { executions, successRate } = stats
122
+
123
+ if (
124
+ currentLevel === 'untrusted' &&
125
+ executions >= provisionalThreshold.executions &&
126
+ successRate >= provisionalThreshold.successRate
127
+ ) {
128
+ return 'provisional'
129
+ }
130
+
131
+ if (
132
+ currentLevel === 'provisional' &&
133
+ executions >= trustedThreshold.executions &&
134
+ successRate >= trustedThreshold.successRate
135
+ ) {
136
+ return 'trusted'
137
+ }
138
+
139
+ return currentLevel
140
+ },
141
+ }
142
+ }
package/src/types.ts ADDED
@@ -0,0 +1,289 @@
1
+ import type { AnyTextAdapter, ModelMessage, ToolRegistry } from '@tanstack/ai'
2
+ import type { CodeModeToolConfig } from '@tanstack/ai-code-mode'
3
+ import type { TrustStrategy } from './trust-strategies'
4
+
5
+ // ============================================================================
6
+ // Trust Levels
7
+ // ============================================================================
8
+
9
+ /**
10
+ * Trust level for a snippet
11
+ * - untrusted: Newly created, not yet proven
12
+ * - provisional: Has been successfully executed 10+ times with 90%+ success
13
+ * - trusted: Has been successfully executed 100+ times with 95%+ success
14
+ */
15
+ export type TrustLevel = 'untrusted' | 'provisional' | 'trusted'
16
+
17
+ // ============================================================================
18
+ // Snippet Statistics
19
+ // ============================================================================
20
+
21
+ /**
22
+ * Execution statistics for a snippet
23
+ */
24
+ export interface SnippetStats {
25
+ /**
26
+ * Total number of times this snippet has been executed
27
+ */
28
+ executions: number
29
+
30
+ /**
31
+ * Success rate (0-1) based on execution history
32
+ */
33
+ successRate: number
34
+ }
35
+
36
+ // ============================================================================
37
+ // Snippet Types
38
+ // ============================================================================
39
+
40
+ /**
41
+ * A reusable snippet that can be executed in the Code Mode sandbox
42
+ */
43
+ export interface Snippet {
44
+ /**
45
+ * Unique identifier for the snippet
46
+ */
47
+ id: string
48
+
49
+ /**
50
+ * Unique name in snake_case (e.g., 'fetch_github_stats')
51
+ * This becomes the function name with snippet_ prefix in the sandbox
52
+ */
53
+ name: string
54
+
55
+ /**
56
+ * Human-readable description of what the snippet does
57
+ */
58
+ description: string
59
+
60
+ /**
61
+ * TypeScript code that implements the snippet
62
+ * The code receives `input` as a variable and can call:
63
+ * - external_* functions (tools)
64
+ * - other snippet_* functions (snippets)
65
+ * Should return a value
66
+ */
67
+ code: string
68
+
69
+ /**
70
+ * JSON Schema describing the input parameter
71
+ */
72
+ inputSchema: Record<string, unknown>
73
+
74
+ /**
75
+ * JSON Schema describing the return value
76
+ */
77
+ outputSchema: Record<string, unknown>
78
+
79
+ /**
80
+ * Hints about when to use this snippet
81
+ * e.g., "Use when comparing NPM package popularity"
82
+ */
83
+ usageHints: Array<string>
84
+
85
+ /**
86
+ * Names of other snippets this snippet depends on/calls
87
+ */
88
+ dependsOn: Array<string>
89
+
90
+ /**
91
+ * Trust level based on execution history
92
+ */
93
+ trustLevel: TrustLevel
94
+
95
+ /**
96
+ * Execution statistics
97
+ */
98
+ stats: SnippetStats
99
+
100
+ /**
101
+ * ISO timestamp when the snippet was created
102
+ */
103
+ createdAt: string
104
+
105
+ /**
106
+ * ISO timestamp when the snippet was last updated
107
+ */
108
+ updatedAt: string
109
+ }
110
+
111
+ // ============================================================================
112
+ // Snippet Index Types
113
+ // ============================================================================
114
+
115
+ /**
116
+ * Lightweight snippet entry for the index (metadata only, no code)
117
+ * Used for fast loading and snippet selection
118
+ */
119
+ export type SnippetIndexEntry = Pick<
120
+ Snippet,
121
+ 'id' | 'name' | 'description' | 'usageHints' | 'trustLevel'
122
+ >
123
+
124
+ // ============================================================================
125
+ // Storage Interface
126
+ // ============================================================================
127
+
128
+ /**
129
+ * Options for searching snippets
130
+ */
131
+ export interface SnippetSearchOptions {
132
+ /**
133
+ * Maximum number of results to return
134
+ * @default 5
135
+ */
136
+ limit?: number
137
+ }
138
+
139
+ /**
140
+ * Interface for snippet storage implementations
141
+ */
142
+ export interface SnippetStorage {
143
+ /**
144
+ * Load the snippet index (lightweight metadata for all snippets)
145
+ */
146
+ loadIndex: () => Promise<Array<SnippetIndexEntry>>
147
+
148
+ /**
149
+ * Load all snippets with full details (including code)
150
+ */
151
+ loadAll: () => Promise<Array<Snippet>>
152
+
153
+ /**
154
+ * Get a snippet by name
155
+ */
156
+ get: (name: string) => Promise<Snippet | null>
157
+
158
+ /**
159
+ * Save a snippet (create or update)
160
+ */
161
+ save: (snippet: Omit<Snippet, 'createdAt' | 'updatedAt'>) => Promise<Snippet>
162
+
163
+ /**
164
+ * Delete a snippet by name
165
+ */
166
+ delete: (name: string) => Promise<boolean>
167
+
168
+ /**
169
+ * Search for snippets by query
170
+ */
171
+ search: (
172
+ query: string,
173
+ options?: SnippetSearchOptions,
174
+ ) => Promise<Array<SnippetIndexEntry>>
175
+
176
+ /**
177
+ * Update execution statistics for a snippet
178
+ */
179
+ updateStats: (name: string, success: boolean) => Promise<void>
180
+
181
+ /**
182
+ * Trust strategy used by this storage (optional, for creating new snippets)
183
+ */
184
+ trustStrategy?: TrustStrategy
185
+ }
186
+
187
+ // ============================================================================
188
+ // Configuration Types
189
+ // ============================================================================
190
+
191
+ /**
192
+ * Configuration for the snippets system
193
+ */
194
+ export interface SnippetsConfig {
195
+ /**
196
+ * Storage implementation for snippets
197
+ */
198
+ storage: SnippetStorage
199
+
200
+ /**
201
+ * Maximum number of snippets to load into context per request
202
+ * @default 5
203
+ */
204
+ maxSnippetsInContext?: number
205
+
206
+ /**
207
+ * Trust strategy for determining snippet trust levels
208
+ * @default createDefaultTrustStrategy()
209
+ */
210
+ trustStrategy?: TrustStrategy
211
+ }
212
+
213
+ /**
214
+ * Options for codeModeWithSnippets
215
+ */
216
+ export interface CodeModeWithSnippetsOptions {
217
+ /**
218
+ * Code Mode tool configuration (driver, tools, timeout, memoryLimit)
219
+ */
220
+ config: CodeModeToolConfig
221
+
222
+ /**
223
+ * Text adapter for snippet selection (should be a cheap/fast model)
224
+ */
225
+ adapter: AnyTextAdapter
226
+
227
+ /**
228
+ * Snippets configuration
229
+ */
230
+ snippets: SnippetsConfig
231
+
232
+ /**
233
+ * Current conversation messages (used for context-aware snippet selection)
234
+ */
235
+ messages: Array<ModelMessage>
236
+
237
+ /**
238
+ * Whether to include snippets as direct tools (not just sandbox bindings).
239
+ * When true, snippets become first-class tools the LLM can call directly.
240
+ * @default true
241
+ */
242
+ snippetsAsTools?: boolean
243
+ }
244
+
245
+ /**
246
+ * Result from codeModeWithSnippets
247
+ */
248
+ export interface CodeModeWithSnippetsResult {
249
+ /**
250
+ * Tool registry for dynamic tool management.
251
+ * Pass this to chat() via the toolRegistry option.
252
+ * Snippets registered mid-stream will be added to this registry.
253
+ */
254
+ toolsRegistry: ToolRegistry
255
+
256
+ /**
257
+ * System prompt documenting available snippets and external functions
258
+ */
259
+ systemPrompt: string
260
+
261
+ /**
262
+ * Snippets that were selected for this request
263
+ */
264
+ selectedSnippets: Array<Snippet>
265
+ }
266
+
267
+ // ============================================================================
268
+ // Snippet Binding Types (internal)
269
+ // ============================================================================
270
+
271
+ /**
272
+ * A snippet transformed into a format suitable for sandbox injection
273
+ */
274
+ export interface SnippetBinding {
275
+ /**
276
+ * Function name with snippet_ prefix
277
+ */
278
+ name: string
279
+
280
+ /**
281
+ * The snippet this binding wraps
282
+ */
283
+ snippet: Snippet
284
+
285
+ /**
286
+ * Execute function that runs the snippet code
287
+ */
288
+ execute: (input: unknown) => Promise<unknown>
289
+ }