refactor(core): remove schema forwarding facades (#33577)
This commit is contained in:
parent
6298db7a77
commit
57895586c2
@ -3,10 +3,10 @@ export * as Config from "./config"
|
|||||||
import path from "path"
|
import path from "path"
|
||||||
import { type ParseError, parse } from "jsonc-parser"
|
import { type ParseError, parse } from "jsonc-parser"
|
||||||
import { Context, Effect, Layer, Option, Schema } from "effect"
|
import { Context, Effect, Layer, Option, Schema } from "effect"
|
||||||
|
import { Permission } from "@opencode-ai/schema/permission"
|
||||||
import { FSUtil } from "./fs-util"
|
import { FSUtil } from "./fs-util"
|
||||||
import { Global } from "./global"
|
import { Global } from "./global"
|
||||||
import { Location } from "./location"
|
import { Location } from "./location"
|
||||||
import { PermissionSchema } from "./permission/schema"
|
|
||||||
import { Policy } from "./policy"
|
import { Policy } from "./policy"
|
||||||
import { AbsolutePath } from "./schema"
|
import { AbsolutePath } from "./schema"
|
||||||
import { ConfigAgent } from "./config/agent"
|
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({
|
username: Schema.String.pipe(Schema.optional).annotate({
|
||||||
description: "Username displayed in conversations and used for telemetry identity",
|
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",
|
description: "Ordered tool permission rules applied to agent tool use",
|
||||||
}),
|
}),
|
||||||
agents: Schema.Record(Schema.String, ConfigAgent.Info).pipe(Schema.optional).annotate({
|
agents: Schema.Record(Schema.String, ConfigAgent.Info).pipe(Schema.optional).annotate({
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
export * as ConfigAgent from "./agent"
|
export * as ConfigAgent from "./agent"
|
||||||
|
|
||||||
import { Schema } from "effect"
|
import { Schema } from "effect"
|
||||||
import { PermissionSchema } from "../permission/schema"
|
import { Permission } from "@opencode-ai/schema/permission"
|
||||||
import { ConfigProvider } from "./provider"
|
import { ConfigProvider } from "./provider"
|
||||||
import { PositiveInt } from "../schema"
|
import { PositiveInt } from "../schema"
|
||||||
|
|
||||||
@ -21,5 +21,5 @@ export class Info extends Schema.Class<Info>("ConfigV2.Agent")({
|
|||||||
color: Color.pipe(Schema.optional),
|
color: Color.pipe(Schema.optional),
|
||||||
steps: PositiveInt.pipe(Schema.optional),
|
steps: PositiveInt.pipe(Schema.optional),
|
||||||
disabled: Schema.Boolean.pipe(Schema.optional),
|
disabled: Schema.Boolean.pipe(Schema.optional),
|
||||||
permissions: PermissionSchema.Ruleset.pipe(Schema.optional),
|
permissions: Permission.Ruleset.pipe(Schema.optional),
|
||||||
}) {}
|
}) {}
|
||||||
|
|||||||
@ -3,8 +3,8 @@ export * as Credential from "./credential"
|
|||||||
import { asc, eq } from "drizzle-orm"
|
import { asc, eq } from "drizzle-orm"
|
||||||
import { Context, Effect, Layer, Schema } from "effect"
|
import { Context, Effect, Layer, Schema } from "effect"
|
||||||
import { Credential } from "@opencode-ai/schema/credential"
|
import { Credential } from "@opencode-ai/schema/credential"
|
||||||
|
import { Integration } from "@opencode-ai/schema/integration"
|
||||||
import { Database } from "./database/database"
|
import { Database } from "./database/database"
|
||||||
import { IntegrationSchema } from "./integration/schema"
|
|
||||||
import { CredentialTable } from "./credential/sql"
|
import { CredentialTable } from "./credential/sql"
|
||||||
|
|
||||||
export const ID = Credential.ID
|
export const ID = Credential.ID
|
||||||
@ -21,7 +21,7 @@ export type Value = Credential.Value
|
|||||||
|
|
||||||
export class Info extends Schema.Class<Info>("Credential.Info")({
|
export class Info extends Schema.Class<Info>("Credential.Info")({
|
||||||
id: ID,
|
id: ID,
|
||||||
integrationID: IntegrationSchema.ID,
|
integrationID: Integration.ID,
|
||||||
label: Schema.String,
|
label: Schema.String,
|
||||||
value: Value,
|
value: Value,
|
||||||
}) {}
|
}) {}
|
||||||
@ -30,12 +30,12 @@ export interface Interface {
|
|||||||
/** Returns every stored credential. */
|
/** Returns every stored credential. */
|
||||||
readonly all: () => Effect.Effect<Info[]>
|
readonly all: () => Effect.Effect<Info[]>
|
||||||
/** Returns stored credentials belonging to one integration. */
|
/** 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. */
|
/** Returns one stored credential by ID. */
|
||||||
readonly get: (id: ID) => Effect.Effect<Info | undefined>
|
readonly get: (id: ID) => Effect.Effect<Info | undefined>
|
||||||
/** Replaces any credential for an integration and returns the new record. */
|
/** Replaces any credential for an integration and returns the new record. */
|
||||||
readonly create: (input: {
|
readonly create: (input: {
|
||||||
readonly integrationID: IntegrationSchema.ID
|
readonly integrationID: Integration.ID
|
||||||
readonly value: Value
|
readonly value: Value
|
||||||
readonly label?: string
|
readonly label?: string
|
||||||
}) => Effect.Effect<Info>
|
}) => Effect.Effect<Info>
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"
|
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"
|
||||||
import { Timestamps } from "../database/schema.sql"
|
import { Timestamps } from "../database/schema.sql"
|
||||||
import type { IntegrationSchema } from "../integration/schema"
|
|
||||||
import type { Credential } from "../credential"
|
import type { Credential } from "../credential"
|
||||||
|
|
||||||
export const CredentialTable = sqliteTable("credential", {
|
export const CredentialTable = sqliteTable("credential", {
|
||||||
id: text().$type<Credential.ID>().primaryKey(),
|
id: text().$type<Credential.ID>().primaryKey(),
|
||||||
integration_id: text().$type<IntegrationSchema.ID>(),
|
integration_id: text().$type<Credential.Info["integrationID"]>(),
|
||||||
label: text().notNull(),
|
label: text().notNull(),
|
||||||
value: text({ mode: "json" }).$type<Credential.Value>().notNull(),
|
value: text({ mode: "json" }).$type<Credential.Value>().notNull(),
|
||||||
connector_id: text(),
|
connector_id: text(),
|
||||||
|
|||||||
@ -7,8 +7,8 @@ import { FSUtil } from "./fs-util"
|
|||||||
import { Location } from "./location"
|
import { Location } from "./location"
|
||||||
import { PositiveInt, RelativePath } from "./schema"
|
import { PositiveInt, RelativePath } from "./schema"
|
||||||
import { FileSystemSearch } from "./filesystem/search"
|
import { FileSystemSearch } from "./filesystem/search"
|
||||||
import { Entry, Match } from "./filesystem/schema"
|
import { Entry, Match } from "@opencode-ai/schema/filesystem"
|
||||||
export { Entry, Match, Submatch } from "./filesystem/schema"
|
export { Entry, Match, Submatch } from "@opencode-ai/schema/filesystem"
|
||||||
|
|
||||||
export const ReadInput = Schema.Struct({
|
export const ReadInput = Schema.Struct({
|
||||||
path: RelativePath,
|
path: RelativePath,
|
||||||
|
|||||||
@ -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
|
|
||||||
@ -16,18 +16,17 @@ import {
|
|||||||
} from "effect"
|
} from "effect"
|
||||||
import { Integration } from "@opencode-ai/schema/integration"
|
import { Integration } from "@opencode-ai/schema/integration"
|
||||||
import { Credential } from "./credential"
|
import { Credential } from "./credential"
|
||||||
import { IntegrationSchema } from "./integration/schema"
|
|
||||||
import { withStatics } from "./schema"
|
import { withStatics } from "./schema"
|
||||||
import { State } from "./state"
|
import { State } from "./state"
|
||||||
import { Identifier } from "./util/identifier"
|
import { Identifier } from "./util/identifier"
|
||||||
import { EventV2 } from "./event"
|
import { EventV2 } from "./event"
|
||||||
import { IntegrationConnection } from "./integration/connection"
|
import { IntegrationConnection } from "./integration/connection"
|
||||||
|
|
||||||
export const ID = IntegrationSchema.ID
|
export const ID = Integration.ID
|
||||||
export type ID = IntegrationSchema.ID
|
export type ID = Integration.ID
|
||||||
|
|
||||||
export const MethodID = IntegrationSchema.MethodID
|
export const MethodID = Integration.MethodID
|
||||||
export type MethodID = IntegrationSchema.MethodID
|
export type MethodID = Integration.MethodID
|
||||||
|
|
||||||
export const AttemptID = Schema.String.pipe(
|
export const AttemptID = Schema.String.pipe(
|
||||||
Schema.brand("Integration.AttemptID"),
|
Schema.brand("Integration.AttemptID"),
|
||||||
@ -103,10 +102,6 @@ export interface EnvImplementation {
|
|||||||
|
|
||||||
export type Implementation = OAuthImplementation | KeyImplementation | 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")({
|
export class Attempt extends Schema.Class<Attempt>("Integration.Attempt")({
|
||||||
attemptID: AttemptID,
|
attemptID: AttemptID,
|
||||||
url: Schema.String,
|
url: Schema.String,
|
||||||
|
|||||||
@ -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
|
|
||||||
@ -1,6 +1,7 @@
|
|||||||
export * as PermissionV2 from "./permission"
|
export * as PermissionV2 from "./permission"
|
||||||
|
|
||||||
import { Context, Deferred, Effect as EffectRuntime, Layer, Schema } from "effect"
|
import { Context, Deferred, Effect as EffectRuntime, Layer, Schema } from "effect"
|
||||||
|
import { Permission } from "@opencode-ai/schema/permission"
|
||||||
import { EventV2 } from "./event"
|
import { EventV2 } from "./event"
|
||||||
import { Location } from "./location"
|
import { Location } from "./location"
|
||||||
import { AgentV2 } from "./agent"
|
import { AgentV2 } from "./agent"
|
||||||
@ -9,14 +10,10 @@ import { SessionStore } from "./session/store"
|
|||||||
import { withStatics } from "./schema"
|
import { withStatics } from "./schema"
|
||||||
import { Identifier } from "./util/identifier"
|
import { Identifier } from "./util/identifier"
|
||||||
import { Wildcard } from "./util/wildcard"
|
import { Wildcard } from "./util/wildcard"
|
||||||
import { PermissionSchema } from "./permission/schema"
|
|
||||||
import { PermissionSaved } from "./permission/saved"
|
import { PermissionSaved } from "./permission/saved"
|
||||||
|
|
||||||
export { Effect, Rule, Ruleset } from "./permission/schema"
|
export { Effect, Rule, Ruleset } from "@opencode-ai/schema/permission"
|
||||||
type Effect = PermissionSchema.Effect
|
const missingAgentPermissions: Permission.Ruleset = [{ action: "*", resource: "*", effect: "deny" }]
|
||||||
type Rule = PermissionSchema.Rule
|
|
||||||
type Ruleset = PermissionSchema.Ruleset
|
|
||||||
const missingAgentPermissions: Ruleset = [{ action: "*", resource: "*", effect: "deny" }]
|
|
||||||
|
|
||||||
export const ID = Schema.String.check(Schema.isStartsWith("per")).pipe(
|
export const ID = Schema.String.check(Schema.isStartsWith("per")).pipe(
|
||||||
Schema.brand("PermissionV2.ID"),
|
Schema.brand("PermissionV2.ID"),
|
||||||
@ -67,7 +64,7 @@ export type ReplyInput = typeof ReplyInput.Type
|
|||||||
|
|
||||||
export const AskResult = Schema.Struct({
|
export const AskResult = Schema.Struct({
|
||||||
id: ID,
|
id: ID,
|
||||||
effect: PermissionSchema.Effect,
|
effect: Permission.Effect,
|
||||||
}).annotate({ identifier: "PermissionV2.AskResult" })
|
}).annotate({ identifier: "PermissionV2.AskResult" })
|
||||||
export type AskResult = typeof AskResult.Type
|
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", {
|
export class DeniedError extends Schema.TaggedErrorClass<DeniedError>()("PermissionV2.DeniedError", {
|
||||||
rules: PermissionSchema.Ruleset,
|
rules: Permission.Ruleset,
|
||||||
}) {}
|
}) {}
|
||||||
|
|
||||||
export class NotFoundError extends Schema.TaggedErrorClass<NotFoundError>()("PermissionV2.NotFoundError", {
|
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 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 (
|
return (
|
||||||
rulesets
|
rulesets
|
||||||
.flat()
|
.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()
|
return rulesets.flat()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,7 +153,7 @@ export const layer = Layer.effect(
|
|||||||
|
|
||||||
const savedRules = EffectRuntime.fnUntraced(function* () {
|
const savedRules = EffectRuntime.fnUntraced(function* () {
|
||||||
return (yield* saved.list({ projectID: location.project.id })).map(
|
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
|
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")
|
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))
|
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 }
|
if (denied(input, rules)) return { effect: "deny" as const, rules }
|
||||||
const all = [...rules, ...(yield* savedRules())]
|
const all = [...rules, ...(yield* savedRules())]
|
||||||
const effects = input.resources.map((resource) => evaluate(input.action, resource, all).effect)
|
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 }
|
return { effect, rules: all }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -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
|
|
||||||
@ -2,9 +2,8 @@ export * as Ripgrep from "./ripgrep"
|
|||||||
|
|
||||||
import { Context, Effect, Fiber, Layer, Schema, Stream } from "effect"
|
import { Context, Effect, Fiber, Layer, Schema, Stream } from "effect"
|
||||||
import { ChildProcess } from "effect/unstable/process"
|
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 { LayerNode } from "./effect/layer-node"
|
||||||
import { Entry, Match } from "./filesystem/schema"
|
|
||||||
import { AppProcess, collectStream, waitForAbort } from "./process"
|
import { AppProcess, collectStream, waitForAbort } from "./process"
|
||||||
import { NonNegativeInt, PositiveInt, RelativePath } from "./schema"
|
import { NonNegativeInt, PositiveInt, RelativePath } from "./schema"
|
||||||
import { RipgrepBinary } from "./ripgrep/binary"
|
import { RipgrepBinary } from "./ripgrep/binary"
|
||||||
|
|||||||
@ -49,12 +49,12 @@ test("Core reuses the canonical shared schemas", async () => {
|
|||||||
import("@opencode-ai/core/command"),
|
import("@opencode-ai/core/command"),
|
||||||
import("@opencode-ai/core/integration/connection"),
|
import("@opencode-ai/core/integration/connection"),
|
||||||
import("@opencode-ai/core/credential"),
|
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/integration"),
|
||||||
import("@opencode-ai/core/location"),
|
import("@opencode-ai/core/location"),
|
||||||
import("@opencode-ai/llm"),
|
import("@opencode-ai/llm"),
|
||||||
import("@opencode-ai/core/model-request"),
|
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/project/schema"),
|
||||||
import("@opencode-ai/core/reference"),
|
import("@opencode-ai/core/reference"),
|
||||||
import("@opencode-ai/core/session/input"),
|
import("@opencode-ai/core/session/input"),
|
||||||
@ -80,6 +80,8 @@ test("Core reuses the canonical shared schemas", async () => {
|
|||||||
[coreFileSystem.Entry, FileSystem.Entry],
|
[coreFileSystem.Entry, FileSystem.Entry],
|
||||||
[coreFileSystem.Submatch, FileSystem.Submatch],
|
[coreFileSystem.Submatch, FileSystem.Submatch],
|
||||||
[coreFileSystem.Match, FileSystem.Match],
|
[coreFileSystem.Match, FileSystem.Match],
|
||||||
|
[coreIntegration.ID, Integration.ID],
|
||||||
|
[coreIntegration.MethodID, Integration.MethodID],
|
||||||
[coreIntegration.When, Integration.When],
|
[coreIntegration.When, Integration.When],
|
||||||
[coreIntegration.TextPrompt, Integration.TextPrompt],
|
[coreIntegration.TextPrompt, Integration.TextPrompt],
|
||||||
[coreIntegration.SelectPrompt, Integration.SelectPrompt],
|
[coreIntegration.SelectPrompt, Integration.SelectPrompt],
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user