@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.
- package/LICENSE +21 -0
- package/README.md +208 -0
- package/dist/esm/code-mode-with-snippets.d.ts +65 -0
- package/dist/esm/code-mode-with-snippets.js +145 -0
- package/dist/esm/code-mode-with-snippets.js.map +1 -0
- package/dist/esm/create-snippet-management-tools.d.ts +40 -0
- package/dist/esm/create-snippet-management-tools.js +173 -0
- package/dist/esm/create-snippet-management-tools.js.map +1 -0
- package/dist/esm/create-snippets-system-prompt.d.ts +22 -0
- package/dist/esm/create-snippets-system-prompt.js +234 -0
- package/dist/esm/create-snippets-system-prompt.js.map +1 -0
- package/dist/esm/generate-snippet-types.d.ts +7 -0
- package/dist/esm/generate-snippet-types.js +79 -0
- package/dist/esm/generate-snippet-types.js.map +1 -0
- package/dist/esm/index.d.ts +14 -0
- package/dist/esm/index.js +10 -0
- package/dist/esm/select-relevant-snippets.d.ts +29 -0
- package/dist/esm/select-relevant-snippets.js +56 -0
- package/dist/esm/select-relevant-snippets.js.map +1 -0
- package/dist/esm/snippets-to-bindings.d.ts +34 -0
- package/dist/esm/snippets-to-bindings.js +74 -0
- package/dist/esm/snippets-to-bindings.js.map +1 -0
- package/dist/esm/snippets-to-tools.d.ts +74 -0
- package/dist/esm/snippets-to-tools.js +147 -0
- package/dist/esm/snippets-to-tools.js.map +1 -0
- package/dist/esm/storage/file-storage.d.ts +27 -0
- package/dist/esm/storage/file-storage.js +155 -0
- package/dist/esm/storage/file-storage.js.map +1 -0
- package/dist/esm/storage/index.d.ts +3 -0
- package/dist/esm/storage/index.js +3 -0
- package/dist/esm/storage/memory-storage.d.ts +17 -0
- package/dist/esm/storage/memory-storage.js +100 -0
- package/dist/esm/storage/memory-storage.js.map +1 -0
- package/dist/esm/trust-strategies.d.ts +50 -0
- package/dist/esm/trust-strategies.js +73 -0
- package/dist/esm/trust-strategies.js.map +1 -0
- package/dist/esm/types.d.ts +216 -0
- package/package.json +92 -0
- package/src/code-mode-with-snippets.ts +210 -0
- package/src/create-snippet-management-tools.ts +298 -0
- package/src/create-snippets-system-prompt.ts +289 -0
- package/src/generate-snippet-types.ts +162 -0
- package/src/index.ts +60 -0
- package/src/select-relevant-snippets.ts +136 -0
- package/src/snippets-to-bindings.ts +135 -0
- package/src/snippets-to-tools.ts +325 -0
- package/src/storage/file-storage.ts +275 -0
- package/src/storage/index.ts +6 -0
- package/src/storage/memory-storage.ts +172 -0
- package/src/trust-strategies.ts +142 -0
- package/src/types.ts +289 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { Snippet } from './types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Convert a JSON Schema to a TypeScript type string
|
|
5
|
+
*/
|
|
6
|
+
function schemaToType(schema: Record<string, unknown>): string {
|
|
7
|
+
if (typeof schema !== 'object') {
|
|
8
|
+
return 'unknown'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const schemaType = schema.type
|
|
12
|
+
|
|
13
|
+
// Handle basic types
|
|
14
|
+
if (schemaType === 'string') return 'string'
|
|
15
|
+
if (schemaType === 'number' || schemaType === 'integer') return 'number'
|
|
16
|
+
if (schemaType === 'boolean') return 'boolean'
|
|
17
|
+
if (schemaType === 'null') return 'null'
|
|
18
|
+
|
|
19
|
+
// Handle arrays
|
|
20
|
+
if (schemaType === 'array') {
|
|
21
|
+
const items = schema.items as Record<string, unknown> | undefined
|
|
22
|
+
const itemType = items ? schemaToType(items) : 'unknown'
|
|
23
|
+
return `Array<${itemType}>`
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Handle objects with properties
|
|
27
|
+
if (schemaType === 'object' && schema.properties) {
|
|
28
|
+
const properties = schema.properties as Record<
|
|
29
|
+
string,
|
|
30
|
+
Record<string, unknown>
|
|
31
|
+
>
|
|
32
|
+
const required = new Set(
|
|
33
|
+
(schema.required as Array<string> | undefined) ?? [],
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
const props = Object.entries(properties)
|
|
37
|
+
.map(([key, propSchema]) => {
|
|
38
|
+
const optional = required.has(key) ? '' : '?'
|
|
39
|
+
const propType = schemaToType(propSchema)
|
|
40
|
+
// Handle property names that need quoting
|
|
41
|
+
const safeName = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key)
|
|
42
|
+
? key
|
|
43
|
+
: `"${key}"`
|
|
44
|
+
return ` ${safeName}${optional}: ${propType};`
|
|
45
|
+
})
|
|
46
|
+
.join('\n')
|
|
47
|
+
|
|
48
|
+
return `{\n${props}\n}`
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Handle enums
|
|
52
|
+
if (schema.enum) {
|
|
53
|
+
const enumValues = schema.enum as Array<unknown>
|
|
54
|
+
return enumValues.map((v) => JSON.stringify(v)).join(' | ')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Handle union types (anyOf, oneOf)
|
|
58
|
+
if (schema.anyOf || schema.oneOf) {
|
|
59
|
+
const variants = (schema.anyOf || schema.oneOf) as Array<
|
|
60
|
+
Record<string, unknown>
|
|
61
|
+
>
|
|
62
|
+
return variants.map((v) => schemaToType(v)).join(' | ')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Handle type arrays (e.g., ["string", "null"])
|
|
66
|
+
if (Array.isArray(schemaType)) {
|
|
67
|
+
return schemaType
|
|
68
|
+
.map((t) => {
|
|
69
|
+
if (t === 'string') return 'string'
|
|
70
|
+
if (t === 'number' || t === 'integer') return 'number'
|
|
71
|
+
if (t === 'boolean') return 'boolean'
|
|
72
|
+
if (t === 'null') return 'null'
|
|
73
|
+
if (t === 'array') return 'Array<unknown>'
|
|
74
|
+
if (t === 'object') return 'object'
|
|
75
|
+
return 'unknown'
|
|
76
|
+
})
|
|
77
|
+
.join(' | ')
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Fallback for unknown schemas
|
|
81
|
+
return 'unknown'
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Capitalize the first letter of a string
|
|
86
|
+
*/
|
|
87
|
+
function capitalize(str: string): string {
|
|
88
|
+
return str.charAt(0).toUpperCase() + str.slice(1)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Convert snake_case to PascalCase
|
|
93
|
+
*/
|
|
94
|
+
function toPascalCase(str: string): string {
|
|
95
|
+
return str
|
|
96
|
+
.split('_')
|
|
97
|
+
.map((part) => capitalize(part))
|
|
98
|
+
.join('')
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Generate TypeScript type stubs for snippets.
|
|
103
|
+
* These are included in the system prompt so the LLM knows
|
|
104
|
+
* the exact type signatures of available snippets.
|
|
105
|
+
*/
|
|
106
|
+
export function generateSnippetTypes(snippets: Array<Snippet>): string {
|
|
107
|
+
const declarations: Array<string> = []
|
|
108
|
+
|
|
109
|
+
for (const snippet of snippets) {
|
|
110
|
+
const baseName = toPascalCase(snippet.name)
|
|
111
|
+
const inputTypeName = `Snippet${baseName}Input`
|
|
112
|
+
const outputTypeName = `Snippet${baseName}Output`
|
|
113
|
+
|
|
114
|
+
// Generate input type
|
|
115
|
+
const inputType = schemaToType(snippet.inputSchema)
|
|
116
|
+
if (
|
|
117
|
+
snippet.inputSchema.type === 'object' &&
|
|
118
|
+
snippet.inputSchema.properties &&
|
|
119
|
+
Object.keys(snippet.inputSchema.properties).length > 0
|
|
120
|
+
) {
|
|
121
|
+
declarations.push(`interface ${inputTypeName} ${inputType}`)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Generate output type
|
|
125
|
+
const outputType = schemaToType(snippet.outputSchema)
|
|
126
|
+
if (
|
|
127
|
+
snippet.outputSchema.type === 'object' &&
|
|
128
|
+
snippet.outputSchema.properties &&
|
|
129
|
+
Object.keys(snippet.outputSchema.properties).length > 0
|
|
130
|
+
) {
|
|
131
|
+
declarations.push(`interface ${outputTypeName} ${outputType}`)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Determine type references
|
|
135
|
+
const inputRef =
|
|
136
|
+
snippet.inputSchema.type === 'object' &&
|
|
137
|
+
snippet.inputSchema.properties &&
|
|
138
|
+
Object.keys(snippet.inputSchema.properties).length > 0
|
|
139
|
+
? inputTypeName
|
|
140
|
+
: inputType
|
|
141
|
+
|
|
142
|
+
const outputRef =
|
|
143
|
+
snippet.outputSchema.type === 'object' &&
|
|
144
|
+
snippet.outputSchema.properties &&
|
|
145
|
+
Object.keys(snippet.outputSchema.properties).length > 0
|
|
146
|
+
? outputTypeName
|
|
147
|
+
: outputType
|
|
148
|
+
|
|
149
|
+
// Generate function declaration with JSDoc
|
|
150
|
+
const hintsDoc = snippet.usageHints.map((h) => ` * @hint ${h}`).join('\n')
|
|
151
|
+
|
|
152
|
+
declarations.push(
|
|
153
|
+
`/**
|
|
154
|
+
* ${snippet.description}
|
|
155
|
+
${hintsDoc}
|
|
156
|
+
*/
|
|
157
|
+
declare function snippet_${snippet.name}(input: ${inputRef}): Promise<${outputRef}>;`,
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return declarations.join('\n\n')
|
|
162
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Main entry point
|
|
2
|
+
export {
|
|
3
|
+
codeModeWithSnippets,
|
|
4
|
+
createCodeModeWithSnippetsConfig,
|
|
5
|
+
} from './code-mode-with-snippets'
|
|
6
|
+
export type {
|
|
7
|
+
CodeModeWithSnippetsOptions,
|
|
8
|
+
CodeModeWithSnippetsResult,
|
|
9
|
+
} from './code-mode-with-snippets'
|
|
10
|
+
|
|
11
|
+
// Trust strategies
|
|
12
|
+
export {
|
|
13
|
+
createDefaultTrustStrategy,
|
|
14
|
+
createAlwaysTrustedStrategy,
|
|
15
|
+
createRelaxedTrustStrategy,
|
|
16
|
+
createCustomTrustStrategy,
|
|
17
|
+
} from './trust-strategies'
|
|
18
|
+
export type { TrustStrategy } from './trust-strategies'
|
|
19
|
+
|
|
20
|
+
// Snippet selection
|
|
21
|
+
export { selectRelevantSnippets } from './select-relevant-snippets'
|
|
22
|
+
|
|
23
|
+
// Snippets to tools (for direct calling)
|
|
24
|
+
export { snippetsToTools, snippetToTool } from './snippets-to-tools'
|
|
25
|
+
export type { SnippetToToolOptions } from './snippets-to-tools'
|
|
26
|
+
|
|
27
|
+
// Snippets to bindings (for sandbox injection - legacy)
|
|
28
|
+
export {
|
|
29
|
+
snippetsToBindings,
|
|
30
|
+
snippetsToSimpleBindings,
|
|
31
|
+
} from './snippets-to-bindings'
|
|
32
|
+
|
|
33
|
+
// Snippet management tools
|
|
34
|
+
export { createSnippetManagementTools } from './create-snippet-management-tools'
|
|
35
|
+
|
|
36
|
+
// System prompt generation
|
|
37
|
+
export { createSnippetsSystemPrompt } from './create-snippets-system-prompt'
|
|
38
|
+
|
|
39
|
+
// Type generation
|
|
40
|
+
export { generateSnippetTypes } from './generate-snippet-types'
|
|
41
|
+
|
|
42
|
+
// Storage implementations
|
|
43
|
+
//
|
|
44
|
+
// Only the worker/browser-safe in-memory storage is re-exported from the root
|
|
45
|
+
// entry. The Node-only file storage (`createFileSnippetStorage`) imports
|
|
46
|
+
// `node:fs` / `node:path`, so it lives behind the `@tanstack/ai-code-mode-snippets/storage`
|
|
47
|
+
// subpath to keep this root export safe for Cloudflare Workers and browser bundlers.
|
|
48
|
+
export { createMemorySnippetStorage } from './storage/memory-storage'
|
|
49
|
+
export type { MemorySnippetStorageOptions } from './storage/memory-storage'
|
|
50
|
+
|
|
51
|
+
// All types
|
|
52
|
+
export type {
|
|
53
|
+
Snippet,
|
|
54
|
+
SnippetIndexEntry,
|
|
55
|
+
SnippetStorage,
|
|
56
|
+
SnippetsConfig,
|
|
57
|
+
SnippetStats,
|
|
58
|
+
TrustLevel,
|
|
59
|
+
SnippetBinding,
|
|
60
|
+
} from './types'
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { chat } from '@tanstack/ai'
|
|
2
|
+
import type { AnyTextAdapter, ModelMessage, StreamChunk } from '@tanstack/ai'
|
|
3
|
+
import type { Snippet, SnippetIndexEntry, SnippetStorage } from './types'
|
|
4
|
+
|
|
5
|
+
interface SelectRelevantSnippetsOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Text adapter for snippet selection (should be a cheap/fast model)
|
|
8
|
+
*/
|
|
9
|
+
adapter: AnyTextAdapter
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Current conversation messages
|
|
13
|
+
*/
|
|
14
|
+
messages: Array<ModelMessage>
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Snippet index (lightweight metadata)
|
|
18
|
+
*/
|
|
19
|
+
snippetIndex: Array<SnippetIndexEntry>
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Maximum number of snippets to select
|
|
23
|
+
*/
|
|
24
|
+
maxSnippets: number
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Storage to load full snippet data
|
|
28
|
+
*/
|
|
29
|
+
storage: SnippetStorage
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Use a cheap/fast LLM to select which snippets are relevant for the current conversation
|
|
34
|
+
*/
|
|
35
|
+
export async function selectRelevantSnippets({
|
|
36
|
+
adapter,
|
|
37
|
+
messages,
|
|
38
|
+
snippetIndex,
|
|
39
|
+
maxSnippets,
|
|
40
|
+
storage,
|
|
41
|
+
}: SelectRelevantSnippetsOptions): Promise<Array<Snippet>> {
|
|
42
|
+
// Early exit conditions
|
|
43
|
+
if (snippetIndex.length === 0) return []
|
|
44
|
+
if (messages.length === 0) return []
|
|
45
|
+
|
|
46
|
+
// Build context from recent messages (last 5)
|
|
47
|
+
const recentMessages = messages.slice(-5)
|
|
48
|
+
const recentContext = recentMessages
|
|
49
|
+
.map((m) => {
|
|
50
|
+
let content: string
|
|
51
|
+
if (typeof m.content === 'string') {
|
|
52
|
+
content = m.content
|
|
53
|
+
} else if (Array.isArray(m.content)) {
|
|
54
|
+
// Handle content parts (text, images, etc.)
|
|
55
|
+
content = m.content
|
|
56
|
+
.map((part: unknown) => {
|
|
57
|
+
if (typeof part === 'string') return part
|
|
58
|
+
if (part && typeof part === 'object' && 'text' in part)
|
|
59
|
+
return (part as { text: string }).text
|
|
60
|
+
return '[non-text content]'
|
|
61
|
+
})
|
|
62
|
+
.join(' ')
|
|
63
|
+
} else {
|
|
64
|
+
content = '[complex content]'
|
|
65
|
+
}
|
|
66
|
+
return `${m.role}: ${content}`
|
|
67
|
+
})
|
|
68
|
+
.join('\n')
|
|
69
|
+
|
|
70
|
+
// Build snippet catalog for selection prompt
|
|
71
|
+
const snippetCatalog = snippetIndex
|
|
72
|
+
.map((s) => {
|
|
73
|
+
const hints = s.usageHints.length > 0 ? ` (${s.usageHints[0]})` : ''
|
|
74
|
+
return `- ${s.name}: ${s.description}${hints}`
|
|
75
|
+
})
|
|
76
|
+
.join('\n')
|
|
77
|
+
|
|
78
|
+
// Ask cheap model to select relevant snippets
|
|
79
|
+
const selectionPrompt = `Given this conversation context:
|
|
80
|
+
---
|
|
81
|
+
${recentContext}
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
Which of these snippets (if any) would be useful for the next response? Return a JSON array of snippet names, max ${maxSnippets}. Return [] if none are relevant.
|
|
85
|
+
|
|
86
|
+
Available snippets:
|
|
87
|
+
${snippetCatalog}
|
|
88
|
+
|
|
89
|
+
Respond with only the JSON array, no explanation. Example: ["snippet_name_1", "snippet_name_2"]`
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
// Use chat to get the selection
|
|
93
|
+
const stream = chat({
|
|
94
|
+
adapter,
|
|
95
|
+
messages: [
|
|
96
|
+
{
|
|
97
|
+
role: 'user',
|
|
98
|
+
content: selectionPrompt,
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
// Collect the full response
|
|
104
|
+
let responseText = ''
|
|
105
|
+
for await (const chunk of stream as AsyncIterable<StreamChunk>) {
|
|
106
|
+
if (chunk.type === 'TEXT_MESSAGE_CONTENT') {
|
|
107
|
+
responseText += chunk.delta
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Parse the JSON response
|
|
112
|
+
// Handle potential markdown code blocks
|
|
113
|
+
let jsonText = responseText.trim()
|
|
114
|
+
if (jsonText.startsWith('```')) {
|
|
115
|
+
// Remove markdown code block
|
|
116
|
+
jsonText = jsonText.replace(/^```(?:json)?\n?/, '').replace(/\n?```$/, '')
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const selectedNames: Array<string> = JSON.parse(jsonText)
|
|
120
|
+
|
|
121
|
+
if (!Array.isArray(selectedNames)) {
|
|
122
|
+
return []
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Load full snippet data for selected snippets
|
|
126
|
+
const selectedSnippets = await Promise.all(
|
|
127
|
+
selectedNames.slice(0, maxSnippets).map((name) => storage.get(name)),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
return selectedSnippets.filter((s): s is Snippet => s !== null)
|
|
131
|
+
} catch (error) {
|
|
132
|
+
// If parsing fails or any error occurs, return empty (safe fallback)
|
|
133
|
+
console.warn('Snippet selection failed, returning empty selection:', error)
|
|
134
|
+
return []
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { ToolExecutionContext } from '@tanstack/ai'
|
|
2
|
+
import type { ToolBinding } from '@tanstack/ai-code-mode'
|
|
3
|
+
import type { Snippet, SnippetStorage } from './types'
|
|
4
|
+
|
|
5
|
+
interface SnippetsToBindingsOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Snippets to convert to bindings
|
|
8
|
+
*/
|
|
9
|
+
snippets: Array<Snippet>
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Tool execution context for emitting custom events
|
|
13
|
+
*/
|
|
14
|
+
context?: ToolExecutionContext
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Function to execute snippet code in the sandbox
|
|
18
|
+
* The snippet code receives `input` as a variable
|
|
19
|
+
*/
|
|
20
|
+
executeInSandbox: (code: string, input: unknown) => Promise<unknown>
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Storage for updating execution stats
|
|
24
|
+
*/
|
|
25
|
+
storage: SnippetStorage
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Convert snippets to sandbox bindings with the snippet_ prefix.
|
|
30
|
+
* Snippets become callable functions inside the sandbox.
|
|
31
|
+
*/
|
|
32
|
+
export function snippetsToBindings({
|
|
33
|
+
snippets,
|
|
34
|
+
context,
|
|
35
|
+
executeInSandbox,
|
|
36
|
+
storage,
|
|
37
|
+
}: SnippetsToBindingsOptions): Record<string, ToolBinding> {
|
|
38
|
+
const bindings: Record<string, ToolBinding> = {}
|
|
39
|
+
|
|
40
|
+
for (const snippet of snippets) {
|
|
41
|
+
const bindingName = `snippet_${snippet.name}`
|
|
42
|
+
|
|
43
|
+
bindings[bindingName] = {
|
|
44
|
+
name: bindingName,
|
|
45
|
+
description: snippet.description,
|
|
46
|
+
inputSchema: snippet.inputSchema,
|
|
47
|
+
outputSchema: snippet.outputSchema,
|
|
48
|
+
execute: async (input: unknown) => {
|
|
49
|
+
const startTime = Date.now()
|
|
50
|
+
|
|
51
|
+
// Emit snippet call event
|
|
52
|
+
context?.emitCustomEvent('code_mode:snippet_call', {
|
|
53
|
+
snippet: snippet.name,
|
|
54
|
+
input,
|
|
55
|
+
timestamp: startTime,
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
// Wrap the snippet code to receive input as a variable
|
|
60
|
+
const wrappedCode = `
|
|
61
|
+
const input = ${JSON.stringify(input)};
|
|
62
|
+
${snippet.code}
|
|
63
|
+
`
|
|
64
|
+
|
|
65
|
+
const result = await executeInSandbox(wrappedCode, input)
|
|
66
|
+
const duration = Date.now() - startTime
|
|
67
|
+
|
|
68
|
+
// Emit success event
|
|
69
|
+
context?.emitCustomEvent('code_mode:snippet_result', {
|
|
70
|
+
snippet: snippet.name,
|
|
71
|
+
result,
|
|
72
|
+
duration,
|
|
73
|
+
timestamp: Date.now(),
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
// Update stats (async, don't await to not block)
|
|
77
|
+
storage.updateStats(snippet.name, true).catch(() => {
|
|
78
|
+
// Silently ignore stats update failures
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
return result
|
|
82
|
+
} catch (error) {
|
|
83
|
+
const duration = Date.now() - startTime
|
|
84
|
+
|
|
85
|
+
// Emit error event
|
|
86
|
+
context?.emitCustomEvent('code_mode:snippet_error', {
|
|
87
|
+
snippet: snippet.name,
|
|
88
|
+
error: error instanceof Error ? error.message : String(error),
|
|
89
|
+
duration,
|
|
90
|
+
timestamp: Date.now(),
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
// Update stats (async, don't await)
|
|
94
|
+
storage.updateStats(snippet.name, false).catch(() => {
|
|
95
|
+
// Silently ignore stats update failures
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
throw error
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return bindings
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Create a simple binding record for snippets without full sandbox execution.
|
|
109
|
+
* This is used when snippets are being documented in the system prompt
|
|
110
|
+
* but not yet being executed.
|
|
111
|
+
*/
|
|
112
|
+
export function snippetsToSimpleBindings(
|
|
113
|
+
snippets: Array<Snippet>,
|
|
114
|
+
): Record<string, ToolBinding> {
|
|
115
|
+
const bindings: Record<string, ToolBinding> = {}
|
|
116
|
+
|
|
117
|
+
for (const snippet of snippets) {
|
|
118
|
+
const bindingName = `snippet_${snippet.name}`
|
|
119
|
+
|
|
120
|
+
bindings[bindingName] = {
|
|
121
|
+
name: bindingName,
|
|
122
|
+
description: snippet.description,
|
|
123
|
+
inputSchema: snippet.inputSchema,
|
|
124
|
+
outputSchema: snippet.outputSchema,
|
|
125
|
+
execute: () =>
|
|
126
|
+
Promise.reject(
|
|
127
|
+
new Error(
|
|
128
|
+
`Snippet ${snippet.name} is not available for execution in this context`,
|
|
129
|
+
),
|
|
130
|
+
),
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return bindings
|
|
135
|
+
}
|