Rename v2 auth service to account (#28260)

This commit is contained in:
Dax
2026-05-20 20:53:27 -04:00
committed by GitHub
parent bd41dac88f
commit 8643c0721e
67 changed files with 2541 additions and 1379 deletions

View File

@@ -1,18 +1,16 @@
import { EOL } from "os"
import { Effect, Layer, Option } from "effect"
import { Effect, Option } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { LocationServiceMap } from "@opencode-ai/core/location-layer"
import { PluginBoot } from "@opencode-ai/core/plugin/boot"
import { effectCmd } from "../../effect-cmd"
const Runtime = Layer.mergeAll(LocationServiceMap.layer)
export const V2Command = effectCmd({
command: "v2",
describe: "debug v2 catalog and built-in plugins",
instance: false,
handler: Effect.fn("Cli.debug.v2")(
function* () {
handler: () =>
Effect.gen(function* () {
yield* PluginBoot.Service.use((service) => service.wait())
const catalog = yield* Catalog.Service
const providers = (yield* catalog.provider.available()).sort((a, b) => a.id.localeCompare(b.id))
@@ -35,12 +33,13 @@ export const V2Command = effectCmd({
),
}
process.stdout.write(JSON.stringify(result, null, 2) + EOL)
},
Effect.provide(
LocationServiceMap.get({
directory: process.cwd(),
}),
}).pipe(
Effect.withSpan("Cli.debug.v2"),
Effect.provide(
LocationServiceMap.get({
directory: process.cwd(),
}),
),
Effect.provide(LocationServiceMap.layer),
),
Effect.provide(Runtime),
),
})

View File

@@ -458,7 +458,7 @@ export const RunCommand = effectCmd({
const name = title()
const result = await sdk.session.create({
title: name,
permission: rules,
permission: [...rules],
})
const id = result.data?.id
if (!id) {
@@ -501,7 +501,7 @@ export const RunCommand = effectCmd({
variant: input.variant,
}
: undefined,
permission: rules,
permission: [...rules],
})
const id = result.data?.id
if (!id) {

View File

@@ -81,7 +81,7 @@ export const layer = Layer.effect(
)
export const defaultLayer = layer.pipe(
Layer.provideMerge(EventV2.defaultLayer),
Layer.provide(EventV2.defaultLayer),
Layer.provide(SyncEvent.defaultLayer),
Layer.provide(ProjectBus.defaultLayer),
)

View File

@@ -1,15 +1 @@
import { Wildcard } from "@/util/wildcard"
type Rule = {
permission: string
pattern: string
action: "allow" | "deny" | "ask"
}
export function evaluate(permission: string, pattern: string, ...rulesets: Rule[][]): Rule {
const rules = rulesets.flat()
const match = rules.findLast(
(rule) => Wildcard.match(permission, rule.permission) && Wildcard.match(pattern, rule.pattern),
)
return match ?? { action: "ask", permission, pattern: "*" }
}
export { evaluate } from "@opencode-ai/core/permission"

View File

@@ -8,15 +8,15 @@ import { PermissionTable } from "@/session/session.sql"
import { Database } from "@/storage/db"
import { eq } from "drizzle-orm"
import * as Log from "@opencode-ai/core/util/log"
import { Wildcard } from "@/util/wildcard"
import { Wildcard } from "@opencode-ai/core/util/wildcard"
import { Deferred, Effect, Layer, Schema, Context } from "effect"
import os from "os"
import { evaluate as evalRule } from "./evaluate"
import { PermissionV2 } from "@opencode-ai/core/permission"
import { PermissionID } from "./schema"
const log = Log.create({ service: "permission" })
export const Action = Schema.Literals(["allow", "deny", "ask"]).annotate({ identifier: "PermissionAction" })
export const Action = PermissionV2.Action.annotate({ identifier: "PermissionAction" })
export type Action = Schema.Schema.Type<typeof Action>
export const Rule = Schema.Struct({
@@ -26,7 +26,7 @@ export const Rule = Schema.Struct({
}).annotate({ identifier: "PermissionRule" })
export type Rule = Schema.Schema.Type<typeof Rule>
export const Ruleset = Schema.mutable(Schema.Array(Rule)).annotate({ identifier: "PermissionRuleset" })
export const Ruleset = Schema.Array(Rule).annotate({ identifier: "PermissionRuleset" })
export type Ruleset = Schema.Schema.Type<typeof Ruleset>
export class Request extends Schema.Class<Request>("PermissionRequest")({
@@ -122,11 +122,11 @@ interface PendingEntry {
interface State {
pending: Map<PermissionID, PendingEntry>
approved: Ruleset
approved: Rule[]
}
export function evaluate(permission: string, pattern: string, ...rulesets: Ruleset[]): Rule {
return evalRule(permission, pattern, ...rulesets)
return PermissionV2.evaluate(permission, pattern, ...rulesets)
}
export class Service extends Context.Service<Service, Interface>()("@opencode/Permission") {}
@@ -142,7 +142,7 @@ export const layer = Layer.effect(
)
const state = {
pending: new Map<PermissionID, PendingEntry>(),
approved: row?.data ?? [],
approved: [...(row?.data ?? [])],
}
yield* Effect.addFinalizer(() =>
@@ -271,7 +271,7 @@ function expand(pattern: string): string {
}
export function fromConfig(permission: ConfigPermission.Info) {
const ruleset: Ruleset = []
const ruleset: Rule[] = []
for (const [key, value] of Object.entries(permission)) {
if (typeof value === "string") {
ruleset.push({ permission: key, action: value, pattern: "*" })
@@ -284,21 +284,12 @@ export function fromConfig(permission: ConfigPermission.Info) {
return ruleset
}
export function merge(...rulesets: Ruleset[]): Ruleset {
return rulesets.flat()
export function merge(...rulesets: Ruleset[]): Rule[] {
return [...PermissionV2.merge(...rulesets)]
}
const EDIT_TOOLS = ["edit", "write", "apply_patch"]
export function disabled(tools: string[], ruleset: Ruleset): Set<string> {
const result = new Set<string>()
for (const tool of tools) {
const permission = EDIT_TOOLS.includes(tool) ? "edit" : tool
const rule = ruleset.findLast((rule) => Wildcard.match(permission, rule.permission))
if (!rule) continue
if (rule.pattern === "*" && rule.action === "deny") result.add(tool)
}
return result
return PermissionV2.disabled(tools, ruleset)
}
export const defaultLayer = layer.pipe(Layer.provide(Bus.layer))

View File

@@ -159,9 +159,15 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
if (body.trim().length === 0) return yield* create({})
const json = yield* tryParseJson(body)
const payload = yield* Schema.decodeUnknownEffect(Session.CreateInput)(json).pipe(
const decoded = yield* Schema.decodeUnknownEffect(Session.CreateInput)(json).pipe(
Effect.mapError(() => new HttpApiError.BadRequest({})),
)
const payload = decoded
? {
...decoded,
permission: decoded.permission ? [...decoded.permission] : undefined,
}
: decoded
return yield* create({ payload })
})

View File

@@ -1216,7 +1216,7 @@ export const layer = Layer.effect(
const message = yield* createUserMessage(input)
yield* sessions.touch(input.sessionID)
const permissions: Permission.Ruleset = []
const permissions: Permission.Rule[] = []
for (const [t, enabled] of Object.entries(input.tools ?? {})) {
permissions.push({ permission: t, action: enabled ? "allow" : "deny", pattern: "*" })
}

View File

@@ -101,7 +101,7 @@ export function fromRow(row: SessionRow): Info {
},
share,
revert,
permission: row.permission ?? undefined,
permission: row.permission ? [...row.permission] : undefined,
time: {
created: row.time_created,
updated: row.time_updated,
@@ -542,7 +542,7 @@ export const layer: Layer.Layer<
title: input.title ?? createDefaultTitle(!!input.parentID),
agent: input.agent,
model: input.model,
permission: input.permission,
permission: input.permission ? [...input.permission] : undefined,
cost: 0,
tokens: EmptyTokens,
time: {
@@ -734,7 +734,7 @@ export const layer: Layer.Layer<
sessionID: SessionID
permission: Permission.Ruleset
}) {
yield* patch(input.sessionID, { permission: input.permission, time: { updated: Date.now() } })
yield* patch(input.sessionID, { permission: [...input.permission], time: { updated: Date.now() } })
})
const setRevert = Effect.fn("Session.setRevert")(function* (input: {

View File

@@ -4,7 +4,7 @@ import { SyncEvent } from "@/sync"
import { Effect, Layer, Scope, Context } from "effect"
import { Config } from "@/config/config"
import { RuntimeFlags } from "@/effect/runtime-flags"
import * as ShareNext from "./share-next"
import { ShareNext } from "./share-next"
export interface Interface {
readonly create: (input?: Session.CreateInput) => Effect.Effect<Session.Info>