refactor(core): remove schema forwarding facades (#33577)

This commit is contained in:
Kit Langton 2026-06-24 04:52:58 +02:00 committed by GitHub
parent 6298db7a77
commit 57895586c2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 31 additions and 70 deletions

View File

@ -3,10 +3,10 @@ export * as Config from "./config"
import path from "path"
import { type ParseError, parse } from "jsonc-parser"
import { Context, Effect, Layer, Option, Schema } from "effect"
import { Permission } from "@opencode-ai/schema/permission"
import { FSUtil } from "./fs-util"
import { Global } from "./global"
import { Location } from "./location"
import { PermissionSchema } from "./permission/schema"
import { Policy } from "./policy"
import { AbsolutePath } from "./schema"
import { ConfigAgent } from "./config/agent"
@ -56,7 +56,7 @@ export class Info extends Schema.Class<Info>("Config.Info")({
username: Schema.String.pipe(Schema.optional).annotate({
description: "Username displayed in conversations and used for telemetry identity",
}),
permissions: PermissionSchema.Ruleset.pipe(Schema.optional).annotate({
permissions: Permission.Ruleset.pipe(Schema.optional).annotate({
description: "Ordered tool permission rules applied to agent tool use",
}),
agents: Schema.Record(Schema.String, ConfigAgent.Info).pipe(Schema.optional).annotate({

View File

@ -1,7 +1,7 @@
export * as ConfigAgent from "./agent"
import { Schema } from "effect"
import { PermissionSchema } from "../permission/schema"
import { Permission } from "@opencode-ai/schema/permission"
import { ConfigProvider } from "./provider"
import { PositiveInt } from "../schema"
@ -21,5 +21,5 @@ export class Info extends Schema.Class<Info>("ConfigV2.Agent")({
color: Color.pipe(Schema.optional),
steps: PositiveInt.pipe(Schema.optional),
disabled: Schema.Boolean.pipe(Schema.optional),
permissions: PermissionSchema.Ruleset.pipe(Schema.optional),
permissions: Permission.Ruleset.pipe(Schema.optional),
}) {}

View File

@ -3,8 +3,8 @@ export * as Credential from "./credential"
import { asc, eq } from "drizzle-orm"
import { Context, Effect, Layer, Schema } from "effect"
import { Credential } from "@opencode-ai/schema/credential"
import { Integration } from "@opencode-ai/schema/integration"
import { Database } from "./database/database"
import { IntegrationSchema } from "./integration/schema"
import { CredentialTable } from "./credential/sql"
export const ID = Credential.ID
@ -21,7 +21,7 @@ export type Value = Credential.Value
export class Info extends Schema.Class<Info>("Credential.Info")({
id: ID,
integrationID: IntegrationSchema.ID,
integrationID: Integration.ID,
label: Schema.String,
value: Value,
}) {}
@ -30,12 +30,12 @@ export interface Interface {
/** Returns every stored credential. */
readonly all: () => Effect.Effect<Info[]>
/** Returns stored credentials belonging to one integration. */
readonly list: (integrationID: IntegrationSchema.ID) => Effect.Effect<Info[]>
readonly list: (integrationID: Integration.ID) => Effect.Effect<Info[]>
/** Returns one stored credential by ID. */
readonly get: (id: ID) => Effect.Effect<Info | undefined>
/** Replaces any credential for an integration and returns the new record. */
readonly create: (input: {
readonly integrationID: IntegrationSchema.ID
readonly integrationID: Integration.ID
readonly value: Value
readonly label?: string
}) => Effect.Effect<Info>

View File

@ -1,11 +1,10 @@
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"
import { Timestamps } from "../database/schema.sql"
import type { IntegrationSchema } from "../integration/schema"
import type { Credential } from "../credential"
export const CredentialTable = sqliteTable("credential", {
id: text().$type<Credential.ID>().primaryKey(),
integration_id: text().$type<IntegrationSchema.ID>(),
integration_id: text().$type<Credential.Info["integrationID"]>(),
label: text().notNull(),
value: text({ mode: "json" }).$type<Credential.Value>().notNull(),
connector_id: text(),

View File

@ -7,8 +7,8 @@ import { FSUtil } from "./fs-util"
import { Location } from "./location"
import { PositiveInt, RelativePath } from "./schema"
import { FileSystemSearch } from "./filesystem/search"
import { Entry, Match } from "./filesystem/schema"
export { Entry, Match, Submatch } from "./filesystem/schema"
import { Entry, Match } from "@opencode-ai/schema/filesystem"
export { Entry, Match, Submatch } from "@opencode-ai/schema/filesystem"
export const ReadInput = Schema.Struct({
path: RelativePath,

View File

@ -1,10 +0,0 @@
import { FileSystem } from "@opencode-ai/schema/filesystem"
export const Entry = FileSystem.Entry
export type Entry = FileSystem.Entry
export const Submatch = FileSystem.Submatch
export type Submatch = FileSystem.Submatch
export const Match = FileSystem.Match
export type Match = FileSystem.Match

View File

@ -16,18 +16,17 @@ import {
} from "effect"
import { Integration } from "@opencode-ai/schema/integration"
import { Credential } from "./credential"
import { IntegrationSchema } from "./integration/schema"
import { withStatics } from "./schema"
import { State } from "./state"
import { Identifier } from "./util/identifier"
import { EventV2 } from "./event"
import { IntegrationConnection } from "./integration/connection"
export const ID = IntegrationSchema.ID
export type ID = IntegrationSchema.ID
export const ID = Integration.ID
export type ID = Integration.ID
export const MethodID = IntegrationSchema.MethodID
export type MethodID = IntegrationSchema.MethodID
export const MethodID = Integration.MethodID
export type MethodID = Integration.MethodID
export const AttemptID = Schema.String.pipe(
Schema.brand("Integration.AttemptID"),
@ -103,10 +102,6 @@ export interface EnvImplementation {
export type Implementation = OAuthImplementation | KeyImplementation | EnvImplementation
function isOAuthImplementation(implementation: Implementation): implementation is OAuthImplementation {
return implementation.method.type === "oauth"
}
export class Attempt extends Schema.Class<Attempt>("Integration.Attempt")({
attemptID: AttemptID,
url: Schema.String,

View File

@ -1,9 +0,0 @@
export * as IntegrationSchema from "./schema"
import { Integration } from "@opencode-ai/schema/integration"
export const ID = Integration.ID
export type ID = Integration.ID
export const MethodID = Integration.MethodID
export type MethodID = Integration.MethodID

View File

@ -1,6 +1,7 @@
export * as PermissionV2 from "./permission"
import { Context, Deferred, Effect as EffectRuntime, Layer, Schema } from "effect"
import { Permission } from "@opencode-ai/schema/permission"
import { EventV2 } from "./event"
import { Location } from "./location"
import { AgentV2 } from "./agent"
@ -9,14 +10,10 @@ import { SessionStore } from "./session/store"
import { withStatics } from "./schema"
import { Identifier } from "./util/identifier"
import { Wildcard } from "./util/wildcard"
import { PermissionSchema } from "./permission/schema"
import { PermissionSaved } from "./permission/saved"
export { Effect, Rule, Ruleset } from "./permission/schema"
type Effect = PermissionSchema.Effect
type Rule = PermissionSchema.Rule
type Ruleset = PermissionSchema.Ruleset
const missingAgentPermissions: Ruleset = [{ action: "*", resource: "*", effect: "deny" }]
export { Effect, Rule, Ruleset } from "@opencode-ai/schema/permission"
const missingAgentPermissions: Permission.Ruleset = [{ action: "*", resource: "*", effect: "deny" }]
export const ID = Schema.String.check(Schema.isStartsWith("per")).pipe(
Schema.brand("PermissionV2.ID"),
@ -67,7 +64,7 @@ export type ReplyInput = typeof ReplyInput.Type
export const AskResult = Schema.Struct({
id: ID,
effect: PermissionSchema.Effect,
effect: Permission.Effect,
}).annotate({ identifier: "PermissionV2.AskResult" })
export type AskResult = typeof AskResult.Type
@ -90,7 +87,7 @@ export class CorrectedError extends Schema.TaggedErrorClass<CorrectedError>()("P
}) {}
export class DeniedError extends Schema.TaggedErrorClass<DeniedError>()("PermissionV2.DeniedError", {
rules: PermissionSchema.Ruleset,
rules: Permission.Ruleset,
}) {}
export class NotFoundError extends Schema.TaggedErrorClass<NotFoundError>()("PermissionV2.NotFoundError", {
@ -99,7 +96,7 @@ export class NotFoundError extends Schema.TaggedErrorClass<NotFoundError>()("Per
export type Error = DeniedError | RejectedError | CorrectedError
export function evaluate(action: string, resource: string, ...rulesets: Ruleset[]): Rule {
export function evaluate(action: string, resource: string, ...rulesets: Permission.Ruleset[]): Permission.Rule {
return (
rulesets
.flat()
@ -111,7 +108,7 @@ export function evaluate(action: string, resource: string, ...rulesets: Ruleset[
)
}
export function merge(...rulesets: Ruleset[]): Ruleset {
export function merge(...rulesets: Permission.Ruleset[]): Permission.Ruleset {
return rulesets.flat()
}
@ -156,7 +153,7 @@ export const layer = Layer.effect(
const savedRules = EffectRuntime.fnUntraced(function* () {
return (yield* saved.list({ projectID: location.project.id })).map(
(item): Rule => ({ action: item.action, resource: item.resource, effect: "allow" }),
(item): Permission.Rule => ({ action: item.action, resource: item.resource, effect: "allow" }),
)
})
@ -170,11 +167,11 @@ export const layer = Layer.effect(
return agent?.permissions ?? missingAgentPermissions
})
function denied(input: AssertInput, rules: Ruleset) {
function denied(input: AssertInput, rules: Permission.Ruleset) {
return input.resources.some((resource) => evaluate(input.action, resource, rules).effect === "deny")
}
function relevant(input: AssertInput, rules: Ruleset) {
function relevant(input: AssertInput, rules: Permission.Ruleset) {
return rules.filter((rule) => Wildcard.match(input.action, rule.action))
}
@ -183,7 +180,7 @@ export const layer = Layer.effect(
if (denied(input, rules)) return { effect: "deny" as const, rules }
const all = [...rules, ...(yield* savedRules())]
const effects = input.resources.map((resource) => evaluate(input.action, resource, all).effect)
const effect: Effect = effects.includes("deny") ? "deny" : effects.includes("ask") ? "ask" : "allow"
const effect: Permission.Effect = effects.includes("deny") ? "deny" : effects.includes("ask") ? "ask" : "allow"
return { effect, rules: all }
})

View File

@ -1,12 +0,0 @@
export * as PermissionSchema from "./schema"
import { Permission } from "@opencode-ai/schema/permission"
export const Effect = Permission.Effect
export type Effect = Permission.Effect
export const Rule = Permission.Rule
export type Rule = Permission.Rule
export const Ruleset = Permission.Ruleset
export type Ruleset = Permission.Ruleset

View File

@ -2,9 +2,8 @@ export * as Ripgrep from "./ripgrep"
import { Context, Effect, Fiber, Layer, Schema, Stream } from "effect"
import { ChildProcess } from "effect/unstable/process"
import path from "path"
import { Entry, Match } from "@opencode-ai/schema/filesystem"
import { LayerNode } from "./effect/layer-node"
import { Entry, Match } from "./filesystem/schema"
import { AppProcess, collectStream, waitForAbort } from "./process"
import { NonNegativeInt, PositiveInt, RelativePath } from "./schema"
import { RipgrepBinary } from "./ripgrep/binary"

View File

@ -49,12 +49,12 @@ test("Core reuses the canonical shared schemas", async () => {
import("@opencode-ai/core/command"),
import("@opencode-ai/core/integration/connection"),
import("@opencode-ai/core/credential"),
import("@opencode-ai/core/filesystem/schema"),
import("@opencode-ai/core/filesystem"),
import("@opencode-ai/core/integration"),
import("@opencode-ai/core/location"),
import("@opencode-ai/llm"),
import("@opencode-ai/core/model-request"),
import("@opencode-ai/core/permission/schema"),
import("@opencode-ai/core/permission"),
import("@opencode-ai/core/project/schema"),
import("@opencode-ai/core/reference"),
import("@opencode-ai/core/session/input"),
@ -80,6 +80,8 @@ test("Core reuses the canonical shared schemas", async () => {
[coreFileSystem.Entry, FileSystem.Entry],
[coreFileSystem.Submatch, FileSystem.Submatch],
[coreFileSystem.Match, FileSystem.Match],
[coreIntegration.ID, Integration.ID],
[coreIntegration.MethodID, Integration.MethodID],
[coreIntegration.When, Integration.When],
[coreIntegration.TextPrompt, Integration.TextPrompt],
[coreIntegration.SelectPrompt, Integration.SelectPrompt],