lonny-agent 0.2.4 → 0.2.6
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/README.md +326 -326
- package/dist/agent/llm.d.ts +4 -0
- package/dist/agent/llm.d.ts.map +1 -1
- package/dist/agent/prompt-builder.d.ts.map +1 -1
- package/dist/agent/prompt-builder.js +13 -9
- package/dist/agent/prompt-builder.js.map +1 -1
- package/dist/agent/providers/google.d.ts.map +1 -1
- package/dist/agent/providers/google.js.map +1 -1
- package/dist/agent/providers/openai.d.ts.map +1 -1
- package/dist/agent/providers/openai.js +43 -3
- package/dist/agent/providers/openai.js.map +1 -1
- package/dist/agent/session.d.ts +4 -0
- package/dist/agent/session.d.ts.map +1 -1
- package/dist/agent/session.js +45 -7
- package/dist/agent/session.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +7 -1
- package/dist/config/index.js.map +1 -1
- package/dist/tools/__tests__/bash.test.js +1 -1
- package/dist/tools/__tests__/bash.test.js.map +1 -1
- package/dist/tools/__tests__/edit.test.js +875 -371
- package/dist/tools/__tests__/edit.test.js.map +1 -1
- package/dist/tools/__tests__/grep.test.js +32 -0
- package/dist/tools/__tests__/grep.test.js.map +1 -1
- package/dist/tools/__tests__/sed.test.d.ts +2 -0
- package/dist/tools/__tests__/sed.test.d.ts.map +1 -0
- package/dist/tools/__tests__/sed.test.js +228 -0
- package/dist/tools/__tests__/sed.test.js.map +1 -0
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +83 -10
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.d.ts +50 -9
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +328 -158
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +54 -30
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/registry.d.ts +0 -6
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +2 -29
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/sed.d.ts +4 -0
- package/dist/tools/sed.d.ts.map +1 -0
- package/dist/tools/sed.js +121 -0
- package/dist/tools/sed.js.map +1 -0
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +26 -3
- package/dist/web/index.js.map +1 -1
- package/dist/web/public/app.js +5 -855
- package/dist/web/public/confirm.js +70 -0
- package/dist/web/public/index.html +121 -120
- package/dist/web/public/input.js +107 -0
- package/dist/web/public/messages.js +395 -0
- package/dist/web/public/sidebar.js +82 -0
- package/dist/web/public/state.js +72 -0
- package/dist/web/public/style.css +996 -949
- package/dist/web/public/utils.js +37 -0
- package/dist/web/public/websocket.js +267 -0
- package/dist/web/public/ws.js +19 -0
- package/dist/web/session-bridge.d.ts.map +1 -1
- package/dist/web/session-bridge.js +3 -7
- package/dist/web/session-bridge.js.map +1 -1
- package/package.json +55 -54
- package/scripts/copy-native.mjs +24 -0
- package/scripts/copy-web.mjs +15 -0
- package/src/agent/index.ts +25 -25
- package/src/agent/llm.ts +4 -0
- package/src/agent/prompt-builder.ts +13 -9
- package/src/agent/providers/anthropic.ts +179 -179
- package/src/agent/providers/google.ts +210 -211
- package/src/agent/providers/ollama.ts +186 -186
- package/src/agent/providers/openai.ts +61 -5
- package/src/agent/session.ts +59 -3
- package/src/config/index.ts +342 -335
- package/src/tools/__tests__/bash.test.ts +1 -1
- package/src/tools/__tests__/edit.test.ts +981 -385
- package/src/tools/__tests__/grep.test.ts +35 -0
- package/src/tools/bash.ts +97 -11
- package/src/tools/edit.ts +362 -169
- package/src/tools/git.ts +76 -76
- package/src/tools/grep.ts +57 -30
- package/src/tools/registry.ts +2 -31
- package/src/tui/index.ts +786 -786
- package/src/web/index.ts +25 -3
- package/src/web/public/app.js +5 -855
- package/src/web/public/confirm.js +70 -0
- package/src/web/public/index.html +121 -120
- package/src/web/public/input.js +107 -0
- package/src/web/public/messages.js +395 -0
- package/src/web/public/sidebar.js +82 -0
- package/src/web/public/state.js +72 -0
- package/src/web/public/style.css +996 -949
- package/src/web/public/utils.js +37 -0
- package/src/web/public/websocket.js +267 -0
- package/src/web/public/ws.js +19 -0
- package/src/web/session-bridge.ts +193 -194
- package/.claude/settings.local.json +0 -14
- package/.kilo/plans/1780064105789-mighty-pixel.md +0 -171
- package/.kilo/plans/1780293725888-quick-wizard.md +0 -62
- package/.lonny/plan-web-cwd-status.md +0 -38
- package/src/tools/exec.ts +0 -348
|
@@ -1,211 +1,210 @@
|
|
|
1
|
-
import { ToolCall, type ToolDefinition } from '../../tools/types.js'
|
|
2
|
-
import type { LLMChunk, LLMMessage, LLMProvider } from '../llm.js'
|
|
3
|
-
|
|
4
|
-
interface GoogleContent {
|
|
5
|
-
role: 'user' | 'model'
|
|
6
|
-
parts: {
|
|
7
|
-
text?: string
|
|
8
|
-
functionCall?: { name: string; args: Record<string, unknown> }
|
|
9
|
-
functionResponse?: { name: string; response: Record<string, unknown> }
|
|
10
|
-
}[]
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
interface GoogleTool {
|
|
14
|
-
functionDeclarations: {
|
|
15
|
-
name: string
|
|
16
|
-
description: string
|
|
17
|
-
parameters: Record<string, unknown>
|
|
18
|
-
}[]
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface GoogleCandidate {
|
|
22
|
-
content: GoogleContent
|
|
23
|
-
finishReason?: string
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface GoogleResponse {
|
|
27
|
-
candidates?: GoogleCandidate[]
|
|
28
|
-
usageMetadata?: {
|
|
29
|
-
promptTokenCount?: number
|
|
30
|
-
candidatesTokenCount?: number
|
|
31
|
-
totalTokenCount?: number
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
interface GoogleStreamChunk {
|
|
36
|
-
candidates?: GoogleCandidate[]
|
|
37
|
-
usageMetadata?: GoogleResponse['usageMetadata']
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export class GoogleProvider implements LLMProvider {
|
|
41
|
-
private apiKey: string
|
|
42
|
-
private model: string
|
|
43
|
-
private baseUrl: string
|
|
44
|
-
|
|
45
|
-
constructor(apiKey: string, baseURL?: string, model?: string) {
|
|
46
|
-
this.apiKey = apiKey
|
|
47
|
-
this.model = model || 'gemini-2.0-flash'
|
|
48
|
-
this.baseUrl = baseURL || 'https://generativelanguage.googleapis.com/v1beta'
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async *chat(
|
|
52
|
-
messages: LLMMessage[],
|
|
53
|
-
tools: ToolDefinition[],
|
|
54
|
-
signal?: AbortSignal,
|
|
55
|
-
): AsyncGenerator<LLMChunk> {
|
|
56
|
-
const systemInstruction = messages.find(m => m.role === 'system')?.content || ''
|
|
57
|
-
const nonSystemMessages = messages.filter(m => m.role !== 'system')
|
|
58
|
-
|
|
59
|
-
// Build Google-format contents
|
|
60
|
-
const contents: GoogleContent[] = []
|
|
61
|
-
|
|
62
|
-
for (const m of nonSystemMessages) {
|
|
63
|
-
if (m.role === 'user') {
|
|
64
|
-
const parts: GoogleContent['parts'] = [{ text: m.content || '' }]
|
|
65
|
-
contents.push({ role: 'user', parts })
|
|
66
|
-
} else if (m.role === 'assistant') {
|
|
67
|
-
const parts: GoogleContent['parts'] = []
|
|
68
|
-
if (m.content) {
|
|
69
|
-
parts.push({ text: m.content })
|
|
70
|
-
}
|
|
71
|
-
if (m.tool_calls && m.tool_calls.length > 0) {
|
|
72
|
-
for (const tc of m.tool_calls) {
|
|
73
|
-
parts.push({
|
|
74
|
-
functionCall: {
|
|
75
|
-
name: tc.name,
|
|
76
|
-
args: tc.input as Record<string, unknown>,
|
|
77
|
-
},
|
|
78
|
-
})
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
contents.push({ role: 'model', parts })
|
|
82
|
-
} else if (m.role === 'tool') {
|
|
83
|
-
// Tool results are sent as functionResponse in a user message
|
|
84
|
-
contents.push({
|
|
85
|
-
role: 'user',
|
|
86
|
-
parts: [
|
|
87
|
-
{
|
|
88
|
-
functionResponse: {
|
|
89
|
-
name: m.name || '',
|
|
90
|
-
response: { content: m.content || '' },
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
],
|
|
94
|
-
})
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Build tools
|
|
99
|
-
const googleTools: GoogleTool[] | undefined =
|
|
100
|
-
tools.length > 0
|
|
101
|
-
? [
|
|
102
|
-
{
|
|
103
|
-
functionDeclarations: tools.map(t => {
|
|
104
|
-
const properties: Record<string, unknown> = {}
|
|
105
|
-
for (const [key, param] of Object.entries(t.parameters)) {
|
|
106
|
-
const { required: _, ...rest } = param
|
|
107
|
-
properties[key] = rest
|
|
108
|
-
}
|
|
109
|
-
return {
|
|
110
|
-
name: t.name,
|
|
111
|
-
description: t.description,
|
|
112
|
-
parameters: {
|
|
113
|
-
type: 'object',
|
|
114
|
-
properties,
|
|
115
|
-
required: Object.entries(t.parameters)
|
|
116
|
-
.filter(([, v]) => v.required)
|
|
117
|
-
.map(([k]) => k),
|
|
118
|
-
},
|
|
119
|
-
}
|
|
120
|
-
}),
|
|
121
|
-
},
|
|
122
|
-
]
|
|
123
|
-
: undefined
|
|
124
|
-
|
|
125
|
-
// Build request body
|
|
126
|
-
const body: Record<string, unknown> = {
|
|
127
|
-
contents,
|
|
128
|
-
tools: googleTools,
|
|
129
|
-
}
|
|
130
|
-
if (systemInstruction) {
|
|
131
|
-
body.system_instruction = { parts: [{ text: systemInstruction }] }
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
'
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
let
|
|
156
|
-
let
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
1
|
+
import { ToolCall, type ToolDefinition } from '../../tools/types.js'
|
|
2
|
+
import type { LLMChunk, LLMMessage, LLMProvider } from '../llm.js'
|
|
3
|
+
|
|
4
|
+
interface GoogleContent {
|
|
5
|
+
role: 'user' | 'model'
|
|
6
|
+
parts: {
|
|
7
|
+
text?: string
|
|
8
|
+
functionCall?: { name: string; args: Record<string, unknown> }
|
|
9
|
+
functionResponse?: { name: string; response: Record<string, unknown> }
|
|
10
|
+
}[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface GoogleTool {
|
|
14
|
+
functionDeclarations: {
|
|
15
|
+
name: string
|
|
16
|
+
description: string
|
|
17
|
+
parameters: Record<string, unknown>
|
|
18
|
+
}[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface GoogleCandidate {
|
|
22
|
+
content: GoogleContent
|
|
23
|
+
finishReason?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface GoogleResponse {
|
|
27
|
+
candidates?: GoogleCandidate[]
|
|
28
|
+
usageMetadata?: {
|
|
29
|
+
promptTokenCount?: number
|
|
30
|
+
candidatesTokenCount?: number
|
|
31
|
+
totalTokenCount?: number
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface GoogleStreamChunk {
|
|
36
|
+
candidates?: GoogleCandidate[]
|
|
37
|
+
usageMetadata?: GoogleResponse['usageMetadata']
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class GoogleProvider implements LLMProvider {
|
|
41
|
+
private apiKey: string
|
|
42
|
+
private model: string
|
|
43
|
+
private baseUrl: string
|
|
44
|
+
|
|
45
|
+
constructor(apiKey: string, baseURL?: string, model?: string) {
|
|
46
|
+
this.apiKey = apiKey
|
|
47
|
+
this.model = model || 'gemini-2.0-flash'
|
|
48
|
+
this.baseUrl = baseURL || 'https://generativelanguage.googleapis.com/v1beta'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async *chat(
|
|
52
|
+
messages: LLMMessage[],
|
|
53
|
+
tools: ToolDefinition[],
|
|
54
|
+
signal?: AbortSignal,
|
|
55
|
+
): AsyncGenerator<LLMChunk> {
|
|
56
|
+
const systemInstruction = messages.find(m => m.role === 'system')?.content || ''
|
|
57
|
+
const nonSystemMessages = messages.filter(m => m.role !== 'system')
|
|
58
|
+
|
|
59
|
+
// Build Google-format contents
|
|
60
|
+
const contents: GoogleContent[] = []
|
|
61
|
+
|
|
62
|
+
for (const m of nonSystemMessages) {
|
|
63
|
+
if (m.role === 'user') {
|
|
64
|
+
const parts: GoogleContent['parts'] = [{ text: m.content || '' }]
|
|
65
|
+
contents.push({ role: 'user', parts })
|
|
66
|
+
} else if (m.role === 'assistant') {
|
|
67
|
+
const parts: GoogleContent['parts'] = []
|
|
68
|
+
if (m.content) {
|
|
69
|
+
parts.push({ text: m.content })
|
|
70
|
+
}
|
|
71
|
+
if (m.tool_calls && m.tool_calls.length > 0) {
|
|
72
|
+
for (const tc of m.tool_calls) {
|
|
73
|
+
parts.push({
|
|
74
|
+
functionCall: {
|
|
75
|
+
name: tc.name,
|
|
76
|
+
args: tc.input as Record<string, unknown>,
|
|
77
|
+
},
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
contents.push({ role: 'model', parts })
|
|
82
|
+
} else if (m.role === 'tool') {
|
|
83
|
+
// Tool results are sent as functionResponse in a user message
|
|
84
|
+
contents.push({
|
|
85
|
+
role: 'user',
|
|
86
|
+
parts: [
|
|
87
|
+
{
|
|
88
|
+
functionResponse: {
|
|
89
|
+
name: m.name || '',
|
|
90
|
+
response: { content: m.content || '' },
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Build tools
|
|
99
|
+
const googleTools: GoogleTool[] | undefined =
|
|
100
|
+
tools.length > 0
|
|
101
|
+
? [
|
|
102
|
+
{
|
|
103
|
+
functionDeclarations: tools.map(t => {
|
|
104
|
+
const properties: Record<string, unknown> = {}
|
|
105
|
+
for (const [key, param] of Object.entries(t.parameters)) {
|
|
106
|
+
const { required: _, ...rest } = param
|
|
107
|
+
properties[key] = rest
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
name: t.name,
|
|
111
|
+
description: t.description,
|
|
112
|
+
parameters: {
|
|
113
|
+
type: 'object',
|
|
114
|
+
properties,
|
|
115
|
+
required: Object.entries(t.parameters)
|
|
116
|
+
.filter(([, v]) => v.required)
|
|
117
|
+
.map(([k]) => k),
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
}),
|
|
121
|
+
},
|
|
122
|
+
]
|
|
123
|
+
: undefined
|
|
124
|
+
|
|
125
|
+
// Build request body
|
|
126
|
+
const body: Record<string, unknown> = {
|
|
127
|
+
contents,
|
|
128
|
+
tools: googleTools,
|
|
129
|
+
}
|
|
130
|
+
if (systemInstruction) {
|
|
131
|
+
body.system_instruction = { parts: [{ text: systemInstruction }] }
|
|
132
|
+
}
|
|
133
|
+
// Make streaming API call
|
|
134
|
+
const url = `${this.baseUrl}/models/${this.model}:streamGenerateContent?alt=sse`
|
|
135
|
+
const response = await fetch(url, {
|
|
136
|
+
method: 'POST',
|
|
137
|
+
headers: {
|
|
138
|
+
'Content-Type': 'application/json',
|
|
139
|
+
'x-goog-api-key': this.apiKey,
|
|
140
|
+
},
|
|
141
|
+
signal,
|
|
142
|
+
body: JSON.stringify(body),
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
if (!response.ok) {
|
|
146
|
+
const errText = await response.text()
|
|
147
|
+
throw new Error(`Google API error (${response.status}): ${errText}`)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const reader = response.body?.getReader()
|
|
151
|
+
if (!reader) throw new Error('No response body')
|
|
152
|
+
|
|
153
|
+
const decoder = new TextDecoder()
|
|
154
|
+
let buffer = ''
|
|
155
|
+
let totalInput = 0
|
|
156
|
+
let totalOutput = 0
|
|
157
|
+
|
|
158
|
+
while (true) {
|
|
159
|
+
const { done, value } = await reader.read()
|
|
160
|
+
if (done) break
|
|
161
|
+
|
|
162
|
+
buffer += decoder.decode(value, { stream: true })
|
|
163
|
+
const lines = buffer.split('\n')
|
|
164
|
+
buffer = lines.pop() || ''
|
|
165
|
+
|
|
166
|
+
for (const line of lines) {
|
|
167
|
+
if (!line.startsWith('data: ')) continue
|
|
168
|
+
const jsonStr = line.slice(6).trim()
|
|
169
|
+
if (!jsonStr || jsonStr === '[DONE]') continue
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const chunk = JSON.parse(jsonStr) as GoogleStreamChunk
|
|
173
|
+
if (chunk.usageMetadata) {
|
|
174
|
+
totalInput = chunk.usageMetadata.promptTokenCount ?? 0
|
|
175
|
+
totalOutput = chunk.usageMetadata.candidatesTokenCount ?? 0
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (!chunk.candidates?.[0]) continue
|
|
179
|
+
const candidate = chunk.candidates[0]
|
|
180
|
+
const part = candidate.content?.parts?.[0]
|
|
181
|
+
|
|
182
|
+
if (part?.text) {
|
|
183
|
+
yield { type: 'text', text: part.text }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (part?.functionCall) {
|
|
187
|
+
yield {
|
|
188
|
+
type: 'tool_use',
|
|
189
|
+
tool_call: {
|
|
190
|
+
id: `fc-${part.functionCall.name}-${Date.now()}`,
|
|
191
|
+
name: part.functionCall.name,
|
|
192
|
+
input: part.functionCall.args,
|
|
193
|
+
},
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (candidate.finishReason) {
|
|
198
|
+
yield {
|
|
199
|
+
type: 'complete',
|
|
200
|
+
finish_reason: candidate.finishReason,
|
|
201
|
+
usage: { input_tokens: totalInput, output_tokens: totalOutput },
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
} catch {
|
|
205
|
+
console.error('[google] Failed to parse stream chunk:', jsonStr)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|