refactor(schema): tighten public contracts (#33771)

This commit is contained in:
Kit Langton 2026-06-25 19:10:23 +02:00 committed by GitHub
commit 9e9d405d7e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
49 changed files with 759 additions and 897 deletions

View file

@ -152,8 +152,8 @@ export type SessionsListOutput = {
readonly id: string readonly id: string
readonly parentID?: string readonly parentID?: string
readonly projectID: string readonly projectID: string
readonly agent?: string | null readonly agent?: string
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string | null } | null readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly cost: number readonly cost: number
readonly tokens: { readonly tokens: {
readonly input: number readonly input: number
@ -161,23 +161,23 @@ export type SessionsListOutput = {
readonly reasoning: number readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number } readonly cache: { readonly read: number; readonly write: number }
} }
readonly time: { readonly created: number; readonly updated: number; readonly archived?: number | null } readonly time: { readonly created: number; readonly updated: number; readonly archived?: number }
readonly title: string readonly title: string
readonly location: { readonly directory: string; readonly workspaceID?: string | null | null } readonly location: { readonly directory: string; readonly workspaceID?: string }
readonly subpath?: string | null readonly subpath?: string
readonly revert?: { readonly revert?: {
readonly messageID: string readonly messageID: string
readonly partID?: string | null readonly partID?: string
readonly snapshot?: string | null readonly snapshot?: string
readonly diff?: string | null readonly diff?: string
readonly files?: ReadonlyArray<{ readonly files?: ReadonlyArray<{
readonly path: string readonly path: string
readonly status: "added" | "modified" | "deleted" readonly status: "added" | "modified" | "deleted"
readonly additions: number readonly additions: number
readonly deletions: number readonly deletions: number
readonly patch: string readonly patch: string
}> | null }>
} | null }
}> }>
readonly cursor: { readonly previous?: string | null; readonly next?: string | null } readonly cursor: { readonly previous?: string | null; readonly next?: string | null }
} }
@ -186,26 +186,26 @@ export type SessionsCreateInput = {
readonly id?: { readonly id?: {
readonly id?: string | null readonly id?: string | null
readonly agent?: string | null readonly agent?: string | null
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string | null } | null readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
readonly location?: { readonly directory: string; readonly workspaceID?: string | null | null } | null readonly location?: { readonly directory: string; readonly workspaceID?: string } | null
}["id"] }["id"]
readonly agent?: { readonly agent?: {
readonly id?: string | null readonly id?: string | null
readonly agent?: string | null readonly agent?: string | null
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string | null } | null readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
readonly location?: { readonly directory: string; readonly workspaceID?: string | null | null } | null readonly location?: { readonly directory: string; readonly workspaceID?: string } | null
}["agent"] }["agent"]
readonly model?: { readonly model?: {
readonly id?: string | null readonly id?: string | null
readonly agent?: string | null readonly agent?: string | null
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string | null } | null readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
readonly location?: { readonly directory: string; readonly workspaceID?: string | null | null } | null readonly location?: { readonly directory: string; readonly workspaceID?: string } | null
}["model"] }["model"]
readonly location?: { readonly location?: {
readonly id?: string | null readonly id?: string | null
readonly agent?: string | null readonly agent?: string | null
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string | null } | null readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string } | null
readonly location?: { readonly directory: string; readonly workspaceID?: string | null | null } | null readonly location?: { readonly directory: string; readonly workspaceID?: string } | null
}["location"] }["location"]
} }
@ -214,8 +214,8 @@ export type SessionsCreateOutput = {
readonly id: string readonly id: string
readonly parentID?: string readonly parentID?: string
readonly projectID: string readonly projectID: string
readonly agent?: string | null readonly agent?: string
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string | null } | null readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly cost: number readonly cost: number
readonly tokens: { readonly tokens: {
readonly input: number readonly input: number
@ -223,23 +223,23 @@ export type SessionsCreateOutput = {
readonly reasoning: number readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number } readonly cache: { readonly read: number; readonly write: number }
} }
readonly time: { readonly created: number; readonly updated: number; readonly archived?: number | null } readonly time: { readonly created: number; readonly updated: number; readonly archived?: number }
readonly title: string readonly title: string
readonly location: { readonly directory: string; readonly workspaceID?: string | null | null } readonly location: { readonly directory: string; readonly workspaceID?: string }
readonly subpath?: string | null readonly subpath?: string
readonly revert?: { readonly revert?: {
readonly messageID: string readonly messageID: string
readonly partID?: string | null readonly partID?: string
readonly snapshot?: string | null readonly snapshot?: string
readonly diff?: string | null readonly diff?: string
readonly files?: ReadonlyArray<{ readonly files?: ReadonlyArray<{
readonly path: string readonly path: string
readonly status: "added" | "modified" | "deleted" readonly status: "added" | "modified" | "deleted"
readonly additions: number readonly additions: number
readonly deletions: number readonly deletions: number
readonly patch: string readonly patch: string
}> | null }>
} | null }
} }
}["data"] }["data"]
@ -250,8 +250,8 @@ export type SessionsGetOutput = {
readonly id: string readonly id: string
readonly parentID?: string readonly parentID?: string
readonly projectID: string readonly projectID: string
readonly agent?: string | null readonly agent?: string
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string | null } | null readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly cost: number readonly cost: number
readonly tokens: { readonly tokens: {
readonly input: number readonly input: number
@ -259,23 +259,23 @@ export type SessionsGetOutput = {
readonly reasoning: number readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number } readonly cache: { readonly read: number; readonly write: number }
} }
readonly time: { readonly created: number; readonly updated: number; readonly archived?: number | null } readonly time: { readonly created: number; readonly updated: number; readonly archived?: number }
readonly title: string readonly title: string
readonly location: { readonly directory: string; readonly workspaceID?: string | null | null } readonly location: { readonly directory: string; readonly workspaceID?: string }
readonly subpath?: string | null readonly subpath?: string
readonly revert?: { readonly revert?: {
readonly messageID: string readonly messageID: string
readonly partID?: string | null readonly partID?: string
readonly snapshot?: string | null readonly snapshot?: string
readonly diff?: string | null readonly diff?: string
readonly files?: ReadonlyArray<{ readonly files?: ReadonlyArray<{
readonly path: string readonly path: string
readonly status: "added" | "modified" | "deleted" readonly status: "added" | "modified" | "deleted"
readonly additions: number readonly additions: number
readonly deletions: number readonly deletions: number
readonly patch: string readonly patch: string
}> | null }>
} | null }
} }
}["data"] }["data"]
@ -289,7 +289,7 @@ export type SessionsSwitchAgentOutput = void
export type SessionsSwitchModelInput = { export type SessionsSwitchModelInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"] readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly model: { readonly model: {
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string | undefined } readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
}["model"] }["model"]
} }
@ -298,96 +298,80 @@ export type SessionsSwitchModelOutput = void
export type SessionsPromptInput = { export type SessionsPromptInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"] readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly id?: { readonly id?: {
readonly id?: string | undefined readonly id?: string | null
readonly prompt: { readonly prompt: {
readonly text: string readonly text: string
readonly files?: readonly files?: ReadonlyArray<{
| ReadonlyArray<{ readonly uri: string
readonly uri: string readonly mime: string
readonly mime: string readonly name?: string
readonly name?: string | undefined readonly description?: string
readonly description?: string | undefined readonly source?: { readonly start: number; readonly end: number; readonly text: string }
readonly source?: { readonly start: number; readonly end: number; readonly text: string } | undefined }>
}> readonly agents?: ReadonlyArray<{
| undefined readonly name: string
readonly agents?: readonly source?: { readonly start: number; readonly end: number; readonly text: string }
| ReadonlyArray<{ }>
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string } | undefined
}>
| undefined
} }
readonly delivery?: "steer" | "queue" | undefined readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | undefined readonly resume?: boolean | null
}["id"] }["id"]
readonly prompt: { readonly prompt: {
readonly id?: string | undefined readonly id?: string | null
readonly prompt: { readonly prompt: {
readonly text: string readonly text: string
readonly files?: readonly files?: ReadonlyArray<{
| ReadonlyArray<{ readonly uri: string
readonly uri: string readonly mime: string
readonly mime: string readonly name?: string
readonly name?: string | undefined readonly description?: string
readonly description?: string | undefined readonly source?: { readonly start: number; readonly end: number; readonly text: string }
readonly source?: { readonly start: number; readonly end: number; readonly text: string } | undefined }>
}> readonly agents?: ReadonlyArray<{
| undefined readonly name: string
readonly agents?: readonly source?: { readonly start: number; readonly end: number; readonly text: string }
| ReadonlyArray<{ }>
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string } | undefined
}>
| undefined
} }
readonly delivery?: "steer" | "queue" | undefined readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | undefined readonly resume?: boolean | null
}["prompt"] }["prompt"]
readonly delivery?: { readonly delivery?: {
readonly id?: string | undefined readonly id?: string | null
readonly prompt: { readonly prompt: {
readonly text: string readonly text: string
readonly files?: readonly files?: ReadonlyArray<{
| ReadonlyArray<{ readonly uri: string
readonly uri: string readonly mime: string
readonly mime: string readonly name?: string
readonly name?: string | undefined readonly description?: string
readonly description?: string | undefined readonly source?: { readonly start: number; readonly end: number; readonly text: string }
readonly source?: { readonly start: number; readonly end: number; readonly text: string } | undefined }>
}> readonly agents?: ReadonlyArray<{
| undefined readonly name: string
readonly agents?: readonly source?: { readonly start: number; readonly end: number; readonly text: string }
| ReadonlyArray<{ }>
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string } | undefined
}>
| undefined
} }
readonly delivery?: "steer" | "queue" | undefined readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | undefined readonly resume?: boolean | null
}["delivery"] }["delivery"]
readonly resume?: { readonly resume?: {
readonly id?: string | undefined readonly id?: string | null
readonly prompt: { readonly prompt: {
readonly text: string readonly text: string
readonly files?: readonly files?: ReadonlyArray<{
| ReadonlyArray<{ readonly uri: string
readonly uri: string readonly mime: string
readonly mime: string readonly name?: string
readonly name?: string | undefined readonly description?: string
readonly description?: string | undefined readonly source?: { readonly start: number; readonly end: number; readonly text: string }
readonly source?: { readonly start: number; readonly end: number; readonly text: string } | undefined }>
}> readonly agents?: ReadonlyArray<{
| undefined readonly name: string
readonly agents?: readonly source?: { readonly start: number; readonly end: number; readonly text: string }
| ReadonlyArray<{ }>
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string } | undefined
}>
| undefined
} }
readonly delivery?: "steer" | "queue" | undefined readonly delivery?: "steer" | "queue" | null
readonly resume?: boolean | undefined readonly resume?: boolean | null
}["resume"] }["resume"]
} }
@ -401,18 +385,18 @@ export type SessionsPromptOutput = {
readonly files?: ReadonlyArray<{ readonly files?: ReadonlyArray<{
readonly uri: string readonly uri: string
readonly mime: string readonly mime: string
readonly name?: string | null readonly name?: string
readonly description?: string | null readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string } | null readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}> | null }>
readonly agents?: ReadonlyArray<{ readonly agents?: ReadonlyArray<{
readonly name: string readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string } | null readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}> | null }>
} }
readonly delivery: "steer" | "queue" readonly delivery: "steer" | "queue"
readonly timeCreated: number readonly timeCreated: number
readonly promotedSeq?: number | null readonly promotedSeq?: number
} }
}["data"] }["data"]
@ -433,18 +417,16 @@ export type SessionsStageInput = {
export type SessionsStageOutput = { export type SessionsStageOutput = {
readonly data: { readonly data: {
readonly messageID: string readonly messageID: string
readonly partID?: string | undefined readonly partID?: string
readonly snapshot?: string | undefined readonly snapshot?: string
readonly diff?: string | undefined readonly diff?: string
readonly files?: readonly files?: ReadonlyArray<{
| ReadonlyArray<{ readonly path: string
readonly path: string readonly status: "added" | "modified" | "deleted"
readonly status: "added" | "modified" | "deleted" readonly additions: number
readonly additions: number readonly deletions: number
readonly deletions: number readonly patch: string
readonly patch: string }>
}>
| undefined
} }
}["data"] }["data"]
@ -462,39 +444,39 @@ export type SessionsContextOutput = {
readonly data: ReadonlyArray< readonly data: ReadonlyArray<
| { | {
readonly id: string readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue } | null readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number } readonly time: { readonly created: number }
readonly type: "agent-switched" readonly type: "agent-switched"
readonly agent: string readonly agent: string
} }
| { | {
readonly id: string readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue } | null readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number } readonly time: { readonly created: number }
readonly type: "model-switched" readonly type: "model-switched"
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string | null } readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
} }
| { | {
readonly id: string readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue } | null readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number } readonly time: { readonly created: number }
readonly text: string readonly text: string
readonly files?: ReadonlyArray<{ readonly files?: ReadonlyArray<{
readonly uri: string readonly uri: string
readonly mime: string readonly mime: string
readonly name?: string | null readonly name?: string
readonly description?: string | null readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string } | null readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}> | null }>
readonly agents?: ReadonlyArray<{ readonly agents?: ReadonlyArray<{
readonly name: string readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string } | null readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}> | null }>
readonly type: "user" readonly type: "user"
} }
| { | {
readonly id: string readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue } | null readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number } readonly time: { readonly created: number }
readonly sessionID: string readonly sessionID: string
readonly text: string readonly text: string
@ -502,15 +484,15 @@ export type SessionsContextOutput = {
} }
| { | {
readonly id: string readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue } | null readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number } readonly time: { readonly created: number }
readonly type: "system" readonly type: "system"
readonly text: string readonly text: string
} }
| { | {
readonly id: string readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue } | null readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number; readonly completed?: number | null } readonly time: { readonly created: number; readonly completed?: number }
readonly type: "shell" readonly type: "shell"
readonly callID: string readonly callID: string
readonly command: string readonly command: string
@ -518,18 +500,18 @@ export type SessionsContextOutput = {
} }
| { | {
readonly id: string readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue } | null readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number; readonly completed?: number | null } readonly time: { readonly created: number; readonly completed?: number }
readonly type: "assistant" readonly type: "assistant"
readonly agent: string readonly agent: string
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string | null } readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly content: ReadonlyArray< readonly content: ReadonlyArray<
| { readonly type: "text"; readonly id: string; readonly text: string } | { readonly type: "text"; readonly id: string; readonly text: string }
| { | {
readonly type: "reasoning" readonly type: "reasoning"
readonly id: string readonly id: string
readonly text: string readonly text: string
readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } } | null readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
} }
| { | {
readonly type: "tool" readonly type: "tool"
@ -537,23 +519,18 @@ export type SessionsContextOutput = {
readonly name: string readonly name: string
readonly provider?: { readonly provider?: {
readonly executed: boolean readonly executed: boolean
readonly metadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } } | null readonly metadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
readonly resultMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } } | null readonly resultMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
} | null }
readonly state: readonly state:
| { readonly status: "pending"; readonly input: string } | { readonly status: "pending"; readonly input: string }
| { | {
readonly status: "running" readonly status: "running"
readonly input: { readonly [x: string]: JsonValue } readonly input: { readonly [x: string]: JsonValue }
readonly structured: { readonly [x: string]: any } readonly structured: { readonly [x: string]: JsonValue }
readonly content: ReadonlyArray< readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string } | { readonly type: "text"; readonly text: string }
| { | { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
readonly type: "file"
readonly uri: string
readonly mime: string
readonly name?: string | null
}
> >
} }
| { | {
@ -562,61 +539,47 @@ export type SessionsContextOutput = {
readonly attachments?: ReadonlyArray<{ readonly attachments?: ReadonlyArray<{
readonly uri: string readonly uri: string
readonly mime: string readonly mime: string
readonly name?: string | null readonly name?: string
readonly description?: string | null readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string } | null readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}> | null }>
readonly content: ReadonlyArray< readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string } | { readonly type: "text"; readonly text: string }
| { | { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
readonly type: "file"
readonly uri: string
readonly mime: string
readonly name?: string | null
}
> >
readonly outputPaths?: ReadonlyArray<string> | null readonly outputPaths?: ReadonlyArray<string>
readonly structured: { readonly [x: string]: any } readonly structured: { readonly [x: string]: JsonValue }
readonly result?: JsonValue | null readonly result?: JsonValue
} }
| { | {
readonly status: "error" readonly status: "error"
readonly input: { readonly [x: string]: JsonValue } readonly input: { readonly [x: string]: JsonValue }
readonly content: ReadonlyArray< readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string } | { readonly type: "text"; readonly text: string }
| { | { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
readonly type: "file"
readonly uri: string
readonly mime: string
readonly name?: string | null
}
> >
readonly structured: { readonly [x: string]: any } readonly structured: { readonly [x: string]: JsonValue }
readonly error: { readonly type: "unknown"; readonly message: string } readonly error: { readonly type: "unknown"; readonly message: string }
readonly result?: JsonValue | null readonly result?: JsonValue
} }
readonly time: { readonly time: {
readonly created: number readonly created: number
readonly ran?: number | null readonly ran?: number
readonly completed?: number | null readonly completed?: number
readonly pruned?: number | null readonly pruned?: number
} }
} }
> >
readonly snapshot?: { readonly snapshot?: { readonly start?: string; readonly end?: string; readonly files?: ReadonlyArray<string> }
readonly start?: string | null readonly finish?: string
readonly end?: string | null readonly cost?: number
readonly files?: ReadonlyArray<string> | null
} | null
readonly finish?: string | null
readonly cost?: number | null
readonly tokens?: { readonly tokens?: {
readonly input: number readonly input: number
readonly output: number readonly output: number
readonly reasoning: number readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number } readonly cache: { readonly read: number; readonly write: number }
} | null }
readonly error?: { readonly type: "unknown"; readonly message: string } | null readonly error?: { readonly type: "unknown"; readonly message: string }
} }
| { | {
readonly type: "compaction" readonly type: "compaction"
@ -624,7 +587,7 @@ export type SessionsContextOutput = {
readonly summary: string readonly summary: string
readonly recent: string readonly recent: string
readonly id: string readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue } | null readonly metadata?: { readonly [x: string]: JsonValue }
readonly time: { readonly created: number } readonly time: { readonly created: number }
} }
> >

