Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/core/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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({
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/config/agent.ts
Original file line number Diff line number Diff line change
@@ -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"

Expand All @@ -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),
}) {}
8 changes: 4 additions & 4 deletions packages/core/src/credential.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
}) {}
Expand All @@ -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>
Expand Down
3 changes: 1 addition & 2 deletions packages/core/src/credential/sql.ts
Original file line number Diff line number Diff line change
@@ -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(),
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/filesystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
10 changes: 0 additions & 10 deletions packages/core/src/filesystem/schema.ts

This file was deleted.

13 changes: 4 additions & 9 deletions packages/core/src/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down Expand Up @@ -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,
Expand Down
9 changes: 0 additions & 9 deletions packages/core/src/integration/schema.ts

This file was deleted.

25 changes: 11 additions & 14 deletions packages/core/src/permission.ts
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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"),
Expand Down Expand Up @@ -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

Expand All @@ -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", {
Expand All @@ -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()
Expand All @@ -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()
}

Expand Down Expand Up @@ -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" }),
)
})

Expand All @@ -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))
}

Expand All @@ -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 }
})

Expand Down
12 changes: 0 additions & 12 deletions packages/core/src/permission/schema.ts

This file was deleted.

3 changes: 1 addition & 2 deletions packages/core/src/ripgrep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
6 changes: 4 additions & 2 deletions packages/core/test/shared-schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand All @@ -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],
Expand Down
Loading