@stacksjs/ai 0.70.55 → 0.70.57
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/package.json +10 -7
- package/src/agents/claude/index.ts +0 -310
- package/src/agents/index.ts +0 -7
- package/src/buddy.ts +0 -619
- package/src/drivers/anthropic/index.ts +0 -430
- package/src/drivers/claude-agent-sdk/index.ts +0 -370
- package/src/drivers/index.ts +0 -14
- package/src/drivers/ollama/index.ts +0 -514
- package/src/drivers/openai/index.ts +0 -529
- package/src/image.ts +0 -607
- package/src/index.ts +0 -53
- package/src/mcp.ts +0 -658
- package/src/personalization.ts +0 -490
- package/src/search.ts +0 -555
- package/src/text.ts +0 -79
- package/src/types.ts +0 -229
- package/src/utils/client-bedrock-runtime.ts +0 -51
- package/src/utils/client-bedrock.ts +0 -75
- package/src/utils/model-access.ts +0 -32
- package/src/utils/retry.ts +0 -124
- package/src/utils/tokens.ts +0 -159
- package/src/utils/usage.ts +0 -98
- package/src/utils/vision.ts +0 -119
- /package/dist/{src/agents → agents}/claude/index.d.ts +0 -0
- /package/dist/{src/agents → agents}/index.d.ts +0 -0
- /package/dist/{src/buddy.d.ts → buddy.d.ts} +0 -0
- /package/dist/{src/drivers → drivers}/anthropic/index.d.ts +0 -0
- /package/dist/{src/drivers → drivers}/claude-agent-sdk/index.d.ts +0 -0
- /package/dist/{src/drivers → drivers}/index.d.ts +0 -0
- /package/dist/{src/drivers → drivers}/ollama/index.d.ts +0 -0
- /package/dist/{src/drivers → drivers}/openai/index.d.ts +0 -0
- /package/dist/{src/image.d.ts → image.d.ts} +0 -0
- /package/dist/{src/index.d.ts → index.d.ts} +0 -0
- /package/dist/{src/index.js → index.js} +0 -0
- /package/dist/{src/mcp.d.ts → mcp.d.ts} +0 -0
- /package/dist/{src/personalization.d.ts → personalization.d.ts} +0 -0
- /package/dist/{src/search.d.ts → search.d.ts} +0 -0
- /package/dist/{src/text.d.ts → text.d.ts} +0 -0
- /package/dist/{src/types.d.ts → types.d.ts} +0 -0
- /package/dist/{src/utils → utils}/client-bedrock-runtime.d.ts +0 -0
- /package/dist/{src/utils → utils}/client-bedrock.d.ts +0 -0
- /package/dist/{src/utils → utils}/model-access.d.ts +0 -0
- /package/dist/{src/utils → utils}/retry.d.ts +0 -0
- /package/dist/{src/utils → utils}/tokens.d.ts +0 -0
- /package/dist/{src/utils → utils}/usage.d.ts +0 -0
- /package/dist/{src/utils → utils}/vision.d.ts +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacksjs/ai",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.70.
|
|
4
|
+
"version": "0.70.57",
|
|
5
5
|
"description": "Stacks Artificial Intelligence.",
|
|
6
6
|
"author": "Chris Breuer",
|
|
7
7
|
"contributors": [
|
|
@@ -34,20 +34,23 @@
|
|
|
34
34
|
"exports": {
|
|
35
35
|
".": {
|
|
36
36
|
"types": "./dist/index.d.ts",
|
|
37
|
-
"
|
|
38
|
-
"
|
|
37
|
+
"development": "./src/index.ts",
|
|
38
|
+
"bun": "./dist/index.js",
|
|
39
|
+
"import": "./dist/index.js",
|
|
40
|
+
"default": "./dist/index.js"
|
|
39
41
|
},
|
|
40
42
|
"./*": {
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
+
"development": "./src/*",
|
|
44
|
+
"bun": "./dist/*",
|
|
45
|
+
"import": "./dist/*",
|
|
46
|
+
"default": "./dist/*"
|
|
43
47
|
}
|
|
44
48
|
},
|
|
45
49
|
"module": "dist/index.js",
|
|
46
50
|
"types": "dist/index.d.ts",
|
|
47
51
|
"files": [
|
|
48
52
|
"README.md",
|
|
49
|
-
"dist"
|
|
50
|
-
"src"
|
|
53
|
+
"dist"
|
|
51
54
|
],
|
|
52
55
|
"scripts": {
|
|
53
56
|
"build": "bun build.ts",
|
|
@@ -1,310 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Claude CLI Agent
|
|
3
|
-
*
|
|
4
|
-
* Provides integration with Claude CLI for local and remote (EC2) execution.
|
|
5
|
-
* Supports both synchronous and streaming responses.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { AIDriver, AIMessage, ClaudeStreamEvent, StreamingResult } from '../../types'
|
|
9
|
-
|
|
10
|
-
// Track prompt count for session feedback
|
|
11
|
-
let promptCount = 0
|
|
12
|
-
|
|
13
|
-
export interface ClaudeAgentConfig {
|
|
14
|
-
/** Working directory for command execution */
|
|
15
|
-
cwd?: string
|
|
16
|
-
/** EC2 host for remote execution */
|
|
17
|
-
ec2Host?: string
|
|
18
|
-
/** EC2 user (default: ubuntu) */
|
|
19
|
-
ec2User?: string
|
|
20
|
-
/** Path to EC2 SSH key */
|
|
21
|
-
ec2Key?: string
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Format tool usage for display
|
|
26
|
-
*/
|
|
27
|
-
function formatToolUsage(toolName: string, input: Record<string, unknown>): string {
|
|
28
|
-
switch (toolName) {
|
|
29
|
-
case 'Read': {
|
|
30
|
-
const filePath = (input.file_path as string) || ''
|
|
31
|
-
const fileName = filePath.split('/').pop() || filePath
|
|
32
|
-
return `\n📖 Reading: ${fileName}\n`
|
|
33
|
-
}
|
|
34
|
-
case 'Glob':
|
|
35
|
-
return `\n🔍 Searching files: ${input.pattern || ''}\n`
|
|
36
|
-
case 'Grep':
|
|
37
|
-
return `\n🔎 Searching for: "${input.pattern || ''}"\n`
|
|
38
|
-
case 'Edit': {
|
|
39
|
-
const filePath = (input.file_path as string) || ''
|
|
40
|
-
const fileName = filePath.split('/').pop() || filePath
|
|
41
|
-
return `\n✏️ Editing: ${fileName}\n`
|
|
42
|
-
}
|
|
43
|
-
case 'Write': {
|
|
44
|
-
const filePath = (input.file_path as string) || ''
|
|
45
|
-
const fileName = filePath.split('/').pop() || filePath
|
|
46
|
-
return `\n📝 Writing: ${fileName}\n`
|
|
47
|
-
}
|
|
48
|
-
case 'Bash': {
|
|
49
|
-
const cmd = ((input.command as string) || '').substring(0, 50)
|
|
50
|
-
return `\n💻 Running: ${cmd}${cmd.length >= 50 ? '...' : ''}\n`
|
|
51
|
-
}
|
|
52
|
-
case 'Task':
|
|
53
|
-
return `\n🚀 Launching agent: ${input.description || 'task'}\n`
|
|
54
|
-
default:
|
|
55
|
-
return `\n🔧 Using: ${toolName}\n`
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Create a local Claude CLI agent
|
|
61
|
-
*/
|
|
62
|
-
export function createClaudeLocalAgent(config: ClaudeAgentConfig = {}): AIDriver {
|
|
63
|
-
return {
|
|
64
|
-
name: 'Claude CLI (Local)',
|
|
65
|
-
|
|
66
|
-
async process(command: string, context: string, _history: AIMessage[]): Promise<string> {
|
|
67
|
-
const { $: _$ } = await import('bun')
|
|
68
|
-
|
|
69
|
-
// Check if claude CLI is available
|
|
70
|
-
const whichResult = await _$`which claude`.quiet().nothrow()
|
|
71
|
-
if (whichResult.exitCode !== 0) {
|
|
72
|
-
throw new Error('Claude CLI not found. Install it with: npm install -g @anthropic-ai/claude-code')
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Build the full prompt with context
|
|
76
|
-
const fullPrompt = context
|
|
77
|
-
? `Context:\n${context}\n\nUser request: ${command}`
|
|
78
|
-
: command
|
|
79
|
-
|
|
80
|
-
const cwd = config.cwd || process.cwd()
|
|
81
|
-
|
|
82
|
-
try {
|
|
83
|
-
const result = await _$`cd ${cwd} && claude --print --dangerously-skip-permissions ${fullPrompt}`.quiet()
|
|
84
|
-
return result.text().trim()
|
|
85
|
-
}
|
|
86
|
-
catch {
|
|
87
|
-
// Try with allowedTools flag as alternative
|
|
88
|
-
try {
|
|
89
|
-
const result = await _$`cd ${cwd} && claude --print --allowedTools "Write,Edit,Bash" ${fullPrompt}`.quiet()
|
|
90
|
-
return result.text().trim()
|
|
91
|
-
}
|
|
92
|
-
catch (innerError) {
|
|
93
|
-
throw new Error(`Claude CLI error: ${(innerError as Error).message}`)
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
},
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Create a remote Claude CLI agent (EC2)
|
|
102
|
-
*/
|
|
103
|
-
export function createClaudeEC2Agent(config: ClaudeAgentConfig): AIDriver {
|
|
104
|
-
const {
|
|
105
|
-
ec2Host = process.env.BUDDY_EC2_HOST,
|
|
106
|
-
ec2User = process.env.BUDDY_EC2_USER || 'ubuntu',
|
|
107
|
-
ec2Key = process.env.BUDDY_EC2_KEY,
|
|
108
|
-
} = config
|
|
109
|
-
|
|
110
|
-
return {
|
|
111
|
-
name: 'Claude CLI (EC2)',
|
|
112
|
-
|
|
113
|
-
async process(command: string, context: string, _history: AIMessage[]): Promise<string> {
|
|
114
|
-
const { $: _$ } = await import('bun')
|
|
115
|
-
|
|
116
|
-
if (!ec2Host) {
|
|
117
|
-
throw new Error('BUDDY_EC2_HOST environment variable not set. Set it to your EC2 instance IP/hostname.')
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Build the full prompt with context
|
|
121
|
-
const fullPrompt = context
|
|
122
|
-
? `Context:\n${context}\n\nUser request: ${command}`
|
|
123
|
-
: command
|
|
124
|
-
|
|
125
|
-
// Escape the prompt for SSH
|
|
126
|
-
const escapedPrompt = fullPrompt.replace(/'/g, `'\\''`)
|
|
127
|
-
|
|
128
|
-
// Build SSH command
|
|
129
|
-
const sshArgs = ec2Key ? `-i ${ec2Key}` : ''
|
|
130
|
-
const sshTarget = `${ec2User}@${ec2Host}`
|
|
131
|
-
|
|
132
|
-
try {
|
|
133
|
-
const result = await _$`ssh ${sshArgs} ${sshTarget} "claude --print '${escapedPrompt}'"`.quiet()
|
|
134
|
-
return result.text().trim()
|
|
135
|
-
}
|
|
136
|
-
catch (error) {
|
|
137
|
-
throw new Error(`EC2 Claude CLI error: ${(error as Error).message}. Make sure SSH is configured and claude CLI is installed on EC2.`)
|
|
138
|
-
}
|
|
139
|
-
},
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Process command with streaming output using Claude CLI
|
|
145
|
-
*
|
|
146
|
-
* Returns a ReadableStream that emits chunks of the response in real-time.
|
|
147
|
-
* Uses --output-format stream-json for detailed streaming with tool usage.
|
|
148
|
-
*/
|
|
149
|
-
export async function processCommandStreaming(
|
|
150
|
-
command: string,
|
|
151
|
-
cwd: string,
|
|
152
|
-
): Promise<StreamingResult> {
|
|
153
|
-
const { spawn } = await import('bun')
|
|
154
|
-
|
|
155
|
-
// Spawn Claude CLI process with stream-json output format
|
|
156
|
-
const proc = spawn([
|
|
157
|
-
'claude',
|
|
158
|
-
'--print',
|
|
159
|
-
'--verbose',
|
|
160
|
-
'--output-format', 'stream-json',
|
|
161
|
-
'--dangerously-skip-permissions',
|
|
162
|
-
command,
|
|
163
|
-
], {
|
|
164
|
-
cwd,
|
|
165
|
-
stdout: 'pipe',
|
|
166
|
-
stderr: 'pipe',
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
let fullResponse = ''
|
|
170
|
-
const decoder = new TextDecoder()
|
|
171
|
-
const encoder = new TextEncoder()
|
|
172
|
-
|
|
173
|
-
const stream = new ReadableStream<Uint8Array>({
|
|
174
|
-
async start(controller) {
|
|
175
|
-
try {
|
|
176
|
-
const reader = proc.stdout.getReader()
|
|
177
|
-
let buffer = ''
|
|
178
|
-
let lastSentText = ''
|
|
179
|
-
|
|
180
|
-
let result = await reader.read()
|
|
181
|
-
while (!result.done) {
|
|
182
|
-
buffer += decoder.decode(result.value, { stream: true })
|
|
183
|
-
|
|
184
|
-
// Parse JSON lines from buffer
|
|
185
|
-
const lines = buffer.split('\n')
|
|
186
|
-
buffer = lines.pop() || ''
|
|
187
|
-
|
|
188
|
-
for (const line of lines) {
|
|
189
|
-
if (!line.trim()) continue
|
|
190
|
-
|
|
191
|
-
try {
|
|
192
|
-
const event = JSON.parse(line) as ClaudeStreamEvent
|
|
193
|
-
let textContent = ''
|
|
194
|
-
|
|
195
|
-
if (event.type === 'system' && event.subtype === 'init') {
|
|
196
|
-
promptCount++
|
|
197
|
-
// UI shows thinking indicator, no need to output text
|
|
198
|
-
}
|
|
199
|
-
// Handle content_block_start - initial text from a block
|
|
200
|
-
else if (event.type === 'content_block_start' && event.content_block?.text) {
|
|
201
|
-
textContent = event.content_block.text
|
|
202
|
-
}
|
|
203
|
-
// Handle content_block_delta - streaming text deltas (most important for real-time feel)
|
|
204
|
-
else if (event.type === 'content_block_delta' && event.delta?.text) {
|
|
205
|
-
textContent = event.delta.text
|
|
206
|
-
}
|
|
207
|
-
// Handle assistant messages with full content
|
|
208
|
-
else if (event.type === 'assistant' && event.message?.content) {
|
|
209
|
-
for (const block of event.message.content) {
|
|
210
|
-
if (block.type === 'text' && block.text) {
|
|
211
|
-
textContent += block.text
|
|
212
|
-
}
|
|
213
|
-
else if (block.type === 'tool_use' && block.name) {
|
|
214
|
-
textContent += formatToolUsage(block.name, block.input || {})
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
// Handle tool results
|
|
219
|
-
else if (event.type === 'user' && event.message?.content) {
|
|
220
|
-
for (const block of event.message.content) {
|
|
221
|
-
if (block.type === 'tool_result') {
|
|
222
|
-
textContent += ` ✓ done\n`
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
// Handle final result
|
|
227
|
-
else if (event.type === 'result' && event.result) {
|
|
228
|
-
fullResponse = event.result
|
|
229
|
-
// Only send result if we haven't already sent equivalent content
|
|
230
|
-
const resultPrefix = event.result.substring(0, Math.min(50, event.result.length))
|
|
231
|
-
if (!lastSentText.includes(resultPrefix)) {
|
|
232
|
-
textContent = event.result
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Send new content immediately for responsive streaming
|
|
237
|
-
if (textContent) {
|
|
238
|
-
// For deltas, always send (they're incremental)
|
|
239
|
-
if (event.type === 'content_block_delta') {
|
|
240
|
-
lastSentText += textContent
|
|
241
|
-
controller.enqueue(encoder.encode(textContent))
|
|
242
|
-
}
|
|
243
|
-
// For other events, check we haven't sent it already
|
|
244
|
-
else if (!lastSentText.endsWith(textContent)) {
|
|
245
|
-
lastSentText += textContent
|
|
246
|
-
controller.enqueue(encoder.encode(textContent))
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
catch {
|
|
251
|
-
// Not valid JSON, might be plain text output
|
|
252
|
-
if (!lastSentText.includes(line)) {
|
|
253
|
-
lastSentText += line
|
|
254
|
-
fullResponse += line
|
|
255
|
-
controller.enqueue(encoder.encode(line))
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
result = await reader.read()
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// Process remaining buffer
|
|
264
|
-
if (buffer.trim()) {
|
|
265
|
-
try {
|
|
266
|
-
const event = JSON.parse(buffer) as ClaudeStreamEvent
|
|
267
|
-
if (event.result && !fullResponse) {
|
|
268
|
-
fullResponse = event.result
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
catch {
|
|
272
|
-
if (!lastSentText.includes(buffer)) {
|
|
273
|
-
fullResponse += buffer
|
|
274
|
-
controller.enqueue(encoder.encode(buffer))
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
await proc.exited
|
|
280
|
-
controller.close()
|
|
281
|
-
}
|
|
282
|
-
catch (error) {
|
|
283
|
-
controller.error(error)
|
|
284
|
-
}
|
|
285
|
-
},
|
|
286
|
-
})
|
|
287
|
-
|
|
288
|
-
const fullResponsePromise = (async () => {
|
|
289
|
-
await proc.exited
|
|
290
|
-
return fullResponse.trim()
|
|
291
|
-
})()
|
|
292
|
-
|
|
293
|
-
return { stream, fullResponse: fullResponsePromise }
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
export function resetPromptCount(): void {
|
|
297
|
-
promptCount = 0
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
export const claudeAgent: {
|
|
301
|
-
createLocal: typeof createClaudeLocalAgent
|
|
302
|
-
createEC2: typeof createClaudeEC2Agent
|
|
303
|
-
processStreaming: typeof processCommandStreaming
|
|
304
|
-
resetPromptCount: typeof resetPromptCount
|
|
305
|
-
} = {
|
|
306
|
-
createLocal: createClaudeLocalAgent,
|
|
307
|
-
createEC2: createClaudeEC2Agent,
|
|
308
|
-
processStreaming: processCommandStreaming,
|
|
309
|
-
resetPromptCount,
|
|
310
|
-
}
|