Prepare Effect HttpApi backend parity (#24853)

This commit is contained in:
Kit Langton
2026-04-29 09:34:50 -04:00
committed by GitHub
parent 65ba1f6c13
commit 6015084fa2
98 changed files with 4290 additions and 2766 deletions

View File

@@ -42,7 +42,7 @@ export const OutputLengthError = namedSchemaError("MessageOutputLengthError", {}
export const AbortedError = namedSchemaError("MessageAbortedError", { message: Schema.String })
export const StructuredOutputError = namedSchemaError("StructuredOutputError", {
message: Schema.String,
retries: Schema.Number,
retries: NonNegativeInt,
})
export const AuthError = namedSchemaError("ProviderAuthError", {
providerID: Schema.String,
@@ -50,7 +50,7 @@ export const AuthError = namedSchemaError("ProviderAuthError", {
})
export const APIError = namedSchemaError("APIError", {
message: Schema.String,
statusCode: Schema.optional(Schema.Number),
statusCode: Schema.optional(NonNegativeInt),
isRetryable: Schema.Boolean,
responseHeaders: Schema.optional(Schema.Record(Schema.String, Schema.String)),
responseBody: Schema.optional(Schema.String),
@@ -116,8 +116,8 @@ export const TextPart = Schema.Struct({
ignored: Schema.optional(Schema.Boolean),
time: Schema.optional(
Schema.Struct({
start: Schema.Number,
end: Schema.optional(Schema.Number),
start: NonNegativeInt,
end: Schema.optional(NonNegativeInt),
}),
),
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)),
@@ -132,8 +132,8 @@ export const ReasoningPart = Schema.Struct({
text: Schema.String,
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)),
time: Schema.Struct({
start: Schema.Number,
end: Schema.optional(Schema.Number),
start: NonNegativeInt,
end: Schema.optional(NonNegativeInt),
}),
})
.annotate({ identifier: "ReasoningPart" })
@@ -143,8 +143,8 @@ export type ReasoningPart = Types.DeepMutable<Schema.Schema.Type<typeof Reasonin
const filePartSourceBase = {
text: Schema.Struct({
value: Schema.String,
start: Schema.Int,
end: Schema.Int,
start: NonNegativeInt,
end: NonNegativeInt,
}).annotate({ identifier: "FilePartSourceText" }),
}
@@ -162,7 +162,7 @@ export const SymbolSource = Schema.Struct({
path: Schema.String,
range: LSP.Range,
name: Schema.String,
kind: Schema.Int,
kind: NonNegativeInt,
})
.annotate({ identifier: "SymbolSource" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
@@ -201,8 +201,8 @@ export const AgentPart = Schema.Struct({
source: Schema.optional(
Schema.Struct({
value: Schema.String,
start: Schema.Int,
end: Schema.Int,
start: NonNegativeInt,
end: NonNegativeInt,
}),
),
})
@@ -242,11 +242,10 @@ export type SubtaskPart = Types.DeepMutable<Schema.Schema.Type<typeof SubtaskPar
export const RetryPart = Schema.Struct({
...partBase,
type: Schema.Literal("retry"),
attempt: Schema.Number,
// APIError is still NamedError-based Zod; bridge via ZodOverride until errors migrate.
error: Schema.Any.annotate({ [ZodOverride]: APIError.Schema }),
attempt: NonNegativeInt,
error: APIError.EffectSchema,
time: Schema.Struct({
created: Schema.Number,
created: NonNegativeInt,
}),
})
.annotate({ identifier: "RetryPart" })
@@ -269,15 +268,15 @@ export const StepFinishPart = Schema.Struct({
type: Schema.Literal("step-finish"),
reason: Schema.String,
snapshot: Schema.optional(Schema.String),
cost: Schema.Number,
cost: Schema.Finite,
tokens: Schema.Struct({
total: Schema.optional(Schema.Number),
input: Schema.Number,
output: Schema.Number,
reasoning: Schema.Number,
total: Schema.optional(NonNegativeInt),
input: NonNegativeInt,
output: NonNegativeInt,
reasoning: NonNegativeInt,
cache: Schema.Struct({
read: Schema.Number,
write: Schema.Number,
read: NonNegativeInt,
write: NonNegativeInt,
}),
}),
})
@@ -300,7 +299,7 @@ export const ToolStateRunning = Schema.Struct({
title: Schema.optional(Schema.String),
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)),
time: Schema.Struct({
start: Schema.Number,
start: NonNegativeInt,
}),
})
.annotate({ identifier: "ToolStateRunning" })
@@ -314,9 +313,9 @@ export const ToolStateCompleted = Schema.Struct({
title: Schema.String,
metadata: Schema.Record(Schema.String, Schema.Any),
time: Schema.Struct({
start: Schema.Number,
end: Schema.Number,
compacted: Schema.optional(Schema.Number),
start: NonNegativeInt,
end: NonNegativeInt,
compacted: Schema.optional(NonNegativeInt),
}),
attachments: Schema.optional(Schema.Array(FilePart)),
})
@@ -336,8 +335,8 @@ export const ToolStateError = Schema.Struct({
error: Schema.String,
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)),
time: Schema.Struct({
start: Schema.Number,
end: Schema.Number,
start: NonNegativeInt,
end: NonNegativeInt,
}),
})
.annotate({ identifier: "ToolStateError" })
@@ -380,7 +379,7 @@ export const User = Schema.Struct({
...messageBase,
role: Schema.Literal("user"),
time: Schema.Struct({
created: Schema.Number,
created: NonNegativeInt,
}),
format: Schema.optional(_Format),
summary: Schema.optional(
@@ -447,9 +446,7 @@ export type Part =
| RetryPart
| CompactionPart
// Errors are still NamedError-based Zod; bridge via ZodOverride so the derived
// Zod + JSON Schema emit the original discriminatedUnion shape. Migrating the
// error classes to Schema.TaggedErrorClass is a separate slice.
// Zod discriminated union kept for the legacy Hono OpenAPI path.
const AssistantErrorZod = z.discriminatedUnion("name", [
AuthError.Schema,
NamedError.Unknown.Schema,
@@ -461,6 +458,17 @@ const AssistantErrorZod = z.discriminatedUnion("name", [
])
type AssistantError = z.infer<typeof AssistantErrorZod>
// Effect Schema for the same union — used by HttpApi OpenAPI generation.
const AssistantErrorSchema = Schema.Union([
AuthError.EffectSchema,
Schema.Struct({ name: Schema.Literal("UnknownError"), data: Schema.Struct({ message: Schema.String }) }).annotate({ identifier: "UnknownError" }),
OutputLengthError.EffectSchema,
AbortedError.EffectSchema,
StructuredOutputError.EffectSchema,
ContextOverflowError.EffectSchema,
APIError.EffectSchema,
]).annotate({ discriminator: "name" })
// ── Prompt input schemas ─────────────────────────────────────────────────────
//
// Consumers of `SessionPrompt.PromptInput.parts` send part drafts without the
@@ -477,8 +485,8 @@ export const TextPartInput = Schema.Struct({
ignored: Schema.optional(Schema.Boolean),
time: Schema.optional(
Schema.Struct({
start: Schema.Number,
end: Schema.optional(Schema.Number),
start: NonNegativeInt,
end: Schema.optional(NonNegativeInt),
}),
),
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)),
@@ -506,8 +514,8 @@ export const AgentPartInput = Schema.Struct({
source: Schema.optional(
Schema.Struct({
value: Schema.String,
start: Schema.Int,
end: Schema.Int,
start: NonNegativeInt,
end: NonNegativeInt,
}),
),
})
@@ -537,10 +545,10 @@ export const Assistant = Schema.Struct({
...messageBase,
role: Schema.Literal("assistant"),
time: Schema.Struct({
created: Schema.Number,
completed: Schema.optional(Schema.Number),
created: NonNegativeInt,
completed: Schema.optional(NonNegativeInt),
}),
error: Schema.optional(Schema.Any.annotate({ [ZodOverride]: AssistantErrorZod })),
error: Schema.optional(AssistantErrorSchema),
parentID: MessageID,
modelID: ModelID,
providerID: ProviderID,
@@ -554,15 +562,15 @@ export const Assistant = Schema.Struct({
root: Schema.String,
}),
summary: Schema.optional(Schema.Boolean),
cost: Schema.Number,
cost: Schema.Finite,
tokens: Schema.Struct({
total: Schema.optional(Schema.Number),
input: Schema.Number,
output: Schema.Number,
reasoning: Schema.Number,
total: Schema.optional(NonNegativeInt),
input: NonNegativeInt,
output: NonNegativeInt,
reasoning: NonNegativeInt,
cache: Schema.Struct({
read: Schema.Number,
write: Schema.Number,
read: NonNegativeInt,
write: NonNegativeInt,
}),
}),
structured: Schema.optional(Schema.Any),
@@ -594,7 +602,7 @@ const RemovedEventSchema = Schema.Struct({
const PartUpdatedEventSchema = Schema.Struct({
sessionID: SessionID,
part: _Part,
time: Schema.Number,
time: NonNegativeInt,
})
const PartRemovedEventSchema = Schema.Struct({
@@ -651,7 +659,7 @@ export type WithParts = {
const Cursor = Schema.Struct({
id: MessageID,
time: Schema.Number,
time: Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0)),
})
type Cursor = typeof Cursor.Type

View File

@@ -2,7 +2,7 @@ import { Schema } from "effect"
import { SessionID } from "./schema"
import { ModelID, ProviderID } from "../provider/schema"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
import { NonNegativeInt, withStatics } from "@/util/schema"
import { namedSchemaError } from "@/util/named-schema-error"
export const OutputLengthError = namedSchemaError("MessageOutputLengthError", {})
@@ -33,7 +33,7 @@ const UnknownErrorEffect = Schema.Struct({
export const ToolCall = Schema.Struct({
state: Schema.Literal("call"),
step: Schema.optional(Schema.Number),
step: Schema.optional(NonNegativeInt),
toolCallId: Schema.String,
toolName: Schema.String,
args: Schema.Unknown,
@@ -44,7 +44,7 @@ export type ToolCall = Schema.Schema.Type<typeof ToolCall>
export const ToolPartialCall = Schema.Struct({
state: Schema.Literal("partial-call"),
step: Schema.optional(Schema.Number),
step: Schema.optional(NonNegativeInt),
toolCallId: Schema.String,
toolName: Schema.String,
args: Schema.Unknown,
@@ -55,7 +55,7 @@ export type ToolPartialCall = Schema.Schema.Type<typeof ToolPartialCall>
export const ToolResult = Schema.Struct({
state: Schema.Literal("result"),
step: Schema.optional(Schema.Number),
step: Schema.optional(NonNegativeInt),
toolCallId: Schema.String,
toolName: Schema.String,
args: Schema.Unknown,
@@ -141,8 +141,8 @@ export const Info = Schema.Struct({
parts: Schema.Array(MessagePart),
metadata: Schema.Struct({
time: Schema.Struct({
created: Schema.Number,
completed: Schema.optional(Schema.Number),
created: NonNegativeInt,
completed: Schema.optional(NonNegativeInt),
}),
error: Schema.optional(Schema.Union([AuthErrorEffect, UnknownErrorEffect, OutputLengthErrorEffect])),
sessionID: SessionID,
@@ -153,8 +153,8 @@ export const Info = Schema.Struct({
title: Schema.String,
snapshot: Schema.optional(Schema.String),
time: Schema.Struct({
start: Schema.Number,
end: Schema.Number,
start: NonNegativeInt,
end: NonNegativeInt,
}),
}),
[Schema.Record(Schema.String, Schema.Unknown)],
@@ -169,15 +169,15 @@ export const Info = Schema.Struct({
cwd: Schema.String,
root: Schema.String,
}),
cost: Schema.Number,
cost: Schema.Finite,
summary: Schema.optional(Schema.Boolean),
tokens: Schema.Struct({
input: Schema.Number,
output: Schema.Number,
reasoning: Schema.Number,
input: NonNegativeInt,
output: NonNegativeInt,
reasoning: NonNegativeInt,
cache: Schema.Struct({
read: Schema.Number,
write: Schema.Number,
read: NonNegativeInt,
write: NonNegativeInt,
}),
}),
}),

View File

@@ -38,7 +38,7 @@ import { Permission } from "@/permission"
import { Global } from "@opencode-ai/core/global"
import { Effect, Layer, Option, Context, Schema, Types } from "effect"
import { zod } from "@/util/effect-zod"
import { optionalOmitUndefined, withStatics } from "@/util/schema"
import { NonNegativeInt, optionalOmitUndefined, withStatics } from "@/util/schema"
const log = Log.create({ service: "session" })
@@ -132,9 +132,9 @@ function sessionPath(worktree: string, cwd: string) {
}
const Summary = Schema.Struct({
additions: Schema.Number,
deletions: Schema.Number,
files: Schema.Number,
additions: NonNegativeInt,
deletions: NonNegativeInt,
files: NonNegativeInt,
diffs: optionalOmitUndefined(Schema.Array(Snapshot.FileDiff)),
})
@@ -143,10 +143,10 @@ const Share = Schema.Struct({
})
const Time = Schema.Struct({
created: Schema.Number,
updated: Schema.Number,
compacting: optionalOmitUndefined(Schema.Number),
archived: optionalOmitUndefined(Schema.Number),
created: NonNegativeInt,
updated: NonNegativeInt,
compacting: optionalOmitUndefined(NonNegativeInt),
archived: optionalOmitUndefined(NonNegativeInt),
})
const Revert = Schema.Struct({
@@ -215,7 +215,7 @@ export const SetTitleInput = Schema.Struct({ sessionID: SessionID, title: Schema
)
export const SetArchivedInput = Schema.Struct({
sessionID: SessionID,
time: Schema.optional(Schema.Number),
time: Schema.optional(NonNegativeInt),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
export const SetPermissionInput = Schema.Struct({
sessionID: SessionID,
@@ -228,7 +228,7 @@ export const SetRevertInput = Schema.Struct({
}).pipe(withStatics((s) => ({ zod: zod(s) })))
export const MessagesInput = Schema.Struct({
sessionID: SessionID,
limit: Schema.optional(Schema.Number),
limit: Schema.optional(NonNegativeInt),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
const CreatedEventSchema = Schema.Struct({
@@ -241,10 +241,10 @@ const UpdatedShare = Schema.Struct({
})
const UpdatedTime = Schema.Struct({
created: Schema.optional(Schema.NullOr(Schema.Number)),
updated: Schema.optional(Schema.NullOr(Schema.Number)),
compacting: Schema.optional(Schema.NullOr(Schema.Number)),
archived: Schema.optional(Schema.NullOr(Schema.Number)),
created: Schema.optional(Schema.NullOr(NonNegativeInt)),
updated: Schema.optional(Schema.NullOr(NonNegativeInt)),
compacting: Schema.optional(Schema.NullOr(NonNegativeInt)),
archived: Schema.optional(Schema.NullOr(NonNegativeInt)),
})
const UpdatedInfo = Schema.Struct({

View File

@@ -3,7 +3,7 @@ import { Bus } from "@/bus"
import { InstanceState } from "@/effect/instance-state"
import { SessionID } from "./schema"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
import { NonNegativeInt, withStatics } from "@/util/schema"
import { Effect, Layer, Context, Schema } from "effect"
import z from "zod"
@@ -13,9 +13,9 @@ export const Info = Schema.Union([
}),
Schema.Struct({
type: Schema.Literal("retry"),
attempt: Schema.Number,
attempt: NonNegativeInt,
message: Schema.String,
next: Schema.Number,
next: NonNegativeInt,
}),
Schema.Struct({
type: Schema.Literal("busy"),