View file

@ -25,6 +25,8 @@ export const Plugin = define({
const directory = doc.path ? path.dirname(doc.path) : location.directory const directory = doc.path ? path.dirname(doc.path) : location.directory
for (const [name, entry] of Object.entries(doc.info.references ?? {})) { for (const [name, entry] of Object.entries(doc.info.references ?? {})) {
if (!validAlias(name)) continue if (!validAlias(name)) continue
const description = typeof entry === "string" ? undefined : entry.description
const hidden = typeof entry === "string" ? undefined : entry.hidden
entries.set( entries.set(
name, name,
local(entry) local(entry)
@ -33,15 +35,15 @@ export const Plugin = define({
path: AbsolutePath.make( path: AbsolutePath.make(
localPath(directory, global.home, typeof entry === "string" ? entry : entry.path), localPath(directory, global.home, typeof entry === "string" ? entry : entry.path),
), ),
description: typeof entry === "string" ? undefined : entry.description, ...(description === undefined ? {} : { description }),
hidden: typeof entry === "string" ? undefined : entry.hidden, ...(hidden === undefined ? {} : { hidden }),
}) })
: Reference.GitSource.make({ : Reference.GitSource.make({
type: "git", type: "git",
repository: typeof entry === "string" ? entry : entry.repository, repository: typeof entry === "string" ? entry : entry.repository,
branch: typeof entry === "string" ? undefined : entry.branch, ...(entry.branch === undefined ? {} : { branch: entry.branch }),
description: typeof entry === "string" ? undefined : entry.description, ...(description === undefined ? {} : { description }),
hidden: typeof entry === "string" ? undefined : entry.hidden, ...(hidden === undefined ? {} : { hidden }),
}), }),
) )
} }

View file

