@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.
Files changed (70) hide show
  1. package/NPM_README.md +78 -0
  2. package/README.md +341 -0
  3. package/bin/dcode-bin +0 -0
  4. package/bin/dcode.js +44 -0
  5. package/cmd/agent_v2.go +448 -0
  6. package/cmd/analyze.go +97 -0
  7. package/cmd/auth.go +338 -0
  8. package/cmd/compose.go +284 -0
  9. package/cmd/context.go +111 -0
  10. package/cmd/edit.go +116 -0
  11. package/cmd/env.go +10 -0
  12. package/cmd/fix.go +145 -0
  13. package/cmd/gemini.go +20 -0
  14. package/cmd/generate.go +47 -0
  15. package/cmd/interactive.go +33 -0
  16. package/cmd/mcp.go +196 -0
  17. package/cmd/patch.go +19 -0
  18. package/cmd/providers.go +67 -0
  19. package/cmd/root.go +41 -0
  20. package/cmd/search.go +61 -0
  21. package/cmd/server.go +36 -0
  22. package/cmd/switch.go +122 -0
  23. package/cmd/terminal.go +277 -0
  24. package/go.mod +42 -0
  25. package/go.sum +86 -0
  26. package/internal/agent/agent.go +332 -0
  27. package/internal/agent/parse.go +25 -0
  28. package/internal/agents/base.go +154 -0
  29. package/internal/agents/documenter.go +77 -0
  30. package/internal/agents/generalist.go +266 -0
  31. package/internal/agents/investigator.go +60 -0
  32. package/internal/agents/registry.go +34 -0
  33. package/internal/agents/reviewer.go +67 -0
  34. package/internal/agents/tester.go +73 -0
  35. package/internal/ai/client.go +634 -0
  36. package/internal/ai/tools.go +332 -0
  37. package/internal/auth/adc.go +108 -0
  38. package/internal/auth/apikey.go +67 -0
  39. package/internal/auth/factory.go +145 -0
  40. package/internal/auth/oauth2.go +227 -0
  41. package/internal/auth/store.go +216 -0
  42. package/internal/auth/types.go +79 -0
  43. package/internal/auth/vertex.go +138 -0
  44. package/internal/config/config.go +428 -0
  45. package/internal/config/policy.go +251 -0
  46. package/internal/context/builder.go +312 -0
  47. package/internal/detector/detector.go +204 -0
  48. package/internal/diffutil/diffutil.go +30 -0
  49. package/internal/fsutil/fsutil.go +35 -0
  50. package/internal/mcp/client.go +314 -0
  51. package/internal/mcp/manager.go +221 -0
  52. package/internal/policy/policy.go +89 -0
  53. package/internal/prompt/interactive.go +338 -0
  54. package/internal/registry/agent.go +201 -0
  55. package/internal/registry/tool.go +181 -0
  56. package/internal/scheduler/scheduler.go +250 -0
  57. package/internal/server/server.go +167 -0
  58. package/internal/tools/file.go +183 -0
  59. package/internal/tools/filesystem.go +286 -0
  60. package/internal/tools/git.go +355 -0
  61. package/internal/tools/memory.go +269 -0
  62. package/internal/tools/registry.go +49 -0
  63. package/internal/tools/search.go +230 -0
  64. package/internal/tools/shell.go +84 -0
  65. package/internal/websearch/search.go +40 -0
  66. package/internal/websearch/tavily.go +79 -0
  67. package/main.go +19 -0
  68. package/package.json +57 -0
  69. package/scripts/install.js +59 -0
  70. package/scripts/uninstall.js +28 -0
