mirror of
https://github.com/coder/coder.git
synced 2026-11-01 04:47:17 +08:00
## Stack Context This stack makes AI Gateway data and budgets the source of truth for AI spend controls. 1. Re-back the per-chat cost endpoint with AI Gateway data (#27328, merged). 2. **This PR:** remove native chat usage limits. 3. Remove native chat cost tracking and its dedicated admin UI (#27330). ## Summary Removes the native usage-limit API, SDK types, SQL, and chat enforcement for deployment, user, and group chat limits. Compact AI Gateway budget indicators remain in the Agents sidebar, user menu, and group settings. Gateway budget rejections and provider quota failures continue to classify as usage-limit errors, including a 409 response for synchronous title generation. Budget-period labels now use the API's UTC boundaries, so users see the same dates in every browser timezone. The documentation explains the AI Gateway replacement, its licensing requirements, and the differences from native limits. No schema is dropped in this release. The usage-limit table, index, user and group columns, constraints, audit mappings, and generated scan fields remain for mixed-version rolling upgrades. #27600 tracks their removal after the compatibility window. ## Breaking change Native day, week, and month chat spend limits are removed and are not migrated. AI Gateway budgets are month-based, group-scoped with per-user overrides, and require the AI Gateway entitlement. Deployments without that entitlement no longer have chat spend enforcement. > Mux prepared this PR on Mike's behalf.
122 lines
3.4 KiB
Go
122 lines
3.4 KiB
Go
package chaterror_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/coder/coder/v2/coderd/x/chatd/chaterror"
|
|
"github.com/coder/coder/v2/codersdk"
|
|
)
|
|
|
|
// TestTerminalMessage covers the per-provider "temporarily
|
|
// unavailable" copy, the stream-silence timeout copy, and the generic
|
|
// fallback string for its intended (unclassified, non-retryable)
|
|
// path.
|
|
func TestTerminalMessage(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
kind codersdk.ChatErrorKind
|
|
provider string
|
|
retryable bool
|
|
statusCode int
|
|
want string
|
|
}{
|
|
{
|
|
name: "Timeout_Retryable_Anthropic",
|
|
kind: codersdk.ChatErrorKindTimeout,
|
|
provider: "anthropic",
|
|
retryable: true,
|
|
want: "Anthropic is temporarily unavailable.",
|
|
},
|
|
{
|
|
name: "Timeout_Retryable_OpenAI",
|
|
kind: codersdk.ChatErrorKindTimeout,
|
|
provider: "openai",
|
|
retryable: true,
|
|
want: "OpenAI is temporarily unavailable.",
|
|
},
|
|
{
|
|
name: "Timeout_Retryable_UnknownProvider",
|
|
kind: codersdk.ChatErrorKindTimeout,
|
|
provider: "",
|
|
retryable: true,
|
|
want: "The AI provider is temporarily unavailable.",
|
|
},
|
|
{
|
|
name: "Timeout_NotRetryable_NoStatus",
|
|
kind: codersdk.ChatErrorKindTimeout,
|
|
provider: "",
|
|
retryable: false,
|
|
want: "The request timed out before it completed.",
|
|
},
|
|
{
|
|
name: "StreamSilenceTimeout_Anthropic",
|
|
kind: codersdk.ChatErrorKindStreamSilenceTimeout,
|
|
provider: "anthropic",
|
|
retryable: true,
|
|
want: "Anthropic did not send response data in time.",
|
|
},
|
|
{
|
|
name: "StreamSilenceTimeout_OpenAI",
|
|
kind: codersdk.ChatErrorKindStreamSilenceTimeout,
|
|
provider: "openai",
|
|
retryable: true,
|
|
want: "OpenAI did not send response data in time.",
|
|
},
|
|
{
|
|
// Generic fallback reserved for genuinely
|
|
// unclassified non-retryable failures.
|
|
name: "Generic_NotRetryable_NoStatus",
|
|
kind: codersdk.ChatErrorKindGeneric,
|
|
provider: "",
|
|
retryable: false,
|
|
want: "The chat request failed unexpectedly.",
|
|
},
|
|
{
|
|
name: "UsageLimit_OpenAI",
|
|
kind: codersdk.ChatErrorKindUsageLimit,
|
|
provider: "openai",
|
|
retryable: false,
|
|
want: "The AI usage limit has been exceeded. Contact an administrator or check the applicable budget and quota settings.",
|
|
},
|
|
{
|
|
name: "UsageLimit_UnknownProvider",
|
|
kind: codersdk.ChatErrorKindUsageLimit,
|
|
provider: "",
|
|
retryable: false,
|
|
want: "The AI usage limit has been exceeded. Contact an administrator or check the applicable budget and quota settings.",
|
|
},
|
|
{
|
|
name: "MissingKey",
|
|
kind: codersdk.ChatErrorKindMissingKey,
|
|
provider: "",
|
|
retryable: false,
|
|
want: "This conversation was started with an API key that is no longer available. Send your message again to continue.",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
classified := chaterror.ClassifiedError{
|
|
Kind: tt.kind,
|
|
Provider: tt.provider,
|
|
Retryable: tt.retryable,
|
|
StatusCode: tt.statusCode,
|
|
}
|
|
// terminalMessage is unexported; round-trip through
|
|
// WithClassification + Classify to exercise it.
|
|
wrapped := chaterror.WithClassification(
|
|
xerrors.New(tt.name),
|
|
classified,
|
|
)
|
|
require.Equal(t, tt.want, chaterror.Classify(wrapped).Message)
|
|
})
|
|
}
|
|
}
|