mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
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:
+3
-3
@@ -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(),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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}),
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user