package/go.sum ADDED
@@ -0,0 +1,86 @@
1
+ al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho=
2
+ al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=
3
+ cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
4
+ cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
5
+ github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
6
+ github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
7
+ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
8
+ github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
9
+ github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
10
+ github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
11
+ github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
12
+ github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
13
+ github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
14
+ github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
15
+ github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
16
+ github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
17
+ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
18
+ github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0=
19
+ github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8=
20
+ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
21
+ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
22
+ github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
23
+ github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
24
+ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
25
+ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
26
+ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
27
+ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
28
+ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
29
+ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
30
+ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
31
+ github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
32
+ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
33
+ github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
34
+ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
35
+ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
36
+ github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
37
+ github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
38
+ github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
39
+ github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
40
+ github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
41
+ github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
42
+ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
43
+ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
44
+ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
45
+ github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
46
+ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
47
+ github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
48
+ github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
49
+ github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
50
+ github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
51
+ github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
52
+ github.com/peterh/liner v1.2.2 h1:aJ4AOodmL+JxOZZEL2u9iJf8omNRpqHc/EbrK+3mAXw=
53
+ github.com/peterh/liner v1.2.2/go.mod h1:xFwJyiKIXJZUKItq5dGHZSTBRAuG/CpeNpWLyiNRNwI=
54
+ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
55
+ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
56
+ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
57
+ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
58
+ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
59
+ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
60
+ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
61
+ github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
62
+ github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
63
+ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
64
+ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
65
+ github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
66
+ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
67
+ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
68
+ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
69
+ github.com/zalando/go-keyring v0.2.6 h1:r7Yc3+H+Ux0+M72zacZoItR3UDxeWfKTcabvkI8ua9s=
70
+ github.com/zalando/go-keyring v0.2.6/go.mod h1:2TCrxYrbUNYfNS/Kgy/LSrkSQzZ5UPVH85RwfczwvcI=
71
+ golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
72
+ golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
73
+ golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
74
+ golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
75
+ golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
76
+ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
77
+ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
78
+ golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
79
+ golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
80
+ golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
81
+ golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
82
+ golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
83
+ golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
84
+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
85
+ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
86
+ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -0,0 +1,332 @@
1
+ package agent
2
+
3
+ import (
4
+ "context"
5
+ "encoding/base64"
6
+ "fmt"
7
+ "os"
8
+ "path/filepath"
9
+ "strings"
10
+
11
+ "github.com/ddhanush1/dcode/internal/ai"
12
+ "github.com/ddhanush1/dcode/internal/websearch"
13
+ )
14
+
15
+ type Attachment struct {
16
+ Type string `json:"type"` // "image" supported
17
+ Mime string `json:"mime,omitempty"`
18
+ DataBase64 string `json:"data_base64,omitempty"`
19
+ }
20
+
21
+ type WebSearchRequest struct {
22
+ Enabled bool `json:"enabled"`
23
+ Query string `json:"query,omitempty"`
24
+ }
25
+
26
+ type ChatRequest struct {
27
+ Messages []ai.Message `json:"messages"`
28
+ Attach []Attachment `json:"attachments,omitempty"`
29
+ WebSearch WebSearchRequest `json:"web_search,omitempty"`
30
+ ProjectDir string `json:"project_dir,omitempty"` // optional, for future
31
+ Metadata map[string]string `json:"metadata,omitempty"`
32
+ }
33
+
34
+ type WebResult struct {
35
+ Title string `json:"title"`
36
+ URL string `json:"url"`
37
+ Snippet string `json:"snippet"`
38
+ }
39
+
40
+ type ChatResponse struct {
41
+ Assistant string `json:"assistant"`
42
+ WebResults []WebResult `json:"web_results,omitempty"`
43
+ }
44
+
45
+ type FileInput struct {
46
+ Path string `json:"path"`
47
+ Content string `json:"content"`
48
+ }
49
+
50
+ type EditRequest struct {
51
+ Prompt string `json:"prompt"`
52
+ Files []FileInput `json:"files"`
53
+ WebSearch WebSearchRequest `json:"web_search,omitempty"`
54
+ Instructions string `json:"instructions,omitempty"` // optional extra system guidance
55
+ }
56
+
57
+ type FileEdit struct {
58
+ Path string `json:"path"`
59
+ NewContent string `json:"new_content"`
60
+ }
61
+
62
+ type EditResponse struct {
63
+ Explanation string `json:"explanation"`
64
+ Edits []FileEdit `json:"edits"`
65
+ }
66
+
67
+ type Diagnostic struct {
68
+ Path string `json:"path"`
69
+ Message string `json:"message"`
70
+ Source string `json:"source,omitempty"`
71
+ Code string `json:"code,omitempty"`
72
+ }
73
+
74
+ type FixRequest struct {
75
+ Prompt string `json:"prompt,omitempty"`
76
+ Files []FileInput `json:"files"`
77
+ Diagnostics []Diagnostic `json:"diagnostics,omitempty"`
78
+ TestOutput string `json:"test_output,omitempty"`
79
+ }
80
+
81
+ type FixResponse = EditResponse
82
+
83
+ type Agent struct {
84
+ client *ai.Client
85
+ search websearch.Searcher
86
+ }
87
+
88
+ func New() (*Agent, error) {
89
+ client, err := ai.NewClient()
90
+ if err != nil {
91
+ return nil, err
92
+ }
93
+
94
+ searcher := websearch.NewFromEnv()
95
+ return &Agent{client: client, search: searcher}, nil
96
+ }
97
+
98
+ func (a *Agent) Client() *ai.Client {
99
+ return a.client
100
+ }
101
+
102
+ func (a *Agent) Chat(ctx context.Context, req ChatRequest) (ChatResponse, error) {
103
+ var webResults []WebResult
104
+ if req.WebSearch.Enabled && a.search != nil {
105
+ query := strings.TrimSpace(req.WebSearch.Query)
106
+ if query == "" {
107
+ // fallback: use last user content
108
+ for i := len(req.Messages) - 1; i >= 0; i-- {
109
+ if req.Messages[i].Role == "user" {
110
+ query = req.Messages[i].Content
111
+ break
112
+ }
113
+ }
114
+ }
115
+ if query != "" {
116
+ results, err := a.search.Search(ctx, query)
117
+ if err == nil {
118
+ webResults = make([]WebResult, 0, len(results))
119
+ for _, r := range results {
120
+ webResults = append(webResults, WebResult{Title: r.Title, URL: r.URL, Snippet: r.Snippet})
121
+ }
122
+ }
123
+ }
124
+ }
125
+
126
+ messages := make([]ai.Message, 0, len(req.Messages)+1)
127
+ if len(webResults) > 0 {
128
+ messages = append(messages, ai.Message{Role: "system", Content: "Web search results (use as optional context):\n" + formatWebResults(webResults)})
129
+ }
130
+ messages = append(messages, req.Messages...)
131
+
132
+ if len(req.Attach) > 0 {
133
+ images, err := convertAttachmentsToImages(req.Attach)
134
+ if err != nil {
135
+ return ChatResponse{}, err
136
+ }
137
+ text, err := a.client.ChatWithImages(ctx, messages, images)
138
+ if err != nil {
139
+ return ChatResponse{}, err
140
+ }
141
+ return ChatResponse{Assistant: text, WebResults: webResults}, nil
142
+ }
143
+
144
+ text, err := a.client.ChatWithContext(ctx, messages)
145
+ if err != nil {
146
+ return ChatResponse{}, err
147
+ }
148
+ return ChatResponse{Assistant: text, WebResults: webResults}, nil
149
+ }
150
+
151
+ func (a *Agent) Edit(ctx context.Context, req EditRequest) (EditResponse, error) {
152
+ if strings.TrimSpace(req.Prompt) == "" {
153
+ return EditResponse{}, fmt.Errorf("prompt is required")
154
+ }
155
+ if len(req.Files) == 0 {
156
+ return EditResponse{}, fmt.Errorf("files is required")
157
+ }
158
+
159
+ var webContext string
160
+ if req.WebSearch.Enabled && a.search != nil {
161
+ query := strings.TrimSpace(req.WebSearch.Query)
162
+ if query == "" {
163
+ query = req.Prompt
164
+ }
165
+ if query != "" {
166
+ results, err := a.search.Search(ctx, query)
167
+ if err == nil {
168
+ webContext = formatWebResultsSimple(results)
169
+ }
170
+ }
171
+ }
172
+
173
+ system := "You are an AI coding agent. Return ONLY a JSON object with shape: {\"explanation\":string,\"edits\":[{\"path\":string,\"new_content\":string}]} .\n" +
174
+ "Rules: preserve unrelated code; keep formatting; ensure code compiles; if you can't, explain in explanation and return the original content."
175
+ if req.Instructions != "" {
176
+ system += "\nExtra instructions: " + req.Instructions
177
+ }
178
+ if webContext != "" {
179
+ system += "\n\nWeb context:\n" + webContext
180
+ }
181
+
182
+ var b strings.Builder
183
+ for _, f := range req.Files {
184
+ cleanPath := filepath.ToSlash(f.Path)
185
+ b.WriteString("FILE: " + cleanPath + "\n")
186
+ b.WriteString("```\n")
187
+ b.WriteString(f.Content)
188
+ if !strings.HasSuffix(f.Content, "\n") {
189
+ b.WriteString("\n")
190
+ }
191
+ b.WriteString("```\n\n")
192
+ }
193
+
194
+ messages := []ai.Message{
195
+ {Role: "system", Content: system},
196
+ {Role: "user", Content: "Task: " + req.Prompt + "\n\n" + b.String()},
197
+ }
198
+
199
+ text, err := a.client.ChatWithContext(ctx, messages)
200
+ if err != nil {
201
+ return EditResponse{}, err
202
+ }
203
+
204
+ resp, err := parseEditResponse(text)
205
+ if err != nil {
206
+ return EditResponse{}, err
207
+ }
208
+ return resp, nil
209
+ }
210
+
211
+ func (a *Agent) Fix(ctx context.Context, req FixRequest) (FixResponse, error) {
212
+ if len(req.Files) == 0 {
213
+ return FixResponse{}, fmt.Errorf("files is required")
214
+ }
215
+
216
+ prompt := strings.TrimSpace(req.Prompt)
217
+ if prompt == "" {
218
+ prompt = "Fix the reported problems and make the code compile/tests pass."
219
+ }
220
+
221
+ system := "You are an AI coding agent fixing code. Return ONLY JSON: {\"explanation\":string,\"edits\":[{\"path\":string,\"new_content\":string}]}. " +
222
+ "Keep changes minimal and targeted; do not refactor unrelated code."
223
+
224
+ var b strings.Builder
225
+ if len(req.Diagnostics) > 0 {
226
+ b.WriteString("DIAGNOSTICS:\n")
227
+ for _, d := range req.Diagnostics {
228
+ b.WriteString("- " + filepath.ToSlash(d.Path) + ": " + d.Message)
229
+ if d.Source != "" {
230
+ b.WriteString(" (" + d.Source + ")")
231
+ }
232
+ b.WriteString("\n")
233
+ }
234
+ b.WriteString("\n")
235
+ }
236
+ if req.TestOutput != "" {
237
+ b.WriteString("TEST OUTPUT:\n")
238
+ b.WriteString(req.TestOutput)
239
+ b.WriteString("\n\n")
240
+ }
241
+
242
+ for _, f := range req.Files {
243
+ cleanPath := filepath.ToSlash(f.Path)
244
+ b.WriteString("FILE: " + cleanPath + "\n")
245
+ b.WriteString("```\n")
246
+ b.WriteString(f.Content)
247
+ if !strings.HasSuffix(f.Content, "\n") {
248
+ b.WriteString("\n")
249
+ }
250
+ b.WriteString("```\n\n")
251
+ }
252
+
253
+ messages := []ai.Message{
254
+ {Role: "system", Content: system},
255
+ {Role: "user", Content: "Task: " + prompt + "\n\n" + b.String()},
256
+ }
257
+
258
+ text, err := a.client.ChatWithContext(ctx, messages)
259
+ if err != nil {
260
+ return FixResponse{}, err
261
+ }
262
+
263
+ resp, err := parseEditResponse(text)
264
+ if err != nil {
265
+ return FixResponse{}, err
266
+ }
267
+ return resp, nil
268
+ }
269
+
270
+ type imageInput struct {
271
+ Mime string
272
+ DataBase64 string
273
+ }
274
+
275
+ func convertAttachmentsToImages(attachments []Attachment) ([]ai.ImageInput, error) {
276
+ images := make([]ai.ImageInput, 0, len(attachments))
277
+ for _, a := range attachments {
278
+ if a.Type != "image" {
279
+ continue
280
+ }
281
+ mime := a.Mime
282
+ if mime == "" {
283
+ mime = "image/png"
284
+ }
285
+ data := strings.TrimSpace(a.DataBase64)
286
+ if data == "" {
287
+ return nil, fmt.Errorf("image attachment missing data_base64")
288
+ }
289
+ // validate base64
290
+ if _, err := base64.StdEncoding.DecodeString(stripDataURLPrefix(data)); err != nil {
291
+ // might already be data url
292
+ if _, err2 := base64.StdEncoding.DecodeString(data); err2 != nil {
293
+ return nil, fmt.Errorf("invalid base64 image")
294
+ }
295
+ }
296
+ images = append(images, ai.ImageInput{Mime: mime, DataBase64: data})
297
+ }
298
+ if len(images) == 0 {
299
+ return nil, fmt.Errorf("no supported attachments")
300
+ }
301
+ return images, nil
302
+ }
303
+
304
+ func stripDataURLPrefix(s string) string {
305
+ if i := strings.Index(s, ","); i != -1 && strings.Contains(s[:i], "base64") {
306
+ return s[i+1:]
307
+ }
308
+ return s
309
+ }
310
+
311
+ func formatWebResults(results []WebResult) string {
312
+ var b strings.Builder
313
+ for i, r := range results {
314
+ b.WriteString(fmt.Sprintf("%d. %s\n %s\n %s\n", i+1, r.Title, r.URL, r.Snippet))
315
+ }
316
+ return b.String()
317
+ }
318
+
319
+ func formatWebResultsSimple(results []websearch.Result) string {
320
+ var b strings.Builder
321
+ for i, r := range results {
322
+ b.WriteString(fmt.Sprintf("%d. %s\n %s\n %s\n", i+1, r.Title, r.URL, r.Snippet))
323
+ }
324
+ return b.String()
325
+ }
326
+
327
+ func mustGetEnv(key, fallback string) string {
328
+ if v := os.Getenv(key); v != "" {
329
+ return v
330
+ }
331
+ return fallback
332
+ }
@@ -0,0 +1,25 @@
1
+ package agent
2
+
3
+ import (
4
+ "encoding/json"
5
+ "fmt"
6
+ "strings"
7
+ )
8
+
9
+ func parseEditResponse(text string) (EditResponse, error) {
10
+ trimmed := strings.TrimSpace(text)
11
+ // Some models wrap JSON in markdown fences
12
+ trimmed = strings.TrimPrefix(trimmed, "```json")
13
+ trimmed = strings.TrimPrefix(trimmed, "```")
14
+ trimmed = strings.TrimSuffix(trimmed, "```")
15
+ trimmed = strings.TrimSpace(trimmed)
16
+
17
+ var resp EditResponse
18
+ if err := json.Unmarshal([]byte(trimmed), &resp); err != nil {
19
+ return EditResponse{}, fmt.Errorf("model did not return valid JSON: %w", err)
20
+ }
21
+ if len(resp.Edits) == 0 {
22
+ return EditResponse{}, fmt.Errorf("model returned no edits")
23
+ }
24
+ return resp, nil
25
+ }
@@ -0,0 +1,154 @@
1
+ package agents
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+
7
+ "github.com/ddhanush1/dcode/internal/registry"
8
+ )
9
+
10
+ // BaseAgent provides common functionality for all agents
11
+ type BaseAgent struct {
12
+ definition *registry.AgentDefinition
13
+ history []registry.Message
14
+ context map[string]interface{}
15
+ }
16
+
17
+ // NewBaseAgent creates a new base agent
18
+ func NewBaseAgent(def *registry.AgentDefinition) *BaseAgent {
19
+ return &BaseAgent{
20
+ definition: def,
21
+ history: make([]registry.Message, 0),
22
+ context: make(map[string]interface{}),
23
+ }
24
+ }
25
+
26
+ // AddMessage adds a message to history
27
+ func (a *BaseAgent) AddMessage(role, content string, data map[string]interface{}) {
28
+ a.history = append(a.history, registry.Message{
29
+ Role: role,
30
+ Content: content,
31
+ Data: data,
32
+ })
33
+ }
34
+
35
+ // GetHistory returns conversation history
36
+ func (a *BaseAgent) GetHistory() []registry.Message {
37
+ return a.history
38
+ }
39
+
40
+ // ClearHistory clears conversation history
41
+ func (a *BaseAgent) ClearHistory() {
42
+ a.history = make([]registry.Message, 0)
43
+ }
44
+
45
+ // SetContext sets context data
46
+ func (a *BaseAgent) SetContext(key string, value interface{}) {
47
+ a.context[key] = value
48
+ }
49
+
50
+ // GetContext gets context data
51
+ func (a *BaseAgent) GetContext(key string) interface{} {
52
+ return a.context[key]
53
+ }
54
+
55
+ // GetDefinition returns the agent definition
56
+ func (a *BaseAgent) GetDefinition() *registry.AgentDefinition {
57
+ return a.definition
58
+ }
59
+
60
+ // AgentManager manages agent lifecycle and execution
61
+ type AgentManager struct {
62
+ registry *registry.AgentRegistry
63
+ toolRegistry *registry.ToolRegistry
64
+ currentAgent string
65
+ activeAgents map[string]*BaseAgent
66
+ }
67
+
68
+ // NewAgentManager creates a new agent manager
69
+ func NewAgentManager(agentRegistry *registry.AgentRegistry, toolRegistry *registry.ToolRegistry) *AgentManager {
70
+ return &AgentManager{
71
+ registry: agentRegistry,
72
+ toolRegistry: toolRegistry,
73
+ currentAgent: "generalist",
74
+ activeAgents: make(map[string]*BaseAgent),
75
+ }
76
+ }
77
+
78
+ // SwitchAgent switches to a different agent
79
+ func (m *AgentManager) SwitchAgent(agentID string) error {
80
+ if !m.registry.Has(agentID) {
81
+ return fmt.Errorf("agent not found: %s", agentID)
82
+ }
83
+
84
+ m.currentAgent = agentID
85
+ return nil
86
+ }
87
+
88
+ // GetCurrentAgent returns the current agent
89
+ func (m *AgentManager) GetCurrentAgent() (*registry.AgentDefinition, error) {
90
+ return m.registry.Get(m.currentAgent)
91
+ }
92
+
93
+ // Execute executes the current agent with input
94
+ func (m *AgentManager) Execute(ctx context.Context, input *registry.AgentInput) (*registry.AgentOutput, error) {
95
+ agent, err := m.GetCurrentAgent()
96
+ if err != nil {
97
+ return nil, err
98
+ }
99
+
100
+ // Get or create active agent instance
101
+ activeAgent, ok := m.activeAgents[agent.ID]
102
+ if !ok {
103
+ activeAgent = NewBaseAgent(agent)
104
+ m.activeAgents[agent.ID] = activeAgent
105
+ }
106
+
107
+ // Add to history only if query is not empty
108
+ if input.Query != "" {
109
+ activeAgent.AddMessage("user", input.Query, nil)
110
+ }
111
+
112
+ // Execute agent
113
+ output, err := agent.Executor(input)
114
+ if err != nil {
115
+ return nil, err
116
+ }
117
+
118
+ // Add response to history only if message is not empty
119
+ if output.Message != "" {
120
+ activeAgent.AddMessage("assistant", output.Message, output.Data)
121
+ }
122
+
123
+ return output, nil
124
+ }
125
+
126
+ // GetHistory returns history for an agent
127
+ func (m *AgentManager) GetHistory(agentID string) []registry.Message {
128
+ if agent, ok := m.activeAgents[agentID]; ok {
129
+ return agent.GetHistory()
130
+ }
131
+ return []registry.Message{}
132
+ }
133
+
134
+ // ClearHistory clears history for an agent
135
+ func (m *AgentManager) ClearHistory(agentID string) {
136
+ if agent, ok := m.activeAgents[agentID]; ok {
137
+ agent.ClearHistory()
138
+ }
139
+ }
140
+
141
+ // AddMessage adds a message to an agent's history
142
+ func (m *AgentManager) AddMessage(agentID, role, content string, data map[string]interface{}) {
143
+ agent, ok := m.activeAgents[agentID]
144
+ if !ok {
145
+ // Create if doesn't exist
146
+ agentDef, err := m.registry.Get(agentID)
147
+ if err != nil {
148
+ return
149
+ }
150
+ agent = NewBaseAgent(agentDef)
151
+ m.activeAgents[agentID] = agent
152
+ }
153
+ agent.AddMessage(role, content, data)
154
+ }
@@ -0,0 +1,77 @@
1
+ package agents
2
+
3
+ import (
4
+ "github.com/ddhanush1/dcode/internal/ai"
5
+ "github.com/ddhanush1/dcode/internal/registry"
6
+ )
7
+
8
+ const documenterSystemPrompt = `You are a Documentation Specialist, an AI agent specialized in creating clear, comprehensive documentation.
9
+
10
+ Your documentation skills:
11
+ - README files and project overviews
12
+ - API documentation (REST, GraphQL, gRPC)
13
+ - Code comments and inline documentation
14
+ - Architecture documentation
15
+ - User guides and tutorials
16
+ - Contributing guidelines
17
+ - Changelog management
18
+
19
+ When writing documentation:
20
+ 1. Start with a clear purpose and audience
21
+ 2. Use clear, concise language
22
+ 3. Include practical examples
23
+ 4. Structure information logically
24
+ 5. Keep it up-to-date with code changes
25
+ 6. Use appropriate formatting (Markdown, etc.)
26
+
27
+ Documentation types:
28
+ - **README**: Project overview, setup, usage, examples
29
+ - **API Docs**: Endpoints, parameters, responses, examples
30
+ - **Code Comments**: Explain WHY, not WHAT
31
+ - **Architecture**: System design, component relationships
32
+ - **Guides**: Step-by-step tutorials for common tasks
33
+ - **Reference**: Comprehensive API/function reference
34
+
35
+ Best practices:
36
+ - Write for your audience's level
37
+ - Include code examples that actually work
38
+ - Use diagrams where helpful
39
+ - Keep documentation close to code
40
+ - Update docs when code changes
41
+ - Make it searchable and navigable
42
+
43
+ Use available tools to:
44
+ - read_file: Understand existing code/docs
45
+ - write_file: Create/update documentation
46
+ - glob: Find files to document
47
+ - git_log: Understand project evolution
48
+ - grep: Search for patterns
49
+ - ls: Explore project structure
50
+
51
+ Create documentation that helps people understand and use the code effectively.`
52
+
53
+ // GetDocumenterTools returns tools best suited for documentation
54
+ func GetDocumenterTools() []string {
55
+ return []string{
56
+ "read_file",
57
+ "write_file",
58
+ "glob",
59
+ "ls",
60
+ "git_log",
61
+ "grep",
62
+ "save_memory",
63
+ "recall_memory",
64
+ }
65
+ }
66
+
67
+ // RegisterDocumenterAgent registers the documenter agent
68
+ func RegisterDocumenterAgent(agentRegistry *registry.AgentRegistry, aiClient *ai.Client) error {
69
+ return agentRegistry.Register(&registry.AgentDefinition{
70
+ ID: "documenter",
71
+ Name: "Documentation Specialist",
72
+ Description: "Specialized in creating clear, comprehensive documentation",
73
+ SystemPrompt: documenterSystemPrompt,
74
+ Tools: GetDocumenterTools(),
75
+ Executor: CreateGeneralistExecutor(aiClient),
76
+ })
77
+ }