refactor(schema): extract shared public schemas (#33571)
This commit is contained in:
parent
60aba622ab
commit
516cfe4e09
130 changed files with 2014 additions and 1593 deletions
|
|
@ -1,46 +1,17 @@
|
|||
export * as AgentV2 from "./agent"
|
||||
|
||||
import { Array, Context, Effect, Layer, Schema, Scope, Types } from "effect"
|
||||
import { ModelV2 } from "./model"
|
||||
import { PermissionSchema } from "./permission/schema"
|
||||
import { ProviderV2 } from "./provider"
|
||||
import { PositiveInt } from "./schema"
|
||||
import { Array, Context, Effect, Layer, Types } from "effect"
|
||||
import { Agent } from "@opencode-ai/schema/agent"
|
||||
import { State } from "./state"
|
||||
|
||||
export const ID = Schema.String.pipe(Schema.brand("AgentV2.ID"))
|
||||
export const ID = Agent.ID
|
||||
export type ID = typeof ID.Type
|
||||
export const defaultID = ID.make("build")
|
||||
|
||||
export const Color = Schema.Union([
|
||||
Schema.String.check(Schema.isPattern(/^#[0-9a-fA-F]{6}$/)),
|
||||
Schema.Literals(["primary", "secondary", "accent", "success", "warning", "error", "info"]),
|
||||
])
|
||||
export const Color = Agent.Color
|
||||
|
||||
export class Info extends Schema.Class<Info>("AgentV2.Info")({
|
||||
id: ID,
|
||||
model: ModelV2.Ref.pipe(Schema.optional),
|
||||
request: ProviderV2.Request,
|
||||
system: Schema.String.pipe(Schema.optional),
|
||||
description: Schema.String.pipe(Schema.optional),
|
||||
mode: Schema.Literals(["subagent", "primary", "all"]),
|
||||
hidden: Schema.Boolean,
|
||||
color: Color.pipe(Schema.optional),
|
||||
steps: PositiveInt.pipe(Schema.optional),
|
||||
permissions: PermissionSchema.Ruleset,
|
||||
}) {
|
||||
static empty(id: ID) {
|
||||
return new Info({
|
||||
id,
|
||||
request: {
|
||||
headers: {},
|
||||
body: {},
|
||||
},
|
||||
mode: "all",
|
||||
hidden: false,
|
||||
permissions: [],
|
||||
})
|
||||
}
|
||||
}
|
||||
export const Info = Agent.Info
|
||||
export type Info = Agent.Info
|
||||
|
||||
export interface Selection {
|
||||
readonly id: ID
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ export const layer = Layer.effect(
|
|||
...ModelRequest.merge({ ...provider.request, generation: {}, options: {} }, model.request),
|
||||
variant: model.request.variant,
|
||||
}
|
||||
return new ModelV2.Info({
|
||||
return ModelV2.Info.make({
|
||||
...model,
|
||||
api,
|
||||
request,
|
||||
|
|
|
|||
|
|
@ -1,17 +1,11 @@
|
|||
export * as CommandV2 from "./command"
|
||||
|
||||
import { Context, Effect, Layer, Schema, Types } from "effect"
|
||||
import { ModelV2 } from "./model"
|
||||
import { Context, Effect, Layer, Types } from "effect"
|
||||
import { Command } from "@opencode-ai/schema/command"
|
||||
import { State } from "./state"
|
||||
|
||||
export class Info extends Schema.Class<Info>("CommandV2.Info")({
|
||||
name: Schema.String,
|
||||
template: Schema.String,
|
||||
description: Schema.String.pipe(Schema.optional),
|
||||
agent: Schema.String.pipe(Schema.optional),
|
||||
model: ModelV2.Ref.pipe(Schema.optional),
|
||||
subtask: Schema.Boolean.pipe(Schema.optional),
|
||||
}) {}
|
||||
export const Info = Command.Info
|
||||
export type Info = Command.Info
|
||||
|
||||
export type Data = {
|
||||
commands: Map<string, Types.DeepMutable<Info>>
|
||||
|
|
@ -40,7 +34,7 @@ export const layer = Layer.effect(
|
|||
list: () => Array.from(draft.commands.values()) as Info[],
|
||||
get: (name) => draft.commands.get(name),
|
||||
update: (name, update) => {
|
||||
const current = draft.commands.get(name) ?? (new Info({ name, template: "" }) as Types.DeepMutable<Info>)
|
||||
const current = draft.commands.get(name) ?? ({ name, template: "" } as Types.DeepMutable<Info>)
|
||||
if (!draft.commands.has(name)) draft.commands.set(name, current)
|
||||
update(current)
|
||||
current.name = name
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export const Plugin = define({
|
|||
entries.set(
|
||||
name,
|
||||
local(entry)
|
||||
? new Reference.LocalSource({
|
||||
? Reference.LocalSource.make({
|
||||
type: "local",
|
||||
path: AbsolutePath.make(
|
||||
localPath(directory, global.home, typeof entry === "string" ? entry : entry.path),
|
||||
|
|
@ -36,7 +36,7 @@ export const Plugin = define({
|
|||
description: typeof entry === "string" ? undefined : entry.description,
|
||||
hidden: typeof entry === "string" ? undefined : entry.hidden,
|
||||
})
|
||||
: new Reference.GitSource({
|
||||
: Reference.GitSource.make({
|
||||
type: "git",
|
||||
repository: typeof entry === "string" ? entry : entry.repository,
|
||||
branch: typeof entry === "string" ? undefined : entry.branch,
|
||||
|
|
|
|||
|
|
@ -22,20 +22,23 @@ export const Plugin = define({
|
|||
const items = entries.flatMap((entry) => (entry.type === "document" ? (entry.info.skills ?? []) : []))
|
||||
for (const directory of directories) {
|
||||
draft.source(
|
||||
new SkillV2.DirectorySource({ type: "directory", path: AbsolutePath.make(path.join(directory, "skill")) }),
|
||||
SkillV2.DirectorySource.make({ type: "directory", path: AbsolutePath.make(path.join(directory, "skill")) }),
|
||||
)
|
||||
draft.source(
|
||||
new SkillV2.DirectorySource({ type: "directory", path: AbsolutePath.make(path.join(directory, "skills")) }),
|
||||
SkillV2.DirectorySource.make({
|
||||
type: "directory",
|
||||
path: AbsolutePath.make(path.join(directory, "skills")),
|
||||
}),
|
||||
)
|
||||
}
|
||||
for (const item of items) {
|
||||
if (URL.canParse(item) && /^(https?:)$/.test(new URL(item).protocol)) {
|
||||
draft.source(new SkillV2.UrlSource({ type: "url", url: item }))
|
||||
draft.source(SkillV2.UrlSource.make({ type: "url", url: item }))
|
||||
continue
|
||||
}
|
||||
const expanded = item.startsWith("~/") ? path.join(global.home, item.slice(2)) : item
|
||||
draft.source(
|
||||
new SkillV2.DirectorySource({
|
||||
SkillV2.DirectorySource.make({
|
||||
type: "directory",
|
||||
path: AbsolutePath.make(path.isAbsolute(expanded) ? expanded : path.join(location.directory, expanded)),
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -2,37 +2,22 @@ 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 { Database } from "./database/database"
|
||||
import { IntegrationSchema } from "./integration/schema"
|
||||
import { NonNegativeInt, withStatics } from "./schema"
|
||||
import { Identifier } from "./util/identifier"
|
||||
import { CredentialTable } from "./credential/sql"
|
||||
|
||||
export const ID = Schema.String.pipe(
|
||||
Schema.brand("Credential.ID"),
|
||||
withStatics((schema) => ({ create: () => schema.make("cred_" + Identifier.ascending()) })),
|
||||
)
|
||||
export type ID = typeof ID.Type
|
||||
export const ID = Credential.ID
|
||||
export type ID = Credential.ID
|
||||
|
||||
export class OAuth extends Schema.Class<OAuth>("Credential.OAuth")({
|
||||
type: Schema.Literal("oauth"),
|
||||
methodID: IntegrationSchema.MethodID,
|
||||
refresh: Schema.String,
|
||||
access: Schema.String,
|
||||
expires: NonNegativeInt,
|
||||
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)),
|
||||
}) {}
|
||||
export const OAuth = Credential.OAuth
|
||||
export type OAuth = Credential.OAuth
|
||||
|
||||
export class Key extends Schema.Class<Key>("Credential.Key")({
|
||||
type: Schema.Literal("key"),
|
||||
key: Schema.String,
|
||||
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Any)),
|
||||
}) {}
|
||||
export const Key = Credential.Key
|
||||
export type Key = Credential.Key
|
||||
|
||||
export const Value = Schema.Union([OAuth, Key])
|
||||
.pipe(Schema.toTaggedUnion("type"))
|
||||
.annotate({ identifier: "Credential.Value" })
|
||||
export type Value = Schema.Schema.Type<typeof Value>
|
||||
export const Value = Credential.Value
|
||||
export type Value = Credential.Value
|
||||
|
||||
export class Info extends Schema.Class<Info>("Credential.Info")({
|
||||
id: ID,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { and, asc, eq, gt } from "drizzle-orm"
|
|||
import { Database } from "./database/database"
|
||||
import { EventSequenceTable, EventTable } from "./event/sql"
|
||||
import { Location } from "./location"
|
||||
import { externalID, type ExternalID, withStatics } from "./schema"
|
||||
import { withStatics } from "./schema"
|
||||
import { Identifier } from "./util/identifier"
|
||||
import { LayerNode } from "./effect/layer-node"
|
||||
import { isDeepStrictEqual } from "node:util"
|
||||
|
|
@ -14,7 +14,6 @@ export const ID = Schema.String.check(Schema.isStartsWith("evt_")).pipe(
|
|||
Schema.brand("Event.ID"),
|
||||
withStatics((schema) => ({
|
||||
create: () => schema.make("evt_" + Identifier.ascending()),
|
||||
fromExternal: (input: ExternalID) => schema.make(externalID("evt", input)),
|
||||
})),
|
||||
)
|
||||
export type ID = typeof ID.Type
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ const baseLayer = Layer.effect(
|
|||
const absolute = path.join(target.absolute, item.name)
|
||||
const relative = path.relative(target.directory, absolute)
|
||||
return [
|
||||
new Entry({
|
||||
Entry.make({
|
||||
path: RelativePath.make(relative + (item.type === "directory" ? path.sep : "")),
|
||||
type: item.type,
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -1,22 +1,10 @@
|
|||
import { Schema } from "effect"
|
||||
import { NonNegativeInt, PositiveInt, RelativePath } from "../schema"
|
||||
import { FileSystem } from "@opencode-ai/schema/filesystem"
|
||||
|
||||
export class Entry extends Schema.Class<Entry>("FileSystem.Entry")({
|
||||
path: RelativePath,
|
||||
type: Schema.Literals(["file", "directory"]),
|
||||
}) {}
|
||||
export const Entry = FileSystem.Entry
|
||||
export type Entry = FileSystem.Entry
|
||||
|
||||
export const Submatch = Schema.Struct({
|
||||
text: Schema.String,
|
||||
start: NonNegativeInt,
|
||||
end: NonNegativeInt,
|
||||
})
|
||||
export type Submatch = typeof Submatch.Type
|
||||
export const Submatch = FileSystem.Submatch
|
||||
export type Submatch = FileSystem.Submatch
|
||||
|
||||
export class Match extends Schema.Class<Match>("FileSystem.Match")({
|
||||
entry: Entry,
|
||||
line: PositiveInt,
|
||||
offset: NonNegativeInt,
|
||||
text: Schema.String,
|
||||
submatches: Schema.Array(Submatch),
|
||||
}) {}
|
||||
export const Match = FileSystem.Match
|
||||
export type Match = FileSystem.Match
|
||||
|
|
|
|||
|
|
@ -59,12 +59,11 @@ export const ripgrepLayer = Layer.effect(
|
|||
})
|
||||
.pipe(
|
||||
Effect.map((result) =>
|
||||
result.map(
|
||||
(entry) =>
|
||||
new FileSystem.Entry({
|
||||
...entry,
|
||||
path: RelativePath.make(path.relative(location.directory, path.resolve(cwd, entry.path))),
|
||||
}),
|
||||
result.map((entry) =>
|
||||
FileSystem.Entry.make({
|
||||
...entry,
|
||||
path: RelativePath.make(path.relative(location.directory, path.resolve(cwd, entry.path))),
|
||||
}),
|
||||
),
|
||||
),
|
||||
Effect.orDie,
|
||||
|
|
@ -85,15 +84,14 @@ export const ripgrepLayer = Layer.effect(
|
|||
})
|
||||
.pipe(
|
||||
Effect.map((result) =>
|
||||
result.map(
|
||||
(match) =>
|
||||
new FileSystem.Match({
|
||||
...match,
|
||||
entry: new FileSystem.Entry({
|
||||
...match.entry,
|
||||
path: RelativePath.make(path.relative(location.directory, path.resolve(cwd, match.entry.path))),
|
||||
}),
|
||||
result.map((match) =>
|
||||
FileSystem.Match.make({
|
||||
...match,
|
||||
entry: FileSystem.Entry.make({
|
||||
...match.entry,
|
||||
path: RelativePath.make(path.relative(location.directory, path.resolve(cwd, match.entry.path))),
|
||||
}),
|
||||
}),
|
||||
),
|
||||
),
|
||||
Effect.orDie,
|
||||
|
|
@ -110,7 +108,7 @@ export const ripgrepLayer = Layer.effect(
|
|||
return fuzzysort.go(input.query, items, { limit: input.limit ?? 50 }).map((item) => {
|
||||
const relative = item.target
|
||||
const type = relative.endsWith(path.sep) ? ("directory" as const) : ("file" as const)
|
||||
return new FileSystem.Entry({
|
||||
return FileSystem.Entry.make({
|
||||
path: RelativePath.make(relative),
|
||||
type,
|
||||
})
|
||||
|
|
@ -145,12 +143,11 @@ export const fffLayer = Layer.effect(
|
|||
pageSize: input.limit,
|
||||
})
|
||||
if (!found.ok) throw found.error
|
||||
return found.value.items.map(
|
||||
(item) =>
|
||||
new FileSystem.Entry({
|
||||
path: RelativePath.make(item.relativePath.replaceAll("\\", "/")),
|
||||
type: "file",
|
||||
}),
|
||||
return found.value.items.map((item) =>
|
||||
FileSystem.Entry.make({
|
||||
path: RelativePath.make(item.relativePath.replaceAll("\\", "/")),
|
||||
type: "file",
|
||||
}),
|
||||
)
|
||||
}),
|
||||
grep: (input) =>
|
||||
|
|
@ -165,8 +162,8 @@ export const fffLayer = Layer.effect(
|
|||
if (!found.ok) throw found.error
|
||||
return found.value.items.map((match) => {
|
||||
const bytes = Buffer.from(match.lineContent)
|
||||
return new FileSystem.Match({
|
||||
entry: new FileSystem.Entry({
|
||||
return FileSystem.Match.make({
|
||||
entry: FileSystem.Entry.make({
|
||||
path: RelativePath.make(match.relativePath.replaceAll("\\", "/")),
|
||||
type: "file",
|
||||
}),
|
||||
|
|
@ -215,7 +212,7 @@ export const fffLayer = Layer.effect(
|
|||
.sort((a, b) => b.score - a.score || a.path.length - b.path.length)
|
||||
.map((item) => {
|
||||
const relative = item.path.replaceAll("\\", "/").replace(/\/$/, "")
|
||||
return new FileSystem.Entry({
|
||||
return FileSystem.Entry.make({
|
||||
path: RelativePath.make(relative + (item.type === "directory" ? path.sep : "")),
|
||||
type: item.type,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { randomBytes } from "crypto"
|
||||
import { create as createIdentifier } from "@opencode-ai/schema/identifier"
|
||||
|
||||
const prefixes = {
|
||||
job: "job",
|
||||
|
|
@ -13,12 +13,6 @@ const prefixes = {
|
|||
workspace: "wrk",
|
||||
} as const
|
||||
|
||||
const LENGTH = 26
|
||||
|
||||
// State for monotonic ID generation
|
||||
let lastTimestamp = 0
|
||||
let counter = 0
|
||||
|
||||
export function ascending(prefix: keyof typeof prefixes, given?: string) {
|
||||
return generateID(prefix, "ascending", given)
|
||||
}
|
||||
|
|
@ -38,35 +32,8 @@ function generateID(prefix: keyof typeof prefixes, direction: "descending" | "as
|
|||
return given
|
||||
}
|
||||
|
||||
function randomBase62(length: number): string {
|
||||
const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
let result = ""
|
||||
const bytes = randomBytes(length)
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars[bytes[i] % 62]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export function create(prefix: string, direction: "descending" | "ascending", timestamp?: number): string {
|
||||
const currentTimestamp = timestamp ?? Date.now()
|
||||
|
||||
if (currentTimestamp !== lastTimestamp) {
|
||||
lastTimestamp = currentTimestamp
|
||||
counter = 0
|
||||
}
|
||||
counter++
|
||||
|
||||
let now = BigInt(currentTimestamp) * BigInt(0x1000) + BigInt(counter)
|
||||
|
||||
now = direction === "descending" ? ~now : now
|
||||
|
||||
const timeBytes = Buffer.alloc(6)
|
||||
for (let i = 0; i < 6; i++) {
|
||||
timeBytes[i] = Number((now >> BigInt(40 - 8 * i)) & BigInt(0xff))
|
||||
}
|
||||
|
||||
return prefix + "_" + timeBytes.toString("hex") + randomBase62(LENGTH - 12)
|
||||
return prefix + "_" + createIdentifier(direction === "descending", timestamp)
|
||||
}
|
||||
|
||||
/** Extract timestamp from an ascending ID. Does not work with descending IDs. */
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {
|
|||
SynchronizedRef,
|
||||
Types,
|
||||
} from "effect"
|
||||
import { Integration } from "@opencode-ai/schema/integration"
|
||||
import { Credential } from "./credential"
|
||||
import { IntegrationSchema } from "./integration/schema"
|
||||
import { withStatics } from "./schema"
|
||||
|
|
@ -34,66 +35,29 @@ export const AttemptID = Schema.String.pipe(
|
|||
)
|
||||
export type AttemptID = typeof AttemptID.Type
|
||||
|
||||
export const When = Schema.Struct({
|
||||
key: Schema.String,
|
||||
op: Schema.Literals(["eq", "neq"]),
|
||||
value: Schema.String,
|
||||
}).annotate({ identifier: "Integration.When" })
|
||||
export type When = typeof When.Type
|
||||
export const When = Integration.When
|
||||
export type When = Integration.When
|
||||
|
||||
export const TextPrompt = Schema.Struct({
|
||||
type: Schema.Literal("text"),
|
||||
key: Schema.String,
|
||||
message: Schema.String,
|
||||
placeholder: Schema.optional(Schema.String),
|
||||
when: Schema.optional(When),
|
||||
}).annotate({ identifier: "Integration.TextPrompt" })
|
||||
export type TextPrompt = typeof TextPrompt.Type
|
||||
export const TextPrompt = Integration.TextPrompt
|
||||
export type TextPrompt = Integration.TextPrompt
|
||||
|
||||
export const SelectPrompt = Schema.Struct({
|
||||
type: Schema.Literal("select"),
|
||||
key: Schema.String,
|
||||
message: Schema.String,
|
||||
options: Schema.mutable(
|
||||
Schema.Array(
|
||||
Schema.Struct({
|
||||
label: Schema.String,
|
||||
value: Schema.String,
|
||||
hint: Schema.optional(Schema.String),
|
||||
}),
|
||||
),
|
||||
),
|
||||
when: Schema.optional(When),
|
||||
}).annotate({ identifier: "Integration.SelectPrompt" })
|
||||
export type SelectPrompt = typeof SelectPrompt.Type
|
||||
export const SelectPrompt = Integration.SelectPrompt
|
||||
export type SelectPrompt = Integration.SelectPrompt
|
||||
|
||||
export const Prompt = Schema.Union([TextPrompt, SelectPrompt]).pipe(Schema.toTaggedUnion("type"))
|
||||
export type Prompt = typeof Prompt.Type
|
||||
export const Prompt = Integration.Prompt
|
||||
export type Prompt = Integration.Prompt
|
||||
|
||||
export const OAuthMethod = Schema.Struct({
|
||||
id: MethodID,
|
||||
type: Schema.Literal("oauth"),
|
||||
label: Schema.String,
|
||||
prompts: Schema.optional(Schema.mutable(Schema.Array(Prompt))),
|
||||
}).annotate({ identifier: "Integration.OAuthMethod" })
|
||||
export type OAuthMethod = typeof OAuthMethod.Type
|
||||
export const OAuthMethod = Integration.OAuthMethod
|
||||
export type OAuthMethod = Integration.OAuthMethod
|
||||
|
||||
export const KeyMethod = Schema.Struct({
|
||||
type: Schema.Literal("key"),
|
||||
label: Schema.optional(Schema.String),
|
||||
}).annotate({ identifier: "Integration.KeyMethod" })
|
||||
export type KeyMethod = typeof KeyMethod.Type
|
||||
export const KeyMethod = Integration.KeyMethod
|
||||
export type KeyMethod = Integration.KeyMethod
|
||||
|
||||
export const EnvMethod = Schema.Struct({
|
||||
type: Schema.Literal("env"),
|
||||
names: Schema.mutable(Schema.Array(Schema.String)),
|
||||
}).annotate({ identifier: "Integration.EnvMethod" })
|
||||
export type EnvMethod = typeof EnvMethod.Type
|
||||
export const EnvMethod = Integration.EnvMethod
|
||||
export type EnvMethod = Integration.EnvMethod
|
||||
|
||||
export const Method = Schema.Union([OAuthMethod, KeyMethod, EnvMethod])
|
||||
.pipe(Schema.toTaggedUnion("type"))
|
||||
.annotate({ identifier: "Integration.Method" })
|
||||
export type Method = typeof Method.Type
|
||||
export const Method = Integration.Method
|
||||
export type Method = Integration.Method
|
||||
|
||||
export class Info extends Schema.Class<Info>("Integration.Info")({
|
||||
id: ID,
|
||||
|
|
@ -102,8 +66,8 @@ export class Info extends Schema.Class<Info>("Integration.Info")({
|
|||
connections: Schema.mutable(Schema.Array(IntegrationConnection.Info)),
|
||||
}) {}
|
||||
|
||||
export const Inputs = Schema.Record(Schema.String, Schema.String).annotate({ identifier: "Integration.Inputs" })
|
||||
export type Inputs = typeof Inputs.Type
|
||||
export const Inputs = Integration.Inputs
|
||||
export type Inputs = Integration.Inputs
|
||||
|
||||
export type OAuthAuthorization = {
|
||||
readonly url: string
|
||||
|
|
@ -188,11 +152,8 @@ export const Event = {
|
|||
}),
|
||||
}
|
||||
|
||||
export const Ref = Schema.Struct({
|
||||
id: ID,
|
||||
name: Schema.String,
|
||||
}).annotate({ identifier: "Integration.Ref" })
|
||||
export type Ref = typeof Ref.Type
|
||||
export const Ref = Integration.Ref
|
||||
export type Ref = Integration.Ref
|
||||
|
||||
type Entry = {
|
||||
ref: Types.DeepMutable<Ref>
|
||||
|
|
@ -464,7 +425,7 @@ export const locationLayer = Layer.effect(
|
|||
resolve: Effect.fn("Integration.connection.resolve")(function* (connection) {
|
||||
if (connection.type === "env") {
|
||||
const key = process.env[connection.name]
|
||||
return key ? new Credential.Key({ type: "key", key }) : undefined
|
||||
return key ? Credential.Key.make({ type: "key", key }) : undefined
|
||||
}
|
||||
const credential = yield* credentials.get(connection.id)
|
||||
if (!credential) return undefined
|
||||
|
|
@ -489,7 +450,7 @@ export const locationLayer = Layer.effect(
|
|||
yield* credentials.create({
|
||||
integrationID: input.integrationID,
|
||||
label: input.label,
|
||||
value: new Credential.Key({ type: "key", key: input.key }),
|
||||
value: Credential.Key.make({ type: "key", key: input.key }),
|
||||
})
|
||||
yield* events.publish(Event.ConnectionUpdated, { integrationID: input.integrationID })
|
||||
yield* events.publish(Event.Updated, {})
|
||||
|
|
|
|||
|
|
@ -1,22 +1,12 @@
|
|||
export * as IntegrationConnection from "./connection"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { Credential } from "../credential"
|
||||
import { Connection } from "@opencode-ai/schema/connection"
|
||||
|
||||
export const CredentialInfo = Schema.Struct({
|
||||
type: Schema.Literal("credential"),
|
||||
id: Credential.ID,
|
||||
label: Schema.String,
|
||||
}).annotate({ identifier: "Connection.CredentialInfo" })
|
||||
export type CredentialInfo = typeof CredentialInfo.Type
|
||||
export const CredentialInfo = Connection.CredentialInfo
|
||||
export type CredentialInfo = Connection.CredentialInfo
|
||||
|
||||
export const EnvInfo = Schema.Struct({
|
||||
type: Schema.Literal("env"),
|
||||
name: Schema.String,
|
||||
}).annotate({ identifier: "Connection.EnvInfo" })
|
||||
export type EnvInfo = typeof EnvInfo.Type
|
||||
export const EnvInfo = Connection.EnvInfo
|
||||
export type EnvInfo = Connection.EnvInfo
|
||||
|
||||
export const Info = Schema.Union([CredentialInfo, EnvInfo])
|
||||
.pipe(Schema.toTaggedUnion("type"))
|
||||
.annotate({ identifier: "Connection.Info" })
|
||||
export type Info = typeof Info.Type
|
||||
export const Info = Connection.Info
|
||||
export type Info = Connection.Info
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
export * as IntegrationSchema from "./schema"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { Integration } from "@opencode-ai/schema/integration"
|
||||
|
||||
export const ID = Schema.String.pipe(Schema.brand("Integration.ID"))
|
||||
export type ID = typeof ID.Type
|
||||
export const ID = Integration.ID
|
||||
export type ID = Integration.ID
|
||||
|
||||
export const MethodID = Schema.String.pipe(Schema.brand("Integration.MethodID"))
|
||||
export type MethodID = typeof MethodID.Type
|
||||
export const MethodID = Integration.MethodID
|
||||
export type MethodID = Integration.MethodID
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
import { Context, Effect, Layer, Schema } from "effect"
|
||||
import { Ref } from "@opencode-ai/schema/location"
|
||||
import { Project } from "./project"
|
||||
import { AbsolutePath, optionalOmitUndefined } from "./schema"
|
||||
import { WorkspaceV2 } from "./workspace"
|
||||
|
||||
export * as Location from "./location"
|
||||
|
||||
export class Ref extends Schema.Class<Ref>("Location.Ref")({
|
||||
directory: AbsolutePath,
|
||||
workspaceID: Schema.optional(WorkspaceV2.ID).pipe(Schema.withConstructorDefault(Effect.succeed(undefined))),
|
||||
}) {}
|
||||
export { Ref }
|
||||
|
||||
export class Info extends Schema.Class<Info>("Location.Info")({
|
||||
directory: AbsolutePath,
|
||||
|
|
|
|||
|
|
@ -1,34 +1,12 @@
|
|||
export * as ModelRequest from "./model-request"
|
||||
|
||||
import { Effect, Schema } from "effect"
|
||||
import { ModelRequest } from "@opencode-ai/schema/model-request"
|
||||
|
||||
export const Generation = Schema.Struct({
|
||||
maxTokens: Schema.Number.pipe(Schema.optional),
|
||||
temperature: Schema.Number.pipe(Schema.optional),
|
||||
topP: Schema.Number.pipe(Schema.optional),
|
||||
topK: Schema.Number.pipe(Schema.optional),
|
||||
frequencyPenalty: Schema.Number.pipe(Schema.optional),
|
||||
presencePenalty: Schema.Number.pipe(Schema.optional),
|
||||
seed: Schema.Number.pipe(Schema.optional),
|
||||
stop: Schema.String.pipe(Schema.Array, Schema.mutable, Schema.optional),
|
||||
})
|
||||
export type Generation = typeof Generation.Type
|
||||
export const Generation = ModelRequest.Generation
|
||||
export type Generation = ModelRequest.Generation
|
||||
|
||||
export const Request = Schema.Struct({
|
||||
headers: Schema.Record(Schema.String, Schema.String),
|
||||
body: Schema.Record(Schema.String, Schema.Any),
|
||||
generation: Generation.pipe(
|
||||
Schema.optionalKey,
|
||||
Schema.withConstructorDefault(Effect.succeed({})),
|
||||
Schema.withDecodingDefaultKey(Effect.succeed({})),
|
||||
),
|
||||
options: Schema.Record(Schema.String, Schema.Any).pipe(
|
||||
Schema.optionalKey,
|
||||
Schema.withConstructorDefault(Effect.succeed({})),
|
||||
Schema.withDecodingDefaultKey(Effect.succeed({})),
|
||||
),
|
||||
})
|
||||
export type Request = typeof Request.Type
|
||||
export const Request = ModelRequest.Request
|
||||
export type Request = ModelRequest.Request
|
||||
|
||||
interface MutableRequest {
|
||||
headers: Record<string, string>
|
||||
|
|
|
|||
|
|
@ -1,119 +1,30 @@
|
|||
import { Schema, Types } from "effect"
|
||||
import { Types } from "effect"
|
||||
import { Model } from "@opencode-ai/schema/model"
|
||||
import { ProviderV2 } from "./provider"
|
||||
import { ModelRequest } from "./model-request"
|
||||
|
||||
export const ID = Schema.String.pipe(Schema.brand("ModelV2.ID"))
|
||||
export const ID = Model.ID
|
||||
export type ID = typeof ID.Type
|
||||
|
||||
export const VariantID = Schema.String.pipe(Schema.brand("VariantID"))
|
||||
export const VariantID = Model.VariantID
|
||||
export type VariantID = typeof VariantID.Type
|
||||
|
||||
// Grouping of models, eg claude opus, claude sonnet
|
||||
export const Family = Schema.String.pipe(Schema.brand("Family"))
|
||||
export type Family = typeof Family.Type
|
||||
export const Family = Model.Family
|
||||
export type Family = Model.Family
|
||||
|
||||
export const Capabilities = Schema.Struct({
|
||||
tools: Schema.Boolean,
|
||||
// mime patterns, image, audio, video/*, text/*
|
||||
input: Schema.String.pipe(Schema.Array, Schema.mutable),
|
||||
output: Schema.String.pipe(Schema.Array, Schema.mutable),
|
||||
})
|
||||
export type Capabilities = typeof Capabilities.Type
|
||||
export const Capabilities = Model.Capabilities
|
||||
export type Capabilities = Model.Capabilities
|
||||
|
||||
export const Cost = Schema.Struct({
|
||||
tier: Schema.Struct({
|
||||
type: Schema.Literal("context"),
|
||||
size: Schema.Int,
|
||||
}).pipe(Schema.optional),
|
||||
input: Schema.Finite,
|
||||
output: Schema.Finite,
|
||||
cache: Schema.Struct({
|
||||
read: Schema.Finite,
|
||||
write: Schema.Finite,
|
||||
}),
|
||||
})
|
||||
export const Cost = Model.Cost
|
||||
|
||||
export const Ref = Schema.Struct({
|
||||
id: ID,
|
||||
providerID: ProviderV2.ID,
|
||||
variant: VariantID.pipe(Schema.optional),
|
||||
})
|
||||
export const Ref = Model.Ref
|
||||
export type Ref = typeof Ref.Type
|
||||
|
||||
export const Api = Schema.Union([
|
||||
Schema.Struct({
|
||||
id: ID,
|
||||
...ProviderV2.AISDK.fields,
|
||||
}),
|
||||
Schema.Struct({
|
||||
id: ID,
|
||||
...ProviderV2.Native.fields,
|
||||
}),
|
||||
]).pipe(Schema.toTaggedUnion("type"))
|
||||
export type Api = typeof Api.Type
|
||||
export const Api = Model.Api
|
||||
export type Api = Model.Api
|
||||
|
||||
export class Info extends Schema.Class<Info>("ModelV2.Info")({
|
||||
id: ID,
|
||||
providerID: ProviderV2.ID,
|
||||
family: Family.pipe(Schema.optional),
|
||||
name: Schema.String,
|
||||
api: Api,
|
||||
capabilities: Capabilities,
|
||||
request: Schema.Struct({
|
||||
...ModelRequest.Request.fields,
|
||||
variant: Schema.String.pipe(Schema.optional),
|
||||
}),
|
||||
variants: Schema.Struct({
|
||||
id: VariantID,
|
||||
...ModelRequest.Request.fields,
|
||||
}).pipe(Schema.Array, Schema.mutable),
|
||||
time: Schema.Struct({
|
||||
released: Schema.Finite,
|
||||
}),
|
||||
cost: Cost.pipe(Schema.Array, Schema.mutable),
|
||||
status: Schema.Literals(["alpha", "beta", "deprecated", "active"]),
|
||||
enabled: Schema.Boolean,
|
||||
limit: Schema.Struct({
|
||||
context: Schema.Int,
|
||||
input: Schema.Int.pipe(Schema.optional),
|
||||
output: Schema.Int,
|
||||
}),
|
||||
}) {
|
||||
static empty(providerID: ProviderV2.ID, modelID: ID): Info {
|
||||
return new Info({
|
||||
id: modelID,
|
||||
providerID,
|
||||
name: modelID,
|
||||
api: {
|
||||
id: modelID,
|
||||
type: "native",
|
||||
settings: {},
|
||||
},
|
||||
capabilities: {
|
||||
tools: false,
|
||||
input: [],
|
||||
output: [],
|
||||
},
|
||||
request: {
|
||||
headers: {},
|
||||
body: {},
|
||||
generation: {},
|
||||
options: {},
|
||||
},
|
||||
variants: [],
|
||||
time: {
|
||||
released: 0,
|
||||
},
|
||||
cost: [],
|
||||
status: "active",
|
||||
enabled: true,
|
||||
limit: {
|
||||
context: 0,
|
||||
output: 0,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
export const Info = Model.Info
|
||||
export type Info = Model.Info
|
||||
|
||||
export type MutableInfo = Omit<Types.DeepMutable<Info>, "api"> & {
|
||||
api: ProviderV2.MutableApi<Api>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,12 @@
|
|||
export * as PermissionSchema from "./schema"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { Permission } from "@opencode-ai/schema/permission"
|
||||
|
||||
export const Effect = Schema.Literals(["allow", "deny", "ask"]).annotate({ identifier: "PermissionV2.Effect" })
|
||||
export type Effect = typeof Effect.Type
|
||||
export const Effect = Permission.Effect
|
||||
export type Effect = Permission.Effect
|
||||
|
||||
export const Rule = Schema.Struct({
|
||||
action: Schema.String,
|
||||
resource: Schema.String,
|
||||
effect: Effect,
|
||||
}).annotate({ identifier: "PermissionV2.Rule" })
|
||||
export type Rule = typeof Rule.Type
|
||||
export const Rule = Permission.Rule
|
||||
export type Rule = Permission.Rule
|
||||
|
||||
export const Ruleset = Schema.mutable(Schema.Array(Rule)).annotate({ identifier: "PermissionV2.Ruleset" })
|
||||
export type Ruleset = typeof Ruleset.Type
|
||||
export const Ruleset = Permission.Ruleset
|
||||
export type Ruleset = Permission.Ruleset
|
||||
|
|
|
|||
|
|
@ -128,12 +128,11 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
|
|||
return {
|
||||
...authorization,
|
||||
callback: authorization.callback.pipe(
|
||||
Effect.map(
|
||||
(credential) =>
|
||||
new Credential.OAuth({
|
||||
...credential,
|
||||
methodID: Integration.MethodID.make(credential.methodID),
|
||||
}),
|
||||
Effect.map((credential) =>
|
||||
Credential.OAuth.make({
|
||||
...credential,
|
||||
methodID: Integration.MethodID.make(credential.methodID),
|
||||
}),
|
||||
),
|
||||
),
|
||||
}
|
||||
|
|
@ -142,12 +141,11 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
|
|||
...authorization,
|
||||
callback: (code: string) =>
|
||||
authorization.callback(code).pipe(
|
||||
Effect.map(
|
||||
(credential) =>
|
||||
new Credential.OAuth({
|
||||
...credential,
|
||||
methodID: Integration.MethodID.make(credential.methodID),
|
||||
}),
|
||||
Effect.map((credential) =>
|
||||
Credential.OAuth.make({
|
||||
...credential,
|
||||
methodID: Integration.MethodID.make(credential.methodID),
|
||||
}),
|
||||
),
|
||||
),
|
||||
}
|
||||
|
|
@ -157,12 +155,11 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
|
|||
? {
|
||||
refresh: (value: Credential.OAuth) =>
|
||||
refresh(value).pipe(
|
||||
Effect.map(
|
||||
(next) =>
|
||||
new Credential.OAuth({
|
||||
...next,
|
||||
methodID: Integration.MethodID.make(next.methodID),
|
||||
}),
|
||||
Effect.map((next) =>
|
||||
Credential.OAuth.make({
|
||||
...next,
|
||||
methodID: Integration.MethodID.make(next.methodID),
|
||||
}),
|
||||
),
|
||||
),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -213,7 +213,7 @@ function refresh(methodID: Integration.MethodID, value: Pick<Credential.OAuth, "
|
|||
}).pipe(
|
||||
Effect.map((tokens) => {
|
||||
const next = credential(methodID, tokens)
|
||||
return new Credential.OAuth({ ...next, metadata: next.metadata ?? value.metadata })
|
||||
return Credential.OAuth.make({ ...next, metadata: next.metadata ?? value.metadata })
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
|
@ -231,7 +231,7 @@ function request<A>(url: string, init: RequestInit) {
|
|||
|
||||
function credential(methodID: Integration.MethodID, tokens: TokenResponse) {
|
||||
const accountID = extractAccountID(tokens)
|
||||
return new Credential.OAuth({
|
||||
return Credential.OAuth.make({
|
||||
type: "oauth",
|
||||
methodID,
|
||||
refresh: tokens.refresh_token,
|
||||
|
|
|
|||
|
|
@ -274,7 +274,7 @@ function credential(http: HttpClient.HttpClient, server: string, token: typeof T
|
|||
{ concurrency: 2 },
|
||||
)
|
||||
const org = orgs.toSorted((a, b) => a.name.localeCompare(b.name) || a.id.localeCompare(b.id))[0]
|
||||
return new Credential.OAuth({
|
||||
return Credential.OAuth.make({
|
||||
type: "oauth" as const,
|
||||
methodID,
|
||||
access: token.access_token,
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ export const Plugin = define({
|
|||
effect: Effect.fn(function* (ctx) {
|
||||
yield* ctx.skill.transform((draft) => {
|
||||
draft.source(
|
||||
new SkillV2.EmbeddedSource({
|
||||
SkillV2.EmbeddedSource.make({
|
||||
type: "embedded",
|
||||
skill: new SkillV2.Info({
|
||||
skill: SkillV2.Info.make({
|
||||
name: "customize-opencode",
|
||||
description:
|
||||
"Use ONLY when the user is editing or creating opencode's own configuration: opencode.json, opencode.jsonc, files under .opencode/, or files under ~/.config/opencode/. Also use when creating or fixing opencode agents, subagents, commands, skills, plugins, MCP servers, or permission rules. Do not use for the user's own application code, or for any project that is not configuring opencode itself.",
|
||||
|
|
|
|||
|
|
@ -1,14 +1,10 @@
|
|||
export * as ProjectSchema from "./schema"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { AbsolutePath, withStatics } from "../schema"
|
||||
import { Project } from "@opencode-ai/schema/project"
|
||||
import { AbsolutePath } from "../schema"
|
||||
|
||||
export const ID = Schema.String.pipe(
|
||||
Schema.brand("Project.ID"),
|
||||
withStatics((schema) => ({
|
||||
global: schema.make("global"),
|
||||
})),
|
||||
)
|
||||
export const ID = Project.ID
|
||||
export type ID = typeof ID.Type
|
||||
|
||||
export const Vcs = Schema.Union([
|
||||
|
|
|
|||
|
|
@ -1,75 +1,25 @@
|
|||
export * as ProviderV2 from "./provider"
|
||||
|
||||
import { withStatics } from "./schema"
|
||||
import { IntegrationSchema } from "./integration/schema"
|
||||
import { Schema, Types } from "effect"
|
||||
import { Types } from "effect"
|
||||
import { Provider } from "@opencode-ai/schema/provider"
|
||||
|
||||
export const ID = Schema.String.pipe(
|
||||
Schema.brand("ProviderV2.ID"),
|
||||
withStatics((schema) => ({
|
||||
// Well-known providers
|
||||
opencode: schema.make("opencode"),
|
||||
anthropic: schema.make("anthropic"),
|
||||
openai: schema.make("openai"),
|
||||
google: schema.make("google"),
|
||||
googleVertex: schema.make("google-vertex"),
|
||||
githubCopilot: schema.make("github-copilot"),
|
||||
amazonBedrock: schema.make("amazon-bedrock"),
|
||||
azure: schema.make("azure"),
|
||||
openrouter: schema.make("openrouter"),
|
||||
mistral: schema.make("mistral"),
|
||||
gitlab: schema.make("gitlab"),
|
||||
})),
|
||||
)
|
||||
export const ID = Provider.ID
|
||||
export type ID = typeof ID.Type
|
||||
|
||||
export const AISDK = Schema.Struct({
|
||||
type: Schema.Literal("aisdk"),
|
||||
package: Schema.String,
|
||||
url: Schema.String.pipe(Schema.optional),
|
||||
settings: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
|
||||
})
|
||||
export const AISDK = Provider.AISDK
|
||||
|
||||
export const Native = Schema.Struct({
|
||||
type: Schema.Literal("native"),
|
||||
url: Schema.String.pipe(Schema.optional),
|
||||
settings: Schema.Record(Schema.String, Schema.Unknown),
|
||||
})
|
||||
export const Native = Provider.Native
|
||||
|
||||
export const Api = Schema.Union([AISDK, Native]).pipe(Schema.toTaggedUnion("type"))
|
||||
export type Api = typeof Api.Type
|
||||
export const Api = Provider.Api
|
||||
export type Api = Provider.Api
|
||||
export type MutableApi<T extends Api = Api> = T extends Api
|
||||
? Omit<Types.DeepMutable<T>, "settings"> & (undefined extends T["settings"] ? { settings?: any } : { settings: any })
|
||||
: never
|
||||
|
||||
export const Request = Schema.Struct({
|
||||
headers: Schema.Record(Schema.String, Schema.String),
|
||||
body: Schema.Record(Schema.String, Schema.Any),
|
||||
})
|
||||
export type Request = typeof Request.Type
|
||||
export const Request = Provider.Request
|
||||
export type Request = Provider.Request
|
||||
|
||||
export class Info extends Schema.Class<Info>("ProviderV2.Info")({
|
||||
id: ID,
|
||||
integrationID: IntegrationSchema.ID.pipe(Schema.optional),
|
||||
name: Schema.String,
|
||||
disabled: Schema.Boolean.pipe(Schema.optional),
|
||||
api: Api,
|
||||
request: Request,
|
||||
}) {
|
||||
static empty(providerID: ID): Info {
|
||||
return new Info({
|
||||
id: providerID,
|
||||
name: providerID,
|
||||
api: {
|
||||
type: "native",
|
||||
settings: {},
|
||||
},
|
||||
request: {
|
||||
headers: {},
|
||||
body: {},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
export const Info = Provider.Info
|
||||
export type Info = Provider.Info
|
||||
|
||||
export type MutableInfo = Omit<Types.DeepMutable<Info>, "api"> & { api: MutableApi }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
export * as Reference from "./reference"
|
||||
|
||||
import { Context, Effect, Layer, Schema, Scope, Types } from "effect"
|
||||
import { Reference } from "@opencode-ai/schema/reference"
|
||||
import { Global } from "./global"
|
||||
import { EventV2 } from "./event"
|
||||
import { Repository } from "./repository"
|
||||
|
|
@ -8,23 +9,14 @@ import { RepositoryCache } from "./repository-cache"
|
|||
import { AbsolutePath } from "./schema"
|
||||
import { State } from "./state"
|
||||
|
||||
export class LocalSource extends Schema.Class<LocalSource>("Reference.LocalSource")({
|
||||
type: Schema.Literal("local"),
|
||||
path: AbsolutePath,
|
||||
description: Schema.String.pipe(Schema.optional),
|
||||
hidden: Schema.Boolean.pipe(Schema.optional),
|
||||
}) {}
|
||||
export const LocalSource = Reference.LocalSource
|
||||
export type LocalSource = Reference.LocalSource
|
||||
|
||||
export class GitSource extends Schema.Class<GitSource>("Reference.GitSource")({
|
||||
type: Schema.Literal("git"),
|
||||
repository: Schema.String,
|
||||
branch: Schema.String.pipe(Schema.optional),
|
||||
description: Schema.String.pipe(Schema.optional),
|
||||
hidden: Schema.Boolean.pipe(Schema.optional),
|
||||
}) {}
|
||||
export const GitSource = Reference.GitSource
|
||||
export type GitSource = Reference.GitSource
|
||||
|
||||
export const Source = Schema.Union([LocalSource, GitSource]).pipe(Schema.toTaggedUnion("type"))
|
||||
export type Source = typeof Source.Type
|
||||
export const Source = Reference.Source
|
||||
export type Source = Reference.Source
|
||||
|
||||
export const Event = {
|
||||
Updated: EventV2.define({ type: "reference.updated", schema: {} }),
|
||||
|
|
|
|||
|
|
@ -176,12 +176,11 @@ export const layer = Layer.effect(
|
|||
),
|
||||
}).pipe(
|
||||
Effect.map((result) =>
|
||||
result.items.map(
|
||||
(relative) =>
|
||||
new Entry({
|
||||
path: RelativePath.make(relative),
|
||||
type: "file",
|
||||
}),
|
||||
result.items.map((relative) =>
|
||||
Entry.make({
|
||||
path: RelativePath.make(relative),
|
||||
type: "file",
|
||||
}),
|
||||
),
|
||||
),
|
||||
Effect.catchTag("Ripgrep.InvalidPatternError", (cause) => Effect.fail(failure(cause.message, cause))),
|
||||
|
|
@ -206,7 +205,7 @@ export const layer = Layer.effect(
|
|||
.replace(/^[\\/]+/u, "")
|
||||
.replaceAll("\\", "/")
|
||||
return Effect.succeed(
|
||||
new Entry({
|
||||
Entry.make({
|
||||
path: RelativePath.make(relative),
|
||||
type: "file",
|
||||
}),
|
||||
|
|
@ -259,8 +258,8 @@ export const layer = Layer.effect(
|
|||
.replace(/^(?:\.[\\/])+/u, "")
|
||||
.replace(/^[\\/]+/u, "")
|
||||
.replaceAll("\\", "/")
|
||||
return new Match({
|
||||
entry: new Entry({
|
||||
return Match.make({
|
||||
entry: Entry.make({
|
||||
path: RelativePath.make(relative),
|
||||
type: "file",
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -1,48 +1,24 @@
|
|||
import { Option, Schema, SchemaGetter } from "effect"
|
||||
import { Hash } from "./util/hash"
|
||||
import { Schema } from "effect"
|
||||
import {
|
||||
AbsolutePath,
|
||||
DateTimeUtcFromMillis,
|
||||
NonNegativeInt,
|
||||
optionalOmitUndefined,
|
||||
PositiveInt,
|
||||
RelativePath,
|
||||
withStatics,
|
||||
} from "@opencode-ai/schema/schema"
|
||||
|
||||
export type ExternalID = {
|
||||
readonly namespace: string
|
||||
readonly key: string
|
||||
export {
|
||||
AbsolutePath,
|
||||
DateTimeUtcFromMillis,
|
||||
NonNegativeInt,
|
||||
optionalOmitUndefined,
|
||||
PositiveInt,
|
||||
RelativePath,
|
||||
withStatics,
|
||||
}
|
||||
|
||||
export const externalID = (prefix: string, input: ExternalID) =>
|
||||
`${prefix}_${Hash.sha256(JSON.stringify([input.namespace, input.key]))}`
|
||||
|
||||
/**
|
||||
* Integer greater than zero.
|
||||
*/
|
||||
export const PositiveInt = Schema.Int.check(Schema.isGreaterThan(0))
|
||||
|
||||
/**
|
||||
* Integer greater than or equal to zero.
|
||||
*/
|
||||
export const NonNegativeInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))
|
||||
|
||||
/**
|
||||
* Relative file path (e.g., `src/components/Button.tsx`).
|
||||
*/
|
||||
export const RelativePath = Schema.String.pipe(Schema.brand("RelativePath"))
|
||||
export type RelativePath = Schema.Schema.Type<typeof RelativePath>
|
||||
|
||||
/**
|
||||
* Absolute file path (e.g., `/home/user/projects/myapp/src/main.ts`).
|
||||
*/
|
||||
export const AbsolutePath = Schema.String.pipe(Schema.brand("AbsolutePath"))
|
||||
export type AbsolutePath = Schema.Schema.Type<typeof AbsolutePath>
|
||||
|
||||
/**
|
||||
* Optional public JSON field that can hold explicit `undefined` on the type
|
||||
* side but encodes it as an omitted key, matching legacy `JSON.stringify`.
|
||||
*/
|
||||
export const optionalOmitUndefined = <S extends Schema.Top>(schema: S) =>
|
||||
Schema.optionalKey(schema).pipe(
|
||||
Schema.decodeTo(Schema.optional(schema), {
|
||||
decode: SchemaGetter.passthrough({ strict: false }),
|
||||
encode: SchemaGetter.transformOptional(Option.filter((value) => value !== undefined)),
|
||||
}),
|
||||
)
|
||||
|
||||
/**
|
||||
* Strip `readonly` from a nested type. Stand-in for `effect`'s `Types.DeepMutable`
|
||||
* until `effect:core/x228my` ("Types.DeepMutable widens unknown to `{}`") lands.
|
||||
|
|
@ -71,22 +47,6 @@ export type DeepMutable<T> = T extends string | number | boolean | bigint | symb
|
|||
? { -readonly [K in keyof T]: DeepMutable<T[K]> }
|
||||
: T
|
||||
|
||||
/**
|
||||
* Attach static methods to a schema object. Designed to be used with `.pipe()`:
|
||||
*
|
||||
* @example
|
||||
* export const Foo = fooSchema.pipe(
|
||||
* withStatics((schema) => ({
|
||||
* zero: schema.make(0),
|
||||
* from: Schema.decodeUnknownOption(schema),
|
||||
* }))
|
||||
* )
|
||||
*/
|
||||
export const withStatics =
|
||||
<S extends object, M extends Record<string, unknown>>(methods: (schema: S) => M) =>
|
||||
(schema: S): S & M =>
|
||||
Object.assign(schema, methods(schema))
|
||||
|
||||
/**
|
||||
* Nominal wrapper for scalar types. The class itself is a valid schema —
|
||||
* pass it directly to `Schema.decode`, `Schema.decodeEffect`, etc.
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ export * as SessionV2 from "./session"
|
|||
export * from "./session/schema"
|
||||
|
||||
import { DateTime, Effect, Layer, Schema, Context, Stream } from "effect"
|
||||
import { ListAnchor } from "@opencode-ai/schema/session"
|
||||
import { and, asc, desc, eq, gt, like, lt, or, type SQL } from "drizzle-orm"
|
||||
import { ProjectV2 } from "./project"
|
||||
import { WorkspaceV2 } from "./workspace"
|
||||
|
|
@ -38,12 +39,7 @@ import { SessionInput } from "./session/input"
|
|||
// - by subpath
|
||||
// - by workspace (home is special)
|
||||
|
||||
export const ListAnchor = Schema.Struct({
|
||||
id: SessionSchema.ID,
|
||||
time: Schema.Finite,
|
||||
direction: Schema.Literals(["previous", "next"]),
|
||||
})
|
||||
export type ListAnchor = typeof ListAnchor.Type
|
||||
export { ListAnchor }
|
||||
|
||||
const ListInputBase = {
|
||||
workspaceID: WorkspaceV2.ID.pipe(Schema.optional),
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { Schema } from "effect"
|
||||
import { ProviderMetadata, ToolContent } from "@opencode-ai/llm"
|
||||
import { ProviderMetadata, ToolContent } from "@opencode-ai/schema/llm"
|
||||
import { Delivery } from "@opencode-ai/schema/session-delivery"
|
||||
import { EventV2 } from "../event"
|
||||
import { ModelV2 } from "../model"
|
||||
import { NonNegativeInt } from "../schema"
|
||||
import { V2Schema } from "../v2-schema"
|
||||
import { DateTimeUtcFromMillis, NonNegativeInt, RelativePath } from "../schema"
|
||||
import { FileAttachment, Prompt } from "./prompt"
|
||||
import { SessionSchema } from "./schema"
|
||||
import { Location } from "../location"
|
||||
import { RelativePath } from "../schema"
|
||||
import { SessionMessageID } from "./message-id"
|
||||
import { SessionMessage } from "./message"
|
||||
|
||||
export { FileAttachment }
|
||||
|
||||
|
|
@ -22,14 +22,14 @@ export const Source = Schema.Struct({
|
|||
export type Source = typeof Source.Type
|
||||
|
||||
const Base = {
|
||||
timestamp: V2Schema.DateTimeUtcFromMillis,
|
||||
timestamp: DateTimeUtcFromMillis,
|
||||
sessionID: SessionSchema.ID,
|
||||
}
|
||||
const PromptFields = {
|
||||
...Base,
|
||||
messageID: SessionMessageID.ID,
|
||||
prompt: Prompt,
|
||||
delivery: Schema.Literals(["steer", "queue"]),
|
||||
delivery: Delivery,
|
||||
}
|
||||
|
||||
const options = {
|
||||
|
|
@ -45,13 +45,8 @@ const stepSettlementOptions = {
|
|||
},
|
||||
} as const
|
||||
|
||||
export const UnknownError = Schema.Struct({
|
||||
type: Schema.Literal("unknown"),
|
||||
message: Schema.String,
|
||||
}).annotate({
|
||||
identifier: "Session.Error.Unknown",
|
||||
})
|
||||
export type UnknownError = typeof UnknownError.Type
|
||||
export const UnknownError = SessionMessage.UnknownError
|
||||
export type UnknownError = SessionMessage.UnknownError
|
||||
|
||||
export const AgentSwitched = EventV2.define({
|
||||
type: "session.next.agent.switched",
|
||||
|
|
|
|||
|
|
@ -2,10 +2,9 @@ export * as SessionInput from "./input"
|
|||
|
||||
import { and, asc, eq, isNull, lte } from "drizzle-orm"
|
||||
import { DateTime, Effect, Schema } from "effect"
|
||||
import { Admitted, Delivery } from "@opencode-ai/schema/session-input"
|
||||
import type { Database } from "../database/database"
|
||||
import type { EventV2 } from "../event"
|
||||
import { NonNegativeInt } from "../schema"
|
||||
import { V2Schema } from "../v2-schema"
|
||||
import { SessionEvent } from "./event"
|
||||
import { SessionMessage } from "./message"
|
||||
import { Prompt } from "./prompt"
|
||||
|
|
@ -14,24 +13,13 @@ import { SessionInputTable, SessionMessageTable } from "./sql"
|
|||
|
||||
type DatabaseService = Database.Interface["db"]
|
||||
|
||||
export const Delivery = Schema.Literals(["steer", "queue"])
|
||||
export type Delivery = typeof Delivery.Type
|
||||
|
||||
export class Admitted extends Schema.Class<Admitted>("SessionInput.Admitted")({
|
||||
admittedSeq: NonNegativeInt,
|
||||
id: SessionMessage.ID,
|
||||
sessionID: SessionSchema.ID,
|
||||
prompt: Prompt,
|
||||
delivery: Delivery,
|
||||
timeCreated: V2Schema.DateTimeUtcFromMillis,
|
||||
promotedSeq: NonNegativeInt.pipe(Schema.optional),
|
||||
}) {}
|
||||
export { Admitted, Delivery }
|
||||
|
||||
const decodePrompt = Schema.decodeUnknownSync(Prompt)
|
||||
const encodePrompt = Schema.encodeSync(Prompt)
|
||||
|
||||
const fromRow = (row: typeof SessionInputTable.$inferSelect): Admitted =>
|
||||
new Admitted({
|
||||
Admitted.make({
|
||||
admittedSeq: row.admitted_seq,
|
||||
id: SessionMessage.ID.make(row.id),
|
||||
sessionID: SessionSchema.ID.make(row.session_id),
|
||||
|
|
@ -76,7 +64,7 @@ export const admit = Effect.fn("SessionInput.admit")(function* (
|
|||
event.durable === undefined
|
||||
? Effect.die("Prompt admission event is missing aggregate sequence")
|
||||
: Effect.succeed(
|
||||
new Admitted({
|
||||
Admitted.make({
|
||||
admittedSeq: event.durable.seq,
|
||||
id: input.id,
|
||||
sessionID: input.sessionID,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,2 @@
|
|||
export * as SessionMessageID from "./message-id"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { withStatics } from "../schema"
|
||||
import { Identifier } from "../util/identifier"
|
||||
|
||||
export const ID = Schema.String.check(Schema.isStartsWith("msg_")).pipe(
|
||||
Schema.brand("Session.Message.ID"),
|
||||
withStatics((schema) => ({
|
||||
create: () => schema.make("msg_" + Identifier.ascending()),
|
||||
})),
|
||||
)
|
||||
export type ID = typeof ID.Type
|
||||
export { ID } from "@opencode-ai/schema/session-message-id"
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||
yield* SessionEvent.All.match(event, {
|
||||
"session.next.agent.switched": (event) => {
|
||||
return adapter.appendMessage(
|
||||
new SessionMessage.AgentSwitched({
|
||||
SessionMessage.AgentSwitched.make({
|
||||
id: event.data.messageID,
|
||||
type: "agent-switched",
|
||||
metadata: event.metadata,
|
||||
|
|
@ -113,7 +113,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||
},
|
||||
"session.next.model.switched": (event) => {
|
||||
return adapter.appendMessage(
|
||||
new SessionMessage.ModelSwitched({
|
||||
SessionMessage.ModelSwitched.make({
|
||||
id: event.data.messageID,
|
||||
type: "model-switched",
|
||||
metadata: event.metadata,
|
||||
|
|
@ -125,7 +125,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||
"session.next.moved": () => Effect.void,
|
||||
"session.next.prompted": (event) => {
|
||||
return adapter.appendMessage(
|
||||
new SessionMessage.User({
|
||||
SessionMessage.User.make({
|
||||
id: event.data.messageID,
|
||||
type: "user",
|
||||
metadata: event.metadata,
|
||||
|
|
@ -139,7 +139,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||
"session.next.prompt.admitted": () => Effect.void,
|
||||
"session.next.context.updated": (event) =>
|
||||
adapter.appendMessage(
|
||||
new SessionMessage.System({
|
||||
SessionMessage.System.make({
|
||||
id: event.data.messageID,
|
||||
type: "system",
|
||||
text: event.data.text,
|
||||
|
|
@ -148,7 +148,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||
),
|
||||
"session.next.synthetic": (event) => {
|
||||
return adapter.appendMessage(
|
||||
new SessionMessage.Synthetic({
|
||||
SessionMessage.Synthetic.make({
|
||||
sessionID: event.data.sessionID,
|
||||
text: event.data.text,
|
||||
id: event.data.messageID,
|
||||
|
|
@ -159,7 +159,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||
},
|
||||
"session.next.shell.started": (event) => {
|
||||
return adapter.appendMessage(
|
||||
new SessionMessage.Shell({
|
||||
SessionMessage.Shell.make({
|
||||
id: event.data.messageID,
|
||||
type: "shell",
|
||||
metadata: event.metadata,
|
||||
|
|
@ -194,7 +194,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||
)
|
||||
}
|
||||
yield* adapter.appendMessage(
|
||||
new SessionMessage.Assistant({
|
||||
SessionMessage.Assistant.make({
|
||||
id: event.data.assistantMessageID,
|
||||
type: "assistant",
|
||||
agent: event.data.agent,
|
||||
|
|
@ -225,7 +225,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||
"session.next.text.started": (event) => {
|
||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
draft.content.push(
|
||||
castDraft(new SessionMessage.AssistantText({ type: "text", id: event.data.textID, text: "" })),
|
||||
castDraft(SessionMessage.AssistantText.make({ type: "text", id: event.data.textID, text: "" })),
|
||||
)
|
||||
})
|
||||
},
|
||||
|
|
@ -245,12 +245,12 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
draft.content.push(
|
||||
castDraft(
|
||||
new SessionMessage.AssistantTool({
|
||||
SessionMessage.AssistantTool.make({
|
||||
type: "tool",
|
||||
id: event.data.callID,
|
||||
name: event.data.name,
|
||||
time: { created: event.data.timestamp },
|
||||
state: new SessionMessage.ToolStatePending({ status: "pending", input: "" }),
|
||||
state: SessionMessage.ToolStatePending.make({ status: "pending", input: "" }),
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
|
@ -270,7 +270,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||
match.provider = event.data.provider
|
||||
match.time.ran = event.data.timestamp
|
||||
match.state = castDraft(
|
||||
new SessionMessage.ToolStateRunning({
|
||||
SessionMessage.ToolStateRunning.make({
|
||||
status: "running",
|
||||
input: event.data.input,
|
||||
structured: {},
|
||||
|
|
@ -300,7 +300,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||
}
|
||||
match.time.completed = event.data.timestamp
|
||||
match.state = castDraft(
|
||||
new SessionMessage.ToolStateCompleted({
|
||||
SessionMessage.ToolStateCompleted.make({
|
||||
status: "completed",
|
||||
input: match.state.input,
|
||||
structured: event.data.structured,
|
||||
|
|
@ -323,7 +323,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||
}
|
||||
match.time.completed = event.data.timestamp
|
||||
match.state = castDraft(
|
||||
new SessionMessage.ToolStateError({
|
||||
SessionMessage.ToolStateError.make({
|
||||
status: "error",
|
||||
error: event.data.error,
|
||||
input: typeof match.state.input === "string" ? {} : match.state.input,
|
||||
|
|
@ -339,7 +339,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
draft.content.push(
|
||||
castDraft(
|
||||
new SessionMessage.AssistantReasoning({
|
||||
SessionMessage.AssistantReasoning.make({
|
||||
type: "reasoning",
|
||||
id: event.data.reasoningID,
|
||||
text: "",
|
||||
|
|
@ -369,7 +369,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
|||
"session.next.compaction.delta": () => Effect.void,
|
||||
"session.next.compaction.ended": (event) => {
|
||||
return adapter.appendMessage(
|
||||
new SessionMessage.Compaction({
|
||||
SessionMessage.Compaction.make({
|
||||
id: event.data.messageID,
|
||||
type: "compaction",
|
||||
metadata: event.metadata,
|
||||
|
|
|
|||
|
|
@ -1,193 +1,2 @@
|
|||
export * as SessionMessage from "./message"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { ProviderMetadata, ToolContent } from "@opencode-ai/llm"
|
||||
import { ModelV2 } from "../model"
|
||||
import { V2Schema } from "../v2-schema"
|
||||
import { SessionEvent } from "./event"
|
||||
import { Prompt } from "./prompt"
|
||||
import { SessionMessageID } from "./message-id"
|
||||
|
||||
export const ID = SessionMessageID.ID
|
||||
export type ID = typeof ID.Type
|
||||
|
||||
const Base = {
|
||||
id: ID,
|
||||
metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
|
||||
time: Schema.Struct({
|
||||
created: V2Schema.DateTimeUtcFromMillis,
|
||||
}),
|
||||
}
|
||||
|
||||
export class AgentSwitched extends Schema.Class<AgentSwitched>("Session.Message.AgentSwitched")({
|
||||
...Base,
|
||||
type: Schema.Literal("agent-switched"),
|
||||
agent: SessionEvent.AgentSwitched.data.fields.agent,
|
||||
}) {}
|
||||
|
||||
export class ModelSwitched extends Schema.Class<ModelSwitched>("Session.Message.ModelSwitched")({
|
||||
...Base,
|
||||
type: Schema.Literal("model-switched"),
|
||||
model: ModelV2.Ref,
|
||||
}) {}
|
||||
|
||||
export class User extends Schema.Class<User>("Session.Message.User")({
|
||||
...Base,
|
||||
text: Prompt.fields.text,
|
||||
files: Prompt.fields.files,
|
||||
agents: Prompt.fields.agents,
|
||||
type: Schema.Literal("user"),
|
||||
time: Schema.Struct({
|
||||
created: V2Schema.DateTimeUtcFromMillis,
|
||||
}),
|
||||
}) {}
|
||||
|
||||
export class Synthetic extends Schema.Class<Synthetic>("Session.Message.Synthetic")({
|
||||
...Base,
|
||||
sessionID: SessionEvent.Synthetic.data.fields.sessionID,
|
||||
text: SessionEvent.Synthetic.data.fields.text,
|
||||
type: Schema.Literal("synthetic"),
|
||||
}) {}
|
||||
|
||||
export class System extends Schema.Class<System>("Session.Message.System")({
|
||||
...Base,
|
||||
type: Schema.Literal("system"),
|
||||
text: SessionEvent.ContextUpdated.data.fields.text,
|
||||
}) {}
|
||||
|
||||
export class Shell extends Schema.Class<Shell>("Session.Message.Shell")({
|
||||
...Base,
|
||||
type: Schema.Literal("shell"),
|
||||
callID: SessionEvent.Shell.Started.data.fields.callID,
|
||||
command: SessionEvent.Shell.Started.data.fields.command,
|
||||
output: Schema.String,
|
||||
time: Schema.Struct({
|
||||
created: V2Schema.DateTimeUtcFromMillis,
|
||||
completed: V2Schema.DateTimeUtcFromMillis.pipe(Schema.optional),
|
||||
}),
|
||||
}) {}
|
||||
|
||||
export class ToolStatePending extends Schema.Class<ToolStatePending>("Session.Message.ToolState.Pending")({
|
||||
status: Schema.Literal("pending"),
|
||||
input: Schema.String,
|
||||
}) {}
|
||||
|
||||
export class ToolStateRunning extends Schema.Class<ToolStateRunning>("Session.Message.ToolState.Running")({
|
||||
status: Schema.Literal("running"),
|
||||
input: Schema.Record(Schema.String, Schema.Unknown),
|
||||
structured: Schema.Record(Schema.String, Schema.Any),
|
||||
content: ToolContent.pipe(Schema.Array),
|
||||
}) {}
|
||||
|
||||
export class ToolStateCompleted extends Schema.Class<ToolStateCompleted>("Session.Message.ToolState.Completed")({
|
||||
status: Schema.Literal("completed"),
|
||||
input: Schema.Record(Schema.String, Schema.Unknown),
|
||||
attachments: SessionEvent.FileAttachment.pipe(Schema.Array, Schema.optional),
|
||||
content: ToolContent.pipe(Schema.Array),
|
||||
outputPaths: SessionEvent.Tool.Success.data.fields.outputPaths,
|
||||
structured: Schema.Record(Schema.String, Schema.Any),
|
||||
result: SessionEvent.Tool.Success.data.fields.result,
|
||||
}) {}
|
||||
|
||||
export class ToolStateError extends Schema.Class<ToolStateError>("Session.Message.ToolState.Error")({
|
||||
status: Schema.Literal("error"),
|
||||
input: Schema.Record(Schema.String, Schema.Unknown),
|
||||
content: ToolContent.pipe(Schema.Array),
|
||||
structured: Schema.Record(Schema.String, Schema.Any),
|
||||
error: SessionEvent.UnknownError,
|
||||
result: SessionEvent.Tool.Failed.data.fields.result,
|
||||
}) {}
|
||||
|
||||
export const ToolState = Schema.Union([ToolStatePending, ToolStateRunning, ToolStateCompleted, ToolStateError]).pipe(
|
||||
Schema.toTaggedUnion("status"),
|
||||
)
|
||||
export type ToolState = Schema.Schema.Type<typeof ToolState>
|
||||
|
||||
export class AssistantTool extends Schema.Class<AssistantTool>("Session.Message.Assistant.Tool")({
|
||||
type: Schema.Literal("tool"),
|
||||
id: Schema.String,
|
||||
name: Schema.String,
|
||||
provider: Schema.Struct({
|
||||
executed: Schema.Boolean,
|
||||
metadata: ProviderMetadata.pipe(Schema.optional),
|
||||
resultMetadata: ProviderMetadata.pipe(Schema.optional),
|
||||
}).pipe(Schema.optional),
|
||||
state: ToolState,
|
||||
time: Schema.Struct({
|
||||
created: V2Schema.DateTimeUtcFromMillis,
|
||||
ran: V2Schema.DateTimeUtcFromMillis.pipe(Schema.optional),
|
||||
completed: V2Schema.DateTimeUtcFromMillis.pipe(Schema.optional),
|
||||
pruned: V2Schema.DateTimeUtcFromMillis.pipe(Schema.optional),
|
||||
}),
|
||||
}) {}
|
||||
|
||||
export class AssistantText extends Schema.Class<AssistantText>("Session.Message.Assistant.Text")({
|
||||
type: Schema.Literal("text"),
|
||||
id: Schema.String,
|
||||
text: Schema.String,
|
||||
}) {}
|
||||
|
||||
export class AssistantReasoning extends Schema.Class<AssistantReasoning>("Session.Message.Assistant.Reasoning")({
|
||||
type: Schema.Literal("reasoning"),
|
||||
id: Schema.String,
|
||||
text: Schema.String,
|
||||
providerMetadata: ProviderMetadata.pipe(Schema.optional),
|
||||
}) {}
|
||||
|
||||
export const AssistantContent = Schema.Union([AssistantText, AssistantReasoning, AssistantTool]).pipe(
|
||||
Schema.toTaggedUnion("type"),
|
||||
)
|
||||
export type AssistantContent = Schema.Schema.Type<typeof AssistantContent>
|
||||
|
||||
export class Assistant extends Schema.Class<Assistant>("Session.Message.Assistant")({
|
||||
...Base,
|
||||
type: Schema.Literal("assistant"),
|
||||
agent: Schema.String,
|
||||
model: SessionEvent.Step.Started.data.fields.model,
|
||||
content: AssistantContent.pipe(Schema.Array),
|
||||
snapshot: Schema.Struct({
|
||||
start: Schema.String.pipe(Schema.optional),
|
||||
end: Schema.String.pipe(Schema.optional),
|
||||
}).pipe(Schema.optional),
|
||||
finish: Schema.String.pipe(Schema.optional),
|
||||
cost: Schema.Finite.pipe(Schema.optional),
|
||||
tokens: Schema.Struct({
|
||||
input: Schema.Finite,
|
||||
output: Schema.Finite,
|
||||
reasoning: Schema.Finite,
|
||||
cache: Schema.Struct({
|
||||
read: Schema.Finite,
|
||||
write: Schema.Finite,
|
||||
}),
|
||||
}).pipe(Schema.optional),
|
||||
error: SessionEvent.Step.Failed.data.fields.error.pipe(Schema.optional),
|
||||
time: Schema.Struct({
|
||||
created: V2Schema.DateTimeUtcFromMillis,
|
||||
completed: V2Schema.DateTimeUtcFromMillis.pipe(Schema.optional),
|
||||
}),
|
||||
}) {}
|
||||
|
||||
export class Compaction extends Schema.Class<Compaction>("Session.Message.Compaction")({
|
||||
type: Schema.Literal("compaction"),
|
||||
reason: SessionEvent.Compaction.Started.data.fields.reason,
|
||||
summary: Schema.String,
|
||||
recent: Schema.String,
|
||||
...Base,
|
||||
}) {}
|
||||
|
||||
export const Message = Schema.Union([
|
||||
AgentSwitched,
|
||||
ModelSwitched,
|
||||
User,
|
||||
Synthetic,
|
||||
System,
|
||||
Shell,
|
||||
Assistant,
|
||||
Compaction,
|
||||
])
|
||||
.pipe(Schema.toTaggedUnion("type"))
|
||||
.annotate({ identifier: "Session.Message" })
|
||||
|
||||
export type Message = Schema.Schema.Type<typeof Message>
|
||||
|
||||
export type Type = Message["type"]
|
||||
export * from "@opencode-ai/schema/session-message"
|
||||
|
|
|
|||
|
|
@ -1,46 +1 @@
|
|||
import * as Schema from "effect/Schema"
|
||||
|
||||
export class Source extends Schema.Class<Source>("Prompt.Source")({
|
||||
start: Schema.Finite,
|
||||
end: Schema.Finite,
|
||||
text: Schema.String,
|
||||
}) {}
|
||||
|
||||
export class FileAttachment extends Schema.Class<FileAttachment>("Prompt.FileAttachment")({
|
||||
uri: Schema.String,
|
||||
mime: Schema.String,
|
||||
name: Schema.String.pipe(Schema.optional),
|
||||
description: Schema.String.pipe(Schema.optional),
|
||||
source: Source.pipe(Schema.optional),
|
||||
}) {
|
||||
static create(input: FileAttachment) {
|
||||
return new FileAttachment({
|
||||
uri: input.uri,
|
||||
mime: input.mime,
|
||||
name: input.name,
|
||||
description: input.description,
|
||||
source: input.source,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class AgentAttachment extends Schema.Class<AgentAttachment>("Prompt.AgentAttachment")({
|
||||
name: Schema.String,
|
||||
source: Source.pipe(Schema.optional),
|
||||
}) {}
|
||||
|
||||
export class Prompt extends Schema.Class<Prompt>("Prompt")({
|
||||
text: Schema.String,
|
||||
files: Schema.Array(FileAttachment).pipe(Schema.optional),
|
||||
agents: Schema.Array(AgentAttachment).pipe(Schema.optional),
|
||||
}) {
|
||||
static readonly equivalence = Schema.toEquivalence(Prompt)
|
||||
|
||||
static fromUserMessage(input: Pick<Prompt, "text" | "files" | "agents">) {
|
||||
return new Prompt({
|
||||
text: input.text,
|
||||
...(input.files === undefined ? {} : { files: input.files }),
|
||||
...(input.agents === undefined ? {} : { agents: input.agents }),
|
||||
})
|
||||
}
|
||||
}
|
||||
export { AgentAttachment, FileAttachment, Prompt, Source } from "@opencode-ai/schema/prompt"
|
||||
|
|
|
|||
|
|
@ -1,49 +1,9 @@
|
|||
export * as SessionSchema from "./schema"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { Location } from "../location"
|
||||
import { ModelV2 } from "../model"
|
||||
import { ProjectV2 } from "../project"
|
||||
import { externalID, type ExternalID, RelativePath, optionalOmitUndefined, withStatics } from "../schema"
|
||||
import { Identifier } from "../util/identifier"
|
||||
import { V2Schema } from "../v2-schema"
|
||||
import { AgentV2 } from "../agent"
|
||||
import { Session } from "@opencode-ai/schema/session"
|
||||
|
||||
export const ID = Schema.String.check(Schema.isStartsWith("ses")).pipe(
|
||||
Schema.brand("SessionID"),
|
||||
withStatics((schema) => {
|
||||
const create = () => schema.make("ses_" + Identifier.descending())
|
||||
return {
|
||||
create,
|
||||
descending: (id?: string) => (id === undefined ? create() : schema.make(id)),
|
||||
fromExternal: (input: ExternalID) => schema.make(externalID("ses", input)),
|
||||
}
|
||||
}),
|
||||
)
|
||||
export const ID = Session.ID
|
||||
export type ID = typeof ID.Type
|
||||
|
||||
export class Info extends Schema.Class<Info>("SessionV2.Info")({
|
||||
id: ID,
|
||||
parentID: ID.pipe(optionalOmitUndefined),
|
||||
projectID: ProjectV2.ID,
|
||||
agent: AgentV2.ID.pipe(Schema.optional),
|
||||
model: ModelV2.Ref.pipe(Schema.optional),
|
||||
cost: Schema.Finite,
|
||||
tokens: Schema.Struct({
|
||||
input: Schema.Finite,
|
||||
output: Schema.Finite,
|
||||
reasoning: Schema.Finite,
|
||||
cache: Schema.Struct({
|
||||
read: Schema.Finite,
|
||||
write: Schema.Finite,
|
||||
}),
|
||||
}),
|
||||
time: Schema.Struct({
|
||||
created: V2Schema.DateTimeUtcFromMillis,
|
||||
updated: V2Schema.DateTimeUtcFromMillis,
|
||||
archived: V2Schema.DateTimeUtcFromMillis.pipe(Schema.optional),
|
||||
}),
|
||||
title: Schema.String,
|
||||
location: Location.Ref,
|
||||
subpath: RelativePath.pipe(Schema.optional),
|
||||
}) {}
|
||||
export const Info = Session.Info
|
||||
export type Info = Session.Info
|
||||
|
|
|
|||
|
|
@ -2,57 +2,29 @@ export * as SkillV2 from "./skill"
|
|||
|
||||
import path from "path"
|
||||
import { Context, Effect, Layer, Schema, Types } from "effect"
|
||||
import { Skill } from "@opencode-ai/schema/skill"
|
||||
import { AgentV2 } from "./agent"
|
||||
import { ConfigMarkdown } from "./config/markdown"
|
||||
import { FSUtil } from "./fs-util"
|
||||
import { PermissionV2 } from "./permission"
|
||||
import { AbsolutePath, withStatics } from "./schema"
|
||||
import { AbsolutePath } from "./schema"
|
||||
import { SkillDiscovery } from "./skill/discovery"
|
||||
import { State } from "./state"
|
||||
|
||||
export class DirectorySource extends Schema.Class<DirectorySource>("SkillV2.DirectorySource")({
|
||||
type: Schema.Literal("directory"),
|
||||
path: AbsolutePath,
|
||||
}) {}
|
||||
export const DirectorySource = Skill.DirectorySource
|
||||
export type DirectorySource = Skill.DirectorySource
|
||||
|
||||
export class UrlSource extends Schema.Class<UrlSource>("SkillV2.UrlSource")({
|
||||
type: Schema.Literal("url"),
|
||||
url: Schema.String,
|
||||
}) {}
|
||||
export const UrlSource = Skill.UrlSource
|
||||
export type UrlSource = Skill.UrlSource
|
||||
|
||||
export class EmbeddedSource extends Schema.Class<EmbeddedSource>("SkillV2.EmbeddedSource")({
|
||||
type: Schema.Literal("embedded"),
|
||||
skill: Schema.suspend(() => Info),
|
||||
}) {}
|
||||
export const EmbeddedSource = Skill.EmbeddedSource
|
||||
export type EmbeddedSource = Skill.EmbeddedSource
|
||||
|
||||
export const Source = Schema.Union([DirectorySource, UrlSource, EmbeddedSource]).pipe(
|
||||
Schema.toTaggedUnion("type"),
|
||||
Schema.annotate({ identifier: "SkillV2.Source" }),
|
||||
withStatics(() => ({
|
||||
equals: (a: DirectorySource | UrlSource | EmbeddedSource, b: DirectorySource | UrlSource | EmbeddedSource) => {
|
||||
if (a.type !== b.type) return false
|
||||
if (a.type === "directory" && b.type === "directory") return a.path === b.path
|
||||
if (a.type === "url" && b.type === "url") return a.url === b.url
|
||||
if (a.type === "embedded" && b.type === "embedded") return a.skill.name === b.skill.name
|
||||
return false
|
||||
},
|
||||
key: (source: DirectorySource | UrlSource | EmbeddedSource) =>
|
||||
source.type === "directory"
|
||||
? `directory:${source.path}`
|
||||
: source.type === "url"
|
||||
? `url:${source.url}`
|
||||
: `embedded:${source.skill.name}`,
|
||||
})),
|
||||
)
|
||||
export const Source = Skill.Source
|
||||
export type Source = typeof Source.Type
|
||||
|
||||
export class Info extends Schema.Class<Info>("SkillV2.Info")({
|
||||
name: Schema.String,
|
||||
description: Schema.String.pipe(Schema.optional),
|
||||
slash: Schema.Boolean.pipe(Schema.optional),
|
||||
location: AbsolutePath,
|
||||
content: Schema.String,
|
||||
}) {}
|
||||
export const Info = Skill.Info
|
||||
export type Info = Skill.Info
|
||||
|
||||
export const available = (skills: ReadonlyArray<Info>, agent: AgentV2.Info) =>
|
||||
skills.filter((skill) => PermissionV2.evaluate("skill", skill.name, agent.permissions).effect !== "deny")
|
||||
|
|
@ -119,15 +91,13 @@ export const layer = Layer.effect(
|
|||
? path.basename(filepath, ".md")
|
||||
: undefined
|
||||
if (!name) continue
|
||||
skills.push(
|
||||
new Info({
|
||||
name,
|
||||
description: frontmatter.description,
|
||||
slash: frontmatter.slash,
|
||||
location: AbsolutePath.make(filepath),
|
||||
content: markdown.content,
|
||||
}),
|
||||
)
|
||||
skills.push({
|
||||
name,
|
||||
description: frontmatter.description,
|
||||
slash: frontmatter.slash,
|
||||
location: AbsolutePath.make(filepath),
|
||||
content: markdown.content,
|
||||
})
|
||||
}
|
||||
}
|
||||
return skills
|
||||
|
|
|
|||
|
|
@ -79,12 +79,11 @@ export const layer = Layer.effectDiscard(
|
|||
})
|
||||
.pipe(
|
||||
Effect.map((result) =>
|
||||
result.map(
|
||||
(entry) =>
|
||||
new FileSystem.Entry({
|
||||
...entry,
|
||||
path: RelativePath.make(path.relative(location.directory, path.resolve(cwd, entry.path))),
|
||||
}),
|
||||
result.map((entry) =>
|
||||
FileSystem.Entry.make({
|
||||
...entry,
|
||||
path: RelativePath.make(path.relative(location.directory, path.resolve(cwd, entry.path))),
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -102,23 +102,22 @@ export const layer = Layer.effectDiscard(
|
|||
})
|
||||
.pipe(
|
||||
Effect.map((result) =>
|
||||
result.map(
|
||||
(match) =>
|
||||
new FileSystem.Match({
|
||||
...match,
|
||||
entry: new FileSystem.Entry({
|
||||
...match.entry,
|
||||
path: RelativePath.make(
|
||||
path.relative(
|
||||
location.directory,
|
||||
path.resolve(
|
||||
info?.type === "Directory" ? target : path.dirname(target),
|
||||
match.entry.path,
|
||||
),
|
||||
result.map((match) =>
|
||||
FileSystem.Match.make({
|
||||
...match,
|
||||
entry: FileSystem.Entry.make({
|
||||
...match.entry,
|
||||
path: RelativePath.make(
|
||||
path.relative(
|
||||
location.directory,
|
||||
path.resolve(
|
||||
info?.type === "Directory" ? target : path.dirname(target),
|
||||
match.entry.path,
|
||||
),
|
||||
),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -335,7 +335,7 @@ export const list = Effect.fn("ReadTool.list")(function* (fs: FSUtil.Interface,
|
|||
const info = yield* fs.stat(target).pipe(Effect.catch(() => Effect.void))
|
||||
const type = info?.type === "Directory" ? "directory" : info?.type === "File" ? "file" : undefined
|
||||
if (!type) return
|
||||
return new FileSystem.Entry({
|
||||
return FileSystem.Entry.make({
|
||||
path: RelativePath.make(item.name + (type === "directory" ? path.sep : "")),
|
||||
type,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,48 +1 @@
|
|||
import { randomBytes } from "crypto"
|
||||
|
||||
export namespace Identifier {
|
||||
const LENGTH = 26
|
||||
|
||||
// State for monotonic ID generation
|
||||
let lastTimestamp = 0
|
||||
let counter = 0
|
||||
|
||||
export function ascending() {
|
||||
return create(false)
|
||||
}
|
||||
|
||||
export function descending() {
|
||||
return create(true)
|
||||
}
|
||||
|
||||
function randomBase62(length: number): string {
|
||||
const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
let result = ""
|
||||
const bytes = randomBytes(length)
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars[bytes[i] % 62]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export function create(descending: boolean, timestamp?: number): string {
|
||||
const currentTimestamp = timestamp ?? Date.now()
|
||||
|
||||
if (currentTimestamp !== lastTimestamp) {
|
||||
lastTimestamp = currentTimestamp
|
||||
counter = 0
|
||||
}
|
||||
counter++
|
||||
|
||||
let now = BigInt(currentTimestamp) * BigInt(0x1000) + BigInt(counter)
|
||||
|
||||
now = descending ? ~now : now
|
||||
|
||||
const timeBytes = Buffer.alloc(6)
|
||||
for (let i = 0; i < 6; i++) {
|
||||
timeBytes[i] = Number((now >> BigInt(40 - 8 * i)) & BigInt(0xff))
|
||||
}
|
||||
|
||||
return timeBytes.toString("hex") + randomBase62(LENGTH - 12)
|
||||
}
|
||||
}
|
||||
export * as Identifier from "@opencode-ai/schema/identifier"
|
||||
|
|
|
|||
|
|
@ -1,10 +1,3 @@
|
|||
import { DateTime, Schema, SchemaGetter } from "effect"
|
||||
|
||||
export const DateTimeUtcFromMillis = Schema.Finite.pipe(
|
||||
Schema.decodeTo(Schema.DateTimeUtc, {
|
||||
decode: SchemaGetter.transform((value) => DateTime.makeUnsafe(value)),
|
||||
encode: SchemaGetter.transform((value) => DateTime.toEpochMillis(value)),
|
||||
}),
|
||||
)
|
||||
|
||||
export * as V2Schema from "./v2-schema"
|
||||
|
||||
export { DateTimeUtcFromMillis } from "@opencode-ai/schema/schema"
|
||||
|
|
|
|||
|
|
@ -1,18 +1,6 @@
|
|||
export * as WorkspaceV2 from "./workspace"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { withStatics } from "./schema"
|
||||
import { Identifier } from "./util/identifier"
|
||||
import { Workspace } from "@opencode-ai/schema/workspace"
|
||||
|
||||
export const ID = Schema.String.check(Schema.isStartsWith("wrk")).pipe(
|
||||
Schema.brand("WorkspaceV2.ID"),
|
||||
withStatics((schema) => ({
|
||||
ascending: (id?: string) => {
|
||||
if (!id) return schema.make("wrk_" + Identifier.ascending())
|
||||
if (!id.startsWith("wrk")) throw new Error(`ID ${id} does not start with wrk`)
|
||||
return schema.make(id)
|
||||
},
|
||||
create: () => schema.make("wrk_" + Identifier.ascending()),
|
||||
})),
|
||||
)
|
||||
export const ID = Workspace.ID
|
||||
export type ID = typeof ID.Type
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue