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.
Files changed (102) hide show
  1. package/README.md +326 -326
  2. package/dist/agent/llm.d.ts +4 -0
  3. package/dist/agent/llm.d.ts.map +1 -1
  4. package/dist/agent/prompt-builder.d.ts.map +1 -1
  5. package/dist/agent/prompt-builder.js +13 -9
  6. package/dist/agent/prompt-builder.js.map +1 -1
  7. package/dist/agent/providers/google.d.ts.map +1 -1
  8. package/dist/agent/providers/google.js.map +1 -1
  9. package/dist/agent/providers/openai.d.ts.map +1 -1
  10. package/dist/agent/providers/openai.js +43 -3
  11. package/dist/agent/providers/openai.js.map +1 -1
  12. package/dist/agent/session.d.ts +4 -0
  13. package/dist/agent/session.d.ts.map +1 -1
  14. package/dist/agent/session.js +45 -7
  15. package/dist/agent/session.js.map +1 -1
  16. package/dist/config/index.d.ts.map +1 -1
  17. package/dist/config/index.js +7 -1
  18. package/dist/config/index.js.map +1 -1
  19. package/dist/tools/__tests__/bash.test.js +1 -1
  20. package/dist/tools/__tests__/bash.test.js.map +1 -1
  21. package/dist/tools/__tests__/edit.test.js +875 -371
  22. package/dist/tools/__tests__/edit.test.js.map +1 -1
  23. package/dist/tools/__tests__/grep.test.js +32 -0
  24. package/dist/tools/__tests__/grep.test.js.map +1 -1
  25. package/dist/tools/__tests__/sed.test.d.ts +2 -0
  26. package/dist/tools/__tests__/sed.test.d.ts.map +1 -0
  27. package/dist/tools/__tests__/sed.test.js +228 -0
  28. package/dist/tools/__tests__/sed.test.js.map +1 -0
  29. package/dist/tools/bash.d.ts.map +1 -1
  30. package/dist/tools/bash.js +83 -10
  31. package/dist/tools/bash.js.map +1 -1
  32. package/dist/tools/edit.d.ts +50 -9
  33. package/dist/tools/edit.d.ts.map +1 -1
  34. package/dist/tools/edit.js +328 -158
  35. package/dist/tools/edit.js.map +1 -1
  36. package/dist/tools/grep.d.ts.map +1 -1
  37. package/dist/tools/grep.js +54 -30
  38. package/dist/tools/grep.js.map +1 -1
  39. package/dist/tools/registry.d.ts +0 -6
  40. package/dist/tools/registry.d.ts.map +1 -1
  41. package/dist/tools/registry.js +2 -29
  42. package/dist/tools/registry.js.map +1 -1
  43. package/dist/tools/sed.d.ts +4 -0
  44. package/dist/tools/sed.d.ts.map +1 -0
  45. package/dist/tools/sed.js +121 -0
  46. package/dist/tools/sed.js.map +1 -0
  47. package/dist/web/index.d.ts.map +1 -1
  48. package/dist/web/index.js +26 -3
  49. package/dist/web/index.js.map +1 -1
  50. package/dist/web/public/app.js +5 -855
  51. package/dist/web/public/confirm.js +70 -0
  52. package/dist/web/public/index.html +121 -120
  53. package/dist/web/public/input.js +107 -0
  54. package/dist/web/public/messages.js +395 -0
  55. package/dist/web/public/sidebar.js +82 -0
  56. package/dist/web/public/state.js +72 -0
  57. package/dist/web/public/style.css +996 -949
  58. package/dist/web/public/utils.js +37 -0
  59. package/dist/web/public/websocket.js +267 -0
  60. package/dist/web/public/ws.js +19 -0
  61. package/dist/web/session-bridge.d.ts.map +1 -1
  62. package/dist/web/session-bridge.js +3 -7
  63. package/dist/web/session-bridge.js.map +1 -1
  64. package/package.json +55 -54
  65. package/scripts/copy-native.mjs +24 -0
  66. package/scripts/copy-web.mjs +15 -0
  67. package/src/agent/index.ts +25 -25
  68. package/src/agent/llm.ts +4 -0
  69. package/src/agent/prompt-builder.ts +13 -9
  70. package/src/agent/providers/anthropic.ts +179 -179
  71. package/src/agent/providers/google.ts +210 -211
  72. package/src/agent/providers/ollama.ts +186 -186
  73. package/src/agent/providers/openai.ts +61 -5
  74. package/src/agent/session.ts +59 -3
  75. package/src/config/index.ts +342 -335
  76. package/src/tools/__tests__/bash.test.ts +1 -1
  77. package/src/tools/__tests__/edit.test.ts +981 -385
  78. package/src/tools/__tests__/grep.test.ts +35 -0
  79. package/src/tools/bash.ts +97 -11
  80. package/src/tools/edit.ts +362 -169
  81. package/src/tools/git.ts +76 -76
  82. package/src/tools/grep.ts +57 -30
  83. package/src/tools/registry.ts +2 -31
  84. package/src/tui/index.ts +786 -786
  85. package/src/web/index.ts +25 -3
  86. package/src/web/public/app.js +5 -855
  87. package/src/web/public/confirm.js +70 -0
  88. package/src/web/public/index.html +121 -120
  89. package/src/web/public/input.js +107 -0
  90. package/src/web/public/messages.js +395 -0
  91. package/src/web/public/sidebar.js +82 -0
  92. package/src/web/public/state.js +72 -0
  93. package/src/web/public/style.css +996 -949
  94. package/src/web/public/utils.js +37 -0
  95. package/src/web/public/websocket.js +267 -0
  96. package/src/web/public/ws.js +19 -0
  97. package/src/web/session-bridge.ts +193 -194
  98. package/.claude/settings.local.json +0 -14
  99. package/.kilo/plans/1780064105789-mighty-pixel.md +0 -171
  100. package/.kilo/plans/1780293725888-quick-wizard.md +0 -62
  101. package/.lonny/plan-web-cwd-status.md +0 -38
  102. 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
