Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
7 changes: 5 additions & 2 deletions packages/opencode/src/cli/cmd/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export const ImportCommand = cmd({
await bootstrap(process.cwd(), async () => {
let exportData:
| {
info: Session.Info
info: SDKSession
Comment thread
kitlangton marked this conversation as resolved.
messages: Array<{
info: Message
parts: Part[]
Expand Down Expand Up @@ -152,7 +152,10 @@ export const ImportCommand = cmd({
return
}

const row = { ...Session.toRow(exportData.info), project_id: Instance.project.id }
const row = {
...Session.toRow({ ...exportData.info, projectID: Instance.project.id }),
project_id: Instance.project.id,
}
Comment thread
kitlangton marked this conversation as resolved.
Outdated
Database.use((db) =>
db
.insert(SessionTable)
Expand Down
3 changes: 2 additions & 1 deletion packages/opencode/src/control-plane/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import z from "zod"
import { Identifier } from "@/id/id"
import { ProjectID } from "@/project/schema"

export const WorkspaceInfo = z.object({
id: Identifier.schema("workspace"),
Expand All @@ -8,7 +9,7 @@ export const WorkspaceInfo = z.object({
name: z.string().nullable(),
directory: z.string().nullable(),
extra: z.unknown().nullable(),
projectID: z.string(),
projectID: ProjectID.zod,
})
export type WorkspaceInfo = z.infer<typeof WorkspaceInfo>

Expand Down
2 changes: 2 additions & 0 deletions packages/opencode/src/control-plane/workspace.sql.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { sqliteTable, text } from "drizzle-orm/sqlite-core"
import { ProjectTable } from "../project/project.sql"
import type { ProjectID } from "../project/schema"

export const WorkspaceTable = sqliteTable("workspace", {
id: text().primaryKey(),
Expand All @@ -9,6 +10,7 @@ export const WorkspaceTable = sqliteTable("workspace", {
directory: text(),
extra: text({ mode: "json" }),
project_id: text()
.$type<ProjectID>()
.notNull()
.references(() => ProjectTable.id, { onDelete: "cascade" }),
})
8 changes: 5 additions & 3 deletions packages/opencode/src/control-plane/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import { Project } from "@/project/project"
import { BusEvent } from "@/bus/bus-event"
import { GlobalBus } from "@/bus/global"
import { Log } from "@/util/log"
import { ProjectID } from "@/project/schema"
import { WorkspaceTable } from "./workspace.sql"
import { getAdaptor } from "./adaptors"
import { WorkspaceInfo } from "./types"
import type { WorkspaceInfo as WorkspaceInfoType } from "./types"
Comment thread
kitlangton marked this conversation as resolved.
Outdated
import { parseSSE } from "./sse"

export namespace Workspace {
Expand All @@ -30,7 +32,7 @@ export namespace Workspace {
export const Info = WorkspaceInfo.meta({
ref: "Workspace",
})
export type Info = z.infer<typeof Info>
export type Info = WorkspaceInfoType

function fromRow(row: typeof WorkspaceTable.$inferSelect): Info {
return {
Expand All @@ -48,7 +50,7 @@ export namespace Workspace {
id: Identifier.schema("workspace").optional(),
type: Info.shape.type,
branch: Info.shape.branch,
projectID: Info.shape.projectID,
projectID: ProjectID.zod,
extra: Info.shape.extra,
})

Expand All @@ -65,7 +67,7 @@ export namespace Workspace {
name: config.name ?? null,
directory: config.directory ?? null,
extra: config.extra ?? null,
projectID: input.projectID,
projectID: config.projectID,
Comment thread
kitlangton marked this conversation as resolved.
Outdated
}

Database.use((db) => {
Expand Down
3 changes: 2 additions & 1 deletion packages/opencode/src/permission/next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Database, eq } from "@/storage/db"
import { PermissionTable } from "@/session/session.sql"
import { fn } from "@/util/fn"
import { Log } from "@/util/log"
import { ProjectID } from "@/project/schema"
import { Wildcard } from "@/util/wildcard"
import os from "os"
import z from "zod"
Expand Down Expand Up @@ -90,7 +91,7 @@ export namespace PermissionNext {
export type Reply = z.infer<typeof Reply>

export const Approval = z.object({
projectID: z.string(),
projectID: ProjectID.zod,
patterns: z.string().array(),
})

Expand Down
3 changes: 2 additions & 1 deletion packages/opencode/src/project/project.sql.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core"
import { Timestamps } from "../storage/schema.sql"
import type { ProjectID } from "./schema"

export const ProjectTable = sqliteTable("project", {
id: text().primaryKey(),
id: text().$type<ProjectID>().primaryKey(),
worktree: text().notNull(),
vcs: text(),
name: text(),
Expand Down
43 changes: 23 additions & 20 deletions packages/opencode/src/project/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { existsSync } from "fs"
import { git } from "../util/git"
import { Glob } from "../util/glob"
import { which } from "../util/which"
import { ProjectID } from "./schema"

export namespace Project {
const log = Log.create({ service: "project" })
Expand All @@ -33,7 +34,7 @@ export namespace Project {

export const Info = z
.object({
id: z.string(),
id: ProjectID.zod,
worktree: z.string(),
vcs: z.literal("git").optional(),
name: z.string().optional(),
Expand Down Expand Up @@ -73,7 +74,7 @@ export namespace Project {
? { url: row.icon_url ?? undefined, color: row.icon_color ?? undefined }
: undefined
return {
id: row.id,
id: ProjectID.make(row.id),
worktree: row.worktree,
vcs: row.vcs ? Info.shape.vcs.parse(row.vcs) : undefined,
name: row.name ?? undefined,
Expand All @@ -91,6 +92,7 @@ export namespace Project {
function readCachedId(dir: string) {
return Filesystem.readText(path.join(dir, "opencode"))
.then((x) => x.trim())
.then(ProjectID.make)
.catch(() => undefined)
}

Expand All @@ -111,7 +113,7 @@ export namespace Project {

if (!gitBinary) {
return {
id: id ?? "global",
id: id ?? ProjectID.global,
worktree: sandbox,
sandbox,
vcs: Info.shape.vcs.parse(Flag.OPENCODE_FAKE_VCS),
Expand All @@ -130,7 +132,7 @@ export namespace Project {

if (!worktree) {
return {
id: id ?? "global",
id: id ?? ProjectID.global,
worktree: sandbox,
sandbox,
vcs: Info.shape.vcs.parse(Flag.OPENCODE_FAKE_VCS),
Expand Down Expand Up @@ -160,22 +162,22 @@ export namespace Project {

if (!roots) {
return {
id: "global",
id: ProjectID.global,
worktree: sandbox,
sandbox,
vcs: Info.shape.vcs.parse(Flag.OPENCODE_FAKE_VCS),
}
}

id = roots[0]
id = roots[0] ? ProjectID.make(roots[0]) : undefined
if (id) {
await Filesystem.write(path.join(dotgit, "opencode"), id).catch(() => undefined)
}
}

if (!id) {
return {
id: "global",
id: ProjectID.global,
worktree: sandbox,
sandbox,
vcs: "git",
Expand Down Expand Up @@ -208,7 +210,7 @@ export namespace Project {
}

return {
id: "global",
id: ProjectID.global,
worktree: "/",
sandbox: "/",
vcs: Info.shape.vcs.parse(Flag.OPENCODE_FAKE_VCS),
Expand All @@ -228,7 +230,7 @@ export namespace Project {
updated: Date.now(),
},
}
if (data.id !== "global") {
if (data.id !== ProjectID.global) {
await migrateFromGlobal(data.id, data.worktree)
}
return fresh
Expand Down Expand Up @@ -308,12 +310,12 @@ export namespace Project {
return
}

async function migrateFromGlobal(id: string, worktree: string) {
const row = Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, "global")).get())
async function migrateFromGlobal(id: ProjectID, worktree: string) {
const row = Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, ProjectID.global)).get())
if (!row) return

const sessions = Database.use((db) =>
db.select().from(SessionTable).where(eq(SessionTable.project_id, "global")).all(),
db.select().from(SessionTable).where(eq(SessionTable.project_id, ProjectID.global)).all(),
)
if (sessions.length === 0) return

Expand All @@ -323,14 +325,14 @@ export namespace Project {
// Skip sessions that belong to a different directory
if (row.directory && row.directory !== worktree) return

log.info("migrating session", { sessionID: row.id, from: "global", to: id })
log.info("migrating session", { sessionID: row.id, from: ProjectID.global, to: id })
Database.use((db) => db.update(SessionTable).set({ project_id: id }).where(eq(SessionTable.id, row.id)).run())
}).catch((error) => {
log.error("failed to migrate sessions from global to project", { error, projectId: id })
})
}

export function setInitialized(id: string) {
export function setInitialized(id: ProjectID) {
Database.use((db) =>
db
.update(ProjectTable)
Expand All @@ -352,7 +354,7 @@ export namespace Project {
)
}

export function get(id: string): Info | undefined {
export function get(id: ProjectID): Info | undefined {
const row = Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, id)).get())
if (!row) return undefined
return fromRow(row)
Expand All @@ -375,12 +377,13 @@ export namespace Project {

export const update = fn(
z.object({
projectID: z.string(),
projectID: ProjectID.zod,
name: z.string().optional(),
icon: Info.shape.icon.optional(),
commands: Info.shape.commands.optional(),
}),
async (input) => {
const id = ProjectID.make(input.projectID)
const result = Database.use((db) =>
db
.update(ProjectTable)
Expand All @@ -391,7 +394,7 @@ export namespace Project {
commands: input.commands,
time_updated: Date.now(),
})
.where(eq(ProjectTable.id, input.projectID))
.where(eq(ProjectTable.id, id))
.returning()
.get(),
)
Expand All @@ -407,7 +410,7 @@ export namespace Project {
},
)

export async function sandboxes(id: string) {
export async function sandboxes(id: ProjectID) {
const row = Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, id)).get())
if (!row) return []
const data = fromRow(row)
Expand All @@ -419,7 +422,7 @@ export namespace Project {
return valid
}

export async function addSandbox(id: string, directory: string) {
export async function addSandbox(id: ProjectID, directory: string) {
const row = Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, id)).get())
if (!row) throw new Error(`Project not found: ${id}`)
const sandboxes = [...row.sandboxes]
Expand All @@ -443,7 +446,7 @@ export namespace Project {
return data
}

export async function removeSandbox(id: string, directory: string) {
export async function removeSandbox(id: ProjectID, directory: string) {
const row = Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, id)).get())
if (!row) throw new Error(`Project not found: ${id}`)
const sandboxes = row.sandboxes.filter((s) => s !== directory)
Expand Down
16 changes: 16 additions & 0 deletions packages/opencode/src/project/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Schema } from "effect"
import z from "zod"

import { withStatics } from "@/util/schema"

const projectIdSchema = Schema.String.pipe(Schema.brand("ProjectId"))

export type ProjectID = typeof projectIdSchema.Type

export const ProjectID = projectIdSchema.pipe(
withStatics((schema: typeof projectIdSchema) => ({
global: schema.makeUnsafe("global"),
make: (id: string) => schema.makeUnsafe(id),
zod: z.string().pipe(z.custom<ProjectID>()),
})),
)
3 changes: 2 additions & 1 deletion packages/opencode/src/server/routes/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { resolver } from "hono-openapi"
import { Instance } from "../../project/instance"
import { Project } from "../../project/project"
import z from "zod"
import { ProjectID } from "../../project/schema"
import { errors } from "../error"
import { lazy } from "../../util/lazy"
import { InstanceBootstrap } from "../../project/bootstrap"
Expand Down Expand Up @@ -105,7 +106,7 @@ export const ProjectRoutes = lazy(() =>
...errors(400, 404),
},
}),
validator("param", z.object({ projectID: z.string() })),
validator("param", z.object({ projectID: ProjectID.zod })),
validator("json", Project.update.schema.omit({ projectID: true })),
async (c) => {
const projectID = c.req.valid("param").projectID
Expand Down
5 changes: 3 additions & 2 deletions packages/opencode/src/session/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { fn } from "@/util/fn"
import { Command } from "../command"
import { Snapshot } from "@/snapshot"
import { WorkspaceContext } from "../control-plane/workspace-context"
import { ProjectID } from "../project/schema"

import type { Provider } from "@/provider/provider"
import { PermissionNext } from "@/permission/next"
Expand Down Expand Up @@ -120,7 +121,7 @@ export namespace Session {
.object({
id: Identifier.schema("session"),
slug: z.string(),
projectID: z.string(),
projectID: ProjectID.zod,
workspaceID: z.string().optional(),
directory: z.string(),
parentID: Identifier.schema("session").optional(),
Expand Down Expand Up @@ -162,7 +163,7 @@ export namespace Session {

export const ProjectInfo = z
.object({
id: z.string(),
id: ProjectID.zod,
name: z.string().optional(),
worktree: z.string(),
})
Expand Down
2 changes: 2 additions & 0 deletions packages/opencode/src/session/session.sql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ProjectTable } from "../project/project.sql"
import type { MessageV2 } from "./message-v2"
import type { Snapshot } from "../snapshot"
import type { PermissionNext } from "../permission/next"
import type { ProjectID } from "../project/schema"
import { Timestamps } from "../storage/schema.sql"

type PartData = Omit<MessageV2.Part, "id" | "sessionID" | "messageID">
Expand All @@ -13,6 +14,7 @@ export const SessionTable = sqliteTable(
{
id: text().primaryKey(),
project_id: text()
.$type<ProjectID>()
.notNull()
.references(() => ProjectTable.id, { onDelete: "cascade" }),
workspace_id: text(),
Expand Down
Loading
Loading