draftgo-cli 4.0.1 → 4.0.23

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 (79) hide show
  1. package/README.md +87 -11
  2. package/package.json +9 -4
  3. package/resources/custom-service-sdk/ai.go +520 -0
  4. package/resources/custom-service-sdk/ai_test.go +156 -0
  5. package/resources/custom-service-sdk/auth_test.go +56 -0
  6. package/resources/custom-service-sdk/billing.go +596 -0
  7. package/resources/custom-service-sdk/billing_test.go +150 -0
  8. package/resources/custom-service-sdk/go.mod +3 -0
  9. package/resources/custom-service-sdk/manifest.json +77 -0
  10. package/resources/custom-service-sdk/platform.go +345 -0
  11. package/resources/custom-service-sdk/platform_logger_test.go +24 -0
  12. package/resources/custom-service-sdk/registration_test.go +39 -0
  13. package/resources/custom-service-sdk/resources.go +246 -0
  14. package/resources/custom-service-sdk/resources_billing_test.go +115 -0
  15. package/resources/custom-service-sdk/resources_files_test.go +57 -0
  16. package/resources/custom-service-sdk/resources_scope_test.go +87 -0
  17. package/resources/custom-service-sdk/sdk.go +208 -0
  18. package/resources/skill/SKILL.md +36 -88
  19. package/resources/skill/init/SKILL.md +4 -4
  20. package/resources/skill/manifest.json +5 -1
  21. package/resources/skill/references/aihub.md +25 -2
  22. package/resources/skill/references/app-api.md +56 -6
  23. package/resources/skill/references/architecture.md +2 -2
  24. package/resources/skill/references/chat-sdk.md +4 -2
  25. package/resources/skill/references/checkout.md +17 -3
  26. package/resources/skill/references/custom-services.md +112 -47
  27. package/resources/skill/references/data.md +19 -4
  28. package/resources/skill/references/delivery.md +33 -0
  29. package/resources/skill/references/diagnostics.md +51 -0
  30. package/resources/skill/references/frontend.md +34 -46
  31. package/resources/skill/references/mcp.md +33 -5
  32. package/resources/skill/references/methods.md +189 -0
  33. package/resources/skill/references/modules.md +37 -9
  34. package/resources/skill/references/runtime.md +23 -1
  35. package/src/cli.js +24 -0
  36. package/src/commandRegistry.js +9 -1
  37. package/src/commands/api.js +21 -10
  38. package/src/commands/apiKey.js +34 -0
  39. package/src/commands/capabilities.js +93 -0
  40. package/src/commands/checkout.js +1 -1
  41. package/src/commands/commit.js +1 -1
  42. package/src/commands/components.js +550 -0
  43. package/src/commands/conflict.js +1 -1
  44. package/src/commands/connect.js +18 -8
  45. package/src/commands/customService.js +20 -4
  46. package/src/commands/dataRange.js +33 -0
  47. package/src/commands/delete.js +12 -1
  48. package/src/commands/diff.js +18 -2
  49. package/src/commands/grant.js +29 -0
  50. package/src/commands/group.js +38 -0
  51. package/src/commands/help.js +64 -20
  52. package/src/commands/init.js +3 -3
  53. package/src/commands/map.js +145 -17
  54. package/src/commands/mcp.js +2 -2
  55. package/src/commands/reconcile.js +1 -1
  56. package/src/commands/role.js +32 -0
  57. package/src/commands/space.js +41 -0
  58. package/src/commands/status.js +110 -7
  59. package/src/commands/update.js +23 -11
  60. package/src/commands/verify.js +75 -0
  61. package/src/commands/worklog.js +6 -2
  62. package/src/consoleEncoding.js +34 -0
  63. package/src/contractCompatibility.js +57 -0
  64. package/src/customServices.js +138 -18
  65. package/src/diffReport.js +106 -0
  66. package/src/index.js +2 -0
  67. package/src/localRuntime/compose.js +14 -17
  68. package/src/localRuntime/index.js +22 -23
  69. package/src/localRuntime/services.js +27 -36
  70. package/src/mcp/client.js +11 -2
  71. package/src/mcp/protocol.js +22 -2
  72. package/src/mcp/tools.js +14 -1
  73. package/src/platforms.js +9 -0
  74. package/src/projectConfig.js +6 -4
  75. package/src/releaseInstall.js +105 -0
  76. package/src/updateCheck.js +48 -28
  77. package/src/worklog.js +2 -1
  78. package/src/worktree/backend.js +1 -1
  79. package/src/worktree/index.js +7 -2
