fix sdk generation for date time schemas

This commit is contained in:
Dax Raad 2026-04-24 18:24:42 -04:00
commit 81d81e6334
3 changed files with 606 additions and 34 deletions

View file

@ -1,6 +1,8 @@
export * as ConfigPermission from "./permission"
import { Schema, SchemaGetter } from "effect"
import z from "zod"
import { zod } from "@/util/effect-zod"
import { ZodOverride } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
export const Action = Schema.Literals(["ask", "allow", "deny"])
@ -18,17 +20,9 @@ export const Rule = Schema.Union([Action, Object])
.pipe(withStatics((s) => ({ zod: zod(s) })))
export type Rule = Schema.Schema.Type<typeof Rule>
// Known permission keys get explicit types — most are full Rule (either a
// single Action or a per-pattern object), but a handful of tools take no
// sub-target patterns and are Action-only. Unknown keys fall through the
// Record rest signature as Rule.
//
// StructWithRest canonicalises key order on decode (known first, then rest),
// which used to require the `__originalKeys` preprocess hack because
// `Permission.fromConfig` depended on the user's insertion order. That
// dependency is gone — `fromConfig` now sorts top-level keys so wildcard
// permissions come before specifics, making the final precedence
// order-independent.
// Known permission keys get explicit types in the Effect schema for generated
// docs/types. Runtime config parsing uses `InfoZod` below so user key order is
// preserved for permission precedence.
const InputObject = Schema.StructWithRest(
Schema.Struct({
read: Schema.optional(Rule),
@ -60,6 +54,18 @@ const InputSchema = Schema.Union([Action, InputObject])
const normalizeInput = (input: Schema.Schema.Type<typeof InputSchema>): Schema.Schema.Type<typeof InputObject> =>
typeof input === "string" ? { "*": input } : input
const ACTION_ONLY = new Set(["todowrite", "question", "webfetch", "websearch", "codesearch", "doom_loop"])
const InfoZod = z
.union([zod(Action), z.record(z.string(), z.union([zod(Action), z.record(z.string(), zod(Action))]))])
.transform(normalizeInput)
.superRefine((input, ctx) => {
for (const [key, value] of globalThis.Object.entries(input)) {
if (!ACTION_ONLY.has(key) || typeof value === "string") continue
ctx.addIssue({ code: "custom", message: `${key} must be a permission action`, path: [key] })
}
})
export const Info = InputSchema.pipe(
Schema.decodeTo(InputObject, {
decode: SchemaGetter.transform(normalizeInput),
@ -70,6 +76,7 @@ export const Info = InputSchema.pipe(
}),
)
.annotate({ identifier: "PermissionConfig" })
.annotate({ [ZodOverride]: InfoZod })
.pipe(
// Walker already emits the decodeTo transform into the derived zod (see
// `encoded()` in effect-zod.ts), so just expose that directly.

View file

@ -360,6 +360,8 @@ function array(ast: SchemaAST.Arrays): z.ZodTypeAny {
}
function decl(ast: SchemaAST.Declaration): z.ZodTypeAny {
const typeConstructor = (ast.annotations as { typeConstructor?: { _tag?: string } }).typeConstructor
if (typeConstructor?._tag === "effect/DateTime.Utc") return z.string().datetime()
if (ast.typeParameters.length !== 1) return fail(ast)
return walk(ast.typeParameters[0])
}

View file

@ -987,6 +987,371 @@ export type EventSessionDeleted = {
}
}
export type SessionEventSource = {
start: number
end: number
text: string
}
export type SessionEventFileAttachment = {
uri: string
mime: string
name?: string
description?: string
source?: SessionEventSource
}
export type SessionEventAgentAttachment = {
name: string
source?: SessionEventSource
}
export type SessionEventPrompt = {
id: string
sessionID: string
metadata?: {
[key: string]: unknown
}
timestamp: string
type: "prompt"
text: string
files?: Array<SessionEventFileAttachment>
agents?: Array<SessionEventAgentAttachment>
}
export type EventPrompt = {
type: "prompt"
properties: SessionEventPrompt
}
export type SessionEventSynthetic = {
id: string
sessionID: string
metadata?: {
[key: string]: unknown
}
timestamp: string
type: "synthetic"
text: string
}
export type EventSynthetic = {
type: "synthetic"
properties: SessionEventSynthetic
}
export type SessionEventStepStarted = {
id: string
sessionID: string
metadata?: {
[key: string]: unknown
}
timestamp: string
type: "step.started"
model: {
id: string
providerID: string
variant?: string
}
}
export type EventStepStarted = {
type: "step.started"
properties: SessionEventStepStarted
}
export type SessionEventStepEnded = {
id: string
sessionID: string
metadata?: {
[key: string]: unknown
}
timestamp: string
type: "step.ended"
reason: string
cost: number
tokens: {
input: number
output: number
reasoning: number
cache: {
read: number
write: number
}
}
}
export type EventStepEnded = {
type: "step.ended"
properties: SessionEventStepEnded
}
export type SessionEventTextStarted = {
id: string
sessionID: string
metadata?: {
[key: string]: unknown
}
timestamp: string
type: "text.started"
}
export type EventTextStarted = {
type: "text.started"
properties: SessionEventTextStarted
}
export type SessionEventTextDelta = {
id: string
sessionID: string
metadata?: {
[key: string]: unknown
}
timestamp: string
type: "text.delta"
delta: string
}
export type EventTextDelta = {
type: "text.delta"
properties: SessionEventTextDelta
}
export type SessionEventTextEnded = {
id: string
sessionID: string
metadata?: {
[key: string]: unknown
}
timestamp: string
type: "text.ended"
text: string
}
export type EventTextEnded = {
type: "text.ended"
properties: SessionEventTextEnded
}
export type SessionEventReasoningStarted = {
id: string
sessionID: string
metadata?: {
[key: string]: unknown
}
timestamp: string
type: "reasoning.started"
}
export type EventReasoningStarted = {
type: "reasoning.started"
properties: SessionEventReasoningStarted
}
export type SessionEventReasoningDelta = {
id: string
sessionID: string
metadata?: {
[key: string]: unknown
}
timestamp: string
type: "reasoning.delta"
delta: string
}
export type EventReasoningDelta = {
type: "reasoning.delta"
properties: SessionEventReasoningDelta
}
export type SessionEventReasoningEnded = {
id: string
sessionID: string
metadata?: {
[key: string]: unknown
}
timestamp: string
type: "reasoning.ended"
text: string
}
export type EventReasoningEnded = {
type: "reasoning.ended"
properties: SessionEventReasoningEnded
}
export type SessionEventToolInputStarted = {
id: string
sessionID: string
metadata?: {
[key: string]: unknown
}
timestamp: string
type: "tool.input.started"
callID: string
name: string
}
export type EventToolInputStarted = {
type: "tool.input.started"
properties: SessionEventToolInputStarted
}
export type SessionEventToolInputDelta = {
id: string
sessionID: string
metadata?: {
[key: string]: unknown
}
timestamp: string
type: "tool.input.delta"
callID: string
delta: string
}
export type EventToolInputDelta = {
type: "tool.input.delta"
properties: SessionEventToolInputDelta
}
export type SessionEventToolInputEnded = {
id: string
sessionID: string
metadata?: {
[key: string]: unknown
}
timestamp: string
type: "tool.input.ended"
callID: string
text: string
}
export type EventToolInputEnded = {
type: "tool.input.ended"
properties: SessionEventToolInputEnded
}
export type SessionEventToolCalled = {
id: string
sessionID: string
metadata?: {
[key: string]: unknown
}
timestamp: string
type: "tool.called"
callID: string
tool: string
input: {
[key: string]: unknown
}
provider: {
executed: boolean
metadata?: {
[key: string]: unknown
}
}
}
export type EventToolCalled = {
type: "tool.called"
properties: SessionEventToolCalled
}
export type SessionEventToolSuccess = {
id: string
sessionID: string
metadata?: {
[key: string]: unknown
}
timestamp: string
type: "tool.success"
callID: string
title: string
output?: string
attachments?: Array<SessionEventFileAttachment>
provider: {
executed: boolean
metadata?: {
[key: string]: unknown
}
}
}
export type EventToolSuccess = {
type: "tool.success"
properties: SessionEventToolSuccess
}
export type SessionEventToolError = {
id: string
sessionID: string
metadata?: {
[key: string]: unknown
}
timestamp: string
type: "tool.error"
callID: string
error: string
provider: {
executed: boolean
metadata?: {
[key: string]: unknown
}
}
}
export type EventToolError = {
type: "tool.error"
properties: SessionEventToolError
}
export type SessionEventRetryError = {
message: string
statusCode?: number
isRetryable: boolean
responseHeaders?: {
[key: string]: string
}
responseBody?: string
metadata?: {
[key: string]: string
}
}
export type SessionEventRetried = {
id: string
sessionID: string
metadata?: {
[key: string]: unknown
}
timestamp: string
type: "retried"
attempt: number
error: SessionEventRetryError
}
export type EventRetried = {
type: "retried"
properties: SessionEventRetried
}
export type SessionEventCompacted = {
id: string
sessionID: string
metadata?: {
[key: string]: unknown
}
timestamp: string
type: "compacted"
auto: boolean
overflow?: boolean
}
export type EventCompacted = {
type: "compacted"
properties: SessionEventCompacted
}
export type SyncEventMessageUpdated = {
type: "sync"
name: "message.updated.1"
@ -1104,6 +1469,168 @@ export type SyncEventSessionDeleted = {
}
}
export type SyncEventPrompt = {
type: "sync"
name: "prompt.1"
id: string
seq: number
aggregateID: "sessionID"
data: SessionEventPrompt
}
export type SyncEventSynthetic = {
type: "sync"
name: "synthetic.1"
id: string
seq: number
aggregateID: "sessionID"
data: SessionEventSynthetic
}
export type SyncEventStepStarted = {
type: "sync"
name: "step.started.1"
id: string
seq: number
aggregateID: "sessionID"
data: SessionEventStepStarted
}
export type SyncEventStepEnded = {
type: "sync"
name: "step.ended.1"
id: string
seq: number
aggregateID: "sessionID"
data: SessionEventStepEnded
}
export type SyncEventTextStarted = {
type: "sync"
name: "text.started.1"
id: string
seq: number
aggregateID: "sessionID"
data: SessionEventTextStarted
}
export type SyncEventTextDelta = {
type: "sync"
name: "text.delta.1"
id: string
seq: number
aggregateID: "sessionID"
data: SessionEventTextDelta
}
export type SyncEventTextEnded = {
type: "sync"
name: "text.ended.1"
id: string
seq: number
aggregateID: "sessionID"
data: SessionEventTextEnded
}
export type SyncEventReasoningStarted = {
type: "sync"
name: "reasoning.started.1"
id: string
seq: number
aggregateID: "sessionID"
data: SessionEventReasoningStarted
}
export type SyncEventReasoningDelta = {
type: "sync"
name: "reasoning.delta.1"
id: string
seq: number
aggregateID: "sessionID"
data: SessionEventReasoningDelta
}
export type SyncEventReasoningEnded = {
type: "sync"
name: "reasoning.ended.1"
id: string
seq: number
aggregateID: "sessionID"
data: SessionEventReasoningEnded
}
export type SyncEventToolInputStarted = {
type: "sync"
name: "tool.input.started.1"
id: string
seq: number
aggregateID: "sessionID"
data: SessionEventToolInputStarted
}
export type SyncEventToolInputDelta = {
type: "sync"
name: "tool.input.delta.1"
id: string
seq: number
aggregateID: "sessionID"
data: SessionEventToolInputDelta
}
export type SyncEventToolInputEnded = {
type: "sync"
name: "tool.input.ended.1"
id: string
seq: number
aggregateID: "sessionID"
data: SessionEventToolInputEnded
}
export type SyncEventToolCalled = {
type: "sync"
name: "tool.called.1"
id: string
seq: number
aggregateID: "sessionID"
data: SessionEventToolCalled
}
export type SyncEventToolSuccess = {
type: "sync"
name: "tool.success.1"
id: string
seq: number
aggregateID: "sessionID"
data: SessionEventToolSuccess
}
export type SyncEventToolError = {
type: "sync"
name: "tool.error.1"
id: string
seq: number
aggregateID: "sessionID"
data: SessionEventToolError
}
export type SyncEventRetried = {
type: "sync"
name: "retried.1"
id: string
seq: number
aggregateID: "sessionID"
data: SessionEventRetried
}
export type SyncEventCompacted = {
type: "sync"
name: "compacted.1"
id: string
seq: number
aggregateID: "sessionID"
data: SessionEventCompacted
}
export type GlobalEvent = {
directory: string
project?: string
@ -1156,6 +1683,24 @@ export type GlobalEvent = {
| EventSessionCreated
| EventSessionUpdated
| EventSessionDeleted
| EventPrompt
| EventSynthetic
| EventStepStarted
| EventStepEnded
| EventTextStarted
| EventTextDelta
| EventTextEnded
| EventReasoningStarted
| EventReasoningDelta
| EventReasoningEnded
| EventToolInputStarted
| EventToolInputDelta
| EventToolInputEnded
| EventToolCalled
| EventToolSuccess
| EventToolError
| EventRetried
| EventCompacted
| SyncEventMessageUpdated
| SyncEventMessageRemoved
| SyncEventMessagePartUpdated
@ -1163,6 +1708,24 @@ export type GlobalEvent = {
| SyncEventSessionCreated
| SyncEventSessionUpdated
| SyncEventSessionDeleted
| SyncEventPrompt
| SyncEventSynthetic
| SyncEventStepStarted
| SyncEventStepEnded
| SyncEventTextStarted
| SyncEventTextDelta
| SyncEventTextEnded
| SyncEventReasoningStarted
| SyncEventReasoningDelta
| SyncEventReasoningEnded
| SyncEventToolInputStarted
| SyncEventToolInputDelta
| SyncEventToolInputEnded
| SyncEventToolCalled
| SyncEventToolSuccess
| SyncEventToolError
| SyncEventRetried
| SyncEventCompacted
}
/**
@ -1198,32 +1761,14 @@ export type ServerConfig = {
export type PermissionActionConfig = "ask" | "allow" | "deny"
export type PermissionObjectConfig = {
[key: string]: PermissionActionConfig
}
export type PermissionRuleConfig = PermissionActionConfig | PermissionObjectConfig
export type PermissionConfig =
| PermissionActionConfig
| {
read?: PermissionRuleConfig
edit?: PermissionRuleConfig
glob?: PermissionRuleConfig
grep?: PermissionRuleConfig
list?: PermissionRuleConfig
bash?: PermissionRuleConfig
task?: PermissionRuleConfig
external_directory?: PermissionRuleConfig
todowrite?: PermissionActionConfig
question?: PermissionActionConfig
webfetch?: PermissionActionConfig
websearch?: PermissionActionConfig
codesearch?: PermissionActionConfig
lsp?: PermissionRuleConfig
doom_loop?: PermissionActionConfig
skill?: PermissionRuleConfig
[key: string]: PermissionRuleConfig | PermissionActionConfig | undefined
[key: string]:
| PermissionActionConfig
| {
[key: string]: PermissionActionConfig
}
}
export type AgentConfig = {
@ -2082,6 +2627,24 @@ export type Event =
| EventSessionCreated
| EventSessionUpdated
| EventSessionDeleted
| EventPrompt
| EventSynthetic
| EventStepStarted
| EventStepEnded
| EventTextStarted
| EventTextDelta
| EventTextEnded
| EventReasoningStarted
| EventReasoningDelta
| EventReasoningEnded
| EventToolInputStarted
| EventToolInputDelta
| EventToolInputEnded
| EventToolCalled
| EventToolSuccess
| EventToolError
| EventRetried
| EventCompacted
export type McpStatusConnected = {
status: "connected"