@dcode-dev/dcode-cli 1.0.0
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/NPM_README.md +78 -0
- package/README.md +341 -0
- package/bin/dcode-bin +0 -0
- package/bin/dcode.js +44 -0
- package/cmd/agent_v2.go +448 -0
- package/cmd/analyze.go +97 -0
- package/cmd/auth.go +338 -0
- package/cmd/compose.go +284 -0
- package/cmd/context.go +111 -0
- package/cmd/edit.go +116 -0
- package/cmd/env.go +10 -0
- package/cmd/fix.go +145 -0
- package/cmd/gemini.go +20 -0
- package/cmd/generate.go +47 -0
- package/cmd/interactive.go +33 -0
- package/cmd/mcp.go +196 -0
- package/cmd/patch.go +19 -0
- package/cmd/providers.go +67 -0
- package/cmd/root.go +41 -0
- package/cmd/search.go +61 -0
- package/cmd/server.go +36 -0
- package/cmd/switch.go +122 -0
- package/cmd/terminal.go +277 -0
- package/go.mod +42 -0
- package/go.sum +86 -0
- package/internal/agent/agent.go +332 -0
- package/internal/agent/parse.go +25 -0
- package/internal/agents/base.go +154 -0
- package/internal/agents/documenter.go +77 -0
- package/internal/agents/generalist.go +266 -0
- package/internal/agents/investigator.go +60 -0
- package/internal/agents/registry.go +34 -0
- package/internal/agents/reviewer.go +67 -0
- package/internal/agents/tester.go +73 -0
- package/internal/ai/client.go +634 -0
- package/internal/ai/tools.go +332 -0
- package/internal/auth/adc.go +108 -0
- package/internal/auth/apikey.go +67 -0
- package/internal/auth/factory.go +145 -0
- package/internal/auth/oauth2.go +227 -0
- package/internal/auth/store.go +216 -0
- package/internal/auth/types.go +79 -0
- package/internal/auth/vertex.go +138 -0
- package/internal/config/config.go +428 -0
- package/internal/config/policy.go +251 -0
- package/internal/context/builder.go +312 -0
- package/internal/detector/detector.go +204 -0
- package/internal/diffutil/diffutil.go +30 -0
- package/internal/fsutil/fsutil.go +35 -0
- package/internal/mcp/client.go +314 -0
- package/internal/mcp/manager.go +221 -0
- package/internal/policy/policy.go +89 -0
- package/internal/prompt/interactive.go +338 -0
- package/internal/registry/agent.go +201 -0
- package/internal/registry/tool.go +181 -0
- package/internal/scheduler/scheduler.go +250 -0
- package/internal/server/server.go +167 -0
- package/internal/tools/file.go +183 -0
- package/internal/tools/filesystem.go +286 -0
- package/internal/tools/git.go +355 -0
- package/internal/tools/memory.go +269 -0
- package/internal/tools/registry.go +49 -0
- package/internal/tools/search.go +230 -0
- package/internal/tools/shell.go +84 -0
- package/internal/websearch/search.go +40 -0
- package/internal/websearch/tavily.go +79 -0
- package/main.go +19 -0
- package/package.json +57 -0
- package/scripts/install.js +59 -0
- package/scripts/uninstall.js +28 -0
|
@@ -0,0 +1,634 @@
|
|
|
1
|
+
package ai
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"context"
|
|
6
|
+
"encoding/json"
|
|
7
|
+
"fmt"
|
|
8
|
+
"io"
|
|
9
|
+
"net/http"
|
|
10
|
+
"os"
|
|
11
|
+
"os/exec"
|
|
12
|
+
"strings"
|
|
13
|
+
"time"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
type Provider string
|
|
17
|
+
|
|
18
|
+
const (
|
|
19
|
+
ProviderOpenAI Provider = "openai"
|
|
20
|
+
ProviderGemini Provider = "gemini"
|
|
21
|
+
ProviderClaude Provider = "claude"
|
|
22
|
+
ProviderGitHub Provider = "github"
|
|
23
|
+
ProviderCopilot Provider = "copilot"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
type Message struct {
|
|
27
|
+
Role string `json:"role"`
|
|
28
|
+
Content string `json:"content"`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type ImageInput struct {
|
|
32
|
+
Mime string `json:"mime"`
|
|
33
|
+
DataBase64 string `json:"data_base64"` // may be raw base64 or data-url
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type Client struct {
|
|
37
|
+
Provider Provider
|
|
38
|
+
Model string
|
|
39
|
+
APIKey string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
func NewClient() (*Client, error) {
|
|
43
|
+
provider := os.Getenv("AI_PROVIDER")
|
|
44
|
+
if provider == "" {
|
|
45
|
+
provider = "openai" // Default to OpenAI
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
var apiKey string
|
|
49
|
+
var model string
|
|
50
|
+
|
|
51
|
+
switch Provider(provider) {
|
|
52
|
+
case ProviderOpenAI:
|
|
53
|
+
apiKey = os.Getenv("OPENAI_API_KEY")
|
|
54
|
+
model = os.Getenv("AI_MODEL")
|
|
55
|
+
if model == "" {
|
|
56
|
+
model = "gpt-5-mini" // Latest fast model
|
|
57
|
+
}
|
|
58
|
+
case ProviderGemini:
|
|
59
|
+
apiKey = os.Getenv("GEMINI_API_KEY")
|
|
60
|
+
model = os.Getenv("AI_MODEL")
|
|
61
|
+
if model == "" {
|
|
62
|
+
model = "gemini-3-flash-preview" // Latest fast model
|
|
63
|
+
}
|
|
64
|
+
case ProviderClaude:
|
|
65
|
+
apiKey = os.Getenv("CLAUDE_API_KEY")
|
|
66
|
+
model = os.Getenv("AI_MODEL")
|
|
67
|
+
if model == "" {
|
|
68
|
+
model = "claude-3-5-sonnet-20241022" // Latest model
|
|
69
|
+
}
|
|
70
|
+
case ProviderGitHub:
|
|
71
|
+
// Get GitHub token from gh CLI
|
|
72
|
+
apiKey = os.Getenv("GITHUB_TOKEN")
|
|
73
|
+
if apiKey == "" {
|
|
74
|
+
// Try to get token from gh CLI
|
|
75
|
+
apiKey = getGitHubToken()
|
|
76
|
+
}
|
|
77
|
+
model = os.Getenv("AI_MODEL")
|
|
78
|
+
if model == "" {
|
|
79
|
+
model = "gpt-4o" // Default GitHub Models model
|
|
80
|
+
}
|
|
81
|
+
case ProviderCopilot:
|
|
82
|
+
// Get Copilot token from config
|
|
83
|
+
apiKey = os.Getenv("COPILOT_TOKEN")
|
|
84
|
+
if apiKey == "" {
|
|
85
|
+
// Try to get token from Copilot CLI config
|
|
86
|
+
apiKey = GetCopilotToken()
|
|
87
|
+
}
|
|
88
|
+
model = os.Getenv("AI_MODEL")
|
|
89
|
+
if model == "" {
|
|
90
|
+
model = "claude-sonnet-4.5" // Default Copilot model
|
|
91
|
+
}
|
|
92
|
+
default:
|
|
93
|
+
return nil, fmt.Errorf("unsupported provider: %s", provider)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if apiKey == "" {
|
|
97
|
+
return nil, fmt.Errorf("API key not found for provider %s", provider)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return &Client{
|
|
101
|
+
Provider: Provider(provider),
|
|
102
|
+
Model: model,
|
|
103
|
+
APIKey: apiKey,
|
|
104
|
+
}, nil
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
func (c *Client) Chat(messages []Message) (string, error) {
|
|
108
|
+
switch c.Provider {
|
|
109
|
+
case ProviderOpenAI:
|
|
110
|
+
return c.chatOpenAI(context.Background(), messages)
|
|
111
|
+
case ProviderGemini:
|
|
112
|
+
return c.chatGemini(messages)
|
|
113
|
+
case ProviderClaude:
|
|
114
|
+
return c.chatClaude(messages)
|
|
115
|
+
default:
|
|
116
|
+
return "", fmt.Errorf("unsupported provider: %s", c.Provider)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ChatWithContext is the preferred entrypoint for new code (supports context).
|
|
121
|
+
func (c *Client) ChatWithContext(ctx context.Context, messages []Message) (string, error) {
|
|
122
|
+
switch c.Provider {
|
|
123
|
+
case ProviderOpenAI:
|
|
124
|
+
return c.chatOpenAI(ctx, messages)
|
|
125
|
+
case ProviderGemini:
|
|
126
|
+
return c.chatGemini(messages)
|
|
127
|
+
case ProviderClaude:
|
|
128
|
+
return c.chatClaude(messages)
|
|
129
|
+
case ProviderGitHub:
|
|
130
|
+
return c.chatGitHub(ctx, messages)
|
|
131
|
+
case ProviderCopilot:
|
|
132
|
+
return c.chatCopilot(ctx, messages)
|
|
133
|
+
default:
|
|
134
|
+
return "", fmt.Errorf("unsupported provider: %s", c.Provider)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ChatWithImages supports OpenAI multimodal chat by attaching images to the last user message.
|
|
139
|
+
func (c *Client) ChatWithImages(ctx context.Context, messages []Message, images []ImageInput) (string, error) {
|
|
140
|
+
if c.Provider != ProviderOpenAI {
|
|
141
|
+
// other providers not yet wired for multimodal; fallback to text.
|
|
142
|
+
return c.ChatWithContext(ctx, messages)
|
|
143
|
+
}
|
|
144
|
+
return c.chatOpenAIWithImages(ctx, messages, images)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
func (c *Client) chatOpenAI(ctx context.Context, messages []Message) (string, error) {
|
|
148
|
+
type Request struct {
|
|
149
|
+
Model string `json:"model"`
|
|
150
|
+
Messages []Message `json:"messages"`
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
type Response struct {
|
|
154
|
+
Choices []struct {
|
|
155
|
+
Message Message `json:"message"`
|
|
156
|
+
} `json:"choices"`
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
reqBody := Request{
|
|
160
|
+
Model: c.Model,
|
|
161
|
+
Messages: messages,
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
jsonData, err := json.Marshal(reqBody)
|
|
165
|
+
if err != nil {
|
|
166
|
+
return "", err
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
req, err := http.NewRequestWithContext(ctx, "POST", "https://api.openai.com/v1/chat/completions", bytes.NewBuffer(jsonData))
|
|
170
|
+
if err != nil {
|
|
171
|
+
return "", err
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
req.Header.Set("Content-Type", "application/json")
|
|
175
|
+
req.Header.Set("Authorization", "Bearer "+c.APIKey)
|
|
176
|
+
|
|
177
|
+
client := &http.Client{Timeout: 60 * time.Second}
|
|
178
|
+
resp, err := client.Do(req)
|
|
179
|
+
if err != nil {
|
|
180
|
+
return "", err
|
|
181
|
+
}
|
|
182
|
+
defer resp.Body.Close()
|
|
183
|
+
|
|
184
|
+
body, err := io.ReadAll(resp.Body)
|
|
185
|
+
if err != nil {
|
|
186
|
+
return "", err
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if resp.StatusCode != http.StatusOK {
|
|
190
|
+
return "", fmt.Errorf("API error (%d): %s", resp.StatusCode, string(body))
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
var response Response
|
|
194
|
+
if err := json.Unmarshal(body, &response); err != nil {
|
|
195
|
+
return "", err
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if len(response.Choices) == 0 {
|
|
199
|
+
return "", fmt.Errorf("no response from API")
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return response.Choices[0].Message.Content, nil
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
func (c *Client) chatOpenAIWithImages(ctx context.Context, messages []Message, images []ImageInput) (string, error) {
|
|
206
|
+
// Use chat.completions multimodal format: message.content as array.
|
|
207
|
+
// We attach images to the last user message (or create a new one if none).
|
|
208
|
+
type imageURL struct {
|
|
209
|
+
URL string `json:"url"`
|
|
210
|
+
}
|
|
211
|
+
type contentPart struct {
|
|
212
|
+
Type string `json:"type"`
|
|
213
|
+
Text string `json:"text,omitempty"`
|
|
214
|
+
ImageURL *imageURL `json:"image_url,omitempty"`
|
|
215
|
+
}
|
|
216
|
+
type mmMessage struct {
|
|
217
|
+
Role string `json:"role"`
|
|
218
|
+
Content []contentPart `json:"content"`
|
|
219
|
+
}
|
|
220
|
+
type request struct {
|
|
221
|
+
Model string `json:"model"`
|
|
222
|
+
Messages []mmMessage `json:"messages"`
|
|
223
|
+
}
|
|
224
|
+
type response struct {
|
|
225
|
+
Choices []struct {
|
|
226
|
+
Message struct {
|
|
227
|
+
Content string `json:"content"`
|
|
228
|
+
} `json:"message"`
|
|
229
|
+
} `json:"choices"`
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
mm := make([]mmMessage, 0, len(messages))
|
|
233
|
+
for _, m := range messages {
|
|
234
|
+
mm = append(mm, mmMessage{Role: m.Role, Content: []contentPart{{Type: "text", Text: m.Content}}})
|
|
235
|
+
}
|
|
236
|
+
idx := -1
|
|
237
|
+
for i := len(mm) - 1; i >= 0; i-- {
|
|
238
|
+
if mm[i].Role == "user" {
|
|
239
|
+
idx = i
|
|
240
|
+
break
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if idx == -1 {
|
|
244
|
+
mm = append(mm, mmMessage{Role: "user", Content: []contentPart{{Type: "text", Text: "(image attached)"}}})
|
|
245
|
+
idx = len(mm) - 1
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
for _, img := range images {
|
|
249
|
+
mime := img.Mime
|
|
250
|
+
if mime == "" {
|
|
251
|
+
mime = "image/png"
|
|
252
|
+
}
|
|
253
|
+
url := img.DataBase64
|
|
254
|
+
// Allow raw base64 (convert to data-url)
|
|
255
|
+
if !strings.HasPrefix(url, "data:") {
|
|
256
|
+
url = fmt.Sprintf("data:%s;base64,%s", mime, url)
|
|
257
|
+
}
|
|
258
|
+
mm[idx].Content = append(mm[idx].Content, contentPart{Type: "image_url", ImageURL: &imageURL{URL: url}})
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
reqBody := request{Model: c.Model, Messages: mm}
|
|
262
|
+
jsonData, err := json.Marshal(reqBody)
|
|
263
|
+
if err != nil {
|
|
264
|
+
return "", err
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
req, err := http.NewRequestWithContext(ctx, "POST", "https://api.openai.com/v1/chat/completions", bytes.NewBuffer(jsonData))
|
|
268
|
+
if err != nil {
|
|
269
|
+
return "", err
|
|
270
|
+
}
|
|
271
|
+
req.Header.Set("Content-Type", "application/json")
|
|
272
|
+
req.Header.Set("Authorization", "Bearer "+c.APIKey)
|
|
273
|
+
|
|
274
|
+
client := &http.Client{Timeout: 120 * time.Second}
|
|
275
|
+
resp, err := client.Do(req)
|
|
276
|
+
if err != nil {
|
|
277
|
+
return "", err
|
|
278
|
+
}
|
|
279
|
+
defer resp.Body.Close()
|
|
280
|
+
|
|
281
|
+
body, err := io.ReadAll(resp.Body)
|
|
282
|
+
if err != nil {
|
|
283
|
+
return "", err
|
|
284
|
+
}
|
|
285
|
+
if resp.StatusCode != http.StatusOK {
|
|
286
|
+
return "", fmt.Errorf("API error (%d): %s", resp.StatusCode, string(body))
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
var decoded response
|
|
290
|
+
if err := json.Unmarshal(body, &decoded); err != nil {
|
|
291
|
+
return "", err
|
|
292
|
+
}
|
|
293
|
+
if len(decoded.Choices) == 0 {
|
|
294
|
+
return "", fmt.Errorf("no response from API")
|
|
295
|
+
}
|
|
296
|
+
return decoded.Choices[0].Message.Content, nil
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
func (c *Client) chatGemini(messages []Message) (string, error) {
|
|
300
|
+
type Content struct {
|
|
301
|
+
Parts []struct {
|
|
302
|
+
Text string `json:"text"`
|
|
303
|
+
} `json:"parts"`
|
|
304
|
+
Role string `json:"role,omitempty"`
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
type Request struct {
|
|
308
|
+
Contents []Content `json:"contents"`
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
type Response struct {
|
|
312
|
+
Candidates []struct {
|
|
313
|
+
Content Content `json:"content"`
|
|
314
|
+
} `json:"candidates"`
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Convert messages to Gemini format
|
|
318
|
+
var contents []Content
|
|
319
|
+
for _, msg := range messages {
|
|
320
|
+
role := msg.Role
|
|
321
|
+
if role == "assistant" {
|
|
322
|
+
role = "model"
|
|
323
|
+
}
|
|
324
|
+
contents = append(contents, Content{
|
|
325
|
+
Parts: []struct {
|
|
326
|
+
Text string `json:"text"`
|
|
327
|
+
}{{Text: msg.Content}},
|
|
328
|
+
Role: role,
|
|
329
|
+
})
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
reqBody := Request{
|
|
333
|
+
Contents: contents,
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
jsonData, err := json.Marshal(reqBody)
|
|
337
|
+
if err != nil {
|
|
338
|
+
return "", err
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
url := fmt.Sprintf("https://generativelanguage.googleapis.com/v1beta/models/%s:generateContent?key=%s", c.Model, c.APIKey)
|
|
342
|
+
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
|
343
|
+
if err != nil {
|
|
344
|
+
return "", err
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
req.Header.Set("Content-Type", "application/json")
|
|
348
|
+
|
|
349
|
+
client := &http.Client{Timeout: 60 * time.Second}
|
|
350
|
+
resp, err := client.Do(req)
|
|
351
|
+
if err != nil {
|
|
352
|
+
return "", err
|
|
353
|
+
}
|
|
354
|
+
defer resp.Body.Close()
|
|
355
|
+
|
|
356
|
+
body, err := io.ReadAll(resp.Body)
|
|
357
|
+
if err != nil {
|
|
358
|
+
return "", err
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if resp.StatusCode != http.StatusOK {
|
|
362
|
+
return "", fmt.Errorf("API error (%d): %s", resp.StatusCode, string(body))
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
var response Response
|
|
366
|
+
if err := json.Unmarshal(body, &response); err != nil {
|
|
367
|
+
return "", err
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if len(response.Candidates) == 0 || len(response.Candidates[0].Content.Parts) == 0 {
|
|
371
|
+
return "", fmt.Errorf("no response from API")
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return response.Candidates[0].Content.Parts[0].Text, nil
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
func (c *Client) chatClaude(messages []Message) (string, error) {
|
|
378
|
+
type Request struct {
|
|
379
|
+
Model string `json:"model"`
|
|
380
|
+
MaxTokens int `json:"max_tokens"`
|
|
381
|
+
Messages []Message `json:"messages"`
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
type Response struct {
|
|
385
|
+
Content []struct {
|
|
386
|
+
Text string `json:"text"`
|
|
387
|
+
} `json:"content"`
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
reqBody := Request{
|
|
391
|
+
Model: c.Model,
|
|
392
|
+
MaxTokens: 4096,
|
|
393
|
+
Messages: messages,
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
jsonData, err := json.Marshal(reqBody)
|
|
397
|
+
if err != nil {
|
|
398
|
+
return "", err
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
req, err := http.NewRequest("POST", "https://api.anthropic.com/v1/messages", bytes.NewBuffer(jsonData))
|
|
402
|
+
if err != nil {
|
|
403
|
+
return "", err
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
req.Header.Set("Content-Type", "application/json")
|
|
407
|
+
req.Header.Set("x-api-key", c.APIKey)
|
|
408
|
+
req.Header.Set("anthropic-version", "2023-06-01")
|
|
409
|
+
|
|
410
|
+
client := &http.Client{Timeout: 60 * time.Second}
|
|
411
|
+
resp, err := client.Do(req)
|
|
412
|
+
if err != nil {
|
|
413
|
+
return "", err
|
|
414
|
+
}
|
|
415
|
+
defer resp.Body.Close()
|
|
416
|
+
|
|
417
|
+
body, err := io.ReadAll(resp.Body)
|
|
418
|
+
if err != nil {
|
|
419
|
+
return "", err
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if resp.StatusCode != http.StatusOK {
|
|
423
|
+
return "", fmt.Errorf("API error (%d): %s", resp.StatusCode, string(body))
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
var response Response
|
|
427
|
+
if err := json.Unmarshal(body, &response); err != nil {
|
|
428
|
+
return "", err
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if len(response.Content) == 0 {
|
|
432
|
+
return "", fmt.Errorf("no response from API")
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return response.Content[0].Text, nil
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// GenerateText is a simple text generation method for agents
|
|
439
|
+
func (c *Client) GenerateText(ctx context.Context, prompt string) (string, error) {
|
|
440
|
+
messages := []Message{
|
|
441
|
+
{
|
|
442
|
+
Role: "user",
|
|
443
|
+
Content: prompt,
|
|
444
|
+
},
|
|
445
|
+
}
|
|
446
|
+
return c.ChatWithContext(ctx, messages)
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// getGitHubToken retrieves the GitHub token from gh CLI
|
|
450
|
+
func getGitHubToken() string {
|
|
451
|
+
cmd := exec.Command("gh", "auth", "token")
|
|
452
|
+
output, err := cmd.Output()
|
|
453
|
+
if err != nil {
|
|
454
|
+
return ""
|
|
455
|
+
}
|
|
456
|
+
return strings.TrimSpace(string(output))
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// chatGitHub calls GitHub Models API
|
|
460
|
+
func (c *Client) chatGitHub(ctx context.Context, messages []Message) (string, error) {
|
|
461
|
+
type Request struct {
|
|
462
|
+
Model string `json:"model"`
|
|
463
|
+
Messages []Message `json:"messages"`
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
type Response struct {
|
|
467
|
+
Choices []struct {
|
|
468
|
+
Message Message `json:"message"`
|
|
469
|
+
} `json:"choices"`
|
|
470
|
+
Error *struct {
|
|
471
|
+
Message string `json:"message"`
|
|
472
|
+
Type string `json:"type"`
|
|
473
|
+
} `json:"error,omitempty"`
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
reqBody := Request{
|
|
477
|
+
Model: c.Model,
|
|
478
|
+
Messages: messages,
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
jsonData, err := json.Marshal(reqBody)
|
|
482
|
+
if err != nil {
|
|
483
|
+
return "", err
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// GitHub Models API endpoint
|
|
487
|
+
apiURL := "https://models.inference.ai.azure.com/chat/completions"
|
|
488
|
+
|
|
489
|
+
req, err := http.NewRequestWithContext(ctx, "POST", apiURL, bytes.NewBuffer(jsonData))
|
|
490
|
+
if err != nil {
|
|
491
|
+
return "", err
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
req.Header.Set("Content-Type", "application/json")
|
|
495
|
+
req.Header.Set("Authorization", "Bearer "+c.APIKey)
|
|
496
|
+
|
|
497
|
+
client := &http.Client{Timeout: 120 * time.Second}
|
|
498
|
+
resp, err := client.Do(req)
|
|
499
|
+
if err != nil {
|
|
500
|
+
return "", fmt.Errorf("GitHub API request failed: %w", err)
|
|
501
|
+
}
|
|
502
|
+
defer resp.Body.Close()
|
|
503
|
+
|
|
504
|
+
body, err := io.ReadAll(resp.Body)
|
|
505
|
+
if err != nil {
|
|
506
|
+
return "", fmt.Errorf("failed to read GitHub API response: %w", err)
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if resp.StatusCode != http.StatusOK {
|
|
510
|
+
return "", fmt.Errorf("GitHub API error (%d): %s", resp.StatusCode, string(body))
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
var response Response
|
|
514
|
+
if err := json.Unmarshal(body, &response); err != nil {
|
|
515
|
+
return "", fmt.Errorf("failed to parse GitHub API response: %w", err)
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if response.Error != nil {
|
|
519
|
+
return "", fmt.Errorf("GitHub API error: %s", response.Error.Message)
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if len(response.Choices) == 0 {
|
|
523
|
+
return "", fmt.Errorf("no response from GitHub API")
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return response.Choices[0].Message.Content, nil
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// GetCopilotToken retrieves the Copilot token from ~/.copilot/config.json
|
|
530
|
+
func GetCopilotToken() string {
|
|
531
|
+
homeDir, err := os.UserHomeDir()
|
|
532
|
+
if err != nil {
|
|
533
|
+
return ""
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
configPath := homeDir + "/.copilot/config.json"
|
|
537
|
+
data, err := os.ReadFile(configPath)
|
|
538
|
+
if err != nil {
|
|
539
|
+
return ""
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
var config map[string]interface{}
|
|
543
|
+
if err := json.Unmarshal(data, &config); err != nil {
|
|
544
|
+
return ""
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
copilotTokens, ok := config["copilot_tokens"].(map[string]interface{})
|
|
548
|
+
if !ok {
|
|
549
|
+
return ""
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Get token for github.com
|
|
553
|
+
for key, value := range copilotTokens {
|
|
554
|
+
if strings.Contains(key, "github.com") {
|
|
555
|
+
if token, ok := value.(string); ok {
|
|
556
|
+
return token
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return ""
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// chatCopilot calls GitHub Copilot Premium API
|
|
565
|
+
func (c *Client) chatCopilot(ctx context.Context, messages []Message) (string, error) {
|
|
566
|
+
type Request struct {
|
|
567
|
+
Model string `json:"model"`
|
|
568
|
+
Messages []Message `json:"messages"`
|
|
569
|
+
Stream bool `json:"stream"`
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
type Response struct {
|
|
573
|
+
Choices []struct {
|
|
574
|
+
Message Message `json:"message"`
|
|
575
|
+
} `json:"choices"`
|
|
576
|
+
Error *struct {
|
|
577
|
+
Message string `json:"message"`
|
|
578
|
+
Type string `json:"type"`
|
|
579
|
+
} `json:"error,omitempty"`
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
reqBody := Request{
|
|
583
|
+
Model: c.Model,
|
|
584
|
+
Messages: messages,
|
|
585
|
+
Stream: false,
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
jsonData, err := json.Marshal(reqBody)
|
|
589
|
+
if err != nil {
|
|
590
|
+
return "", err
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// GitHub Copilot Premium API endpoint
|
|
594
|
+
apiURL := "https://api.githubcopilot.com/chat/completions"
|
|
595
|
+
|
|
596
|
+
req, err := http.NewRequestWithContext(ctx, "POST", apiURL, bytes.NewBuffer(jsonData))
|
|
597
|
+
if err != nil {
|
|
598
|
+
return "", err
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
req.Header.Set("Content-Type", "application/json")
|
|
602
|
+
req.Header.Set("Authorization", "Bearer "+c.APIKey)
|
|
603
|
+
|
|
604
|
+
client := &http.Client{Timeout: 120 * time.Second}
|
|
605
|
+
resp, err := client.Do(req)
|
|
606
|
+
if err != nil {
|
|
607
|
+
return "", fmt.Errorf("Copilot API request failed: %w", err)
|
|
608
|
+
}
|
|
609
|
+
defer resp.Body.Close()
|
|
610
|
+
|
|
611
|
+
body, err := io.ReadAll(resp.Body)
|
|
612
|
+
if err != nil {
|
|
613
|
+
return "", fmt.Errorf("failed to read Copilot API response: %w", err)
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if resp.StatusCode != http.StatusOK {
|
|
617
|
+
return "", fmt.Errorf("Copilot API error (%d): %s", resp.StatusCode, string(body))
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
var response Response
|
|
621
|
+
if err := json.Unmarshal(body, &response); err != nil {
|
|
622
|
+
return "", fmt.Errorf("failed to parse Copilot API response: %w", err)
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if response.Error != nil {
|
|
626
|
+
return "", fmt.Errorf("Copilot API error: %s", response.Error.Message)
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if len(response.Choices) == 0 {
|
|
630
|
+
return "", fmt.Errorf("no response from Copilot API")
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
return response.Choices[0].Message.Content, nil
|
|
634
|
+
}
|