refactor: type computer use provider as an enum (#27086)

The deployment-wide computer use provider was passed around as a bare `string` on the `codersdk` wire structs, in `chattool`, and in the generated TypeScript, and its valid values (`anthropic`, `openai`) were never exposed as a `codersdk` enum. That's out of step with our other chat settings (`ChatDebugRunKind`, `ChatUsageLimitPeriod`), which already define enums with `Valid()` and an `All<Name>s` slice, and it left the allowed values duplicated as literals with no typed contract for clients.

This adds `codersdk.ChatComputerUseProvider` as the single source of truth and routes the API boundary, `chattool`, `chatd`, and the generated TypeScript through it. The DB layer and chattool's internal model-provider routing stay `string` on purpose, since they handle untrusted or fantasy-model values that just happen to share the names.
This commit is contained in:
Ethan
2026-07-13 18:37:48 +10:00
committed by GitHub
parent 27ed052d86
commit 1497ba14fe
13 changed files with 120 additions and 97 deletions
+3 -3
View File
@@ -5376,7 +5376,7 @@ func (api *API) getChatComputerUseProvider(rw http.ResponseWriter, r *http.Reque
return
}
httpapi.Write(ctx, rw, http.StatusOK, codersdk.ChatComputerUseProviderResponse{
Provider: chattool.DefaultComputerUseProvider(provider),
Provider: chattool.DefaultComputerUseProvider(codersdk.ChatComputerUseProvider(provider)),
})
}
@@ -5392,7 +5392,7 @@ func (api *API) putChatComputerUseProvider(rw http.ResponseWriter, r *http.Reque
if !httpapi.Read(ctx, rw, r, &req) {
return
}
if !chattool.IsSupportedComputerUseProvider(req.Provider) {
if !req.Provider.Valid() {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Invalid computer use provider.",
Detail: fmt.Sprintf(
@@ -5404,7 +5404,7 @@ func (api *API) putChatComputerUseProvider(rw http.ResponseWriter, r *http.Reque
return
}
if err := api.Database.UpsertChatComputerUseProvider(ctx, req.Provider); err != nil {
if err := api.Database.UpsertChatComputerUseProvider(ctx, string(req.Provider)); err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error updating computer use provider.",
Detail: err.Error(),
+6 -6
View File
@@ -13299,7 +13299,7 @@ func TestChatComputerUseProvider(t *testing.T) {
resp, err := adminClient.GetChatComputerUseProvider(ctx)
require.NoError(t, err)
require.Equal(t, "anthropic", resp.Provider)
require.Equal(t, codersdk.ChatComputerUseProviderAnthropic, resp.Provider)
})
t.Run("AdminCanSetAnthropic", func(t *testing.T) {
@@ -13316,7 +13316,7 @@ func TestChatComputerUseProvider(t *testing.T) {
resp, err := adminClient.GetChatComputerUseProvider(ctx)
require.NoError(t, err)
require.Equal(t, "anthropic", resp.Provider)
require.Equal(t, codersdk.ChatComputerUseProviderAnthropic, resp.Provider)
})
t.Run("AdminCanSetOpenAI", func(t *testing.T) {
@@ -13333,7 +13333,7 @@ func TestChatComputerUseProvider(t *testing.T) {
resp, err := adminClient.GetChatComputerUseProvider(ctx)
require.NoError(t, err)
require.Equal(t, "openai", resp.Provider)
require.Equal(t, codersdk.ChatComputerUseProviderOpenAI, resp.Provider)
})
t.Run("AdminCanSwitchProviders", func(t *testing.T) {
@@ -13355,7 +13355,7 @@ func TestChatComputerUseProvider(t *testing.T) {
resp, err := adminClient.GetChatComputerUseProvider(ctx)
require.NoError(t, err)
require.Equal(t, "anthropic", resp.Provider)
require.Equal(t, codersdk.ChatComputerUseProviderAnthropic, resp.Provider)
})
t.Run("InvalidProviderRejected", func(t *testing.T) {
@@ -13367,7 +13367,7 @@ func TestChatComputerUseProvider(t *testing.T) {
for _, provider := range []string{"", "invalid"} {
err := adminClient.UpdateChatComputerUseProvider(ctx, codersdk.UpdateChatComputerUseProviderRequest{
Provider: provider,
Provider: codersdk.ChatComputerUseProvider(provider),
})
requireSDKError(t, err, http.StatusBadRequest)
}
@@ -13389,7 +13389,7 @@ func TestChatComputerUseProvider(t *testing.T) {
resp, err := memberClient.GetChatComputerUseProvider(ctx)
require.NoError(t, err)
require.Equal(t, "openai", resp.Provider)
require.Equal(t, codersdk.ChatComputerUseProviderOpenAI, resp.Provider)
})
t.Run("NonAdminWriteFails", func(t *testing.T) {
+9 -9
View File
@@ -93,18 +93,18 @@ func TestComputerUseProviderAndModelFromConfig(t *testing.T) {
tests := []struct {
name string
rawProvider string
wantProvider string
wantProvider codersdk.ChatComputerUseProvider
wantErr string
}{
{
name: "DefaultAnthropic",
rawProvider: "",
wantProvider: chattool.ComputerUseProviderAnthropic,
wantProvider: codersdk.ChatComputerUseProviderAnthropic,
},
{
name: "OpenAI",
rawProvider: " openai ",
wantProvider: chattool.ComputerUseProviderOpenAI,
wantProvider: codersdk.ChatComputerUseProviderOpenAI,
},
{
name: "Unknown",
@@ -168,10 +168,10 @@ func TestResolveUserProviderAPIKeysAndProviderForProviderTypeProviderMatch(t *te
keys, aiProvider, err := server.resolveUserProviderAPIKeysAndProviderForProviderType(
ctx,
ownerID,
chattool.ComputerUseProviderOpenAI,
string(codersdk.ChatComputerUseProviderOpenAI),
)
require.NoError(t, err)
require.Equal(t, "test-key", keys.APIKey(chattool.ComputerUseProviderOpenAI))
require.Equal(t, "test-key", keys.APIKey(string(codersdk.ChatComputerUseProviderOpenAI)))
require.NotNil(t, aiProvider)
require.Equal(t, providerID, aiProvider.ID)
require.Equal(t, database.AIProviderTypeOpenai, aiProvider.Type)
@@ -190,7 +190,7 @@ func TestResolveModelRouteForProviderTypeAIGatewayRequiresProvider(t *testing.T)
_, err := server.resolveModelRouteForProviderType(
ctx,
uuid.New(),
chattool.ComputerUseProviderOpenAI,
string(codersdk.ChatComputerUseProviderOpenAI),
)
require.ErrorContains(t, err, "AI Gateway routing requires a usable AI provider")
}
@@ -201,7 +201,7 @@ func TestAppendComputerUseProviderTool(t *testing.T) {
providerTools, err := appendComputerUseProviderTool(
nil,
computerUseProviderToolOptions{
provider: chattool.ComputerUseProviderOpenAI,
provider: codersdk.ChatComputerUseProviderOpenAI,
isComputerUse: true,
logger: slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}),
},
@@ -251,7 +251,7 @@ func TestAppendComputerUseProviderTool_Gates(t *testing.T) {
providerTools, err := appendComputerUseProviderTool(
baseTools,
computerUseProviderToolOptions{
provider: chattool.ComputerUseProviderOpenAI,
provider: codersdk.ChatComputerUseProviderOpenAI,
isPlanModeTurn: tt.isPlanModeTurn,
isComputerUse: tt.isComputerUse,
},
@@ -269,7 +269,7 @@ func TestAppendComputerUseProviderTool_AnthropicHasNoResultMetadata(t *testing.T
providerTools, err := appendComputerUseProviderTool(
nil,
computerUseProviderToolOptions{
provider: chattool.ComputerUseProviderAnthropic,
provider: codersdk.ChatComputerUseProviderAnthropic,
isComputerUse: true,
logger: slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}),
},
+2 -2
View File
@@ -9312,9 +9312,9 @@ func TestComputerUseSubagentToolsAndModel(t *testing.T) {
db, ps := dbtestutil.NewDB(t)
ctx := testutil.Context(t, testutil.WaitLong)
computerUseModelProvider, computerUseModelName, ok := chattool.DefaultComputerUseModel(chattool.ComputerUseProviderAnthropic)
computerUseModelProvider, computerUseModelName, ok := chattool.DefaultComputerUseModel(codersdk.ChatComputerUseProviderAnthropic)
require.True(t, ok)
require.Equal(t, chattool.ComputerUseProviderAnthropic, computerUseModelProvider)
require.EqualValues(t, codersdk.ChatComputerUseProviderAnthropic, computerUseModelProvider)
// Track tools and model from the Anthropic LLM calls (the
// computer use child chat). We use a raw HTTP handler because
+22 -32
View File
@@ -4,28 +4,22 @@ import (
"context"
"encoding/base64"
"fmt"
"slices"
"strings"
"time"
"charm.land/fantasy"
fantasyanthropic "charm.land/fantasy/providers/anthropic"
fantasyopenai "charm.land/fantasy/providers/openai"
"golang.org/x/xerrors"
"cdr.dev/slog/v3"
openaicomputeruse "github.com/coder/coder/v2/coderd/x/chatd/chatopenai/computeruse"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/workspacesdk"
"github.com/coder/quartz"
)
const (
// ComputerUseProviderAnthropic identifies Anthropic computer use.
ComputerUseProviderAnthropic = "anthropic"
// ComputerUseProviderOpenAI identifies OpenAI computer use.
ComputerUseProviderOpenAI = "openai"
// ComputerUseModelProviderDefault is the default model provider name for
// computer use, equal to ComputerUseProviderAnthropic.
ComputerUseModelProviderDefault = ComputerUseProviderAnthropic
// ComputerUseAnthropicModelName is the default Anthropic model used for
// computer use subagents.
ComputerUseAnthropicModelName = "claude-opus-4-6"
@@ -36,33 +30,29 @@ const (
// SupportedComputerUseProviders returns the providers supported by computer use.
// The returned slice is a fresh copy and safe to mutate.
func SupportedComputerUseProviders() []string {
return []string{
ComputerUseProviderAnthropic,
ComputerUseProviderOpenAI,
providers := make([]string, len(codersdk.AllChatComputerUseProviders))
for i, p := range codersdk.AllChatComputerUseProviders {
providers[i] = string(p)
}
}
// IsSupportedComputerUseProvider reports whether provider supports computer use.
func IsSupportedComputerUseProvider(provider string) bool {
return slices.Contains(SupportedComputerUseProviders(), provider)
return providers
}
// DefaultComputerUseProvider returns the effective computer use provider.
func DefaultComputerUseProvider(provider string) string {
func DefaultComputerUseProvider(provider codersdk.ChatComputerUseProvider) codersdk.ChatComputerUseProvider {
if provider == "" {
return ComputerUseProviderAnthropic
return codersdk.ChatComputerUseProviderAnthropic
}
return provider
}
// DefaultComputerUseModel returns the default model for a computer use provider.
func DefaultComputerUseModel(provider string) (modelProvider, modelName string, ok bool) {
func DefaultComputerUseModel(provider codersdk.ChatComputerUseProvider) (modelProvider, modelName string, ok bool) {
switch DefaultComputerUseProvider(provider) {
case ComputerUseProviderAnthropic:
return ComputerUseModelProviderDefault, ComputerUseAnthropicModelName, true
case ComputerUseProviderOpenAI:
case codersdk.ChatComputerUseProviderAnthropic:
return fantasyanthropic.Name, ComputerUseAnthropicModelName, true
case codersdk.ChatComputerUseProviderOpenAI:
// Keep OpenAI isolated here because computer-use models may advance.
return ComputerUseProviderOpenAI, ComputerUseOpenAIModelName, true
return fantasyopenai.Name, ComputerUseOpenAIModelName, true
default:
return "", "", false
}
@@ -70,9 +60,9 @@ func DefaultComputerUseModel(provider string) (modelProvider, modelName string,
// DefaultComputerUseDesktopGeometry returns provider-specific model-facing
// desktop geometry for computer use.
func DefaultComputerUseDesktopGeometry(provider string) workspacesdk.DesktopGeometry {
func DefaultComputerUseDesktopGeometry(provider codersdk.ChatComputerUseProvider) workspacesdk.DesktopGeometry {
switch DefaultComputerUseProvider(provider) {
case ComputerUseProviderOpenAI:
case codersdk.ChatComputerUseProviderOpenAI:
return workspacesdk.DefaultOpenAIComputerUseDesktopGeometry()
default:
return workspacesdk.DefaultDesktopGeometry()
@@ -81,7 +71,7 @@ func DefaultComputerUseDesktopGeometry(provider string) workspacesdk.DesktopGeom
// computerUseTool implements fantasy.AgentTool and chatloop.ToolDefiner.
type computerUseTool struct {
provider string
provider codersdk.ChatComputerUseProvider
declaredWidth int
declaredHeight int
getWorkspaceConn func(ctx context.Context) (workspacesdk.AgentConn, error)
@@ -96,7 +86,7 @@ type computerUseTool struct {
// are the model-facing desktop dimensions advertised to providers and requested
// for screenshots.
func NewComputerUseTool(
provider string,
provider codersdk.ChatComputerUseProvider,
declaredWidth, declaredHeight int,
getWorkspaceConn func(ctx context.Context) (workspacesdk.AgentConn, error),
storeFile StoreFileFunc,
@@ -127,9 +117,9 @@ func (*computerUseTool) Info() fantasy.ToolInfo {
// ComputerUseProviderTool creates the provider-defined computer-use tool
// definition using the declared model-facing desktop geometry.
func ComputerUseProviderTool(provider string, declaredWidth, declaredHeight int) (fantasy.Tool, error) {
func ComputerUseProviderTool(provider codersdk.ChatComputerUseProvider, declaredWidth, declaredHeight int) (fantasy.Tool, error) {
switch DefaultComputerUseProvider(provider) {
case ComputerUseProviderAnthropic:
case codersdk.ChatComputerUseProviderAnthropic:
// The run callback is nil because execution is handled separately
// by the AgentTool runner in the chatloop. We extract just the
// provider-defined tool definition.
@@ -141,7 +131,7 @@ func ComputerUseProviderTool(provider string, declaredWidth, declaredHeight int)
},
nil,
).Definition(), nil
case ComputerUseProviderOpenAI:
case codersdk.ChatComputerUseProviderOpenAI:
// OpenAI's GA computer tool schema does not accept display
// dimensions. The declared geometry is applied through screenshot
// sizing and desktop action coordinate scaling.
@@ -162,9 +152,9 @@ func (t *computerUseTool) SetProviderOptions(opts fantasy.ProviderOptions) {
func (t *computerUseTool) Run(ctx context.Context, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
switch DefaultComputerUseProvider(t.provider) {
case ComputerUseProviderAnthropic:
case codersdk.ChatComputerUseProviderAnthropic:
return t.runAnthropicComputerUse(ctx, call)
case ComputerUseProviderOpenAI:
case codersdk.ChatComputerUseProviderOpenAI:
return t.runOpenAIComputerUse(ctx, call)
default:
return fantasy.NewTextErrorResponse(fmt.Sprintf(
+25 -23
View File
@@ -9,6 +9,7 @@ import (
"charm.land/fantasy"
fantasyanthropic "charm.land/fantasy/providers/anthropic"
fantasyopenai "charm.land/fantasy/providers/openai"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -18,6 +19,7 @@ import (
"cdr.dev/slog/v3/sloggers/slogtest"
openaicomputeruse "github.com/coder/coder/v2/coderd/x/chatd/chatopenai/computeruse"
"github.com/coder/coder/v2/coderd/x/chatd/chattool"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/workspacesdk"
"github.com/coder/coder/v2/codersdk/workspacesdk/agentconnmock"
"github.com/coder/coder/v2/testutil"
@@ -29,7 +31,7 @@ func TestDefaultComputerUseModel(t *testing.T) {
tests := []struct {
name string
provider string
provider codersdk.ChatComputerUseProvider
wantModelProvider string
wantModelName string
wantOK bool
@@ -37,27 +39,27 @@ func TestDefaultComputerUseModel(t *testing.T) {
{
name: "empty defaults to Anthropic",
provider: "",
wantModelProvider: chattool.ComputerUseModelProviderDefault,
wantModelProvider: fantasyanthropic.Name,
wantModelName: chattool.ComputerUseAnthropicModelName,
wantOK: true,
},
{
name: "Anthropic",
provider: chattool.ComputerUseProviderAnthropic,
wantModelProvider: chattool.ComputerUseModelProviderDefault,
provider: codersdk.ChatComputerUseProviderAnthropic,
wantModelProvider: fantasyanthropic.Name,
wantModelName: chattool.ComputerUseAnthropicModelName,
wantOK: true,
},
{
name: "OpenAI",
provider: chattool.ComputerUseProviderOpenAI,
wantModelProvider: chattool.ComputerUseProviderOpenAI,
provider: codersdk.ChatComputerUseProviderOpenAI,
wantModelProvider: fantasyopenai.Name,
wantModelName: chattool.ComputerUseOpenAIModelName,
wantOK: true,
},
{
name: "unsupported",
provider: "unsupported",
provider: codersdk.ChatComputerUseProvider("unsupported"),
wantOK: false,
},
}
@@ -79,7 +81,7 @@ func TestDefaultComputerUseDesktopGeometry(t *testing.T) {
tests := []struct {
name string
provider string
provider codersdk.ChatComputerUseProvider
declaredWidth int
declaredHeight int
}{
@@ -91,13 +93,13 @@ func TestDefaultComputerUseDesktopGeometry(t *testing.T) {
},
{
name: "Anthropic",
provider: chattool.ComputerUseProviderAnthropic,
provider: codersdk.ChatComputerUseProviderAnthropic,
declaredWidth: 1280,
declaredHeight: 720,
},
{
name: "OpenAI",
provider: chattool.ComputerUseProviderOpenAI,
provider: codersdk.ChatComputerUseProviderOpenAI,
declaredWidth: 1600,
declaredHeight: 900,
},
@@ -119,7 +121,7 @@ func TestComputerUseProviderTool(t *testing.T) {
geometry := workspacesdk.DefaultDesktopGeometry()
def, err := chattool.ComputerUseProviderTool(
chattool.ComputerUseProviderAnthropic,
codersdk.ChatComputerUseProviderAnthropic,
geometry.DeclaredWidth,
geometry.DeclaredHeight,
)
@@ -133,7 +135,7 @@ func TestComputerUseProviderTool(t *testing.T) {
assert.Equal(t, int64(geometry.DeclaredHeight), pdt.Args["display_height_px"])
openAITool, err := chattool.ComputerUseProviderTool(
chattool.ComputerUseProviderOpenAI,
codersdk.ChatComputerUseProviderOpenAI,
geometry.DeclaredWidth,
geometry.DeclaredHeight,
)
@@ -141,7 +143,7 @@ func TestComputerUseProviderTool(t *testing.T) {
assert.True(t, openaicomputeruse.IsTool(openAITool))
_, err = chattool.ComputerUseProviderTool(
"unsupported",
codersdk.ChatComputerUseProvider("unsupported"),
geometry.DeclaredWidth,
geometry.DeclaredHeight,
)
@@ -172,7 +174,7 @@ func TestComputerUseTool_Run_Screenshot(t *testing.T) {
}, nil
})
tool := chattool.NewComputerUseTool(chattool.ComputerUseProviderAnthropic, geometry.DeclaredWidth, geometry.DeclaredHeight, func(_ context.Context) (workspacesdk.AgentConn, error) {
tool := chattool.NewComputerUseTool(codersdk.ChatComputerUseProviderAnthropic, geometry.DeclaredWidth, geometry.DeclaredHeight, func(_ context.Context) (workspacesdk.AgentConn, error) {
return mockConn, nil
}, nil, quartz.NewReal(), slogtest.Make(t, nil))
@@ -216,7 +218,7 @@ func TestComputerUseTool_Run_Screenshot_PersistsAttachment(t *testing.T) {
var storedName string
var storedType string
var storedData []byte
tool := chattool.NewComputerUseTool(chattool.ComputerUseProviderAnthropic, geometry.DeclaredWidth, geometry.DeclaredHeight, func(_ context.Context) (workspacesdk.AgentConn, error) {
tool := chattool.NewComputerUseTool(codersdk.ChatComputerUseProviderAnthropic, geometry.DeclaredWidth, geometry.DeclaredHeight, func(_ context.Context) (workspacesdk.AgentConn, error) {
return mockConn, nil
}, func(_ context.Context, name string, detectName string, data []byte) (chattool.AttachmentMetadata, error) {
storedName = name
@@ -270,7 +272,7 @@ func TestComputerUseTool_Run_Screenshot_StoreErrorFallsBackToImage(t *testing.T)
ScreenshotHeight: geometry.DeclaredHeight,
}, nil)
tool := chattool.NewComputerUseTool(chattool.ComputerUseProviderAnthropic, geometry.DeclaredWidth, geometry.DeclaredHeight, func(_ context.Context) (workspacesdk.AgentConn, error) {
tool := chattool.NewComputerUseTool(codersdk.ChatComputerUseProviderAnthropic, geometry.DeclaredWidth, geometry.DeclaredHeight, func(_ context.Context) (workspacesdk.AgentConn, error) {
return mockConn, nil
}, func(_ context.Context, _ string, _ string, _ []byte) (chattool.AttachmentMetadata, error) {
return chattool.AttachmentMetadata{}, xerrors.New("ETOOMANYFILES")
@@ -306,7 +308,7 @@ func TestComputerUseTool_Run_Screenshot_OversizedAttachmentFallsBackToImage(t *t
ScreenshotHeight: geometry.DeclaredHeight,
}, nil)
tool := chattool.NewComputerUseTool(chattool.ComputerUseProviderAnthropic, geometry.DeclaredWidth, geometry.DeclaredHeight, func(_ context.Context) (workspacesdk.AgentConn, error) {
tool := chattool.NewComputerUseTool(codersdk.ChatComputerUseProviderAnthropic, geometry.DeclaredWidth, geometry.DeclaredHeight, func(_ context.Context) (workspacesdk.AgentConn, error) {
return mockConn, nil
}, func(_ context.Context, _ string, _ string, _ []byte) (chattool.AttachmentMetadata, error) {
t.Fatal("storeFile should not be called for oversized screenshots")
@@ -366,7 +368,7 @@ func TestComputerUseTool_Run_LeftClick(t *testing.T) {
}, nil
})
tool := chattool.NewComputerUseTool(chattool.ComputerUseProviderAnthropic, geometry.DeclaredWidth, geometry.DeclaredHeight, func(_ context.Context) (workspacesdk.AgentConn, error) {
tool := chattool.NewComputerUseTool(codersdk.ChatComputerUseProviderAnthropic, geometry.DeclaredWidth, geometry.DeclaredHeight, func(_ context.Context) (workspacesdk.AgentConn, error) {
return mockConn, nil
}, func(_ context.Context, _ string, _ string, _ []byte) (chattool.AttachmentMetadata, error) {
t.Fatal("storeFile should not be called for left_click follow-up screenshots")
@@ -414,7 +416,7 @@ func TestComputerUseTool_Run_Wait(t *testing.T) {
}, nil
})
tool := chattool.NewComputerUseTool(chattool.ComputerUseProviderAnthropic, geometry.DeclaredWidth, geometry.DeclaredHeight, func(_ context.Context) (workspacesdk.AgentConn, error) {
tool := chattool.NewComputerUseTool(codersdk.ChatComputerUseProviderAnthropic, geometry.DeclaredWidth, geometry.DeclaredHeight, func(_ context.Context) (workspacesdk.AgentConn, error) {
return mockConn, nil
}, func(_ context.Context, _ string, _ string, _ []byte) (chattool.AttachmentMetadata, error) {
t.Fatal("storeFile should not be called for wait screenshots")
@@ -461,7 +463,7 @@ func TestComputerUseTool_Run_ScreenshotDataIsDecodedBinary(t *testing.T) {
}, nil)
tool := chattool.NewComputerUseTool(
chattool.ComputerUseProviderAnthropic,
codersdk.ChatComputerUseProviderAnthropic,
geometry.DeclaredWidth,
geometry.DeclaredHeight,
func(_ context.Context) (workspacesdk.AgentConn, error) {
@@ -503,7 +505,7 @@ func TestComputerUseTool_Run_ConnError(t *testing.T) {
t.Parallel()
geometry := workspacesdk.DefaultDesktopGeometry()
tool := chattool.NewComputerUseTool(chattool.ComputerUseProviderAnthropic, geometry.DeclaredWidth, geometry.DeclaredHeight, func(_ context.Context) (workspacesdk.AgentConn, error) {
tool := chattool.NewComputerUseTool(codersdk.ChatComputerUseProviderAnthropic, geometry.DeclaredWidth, geometry.DeclaredHeight, func(_ context.Context) (workspacesdk.AgentConn, error) {
return nil, xerrors.New("workspace not available")
}, nil, quartz.NewReal(), slogtest.Make(t, nil))
@@ -523,7 +525,7 @@ func TestComputerUseTool_Run_InvalidInput(t *testing.T) {
t.Parallel()
geometry := workspacesdk.DefaultDesktopGeometry()
tool := chattool.NewComputerUseTool(chattool.ComputerUseProviderAnthropic, geometry.DeclaredWidth, geometry.DeclaredHeight, func(_ context.Context) (workspacesdk.AgentConn, error) {
tool := chattool.NewComputerUseTool(codersdk.ChatComputerUseProviderAnthropic, geometry.DeclaredWidth, geometry.DeclaredHeight, func(_ context.Context) (workspacesdk.AgentConn, error) {
return nil, xerrors.New("should not be called")
}, nil, quartz.NewReal(), slogtest.Make(t, nil))
@@ -999,7 +1001,7 @@ func newOpenAIComputerUseTool(
) fantasy.AgentTool {
t.Helper()
return chattool.NewComputerUseTool(
chattool.ComputerUseProviderOpenAI,
codersdk.ChatComputerUseProviderOpenAI,
geometry.DeclaredWidth,
geometry.DeclaredHeight,
func(_ context.Context) (workspacesdk.AgentConn, error) {
+8 -8
View File
@@ -14,6 +14,7 @@ import (
openaicomputeruse "github.com/coder/coder/v2/coderd/x/chatd/chatopenai/computeruse"
"github.com/coder/coder/v2/coderd/x/chatd/chatprovider"
"github.com/coder/coder/v2/coderd/x/chatd/chattool"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/workspacesdk"
"github.com/coder/quartz"
)
@@ -33,7 +34,7 @@ func computerUseConfigContext(ctx context.Context) context.Context {
func (p *Server) computerUseProviderAndModelFromConfig(
ctx context.Context,
) (provider, modelProvider, modelName string, err error) {
) (provider codersdk.ChatComputerUseProvider, modelProvider, modelName string, err error) {
rawProvider, err := p.db.GetChatComputerUseProvider(
computerUseConfigContext(ctx),
)
@@ -41,10 +42,9 @@ func (p *Server) computerUseProviderAndModelFromConfig(
return "", "", "", xerrors.Errorf("get computer use provider: %w", err)
}
provider = strings.TrimSpace(rawProvider)
if provider == "" {
provider = chattool.ComputerUseProviderAnthropic
}
provider = chattool.DefaultComputerUseProvider(
codersdk.ChatComputerUseProvider(strings.TrimSpace(rawProvider)),
)
modelProvider, modelName, ok := chattool.DefaultComputerUseModel(provider)
if !ok {
@@ -61,7 +61,7 @@ func (p *Server) resolveComputerUseModel(
ctx context.Context,
chat database.Chat,
route aiGatewayModelRoute,
computerUseProvider string,
computerUseProvider codersdk.ChatComputerUseProvider,
computerUseModelProvider string,
computerUseModelName string,
modelOpts modelBuildOptions,
@@ -104,7 +104,7 @@ func (p *Server) resolveComputerUseModel(
}
type computerUseProviderToolOptions struct {
provider string
provider codersdk.ChatComputerUseProvider
isPlanModeTurn bool
isComputerUse bool
getWorkspaceConn func(context.Context) (workspacesdk.AgentConn, error)
@@ -155,7 +155,7 @@ func appendComputerUseProviderTool(
opts.logger,
),
}
if opts.provider == chattool.ComputerUseProviderOpenAI {
if opts.provider == codersdk.ChatComputerUseProviderOpenAI {
// OpenAI computer-use image results need detail metadata so the model receives
// the screenshot at original detail when the chat loop sends the tool result.
providerTool.ResultProviderMetadata = openaicomputeruse.ResultProviderMetadata
@@ -778,7 +778,7 @@ func TestAIBridgeComputerUseModelUsesRoute(t *testing.T) {
server := &Server{
aibridgeTransportFactory: aibridgeTestFactoryPointer(factory),
}
provider := chattool.ComputerUseProviderOpenAI
provider := codersdk.ChatComputerUseProviderOpenAI
modelProvider, modelName, ok := chattool.DefaultComputerUseModel(provider)
require.True(t, ok)
@@ -795,7 +795,7 @@ func TestAIBridgeComputerUseModelUsesRoute(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, model)
require.False(t, debugEnabled)
require.Equal(t, chattool.ComputerUseProviderOpenAI, resolvedProvider)
require.EqualValues(t, codersdk.ChatComputerUseProviderOpenAI, resolvedProvider)
require.Equal(t, modelName, resolvedModel)
require.Equal(t, "primary-openai", factory.providerName)
require.Equal(t, aibridge.SourceAgents, factory.source)
@@ -813,7 +813,7 @@ func TestResolveComputerUseModel_AIGatewayMissingAPIKeyID(t *testing.T) {
server := &Server{
aibridgeTransportFactory: aibridgeTestFactoryPointer(factory),
}
provider := chattool.ComputerUseProviderOpenAI
provider := codersdk.ChatComputerUseProviderOpenAI
modelProvider, modelName, ok := chattool.DefaultComputerUseModel(provider)
require.True(t, ok)
+2 -2
View File
@@ -134,11 +134,11 @@ func allSubagentDefinitions() []subagentDefinition {
if err != nil {
return childSubagentChatOptions{}, err
}
providerKeys, err := p.resolveUserProviderAPIKeysForProviderType(ctx, currentChat.OwnerID, provider)
providerKeys, err := p.resolveUserProviderAPIKeysForProviderType(ctx, currentChat.OwnerID, string(provider))
if err != nil {
return childSubagentChatOptions{}, err
}
if !userCanUseProviderKeys(providerKeys, provider) {
if !userCanUseProviderKeys(providerKeys, string(provider)) {
return childSubagentChatOptions{}, xerrors.Errorf(
`API key for computer-use provider %q is not configured`,
provider,
+4 -4
View File
@@ -2479,7 +2479,7 @@ func TestSpawnAgent_ComputerUseAvailabilityUsesConfiguredProvider(t *testing.T)
ctx := chatdTestContext(t)
require.NoError(t, db.UpsertChatComputerUseProvider(
ctx,
chattool.ComputerUseProviderOpenAI,
string(codersdk.ChatComputerUseProviderOpenAI),
))
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
@@ -2499,7 +2499,7 @@ func TestSpawnAgent_ComputerUseRejectsMissingConfiguredProvider(t *testing.T) {
ctx := chatdTestContext(t)
require.NoError(t, db.UpsertChatComputerUseProvider(
ctx,
chattool.ComputerUseProviderOpenAI,
string(codersdk.ChatComputerUseProviderOpenAI),
))
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
@@ -2513,7 +2513,7 @@ func TestSpawnAgent_ComputerUseRejectsMissingConfiguredProvider(t *testing.T) {
model := insertInternalChatModelConfigForProvider(
t,
db,
chattool.ComputerUseProviderOpenAI,
string(codersdk.ChatComputerUseProviderOpenAI),
"gpt-4o-mini",
true,
)
@@ -2921,7 +2921,7 @@ func TestSpawnAgent_ComputerUseUsesComputerUseModelNotParent(t *testing.T) {
require.Equal(t, parentChat.AgentID, childChat.AgentID)
require.True(t, childChat.Mode.Valid)
assert.Equal(t, database.ChatModeComputerUse, childChat.Mode.ChatMode)
computerUseModelProvider, computerUseModelName, ok := chattool.DefaultComputerUseModel(chattool.ComputerUseProviderAnthropic)
computerUseModelProvider, computerUseModelName, ok := chattool.DefaultComputerUseModel(codersdk.ChatComputerUseProviderAnthropic)
require.True(t, ok)
assert.NotEqual(t, string(seedProvider.Type), computerUseModelProvider,
"computer use model provider must differ from parent model provider")