@@ -0,0 +1,520 @@
1
+ package sdk
2
+
3
+ import (
4
+ "context"
5
+ "encoding/base64"
6
+ "fmt"
7
+ "net/http"
8
+ "strconv"
9
+ "strings"
10
+ )
11
+
12
+ // AIRequest is the future-compatible escape hatch for AI endpoints that have
13
+ // not yet gained a typed helper. Path must target a DraftGo AI API route.
14
+ type AIRequest struct {
15
+ Method string `json:"method"`
16
+ Path string `json:"path"`
17
+ Query map[string]any `json:"query,omitempty"`
18
+ Body any `json:"body,omitempty"`
19
+ }
20
+
21
+ type AIChatRequest struct {
22
+ AgentID int64 `json:"agent_id,omitempty"`
23
+ Message string `json:"message,omitempty"`
24
+ Messages []map[string]any `json:"messages,omitempty"`
25
+ Model string `json:"model,omitempty"`
26
+ Context map[string]any `json:"context,omitempty"`
27
+ SessionID string `json:"session_id,omitempty"`
28
+ RunID string `json:"run_id,omitempty"`
29
+ Options map[string]any `json:"options,omitempty"`
30
+ }
31
+
32
+ type AIImageRequest struct {
33
+ AgentID int64 `json:"agent_id,omitempty"`
34
+ Prompt string `json:"prompt"`
35
+ Model string `json:"model,omitempty"`
36
+ Options map[string]any `json:"options,omitempty"`
37
+ }
38
+
39
+ type AIEmbeddingRequest struct {
40
+ Model string `json:"model"`
41
+ Input any `json:"input"`
42
+ Options map[string]any `json:"options,omitempty"`
43
+ }
44
+
45
+ // AIResponsesRequest is the synchronous typed helper for the OpenAI-compatible
46
+ // Responses and Responses Compact APIs. Streaming remains available through
47
+ // AIHub.Request because the custom-service RPC transport returns decoded values.
48
+ type AIResponsesRequest struct {
49
+ Model string `json:"model"`
50
+ Input any `json:"input,omitempty"`
51
+ Instructions string `json:"instructions,omitempty"`
52
+ PreviousResponseID string `json:"previous_response_id,omitempty"`
53
+ Tools []map[string]any `json:"tools,omitempty"`
54
+ Options map[string]any `json:"options,omitempty"`
55
+ }
56
+
57
+ type AIRerankRequest struct {
58
+ Model string `json:"model"`
59
+ Query string `json:"query"`
60
+ Documents []string `json:"documents"`
61
+ TopN int `json:"top_n,omitempty"`
62
+ ReturnDocuments bool `json:"return_documents,omitempty"`
63
+ Options map[string]any `json:"options,omitempty"`
64
+ }
65
+
66
+ type AITTSRequest struct {
67
+ Model string `json:"model"`
68
+ Input string `json:"input"`
69
+ Voice string `json:"voice,omitempty"`
70
+ Instructions string `json:"instructions,omitempty"`
71
+ ResponseFormat string `json:"response_format,omitempty"`
72
+ OptimizeTextPreview bool `json:"optimize_text_preview,omitempty"`
73
+ Options map[string]any `json:"options,omitempty"`
74
+ }
75
+
76
+ type AIAudioResponse struct {
77
+ Content []byte
78
+ MIMEType string
79
+ }
80
+
81
+ type AITranscriptionRequest struct {
82
+ Model string `json:"model"`
83
+ Content []byte `json:"-"`
84
+ MIMEType string `json:"mime_type"`
85
+ Language string `json:"language,omitempty"`
86
+ ResponseFormat string `json:"response_format,omitempty"`
87
+ Options map[string]any `json:"options,omitempty"`
88
+ }
89
+
90
+ type SkillInstallRequest struct {
91
+ Repository string `json:"repo"`
92
+ Ref string `json:"ref,omitempty"`
93
+ }
94
+
95
+ type SkillArchive struct {
96
+ Filename string `json:"filename"`
97
+ Content []byte `json:"-"`
98
+ }
99
+
100
+ type KnowledgeDocumentUpload struct {
101
+ Filename string `json:"filename"`
102
+ MIMEType string `json:"mime_type,omitempty"`
103
+ Content []byte `json:"-"`
104
+ Metadata map[string]any `json:"metadata,omitempty"`
105
+ AutoTag bool `json:"auto_tag,omitempty"`
106
+ TaggingModelID int64 `json:"tagging_model_id,omitempty"`
107
+ }
108
+
109
+ // AIHub covers inference plus the complete AI asset and model-management API.
110
+ // Asset CRUD is used for agent, prompt, skill and MCP records.
111
+ type AIHub interface {
112
+ Request(context.Context, AIRequest) (any, error)
113
+ Chat(context.Context, AIChatRequest) (map[string]any, error)
114
+ GenerateImage(context.Context, AIImageRequest) (map[string]any, error)
115
+ Responses(context.Context, AIResponsesRequest) (map[string]any, error)
116
+ CompactResponse(context.Context, AIResponsesRequest) (map[string]any, error)
117
+ Embeddings(context.Context, AIEmbeddingRequest) (map[string]any, error)
118
+ Rerank(context.Context, AIRerankRequest) (map[string]any, error)
119
+ TextToSpeech(context.Context, AITTSRequest) (AIAudioResponse, error)
120
+ Transcribe(context.Context, AITranscriptionRequest) (map[string]any, error)
121
+
122
+ ListCallableAgents(context.Context) ([]map[string]any, error)
123
+ AgentSelectableModels(context.Context, int64) (map[string]any, error)
124
+ PreviewAgentChat(context.Context, int64, AIChatRequest) (map[string]any, error)
125
+ ListAssetTypes(context.Context) ([]map[string]any, error)
126
+ ListAssets(context.Context, map[string]any) (map[string]any, error)
127
+ GetAsset(context.Context, int64) (map[string]any, error)
128
+ CreateAsset(context.Context, map[string]any) ([]map[string]any, error)
129
+ UpdateAsset(context.Context, int64, map[string]any) ([]map[string]any, error)
130
+ UpdateAssets(context.Context, []map[string]any) ([]map[string]any, error)
131
+ DeleteAsset(context.Context, int64, bool) error
132
+ AgentReadiness(context.Context, int64) (map[string]any, error)
133
+ PublishAgent(context.Context, int64) (map[string]any, error)
134
+ DiscoverMCPTools(context.Context, map[string]any) (map[string]any, error)
135
+
136
+ ListProviderKinds(context.Context) (map[string]any, error)
137
+ ListProviders(context.Context, map[string]any) (map[string]any, error)
138
+ GetProvider(context.Context, int64) (map[string]any, error)
139
+ CreateProvider(context.Context, map[string]any) (map[string]any, error)
140
+ UpdateProvider(context.Context, int64, map[string]any) (map[string]any, error)
141
+ DeleteProvider(context.Context, int64) error
142
+ DiscoverProvider(context.Context, int64) (map[string]any, error)
143
+ SyncProvider(context.Context, int64) (map[string]any, error)
144
+
145
+ ListModels(context.Context, map[string]any) (map[string]any, error)
146
+ GetModel(context.Context, int64) (map[string]any, error)
147
+ CreateModel(context.Context, map[string]any) (map[string]any, error)
148
+ UpdateModel(context.Context, int64, map[string]any) (map[string]any, error)
149
+ DeleteModel(context.Context, int64) error
150
+ ProbeModelRoute(context.Context, int64, string) (map[string]any, error)
151
+ UpdateModelRoute(context.Context, int64, map[string]any) (map[string]any, error)
152
+ DeleteModelRoute(context.Context, int64) (map[string]any, error)
153
+
154
+ InstallSkill(context.Context, SkillInstallRequest) (map[string]any, error)
155
+ InstallSkillArchive(context.Context, SkillArchive) (map[string]any, error)
156
+ ListSkillVersions(context.Context, int64) ([]map[string]any, error)
157
+ RollbackSkill(context.Context, int64, int) (map[string]any, error)
158
+ ListRuns(context.Context, map[string]any) (map[string]any, error)
159
+ GetRun(context.Context, string) (map[string]any, error)
160
+ DeleteRuns(context.Context, map[string]any) (map[string]any, error)
161
+ }
162
+
163
+ type Knowledge interface {
164
+ List(context.Context, map[string]any) (map[string]any, error)
165
+ Get(context.Context, int64) (map[string]any, error)
166
+ Create(context.Context, map[string]any) (map[string]any, error)
167
+ Update(context.Context, int64, map[string]any) (map[string]any, error)
168
+ Delete(context.Context, int64) error
169
+ ListDocuments(context.Context, int64) ([]map[string]any, error)
170
+ GetDocument(context.Context, int64, int64) (map[string]any, error)
171
+ UploadDocument(context.Context, int64, KnowledgeDocumentUpload) (map[string]any, error)
172
+ UpdateDocument(context.Context, int64, int64, map[string]any) (map[string]any, error)
173
+ DeleteDocument(context.Context, int64, int64) error
174
+ ReindexDocument(context.Context, int64, int64) (map[string]any, error)
175
+ ListChunks(context.Context, int64, map[string]any) ([]map[string]any, error)
176
+ UpdateChunk(context.Context, int64, int64, map[string]any) (map[string]any, error)
177
+ Retrieve(context.Context, int64, map[string]any) (map[string]any, error)
178
+ RebuildIndex(context.Context, int64) (map[string]any, error)
179
+ }
180
+
181
+ type Memory interface {
182
+ List(context.Context, map[string]any) (map[string]any, error)
183
+ Get(context.Context, int64) (map[string]any, error)
184
+ Create(context.Context, map[string]any) (map[string]any, error)
185
+ Update(context.Context, int64, map[string]any) (map[string]any, error)
186
+ Delete(context.Context, int64) error
187
+ GetConfig(context.Context) (map[string]any, error)
188
+ UpdateConfig(context.Context, map[string]any) (map[string]any, error)
189
+ }
190
+
191
+ func (p aiHubClient) Request(ctx context.Context, request AIRequest) (any, error) {
192
+ return p.call(ctx, "ai.request", request)
193
+ }
194
+
195
+ func (p aiHubClient) requestMap(ctx context.Context, method, path string, query map[string]any, body any) (map[string]any, error) {
196
+ value, err := p.Request(ctx, AIRequest{Method: method, Path: path, Query: query, Body: body})
197
+ return mapResult(value), err
198
+ }
199
+
200
+ func (p aiHubClient) Chat(ctx context.Context, request AIChatRequest) (map[string]any, error) {
201
+ value, err := p.call(ctx, "aihub.chat", map[string]any{"agent_id": request.AgentID, "message": request.Message, "messages": request.Messages, "model": request.Model, "context": request.Context, "session_id": request.SessionID, "run_id": request.RunID, "kwargs": request.Options})
202
+ return mapResult(value), err
203
+ }
204
+
205
+ func (p aiHubClient) GenerateImage(ctx context.Context, request AIImageRequest) (map[string]any, error) {
206
+ value, err := p.call(ctx, "aihub.image", map[string]any{"agent_id": request.AgentID, "prompt": request.Prompt, "model": request.Model, "kwargs": request.Options})
207
+ return mapResult(value), err
208
+ }
209
+
210
+ func (p aiHubClient) Responses(ctx context.Context, request AIResponsesRequest) (map[string]any, error) {
211
+ value, err := p.call(ctx, "aihub.responses", responsesRequestBody(request))
212
+ return mapResult(value), err
213
+ }
214
+
215
+ func (p aiHubClient) CompactResponse(ctx context.Context, request AIResponsesRequest) (map[string]any, error) {
216
+ value, err := p.call(ctx, "aihub.responses.compact", responsesRequestBody(request))
217
+ return mapResult(value), err
218
+ }
219
+
220
+ func responsesRequestBody(request AIResponsesRequest) map[string]any {
221
+ body := cloneSDKMap(request.Options)
222
+ body["model"], body["input"] = request.Model, request.Input
223
+ body["stream"] = false
224
+ if request.Instructions != "" {
225
+ body["instructions"] = request.Instructions
226
+ }
227
+ if request.PreviousResponseID != "" {
228
+ body["previous_response_id"] = request.PreviousResponseID
229
+ }
230
+ if len(request.Tools) > 0 {
231
+ body["tools"] = request.Tools
232
+ }
233
+ return body
234
+ }
235
+
236
+ func (p aiHubClient) Embeddings(ctx context.Context, request AIEmbeddingRequest) (map[string]any, error) {
237
+ body := cloneSDKMap(request.Options)
238
+ body["model"], body["input"] = request.Model, request.Input
239
+ value, err := p.call(ctx, "aihub.embeddings", body)
240
+ return mapResult(value), err
241
+ }
242
+
243
+ func (p aiHubClient) Rerank(ctx context.Context, request AIRerankRequest) (map[string]any, error) {
244
+ body := cloneSDKMap(request.Options)
245
+ body["model"], body["query"], body["documents"] = request.Model, request.Query, request.Documents
246
+ if request.TopN > 0 {
247
+ body["top_n"] = request.TopN
248
+ }
249
+ body["return_documents"] = request.ReturnDocuments
250
+ value, err := p.call(ctx, "aihub.rerank", body)
251
+ return mapResult(value), err
252
+ }
253
+
254
+ func (p aiHubClient) TextToSpeech(ctx context.Context, request AITTSRequest) (AIAudioResponse, error) {
255
+ body := cloneSDKMap(request.Options)
256
+ body["model"], body["input"], body["voice"] = request.Model, request.Input, request.Voice
257
+ body["instructions"], body["response_format"] = request.Instructions, request.ResponseFormat
258
+ body["optimize_text_preview"] = request.OptimizeTextPreview
259
+ value, err := p.call(ctx, "aihub.tts", body)
260
+ if err != nil {
261
+ return AIAudioResponse{}, err
262
+ }
263
+ result := mapResult(value)
264
+ content, err := base64.StdEncoding.DecodeString(fmt.Sprint(result["audio_base64"]))
265
+ if err != nil {
266
+ return AIAudioResponse{}, fmt.Errorf("decode TTS audio: %w", err)
267
+ }
268
+ return AIAudioResponse{Content: content, MIMEType: fmt.Sprint(result["mime_type"])}, nil
269
+ }
270
+
271
+ func (p aiHubClient) Transcribe(ctx context.Context, request AITranscriptionRequest) (map[string]any, error) {
272
+ body := cloneSDKMap(request.Options)
273
+ body["model"], body["audio_base64"], body["audio_mime_type"] = request.Model, base64.StdEncoding.EncodeToString(request.Content), request.MIMEType
274
+ body["language"], body["response_format"] = request.Language, request.ResponseFormat
275
+ value, err := p.call(ctx, "aihub.transcribe", body)
276
+ return mapResult(value), err
277
+ }
278
+
279
+ func (p aiHubClient) ListCallableAgents(ctx context.Context) ([]map[string]any, error) {
280
+ value, err := p.Request(ctx, AIRequest{Method: http.MethodGet, Path: "/api/agents"})
281
+ return mapSlice(value), err
282
+ }
283
+ func (p aiHubClient) AgentSelectableModels(ctx context.Context, id int64) (map[string]any, error) {
284
+ return p.requestMap(ctx, http.MethodGet, idPath("/api/agents", id)+"/selectable-models", nil, nil)
285
+ }
286
+ func (p aiHubClient) PreviewAgentChat(ctx context.Context, id int64, request AIChatRequest) (map[string]any, error) {
287
+ body := cloneSDKMap(request.Options)
288
+ body["message"], body["messages"], body["model"], body["context"] = request.Message, request.Messages, request.Model, request.Context
289
+ return p.requestMap(ctx, http.MethodPost, idPath("/api/agents", id)+"/preview-chat", nil, body)
290
+ }
291
+ func (p aiHubClient) ListAssetTypes(ctx context.Context) ([]map[string]any, error) {
292
+ value, err := p.Request(ctx, AIRequest{Method: http.MethodGet, Path: "/api/aihub/types"})
293
+ return mapSlice(value), err
294
+ }
295
+ func (p aiHubClient) ListAssets(ctx context.Context, query map[string]any) (map[string]any, error) {
296
+ return p.requestMap(ctx, http.MethodGet, "/api/aihub", query, nil)
297
+ }
298
+ func (p aiHubClient) GetAsset(ctx context.Context, id int64) (map[string]any, error) {
299
+ return p.requestMap(ctx, http.MethodGet, idPath("/api/aihub", id), nil, nil)
300
+ }
301
+ func (p aiHubClient) CreateAsset(ctx context.Context, data map[string]any) ([]map[string]any, error) {
302
+ value, err := p.Request(ctx, AIRequest{Method: http.MethodPost, Path: "/api/aihub", Body: data})
303
+ return mapSlice(value), err
304
+ }
305
+ func (p aiHubClient) UpdateAsset(ctx context.Context, id int64, data map[string]any) ([]map[string]any, error) {
306
+ value, err := p.Request(ctx, AIRequest{Method: http.MethodPut, Path: idPath("/api/aihub", id), Body: data})
307
+ return mapSlice(value), err
308
+ }
309
+ func (p aiHubClient) UpdateAssets(ctx context.Context, data []map[string]any) ([]map[string]any, error) {
310
+ value, err := p.Request(ctx, AIRequest{Method: http.MethodPatch, Path: "/api/aihub/batch", Body: data})
311
+ return mapSlice(value), err
312
+ }
313
+ func (p aiHubClient) DeleteAsset(ctx context.Context, id int64, force bool) error {
314
+ _, err := p.Request(ctx, AIRequest{Method: http.MethodDelete, Path: idPath("/api/aihub", id), Query: map[string]any{"force": force}})
315
+ return err
316
+ }
317
+ func (p aiHubClient) AgentReadiness(ctx context.Context, id int64) (map[string]any, error) {
318
+ return p.requestMap(ctx, http.MethodGet, idPath("/api/aihub", id)+"/readiness", nil, nil)
319
+ }
320
+ func (p aiHubClient) PublishAgent(ctx context.Context, id int64) (map[string]any, error) {
321
+ return p.requestMap(ctx, http.MethodPost, idPath("/api/aihub", id)+"/publish", nil, nil)
322
+ }
323
+ func (p aiHubClient) DiscoverMCPTools(ctx context.Context, data map[string]any) (map[string]any, error) {
324
+ return p.requestMap(ctx, http.MethodPost, "/api/aihub/mcp/discover-tools", nil, data)
325
+ }
326
+
327
+ func (p aiHubClient) ListProviderKinds(ctx context.Context) (map[string]any, error) {
328
+ return p.requestMap(ctx, http.MethodGet, "/api/aihub/provider-kinds", nil, nil)
329
+ }
330
+ func (p aiHubClient) ListProviders(ctx context.Context, query map[string]any) (map[string]any, error) {
331
+ return p.requestMap(ctx, http.MethodGet, "/api/aihub/providers", query, nil)
332
+ }
333
+ func (p aiHubClient) GetProvider(ctx context.Context, id int64) (map[string]any, error) {
334
+ return p.requestMap(ctx, http.MethodGet, idPath("/api/aihub/providers", id), nil, nil)
335
+ }
336
+ func (p aiHubClient) CreateProvider(ctx context.Context, data map[string]any) (map[string]any, error) {
337
+ return p.requestMap(ctx, http.MethodPost, "/api/aihub/providers", nil, data)
338
+ }
339
+ func (p aiHubClient) UpdateProvider(ctx context.Context, id int64, data map[string]any) (map[string]any, error) {
340
+ return p.requestMap(ctx, http.MethodPatch, idPath("/api/aihub/providers", id), nil, data)
341
+ }
342
+ func (p aiHubClient) DeleteProvider(ctx context.Context, id int64) error {
343
+ _, err := p.Request(ctx, AIRequest{Method: http.MethodDelete, Path: idPath("/api/aihub/providers", id)})
344
+ return err
345
+ }
346
+ func (p aiHubClient) DiscoverProvider(ctx context.Context, id int64) (map[string]any, error) {
347
+ return p.requestMap(ctx, http.MethodPost, idPath("/api/aihub/providers", id)+"/discover", nil, nil)
348
+ }
349
+ func (p aiHubClient) SyncProvider(ctx context.Context, id int64) (map[string]any, error) {
350
+ return p.requestMap(ctx, http.MethodPost, idPath("/api/aihub/providers", id)+"/sync", nil, nil)
351
+ }
352
+ func (p aiHubClient) ListModels(ctx context.Context, query map[string]any) (map[string]any, error) {
353
+ return p.requestMap(ctx, http.MethodGet, "/api/aihub/models", query, nil)
354
+ }
355
+ func (p aiHubClient) GetModel(ctx context.Context, id int64) (map[string]any, error) {
356
+ result, err := p.ListModels(ctx, nil)
357
+ if err != nil {
358
+ return nil, err
359
+ }
360
+ for _, model := range mapSlice(result["items"]) {
361
+ if int64(asInt(model["id"])) == id {
362
+ return model, nil
363
+ }
364
+ }
365
+ return nil, fmt.Errorf("AI model %d was not found", id)
366
+ }
367
+ func (p aiHubClient) CreateModel(ctx context.Context, data map[string]any) (map[string]any, error) {
368
+ return p.requestMap(ctx, http.MethodPost, "/api/aihub/models/manual", nil, data)
369
+ }
370
+ func (p aiHubClient) UpdateModel(ctx context.Context, id int64, data map[string]any) (map[string]any, error) {
371
+ return p.requestMap(ctx, http.MethodPatch, idPath("/api/aihub/models", id), nil, data)
372
+ }
373
+ func (p aiHubClient) DeleteModel(ctx context.Context, id int64) error {
374
+ _, err := p.Request(ctx, AIRequest{Method: http.MethodDelete, Path: idPath("/api/aihub/models", id)})
375
+ return err
376
+ }
377
+ func (p aiHubClient) ProbeModelRoute(ctx context.Context, id int64, task string) (map[string]any, error) {
378
+ return p.requestMap(ctx, http.MethodPost, idPath("/api/aihub/model-routes", id)+"/probe", nil, map[string]any{"task": task})
379
+ }
380
+ func (p aiHubClient) UpdateModelRoute(ctx context.Context, id int64, data map[string]any) (map[string]any, error) {
381
+ return p.requestMap(ctx, http.MethodPatch, idPath("/api/aihub/model-routes", id), nil, data)
382
+ }
383
+ func (p aiHubClient) DeleteModelRoute(ctx context.Context, id int64) (map[string]any, error) {
384
+ return p.requestMap(ctx, http.MethodDelete, idPath("/api/aihub/model-routes", id), nil, nil)
385
+ }
386
+ func (p aiHubClient) InstallSkill(ctx context.Context, request SkillInstallRequest) (map[string]any, error) {
387
+ return p.requestMap(ctx, http.MethodPost, "/api/skills/install", nil, map[string]any{"source": "github", "repo": request.Repository, "ref": request.Ref})
388
+ }
389
+ func (p aiHubClient) InstallSkillArchive(ctx context.Context, archive SkillArchive) (map[string]any, error) {
390
+ value, err := p.call(ctx, "ai.skill.install_archive", map[string]any{"filename": archive.Filename, "content_base64": base64.StdEncoding.EncodeToString(archive.Content)})
391
+ return mapResult(value), err
392
+ }
393
+ func (p aiHubClient) ListSkillVersions(ctx context.Context, id int64) ([]map[string]any, error) {
394
+ value, err := p.Request(ctx, AIRequest{Method: http.MethodGet, Path: idPath("/api/skills", id) + "/versions"})
395
+ return mapSlice(value), err
396
+ }
397
+ func (p aiHubClient) RollbackSkill(ctx context.Context, id int64, version int) (map[string]any, error) {
398
+ return p.requestMap(ctx, http.MethodPost, idPath("/api/skills", id)+"/rollback", nil, map[string]any{"version": version})
399
+ }
400
+ func (p aiHubClient) ListRuns(ctx context.Context, query map[string]any) (map[string]any, error) {
401
+ return p.requestMap(ctx, http.MethodGet, "/api/aihub/runs", query, nil)
402
+ }
403
+ func (p aiHubClient) GetRun(ctx context.Context, traceID string) (map[string]any, error) {
404
+ return p.requestMap(ctx, http.MethodGet, "/api/aihub/runs/"+traceID, nil, nil)
405
+ }
406
+ func (p aiHubClient) DeleteRuns(ctx context.Context, query map[string]any) (map[string]any, error) {
407
+ query = cloneSDKMap(query)
408
+ switch values := query["trace_ids"].(type) {
409
+ case []string:
410
+ query["trace_ids"] = strings.Join(values, ",")
411
+ case []any:
412
+ items := make([]string, 0, len(values))
413
+ for _, value := range values {
414
+ items = append(items, fmt.Sprint(value))
415
+ }
416
+ query["trace_ids"] = strings.Join(items, ",")
417
+ }
418
+ return p.requestMap(ctx, http.MethodDelete, "/api/aihub/runs", query, nil)
419
+ }
420
+
421
+ func (p knowledgeClient) requestMap(ctx context.Context, method, path string, query map[string]any, body any) (map[string]any, error) {
422
+ value, err := p.call(ctx, "ai.request", AIRequest{Method: method, Path: path, Query: query, Body: body})
423
+ return mapResult(value), err
424
+ }
425
+ func (p knowledgeClient) requestSlice(ctx context.Context, method, path string, query map[string]any) ([]map[string]any, error) {
426
+ value, err := p.call(ctx, "ai.request", AIRequest{Method: method, Path: path, Query: query})
427
+ if object := mapResult(value); object != nil {
428
+ value = object["items"]
429
+ }
430
+ return mapSlice(value), err
431
+ }
432
+ func (p knowledgeClient) List(ctx context.Context, query map[string]any) (map[string]any, error) {
433
+ return p.requestMap(ctx, http.MethodGet, "/api/knowledge-bases", query, nil)
434
+ }
435
+ func (p knowledgeClient) Get(ctx context.Context, id int64) (map[string]any, error) {
436
+ return p.requestMap(ctx, http.MethodGet, idPath("/api/knowledge-bases", id), nil, nil)
437
+ }
438
+ func (p knowledgeClient) Create(ctx context.Context, data map[string]any) (map[string]any, error) {
439
+ return p.requestMap(ctx, http.MethodPost, "/api/knowledge-bases", nil, data)
440
+ }
441
+ func (p knowledgeClient) Update(ctx context.Context, id int64, data map[string]any) (map[string]any, error) {
442
+ return p.requestMap(ctx, http.MethodPatch, idPath("/api/knowledge-bases", id), nil, data)
443
+ }
444
+ func (p knowledgeClient) Delete(ctx context.Context, id int64) error {
445
+ _, err := p.call(ctx, "ai.request", AIRequest{Method: http.MethodDelete, Path: idPath("/api/knowledge-bases", id)})
446
+ return err
447
+ }
448
+ func (p knowledgeClient) ListDocuments(ctx context.Context, baseID int64) ([]map[string]any, error) {
449
+ return p.requestSlice(ctx, http.MethodGet, idPath("/api/knowledge-bases", baseID)+"/documents", nil)
450
+ }
451
+ func (p knowledgeClient) GetDocument(ctx context.Context, baseID, documentID int64) (map[string]any, error) {
452
+ return p.requestMap(ctx, http.MethodGet, nestedIDPath("/api/knowledge-bases", baseID, "documents", documentID), nil, nil)
453
+ }
454
+ func (p knowledgeClient) UploadDocument(ctx context.Context, baseID int64, upload KnowledgeDocumentUpload) (map[string]any, error) {
455
+ value, err := p.call(ctx, "ai.knowledge.upload", map[string]any{"base_id": baseID, "filename": upload.Filename, "mime_type": upload.MIMEType, "content_base64": base64.StdEncoding.EncodeToString(upload.Content), "metadata": upload.Metadata, "auto_tag": upload.AutoTag, "tagging_model_id": upload.TaggingModelID})
456
+ return mapResult(value), err
457
+ }
458
+ func (p knowledgeClient) UpdateDocument(ctx context.Context, baseID, documentID int64, data map[string]any) (map[string]any, error) {
459
+ return p.requestMap(ctx, http.MethodPatch, nestedIDPath("/api/knowledge-bases", baseID, "documents", documentID), nil, data)
460
+ }
461
+ func (p knowledgeClient) DeleteDocument(ctx context.Context, baseID, documentID int64) error {
462
+ _, err := p.call(ctx, "ai.request", AIRequest{Method: http.MethodDelete, Path: nestedIDPath("/api/knowledge-bases", baseID, "documents", documentID)})
463
+ return err
464
+ }
465
+ func (p knowledgeClient) ReindexDocument(ctx context.Context, baseID, documentID int64) (map[string]any, error) {
466
+ return p.requestMap(ctx, http.MethodPost, nestedIDPath("/api/knowledge-bases", baseID, "documents", documentID)+"/reindex", nil, nil)
467
+ }
468
+ func (p knowledgeClient) ListChunks(ctx context.Context, baseID int64, query map[string]any) ([]map[string]any, error) {
469
+ return p.requestSlice(ctx, http.MethodGet, idPath("/api/knowledge-bases", baseID)+"/chunks", query)
470
+ }
471
+ func (p knowledgeClient) UpdateChunk(ctx context.Context, baseID, chunkID int64, data map[string]any) (map[string]any, error) {
472
+ return p.requestMap(ctx, http.MethodPatch, nestedIDPath("/api/knowledge-bases", baseID, "chunks", chunkID), nil, data)
473
+ }
474
+ func (p knowledgeClient) Retrieve(ctx context.Context, baseID int64, data map[string]any) (map[string]any, error) {
475
+ return p.requestMap(ctx, http.MethodPost, idPath("/api/knowledge-bases", baseID)+"/retrieve", nil, data)
476
+ }
477
+ func (p knowledgeClient) RebuildIndex(ctx context.Context, baseID int64) (map[string]any, error) {
478
+ return p.requestMap(ctx, http.MethodPost, idPath("/api/knowledge-bases", baseID)+"/rebuild-index", nil, nil)
479
+ }
480
+
481
+ func (p memoryClient) requestMap(ctx context.Context, method, path string, query map[string]any, body any) (map[string]any, error) {
482
+ value, err := p.call(ctx, "ai.request", AIRequest{Method: method, Path: path, Query: query, Body: body})
483
+ return mapResult(value), err
484
+ }
485
+ func (p memoryClient) List(ctx context.Context, query map[string]any) (map[string]any, error) {
486
+ return p.requestMap(ctx, http.MethodGet, "/api/memories", query, nil)
487
+ }
488
+ func (p memoryClient) Get(ctx context.Context, id int64) (map[string]any, error) {
489
+ return p.requestMap(ctx, http.MethodGet, idPath("/api/memories", id), nil, nil)
490
+ }
491
+ func (p memoryClient) Create(ctx context.Context, data map[string]any) (map[string]any, error) {
492
+ return p.requestMap(ctx, http.MethodPost, "/api/memories", nil, data)
493
+ }
494
+ func (p memoryClient) Update(ctx context.Context, id int64, data map[string]any) (map[string]any, error) {
495
+ return p.requestMap(ctx, http.MethodPatch, idPath("/api/memories", id), nil, data)
496
+ }
497
+ func (p memoryClient) Delete(ctx context.Context, id int64) error {
498
+ _, err := p.call(ctx, "ai.request", AIRequest{Method: http.MethodDelete, Path: idPath("/api/memories", id)})
499
+ return err
500
+ }
501
+ func (p memoryClient) GetConfig(ctx context.Context) (map[string]any, error) {
502
+ return p.requestMap(ctx, http.MethodGet, "/api/memories/config", nil, nil)
503
+ }
504
+ func (p memoryClient) UpdateConfig(ctx context.Context, data map[string]any) (map[string]any, error) {
505
+ return p.requestMap(ctx, http.MethodPatch, "/api/memories/config", nil, data)
506
+ }
507
+
508
+ func cloneSDKMap(value map[string]any) map[string]any {
509
+ result := make(map[string]any, len(value)+2)
510
+ for key, item := range value {
511
+ result[key] = item
512
+ }
513
+ return result
514
+ }
515
+ func idPath(base string, id int64) string {
516
+ return strings.TrimRight(base, "/") + "/" + strconv.FormatInt(id, 10)
517
+ }
518
+ func nestedIDPath(base string, parentID int64, collection string, id int64) string {
519
+ return fmt.Sprintf("%s/%d/%s/%d", strings.TrimRight(base, "/"), parentID, collection, id)
520
+ }
@@ -0,0 +1,156 @@
1
+ package sdk
2
+
3
+ import (
4
+ "context"
5
+ "encoding/base64"
6
+ "encoding/json"
7
+ "testing"
8
+ )
9
+
10
+ type recordingClient struct {
11
+ op string
12
+ args map[string]any
13
+ }
14
+
15
+ func (client *recordingClient) Call(_ context.Context, operation string, args any) (any, error) {
16
+ client.op = operation
17
+ raw, _ := json.Marshal(args)
18
+ _ = json.Unmarshal(raw, &client.args)
19
+ switch operation {
20
+ case "aihub.tts":
21
+ return map[string]any{"audio_base64": base64.StdEncoding.EncodeToString([]byte("audio")), "mime_type": "audio/wav"}, nil
22
+ case "aihub.transcribe":
23
+ return map[string]any{"text": "hello"}, nil
24
+ case "ai.request":
25
+ path, _ := client.args["path"].(string)
26
+ if path == "/api/knowledge-bases/7/documents" {
27
+ return map[string]any{"items": []any{map[string]any{"id": float64(3)}}}, nil
28
+ }
29
+ return map[string]any{"ok": true}, nil
30
+ default:
31
+ return map[string]any{"ok": true}, nil
32
+ }
33
+ }
34
+
35
+ func TestAIHubInferenceOperations(t *testing.T) {
36
+ client := &recordingClient{}
37
+ draftgo := NewContext(context.Background(), nil, nil, nil, client)
38
+
39
+ if _, err := draftgo.AIHub.Chat(draftgo.Context(), AIChatRequest{AgentID: 12, Message: "hello", SessionID: "session-1", RunID: "run-1"}); err != nil {
40
+ t.Fatal(err)
41
+ }
42
+ if client.op != "aihub.chat" || client.args["agent_id"] != float64(12) {
43
+ t.Fatalf("unexpected chat call: %s %#v", client.op, client.args)
44
+ }
45
+ if client.args["session_id"] != "session-1" || client.args["run_id"] != "run-1" {
46
+ t.Fatalf("chat session metadata missing: %#v", client.args)
47
+ }
48
+
49
+ if _, err := draftgo.AIHub.Responses(draftgo.Context(), AIResponsesRequest{
50
+ Model: "responses", Input: "hello", Instructions: "be concise", PreviousResponseID: "resp_previous",
51
+ Tools: []map[string]any{{"type": "web_search"}}, Options: map[string]any{"reasoning": map[string]any{"effort": "high"}, "stream": true},
52
+ }); err != nil {
53
+ t.Fatal(err)
54
+ }
55
+ if client.op != "aihub.responses" || client.args["model"] != "responses" || client.args["stream"] != false || client.args["previous_response_id"] != "resp_previous" || client.args["reasoning"] == nil {
56
+ t.Fatalf("unexpected Responses call: %s %#v", client.op, client.args)
57
+ }
58
+ if _, err := draftgo.AIHub.CompactResponse(draftgo.Context(), AIResponsesRequest{Model: "responses", Input: []any{"one"}}); err != nil {
59
+ t.Fatal(err)
60
+ }
61
+ if client.op != "aihub.responses.compact" || client.args["stream"] != false {
62
+ t.Fatalf("unexpected compact Responses call: %s %#v", client.op, client.args)
63
+ }
64
+
65
+ if _, err := draftgo.AIHub.Embeddings(draftgo.Context(), AIEmbeddingRequest{Model: "embed", Input: []string{"one"}}); err != nil {
66
+ t.Fatal(err)
67
+ }
68
+ if client.op != "aihub.embeddings" || client.args["model"] != "embed" {
69
+ t.Fatalf("unexpected embeddings call: %s %#v", client.op, client.args)
70
+ }
71
+
72
+ if _, err := draftgo.AIHub.Rerank(draftgo.Context(), AIRerankRequest{Model: "rerank", Query: "query", Documents: []string{"one", "two"}, TopN: 1, ReturnDocuments: true}); err != nil {
73
+ t.Fatal(err)
74
+ }
75
+ if client.op != "aihub.rerank" || client.args["model"] != "rerank" || client.args["top_n"] != float64(1) || client.args["return_documents"] != true {
76
+ t.Fatalf("unexpected rerank call: %s %#v", client.op, client.args)
77
+ }
78
+
79
+ audio, err := draftgo.AIHub.TextToSpeech(draftgo.Context(), AITTSRequest{Model: "tts", Input: "hello", Voice: "Mia"})
80
+ if err != nil || string(audio.Content) != "audio" || audio.MIMEType != "audio/wav" {
81
+ t.Fatalf("unexpected TTS result: %+v %v", audio, err)
82
+ }
83
+ if client.op != "aihub.tts" || client.args["model"] != "tts" {
84
+ t.Fatalf("unexpected TTS call: %s %#v", client.op, client.args)
85
+ }
86
+ transcript, err := draftgo.AIHub.Transcribe(draftgo.Context(), AITranscriptionRequest{Model: "asr", Content: []byte("audio"), MIMEType: "audio/wav", Language: "zh"})
87
+ if err != nil || transcript["text"] != "hello" {
88
+ t.Fatalf("unexpected ASR result: %#v %v", transcript, err)
89
+ }
90
+ if client.op != "aihub.transcribe" || client.args["audio_base64"] != base64.StdEncoding.EncodeToString([]byte("audio")) {
91
+ t.Fatalf("unexpected ASR call: %s %#v", client.op, client.args)
92
+ }
93
+ }
94
+
95
+ func TestAIHubChatUsesCanonicalRPCOperationAndLogicalModel(t *testing.T) {
96
+ client := &recordingClient{}
97
+ draftgo := NewContext(context.Background(), nil, nil, nil, client)
98
+
99
+ result, err := draftgo.AIHub.Chat(draftgo.Context(), AIChatRequest{
100
+ Model: "shared-logical-model",
101
+ Messages: []map[string]any{{"role": "user", "content": "hello"}},
102
+ Options: map[string]any{"temperature": 0.2},
103
+ })
104
+ if err != nil {
105
+ t.Fatal(err)
106
+ }
107
+ if result == nil || client.op != "aihub.chat" {
108
+ t.Fatalf("result=%#v operation=%q", result, client.op)
109
+ }
110
+ if client.args["model"] != "shared-logical-model" || client.args["agent_id"] != float64(0) {
111
+ t.Fatalf("canonical chat target = %#v", client.args)
112
+ }
113
+ kwargs, _ := client.args["kwargs"].(map[string]any)
114
+ if kwargs["temperature"] != float64(0.2) {
115
+ t.Fatalf("chat options = %#v", kwargs)
116
+ }
117
+ }
118
+
119
+ func TestAdminAIRequestCarriesElevationMarker(t *testing.T) {
120
+ client := &recordingClient{}
121
+ draftgo := NewContext(context.Background(), nil, nil, nil, client)
122
+ if _, err := draftgo.Admin.AIHub.ListModels(draftgo.Context(), map[string]any{"provider_id": 4}); err != nil {
123
+ t.Fatal(err)
124
+ }
125
+ if client.op != "ai.request" || client.args["__draftgo_admin"] != true {
126
+ t.Fatalf("admin marker missing: %s %#v", client.op, client.args)
127
+ }
128
+ if client.args["path"] != "/api/aihub/models" {
129
+ t.Fatalf("unexpected path: %#v", client.args["path"])
130
+ }
131
+ }
132
+
133
+ func TestKnowledgeAndArchiveEncoding(t *testing.T) {
134
+ client := &recordingClient{}
135
+ draftgo := NewContext(context.Background(), nil, nil, nil, client)
136
+
137
+ documents, err := draftgo.Knowledge.ListDocuments(draftgo.Context(), 7)
138
+ if err != nil || len(documents) != 1 || documents[0]["id"] != float64(3) {
139
+ t.Fatalf("unexpected documents: %#v, %v", documents, err)
140
+ }
141
+
142
+ content := []byte{0, 1, 2, 255}
143
+ if _, err = draftgo.Knowledge.UploadDocument(draftgo.Context(), 7, KnowledgeDocumentUpload{Filename: "data.pdf", Content: content}); err != nil {
144
+ t.Fatal(err)
145
+ }
146
+ if client.op != "ai.knowledge.upload" || client.args["content_base64"] != base64.StdEncoding.EncodeToString(content) {
147
+ t.Fatalf("document encoding mismatch: %s %#v", client.op, client.args)
148
+ }
149
+
150
+ if _, err = draftgo.AIHub.InstallSkillArchive(draftgo.Context(), SkillArchive{Filename: "skill.zip", Content: content}); err != nil {
151
+ t.Fatal(err)
152
+ }
153
+ if client.op != "ai.skill.install_archive" || client.args["content_base64"] != base64.StdEncoding.EncodeToString(content) {
154
+ t.Fatalf("archive encoding mismatch: %s %#v", client.op, client.args)
155
+ }
156
+ }