@ -12,8 +12,11 @@ import { ModelV2 } from "../model"
import { PluginV2 } from "../plugin" import { PluginV2 } from "../plugin"
import { ProviderV2 } from "../provider" import { ProviderV2 } from "../provider"
import { Reference } from "../reference" import { Reference } from "../reference"
import type { DeepMutable } from "../schema"
import { SkillV2 } from "../skill" import { SkillV2 } from "../skill"
const mutable = <T>(value: T) => value as DeepMutable<T>
export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Interface) { export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Interface) {
const agents = yield* AgentV2.Service const agents = yield* AgentV2.Service
const aisdk = yield* AISDK.Service const aisdk = yield* AISDK.Service
@ -30,8 +33,8 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
transform: (callback) => transform: (callback) =>
agents.transform((draft) => agents.transform((draft) =>
callback({ callback({
list: draft.list, list: () => mutable(draft.list()),
get: (id) => draft.get(AgentV2.ID.make(id)), get: (id) => mutable(draft.get(AgentV2.ID.make(id))),
default: (id) => draft.default(id === undefined ? undefined : AgentV2.ID.make(id)), default: (id) => draft.default(id === undefined ? undefined : AgentV2.ID.make(id)),
update: (id, update) => draft.update(AgentV2.ID.make(id), update), update: (id, update) => draft.update(AgentV2.ID.make(id), update),
remove: (id) => draft.remove(AgentV2.ID.make(id)), remove: (id) => draft.remove(AgentV2.ID.make(id)),
@ -42,7 +45,7 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
sdk: (callback) => sdk: (callback) =>
aisdk.hook.sdk((event) => { aisdk.hook.sdk((event) => {
const output = { const output = {
model: event.model, model: mutable(event.model),
package: event.package, package: event.package,
options: event.options, options: event.options,
sdk: event.sdk, sdk: event.sdk,
@ -55,7 +58,7 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
language: (callback) => language: (callback) =>
aisdk.hook.language((event) => { aisdk.hook.language((event) => {
const output = { const output = {
model: event.model, model: mutable(event.model),
sdk: event.sdk, sdk: event.sdk,
options: event.options, options: event.options,
language: event.language, language: event.language,
@ -72,13 +75,14 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
catalog.transform((draft) => catalog.transform((draft) =>
callback({ callback({
provider: { provider: {
list: draft.provider.list, list: () => mutable(draft.provider.list()),
get: (id) => draft.provider.get(ProviderV2.ID.make(id)), get: (id) => mutable(draft.provider.get(ProviderV2.ID.make(id))),
update: (id, update) => draft.provider.update(ProviderV2.ID.make(id), update), update: (id, update) => draft.provider.update(ProviderV2.ID.make(id), update),
remove: (id) => draft.provider.remove(ProviderV2.ID.make(id)), remove: (id) => draft.provider.remove(ProviderV2.ID.make(id)),
}, },
model: { model: {
get: (providerID, modelID) => draft.model.get(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID)), get: (providerID, modelID) =>
mutable(draft.model.get(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID))),
update: (providerID, modelID, update) => update: (providerID, modelID, update) =>
draft.model.update(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID), update), draft.model.update(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID), update),
remove: (providerID, modelID) => remove: (providerID, modelID) =>
@ -108,12 +112,12 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
transform: (callback) => transform: (callback) =>
integration.transform((draft) => integration.transform((draft) =>
callback({ callback({
list: draft.list, list: () => mutable(draft.list()),
get: (id) => draft.get(Integration.ID.make(id)), get: (id) => mutable(draft.get(Integration.ID.make(id))),
update: (id, update) => draft.update(Integration.ID.make(id), update), update: (id, update) => draft.update(Integration.ID.make(id), update),
remove: (id) => draft.remove(Integration.ID.make(id)), remove: (id) => draft.remove(Integration.ID.make(id)),
method: { method: {
list: (id) => draft.method.list(Integration.ID.make(id)), list: (id) => mutable(draft.method.list(Integration.ID.make(id))),
update: (input) => { update: (input) => {
if ("authorize" in input) { if ("authorize" in input) {
const methodID = Integration.MethodID.make(input.method.id) const methodID = Integration.MethodID.make(input.method.id)

View file

@ -65,8 +65,8 @@ export const layer = Layer.effect(
new Info({ new Info({
name, name,
path: source.path, path: source.path,
description: source.description, ...(source.description === undefined ? {} : { description: source.description }),
hidden: source.hidden, ...(source.hidden === undefined ? {} : { hidden: source.hidden }),
source, source,
}), }),
) )
@ -86,13 +86,13 @@ export const layer = Layer.effect(
seen.set(target, source.branch) seen.set(target, source.branch)
materialized.set( materialized.set(
name, name,
new Info({ new Info({
name, name,
path: AbsolutePath.make(target), path: AbsolutePath.make(target),
description: source.description, ...(source.description === undefined ? {} : { description: source.description }),
hidden: source.hidden, ...(source.hidden === undefined ? {} : { hidden: source.hidden }),
source, source,
}), }),
) )
yield* cache.ensure({ reference: repository, branch: source.branch, refresh: true }).pipe( yield* cache.ensure({ reference: repository, branch: source.branch, refresh: true }).pipe(
Effect.catchCause((cause) => Effect.catchCause((cause) =>

View file

@ -170,6 +170,7 @@ function providers(info?: Readonly<Record<string, ConfigProviderV1.Info>>) {
function migrateProvider(info: ConfigProviderV1.Info) { function migrateProvider(info: ConfigProviderV1.Info) {
const lowerer = ConfigProviderOptionsV1.get(info.npm) const lowerer = ConfigProviderOptionsV1.get(info.npm)
const options = lowerer.provider(info.options ?? {}) const options = lowerer.provider(info.options ?? {})
const url = info.api ?? options.url
return { return {
name: info.name, name: info.name,
env: info.env, env: info.env,
@ -177,7 +178,7 @@ function migrateProvider(info: ConfigProviderV1.Info) {
? { ? {
type: "aisdk" as const, type: "aisdk" as const,
package: info.npm, package: info.npm,
url: info.api ?? options.url, ...(url === undefined ? {} : { url }),
settings: options.settings ?? {}, settings: options.settings ?? {},
} }
: undefined, : undefined,
@ -221,7 +222,7 @@ function migrateModel(info: typeof ConfigProviderV1.Model.Type, packageName?: st
...(info.id === undefined ? {} : { id: info.id }), ...(info.id === undefined ? {} : { id: info.id }),
type: "aisdk" as const, type: "aisdk" as const,
package: info.provider.npm, package: info.provider.npm,
url: info.provider.api, ...(info.provider.api === undefined ? {} : { url: info.provider.api }),
settings: {}, settings: {},
} }
: info.id === undefined : info.id === undefined

View file

@ -106,7 +106,6 @@ describe("Config", () => {
expect(migrated.providers?.bedrock?.api).toEqual({ expect(migrated.providers?.bedrock?.api).toEqual({
type: "aisdk", type: "aisdk",
package: "@ai-sdk/amazon-bedrock", package: "@ai-sdk/amazon-bedrock",
url: undefined,
settings: { region: "us-east-1", profile: "dev" }, settings: { region: "us-east-1", profile: "dev" },
}) })
expect(migrated.providers?.bedrock?.request).toEqual({ expect(migrated.providers?.bedrock?.request).toEqual({

View file

@ -77,7 +77,9 @@ describe("TodoWriteTool", () => {
yield* setup yield* setup
const registry = yield* ToolRegistry.Service const registry = yield* ToolRegistry.Service
const service = yield* SessionTodo.Service const service = yield* SessionTodo.Service
const todoList = [{ content: "Implement slice", status: "in_progress", priority: "high" }] const todoList: ReadonlyArray<SessionTodo.Info> = [
{ content: "Implement slice", status: "in_progress", priority: "high" },
]
expect((yield* toolDefinitions(registry)).map((tool) => tool.name)).toEqual([TodoWriteTool.name]) expect((yield* toolDefinitions(registry)).map((tool) => tool.name)).toEqual([TodoWriteTool.name])
expect(yield* settleTool(registry, call(todoList))).toEqual({ expect(yield* settleTool(registry, call(todoList))).toEqual({

View file

@ -14,7 +14,7 @@ export type Info = SessionTodo.Info
export const Event = SessionTodo.Event export const Event = SessionTodo.Event
export interface Interface { export interface Interface {
readonly update: (input: { sessionID: SessionID; todos: Info[] }) => Effect.Effect<void> readonly update: (input: { sessionID: SessionID; todos: ReadonlyArray<Info> }) => Effect.Effect<void>
readonly get: (sessionID: SessionID) => Effect.Effect<Info[]> readonly get: (sessionID: SessionID) => Effect.Effect<Info[]>
} }
@ -26,7 +26,7 @@ export const layer = Layer.effect(
const events = yield* EventV2Bridge.Service const events = yield* EventV2Bridge.Service
const { db } = yield* Database.Service const { db } = yield* Database.Service
const update = Effect.fn("Todo.update")(function* (input: { sessionID: SessionID; todos: Info[] }) { const update = Effect.fn("Todo.update")(function* (input: { sessionID: SessionID; todos: ReadonlyArray<Info> }) {
yield* db yield* db
.transaction((tx) => .transaction((tx) =>
Effect.gen(function* () { Effect.gen(function* () {

View file

@ -3,19 +3,8 @@ import * as Tool from "./tool"
import DESCRIPTION_WRITE from "./todowrite.txt" import DESCRIPTION_WRITE from "./todowrite.txt"
import { Todo } from "../session/todo" import { Todo } from "../session/todo"
// Todo.Info is still a zod schema (session/todo.ts). Inline the field shape
// here rather than referencing its `.shape` — the LLM-visible JSON Schema is
// identical, and it removes the last zod dependency from this tool.
const TodoItem = Schema.Struct({
content: Schema.String.annotate({ description: "Brief description of the task" }),
status: Schema.String.annotate({
description: "Current status of the task: pending, in_progress, completed, cancelled",
}),
priority: Schema.String.annotate({ description: "Priority level of the task: high, medium, low" }),
})
export const Parameters = Schema.Struct({ export const Parameters = Schema.Struct({
todos: Schema.mutable(Schema.Array(TodoItem)).annotate({ description: "The updated todo list" }), todos: Schema.mutable(Schema.Array(Todo.Info)).annotate({ description: "The updated todo list" }),
}) })
type Metadata = { type Metadata = {

View file

@ -1195,7 +1195,7 @@ const scenarios: Scenario[] = [
.seeded((ctx) => .seeded((ctx) =>
Effect.gen(function* () { Effect.gen(function* () {
const session = yield* ctx.session({ title: "Todo session" }) const session = yield* ctx.session({ title: "Todo session" })
const todos = [{ content: "cover session todo", status: "pending", priority: "high" }] const todos = [{ content: "cover session todo", status: "pending" as const, priority: "high" as const }]
yield* ctx.todos(session.id, todos) yield* ctx.todos(session.id, todos)
return { session, todos } return { session, todos }
}), }),

View file

@ -119,5 +119,9 @@ export type Result =
| { status: "skip"; scenario: TodoScenario } | { status: "skip"; scenario: TodoScenario }
export type SessionInfo = { id: SessionID; title: string; parentID?: SessionID } export type SessionInfo = { id: SessionID; title: string; parentID?: SessionID }
export type TodoInfo = { content: string; status: string; priority: string } export type TodoInfo = {
content: string
status: "pending" | "in_progress" | "completed" | "cancelled"
priority: "high" | "medium" | "low"
}
export type MessageSeed = { info: SessionV1.User; part: SessionV1.TextPart } export type MessageSeed = { info: SessionV1.User; part: SessionV1.TextPart }

View file

@ -44,26 +44,18 @@ describe("reference HttpApi", () => {
{ {
name: "docs", name: "docs",
path: path.join(tmp.path, "docs"), path: path.join(tmp.path, "docs"),
description: null,
hidden: null,
source: { source: {
type: "local", type: "local",
path: path.join(tmp.path, "docs"), path: path.join(tmp.path, "docs"),
description: null,
hidden: null,
}, },
}, },
{ {
name: "effect", name: "effect",
path: path.join(Global.Path.repos, "github.com", "Effect-TS", "effect"), path: path.join(Global.Path.repos, "github.com", "Effect-TS", "effect"),
description: null,
hidden: null,
source: { source: {
type: "git", type: "git",
repository: "Effect-TS/effect", repository: "Effect-TS/effect",
branch: "main", branch: "main",
description: null,
hidden: null,
}, },
}, },
]) ])

View file

@ -256,7 +256,7 @@ describe("Todo.Info", () => {
const decode = decodeUnknown(Todo.Info) const decode = decodeUnknown(Todo.Info)
test("three-field round-trip", () => { test("three-field round-trip", () => {
const input = { content: "do a thing", status: "pending", priority: "high" } const input = Todo.Info.make({ content: "do a thing", status: "pending", priority: "high" })
expect(decode(input)).toEqual(input) expect(decode(input)).toEqual(input)
}) })
}) })

View file

@ -1,6 +1,7 @@
export * as Agent from "./agent" export * as Agent from "./agent"
import { Schema } from "effect" import { Schema } from "effect"
import { optional } from "./schema"
import { Model } from "./model" import { Model } from "./model"
import { Permission } from "./permission" import { Permission } from "./permission"
import { Provider } from "./provider" import { Provider } from "./provider"
@ -12,20 +13,20 @@ export type ID = typeof ID.Type
export const Color = Schema.Union([ export const Color = Schema.Union([
Schema.String.check(Schema.isPattern(/^#[0-9a-fA-F]{6}$/)), Schema.String.check(Schema.isPattern(/^#[0-9a-fA-F]{6}$/)),
Schema.Literals(["primary", "secondary", "accent", "success", "warning", "error", "info"]), Schema.Literals(["primary", "secondary", "accent", "success", "warning", "error", "info"]),
]) ]).annotate({ identifier: "Agent.Color" })
export type Color = typeof Color.Type export type Color = typeof Color.Type
export interface Info extends Schema.Schema.Type<typeof Info> {} export interface Info extends Schema.Schema.Type<typeof Info> {}
export const Info = Schema.Struct({ export const Info = Schema.Struct({
id: ID, id: ID,
model: Model.Ref.pipe(Schema.optional), model: Model.Ref.pipe(optional),
request: Provider.Request, request: Provider.Request,
system: Schema.String.pipe(Schema.optional), system: Schema.String.pipe(optional),
description: Schema.String.pipe(Schema.optional), description: Schema.String.pipe(optional),
mode: Schema.Literals(["subagent", "primary", "all"]), mode: Schema.Literals(["subagent", "primary", "all"]),
hidden: Schema.Boolean, hidden: Schema.Boolean,
color: Color.pipe(Schema.optional), color: Color.pipe(optional),
steps: PositiveInt.pipe(Schema.optional), steps: PositiveInt.pipe(optional),
permissions: Permission.Ruleset, permissions: Permission.Ruleset,
}) })
.annotate({ identifier: "AgentV2.Info" }) .annotate({ identifier: "AgentV2.Info" })

View file

@ -1,14 +1,15 @@
export * as Command from "./command" export * as Command from "./command"
import { Schema } from "effect" import { Schema } from "effect"
import { optional } from "./schema"
import { Model } from "./model" import { Model } from "./model"
export interface Info extends Schema.Schema.Type<typeof Info> {} export interface Info extends Schema.Schema.Type<typeof Info> {}
export const Info = Schema.Struct({ export const Info = Schema.Struct({
name: Schema.String, name: Schema.String,
template: Schema.String, template: Schema.String,
description: Schema.String.pipe(Schema.optional), description: Schema.String.pipe(optional),
agent: Schema.String.pipe(Schema.optional), agent: Schema.String.pipe(optional),
model: Model.Ref.pipe(Schema.optional), model: Model.Ref.pipe(optional),
subtask: Schema.Boolean.pipe(Schema.optional), subtask: Schema.Boolean.pipe(optional),
}).annotate({ identifier: "CommandV2.Info" }) }).annotate({ identifier: "CommandV2.Info" })

View file

@ -1,6 +1,7 @@
export * as Credential from "./credential" export * as Credential from "./credential"
import { Schema } from "effect" import { Schema } from "effect"
import { optional } from "./schema"
import { IntegrationMethodID } from "./integration-id" import { IntegrationMethodID } from "./integration-id"
import { ascending } from "./identifier" import { ascending } from "./identifier"
import { NonNegativeInt, statics } from "./schema" import { NonNegativeInt, statics } from "./schema"
@ -18,14 +19,14 @@ export const OAuth = Schema.Struct({
refresh: Schema.String, refresh: Schema.String,
access: Schema.String, access: Schema.String,
expires: NonNegativeInt, expires: NonNegativeInt,
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)), metadata: optional(Schema.Record(Schema.String, Schema.Unknown)),
}).annotate({ identifier: "Credential.OAuth" }) }).annotate({ identifier: "Credential.OAuth" })
export interface Key extends Schema.Schema.Type<typeof Key> {} export interface Key extends Schema.Schema.Type<typeof Key> {}
export const Key = Schema.Struct({ export const Key = Schema.Struct({
type: Schema.Literal("key"), type: Schema.Literal("key"),
key: Schema.String, key: Schema.String,
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)), metadata: optional(Schema.Record(Schema.String, Schema.Unknown)),
}).annotate({ identifier: "Credential.Key" }) }).annotate({ identifier: "Credential.Key" })
export const Value = Schema.Union([OAuth, Key]) export const Value = Schema.Union([OAuth, Key])

View file

@ -1,6 +1,7 @@
export * as Event from "./event" export * as Event from "./event"
import { Schema } from "effect" import { Schema } from "effect"
import { optional } from "./schema"
import { ascending } from "./identifier" import { ascending } from "./identifier"
import { Location } from "./location" import { Location } from "./location"
import { statics } from "./schema" import { statics } from "./schema"
@ -53,12 +54,10 @@ export function define<
return Object.assign( return Object.assign(
Schema.Struct({ Schema.Struct({
id: ID, id: ID,
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)), metadata: optional(Schema.Record(Schema.String, Schema.Unknown)),
type: Schema.Literal(input.type), type: Schema.Literal(input.type),
durable: Schema.optional( durable: optional(Schema.Struct({ aggregateID: Schema.String, seq: Schema.Number, version: Schema.Number })),
Schema.Struct({ aggregateID: Schema.String, seq: Schema.Number, version: Schema.Number }), location: optional(Location.Ref),
),
location: Schema.optional(Location.Ref),
data, data,
}).annotate({ identifier: input.type }), }).annotate({ identifier: input.type }),
{ {

View file

@ -1,12 +1,13 @@
export * as FileDiff from "./file-diff" export * as FileDiff from "./file-diff"
import { Schema } from "effect" import { Schema } from "effect"
import { optional } from "./schema"
export const Info = Schema.Struct({ export const Info = Schema.Struct({
file: Schema.optional(Schema.String), file: optional(Schema.String),
patch: Schema.optional(Schema.String), patch: optional(Schema.String),
additions: Schema.Finite, additions: Schema.Finite,
deletions: Schema.Finite, deletions: Schema.Finite,
status: Schema.optional(Schema.Literals(["added", "deleted", "modified"])), status: optional(Schema.Literals(["added", "deleted", "modified"])),
}).annotate({ identifier: "SnapshotFileDiff" }) }).annotate({ identifier: "SnapshotFileDiff" })
export type Info = typeof Info.Type export interface Info extends Schema.Schema.Type<typeof Info> {}

View file

@ -1,6 +1,7 @@
export * as FileSystem from "./filesystem" export * as FileSystem from "./filesystem"
import { Schema } from "effect" import { Schema } from "effect"
import { optional } from "./schema"
import { define, inventory } from "./event" import { define, inventory } from "./event"
import { NonNegativeInt, PositiveInt, RelativePath } from "./schema" import { NonNegativeInt, PositiveInt, RelativePath } from "./schema"
@ -21,7 +22,7 @@ export const Submatch = Schema.Struct({
text: Schema.String, text: Schema.String,
start: NonNegativeInt, start: NonNegativeInt,
end: NonNegativeInt, end: NonNegativeInt,
}) }).annotate({ identifier: "FileSystem.Submatch" })
export interface Match extends Schema.Schema.Type<typeof Match> {} export interface Match extends Schema.Schema.Type<typeof Match> {}
export const Match = Schema.Struct({ export const Match = Schema.Struct({
@ -34,6 +35,6 @@ export const Match = Schema.Struct({
export class FindInput extends Schema.Class<FindInput>("FileSystem.FindInput")({ export class FindInput extends Schema.Class<FindInput>("FileSystem.FindInput")({
query: Schema.String, query: Schema.String,
type: Schema.Literals(["file", "directory"]).pipe(Schema.optional), type: Schema.Literals(["file", "directory"]).pipe(optional),
limit: PositiveInt.pipe(Schema.optional), limit: PositiveInt.pipe(optional),
}) {} }) {}

View file

@ -1,6 +1,7 @@
export * as Integration from "./integration" export * as Integration from "./integration"
import { Schema } from "effect" import { Schema } from "effect"
import { optional } from "./schema"
import { define, inventory } from "./event" import { define, inventory } from "./event"
import { Connection } from "./connection" import { Connection } from "./connection"
import { ascending } from "./identifier" import { ascending } from "./identifier"
@ -25,8 +26,8 @@ export const TextPrompt = Schema.Struct({
type: Schema.Literal("text"), type: Schema.Literal("text"),
key: Schema.String, key: Schema.String,
message: Schema.String, message: Schema.String,
placeholder: Schema.optional(Schema.String), placeholder: optional(Schema.String),
when: Schema.optional(When), when: optional(When),
}).annotate({ identifier: "Integration.TextPrompt" }) }).annotate({ identifier: "Integration.TextPrompt" })
export interface SelectPrompt extends Schema.Schema.Type<typeof SelectPrompt> {} export interface SelectPrompt extends Schema.Schema.Type<typeof SelectPrompt> {}
@ -34,16 +35,14 @@ export const SelectPrompt = Schema.Struct({
type: Schema.Literal("select"), type: Schema.Literal("select"),
key: Schema.String, key: Schema.String,
message: Schema.String, message: Schema.String,
options: Schema.mutable( options: Schema.Array(
Schema.Array( Schema.Struct({
Schema.Struct({ label: Schema.String,
label: Schema.String, value: Schema.String,
value: Schema.String, hint: optional(Schema.String),
hint: Schema.optional(Schema.String), }),
}),
),
), ),
when: Schema.optional(When), when: optional(When),
}).annotate({ identifier: "Integration.SelectPrompt" }) }).annotate({ identifier: "Integration.SelectPrompt" })
export const Prompt = Schema.Union([TextPrompt, SelectPrompt]).pipe(Schema.toTaggedUnion("type")) export const Prompt = Schema.Union([TextPrompt, SelectPrompt]).pipe(Schema.toTaggedUnion("type"))
@ -54,19 +53,19 @@ export const OAuthMethod = Schema.Struct({
id: MethodID, id: MethodID,
type: Schema.Literal("oauth"), type: Schema.Literal("oauth"),
label: Schema.String, label: Schema.String,
prompts: Schema.optional(Schema.mutable(Schema.Array(Prompt))), prompts: optional(Schema.Array(Prompt)),
}).annotate({ identifier: "Integration.OAuthMethod" }) }).annotate({ identifier: "Integration.OAuthMethod" })
export interface KeyMethod extends Schema.Schema.Type<typeof KeyMethod> {} export interface KeyMethod extends Schema.Schema.Type<typeof KeyMethod> {}
export const KeyMethod = Schema.Struct({ export const KeyMethod = Schema.Struct({
type: Schema.Literal("key"), type: Schema.Literal("key"),
label: Schema.optional(Schema.String), label: optional(Schema.String),
}).annotate({ identifier: "Integration.KeyMethod" }) }).annotate({ identifier: "Integration.KeyMethod" })
export interface EnvMethod extends Schema.Schema.Type<typeof EnvMethod> {} export interface EnvMethod extends Schema.Schema.Type<typeof EnvMethod> {}
export const EnvMethod = Schema.Struct({ export const EnvMethod = Schema.Struct({
type: Schema.Literal("env"), type: Schema.Literal("env"),
names: Schema.mutable(Schema.Array(Schema.String)), names: Schema.Array(Schema.String),
}).annotate({ identifier: "Integration.EnvMethod" }) }).annotate({ identifier: "Integration.EnvMethod" })
export const Method = Schema.Union([OAuthMethod, KeyMethod, EnvMethod]) export const Method = Schema.Union([OAuthMethod, KeyMethod, EnvMethod])
@ -96,8 +95,8 @@ export const Ref = Schema.Struct({
export class Info extends Schema.Class<Info>("Integration.Info")({ export class Info extends Schema.Class<Info>("Integration.Info")({
id: ID, id: ID,
name: Schema.String, name: Schema.String,
methods: Schema.mutable(Schema.Array(Method)), methods: Schema.Array(Method),
connections: Schema.mutable(Schema.Array(Connection.Info)), connections: Schema.Array(Connection.Info),
}) {} }) {}
export const AttemptID = Schema.String.pipe( export const AttemptID = Schema.String.pipe(
@ -124,5 +123,7 @@ export const AttemptStatus = Schema.Union([
Schema.Struct({ status: Schema.Literal("complete"), time: AttemptTime }), Schema.Struct({ status: Schema.Literal("complete"), time: AttemptTime }),
Schema.Struct({ status: Schema.Literal("failed"), message: Schema.String, time: AttemptTime }), Schema.Struct({ status: Schema.Literal("failed"), message: Schema.String, time: AttemptTime }),
Schema.Struct({ status: Schema.Literal("expired"), time: AttemptTime }), Schema.Struct({ status: Schema.Literal("expired"), time: AttemptTime }),
]).pipe(Schema.toTaggedUnion("status")) ])
.pipe(Schema.toTaggedUnion("status"))
.annotate({ identifier: "Integration.AttemptStatus" })
export type AttemptStatus = typeof AttemptStatus.Type export type AttemptStatus = typeof AttemptStatus.Type

View file

@ -1,8 +1,11 @@
export * as LLM from "./llm" export * as LLM from "./llm"
import { Schema } from "effect" import { Schema } from "effect"
import { optional } from "./schema"
export const ProviderMetadata = Schema.Record(Schema.String, Schema.Record(Schema.String, Schema.Unknown)) export const ProviderMetadata = Schema.Record(Schema.String, Schema.Record(Schema.String, Schema.Unknown)).annotate({
identifier: "LLM.ProviderMetadata",
})
export type ProviderMetadata = Schema.Schema.Type<typeof ProviderMetadata> export type ProviderMetadata = Schema.Schema.Type<typeof ProviderMetadata>
export interface ToolTextContent extends Schema.Schema.Type<typeof ToolTextContent> {} export interface ToolTextContent extends Schema.Schema.Type<typeof ToolTextContent> {}
@ -16,8 +19,10 @@ export const ToolFileContent = Schema.Struct({
type: Schema.Literal("file"), type: Schema.Literal("file"),
uri: Schema.String, uri: Schema.String,
mime: Schema.String, mime: Schema.String,
name: Schema.optional(Schema.String), name: optional(Schema.String),
}).annotate({ identifier: "Tool.FileContent" }) }).annotate({ identifier: "Tool.FileContent" })
export const ToolContent = Schema.Union([ToolTextContent, ToolFileContent]).pipe(Schema.toTaggedUnion("type")) export const ToolContent = Schema.Union([ToolTextContent, ToolFileContent])
.pipe(Schema.toTaggedUnion("type"))
.annotate({ identifier: "LLM.ToolContent" })
export type ToolContent = Schema.Schema.Type<typeof ToolContent> export type ToolContent = Schema.Schema.Type<typeof ToolContent>

View file

@ -1,6 +1,6 @@
export * as Location from "./location" export * as Location from "./location"
import { Effect, Schema } from "effect" import { Schema } from "effect"
import { AbsolutePath, optional } from "./schema" import { AbsolutePath, optional } from "./schema"
import { ProjectID } from "./project-id" import { ProjectID } from "./project-id"
import { WorkspaceID } from "./workspace-id" import { WorkspaceID } from "./workspace-id"
@ -8,10 +8,7 @@ import { WorkspaceID } from "./workspace-id"
export interface Ref extends Schema.Schema.Type<typeof Ref> {} export interface Ref extends Schema.Schema.Type<typeof Ref> {}
export const Ref = Schema.Struct({ export const Ref = Schema.Struct({
directory: AbsolutePath, directory: AbsolutePath,
workspaceID: Schema.optional(WorkspaceID).pipe( workspaceID: optional(WorkspaceID),
Schema.withDecodingDefault(Effect.succeed(undefined)),
Schema.withConstructorDefault(Effect.succeed(undefined)),
),
}).annotate({ identifier: "Location.Ref" }) }).annotate({ identifier: "Location.Ref" })
export class Info extends Schema.Class<Info>("Location.Info")({ export class Info extends Schema.Class<Info>("Location.Info")({

View file

@ -1,6 +1,7 @@
export * as Model from "./model" export * as Model from "./model"
import { Schema } from "effect" import { Schema } from "effect"
import { optional } from "./schema"
import { Provider } from "./provider" import { Provider } from "./provider"
import { statics } from "./schema" import { statics } from "./schema"
@ -13,9 +14,9 @@ export type VariantID = typeof VariantID.Type
export const Ref = Schema.Struct({ export const Ref = Schema.Struct({
id: ID, id: ID,
providerID: Provider.ID, providerID: Provider.ID,
variant: VariantID.pipe(Schema.optional), variant: VariantID.pipe(optional),
}) }).annotate({ identifier: "Model.Ref" })
export type Ref = typeof Ref.Type export interface Ref extends Schema.Schema.Type<typeof Ref> {}
export const Family = Schema.String.pipe(Schema.brand("Family")) export const Family = Schema.String.pipe(Schema.brand("Family"))
export type Family = typeof Family.Type export type Family = typeof Family.Type
@ -23,23 +24,23 @@ export type Family = typeof Family.Type
export interface Capabilities extends Schema.Schema.Type<typeof Capabilities> {} export interface Capabilities extends Schema.Schema.Type<typeof Capabilities> {}
export const Capabilities = Schema.Struct({ export const Capabilities = Schema.Struct({
tools: Schema.Boolean, tools: Schema.Boolean,
input: Schema.String.pipe(Schema.Array, Schema.mutable), input: Schema.Array(Schema.String),
output: Schema.String.pipe(Schema.Array, Schema.mutable), output: Schema.Array(Schema.String),
}) }).annotate({ identifier: "Model.Capabilities" })
export interface Cost extends Schema.Schema.Type<typeof Cost> {} export interface Cost extends Schema.Schema.Type<typeof Cost> {}
export const Cost = Schema.Struct({ export const Cost = Schema.Struct({
tier: Schema.Struct({ tier: Schema.Struct({
type: Schema.Literal("context"), type: Schema.Literal("context"),
size: Schema.Int, size: Schema.Int,
}).pipe(Schema.optional), }).pipe(optional),
input: Schema.Finite, input: Schema.Finite,
output: Schema.Finite, output: Schema.Finite,
cache: Schema.Struct({ cache: Schema.Struct({
read: Schema.Finite, read: Schema.Finite,
write: Schema.Finite, write: Schema.Finite,
}), }),
}) }).annotate({ identifier: "Model.Cost" })
export const Api = Schema.Union([ export const Api = Schema.Union([
Schema.Struct({ Schema.Struct({
@ -50,34 +51,36 @@ export const Api = Schema.Union([
id: ID, id: ID,
...Provider.Native.fields, ...Provider.Native.fields,
}), }),
]).pipe(Schema.toTaggedUnion("type")) ])
.pipe(Schema.toTaggedUnion("type"))
.annotate({ identifier: "Model.Api" })
export type Api = typeof Api.Type export type Api = typeof Api.Type
export interface Info extends Schema.Schema.Type<typeof Info> {} export interface Info extends Schema.Schema.Type<typeof Info> {}
export const Info = Schema.Struct({ export const Info = Schema.Struct({
id: ID, id: ID,
providerID: Provider.ID, providerID: Provider.ID,
family: Family.pipe(Schema.optional), family: Family.pipe(optional),
name: Schema.String, name: Schema.String,
api: Api, api: Api,
capabilities: Capabilities, capabilities: Capabilities,
request: Schema.Struct({ request: Schema.Struct({
...Provider.Request.fields, ...Provider.Request.fields,
variant: Schema.String.pipe(Schema.optional), variant: Schema.String.pipe(optional),
}), }),
variants: Schema.Struct({ variants: Schema.Struct({
id: VariantID, id: VariantID,
...Provider.Request.fields, ...Provider.Request.fields,
}).pipe(Schema.Array, Schema.mutable), }).pipe(Schema.Array),
time: Schema.Struct({ time: Schema.Struct({
released: Schema.Finite, released: Schema.Finite,
}), }),
cost: Cost.pipe(Schema.Array, Schema.mutable), cost: Schema.Array(Cost),
status: Schema.Literals(["alpha", "beta", "deprecated", "active"]), status: Schema.Literals(["alpha", "beta", "deprecated", "active"]),
enabled: Schema.Boolean, enabled: Schema.Boolean,
limit: Schema.Struct({ limit: Schema.Struct({
context: Schema.Int, context: Schema.Int,
input: Schema.Int.pipe(Schema.optional), input: Schema.Int.pipe(optional),
output: Schema.Int, output: Schema.Int,
}), }),
}) })

View file

@ -17,4 +17,4 @@ export const Info = Schema.Struct({
action: Schema.String, action: Schema.String,
resource: Schema.String, resource: Schema.String,
}).annotate({ identifier: "PermissionSaved.Info" }) }).annotate({ identifier: "PermissionSaved.Info" })
export type Info = typeof Info.Type export interface Info extends Schema.Schema.Type<typeof Info> {}

View file

@ -1,6 +1,7 @@
export * as Permission from "./permission" export * as Permission from "./permission"
import { Schema } from "effect" import { Schema } from "effect"
import { optional } from "./schema"
import { define, inventory } from "./event" import { define, inventory } from "./event"
import { ascending } from "./identifier" import { ascending } from "./identifier"
import { SessionID } from "./session-id" import { SessionID } from "./session-id"
@ -25,16 +26,16 @@ const RequestFields = {
sessionID: SessionID, sessionID: SessionID,
action: Schema.String, action: Schema.String,
resources: Schema.Array(Schema.String), resources: Schema.Array(Schema.String),
save: Schema.Array(Schema.String).pipe(Schema.optional), save: Schema.Array(Schema.String).pipe(optional),
metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional), metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(optional),
source: Source.pipe(Schema.optional), source: Source.pipe(optional),
} }
export const Request = Schema.Struct({ export const Request = Schema.Struct({
id: ID, id: ID,
...RequestFields, ...RequestFields,
}).annotate({ identifier: "PermissionV2.Request" }) }).annotate({ identifier: "PermissionV2.Request" })
export type Request = typeof Request.Type export interface Request extends Schema.Schema.Type<typeof Request> {}
export const Reply = Schema.Literals(["once", "always", "reject"]).annotate({ identifier: "PermissionV2.Reply" }) export const Reply = Schema.Literals(["once", "always", "reject"]).annotate({ identifier: "PermissionV2.Reply" })
export type Reply = typeof Reply.Type export type Reply = typeof Reply.Type
@ -60,5 +61,5 @@ export const Rule = Schema.Struct({
effect: Effect, effect: Effect,
}).annotate({ identifier: "PermissionV2.Rule" }) }).annotate({ identifier: "PermissionV2.Rule" })
export const Ruleset = Schema.mutable(Schema.Array(Rule)).annotate({ identifier: "PermissionV2.Ruleset" }) export const Ruleset = Schema.Array(Rule).annotate({ identifier: "PermissionV2.Ruleset" })
export type Ruleset = typeof Ruleset.Type export type Ruleset = typeof Ruleset.Type

View file

@ -1,6 +1,7 @@
export * as ProjectCopy from "./project-copy" export * as ProjectCopy from "./project-copy"
import { Schema } from "effect" import { Schema } from "effect"
import { optional } from "./schema"
import { ProjectID } from "./project-id" import { ProjectID } from "./project-id"
import { AbsolutePath } from "./schema" import { AbsolutePath } from "./schema"
@ -12,18 +13,18 @@ export const CreateInput = Schema.Struct({
strategy: StrategyID, strategy: StrategyID,
sourceDirectory: AbsolutePath, sourceDirectory: AbsolutePath,
directory: AbsolutePath, directory: AbsolutePath,
name: Schema.optional(Schema.String), name: optional(Schema.String),
}).annotate({ identifier: "ProjectCopy.CreateInput" }) }).annotate({ identifier: "ProjectCopy.CreateInput" })
export type CreateInput = typeof CreateInput.Type export interface CreateInput extends Schema.Schema.Type<typeof CreateInput> {}
export const RemoveInput = Schema.Struct({ export const RemoveInput = Schema.Struct({
projectID: ProjectID, projectID: ProjectID,
directory: AbsolutePath, directory: AbsolutePath,
force: Schema.Boolean, force: Schema.Boolean,
}).annotate({ identifier: "ProjectCopy.RemoveInput" }) }).annotate({ identifier: "ProjectCopy.RemoveInput" })
export type RemoveInput = typeof RemoveInput.Type export interface RemoveInput extends Schema.Schema.Type<typeof RemoveInput> {}
export const Copy = Schema.Struct({ export const Copy = Schema.Struct({
directory: AbsolutePath, directory: AbsolutePath,
}).annotate({ identifier: "ProjectCopy.Copy" }) }).annotate({ identifier: "ProjectCopy.Copy" })
export type Copy = typeof Copy.Type export interface Copy extends Schema.Schema.Type<typeof Copy> {}

View file

@ -8,22 +8,25 @@ import { ProjectID } from "./project-id"
export const ID = ProjectID export const ID = ProjectID
export type ID = typeof ID.Type export type ID = typeof ID.Type
export const Vcs = Schema.Literal("git") export const Vcs = Schema.Literal("git").annotate({ identifier: "Project.Vcs" })
export const Icon = Schema.Struct({ export const Icon = Schema.Struct({
url: optional(Schema.String), url: optional(Schema.String),
override: optional(Schema.String), override: optional(Schema.String),
color: optional(Schema.String), color: optional(Schema.String),
}) }).annotate({ identifier: "Project.Icon" })
export interface Icon extends Schema.Schema.Type<typeof Icon> {}
export const Commands = Schema.Struct({ export const Commands = Schema.Struct({
start: optional( start: optional(
Schema.String.annotate({ description: "Startup script to run when creating a new workspace (worktree)" }), Schema.String.annotate({ description: "Startup script to run when creating a new workspace (worktree)" }),
), ),
}) }).annotate({ identifier: "Project.Commands" })
export interface Commands extends Schema.Schema.Type<typeof Commands> {}
export const Time = Schema.Struct({ export const Time = Schema.Struct({
created: NonNegativeInt, created: NonNegativeInt,
updated: NonNegativeInt, updated: NonNegativeInt,
initialized: optional(NonNegativeInt), initialized: optional(NonNegativeInt),
}) }).annotate({ identifier: "Project.Time" })
export interface Time extends Schema.Schema.Type<typeof Time> {}
export const Info = Schema.Struct({ export const Info = Schema.Struct({
id: ID, id: ID,
@ -35,7 +38,7 @@ export const Info = Schema.Struct({
time: Time, time: Time,
sandboxes: Schema.Array(Schema.String), sandboxes: Schema.Array(Schema.String),
}).annotate({ identifier: "Project" }) }).annotate({ identifier: "Project" })
export type Info = typeof Info.Type export interface Info extends Schema.Schema.Type<typeof Info> {}
const Updated = define({ type: "project.updated", schema: Info.fields }) const Updated = define({ type: "project.updated", schema: Info.fields })
export const Event = { Updated, Definitions: inventory(Updated) } export const Event = { Updated, Definitions: inventory(Updated) }

View file

@ -1,4 +1,5 @@
import { Schema } from "effect" import { Schema } from "effect"
import { optional } from "./schema"
import { statics } from "./schema" import { statics } from "./schema"
export interface Source extends Schema.Schema.Type<typeof Source> {} export interface Source extends Schema.Schema.Type<typeof Source> {}
@ -12,9 +13,9 @@ export interface FileAttachment extends Schema.Schema.Type<typeof FileAttachment
export const FileAttachment = Schema.Struct({ export const FileAttachment = Schema.Struct({
uri: Schema.String, uri: Schema.String,
mime: Schema.String, mime: Schema.String,
name: Schema.String.pipe(Schema.optional), name: Schema.String.pipe(optional),
description: Schema.String.pipe(Schema.optional), description: Schema.String.pipe(optional),
source: Source.pipe(Schema.optional), source: Source.pipe(optional),
}) })
.annotate({ identifier: "Prompt.FileAttachment" }) .annotate({ identifier: "Prompt.FileAttachment" })
.pipe( .pipe(
@ -33,14 +34,14 @@ export const FileAttachment = Schema.Struct({
export interface AgentAttachment extends Schema.Schema.Type<typeof AgentAttachment> {} export interface AgentAttachment extends Schema.Schema.Type<typeof AgentAttachment> {}
export const AgentAttachment = Schema.Struct({ export const AgentAttachment = Schema.Struct({
name: Schema.String, name: Schema.String,
source: Source.pipe(Schema.optional), source: Source.pipe(optional),
}).annotate({ identifier: "Prompt.AgentAttachment" }) }).annotate({ identifier: "Prompt.AgentAttachment" })
export interface Prompt extends Schema.Schema.Type<typeof Prompt> {} export interface Prompt extends Schema.Schema.Type<typeof Prompt> {}
export const Prompt = Schema.Struct({ export const Prompt = Schema.Struct({
text: Schema.String, text: Schema.String,
files: Schema.Array(FileAttachment).pipe(Schema.optional), files: Schema.Array(FileAttachment).pipe(optional),
agents: Schema.Array(AgentAttachment).pipe(Schema.optional), agents: Schema.Array(AgentAttachment).pipe(optional),
}) })
.annotate({ identifier: "Prompt" }) .annotate({ identifier: "Prompt" })
.pipe( .pipe(

View file

@ -1,6 +1,7 @@
export * as Provider from "./provider" export * as Provider from "./provider"
import { Schema } from "effect" import { Schema } from "effect"
import { optional } from "./schema"
import { Integration } from "./integration" import { Integration } from "./integration"
import { statics } from "./schema" import { statics } from "./schema"
@ -26,32 +27,34 @@ export interface AISDK extends Schema.Schema.Type<typeof AISDK> {}
export const AISDK = Schema.Struct({ export const AISDK = Schema.Struct({
type: Schema.Literal("aisdk"), type: Schema.Literal("aisdk"),
package: Schema.String, package: Schema.String,
url: Schema.String.pipe(Schema.optional), url: Schema.String.pipe(optional),
settings: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional), settings: Schema.Record(Schema.String, Schema.Unknown).pipe(optional),
}) }).annotate({ identifier: "Provider.AISDK" })
export interface Native extends Schema.Schema.Type<typeof Native> {} export interface Native extends Schema.Schema.Type<typeof Native> {}
export const Native = Schema.Struct({ export const Native = Schema.Struct({
type: Schema.Literal("native"), type: Schema.Literal("native"),
url: Schema.String.pipe(Schema.optional), url: Schema.String.pipe(optional),
settings: Schema.Record(Schema.String, Schema.Unknown), settings: Schema.Record(Schema.String, Schema.Unknown),
}) }).annotate({ identifier: "Provider.Native" })
export const Api = Schema.Union([AISDK, Native]).pipe(Schema.toTaggedUnion("type")) export const Api = Schema.Union([AISDK, Native])
.pipe(Schema.toTaggedUnion("type"))
.annotate({ identifier: "Provider.Api" })
export type Api = typeof Api.Type export type Api = typeof Api.Type
export interface Request extends Schema.Schema.Type<typeof Request> {} export interface Request extends Schema.Schema.Type<typeof Request> {}
export const Request = Schema.Struct({ export const Request = Schema.Struct({
headers: Schema.Record(Schema.String, Schema.String), headers: Schema.Record(Schema.String, Schema.String),
body: Schema.Record(Schema.String, Schema.Any), body: Schema.Record(Schema.String, Schema.Json),
}) }).annotate({ identifier: "Provider.Request" })
export interface Info extends Schema.Schema.Type<typeof Info> {} export interface Info extends Schema.Schema.Type<typeof Info> {}
export const Info = Schema.Struct({ export const Info = Schema.Struct({
id: ID, id: ID,
integrationID: Integration.ID.pipe(Schema.optional), integrationID: Integration.ID.pipe(optional),
name: Schema.String, name: Schema.String,
disabled: Schema.Boolean.pipe(Schema.optional), disabled: Schema.Boolean.pipe(optional),
api: Api, api: Api,
request: Request, request: Request,
}) })

View file

@ -6,4 +6,5 @@ import { PositiveInt } from "./schema"
export const ConnectToken = Schema.Struct({ export const ConnectToken = Schema.Struct({
ticket: Schema.String, ticket: Schema.String,
expires_in: PositiveInt, expires_in: PositiveInt,
}) }).annotate({ identifier: "PtyTicket.ConnectToken" })
export interface ConnectToken extends Schema.Schema.Type<typeof ConnectToken> {}

View file

@ -1,6 +1,7 @@
export * as Pty from "./pty" export * as Pty from "./pty"
import { Schema } from "effect" import { Schema } from "effect"
import { optional } from "./schema"
import { define, inventory } from "./event" import { define, inventory } from "./event"
import { ascending } from "./identifier" import { ascending } from "./identifier"
import { NonNegativeInt, PositiveInt, statics } from "./schema" import { NonNegativeInt, PositiveInt, statics } from "./schema"
@ -8,9 +9,13 @@ import { NonNegativeInt, PositiveInt, statics } from "./schema"
const IDSchema = Schema.String.check(Schema.isStartsWith("pty")).pipe(Schema.brand("PtyID")) const IDSchema = Schema.String.check(Schema.isStartsWith("pty")).pipe(Schema.brand("PtyID"))
export const ID = IDSchema.pipe( export const ID = IDSchema.pipe(
statics((schema: typeof IDSchema) => ({ statics((schema: typeof IDSchema) => {
ascending: (id?: string) => schema.make(id ?? "pty_" + ascending()), const create = () => schema.make("pty_" + ascending())
})), return {
create,
ascending: (id?: string) => (id === undefined ? create() : schema.make(id)),
}
}),
) )
export type ID = typeof ID.Type export type ID = typeof ID.Type
@ -22,8 +27,9 @@ export const Info = Schema.Struct({
cwd: Schema.String, cwd: Schema.String,
status: Schema.Literals(["running", "exited"]), status: Schema.Literals(["running", "exited"]),
pid: NonNegativeInt, pid: NonNegativeInt,
exitCode: Schema.optional(NonNegativeInt), exitCode: optional(NonNegativeInt),
}).annotate({ identifier: "Pty" }) }).annotate({ identifier: "Pty" })
export interface Info extends Schema.Schema.Type<typeof Info> {}
const Created = define({ type: "pty.created", schema: { info: Info } }) const Created = define({ type: "pty.created", schema: { info: Info } })
const Updated = define({ type: "pty.updated", schema: { info: Info } }) const Updated = define({ type: "pty.updated", schema: { info: Info } })
@ -32,21 +38,21 @@ const Deleted = define({ type: "pty.deleted", schema: { id: ID } })
export const Event = { Created, Updated, Exited, Deleted, Definitions: inventory(Created, Updated, Exited, Deleted) } export const Event = { Created, Updated, Exited, Deleted, Definitions: inventory(Created, Updated, Exited, Deleted) }
export const CreateInput = Schema.Struct({ export const CreateInput = Schema.Struct({
command: Schema.optional(Schema.String), command: optional(Schema.String),
args: Schema.optional(Schema.Array(Schema.String)), args: optional(Schema.Array(Schema.String)),
cwd: Schema.optional(Schema.String), cwd: optional(Schema.String),
title: Schema.optional(Schema.String), title: optional(Schema.String),
env: Schema.optional(Schema.Record(Schema.String, Schema.String)), env: optional(Schema.Record(Schema.String, Schema.String)),
}) })
export type CreateInput = typeof CreateInput.Type export interface CreateInput extends Schema.Schema.Type<typeof CreateInput> {}
export const UpdateInput = Schema.Struct({ export const UpdateInput = Schema.Struct({
title: Schema.optional(Schema.String), title: optional(Schema.String),
size: Schema.optional( size: optional(
Schema.Struct({ Schema.Struct({
rows: PositiveInt, rows: PositiveInt,
cols: PositiveInt, cols: PositiveInt,
}), }),
), ),
}) })
export type UpdateInput = typeof UpdateInput.Type export interface UpdateInput extends Schema.Schema.Type<typeof UpdateInput> {}

View file

@ -1,6 +1,7 @@
export * as Question from "./question" export * as Question from "./question"
import { Schema } from "effect" import { Schema } from "effect"
import { optional } from "./schema"
import { define, inventory } from "./event" import { define, inventory } from "./event"
import { ascending } from "./identifier" import { ascending } from "./identifier"
import { SessionID } from "./session-id" import { SessionID } from "./session-id"
@ -8,7 +9,13 @@ import { statics } from "./schema"
export const ID = Schema.String.check(Schema.isStartsWith("que")).pipe( export const ID = Schema.String.check(Schema.isStartsWith("que")).pipe(
Schema.brand("QuestionV2.ID"), Schema.brand("QuestionV2.ID"),
statics((schema) => ({ ascending: (id?: string) => schema.make(id ?? "que_" + ascending()) })), statics((schema) => {
const create = () => schema.make("que_" + ascending())
return {
create,
ascending: (id?: string) => (id === undefined ? create() : schema.make(id)),
}
}),
) )
export type ID = typeof ID.Type export type ID = typeof ID.Type
@ -16,39 +23,39 @@ export const Option = Schema.Struct({
label: Schema.String.annotate({ description: "Display text (1-5 words, concise)" }), label: Schema.String.annotate({ description: "Display text (1-5 words, concise)" }),
description: Schema.String.annotate({ description: "Explanation of choice" }), description: Schema.String.annotate({ description: "Explanation of choice" }),
}).annotate({ identifier: "QuestionV2.Option" }) }).annotate({ identifier: "QuestionV2.Option" })
export type Option = typeof Option.Type export interface Option extends Schema.Schema.Type<typeof Option> {}
const base = { const base = {
question: Schema.String.annotate({ description: "Complete question" }), question: Schema.String.annotate({ description: "Complete question" }),
header: Schema.String.annotate({ description: "Very short label (max 30 chars)" }), header: Schema.String.annotate({ description: "Very short label (max 30 chars)" }),
options: Schema.Array(Option).annotate({ description: "Available choices" }), options: Schema.Array(Option).annotate({ description: "Available choices" }),
multiple: Schema.Boolean.pipe(Schema.optional).annotate({ description: "Allow selecting multiple choices" }), multiple: Schema.Boolean.pipe(optional).annotate({ description: "Allow selecting multiple choices" }),
} }
export const Info = Schema.Struct({ export const Info = Schema.Struct({
...base, ...base,
custom: Schema.Boolean.pipe(Schema.optional).annotate({ custom: Schema.Boolean.pipe(optional).annotate({
description: "Allow typing a custom answer (default: true)", description: "Allow typing a custom answer (default: true)",
}), }),
}).annotate({ identifier: "QuestionV2.Info" }) }).annotate({ identifier: "QuestionV2.Info" })
export type Info = typeof Info.Type export interface Info extends Schema.Schema.Type<typeof Info> {}
export const Prompt = Schema.Struct(base).annotate({ identifier: "QuestionV2.Prompt" }) export const Prompt = Schema.Struct(base).annotate({ identifier: "QuestionV2.Prompt" })
export type Prompt = typeof Prompt.Type export interface Prompt extends Schema.Schema.Type<typeof Prompt> {}
export const Tool = Schema.Struct({ export const Tool = Schema.Struct({
messageID: Schema.String, messageID: Schema.String,
callID: Schema.String, callID: Schema.String,
}).annotate({ identifier: "QuestionV2.Tool" }) }).annotate({ identifier: "QuestionV2.Tool" })
export type Tool = typeof Tool.Type export interface Tool extends Schema.Schema.Type<typeof Tool> {}
export const Request = Schema.Struct({ export const Request = Schema.Struct({
id: ID, id: ID,
sessionID: SessionID, sessionID: SessionID,
questions: Schema.Array(Info).annotate({ description: "Questions to ask" }), questions: Schema.Array(Info).annotate({ description: "Questions to ask" }),
tool: Tool.pipe(Schema.optional), tool: Tool.pipe(optional),
}).annotate({ identifier: "QuestionV2.Request" }) }).annotate({ identifier: "QuestionV2.Request" })
export type Request = typeof Request.Type export interface Request extends Schema.Schema.Type<typeof Request> {}
export const Answer = Schema.Array(Schema.String).annotate({ identifier: "QuestionV2.Answer" }) export const Answer = Schema.Array(Schema.String).annotate({ identifier: "QuestionV2.Answer" })
export type Answer = typeof Answer.Type export type Answer = typeof Answer.Type
@ -58,7 +65,7 @@ export const Reply = Schema.Struct({
description: "User answers in order of questions (each answer is an array of selected labels)", description: "User answers in order of questions (each answer is an array of selected labels)",
}), }),
}).annotate({ identifier: "QuestionV2.Reply" }) }).annotate({ identifier: "QuestionV2.Reply" })
export type Reply = typeof Reply.Type export interface Reply extends Schema.Schema.Type<typeof Reply> {}
const Asked = define({ type: "question.v2.asked", schema: Request.fields }) const Asked = define({ type: "question.v2.asked", schema: Request.fields })
const Replied = define({ const Replied = define({

View file

@ -1,6 +1,7 @@
export * as Reference from "./reference" export * as Reference from "./reference"
import { Schema } from "effect" import { Schema } from "effect"
import { optional } from "./schema"
import { define, inventory } from "./event" import { define, inventory } from "./event"
import { AbsolutePath } from "./schema" import { AbsolutePath } from "./schema"
@ -11,26 +12,28 @@ export interface LocalSource extends Schema.Schema.Type<typeof LocalSource> {}
export const LocalSource = Schema.Struct({ export const LocalSource = Schema.Struct({
type: Schema.Literal("local"), type: Schema.Literal("local"),
path: AbsolutePath, path: AbsolutePath,
description: Schema.String.pipe(Schema.optional), description: Schema.String.pipe(optional),
hidden: Schema.Boolean.pipe(Schema.optional), hidden: Schema.Boolean.pipe(optional),
}).annotate({ identifier: "Reference.LocalSource" }) }).annotate({ identifier: "Reference.LocalSource" })
export interface GitSource extends Schema.Schema.Type<typeof GitSource> {} export interface GitSource extends Schema.Schema.Type<typeof GitSource> {}
export const GitSource = Schema.Struct({ export const GitSource = Schema.Struct({
type: Schema.Literal("git"), type: Schema.Literal("git"),
repository: Schema.String, repository: Schema.String,
branch: Schema.String.pipe(Schema.optional), branch: Schema.String.pipe(optional),
description: Schema.String.pipe(Schema.optional), description: Schema.String.pipe(optional),
hidden: Schema.Boolean.pipe(Schema.optional), hidden: Schema.Boolean.pipe(optional),
}).annotate({ identifier: "Reference.GitSource" }) }).annotate({ identifier: "Reference.GitSource" })
export const Source = Schema.Union([LocalSource, GitSource]).pipe(Schema.toTaggedUnion("type")) export const Source = Schema.Union([LocalSource, GitSource])
.pipe(Schema.toTaggedUnion("type"))
.annotate({ identifier: "Reference.Source" })
export type Source = typeof Source.Type export type Source = typeof Source.Type
export class Info extends Schema.Class<Info>("Reference.Info")({ export class Info extends Schema.Class<Info>("Reference.Info")({
name: Schema.String, name: Schema.String,
path: AbsolutePath, path: AbsolutePath,
description: Schema.String.pipe(Schema.optional), description: Schema.String.pipe(optional),
hidden: Schema.Boolean.pipe(Schema.optional), hidden: Schema.Boolean.pipe(optional),
source: Source, source: Source,
}) {} }) {}

View file

@ -1,6 +1,7 @@
export * as Revert from "./revert" export * as Revert from "./revert"
import { Schema } from "effect" import { Schema } from "effect"
import { optional } from "./schema"
import { NonNegativeInt, RelativePath } from "./schema" import { NonNegativeInt, RelativePath } from "./schema"
import { SessionMessage } from "./session-message" import { SessionMessage } from "./session-message"
@ -11,13 +12,13 @@ export const FileDiff = Schema.Struct({
deletions: NonNegativeInt, deletions: NonNegativeInt,
patch: Schema.String, patch: Schema.String,
}).annotate({ identifier: "File.Diff" }) }).annotate({ identifier: "File.Diff" })
export type FileDiff = typeof FileDiff.Type export interface FileDiff extends Schema.Schema.Type<typeof FileDiff> {}
export const State = Schema.Struct({ export const State = Schema.Struct({
messageID: SessionMessage.ID, messageID: SessionMessage.ID,
partID: Schema.String.pipe(Schema.optional), partID: Schema.String.pipe(optional),
snapshot: Schema.String.pipe(Schema.optional), snapshot: Schema.String.pipe(optional),
diff: Schema.String.pipe(Schema.optional), diff: Schema.String.pipe(optional),
files: Schema.Array(FileDiff).pipe(Schema.optional), files: Schema.Array(FileDiff).pipe(optional),
}) }).annotate({ identifier: "Revert.State" })
export type State = typeof State.Type export interface State extends Schema.Schema.Type<typeof State> {}

View file

@ -11,7 +11,7 @@ export type AbsolutePath = typeof AbsolutePath.Type
export const optional = <S extends Schema.Top>(schema: S) => export const optional = <S extends Schema.Top>(schema: S) =>
Schema.optionalKey(schema).pipe( Schema.optionalKey(schema).pipe(
Schema.decodeTo(Schema.optional(schema), { Schema.decodeTo(Schema.optional(Schema.toType(schema)), {
decode: SchemaGetter.passthrough({ strict: false }), decode: SchemaGetter.passthrough({ strict: false }),
encode: SchemaGetter.transformOptional(Option.filter((value) => value !== undefined)), encode: SchemaGetter.transformOptional(Option.filter((value) => value !== undefined)),
}), }),

View file

@ -1,6 +1,7 @@
export * as SessionEvent from "./session-event" export * as SessionEvent from "./session-event"
import { Schema } from "effect" import { Schema } from "effect"
import { optional } from "./schema"
import { Event } from "./event" import { Event } from "./event"
import { ProviderMetadata, ToolContent } from "./llm" import { ProviderMetadata, ToolContent } from "./llm"
import { Delivery } from "./session-delivery" import { Delivery } from "./session-delivery"
@ -21,7 +22,7 @@ export const Source = Schema.Struct({
}).annotate({ }).annotate({
identifier: "session.next.event.source", identifier: "session.next.event.source",
}) })
export type Source = typeof Source.Type export interface Source extends Schema.Schema.Type<typeof Source> {}
const Base = { const Base = {
timestamp: DateTimeUtcFromMillis, timestamp: DateTimeUtcFromMillis,
@ -78,7 +79,7 @@ export const Moved = Event.define({
schema: { schema: {
...Base, ...Base,
location: Location.Ref, location: Location.Ref,
subdirectory: RelativePath.pipe(Schema.optional), subdirectory: RelativePath.pipe(optional),
}, },
}) })
export type Moved = typeof Moved.Type export type Moved = typeof Moved.Type
@ -153,7 +154,7 @@ export namespace Step {
assistantMessageID: SessionMessage.ID, assistantMessageID: SessionMessage.ID,
agent: Schema.String, agent: Schema.String,
model: Model.Ref, model: Model.Ref,
snapshot: Schema.String.pipe(Schema.optional), snapshot: Schema.String.pipe(optional),
}, },
}) })
export type Started = typeof Started.Type export type Started = typeof Started.Type
@ -175,8 +176,8 @@ export namespace Step {
write: Schema.Finite, write: Schema.Finite,
}), }),
}), }),
snapshot: Schema.String.pipe(Schema.optional), snapshot: Schema.String.pipe(optional),
files: Schema.Array(RelativePath).pipe(Schema.optional), files: Schema.Array(RelativePath).pipe(optional),
}, },
}) })
export type Ended = typeof Ended.Type export type Ended = typeof Ended.Type
@ -238,7 +239,7 @@ export namespace Reasoning {
...Base, ...Base,
assistantMessageID: SessionMessage.ID, assistantMessageID: SessionMessage.ID,
reasoningID: Schema.String, reasoningID: Schema.String,
providerMetadata: ProviderMetadata.pipe(Schema.optional), providerMetadata: ProviderMetadata.pipe(optional),
}, },
}) })
export type Started = typeof Started.Type export type Started = typeof Started.Type
@ -263,7 +264,7 @@ export namespace Reasoning {
assistantMessageID: SessionMessage.ID, assistantMessageID: SessionMessage.ID,
reasoningID: Schema.String, reasoningID: Schema.String,
text: Schema.String, text: Schema.String,
providerMetadata: ProviderMetadata.pipe(Schema.optional), providerMetadata: ProviderMetadata.pipe(optional),
}, },
}) })
export type Ended = typeof Ended.Type export type Ended = typeof Ended.Type
@ -317,7 +318,7 @@ export namespace Tool {
input: Schema.Record(Schema.String, Schema.Unknown), input: Schema.Record(Schema.String, Schema.Unknown),
provider: Schema.Struct({ provider: Schema.Struct({
executed: Schema.Boolean, executed: Schema.Boolean,
metadata: ProviderMetadata.pipe(Schema.optional), metadata: ProviderMetadata.pipe(optional),
}), }),
}, },
}) })
@ -332,7 +333,7 @@ export namespace Tool {
...options, ...options,
schema: { schema: {
...ToolBase, ...ToolBase,
structured: Schema.Record(Schema.String, Schema.Any), structured: Schema.Record(Schema.String, Schema.Unknown),
content: Schema.Array(ToolContent), content: Schema.Array(ToolContent),
}, },
}) })
@ -343,13 +344,13 @@ export namespace Tool {
...options, ...options,
schema: { schema: {
...ToolBase, ...ToolBase,
structured: Schema.Record(Schema.String, Schema.Any), structured: Schema.Record(Schema.String, Schema.Unknown),
content: Schema.Array(ToolContent), content: Schema.Array(ToolContent),
outputPaths: Schema.Array(Schema.String).pipe(Schema.optional), outputPaths: Schema.Array(Schema.String).pipe(optional),
result: Schema.Unknown.pipe(Schema.optional), result: Schema.Unknown.pipe(optional),
provider: Schema.Struct({ provider: Schema.Struct({
executed: Schema.Boolean, executed: Schema.Boolean,
metadata: ProviderMetadata.pipe(Schema.optional), metadata: ProviderMetadata.pipe(optional),
}), }),
}, },
}) })
@ -361,10 +362,10 @@ export namespace Tool {
schema: { schema: {
...ToolBase, ...ToolBase,
error: UnknownError, error: UnknownError,
result: Schema.Unknown.pipe(Schema.optional), result: Schema.Unknown.pipe(optional),
provider: Schema.Struct({ provider: Schema.Struct({
executed: Schema.Boolean, executed: Schema.Boolean,
metadata: ProviderMetadata.pipe(Schema.optional), metadata: ProviderMetadata.pipe(optional),
}), }),
}, },
}) })
@ -373,15 +374,15 @@ export namespace Tool {
export const RetryError = Schema.Struct({ export const RetryError = Schema.Struct({
message: Schema.String, message: Schema.String,
statusCode: Schema.Finite.pipe(Schema.optional), statusCode: Schema.Finite.pipe(optional),
isRetryable: Schema.Boolean, isRetryable: Schema.Boolean,
responseHeaders: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional), responseHeaders: Schema.Record(Schema.String, Schema.String).pipe(optional),
responseBody: Schema.String.pipe(Schema.optional), responseBody: Schema.String.pipe(optional),
metadata: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional), metadata: Schema.Record(Schema.String, Schema.String).pipe(optional),
}).annotate({ }).annotate({
identifier: "session.next.retry_error", identifier: "session.next.retry_error",
}) })
export type RetryError = typeof RetryError.Type export interface RetryError extends Schema.Schema.Type<typeof RetryError> {}
export const Retried = Event.define({ export const Retried = Event.define({
type: "session.next.retried", type: "session.next.retried",

View file

@ -1,6 +1,7 @@
export * as SessionInput from "./session-input" export * as SessionInput from "./session-input"
import { Schema } from "effect" import { Schema } from "effect"
import { optional } from "./schema"
import { Prompt } from "./prompt" import { Prompt } from "./prompt"
import { DateTimeUtcFromMillis, NonNegativeInt } from "./schema" import { DateTimeUtcFromMillis, NonNegativeInt } from "./schema"
import { SessionDelivery } from "./session-delivery" import { SessionDelivery } from "./session-delivery"
@ -18,5 +19,5 @@ export const Admitted = Schema.Struct({
prompt: Prompt, prompt: Prompt,
delivery: Delivery, delivery: Delivery,
timeCreated: DateTimeUtcFromMillis, timeCreated: DateTimeUtcFromMillis,
promotedSeq: NonNegativeInt.pipe(Schema.optional), promotedSeq: NonNegativeInt.pipe(optional),
}).annotate({ identifier: "SessionInput.Admitted" }) }).annotate({ identifier: "SessionInput.Admitted" })

View file

@ -1,6 +1,7 @@
export * as SessionMessage from "./session-message" export * as SessionMessage from "./session-message"
import { Schema } from "effect" import { Schema } from "effect"
import { optional } from "./schema"
import { ProviderMetadata, ToolContent } from "./llm" import { ProviderMetadata, ToolContent } from "./llm"
import { Model } from "./model" import { Model } from "./model"
import { FileAttachment, Prompt } from "./prompt" import { FileAttachment, Prompt } from "./prompt"
@ -22,7 +23,7 @@ export const UnknownError = Schema.Struct({
const Base = { const Base = {
id: ID, id: ID,
metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional), metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(optional),
time: Schema.Struct({ created: DateTimeUtcFromMillis }), time: Schema.Struct({ created: DateTimeUtcFromMillis }),
} }
@ -73,7 +74,7 @@ export const Shell = Schema.Struct({
output: Schema.String, output: Schema.String,
time: Schema.Struct({ time: Schema.Struct({
created: DateTimeUtcFromMillis, created: DateTimeUtcFromMillis,
completed: DateTimeUtcFromMillis.pipe(Schema.optional), completed: DateTimeUtcFromMillis.pipe(optional),
}), }),
}).annotate({ identifier: "Session.Message.Shell" }) }).annotate({ identifier: "Session.Message.Shell" })
@ -87,7 +88,7 @@ export interface ToolStateRunning extends Schema.Schema.Type<typeof ToolStateRun
export const ToolStateRunning = Schema.Struct({ export const ToolStateRunning = Schema.Struct({
status: Schema.Literal("running"), status: Schema.Literal("running"),
input: Schema.Record(Schema.String, Schema.Unknown), input: Schema.Record(Schema.String, Schema.Unknown),
structured: Schema.Record(Schema.String, Schema.Any), structured: Schema.Record(Schema.String, Schema.Unknown),
content: ToolContent.pipe(Schema.Array), content: ToolContent.pipe(Schema.Array),
}).annotate({ identifier: "Session.Message.ToolState.Running" }) }).annotate({ identifier: "Session.Message.ToolState.Running" })
@ -95,11 +96,11 @@ export interface ToolStateCompleted extends Schema.Schema.Type<typeof ToolStateC
export const ToolStateCompleted = Schema.Struct({ export const ToolStateCompleted = Schema.Struct({
status: Schema.Literal("completed"), status: Schema.Literal("completed"),
input: Schema.Record(Schema.String, Schema.Unknown), input: Schema.Record(Schema.String, Schema.Unknown),
attachments: FileAttachment.pipe(Schema.Array, Schema.optional), attachments: FileAttachment.pipe(Schema.Array, optional),
content: ToolContent.pipe(Schema.Array), content: ToolContent.pipe(Schema.Array),
outputPaths: Schema.Array(Schema.String).pipe(Schema.optional), outputPaths: Schema.Array(Schema.String).pipe(optional),
structured: Schema.Record(Schema.String, Schema.Any), structured: Schema.Record(Schema.String, Schema.Unknown),
result: Schema.Unknown.pipe(Schema.optional), result: Schema.Unknown.pipe(optional),
}).annotate({ identifier: "Session.Message.ToolState.Completed" }) }).annotate({ identifier: "Session.Message.ToolState.Completed" })
export interface ToolStateError extends Schema.Schema.Type<typeof ToolStateError> {} export interface ToolStateError extends Schema.Schema.Type<typeof ToolStateError> {}
@ -107,9 +108,9 @@ export const ToolStateError = Schema.Struct({
status: Schema.Literal("error"), status: Schema.Literal("error"),
input: Schema.Record(Schema.String, Schema.Unknown), input: Schema.Record(Schema.String, Schema.Unknown),
content: ToolContent.pipe(Schema.Array), content: ToolContent.pipe(Schema.Array),
structured: Schema.Record(Schema.String, Schema.Any), structured: Schema.Record(Schema.String, Schema.Unknown),
error: UnknownError, error: UnknownError,
result: Schema.Unknown.pipe(Schema.optional), result: Schema.Unknown.pipe(optional),
}).annotate({ identifier: "Session.Message.ToolState.Error" }) }).annotate({ identifier: "Session.Message.ToolState.Error" })
export const ToolState = Schema.Union([ToolStatePending, ToolStateRunning, ToolStateCompleted, ToolStateError]).pipe( export const ToolState = Schema.Union([ToolStatePending, ToolStateRunning, ToolStateCompleted, ToolStateError]).pipe(
@ -124,15 +125,15 @@ export const AssistantTool = Schema.Struct({
name: Schema.String, name: Schema.String,
provider: Schema.Struct({ provider: Schema.Struct({
executed: Schema.Boolean, executed: Schema.Boolean,
metadata: ProviderMetadata.pipe(Schema.optional), metadata: ProviderMetadata.pipe(optional),
resultMetadata: ProviderMetadata.pipe(Schema.optional), resultMetadata: ProviderMetadata.pipe(optional),
}).pipe(Schema.optional), }).pipe(optional),
state: ToolState, state: ToolState,
time: Schema.Struct({ time: Schema.Struct({
created: DateTimeUtcFromMillis, created: DateTimeUtcFromMillis,
ran: DateTimeUtcFromMillis.pipe(Schema.optional), ran: DateTimeUtcFromMillis.pipe(optional),
completed: DateTimeUtcFromMillis.pipe(Schema.optional), completed: DateTimeUtcFromMillis.pipe(optional),
pruned: DateTimeUtcFromMillis.pipe(Schema.optional), pruned: DateTimeUtcFromMillis.pipe(optional),
}), }),
}).annotate({ identifier: "Session.Message.Assistant.Tool" }) }).annotate({ identifier: "Session.Message.Assistant.Tool" })
@ -148,7 +149,7 @@ export const AssistantReasoning = Schema.Struct({
type: Schema.Literal("reasoning"), type: Schema.Literal("reasoning"),
id: Schema.String, id: Schema.String,
text: Schema.String, text: Schema.String,
providerMetadata: ProviderMetadata.pipe(Schema.optional), providerMetadata: ProviderMetadata.pipe(optional),
}).annotate({ identifier: "Session.Message.Assistant.Reasoning" }) }).annotate({ identifier: "Session.Message.Assistant.Reasoning" })
export const AssistantContent = Schema.Union([AssistantText, AssistantReasoning, AssistantTool]).pipe( export const AssistantContent = Schema.Union([AssistantText, AssistantReasoning, AssistantTool]).pipe(
@ -164,22 +165,22 @@ export const Assistant = Schema.Struct({
model: Model.Ref, model: Model.Ref,
content: AssistantContent.pipe(Schema.Array), content: AssistantContent.pipe(Schema.Array),
snapshot: Schema.Struct({ snapshot: Schema.Struct({
start: Schema.String.pipe(Schema.optional), start: Schema.String.pipe(optional),
end: Schema.String.pipe(Schema.optional), end: Schema.String.pipe(optional),
files: Schema.Array(RelativePath).pipe(Schema.optional), files: Schema.Array(RelativePath).pipe(optional),
}).pipe(Schema.optional), }).pipe(optional),
finish: Schema.String.pipe(Schema.optional), finish: Schema.String.pipe(optional),
cost: Schema.Finite.pipe(Schema.optional), cost: Schema.Finite.pipe(optional),
tokens: Schema.Struct({ tokens: Schema.Struct({
input: Schema.Finite, input: Schema.Finite,
output: Schema.Finite, output: Schema.Finite,
reasoning: Schema.Finite, reasoning: Schema.Finite,
cache: Schema.Struct({ read: Schema.Finite, write: Schema.Finite }), cache: Schema.Struct({ read: Schema.Finite, write: Schema.Finite }),
}).pipe(Schema.optional), }).pipe(optional),
error: UnknownError.pipe(Schema.optional), error: UnknownError.pipe(optional),
time: Schema.Struct({ time: Schema.Struct({
created: DateTimeUtcFromMillis, created: DateTimeUtcFromMillis,
completed: DateTimeUtcFromMillis.pipe(Schema.optional), completed: DateTimeUtcFromMillis.pipe(optional),
}), }),
}).annotate({ identifier: "Session.Message.Assistant" }) }).annotate({ identifier: "Session.Message.Assistant" })

View file

@ -1,6 +1,7 @@
export * as SessionStatusEvent from "./session-status-event" export * as SessionStatusEvent from "./session-status-event"
import { Schema } from "effect" import { Schema } from "effect"
import { optional } from "./schema"
import { Event } from "./event" import { Event } from "./event"
import { NonNegativeInt } from "./schema" import { NonNegativeInt } from "./schema"
import { SessionID } from "./session-id" import { SessionID } from "./session-id"
@ -13,14 +14,14 @@ export const Info = Schema.Union([
type: Schema.Literal("retry"), type: Schema.Literal("retry"),
attempt: NonNegativeInt, attempt: NonNegativeInt,
message: Schema.String, message: Schema.String,
action: Schema.optional( action: optional(
Schema.Struct({ Schema.Struct({
reason: Schema.String, reason: Schema.String,
provider: Schema.String, provider: Schema.String,
title: Schema.String, title: Schema.String,
message: Schema.String, message: Schema.String,
label: Schema.String, label: Schema.String,
link: Schema.optional(Schema.String), link: optional(Schema.String),
}), }),
), ),
next: NonNegativeInt, next: NonNegativeInt,

View file

@ -9,9 +9,11 @@ export const Info = Schema.Struct({
status: Schema.String.annotate({ status: Schema.String.annotate({
description: "Current status of the task: pending, in_progress, completed, cancelled", description: "Current status of the task: pending, in_progress, completed, cancelled",
}), }),
priority: Schema.String.annotate({ description: "Priority level of the task: high, medium, low" }), priority: Schema.String.annotate({
description: "Priority level of the task: high, medium, low",
}),
}).annotate({ identifier: "Todo" }) }).annotate({ identifier: "Todo" })
export type Info = typeof Info.Type export interface Info extends Schema.Schema.Type<typeof Info> {}
const Updated = define({ const Updated = define({
type: "todo.updated", type: "todo.updated",

View file

@ -20,8 +20,8 @@ export const Info = Schema.Struct({
id: ID, id: ID,
parentID: ID.pipe(optional), parentID: ID.pipe(optional),
projectID: Project.ID, projectID: Project.ID,
agent: Agent.ID.pipe(Schema.optional), agent: Agent.ID.pipe(optional),
model: Model.Ref.pipe(Schema.optional), model: Model.Ref.pipe(optional),
cost: Schema.Finite, cost: Schema.Finite,
tokens: Schema.Struct({ tokens: Schema.Struct({
input: Schema.Finite, input: Schema.Finite,
@ -35,17 +35,17 @@ export const Info = Schema.Struct({
time: Schema.Struct({ time: Schema.Struct({
created: DateTimeUtcFromMillis, created: DateTimeUtcFromMillis,
updated: DateTimeUtcFromMillis, updated: DateTimeUtcFromMillis,
archived: DateTimeUtcFromMillis.pipe(Schema.optional), archived: DateTimeUtcFromMillis.pipe(optional),
}), }),
title: Schema.String, title: Schema.String,
location: Location.Ref, location: Location.Ref,
subpath: RelativePath.pipe(Schema.optional), subpath: RelativePath.pipe(optional),
revert: Revert.State.pipe(Schema.optional), revert: Revert.State.pipe(optional),
}).annotate({ identifier: "SessionV2.Info" }) }).annotate({ identifier: "SessionV2.Info" })
export const ListAnchor = Schema.Struct({ export const ListAnchor = Schema.Struct({
id: ID, id: ID,
time: Schema.Finite, time: Schema.Finite,
direction: Schema.Literals(["previous", "next"]), direction: Schema.Literals(["previous", "next"]),
}) }).annotate({ identifier: "Session.ListAnchor" })
export type ListAnchor = typeof ListAnchor.Type export interface ListAnchor extends Schema.Schema.Type<typeof ListAnchor> {}

View file

@ -1,6 +1,7 @@
export * as Skill from "./skill" export * as Skill from "./skill"
import { Schema } from "effect" import { Schema } from "effect"
import { optional } from "./schema"
import { AbsolutePath } from "./schema" import { AbsolutePath } from "./schema"
export interface DirectorySource extends Schema.Schema.Type<typeof DirectorySource> {} export interface DirectorySource extends Schema.Schema.Type<typeof DirectorySource> {}
@ -18,8 +19,8 @@ export const UrlSource = Schema.Struct({
export interface Info extends Schema.Schema.Type<typeof Info> {} export interface Info extends Schema.Schema.Type<typeof Info> {}
export const Info = Schema.Struct({ export const Info = Schema.Struct({
name: Schema.String, name: Schema.String,
description: Schema.String.pipe(Schema.optional), description: Schema.String.pipe(optional),
slash: Schema.Boolean.pipe(Schema.optional), slash: Schema.Boolean.pipe(optional),
location: AbsolutePath, location: AbsolutePath,
content: Schema.String, content: Schema.String,
}).annotate({ identifier: "SkillV2.Info" }) }).annotate({ identifier: "SkillV2.Info" })

View file

@ -1,6 +1,7 @@
export * as TuiEvent from "./tui-event" export * as TuiEvent from "./tui-event"
import { Effect, Schema } from "effect" import { Effect, Schema } from "effect"
import { optional } from "./schema"
import { Event } from "./event" import { Event } from "./event"
import { PositiveInt } from "./schema" import { PositiveInt } from "./schema"
import { SessionID } from "./session-id" import { SessionID } from "./session-id"
@ -39,7 +40,7 @@ export const CommandExecute = Event.define({
export const ToastShow = Event.define({ export const ToastShow = Event.define({
type: "tui.toast.show", type: "tui.toast.show",
schema: { schema: {
title: Schema.optional(Schema.String), title: optional(Schema.String),
message: Schema.String, message: Schema.String,
variant: Schema.Literals(["info", "success", "warning", "error"]), variant: Schema.Literals(["info", "success", "warning", "error"]),
duration: PositiveInt.pipe(Schema.withDecodingDefault(Effect.succeed(DEFAULT_TOAST_DURATION))).annotate({ duration: PositiveInt.pipe(Schema.withDecodingDefault(Effect.succeed(DEFAULT_TOAST_DURATION))).annotate({

View file

@ -1,12 +1,13 @@
export * as VcsEvent from "./vcs-event" export * as VcsEvent from "./vcs-event"
import { Schema } from "effect" import { Schema } from "effect"
import { optional } from "./schema"
import { Event } from "./event" import { Event } from "./event"
export const BranchUpdated = Event.define({ export const BranchUpdated = Event.define({
type: "vcs.branch.updated", type: "vcs.branch.updated",
schema: { schema: {
branch: Schema.optional(Schema.String), branch: optional(Schema.String),
}, },
}) })

View file

@ -7,8 +7,8 @@ import { WorkspaceID } from "./workspace-id"
export const ConnectionStatus = Schema.Struct({ export const ConnectionStatus = Schema.Struct({
workspaceID: WorkspaceID, workspaceID: WorkspaceID,
status: Schema.Literals(["connected", "connecting", "disconnected", "error"]), status: Schema.Literals(["connected", "connecting", "disconnected", "error"]),
}) }).annotate({ identifier: "WorkspaceEvent.ConnectionStatus" })
export type ConnectionStatus = typeof ConnectionStatus.Type export interface ConnectionStatus extends Schema.Schema.Type<typeof ConnectionStatus> {}
export const Ready = Event.define({ export const Ready = Event.define({
type: "workspace.ready", type: "workspace.ready",

View file

@ -1,13 +1,14 @@
export * as WorktreeEvent from "./worktree-event" export * as WorktreeEvent from "./worktree-event"
import { Schema } from "effect" import { Schema } from "effect"
import { optional } from "./schema"
import { Event } from "./event" import { Event } from "./event"
export const Ready = Event.define({ export const Ready = Event.define({
type: "worktree.ready", type: "worktree.ready",
schema: { schema: {
name: Schema.String, name: Schema.String,
branch: Schema.optional(Schema.String), branch: optional(Schema.String),
}, },
}) })

View file

@ -0,0 +1,67 @@
import { describe, expect, test } from "bun:test"
import { Schema } from "effect"
import { Agent } from "../src/agent"
import { FileSystem } from "../src/filesystem"
import { Model } from "../src/model"
import { Project } from "../src/project"
import { Pty } from "../src/pty"
import { Question } from "../src/question"
import { Session } from "../src/session"
import { SessionEvent } from "../src/session-event"
import { SessionTodo } from "../src/session-todo"
import { optional } from "../src/schema"
describe("contract hygiene", () => {
test("optional properties preserve transformations and omit undefined while encoding", () => {
const Value = Schema.Struct({ value: optional(Schema.FiniteFromString) })
expect(Schema.decodeUnknownSync(Value)({ value: "1" })).toEqual({ value: 1 })
expect(Schema.encodeSync(Value)({ value: 1 })).toEqual({ value: "1" })
expect(Schema.encodeSync(Value)({ value: undefined })).toEqual({})
})
test("todo status and priority preserve arbitrary strings", () => {
const decode = Schema.decodeUnknownSync(SessionTodo.Info)
expect(decode({ content: "ship", status: "waiting", priority: "urgent" })).toEqual({
content: "ship",
status: "waiting",
priority: "urgent",
})
})
test("current ID constructors expose create", () => {
expect(Question.ID.create()).toStartWith("que_")
expect(Pty.ID.create()).toStartWith("pty_")
})
test("reusable public identifiers are stable and unique", () => {
const identifiers = [
Agent.Color,
FileSystem.Submatch,
Model.Ref,
Model.Capabilities,
Model.Cost,
Model.Api,
Project.Icon,
Project.Commands,
Project.Time,
Project.Info,
Pty.Info,
Session.ListAnchor,
].map((schema) => schema.ast.annotations?.identifier)
expect(identifiers.every((identifier) => typeof identifier === "string")).toBe(true)
expect(new Set(identifiers).size).toBe(identifiers.length)
})
test("current source avoids Any and mutable contract wrappers", async () => {
const files = [...new Bun.Glob("*.ts").scanSync(new URL("../src", import.meta.url).pathname)].filter(
(file) => !file.endsWith("-v1.ts"),
)
const source = await Promise.all(
files.map((file) => Bun.file(new URL(`../src/${file}`, import.meta.url)).text()),
).then((values) => values.join("\n"))
expect(source).not.toContain("Schema.Any")
expect(source).not.toContain("Schema.mutable")
})
})

View file

@ -111,6 +111,7 @@ import type {
McpRemoteConfig, McpRemoteConfig,
McpStatusErrors, McpStatusErrors,
McpStatusResponses, McpStatusResponses,
ModelRef,
MoveSessionDestination, MoveSessionDestination,
OutputFormat, OutputFormat,
Part as Part2, Part as Part2,
@ -129,10 +130,12 @@ import type {
PermissionRuleset, PermissionRuleset,
PermissionV2Reply, PermissionV2Reply,
PermissionV2Source, PermissionV2Source,
ProjectCommands,
ProjectCurrentErrors, ProjectCurrentErrors,
ProjectCurrentResponses, ProjectCurrentResponses,
ProjectDirectoriesErrors, ProjectDirectoriesErrors,
ProjectDirectoriesResponses, ProjectDirectoriesResponses,
ProjectIcon,
ProjectInitGitErrors, ProjectInitGitErrors,
ProjectInitGitResponses, ProjectInitGitResponses,
ProjectListErrors, ProjectListErrors,
@ -2616,17 +2619,8 @@ export class Project extends HeyApiClient {
directory?: string directory?: string
workspace?: string workspace?: string
name?: string name?: string
icon?: { icon?: ProjectIcon
url?: string commands?: ProjectCommands
override?: string
color?: string
}
commands?: {
/**
* Startup script to run when creating a new workspace (worktree)
*/
start?: string
}
}, },
options?: Options<never, ThrowOnError>, options?: Options<never, ThrowOnError>,
) { ) {
@ -5471,11 +5465,7 @@ export class Session3 extends HeyApiClient {
parameters?: { parameters?: {
id?: string id?: string
agent?: string agent?: string
model?: { model?: ModelRef
id: string
providerID: string
variant?: string
}
location?: LocationRef location?: LocationRef
}, },
options?: Options<never, ThrowOnError>, options?: Options<never, ThrowOnError>,
@ -5571,11 +5561,7 @@ export class Session3 extends HeyApiClient {
public switchModel<ThrowOnError extends boolean = false>( public switchModel<ThrowOnError extends boolean = false>(
parameters: { parameters: {
sessionID: string sessionID: string
model?: { model?: ModelRef
id: string
providerID: string
variant?: string
}
}, },
options?: Options<never, ThrowOnError>, options?: Options<never, ThrowOnError>,
) { ) {

File diff suppressed because it is too large Load diff