feat(core): add embedded v2 session runtime and tool foundation (#30632)

This commit is contained in:
Kit Langton 2026-06-03 23:02:17 -04:00 committed by GitHub
commit 76ee87ead8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
215 changed files with 31398 additions and 3332 deletions

View file

@ -27,5 +27,9 @@ export const migrations = (
import("./migration/20260601202201_amazing_prowler"),
import("./migration/20260602002951_lowly_union_jack"),
import("./migration/20260602182828_add_project_directories"),
import("./migration/20260603001617_session_message_projection_indexes"),
import("./migration/20260603040000_session_message_projection_order"),
import("./migration/20260603141458_session_input_inbox"),
import("./migration/20260603160727_jittery_ezekiel_stane"),
])
).map((module) => module.default) satisfies DatabaseMigration.Migration[]

View file

@ -1,12 +1,13 @@
export * as DatabaseMigration from "./migration"
import { sql } from "drizzle-orm"
import { Effect } from "effect"
import { Effect, Semaphore } from "effect"
import type { EffectDrizzleSqlite } from "@opencode-ai/effect-drizzle-sqlite"
import { migrations } from "./migration.gen"
type Database = EffectDrizzleSqlite.EffectSQLiteDatabase
type Transaction = Parameters<Parameters<Database["transaction"]>[0]>[0]
const lock = Semaphore.makeUnsafe(1)
export type Migration = {
id: string
@ -14,7 +15,7 @@ export type Migration = {
}
export function apply(db: Database) {
return applyOnly(db, migrations)
return lock.withPermit(applyOnly(db, migrations))
}
export function applyOnly(db: Database, input: Migration[]) {

View file

@ -0,0 +1,15 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
id: "20260603001617_session_message_projection_indexes",
up(tx) {
return Effect.gen(function* () {
yield* tx.run(`DROP INDEX IF EXISTS \`session_message_session_idx\`;`)
yield* tx.run(`DROP INDEX IF EXISTS \`session_message_session_type_idx\`;`)
yield* tx.run(`CREATE INDEX \`event_aggregate_seq_idx\` ON \`event\` (\`aggregate_id\`,\`seq\`);`)
yield* tx.run(`CREATE INDEX \`session_message_session_time_created_id_idx\` ON \`session_message\` (\`session_id\`,\`time_created\`,\`id\`);`)
yield* tx.run(`CREATE INDEX \`session_message_session_type_time_created_id_idx\` ON \`session_message\` (\`session_id\`,\`type\`,\`time_created\`,\`id\`);`)
})
},
} satisfies DatabaseMigration.Migration

View file

@ -0,0 +1,18 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
id: "20260603040000_session_message_projection_order",
up(tx) {
return Effect.gen(function* () {
yield* tx.run(`ALTER TABLE \`session_message\` ADD COLUMN \`seq\` integer NOT NULL DEFAULT 0;`)
yield* tx.run(`UPDATE \`session_message\` SET \`seq\` = COALESCE((SELECT \`seq\` + 1 FROM \`event\` WHERE \`event\`.\`id\` = \`session_message\`.\`id\`), 0);`)
const unmatched = yield* tx.get<{ count: number }>(`SELECT COUNT(*) AS \`count\` FROM \`session_message\` WHERE \`seq\` = 0;`)
if ((unmatched?.count ?? 0) > 0) return yield* Effect.die("Cannot migrate session_message projections without matching durable events")
yield* tx.run(`UPDATE \`session_message\` SET \`seq\` = \`seq\` - 1;`)
yield* tx.run(`DROP INDEX IF EXISTS \`session_message_session_type_time_created_id_idx\`;`)
yield* tx.run(`CREATE INDEX \`session_message_session_seq_idx\` ON \`session_message\` (\`session_id\`,\`seq\`);`)
yield* tx.run(`CREATE INDEX \`session_message_session_type_seq_idx\` ON \`session_message\` (\`session_id\`,\`type\`,\`seq\`);`)
})
},
} satisfies DatabaseMigration.Migration

View file

@ -0,0 +1,25 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
id: "20260603141458_session_input_inbox",
up(tx) {
return Effect.gen(function* () {
yield* tx.run(`
CREATE TABLE \`session_input\` (
\`seq\` integer PRIMARY KEY AUTOINCREMENT,
\`id\` text NOT NULL UNIQUE,
\`session_id\` text NOT NULL,
\`prompt\` text NOT NULL,
\`delivery\` text NOT NULL,
\`promoted_seq\` integer,
\`time_created\` integer NOT NULL,
CONSTRAINT \`fk_session_input_session_id_session_id_fk\` FOREIGN KEY (\`session_id\`) REFERENCES \`session\`(\`id\`) ON DELETE CASCADE
);
`)
yield* tx.run(
`CREATE INDEX \`session_input_session_pending_seq_idx\` ON \`session_input\` (\`session_id\`,\`promoted_seq\`,\`seq\`);`,
)
})
},
} satisfies DatabaseMigration.Migration

View file

@ -0,0 +1,18 @@
import { Effect } from "effect"
import type { DatabaseMigration } from "../migration"
export default {
id: "20260603160727_jittery_ezekiel_stane",
up(tx) {
return Effect.gen(function* () {
yield* tx.run(`DROP INDEX IF EXISTS \`session_input_session_pending_seq_idx\`;`)
yield* tx.run(`CREATE INDEX IF NOT EXISTS \`event_aggregate_type_seq_idx\` ON \`event\` (\`aggregate_id\`,\`type\`,\`seq\`);`)
yield* tx.run(
`CREATE INDEX IF NOT EXISTS \`session_input_session_pending_delivery_seq_idx\` ON \`session_input\` (\`session_id\`,\`promoted_seq\`,\`delivery\`,\`seq\`);`,
)
yield* tx.run(
`CREATE INDEX IF NOT EXISTS \`session_message_session_time_created_id_idx\` ON \`session_message\` (\`session_id\`,\`time_created\`,\`id\`);`,
)
})
},
} satisfies DatabaseMigration.Migration

View file

@ -175,8 +175,9 @@ const drizzleLayer = Layer.effect(
}),
)
export const layer = (config: Config) =>
Layer.merge(
nativeLayer(config),
Layer.merge(sqliteLayer(config), drizzleLayer).pipe(Layer.provide(nativeLayer(config))),
).pipe(Layer.provide(Reactivity.layer))
export const layer = (config: Config) => {
const native = nativeLayer(config)
return Layer.merge(native, Layer.merge(sqliteLayer(config), drizzleLayer).pipe(Layer.provide(native))).pipe(
Layer.provide(Reactivity.layer),
)
}

View file

@ -170,8 +170,9 @@ const drizzleLayer = Layer.effect(
}),
)
export const layer = (config: Config) =>
Layer.merge(
nativeLayer(config),
Layer.merge(sqliteLayer(config), drizzleLayer).pipe(Layer.provide(nativeLayer(config))),
).pipe(Layer.provide(Reactivity.layer))
export const layer = (config: Config) => {
const native = nativeLayer(config)
return Layer.merge(native, Layer.merge(sqliteLayer(config), drizzleLayer).pipe(Layer.provide(native))).pipe(
Layer.provide(Reactivity.layer),
)
}