mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-24 06:33:01 -04:00
fix(opencode): retry network finish errors
This commit is contained in:
@@ -29,6 +29,8 @@ import * as OtelTracer from "@effect/opentelemetry/Tracer"
|
||||
import { LLMAISDK } from "./llm/ai-sdk"
|
||||
import { LLMNativeRuntime } from "./llm/native-runtime"
|
||||
import { LLMRequestPrep } from "./llm/request"
|
||||
import { ProviderError } from "@/provider/error"
|
||||
import type { LanguageModelV3StreamPart } from "@ai-sdk/provider"
|
||||
|
||||
export const OUTPUT_TOKEN_MAX = ProviderTransform.OUTPUT_TOKEN_MAX
|
||||
|
||||
@@ -338,6 +340,26 @@ const live: Layer.Layer<
|
||||
}
|
||||
return args.params
|
||||
},
|
||||
async wrapStream({ doStream }) {
|
||||
const result = await doStream()
|
||||
return {
|
||||
...result,
|
||||
stream: result.stream.pipeThrough(
|
||||
new TransformStream<LanguageModelV3StreamPart, LanguageModelV3StreamPart>({
|
||||
transform(part, controller) {
|
||||
if (part.type === "finish" && part.finishReason.raw === "network_error") {
|
||||
controller.enqueue({
|
||||
type: "error",
|
||||
error: new ProviderError.ResponseStreamError("Provider finish_reason: network_error"),
|
||||
})
|
||||
return
|
||||
}
|
||||
controller.enqueue(part)
|
||||
},
|
||||
}),
|
||||
),
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
@@ -27,6 +27,7 @@ import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { LayerNodePlatform } from "@opencode-ai/core/effect/app-node-platform"
|
||||
import { ProviderError } from "@/provider/error"
|
||||
|
||||
type ConfigModel = NonNullable<NonNullable<ConfigV1.Info["provider"]>[string]["models"]>[string]
|
||||
|
||||
@@ -832,6 +833,70 @@ describe("session.llm.stream", () => {
|
||||
},
|
||||
)
|
||||
|
||||
it.instance(
|
||||
"surfaces network_error finish reasons as retryable stream failures",
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const fixture = loadFixture(vivgridFixture.providerID, vivgridFixture.modelID)
|
||||
const request = waitRequest(
|
||||
"/chat/completions",
|
||||
createEventResponse(
|
||||
[
|
||||
{
|
||||
id: "chatcmpl-network-error",
|
||||
object: "chat.completion.chunk",
|
||||
choices: [{ index: 0, delta: { role: "assistant", content: "" }, finish_reason: "network_error" }],
|
||||
},
|
||||
],
|
||||
true,
|
||||
),
|
||||
)
|
||||
const resolved = yield* Provider.use.getModel(
|
||||
ProviderV2.ID.make(vivgridFixture.providerID),
|
||||
ModelV2.ID.make(fixture.model.id),
|
||||
)
|
||||
const sessionID = SessionID.make("session-test-network-error")
|
||||
const agent = {
|
||||
name: "test",
|
||||
mode: "primary",
|
||||
options: {},
|
||||
permission: [{ permission: "*", pattern: "*", action: "allow" }],
|
||||
} satisfies Agent.Info
|
||||
const user = {
|
||||
id: MessageID.make("msg_user-network-error"),
|
||||
sessionID,
|
||||
role: "user",
|
||||
time: { created: Date.now() },
|
||||
agent: agent.name,
|
||||
model: { providerID: ProviderV2.ID.make(vivgridFixture.providerID), modelID: resolved.id },
|
||||
} satisfies SessionV1.User
|
||||
|
||||
const error = yield* drain({
|
||||
user,
|
||||
sessionID,
|
||||
model: resolved,
|
||||
agent,
|
||||
system: ["You are a helpful assistant."],
|
||||
messages: [{ role: "user", content: "Hello" }],
|
||||
tools: {},
|
||||
}).pipe(Effect.flip)
|
||||
yield* Effect.promise(() => request)
|
||||
|
||||
expect(error).toBeInstanceOf(ProviderError.ResponseStreamError)
|
||||
expect(error.message).toBe("Provider finish_reason: network_error")
|
||||
}),
|
||||
{
|
||||
config: () => ({
|
||||
enabled_providers: [vivgridFixture.providerID],
|
||||
provider: {
|
||||
[vivgridFixture.providerID]: {
|
||||
options: { apiKey: "test-key", baseURL: `${state.server!.url.origin}/v1` },
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
)
|
||||
|
||||
const cerebrasFixture = { providerID: "cerebras", modelID: "gpt-oss-120b" }
|
||||
it.instance(
|
||||
"replays Cerebras assistant reasoning using the provider-supported field",
|
||||
|
||||
Reference in New Issue
Block a user