- // Make streaming API call
135
- const url = `${this.baseUrl}/models/${this.model}:streamGenerateContent?alt=sse`
136
- const response = await fetch(url, {
137
- method: 'POST',
138
- headers: {
139
- 'Content-Type': 'application/json',
140
- 'x-goog-api-key': this.apiKey,
141
- },
142
- signal,
143
- body: JSON.stringify(body),
144
- })
145
-
146
- if (!response.ok) {
147
- const errText = await response.text()
148
- throw new Error(`Google API error (${response.status}): ${errText}`)
149
- }
150
-
151
- const reader = response.body?.getReader()
152
- if (!reader) throw new Error('No response body')
153
-
154
- const decoder = new TextDecoder()
155
- let buffer = ''
156
- let totalInput = 0
157
- let totalOutput = 0
158
-
159
- while (true) {
160
- const { done, value } = await reader.read()
161
- if (done) break
162
-
163
- buffer += decoder.decode(value, { stream: true })
164
- const lines = buffer.split('\n')
165
- buffer = lines.pop() || ''
166
-
167
- for (const line of lines) {
168
- if (!line.startsWith('data: ')) continue
169
- const jsonStr = line.slice(6).trim()
170
- if (!jsonStr || jsonStr === '[DONE]') continue
171
-
172
- try {
173
- const chunk = JSON.parse(jsonStr) as GoogleStreamChunk
174
- if (chunk.usageMetadata) {
175
- totalInput = chunk.usageMetadata.promptTokenCount ?? 0
176
- totalOutput = chunk.usageMetadata.candidatesTokenCount ?? 0
177
- }
178
-
179
- if (!chunk.candidates?.[0]) continue
180
- const candidate = chunk.candidates[0]
181
- const part = candidate.content?.parts?.[0]
182
-
183
- if (part?.text) {
184
- yield { type: 'text', text: part.text }
185
- }
186
-
187
- if (part?.functionCall) {
188
- yield {
189
- type: 'tool_use',
190
- tool_call: {
191
- id: `fc-${part.functionCall.name}-${Date.now()}`,
192
- name: part.functionCall.name,
193
- input: part.functionCall.args,
194
- },
195
- }
196
- }
197
-
198
- if (candidate.finishReason) {
199
- yield {
200
- type: 'complete',
201
- finish_reason: candidate.finishReason,
202
- usage: { input_tokens: totalInput, output_tokens: totalOutput },
203
- }
204
- }
205
- } catch {
206
- console.error('[google] Failed to parse stream chunk:', jsonStr)
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
+ }