Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitbd14ddd

Browse files
committed
feat(coderd): add tasks list and get endpoints
Fixescoder/internal#899
1 parent72f58c0 commitbd14ddd

File tree

4 files changed

+536
-1
lines changed

4 files changed

+536
-1
lines changed

‎coderd/aitasks.go‎

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"slices"
99
"strings"
1010

11+
"github.com/go-chi/chi/v5"
1112
"github.com/google/uuid"
1213

1314
"cdr.dev/slog"
@@ -17,6 +18,8 @@ import (
1718
"github.com/coder/coder/v2/coderd/httpapi"
1819
"github.com/coder/coder/v2/coderd/httpmw"
1920
"github.com/coder/coder/v2/coderd/rbac"
21+
"github.com/coder/coder/v2/coderd/rbac/policy"
22+
"github.com/coder/coder/v2/coderd/searchquery"
2023
"github.com/coder/coder/v2/coderd/taskname"
2124
"github.com/coder/coder/v2/codersdk"
2225
)
@@ -186,3 +189,267 @@ func (api *API) tasksCreate(rw http.ResponseWriter, r *http.Request) {
186189
defercommitAudit()
187190
createWorkspace(ctx,aReq,apiKey.UserID,api,owner,createReq,rw,r)
188191
}
192+
193+
// tasksListResponse wraps a list of experimental tasks.
194+
//
195+
// Experimental: Response shape is experimental and may change.
196+
typetasksListResponsestruct {
197+
Tasks []codersdk.Task`json:"tasks"`
198+
Countint`json:"count"`
199+
}
200+
201+
funcmapTaskStatus(ws codersdk.Workspace) codersdk.TaskStatus {
202+
ifws.LatestAppStatus!=nil {
203+
switchws.LatestAppStatus.State {
204+
casecodersdk.WorkspaceAppStatusStateWorking:
205+
returncodersdk.TaskStatusWorking
206+
casecodersdk.WorkspaceAppStatusStateIdle:
207+
returncodersdk.TaskStatusIdle
208+
casecodersdk.WorkspaceAppStatusStateComplete:
209+
returncodersdk.TaskStatusCompleted
210+
casecodersdk.WorkspaceAppStatusStateFailure:
211+
returncodersdk.TaskStatusFailed
212+
}
213+
}
214+
215+
switchws.LatestBuild.Status {
216+
casecodersdk.WorkspaceStatusPending,codersdk.WorkspaceStatusStarting,codersdk.WorkspaceStatusRunning:
217+
returncodersdk.TaskStatusWorking
218+
casecodersdk.WorkspaceStatusStopping,codersdk.WorkspaceStatusStopped,codersdk.WorkspaceStatusDeleting,codersdk.WorkspaceStatusDeleted:
219+
returncodersdk.TaskStatusCompleted
220+
casecodersdk.WorkspaceStatusFailed,codersdk.WorkspaceStatusCanceling,codersdk.WorkspaceStatusCanceled:
221+
returncodersdk.TaskStatusFailed
222+
default:
223+
returncodersdk.TaskStatusWorking
224+
}
225+
}
226+
227+
// tasksList is an experimental endpoint to list AI tasks by mapping
228+
// workspaces to a task-shaped response.
229+
func (api*API)tasksList(rw http.ResponseWriter,r*http.Request) {
230+
ctx:=r.Context()
231+
apiKey:=httpmw.APIKey(r)
232+
233+
// Support standard pagination/filters for workspaces.
234+
page,ok:=ParsePagination(rw,r)
235+
if!ok {
236+
return
237+
}
238+
queryStr:=r.URL.Query().Get("q")
239+
filter,errs:=searchquery.Workspaces(ctx,api.Database,queryStr,page,api.AgentInactiveDisconnectTimeout)
240+
iflen(errs)>0 {
241+
httpapi.Write(ctx,rw,http.StatusBadRequest, codersdk.Response{
242+
Message:"Invalid workspace search query.",
243+
Validations:errs,
244+
})
245+
return
246+
}
247+
248+
// Ensure that we only include AI task workspaces in the results.
249+
filter.HasAITask= sql.NullBool{Valid:true,Bool:true}
250+
251+
iffilter.OwnerUsername=="me"||filter.OwnerUsername=="" {
252+
filter.OwnerID=apiKey.UserID
253+
filter.OwnerUsername=""
254+
}
255+
256+
prepared,err:=api.HTTPAuth.AuthorizeSQLFilter(r,policy.ActionRead,rbac.ResourceWorkspace.Type)
257+
iferr!=nil {
258+
httpapi.Write(ctx,rw,http.StatusInternalServerError, codersdk.Response{
259+
Message:"Internal error preparing sql filter.",
260+
Detail:err.Error(),
261+
})
262+
return
263+
}
264+
265+
// Order with requester's favorites first, include summary row.
266+
filter.RequesterID=apiKey.UserID
267+
filter.WithSummary=true
268+
269+
workspaceRows,err:=api.Database.GetAuthorizedWorkspaces(ctx,filter,prepared)
270+
iferr!=nil {
271+
httpapi.Write(ctx,rw,http.StatusInternalServerError, codersdk.Response{
272+
Message:"Internal error fetching workspaces.",
273+
Detail:err.Error(),
274+
})
275+
return
276+
}
277+
iflen(workspaceRows)==0 {
278+
httpapi.Write(ctx,rw,http.StatusInternalServerError, codersdk.Response{
279+
Message:"Internal error fetching workspaces.",
280+
Detail:"Workspace summary row is missing.",
281+
})
282+
return
283+
}
284+
iflen(workspaceRows)==1 {
285+
httpapi.Write(ctx,rw,http.StatusOK,tasksListResponse{
286+
Tasks: []codersdk.Task{},
287+
Count:0,
288+
})
289+
return
290+
}
291+
292+
// Skip summary row.
293+
workspaceRows=workspaceRows[:len(workspaceRows)-1]
294+
295+
workspaces:=database.ConvertWorkspaceRows(workspaceRows)
296+
297+
// Gather associated data and convert to API workspaces.
298+
data,err:=api.workspaceData(ctx,workspaces)
299+
iferr!=nil {
300+
httpapi.Write(ctx,rw,http.StatusInternalServerError, codersdk.Response{
301+
Message:"Internal error fetching workspace resources.",
302+
Detail:err.Error(),
303+
})
304+
return
305+
}
306+
apiWorkspaces,err:=convertWorkspaces(apiKey.UserID,workspaces,data)
307+
iferr!=nil {
308+
httpapi.Write(ctx,rw,http.StatusInternalServerError, codersdk.Response{
309+
Message:"Internal error converting workspaces.",
310+
Detail:err.Error(),
311+
})
312+
return
313+
}
314+
315+
// Fetch prompts for each workspace build and map by build ID.
316+
buildIDs:=make([]uuid.UUID,0,len(apiWorkspaces))
317+
for_,ws:=rangeapiWorkspaces {
318+
buildIDs=append(buildIDs,ws.LatestBuild.ID)
319+
}
320+
parameters,err:=api.Database.GetWorkspaceBuildParametersByBuildIDs(ctx,buildIDs)
321+
iferr!=nil {
322+
httpapi.Write(ctx,rw,http.StatusInternalServerError, codersdk.Response{
323+
Message:"Internal error fetching task prompts.",
324+
Detail:err.Error(),
325+
})
326+
return
327+
}
328+
promptsByBuildID:=make(map[uuid.UUID]string,len(parameters))
329+
for_,p:=rangeparameters {
330+
ifp.Name==codersdk.AITaskPromptParameterName {
331+
promptsByBuildID[p.WorkspaceBuildID]=p.Value
332+
}
333+
}
334+
335+
tasks:=make([]codersdk.Task,0,len(apiWorkspaces))
336+
for_,ws:=rangeapiWorkspaces {
337+
tasks=append(tasks, codersdk.Task{
338+
ID:ws.ID,
339+
OrganizationID:ws.OrganizationID,
340+
OwnerID:ws.OwnerID,
341+
Name:ws.Name,
342+
TemplateID:ws.TemplateID,
343+
WorkspaceID: uuid.NullUUID{Valid:true,UUID:ws.ID},
344+
Prompt:promptsByBuildID[ws.LatestBuild.ID],
345+
Status:mapTaskStatus(ws),
346+
CreatedAt:ws.CreatedAt,
347+
UpdatedAt:ws.UpdatedAt,
348+
})
349+
}
350+
351+
httpapi.Write(ctx,rw,http.StatusOK,tasksListResponse{
352+
Tasks:tasks,
353+
Count:len(tasks),
354+
})
355+
}
356+
357+
// taskGet is an experimental endpoint to fetch a single AI task by ID
358+
// (workspace ID). It returns a synthesized task response including
359+
// prompt and status.
360+
func (api*API)taskGet(rw http.ResponseWriter,r*http.Request) {
361+
ctx:=r.Context()
362+
apiKey:=httpmw.APIKey(r)
363+
364+
idStr:=chi.URLParam(r,"id")
365+
taskID,err:=uuid.Parse(idStr)
366+
iferr!=nil {
367+
httpapi.Write(ctx,rw,http.StatusBadRequest, codersdk.Response{
368+
Message:fmt.Sprintf("Invalid UUID %q for task ID.",idStr),
369+
})
370+
return
371+
}
372+
373+
workspace,err:=api.Database.GetWorkspaceByID(ctx,taskID)
374+
ifhttpapi.Is404Error(err) {
375+
httpapi.ResourceNotFound(rw)
376+
return
377+
}
378+
iferr!=nil {
379+
httpapi.Write(ctx,rw,http.StatusInternalServerError, codersdk.Response{
380+
Message:"Internal error fetching workspace.",
381+
Detail:err.Error(),
382+
})
383+
return
384+
}
385+
386+
data,err:=api.workspaceData(ctx, []database.Workspace{workspace})
387+
iferr!=nil {
388+
httpapi.Write(ctx,rw,http.StatusInternalServerError, codersdk.Response{
389+
Message:"Internal error fetching workspace resources.",
390+
Detail:err.Error(),
391+
})
392+
return
393+
}
394+
iflen(data.builds)==0||len(data.templates)==0 {
395+
httpapi.ResourceNotFound(rw)
396+
return
397+
}
398+
ifdata.builds[0].HasAITask==nil||!*data.builds[0].HasAITask {
399+
httpapi.ResourceNotFound(rw)
400+
return
401+
}
402+
403+
appStatus:= codersdk.WorkspaceAppStatus{}
404+
iflen(data.appStatuses)>0 {
405+
appStatus=data.appStatuses[0]
406+
}
407+
408+
ws,err:=convertWorkspace(
409+
apiKey.UserID,
410+
workspace,
411+
data.builds[0],
412+
data.templates[0],
413+
api.Options.AllowWorkspaceRenames,
414+
appStatus,
415+
)
416+
iferr!=nil {
417+
httpapi.Write(ctx,rw,http.StatusInternalServerError, codersdk.Response{
418+
Message:"Internal error converting workspace.",
419+
Detail:err.Error(),
420+
})
421+
return
422+
}
423+
424+
// Fetch the AI prompt from the build parameters.
425+
params,err:=api.Database.GetWorkspaceBuildParametersByBuildIDs(ctx, []uuid.UUID{ws.LatestBuild.ID})
426+
iferr!=nil {
427+
httpapi.Write(ctx,rw,http.StatusInternalServerError, codersdk.Response{
428+
Message:"Internal error fetching task prompt.",
429+
Detail:err.Error(),
430+
})
431+
return
432+
}
433+
prompt:=""
434+
for_,p:=rangeparams {
435+
ifp.Name==codersdk.AITaskPromptParameterName {
436+
prompt=p.Value
437+
break
438+
}
439+
}
440+
441+
resp:= codersdk.Task{
442+
ID:ws.ID,
443+
OrganizationID:ws.OrganizationID,
444+
OwnerID:ws.OwnerID,
445+
Name:ws.Name,
446+
TemplateID:ws.TemplateID,
447+
WorkspaceID: uuid.NullUUID{Valid:true,UUID:ws.ID},
448+
Prompt:prompt,
449+
Status:mapTaskStatus(ws),
450+
CreatedAt:ws.CreatedAt,
451+
UpdatedAt:ws.UpdatedAt,
452+
}
453+
454+
httpapi.Write(ctx,rw,http.StatusOK,resp)
455+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp