refactor: use Schema.Finite instead of Schema.Number for OpenAPI-facing schemas

Schema.Number emits anyOf:[number, string enum(Infinity/-Infinity/NaN)] in
OpenAPI because JS numbers include non-finite values. Schema.Finite adds an
isFinite check so the OpenAPI output is just {type:"number"}, eliminating
the need for post-hoc normalization in public.ts.
This commit is contained in:
Kit Langton
2026-04-28 20:20:36 -04:00
parent 97caa6bf82
commit 41367fa3ba
19 changed files with 99 additions and 99 deletions
+3 -3
View File
@@ -31,8 +31,8 @@ export const Info = Schema.Struct({
mode: Schema.Literals(["subagent", "primary", "all"]),
native: Schema.optional(Schema.Boolean),
hidden: Schema.optional(Schema.Boolean),
topP: Schema.optional(Schema.Number),
temperature: Schema.optional(Schema.Number),
topP: Schema.optional(Schema.Finite),
temperature: Schema.optional(Schema.Finite),
color: Schema.optional(Schema.String),
permission: Permission.Ruleset,
model: Schema.optional(
@@ -44,7 +44,7 @@ export const Info = Schema.Struct({
variant: Schema.optional(Schema.String),
prompt: Schema.optional(Schema.String),
options: Schema.Record(Schema.String, Schema.Unknown),
steps: Schema.optional(Schema.Number),
steps: Schema.optional(Schema.Finite),
})
.annotate({ identifier: "Agent" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
+1 -1
View File
@@ -14,7 +14,7 @@ export class Oauth extends Schema.Class<Oauth>("OAuth")({
type: Schema.Literal("oauth"),
refresh: Schema.String,
access: Schema.String,
expires: Schema.Number,
expires: Schema.Finite,
accountId: Schema.optional(Schema.String),
enterpriseUrl: Schema.optional(Schema.String),
}) {}
+1 -1
View File
@@ -38,7 +38,7 @@ export const TuiEvent = {
title: Schema.optional(Schema.String),
message: Schema.String,
variant: Schema.Literals(["info", "success", "warning", "error"]),
duration: Schema.Number.pipe(Schema.withDecodingDefault(Effect.succeed(DEFAULT_TOAST_DURATION))).annotate({
duration: Schema.Finite.pipe(Schema.withDecodingDefault(Effect.succeed(DEFAULT_TOAST_DURATION))).annotate({
description: "Duration in milliseconds",
}),
}),
+2 -2
View File
@@ -26,8 +26,8 @@ const AgentSchema = Schema.StructWithRest(
variant: Schema.optional(Schema.String).annotate({
description: "Default model variant for this agent (applies only when using the agent's configured model).",
}),
temperature: Schema.optional(Schema.Number),
top_p: Schema.optional(Schema.Number),
temperature: Schema.optional(Schema.Finite),
top_p: Schema.optional(Schema.Finite),
prompt: Schema.optional(Schema.String),
tools: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)).annotate({
description: "@deprecated Use 'permission' field instead",
+2 -2
View File
@@ -13,7 +13,7 @@ export const Local = Schema.Struct({
enabled: Schema.optional(Schema.Boolean).annotate({
description: "Enable or disable the MCP server on startup",
}),
timeout: Schema.optional(Schema.Number).annotate({
timeout: Schema.optional(Schema.Finite).annotate({
description: "Timeout in ms for MCP server requests. Defaults to 5000 (5 seconds) if not specified.",
}),
})
@@ -49,7 +49,7 @@ export const Remote = Schema.Struct({
oauth: Schema.optional(Schema.Union([OAuth, Schema.Literal(false)])).annotate({
description: "OAuth authentication configuration for the MCP server. Set to false to disable OAuth auto-detection.",
}),
timeout: Schema.optional(Schema.Number).annotate({
timeout: Schema.optional(Schema.Finite).annotate({
description: "Timeout in ms for MCP server requests. Defaults to 5000 (5 seconds) if not specified.",
}),
})
+2 -2
View File
@@ -23,8 +23,8 @@ export const Event = {
}
const Position = Schema.Struct({
line: Schema.Number,
character: Schema.Number,
line: Schema.Finite,
character: Schema.Finite,
})
export const Range = Schema.Struct({
+3 -3
View File
@@ -35,9 +35,9 @@ const ProjectCommands = Schema.Struct({
})
const ProjectTime = Schema.Struct({
created: Schema.Number,
updated: Schema.Number,
initialized: Schema.optional(Schema.Number),
created: Schema.Finite,
updated: Schema.Finite,
initialized: Schema.optional(Schema.Finite),
})
export const Info = Schema.Struct({
+2 -2
View File
@@ -125,8 +125,8 @@ export type Info = Schema.Schema.Type<typeof Info>
export const FileDiff = Schema.Struct({
file: Schema.String,
patch: Schema.String,
additions: Schema.Number,
deletions: Schema.Number,
additions: Schema.Finite,
deletions: Schema.Finite,
status: Schema.optional(Schema.Literals(["added", "deleted", "modified"])),
})
.annotate({ identifier: "VcsFileDiff" })
+2 -2
View File
@@ -58,13 +58,13 @@ export class Authorization extends Schema.Class<Authorization>("ProviderAuthAuth
}
export const AuthorizeInput = Schema.Struct({
method: Schema.Number.annotate({ description: "Auth method index" }),
method: Schema.Finite.annotate({ description: "Auth method index" }),
inputs: Schema.optional(Schema.Record(Schema.String, Schema.String)).annotate({ description: "Prompt inputs" }),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
export type AuthorizeInput = Schema.Schema.Type<typeof AuthorizeInput>
export const CallbackInput = Schema.Struct({
method: Schema.Number.annotate({ description: "Auth method index" }),
method: Schema.Finite.annotate({ description: "Auth method index" }),
code: Schema.optional(Schema.String).annotate({ description: "OAuth authorization code" }),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
export type CallbackInput = Schema.Schema.Type<typeof CallbackInput>
+11 -11
View File
@@ -22,16 +22,16 @@ const filepath = path.join(
const ttl = 5 * 60 * 1000
const Cost = Schema.Struct({
input: Schema.Number,
output: Schema.Number,
cache_read: Schema.optional(Schema.Number),
cache_write: Schema.optional(Schema.Number),
input: Schema.Finite,
output: Schema.Finite,
cache_read: Schema.optional(Schema.Finite),
cache_write: Schema.optional(Schema.Finite),
context_over_200k: Schema.optional(
Schema.Struct({
input: Schema.Number,
output: Schema.Number,
cache_read: Schema.optional(Schema.Number),
cache_write: Schema.optional(Schema.Number),
input: Schema.Finite,
output: Schema.Finite,
cache_read: Schema.optional(Schema.Finite),
cache_write: Schema.optional(Schema.Finite),
}),
),
})
@@ -55,9 +55,9 @@ export const Model = Schema.Struct({
),
cost: Schema.optional(Cost),
limit: Schema.Struct({
context: Schema.Number,
input: Schema.optional(Schema.Number),
output: Schema.Number,
context: Schema.Finite,
input: Schema.optional(Schema.Finite),
output: Schema.Finite,
}),
modalities: Schema.optional(
Schema.Struct({
+9 -9
View File
@@ -848,27 +848,27 @@ const ProviderCapabilities = Schema.Struct({
})
const ProviderCacheCost = Schema.Struct({
read: Schema.Number,
write: Schema.Number,
read: Schema.Finite,
write: Schema.Finite,
})
const ProviderCost = Schema.Struct({
input: Schema.Number,
output: Schema.Number,
input: Schema.Finite,
output: Schema.Finite,
cache: ProviderCacheCost,
experimentalOver200K: Schema.optional(
Schema.Struct({
input: Schema.Number,
output: Schema.Number,
input: Schema.Finite,
output: Schema.Finite,
cache: ProviderCacheCost,
}),
),
})
const ProviderLimit = Schema.Struct({
context: Schema.Number,
input: Schema.optional(Schema.Number),
output: Schema.Number,
context: Schema.Finite,
input: Schema.optional(Schema.Finite),
output: Schema.Finite,
})
export const Model = Schema.Struct({
+4 -4
View File
@@ -62,7 +62,7 @@ export const Info = Schema.Struct({
args: Schema.Array(Schema.String),
cwd: Schema.String,
status: Schema.Literals(["running", "exited"]),
pid: Schema.Number,
pid: Schema.Finite,
})
.annotate({ identifier: "Pty" })
.pipe(withStatics((s) => ({ zod: zod(s) })))
@@ -83,8 +83,8 @@ export const UpdateInput = Schema.Struct({
title: Schema.optional(Schema.String),
size: Schema.optional(
Schema.Struct({
rows: Schema.Number,
cols: Schema.Number,
rows: Schema.Finite,
cols: Schema.Finite,
}),
),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
@@ -94,7 +94,7 @@ export type UpdateInput = Types.DeepMutable<Schema.Schema.Type<typeof UpdateInpu
export const Event = {
Created: BusEvent.define("pty.created", Schema.Struct({ info: Info })),
Updated: BusEvent.define("pty.updated", Schema.Struct({ info: Info })),
Exited: BusEvent.define("pty.exited", Schema.Struct({ id: PtyID, exitCode: Schema.Number })),
Exited: BusEvent.define("pty.exited", Schema.Struct({ id: PtyID, exitCode: Schema.Finite })),
Deleted: BusEvent.define("pty.deleted", Schema.Struct({ id: PtyID })),
}
+35 -35
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: Schema.Finite,
})
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(Schema.Finite),
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: Schema.Finite,
end: Schema.optional(Schema.Finite),
}),
),
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: Schema.Finite,
end: Schema.optional(Schema.Finite),
}),
})
.annotate({ identifier: "ReasoningPart" })
@@ -242,11 +242,11 @@ export type SubtaskPart = Types.DeepMutable<Schema.Schema.Type<typeof SubtaskPar
export const RetryPart = Schema.Struct({
...partBase,
type: Schema.Literal("retry"),
attempt: Schema.Number,
attempt: Schema.Finite,
// APIError is still NamedError-based Zod; bridge via ZodOverride until errors migrate.
error: Schema.Any.annotate({ [ZodOverride]: APIError.Schema }),
time: Schema.Struct({
created: Schema.Number,
created: Schema.Finite,
}),
})
.annotate({ identifier: "RetryPart" })
@@ -269,15 +269,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(Schema.Finite),
input: Schema.Finite,
output: Schema.Finite,
reasoning: Schema.Finite,
cache: Schema.Struct({
read: Schema.Number,
write: Schema.Number,
read: Schema.Finite,
write: Schema.Finite,
}),
}),
})
@@ -300,7 +300,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: Schema.Finite,
}),
})
.annotate({ identifier: "ToolStateRunning" })
@@ -314,9 +314,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: Schema.Finite,
end: Schema.Finite,
compacted: Schema.optional(Schema.Finite),
}),
attachments: Schema.optional(Schema.Array(FilePart)),
})
@@ -336,8 +336,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: Schema.Finite,
end: Schema.Finite,
}),
})
.annotate({ identifier: "ToolStateError" })
@@ -380,7 +380,7 @@ export const User = Schema.Struct({
...messageBase,
role: Schema.Literal("user"),
time: Schema.Struct({
created: Schema.Number,
created: Schema.Finite,
}),
format: Schema.optional(_Format),
summary: Schema.optional(
@@ -477,8 +477,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: Schema.Finite,
end: Schema.optional(Schema.Finite),
}),
),
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)),
@@ -537,8 +537,8 @@ export const Assistant = Schema.Struct({
...messageBase,
role: Schema.Literal("assistant"),
time: Schema.Struct({
created: Schema.Number,
completed: Schema.optional(Schema.Number),
created: Schema.Finite,
completed: Schema.optional(Schema.Finite),
}),
error: Schema.optional(Schema.Any.annotate({ [ZodOverride]: AssistantErrorZod })),
parentID: MessageID,
@@ -554,15 +554,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(Schema.Finite),
input: Schema.Finite,
output: Schema.Finite,
reasoning: Schema.Finite,
cache: Schema.Struct({
read: Schema.Number,
write: Schema.Number,
read: Schema.Finite,
write: Schema.Finite,
}),
}),
structured: Schema.optional(Schema.Any),
@@ -594,7 +594,7 @@ const RemovedEventSchema = Schema.Struct({
const PartUpdatedEventSchema = Schema.Struct({
sessionID: SessionID,
part: _Part,
time: Schema.Number,
time: Schema.Finite,
})
const PartRemovedEventSchema = Schema.Struct({
@@ -651,7 +651,7 @@ export type WithParts = {
const Cursor = Schema.Struct({
id: MessageID,
time: Schema.Number,
time: Schema.Finite,
})
type Cursor = typeof Cursor.Type
+13 -13
View File
@@ -132,9 +132,9 @@ function sessionPath(worktree: string, cwd: string) {
}
const Summary = Schema.Struct({
additions: Schema.Number,
deletions: Schema.Number,
files: Schema.Number,
additions: Schema.Finite,
deletions: Schema.Finite,
files: Schema.Finite,
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: Schema.Finite,
updated: Schema.Finite,
compacting: optionalOmitUndefined(Schema.Finite),
archived: optionalOmitUndefined(Schema.Finite),
})
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(Schema.Finite),
}).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(Schema.Finite),
}).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(Schema.Finite)),
updated: Schema.optional(Schema.NullOr(Schema.Finite)),
compacting: Schema.optional(Schema.NullOr(Schema.Finite)),
archived: Schema.optional(Schema.NullOr(Schema.Finite)),
})
const UpdatedInfo = Schema.Struct({
+2 -2
View File
@@ -13,9 +13,9 @@ export const Info = Schema.Union([
}),
Schema.Struct({
type: Schema.Literal("retry"),
attempt: Schema.Number,
attempt: Schema.Finite,
message: Schema.String,
next: Schema.Number,
next: Schema.Finite,
}),
Schema.Struct({
type: Schema.Literal("busy"),
+2 -2
View File
@@ -22,8 +22,8 @@ export type Patch = typeof Patch.Type
export const FileDiff = Schema.Struct({
file: Schema.String,
patch: Schema.String,
additions: Schema.Number,
deletions: Schema.Number,
additions: Schema.Finite,
deletions: Schema.Finite,
status: Schema.optional(Schema.Literals(["added", "deleted", "modified"])),
})
.annotate({ identifier: "SnapshotFileDiff" })
+2 -2
View File
@@ -41,8 +41,8 @@ const MessageFile = Schema.Struct({
})
const DiffFile = Schema.Struct({
additions: Schema.Number,
deletions: Schema.Number,
additions: Schema.Finite,
deletions: Schema.Finite,
})
const SummaryFile = Schema.Struct({
+1 -1
View File
@@ -53,7 +53,7 @@ const SWITCHES = new Set(["-confirm", "-debug", "-force", "-nonewline", "-recurs
export const Parameters = Schema.Struct({
command: Schema.String.annotate({ description: "The command to execute" }),
timeout: Schema.optional(Schema.Number).annotate({ description: "Optional timeout in milliseconds" }),
timeout: Schema.optional(Schema.Finite).annotate({ description: "Optional timeout in milliseconds" }),
workdir: Schema.optional(Schema.String).annotate({
description: `The working directory to run the command in. Defaults to the current directory. Use this instead of 'cd' commands.`,
}),
+2 -2
View File
@@ -25,10 +25,10 @@ const SAMPLE_BYTES = 4096
// unchanged; purely CLI-facing uses must now send numbers rather than strings.
export const Parameters = Schema.Struct({
filePath: Schema.String.annotate({ description: "The absolute path to the file or directory to read" }),
offset: Schema.optional(Schema.Number).annotate({
offset: Schema.optional(Schema.Finite).annotate({
description: "The line number to start reading from (1-indexed)",
}),
limit: Schema.optional(Schema.Number).annotate({
limit: Schema.optional(Schema.Finite).annotate({
description: "The maximum number of lines to read (defaults to 2000)",
}),
})