mirror of
https://github.com/coder/coder.git
synced 2026-09-21 20:51:01 +08:00
fix!: rename chat stream silence timeout error (#25973)
Renames the Agents chat stream-silence error from `startup_timeout` to `stream_silence_timeout` now that the timeout applies to any gap between provider stream parts, not just first-token startup. Updates the SDK enum, generated API docs/types, chat error copy, and Agents UI stories/status labels so the user-facing wording describes a stalled provider response instead of startup delay. > **Breaking change:** This is a very minor breaking change for the Coder Agents API: the public chat error kind enum no longer includes `startup_timeout`, so clients matching that specific value should handle `stream_silence_timeout` instead.
This commit is contained in:
Generated
+2
-2
@@ -16723,7 +16723,7 @@ const docTemplate = `{
|
||||
"overloaded",
|
||||
"rate_limit",
|
||||
"timeout",
|
||||
"startup_timeout",
|
||||
"stream_silence_timeout",
|
||||
"auth",
|
||||
"config",
|
||||
"usage_limit",
|
||||
@@ -16735,7 +16735,7 @@ const docTemplate = `{
|
||||
"ChatErrorKindOverloaded",
|
||||
"ChatErrorKindRateLimit",
|
||||
"ChatErrorKindTimeout",
|
||||
"ChatErrorKindStartupTimeout",
|
||||
"ChatErrorKindStreamSilenceTimeout",
|
||||
"ChatErrorKindAuth",
|
||||
"ChatErrorKindConfig",
|
||||
"ChatErrorKindUsageLimit",
|
||||
|
||||
Generated
+2
-2
@@ -15049,7 +15049,7 @@
|
||||
"overloaded",
|
||||
"rate_limit",
|
||||
"timeout",
|
||||
"startup_timeout",
|
||||
"stream_silence_timeout",
|
||||
"auth",
|
||||
"config",
|
||||
"usage_limit",
|
||||
@@ -15061,7 +15061,7 @@
|
||||
"ChatErrorKindOverloaded",
|
||||
"ChatErrorKindRateLimit",
|
||||
"ChatErrorKindTimeout",
|
||||
"ChatErrorKindStartupTimeout",
|
||||
"ChatErrorKindStreamSilenceTimeout",
|
||||
"ChatErrorKindAuth",
|
||||
"ChatErrorKindConfig",
|
||||
"ChatErrorKindUsageLimit",
|
||||
|
||||
@@ -981,21 +981,21 @@ func TestClassify_StatusCodeBeatsHTTP2Transport(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassify_StartupTimeoutWrappedClassificationWins(t *testing.T) {
|
||||
func TestClassify_StreamSilenceTimeoutWrappedClassificationWins(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
wrapped := chaterror.WithClassification(
|
||||
xerrors.New("context canceled"),
|
||||
chaterror.ClassifiedError{
|
||||
Kind: codersdk.ChatErrorKindStartupTimeout,
|
||||
Kind: codersdk.ChatErrorKindStreamSilenceTimeout,
|
||||
Provider: "openai",
|
||||
Retryable: true,
|
||||
},
|
||||
)
|
||||
|
||||
require.Equal(t, chaterror.ClassifiedError{
|
||||
Message: "OpenAI did not start responding in time.",
|
||||
Kind: codersdk.ChatErrorKindStartupTimeout,
|
||||
Message: "OpenAI did not send response data in time.",
|
||||
Kind: codersdk.ChatErrorKindStreamSilenceTimeout,
|
||||
Provider: "openai",
|
||||
Retryable: true,
|
||||
StatusCode: 0,
|
||||
|
||||
@@ -28,9 +28,9 @@ func terminalMessage(classified ClassifiedError) string {
|
||||
}
|
||||
return stringutil.Capitalize(fmt.Sprintf("%s is temporarily unavailable.", subject))
|
||||
|
||||
case codersdk.ChatErrorKindStartupTimeout:
|
||||
case codersdk.ChatErrorKindStreamSilenceTimeout:
|
||||
return stringutil.Capitalize(fmt.Sprintf(
|
||||
"%s did not start responding in time.", subject,
|
||||
"%s did not send response data in time.", subject,
|
||||
))
|
||||
|
||||
case codersdk.ChatErrorKindUsageLimit:
|
||||
@@ -89,9 +89,9 @@ func retryMessage(classified ClassifiedError) string {
|
||||
return stringutil.Capitalize(fmt.Sprintf("%s is rate limiting requests.", subject))
|
||||
case codersdk.ChatErrorKindTimeout:
|
||||
return stringutil.Capitalize(fmt.Sprintf("%s is temporarily unavailable.", subject))
|
||||
case codersdk.ChatErrorKindStartupTimeout:
|
||||
case codersdk.ChatErrorKindStreamSilenceTimeout:
|
||||
return stringutil.Capitalize(fmt.Sprintf(
|
||||
"%s did not start responding in time.", subject,
|
||||
"%s did not send response data in time.", subject,
|
||||
))
|
||||
case codersdk.ChatErrorKindAuth:
|
||||
return fmt.Sprintf(
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
// TestTerminalMessage covers the per-provider "temporarily
|
||||
// unavailable" copy, the startup-timeout copy, and the generic
|
||||
// unavailable" copy, the stream-silence timeout copy, and the generic
|
||||
// fallback string for its intended (unclassified, non-retryable)
|
||||
// path.
|
||||
func TestTerminalMessage(t *testing.T) {
|
||||
@@ -54,18 +54,18 @@ func TestTerminalMessage(t *testing.T) {
|
||||
want: "The request timed out before it completed.",
|
||||
},
|
||||
{
|
||||
name: "StartupTimeout_Anthropic",
|
||||
kind: codersdk.ChatErrorKindStartupTimeout,
|
||||
name: "StreamSilenceTimeout_Anthropic",
|
||||
kind: codersdk.ChatErrorKindStreamSilenceTimeout,
|
||||
provider: "anthropic",
|
||||
retryable: true,
|
||||
want: "Anthropic did not start responding in time.",
|
||||
want: "Anthropic did not send response data in time.",
|
||||
},
|
||||
{
|
||||
name: "StartupTimeout_OpenAI",
|
||||
kind: codersdk.ChatErrorKindStartupTimeout,
|
||||
name: "StreamSilenceTimeout_OpenAI",
|
||||
kind: codersdk.ChatErrorKindStreamSilenceTimeout,
|
||||
provider: "openai",
|
||||
retryable: true,
|
||||
want: "OpenAI did not start responding in time.",
|
||||
want: "OpenAI did not send response data in time.",
|
||||
},
|
||||
{
|
||||
// Generic fallback reserved for genuinely
|
||||
|
||||
@@ -867,7 +867,7 @@ func classifyStreamSilenceTimeout(
|
||||
err = errStreamSilenceTimeout
|
||||
}
|
||||
return chaterror.WithClassification(err, chaterror.ClassifiedError{
|
||||
Kind: codersdk.ChatErrorKindStartupTimeout,
|
||||
Kind: codersdk.ChatErrorKindStreamSilenceTimeout,
|
||||
Provider: provider,
|
||||
Retryable: true,
|
||||
})
|
||||
|
||||
@@ -700,12 +700,12 @@ func TestRun_RetriesSilenceTimeoutWhileOpeningStream(t *testing.T) {
|
||||
require.NoError(t, awaitRunResult(ctx, t, done))
|
||||
require.Equal(t, 2, attempts)
|
||||
require.Len(t, retries, 1)
|
||||
require.Equal(t, codersdk.ChatErrorKindStartupTimeout, retries[0].Kind)
|
||||
require.Equal(t, codersdk.ChatErrorKindStreamSilenceTimeout, retries[0].Kind)
|
||||
require.True(t, retries[0].Retryable)
|
||||
require.Equal(t, "openai", retries[0].Provider)
|
||||
require.Equal(
|
||||
t,
|
||||
"OpenAI did not start responding in time.",
|
||||
"OpenAI did not send response data in time.",
|
||||
retries[0].Message,
|
||||
)
|
||||
select {
|
||||
@@ -930,12 +930,12 @@ func TestRun_RetriesSilenceTimeoutBeforeFirstPart(t *testing.T) {
|
||||
require.NoError(t, awaitRunResult(ctx, t, done))
|
||||
require.Equal(t, 2, attempts)
|
||||
require.Len(t, retries, 1)
|
||||
require.Equal(t, codersdk.ChatErrorKindStartupTimeout, retries[0].Kind)
|
||||
require.Equal(t, codersdk.ChatErrorKindStreamSilenceTimeout, retries[0].Kind)
|
||||
require.True(t, retries[0].Retryable)
|
||||
require.Equal(t, "openai", retries[0].Provider)
|
||||
require.Equal(
|
||||
t,
|
||||
"OpenAI did not start responding in time.",
|
||||
"OpenAI did not send response data in time.",
|
||||
retries[0].Message,
|
||||
)
|
||||
select {
|
||||
@@ -1161,7 +1161,7 @@ func TestRun_RetriesSilenceTimeoutBetweenParts(t *testing.T) {
|
||||
require.NoError(t, awaitRunResult(ctx, t, done))
|
||||
require.Equal(t, 2, attempts)
|
||||
require.Len(t, retries, 1)
|
||||
require.Equal(t, codersdk.ChatErrorKindStartupTimeout, retries[0].Kind)
|
||||
require.Equal(t, codersdk.ChatErrorKindStreamSilenceTimeout, retries[0].Kind)
|
||||
require.True(t, retries[0].Retryable)
|
||||
require.Equal(t, "openai", retries[0].Provider)
|
||||
select {
|
||||
@@ -1278,12 +1278,12 @@ func TestRun_RetriesSilenceTimeoutWhenStreamStaysSilent(t *testing.T) {
|
||||
require.NoError(t, awaitRunResult(ctx, t, done))
|
||||
require.Equal(t, 2, attempts)
|
||||
require.Len(t, retries, 1)
|
||||
require.Equal(t, codersdk.ChatErrorKindStartupTimeout, retries[0].Kind)
|
||||
require.Equal(t, codersdk.ChatErrorKindStreamSilenceTimeout, retries[0].Kind)
|
||||
require.True(t, retries[0].Retryable)
|
||||
require.Equal(t, "openai", retries[0].Provider)
|
||||
require.Equal(
|
||||
t,
|
||||
"OpenAI did not start responding in time.",
|
||||
"OpenAI did not send response data in time.",
|
||||
retries[0].Message,
|
||||
)
|
||||
select {
|
||||
|
||||
@@ -293,7 +293,7 @@ func TestRecordStreamRetry(t *testing.T) {
|
||||
{name: "overloaded", kind: codersdk.ChatErrorKindOverloaded},
|
||||
{name: "rate_limit", kind: codersdk.ChatErrorKindRateLimit},
|
||||
{name: "timeout", kind: codersdk.ChatErrorKindTimeout},
|
||||
{name: "startup_timeout", kind: codersdk.ChatErrorKindStartupTimeout},
|
||||
{name: "stream_silence_timeout", kind: codersdk.ChatErrorKindStreamSilenceTimeout},
|
||||
{name: "auth", kind: codersdk.ChatErrorKindAuth},
|
||||
{name: "config", kind: codersdk.ChatErrorKindConfig},
|
||||
{name: "missing_key", kind: codersdk.ChatErrorKindMissingKey},
|
||||
|
||||
+11
-11
@@ -1525,16 +1525,16 @@ type ChatStreamStatus struct {
|
||||
type ChatErrorKind string
|
||||
|
||||
const (
|
||||
ChatErrorKindGeneric ChatErrorKind = "generic"
|
||||
ChatErrorKindOverloaded ChatErrorKind = "overloaded"
|
||||
ChatErrorKindRateLimit ChatErrorKind = "rate_limit"
|
||||
ChatErrorKindTimeout ChatErrorKind = "timeout"
|
||||
ChatErrorKindStartupTimeout ChatErrorKind = "startup_timeout"
|
||||
ChatErrorKindAuth ChatErrorKind = "auth"
|
||||
ChatErrorKindConfig ChatErrorKind = "config"
|
||||
ChatErrorKindUsageLimit ChatErrorKind = "usage_limit"
|
||||
ChatErrorKindMissingKey ChatErrorKind = "missing_key"
|
||||
ChatErrorKindProviderDisabled ChatErrorKind = "provider_disabled"
|
||||
ChatErrorKindGeneric ChatErrorKind = "generic"
|
||||
ChatErrorKindOverloaded ChatErrorKind = "overloaded"
|
||||
ChatErrorKindRateLimit ChatErrorKind = "rate_limit"
|
||||
ChatErrorKindTimeout ChatErrorKind = "timeout"
|
||||
ChatErrorKindStreamSilenceTimeout ChatErrorKind = "stream_silence_timeout"
|
||||
ChatErrorKindAuth ChatErrorKind = "auth"
|
||||
ChatErrorKindConfig ChatErrorKind = "config"
|
||||
ChatErrorKindUsageLimit ChatErrorKind = "usage_limit"
|
||||
ChatErrorKindMissingKey ChatErrorKind = "missing_key"
|
||||
ChatErrorKindProviderDisabled ChatErrorKind = "provider_disabled"
|
||||
)
|
||||
|
||||
// AllChatErrorKinds contains every ChatErrorKind value.
|
||||
@@ -1544,7 +1544,7 @@ var AllChatErrorKinds = []ChatErrorKind{
|
||||
ChatErrorKindOverloaded,
|
||||
ChatErrorKindRateLimit,
|
||||
ChatErrorKindTimeout,
|
||||
ChatErrorKindStartupTimeout,
|
||||
ChatErrorKindStreamSilenceTimeout,
|
||||
ChatErrorKindAuth,
|
||||
ChatErrorKindConfig,
|
||||
ChatErrorKindUsageLimit,
|
||||
|
||||
Generated
+7
-7
@@ -292,13 +292,13 @@ Status Code **200**
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value(s) |
|
||||
|---------------|------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `client_type` | `api`, `ui` |
|
||||
| `kind` | `auth`, `config`, `generic`, `missing_key`, `overloaded`, `provider_disabled`, `rate_limit`, `startup_timeout`, `timeout`, `usage_limit` |
|
||||
| `type` | `context-file`, `file`, `file-reference`, `reasoning`, `skill`, `source`, `text`, `tool-call`, `tool-result` |
|
||||
| `plan_mode` | `plan` |
|
||||
| `status` | `completed`, `error`, `paused`, `pending`, `requires_action`, `running`, `waiting` |
|
||||
| Property | Value(s) |
|
||||
|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `client_type` | `api`, `ui` |
|
||||
| `kind` | `auth`, `config`, `generic`, `missing_key`, `overloaded`, `provider_disabled`, `rate_limit`, `stream_silence_timeout`, `timeout`, `usage_limit` |
|
||||
| `type` | `context-file`, `file`, `file-reference`, `reasoning`, `skill`, `source`, `text`, `tool-call`, `tool-result` |
|
||||
| `plan_mode` | `plan` |
|
||||
| `status` | `completed`, `error`, `paused`, `pending`, `requires_action`, `running`, `waiting` |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
|
||||
Generated
+3
-3
@@ -2703,9 +2703,9 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Value(s) |
|
||||
|------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `auth`, `config`, `generic`, `missing_key`, `overloaded`, `provider_disabled`, `rate_limit`, `startup_timeout`, `timeout`, `usage_limit` |
|
||||
| Value(s) |
|
||||
|-------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `auth`, `config`, `generic`, `missing_key`, `overloaded`, `provider_disabled`, `rate_limit`, `stream_silence_timeout`, `timeout`, `usage_limit` |
|
||||
|
||||
## codersdk.ChatFileMetadata
|
||||
|
||||
|
||||
Generated
+2
-2
@@ -1992,7 +1992,7 @@ export type ChatErrorKind =
|
||||
| "overloaded"
|
||||
| "provider_disabled"
|
||||
| "rate_limit"
|
||||
| "startup_timeout"
|
||||
| "stream_silence_timeout"
|
||||
| "timeout"
|
||||
| "usage_limit";
|
||||
|
||||
@@ -2004,7 +2004,7 @@ export const ChatErrorKinds: ChatErrorKind[] = [
|
||||
"overloaded",
|
||||
"provider_disabled",
|
||||
"rate_limit",
|
||||
"startup_timeout",
|
||||
"stream_silence_timeout",
|
||||
"timeout",
|
||||
"usage_limit",
|
||||
];
|
||||
|
||||
@@ -257,14 +257,14 @@ export const RetryingTimeoutAnthropic: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
/** Terminal startup timeouts get a specific heading without provider metadata. */
|
||||
export const TerminalStartupTimeoutError: Story = {
|
||||
/** Terminal stream-silence timeouts get a specific heading without provider metadata. */
|
||||
export const TerminalStreamSilenceTimeoutError: Story = {
|
||||
args: {
|
||||
...defaultArgs,
|
||||
liveStatus: buildLiveStatus({
|
||||
persistedError: {
|
||||
kind: "startup_timeout",
|
||||
message: "Anthropic did not start responding in time.",
|
||||
kind: "stream_silence_timeout",
|
||||
message: "Anthropic did not send response data in time.",
|
||||
provider: "anthropic",
|
||||
retryable: true,
|
||||
},
|
||||
@@ -273,10 +273,10 @@ export const TerminalStartupTimeoutError: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
expect(
|
||||
canvas.getByRole("heading", { name: /startup timed out/i }),
|
||||
canvas.getByRole("heading", { name: /response stalled/i }),
|
||||
).toBeVisible();
|
||||
expect(
|
||||
canvas.getByText(/anthropic did not start responding in time./i),
|
||||
canvas.getByText(/anthropic did not send response data in time./i),
|
||||
).toBeVisible();
|
||||
expect(canvas.queryByText(/please try again/i)).not.toBeInTheDocument();
|
||||
expect(canvas.queryByText(/^retryable$/i)).not.toBeInTheDocument();
|
||||
|
||||
@@ -225,15 +225,15 @@ export const RetryTimeout: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
/** Startup timeouts explain the first-token delay before retrying. */
|
||||
export const RetryStartupTimeout: Story = {
|
||||
/** Stream-silence timeouts explain the first-token delay before retrying. */
|
||||
export const RetryStreamSilenceTimeout: Story = {
|
||||
args: {
|
||||
streamState: null,
|
||||
streamTools: [],
|
||||
liveStatus: buildLiveStatus({
|
||||
retryState: buildRetryState({
|
||||
kind: "startup_timeout",
|
||||
error: "Anthropic did not start responding in time.",
|
||||
kind: "stream_silence_timeout",
|
||||
error: "Anthropic did not send response data in time.",
|
||||
}),
|
||||
isAwaitingFirstStreamChunk: true,
|
||||
}),
|
||||
@@ -241,10 +241,10 @@ export const RetryStartupTimeout: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
expect(
|
||||
canvas.getByRole("heading", { name: /startup timed out/i }),
|
||||
canvas.getByRole("heading", { name: /response stalled/i }),
|
||||
).toBeVisible();
|
||||
expect(
|
||||
canvas.getByText(/anthropic did not start responding in time/i),
|
||||
canvas.getByText(/anthropic did not send response data in time/i),
|
||||
).toBeVisible();
|
||||
expect(canvas.queryByText(/please try again/i)).not.toBeInTheDocument();
|
||||
expect(canvas.queryByText(/provider anthropic/i)).not.toBeInTheDocument();
|
||||
|
||||
@@ -34,8 +34,8 @@ export const getErrorTitle = (
|
||||
return "Rate limited";
|
||||
case "timeout":
|
||||
return "Request timed out";
|
||||
case "startup_timeout":
|
||||
return "Startup timed out";
|
||||
case "stream_silence_timeout":
|
||||
return "Response stalled";
|
||||
case "auth":
|
||||
return "Authentication failed";
|
||||
case "config":
|
||||
|
||||
Reference in New Issue
Block a user