Compare commits

...

2 Commits

Author SHA1 Message Date
Aiden b0ae70bc26 test(opencode): narrow stream error assertion 2026-08-21 07:03:11 +00:00
Aiden 75473de591 fix(opencode): retry network finish errors 2026-08-21 06:56:46 +00:00
2 changed files with 87 additions and 0 deletions
+22
View File
@@ -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)
if (!(error instanceof ProviderError.ResponseStreamError)) throw error
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",