From 57895586c2491a1287685f09afc1a08234906b2b Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Wed, 24 Jun 2026 04:52:58 +0200 Subject: [PATCH] refactor(core): remove schema forwarding facades (#33577) --- packages/core/src/config.ts | 4 ++-- packages/core/src/config/agent.ts | 4 ++-- packages/core/src/credential.ts | 8 ++++---- packages/core/src/credential/sql.ts | 3 +-- packages/core/src/filesystem.ts | 4 ++-- packages/core/src/filesystem/schema.ts | 10 ---------- packages/core/src/integration.ts | 13 ++++-------- packages/core/src/integration/schema.ts | 9 --------- packages/core/src/permission.ts | 25 +++++++++++------------- packages/core/src/permission/schema.ts | 12 ------------ packages/core/src/ripgrep.ts | 3 +-- packages/core/test/shared-schema.test.ts | 6 ++++-- 12 files changed, 31 insertions(+), 70 deletions(-) delete mode 100644 packages/core/src/filesystem/schema.ts delete mode 100644 packages/core/src/integration/schema.ts delete mode 100644 packages/core/src/permission/schema.ts diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 26cd3720d..1f97194ad 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -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("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({ diff --git a/packages/core/src/config/agent.ts b/packages/core/src/config/agent.ts index 1dea6044b..63df995f8 100644 --- a/packages/core/src/config/agent.ts +++ b/packages/core/src/config/agent.ts @@ -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("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), }) {} diff --git a/packages/core/src/credential.ts b/packages/core/src/credential.ts index 42cb3b64b..caef1db1b 100644 --- a/packages/core/src/credential.ts +++ b/packages/core/src/credential.ts @@ -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("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 /** Returns stored credentials belonging to one integration. */ - readonly list: (integrationID: IntegrationSchema.ID) => Effect.Effect + readonly list: (integrationID: Integration.ID) => Effect.Effect /** Returns one stored credential by ID. */ readonly get: (id: ID) => Effect.Effect /** 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 diff --git a/packages/core/src/credential/sql.ts b/packages/core/src/credential/sql.ts index 3afd7284a..f6b59197a 100644 --- a/packages/core/src/credential/sql.ts +++ b/packages/core/src/credential/sql.ts @@ -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().primaryKey(), - integration_id: text().$type(), + integration_id: text().$type(), label: text().notNull(), value: text({ mode: "json" }).$type().notNull(), connector_id: text(), diff --git a/packages/core/src/filesystem.ts b/packages/core/src/filesystem.ts index 9a95f4a48..d7536ab62 100644 --- a/packages/core/src/filesystem.ts +++ b/packages/core/src/filesystem.ts @@ -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, diff --git a/packages/core/src/filesystem/schema.ts b/packages/core/src/filesystem/schema.ts deleted file mode 100644 index 8f4f27cc5..000000000 --- a/packages/core/src/filesystem/schema.ts +++ /dev/null @@ -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 diff --git a/packages/core/src/integration.ts b/packages/core/src/integration.ts index ed0e3935e..bd56cc025 100644 --- a/packages/core/src/integration.ts +++ b/packages/core/src/integration.ts @@ -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("Integration.Attempt")({ attemptID: AttemptID, url: Schema.String, diff --git a/packages/core/src/integration/schema.ts b/packages/core/src/integration/schema.ts deleted file mode 100644 index bb73d571e..000000000 --- a/packages/core/src/integration/schema.ts +++ /dev/null @@ -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 diff --git a/packages/core/src/permission.ts b/packages/core/src/permission.ts index bbfc6014e..f509cd1be 100644 --- a/packages/core/src/permission.ts +++ b/packages/core/src/permission.ts @@ -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()("P }) {} export class DeniedError extends Schema.TaggedErrorClass()("PermissionV2.DeniedError", { - rules: PermissionSchema.Ruleset, + rules: Permission.Ruleset, }) {} export class NotFoundError extends Schema.TaggedErrorClass()("PermissionV2.NotFoundError", { @@ -99,7 +96,7 @@ export class NotFoundError extends Schema.TaggedErrorClass()("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 } }) diff --git a/packages/core/src/permission/schema.ts b/packages/core/src/permission/schema.ts deleted file mode 100644 index c48511654..000000000 --- a/packages/core/src/permission/schema.ts +++ /dev/null @@ -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 diff --git a/packages/core/src/ripgrep.ts b/packages/core/src/ripgrep.ts index e878c30fc..7f0c61e1f 100644 --- a/packages/core/src/ripgrep.ts +++ b/packages/core/src/ripgrep.ts @@ -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" diff --git a/packages/core/test/shared-schema.test.ts b/packages/core/test/shared-schema.test.ts index 063202ec7..a1794b926 100644 --- a/packages/core/test/shared-schema.test.ts +++ b/packages/core/test/shared-schema.test.ts @@ -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],