Compare commits

...

6 Commits

Author SHA1 Message Date
Kit Langton 5d37d68bc4 fix(tui): move debug overlay to devtools 2026-08-07 20:33:54 +00:00
opencode-agent[bot] 0b84e24e65 fix(tui): standardize compact terminology (#41141) 2026-08-07 16:26:14 -04:00
opencode-agent[bot] 3776975d5c fix(tui): unify integration connection copy (#41137) 2026-08-07 16:11:51 -04:00
James Long d2c99ba97c chore: improve incremental typecheck performance (#40925)
Co-authored-by: exe.dev user <exedev@jlongster-site.exe.xyz>
2026-08-07 15:34:36 -04:00
Aiden Cline 6f3a3600b9 fix(ai): forward chat cache keys (#41131) 2026-08-07 14:06:37 -05:00
Aiden Cline 9ca650f97c refactor(ai): promote prompt cache key (#39965) 2026-08-07 13:43:00 -05:00
116 changed files with 438 additions and 254 deletions
+4 -3
View File
@@ -368,11 +368,12 @@ Other provider exports listed above remain direct facades until they explicitly
## Provider options & HTTP overlays ## Provider options & HTTP overlays
Three escape hatches in order of stability: Request options in order of stability:
1. **`generation`** — portable knobs (`maxTokens`, `temperature`, `topP`, `topK`, penalties, seed, stop). 1. **`generation`** — portable knobs (`maxTokens`, `temperature`, `topP`, `topK`, penalties, seed, stop).
2. **`providerOptions: { <provider>: {...} }`** — typed-at-the-facade provider-specific knobs (OpenAI `promptCacheKey`, Anthropic `thinking`, Gemini `thinkingConfig`, OpenRouter routing). 2. **`promptCacheKey`** — stable cache affinity lowered by every protocol that supports it.
3. **`http: { body, headers, query }`** — last-resort serializable overlays merged into the final HTTP request. Reach for this only when a stable typed path doesn't yet exist. 3. **`providerOptions: { <provider>: {...} }`** — typed-at-the-facade provider-specific knobs (OpenAI `store`, Anthropic `thinking`, Gemini `thinkingConfig`, OpenRouter routing).
4. **`http: { body, headers, query }`** — last-resort serializable overlays merged into the final HTTP request. Reach for this only when a stable typed path doesn't yet exist.
Route/provider defaults are overridden by request-level values for each axis. Route/provider defaults are overridden by request-level values for each axis.
+4 -5
View File
@@ -33,9 +33,10 @@ const model = OpenAI.configure({
// //
// - `generation`: common controls such as max tokens, temperature, topP/topK, // - `generation`: common controls such as max tokens, temperature, topP/topK,
// penalties, seed, and stop sequences. // penalties, seed, and stop sequences.
// - `promptCacheKey`: stable cache affinity for protocols that support it.
// - `providerOptions`: namespaced provider-native behavior. For example, // - `providerOptions`: namespaced provider-native behavior. For example,
// OpenAI cache keys and store behavior, Anthropic thinking, Gemini thinking // OpenAI store behavior, Anthropic thinking, Gemini thinking config, or
// config, or OpenRouter routing/reasoning. // OpenRouter routing/reasoning.
// - `http`: last-resort serializable overlays for final request body, headers, // - `http`: last-resort serializable overlays for final request body, headers,
// and query params. Prefer typed `providerOptions` when a field is stable. // and query params. Prefer typed `providerOptions` when a field is stable.
// //
@@ -45,9 +46,7 @@ const request = LLM.request({
system: "You are concise and practical.", system: "You are concise and practical.",
prompt: "Tell me a joke", prompt: "Tell me a joke",
generation: { maxTokens: 80, temperature: 0.7 }, generation: { maxTokens: 80, temperature: 0.7 },
providerOptions: { promptCacheKey: "tutorial-joke",
openai: { promptCacheKey: "tutorial-joke" },
},
}) })
// 3. `generate` sends the request and collects the event stream into one // 3. `generate` sends the request and collects the event stream into one
+1 -1
View File
@@ -539,7 +539,7 @@ const lowerOptions = (request: LLMRequest) => {
return { return {
...(options.instructions ? { instructions: options.instructions } : {}), ...(options.instructions ? { instructions: options.instructions } : {}),
...(options.store !== undefined ? { store: options.store } : {}), ...(options.store !== undefined ? { store: options.store } : {}),
...(options.promptCacheKey ? { prompt_cache_key: options.promptCacheKey } : {}), ...(request.promptCacheKey ? { prompt_cache_key: request.promptCacheKey } : {}),
...(options.include ? { include: options.include } : {}), ...(options.include ? { include: options.include } : {}),
...(options.reasoningEffort || options.reasoningSummary ...(options.reasoningEffort || options.reasoningSummary
? { reasoning: { effort: options.reasoningEffort, summary: options.reasoningSummary } } ? { reasoning: { effort: options.reasoningEffort, summary: options.reasoningSummary } }
+2
View File
@@ -132,6 +132,7 @@ export const bodyFields = {
stream: Schema.Literal(true), stream: Schema.Literal(true),
stream_options: Schema.optional(Schema.Struct({ include_usage: Schema.Boolean })), stream_options: Schema.optional(Schema.Struct({ include_usage: Schema.Boolean })),
store: Schema.optional(Schema.Boolean), store: Schema.optional(Schema.Boolean),
prompt_cache_key: Schema.optional(Schema.String),
reasoning_effort: Schema.optional(OpenAIOptions.OpenAIReasoningEffort), reasoning_effort: Schema.optional(OpenAIOptions.OpenAIReasoningEffort),
max_completion_tokens: Schema.optional(Schema.Number), max_completion_tokens: Schema.optional(Schema.Number),
max_tokens: Schema.optional(Schema.Number), max_tokens: Schema.optional(Schema.Number),
@@ -509,6 +510,7 @@ const lowerOptions = (request: LLMRequest) => {
const options = OpenAIOptions.resolve(request) const options = OpenAIOptions.resolve(request)
return { return {
...(options.store !== undefined ? { store: options.store } : {}), ...(options.store !== undefined ? { store: options.store } : {}),
...(request.promptCacheKey ? { prompt_cache_key: request.promptCacheKey } : {}),
...(options.reasoningEffort ? { reasoning_effort: options.reasoningEffort } : {}), ...(options.reasoningEffort ? { reasoning_effort: options.reasoningEffort } : {}),
} }
} }
@@ -33,7 +33,6 @@ export const ServiceTierSchema = Schema.Literals(ServiceTiers)
export interface Resolved { export interface Resolved {
readonly instructions?: string readonly instructions?: string
readonly store?: boolean readonly store?: boolean
readonly promptCacheKey?: string
readonly reasoningEffort?: string readonly reasoningEffort?: string
readonly reasoningSummary?: "auto" | "concise" | "detailed" readonly reasoningSummary?: "auto" | "concise" | "detailed"
readonly include?: ReadonlyArray<ResponseIncludable> readonly include?: ReadonlyArray<ResponseIncludable>
@@ -50,7 +49,6 @@ export const resolve = (request: LLMRequest): Resolved => {
return { return {
instructions: typeof input?.instructions === "string" ? input.instructions : undefined, instructions: typeof input?.instructions === "string" ? input.instructions : undefined,
store: typeof input?.store === "boolean" ? input.store : undefined, store: typeof input?.store === "boolean" ? input.store : undefined,
promptCacheKey: typeof input?.promptCacheKey === "string" ? input.promptCacheKey : undefined,
reasoningEffort: typeof input?.reasoningEffort === "string" ? input.reasoningEffort : undefined, reasoningEffort: typeof input?.reasoningEffort === "string" ? input.reasoningEffort : undefined,
reasoningSummary: reasoningSummary:
reasoningSummary === "auto" || reasoningSummary === "concise" || reasoningSummary === "detailed" reasoningSummary === "auto" || reasoningSummary === "concise" || reasoningSummary === "detailed"
@@ -5,7 +5,6 @@ export interface OpenResponsesOptionsInput {
readonly [key: string]: unknown readonly [key: string]: unknown
readonly instructions?: string readonly instructions?: string
readonly store?: boolean readonly store?: boolean
readonly promptCacheKey?: string
readonly reasoningEffort?: ReasoningEffort readonly reasoningEffort?: ReasoningEffort
readonly reasoningSummary?: "auto" | "concise" | "detailed" readonly reasoningSummary?: "auto" | "concise" | "detailed"
readonly include?: ReadonlyArray<ResponseIncludable> readonly include?: ReadonlyArray<ResponseIncludable>
@@ -17,7 +17,6 @@ const openAIProviderOptions = (options: OpenAIOptionsInput | undefined): Provide
const openai = Object.fromEntries( const openai = Object.fromEntries(
definedEntries({ definedEntries({
store: options?.store, store: options?.store,
promptCacheKey: options?.promptCacheKey,
reasoningEffort: options?.reasoningEffort, reasoningEffort: options?.reasoningEffort,
reasoningSummary: options?.reasoningSummary, reasoningSummary: options?.reasoningSummary,
include: options?.include, include: options?.include,
+1 -2
View File
@@ -55,7 +55,6 @@ export interface OpenRouterOptions {
readonly debug?: Readonly<{ echo_upstream_body?: boolean }> readonly debug?: Readonly<{ echo_upstream_body?: boolean }>
readonly models?: ReadonlyArray<string> readonly models?: ReadonlyArray<string>
readonly plugins?: ReadonlyArray<OpenRouterPlugin> readonly plugins?: ReadonlyArray<OpenRouterPlugin>
readonly promptCacheKey?: string
readonly provider?: OpenRouterProviderRouting readonly provider?: OpenRouterProviderRouting
readonly reasoning?: Readonly<{ readonly reasoning?: Readonly<{
enabled?: boolean enabled?: boolean
@@ -122,6 +121,7 @@ export const protocol = Protocol.make({
...body, ...body,
messages, messages,
...bodyOptions(request.providerOptions?.openrouter), ...bodyOptions(request.providerOptions?.openrouter),
...(request.promptCacheKey ? { prompt_cache_key: request.promptCacheKey } : {}),
} as OpenRouterBody } as OpenRouterBody
}), }),
), ),
@@ -161,7 +161,6 @@ const bodyOptions = (input: unknown) => {
...(isRecord(debug) ? { debug } : {}), ...(isRecord(debug) ? { debug } : {}),
...(typeof user === "string" ? { user } : {}), ...(typeof user === "string" ? { user } : {}),
...(isRecord(reasoning) ? { reasoning } : {}), ...(isRecord(reasoning) ? { reasoning } : {}),
...(typeof promptCacheKey === "string" ? { prompt_cache_key: promptCacheKey } : {}),
} }
} }
+2
View File
@@ -47,6 +47,8 @@ const chatRoute = Route.make({
protocol: OpenAIChat.protocol, protocol: OpenAIChat.protocol,
endpoint: Endpoint.path("/chat/completions", { baseURL: OpenAICompatibleProfiles.profiles.xai.baseURL }), endpoint: Endpoint.path("/chat/completions", { baseURL: OpenAICompatibleProfiles.profiles.xai.baseURL }),
transport: OpenAICompatibleChat.route.transport, transport: OpenAICompatibleChat.route.transport,
headers: ({ request }): Record<string, string> =>
request.promptCacheKey ? { "x-grok-conv-id": request.promptCacheKey } : {},
}) })
export const routes = [responsesRoute, chatRoute] export const routes = [responsesRoute, chatRoute]
+3
View File
@@ -272,6 +272,8 @@ export class LLMRequest extends Schema.Class<LLMRequest>("LLM.Request")({
providerOptions: Schema.optional(ProviderOptions), providerOptions: Schema.optional(ProviderOptions),
http: Schema.optional(HttpOptions), http: Schema.optional(HttpOptions),
cache: Schema.optional(CachePolicy), cache: Schema.optional(CachePolicy),
// Stable cache affinity for protocols that support provider-managed prompt caching.
promptCacheKey: Schema.optional(Schema.String),
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)), metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
}) {} }) {}
@@ -289,6 +291,7 @@ export namespace LLMRequest {
providerOptions: request.providerOptions, providerOptions: request.providerOptions,
http: request.http, http: request.http,
cache: request.cache, cache: request.cache,
promptCacheKey: request.promptCacheKey,
metadata: request.metadata, metadata: request.metadata,
}) })
@@ -3,11 +3,11 @@ import { CloudflareWorkersAI } from "../../src/providers"
const model = CloudflareWorkersAI.configure({ accountId: "account", apiKey: "test" }).model("model") const model = CloudflareWorkersAI.configure({ accountId: "account", apiKey: "test" }).model("model")
LLM.request({ model, prompt: "Hello", providerOptions: { openai: { promptCacheKey: "cache" } } }) LLM.request({ model, prompt: "Hello", promptCacheKey: "cache" })
LLM.request({ LLM.request({
model, model,
prompt: "Hello", prompt: "Hello",
// @ts-expect-error Cloudflare's OpenAI-compatible prompt cache key must be a string. // @ts-expect-error Prompt cache keys must be strings.
providerOptions: { openai: { promptCacheKey: 1 } }, promptCacheKey: 1,
}) })
@@ -15,6 +15,8 @@ import {
} from "../../src" } from "../../src"
import * as Azure from "../../src/providers/azure" import * as Azure from "../../src/providers/azure"
import * as OpenAI from "../../src/providers/openai" import * as OpenAI from "../../src/providers/openai"
import * as OpenAICompatible from "../../src/providers/openai-compatible"
import * as XAI from "../../src/providers/xai"
import * as OpenAIChat from "../../src/protocols/openai-chat" import * as OpenAIChat from "../../src/protocols/openai-chat"
import { ProviderShared } from "../../src/protocols/shared" import { ProviderShared } from "../../src/protocols/shared"
import { Auth, LLMClient } from "../../src/route" import { Auth, LLMClient } from "../../src/route"
@@ -154,6 +156,47 @@ describe("OpenAI Chat route", () => {
}), }),
) )
it.effect("maps the request prompt cache key", () =>
Effect.gen(function* () {
const prepared = yield* compileRequest(
LLM.request({
model: OpenAICompatible.configure({
baseURL: "https://api.compatible.test/v1",
apiKey: "test",
}).model("compatible-model"),
prompt: "Hello",
promptCacheKey: "session_123",
}),
)
expect(prepared.body.prompt_cache_key).toBe("session_123")
}),
)
it.effect("maps the xAI Chat prompt cache key to conversation affinity", () =>
LLMClient.generate(
LLM.request({
model: XAI.configure({ apiKey: "test", baseURL: "https://api.x.ai/v1" }).chat("grok-4.5"),
prompt: "Hello",
promptCacheKey: "session_123",
}),
).pipe(
Effect.provide(
dynamicResponse((input) =>
Effect.gen(function* () {
const web = yield* HttpClientRequest.toWeb(input.request).pipe(Effect.orDie)
expect(web.headers.get("x-grok-conv-id")).toBe("session_123")
const body = decodeJson(yield* Effect.promise(() => web.text()))
expect(ProviderShared.isRecord(body) ? body.prompt_cache_key : undefined).toBe("session_123")
return input.respond(sseEvents(deltaChunk({}, "stop")), {
headers: { "content-type": "text/event-stream" },
})
}),
),
),
),
)
it.effect("passes through custom OpenAI-compatible reasoning effort strings", () => it.effect("passes through custom OpenAI-compatible reasoning effort strings", () =>
Effect.gen(function* () { Effect.gen(function* () {
const prepared = yield* compileRequest( const prepared = yield* compileRequest(
@@ -20,7 +20,7 @@ const cacheRequest = LLM.request({
system: LARGE_CACHEABLE_SYSTEM, system: LARGE_CACHEABLE_SYSTEM,
prompt: "Say hi.", prompt: "Say hi.",
generation: { maxTokens: 16, temperature: 0 }, generation: { maxTokens: 16, temperature: 0 },
providerOptions: { openai: { promptCacheKey: "recorded-cache-test" } }, promptCacheKey: "recorded-cache-test",
}) })
const recorded = recordedTests({ const recorded = recordedTests({
@@ -682,9 +682,9 @@ describe("OpenAI Responses route", () => {
LLM.request({ LLM.request({
model: OpenAI.configure({ baseURL: "https://api.openai.test/v1/", apiKey: "test" }).model("gpt-5.2"), model: OpenAI.configure({ baseURL: "https://api.openai.test/v1/", apiKey: "test" }).model("gpt-5.2"),
prompt: "think", prompt: "think",
promptCacheKey: "session_123",
providerOptions: { providerOptions: {
openai: { openai: {
promptCacheKey: "session_123",
reasoningEffort: "high", reasoningEffort: "high",
reasoningSummary: "auto", reasoningSummary: "auto",
include: ["reasoning.encrypted_content"], include: ["reasoning.encrypted_content"],
@@ -803,17 +803,16 @@ describe("OpenAI Responses route", () => {
}), }),
) )
it.effect("request OpenAI provider options override route defaults", () => it.effect("maps the request prompt cache key", () =>
Effect.gen(function* () { Effect.gen(function* () {
const prepared = yield* compileRequest( const prepared = yield* compileRequest(
LLM.request({ LLM.request({
model: OpenAI.configure({ model: OpenAI.configure({
baseURL: "https://api.openai.test/v1/", baseURL: "https://api.openai.test/v1/",
apiKey: "test", apiKey: "test",
providerOptions: { openai: { promptCacheKey: "model_cache" } },
}).model("gpt-4.1-mini"), }).model("gpt-4.1-mini"),
prompt: "no cache", prompt: "no cache",
providerOptions: { openai: { promptCacheKey: "request_cache" } }, promptCacheKey: "request_cache",
}), }),
) )
+1 -1
View File
@@ -162,7 +162,6 @@ describe("OpenRouter", () => {
openrouter: { openrouter: {
usage: true, usage: true,
reasoning: { effort: "high" }, reasoning: { effort: "high" },
promptCacheKey: "session_123",
models: ["anthropic/claude-sonnet-4.6", "google/gemini-3.1-pro"], models: ["anthropic/claude-sonnet-4.6", "google/gemini-3.1-pro"],
provider: { order: ["anthropic", "google"], require_parameters: true }, provider: { order: ["anthropic", "google"], require_parameters: true },
plugins: [{ id: "response-healing" }], plugins: [{ id: "response-healing" }],
@@ -174,6 +173,7 @@ describe("OpenRouter", () => {
}, },
}).model("anthropic/claude-3.7-sonnet:thinking"), }).model("anthropic/claude-3.7-sonnet:thinking"),
prompt: "Think briefly.", prompt: "Think briefly.",
promptCacheKey: "session_123",
}), }),
) )
+2 -1
View File
@@ -22,5 +22,6 @@
} }
}, },
"include": ["src", "package.json"], "include": ["src", "package.json"],
"exclude": ["dist", "ts-dist"] "exclude": ["dist", "ts-dist"],
"references": [{ "path": "../core" }]
} }
+1 -1
View File
@@ -11,7 +11,7 @@
"fix-node-pty": "bun run script/fix-node-pty.ts", "fix-node-pty": "bun run script/fix-node-pty.ts",
"benchmark:location": "bun run script/benchmark-location.ts", "benchmark:location": "bun run script/benchmark-location.ts",
"test": "bun test --only-failures", "test": "bun test --only-failures",
"typecheck": "tsgo --noEmit" "typecheck": "tsgo -b tsconfig.json tsconfig.tests.json"
}, },
"bin": { "bin": {
"opencode": "./bin/opencode" "opencode": "./bin/opencode"
+10 -6
View File
@@ -132,14 +132,16 @@ function renderMigration(name: string, sql: string) {
return `import { Effect } from "effect" return `import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: ${JSON.stringify(name)}, id: ${JSON.stringify(name)},
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
${renderStatements(sql)} ${renderStatements(sql)}
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
` `
} }
@@ -147,13 +149,15 @@ function renderSchema(sql: string) {
return `import { Effect } from "effect" return `import { Effect } from "effect"
import type { DatabaseMigration } from "./migration" import type { DatabaseMigration } from "./migration"
export default { const schema: Omit<DatabaseMigration.Migration, "id"> = {
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
${renderStatements(sql)} ${renderStatements(sql)}
}) })
}, },
} satisfies Omit<DatabaseMigration.Migration, "id"> }
export default schema
` `
} }
@@ -191,10 +195,10 @@ async function formatTypescript(input: string) {
function renderRegistry(names: string[]) { function renderRegistry(names: string[]) {
return `import type { DatabaseMigration } from "./migration" return `import type { DatabaseMigration } from "./migration"
export const migrations = ( export const migrations: DatabaseMigration.Migration[] = (
await Promise.all([ await Promise.all([
${names.map((name) => ` import("./migration/${name}"),`).join("\n")} ${names.map((name) => ` import("./migration/${name}"),`).join("\n")}
]) ])
).map((module) => module.default) satisfies DatabaseMigration.Migration[] ).map((module) => module.default)
` `
} }
+1 -1
View File
@@ -263,6 +263,7 @@ function mapOpenRouterOptions(settings: Readonly<Record<string, unknown>>) {
"extraBody", "extraBody",
"fetch", "fetch",
"headers", "headers",
"promptCacheKey",
"timeout", "timeout",
].includes(key), ].includes(key),
), ),
@@ -279,7 +280,6 @@ function mapXAIOptions(settings: Readonly<Record<string, unknown>>) {
const options = { const options = {
...(typeof settings.reasoningEffort === "string" ? { reasoningEffort: settings.reasoningEffort } : {}), ...(typeof settings.reasoningEffort === "string" ? { reasoningEffort: settings.reasoningEffort } : {}),
...(typeof settings.store === "boolean" ? { store: settings.store } : {}), ...(typeof settings.store === "boolean" ? { store: settings.store } : {}),
...(typeof settings.promptCacheKey === "string" ? { promptCacheKey: settings.promptCacheKey } : {}),
} }
if (Object.keys(options).length === 0) return {} if (Object.keys(options).length === 0) return {}
return { providerOptions: { xai: options } } return { providerOptions: { xai: options } }
+1 -1
View File
@@ -126,7 +126,7 @@ ${render(current)}`
const key = Instructions.Key.make("core/codemode") const key = Instructions.Key.make("core/codemode")
const codec = Schema.toCodecJson(CodeModeCatalog.Summary) const codec = Schema.toCodecJson(CodeModeCatalog.Summary)
export const make = (entries?: ReadonlyArray<CodeModeCatalog.Entry>): Instructions.Instructions => { export const make = (entries?: ReadonlyArray<CodeModeCatalog.Entry>): Instructions.List => {
const catalog = entries === undefined ? Instructions.removed : CodeModeCatalog.summarize(entries) const catalog = entries === undefined ? Instructions.removed : CodeModeCatalog.summarize(entries)
return Instructions.make({ return Instructions.make({
key, key,
+2 -2
View File
@@ -1,6 +1,6 @@
import type { DatabaseMigration } from "./migration" import type { DatabaseMigration } from "./migration"
export const migrations = ( export const migrations: DatabaseMigration.Migration[] = (
await Promise.all([ await Promise.all([
import("./migration/20260127222353_familiar_lady_ursula"), import("./migration/20260127222353_familiar_lady_ursula"),
import("./migration/20260211171708_add_project_commands"), import("./migration/20260211171708_add_project_commands"),
@@ -43,4 +43,4 @@ export const migrations = (
import("./migration/20260804233008_loose_psylocke"), import("./migration/20260804233008_loose_psylocke"),
import("./migration/20260805200742_import_legacy_credentials"), import("./migration/20260805200742_import_legacy_credentials"),
]) ])
).map((module) => module.default) satisfies DatabaseMigration.Migration[] ).map((module) => module.default)
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260127222353_familiar_lady_ursula", id: "20260127222353_familiar_lady_ursula",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -104,4 +104,6 @@ export default {
yield* tx.run(`CREATE INDEX \`todo_session_idx\` ON \`todo\` (\`session_id\`);`) yield* tx.run(`CREATE INDEX \`todo_session_idx\` ON \`todo\` (\`session_id\`);`)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,11 +1,13 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260211171708_add_project_commands", id: "20260211171708_add_project_commands",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
yield* tx.run(`ALTER TABLE \`project\` ADD \`commands\` text;`) yield* tx.run(`ALTER TABLE \`project\` ADD \`commands\` text;`)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260213144116_wakeful_the_professor", id: "20260213144116_wakeful_the_professor",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -20,4 +20,6 @@ export default {
`) `)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260225215848_workspace", id: "20260225215848_workspace",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -16,4 +16,6 @@ export default {
`) `)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260227213759_add_session_workspace_id", id: "20260227213759_add_session_workspace_id",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -9,4 +9,6 @@ export default {
yield* tx.run(`CREATE INDEX \`session_workspace_idx\` ON \`session\` (\`workspace_id\`);`) yield* tx.run(`CREATE INDEX \`session_workspace_idx\` ON \`session\` (\`workspace_id\`);`)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260228203230_blue_harpoon", id: "20260228203230_blue_harpoon",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -27,4 +27,6 @@ export default {
`) `)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260303231226_add_workspace_fields", id: "20260303231226_add_workspace_fields",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -12,4 +12,6 @@ export default {
yield* tx.run(`ALTER TABLE \`workspace\` DROP COLUMN \`config\`;`) yield* tx.run(`ALTER TABLE \`workspace\` DROP COLUMN \`config\`;`)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260309230000_move_org_to_state", id: "20260309230000_move_org_to_state",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -12,4 +12,6 @@ export default {
yield* tx.run(`ALTER TABLE \`account\` DROP COLUMN \`selected_org_id\`;`) yield* tx.run(`ALTER TABLE \`account\` DROP COLUMN \`selected_org_id\`;`)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260312043431_session_message_cursor", id: "20260312043431_session_message_cursor",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -13,4 +13,6 @@ export default {
yield* tx.run(`CREATE INDEX \`part_message_id_id_idx\` ON \`part\` (\`message_id\`,\`id\`);`) yield* tx.run(`CREATE INDEX \`part_message_id_id_idx\` ON \`part\` (\`message_id\`,\`id\`);`)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260323234822_events", id: "20260323234822_events",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -23,4 +23,6 @@ export default {
`) `)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260410174513_workspace-name", id: "20260410174513_workspace-name",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -26,4 +26,6 @@ export default {
yield* tx.run(`PRAGMA foreign_keys=ON;`) yield* tx.run(`PRAGMA foreign_keys=ON;`)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260413175956_chief_energizer", id: "20260413175956_chief_energizer",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -21,4 +21,6 @@ export default {
yield* tx.run(`CREATE INDEX \`session_entry_time_created_idx\` ON \`session_entry\` (\`time_created\`);`) yield* tx.run(`CREATE INDEX \`session_entry_time_created_idx\` ON \`session_entry\` (\`time_created\`);`)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260423070820_add_icon_url_override", id: "20260423070820_add_icon_url_override",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -11,4 +11,6 @@ export default {
`) `)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260427172553_slow_nightmare", id: "20260427172553_slow_nightmare",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -27,4 +27,6 @@ export default {
yield* tx.run(`DROP TABLE \`session_entry\`;`) yield* tx.run(`DROP TABLE \`session_entry\`;`)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,11 +1,13 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260428004200_add_session_path", id: "20260428004200_add_session_path",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
yield* tx.run(`ALTER TABLE \`session\` ADD \`path\` text;`) yield* tx.run(`ALTER TABLE \`session\` ADD \`path\` text;`)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260501142318_next_venus", id: "20260501142318_next_venus",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -9,4 +9,6 @@ export default {
yield* tx.run(`ALTER TABLE \`session\` ADD \`model\` text;`) yield* tx.run(`ALTER TABLE \`session\` ADD \`model\` text;`)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,11 +1,13 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260504145000_add_sync_owner", id: "20260504145000_add_sync_owner",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
yield* tx.run(`ALTER TABLE \`event_sequence\` ADD \`owner_id\` text;`) yield* tx.run(`ALTER TABLE \`event_sequence\` ADD \`owner_id\` text;`)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,11 +1,13 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260507164347_add_workspace_time", id: "20260507164347_add_workspace_time",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
yield* tx.run(`ALTER TABLE \`workspace\` ADD \`time_used\` integer NOT NULL DEFAULT 0;`) yield* tx.run(`ALTER TABLE \`workspace\` ADD \`time_used\` integer NOT NULL DEFAULT 0;`)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260510033149_session_usage", id: "20260510033149_session_usage",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -53,4 +53,6 @@ export default {
`) `)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260511000411_data_migration_state", id: "20260511000411_data_migration_state",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -13,4 +13,6 @@ export default {
`) `)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260511173437_session-metadata", id: "20260511173437_session-metadata",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -13,4 +13,6 @@ export default {
yield* tx.run(`ALTER TABLE \`session\` ADD \`metadata\` text;`) yield* tx.run(`ALTER TABLE \`session\` ADD \`metadata\` text;`)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260601010001_normalize_storage_paths", id: "20260601010001_normalize_storage_paths",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -19,4 +19,6 @@ export default {
) )
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,11 +1,13 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260601202201_amazing_prowler", id: "20260601202201_amazing_prowler",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
yield* tx.run(`DROP TABLE \`permission\`;`) yield* tx.run(`DROP TABLE \`permission\`;`)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260602002951_lowly_union_jack", id: "20260602002951_lowly_union_jack",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -21,4 +21,6 @@ export default {
) )
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260602182828_add_project_directories", id: "20260602182828_add_project_directories",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -17,4 +17,6 @@ export default {
`) `)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260603001617_session_message_projection_indexes", id: "20260603001617_session_message_projection_indexes",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -16,4 +16,6 @@ export default {
) )
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260603040000_session_message_projection_order", id: "20260603040000_session_message_projection_order",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -16,4 +16,6 @@ export default {
) )
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260603141458_session_input_inbox", id: "20260603141458_session_input_inbox",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -22,4 +22,6 @@ export default {
) )
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260603160727_jittery_ezekiel_stane", id: "20260603160727_jittery_ezekiel_stane",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -17,4 +17,6 @@ export default {
) )
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260604172448_event_sourced_session_input", id: "20260604172448_event_sourced_session_input",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -44,4 +44,6 @@ export default {
) )
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260605003541_add_session_context_snapshot", id: "20260605003541_add_session_context_snapshot",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -18,4 +18,6 @@ export default {
`) `)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,11 +1,13 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260605042240_add_context_epoch_agent", id: "20260605042240_add_context_epoch_agent",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
yield* tx.run(`ALTER TABLE \`session_context_epoch\` ADD \`agent\` text DEFAULT 'build' NOT NULL;`) yield* tx.run(`ALTER TABLE \`session_context_epoch\` ADD \`agent\` text DEFAULT 'build' NOT NULL;`)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260611035744_credential", id: "20260611035744_credential",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -22,4 +22,6 @@ export default {
) )
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260611192811_lush_chimera", id: "20260611192811_lush_chimera",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -22,4 +22,6 @@ export default {
`) `)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260612174303_project_dir_strategy", id: "20260612174303_project_dir_strategy",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -26,4 +26,6 @@ export default {
yield* tx.run(`PRAGMA foreign_keys=ON;`) yield* tx.run(`PRAGMA foreign_keys=ON;`)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260622142730_simplify_session_context_epoch", id: "20260622142730_simplify_session_context_epoch",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -10,4 +10,6 @@ export default {
yield* tx.run(`ALTER TABLE \`session_context_epoch\` DROP COLUMN \`revision\`;`) yield* tx.run(`ALTER TABLE \`session_context_epoch\` DROP COLUMN \`revision\`;`)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260622170816_reset_v2_session_state", id: "20260622170816_reset_v2_session_state",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -12,4 +12,6 @@ export default {
yield* tx.run(`DELETE FROM \`event_sequence\`;`) yield* tx.run(`DELETE FROM \`event_sequence\`;`)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260622202450_simplify_session_input", id: "20260622202450_simplify_session_input",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -14,4 +14,6 @@ export default {
yield* tx.run(`DELETE FROM \`workspace\`;`) yield* tx.run(`DELETE FROM \`workspace\`;`)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "../migration" import type { DatabaseMigration } from "../migration"
export default { const migration: DatabaseMigration.Migration = {
id: "20260804233008_loose_psylocke", id: "20260804233008_loose_psylocke",
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
@@ -135,4 +135,6 @@ export default {
yield* tx.run(`DROP TABLE \`session_input\`;`) yield* tx.run(`DROP TABLE \`session_input\`;`)
}) })
}, },
} satisfies DatabaseMigration.Migration }
export default migration
@@ -30,12 +30,14 @@ const decodeJson = Schema.decodeUnknownOption(Schema.UnknownFromJsonString)
const decodeValue = Schema.decodeUnknownOption(LegacyValue) const decodeValue = Schema.decodeUnknownOption(LegacyValue)
const wellKnownSourcesKey = "wellknown:sources" const wellKnownSourcesKey = "wellknown:sources"
export default { const migration: DatabaseMigration.Migration = {
id: "20260805200742_import_legacy_credentials", id: "20260805200742_import_legacy_credentials",
up(tx) { up(tx) {
return importLegacyCredentials(tx, path.join(Global.Path.data, "auth.json")) return importLegacyCredentials(tx, path.join(Global.Path.data, "auth.json"))
}, },
} satisfies DatabaseMigration.Migration }
export default migration
export function importLegacyCredentials(tx: Parameters<DatabaseMigration.Migration["up"]>[0], filepath: string) { export function importLegacyCredentials(tx: Parameters<DatabaseMigration.Migration["up"]>[0], filepath: string) {
return Effect.gen(function* () { return Effect.gen(function* () {
+4 -2
View File
@@ -1,7 +1,7 @@
import { Effect } from "effect" import { Effect } from "effect"
import type { DatabaseMigration } from "./migration" import type { DatabaseMigration } from "./migration"
export default { const schema: Omit<DatabaseMigration.Migration, "id"> = {
up(tx) { up(tx) {
return Effect.gen(function* () { return Effect.gen(function* () {
yield* tx.run(` yield* tx.run(`
@@ -248,4 +248,6 @@ export default {
) )
}) })
}, },
} satisfies Omit<DatabaseMigration.Migration, "id"> }
export default schema
+1 -1
View File
@@ -18,7 +18,7 @@ const Files = Schema.Array(File)
const key = Instructions.Key.make("core/instructions") const key = Instructions.Key.make("core/instructions")
export interface Interface { export interface Interface {
readonly load: () => Effect.Effect<Instructions.Instructions> readonly load: () => Effect.Effect<Instructions.List>
} }
export const Options = Schema.Struct({ export const Options = Schema.Struct({
+1 -1
View File
@@ -8,7 +8,7 @@ import { SessionSchema } from "../session/schema"
import { Instructions } from "./index" import { Instructions } from "./index"
export interface Interface { export interface Interface {
readonly load: (sessionID: SessionSchema.ID) => Effect.Effect<Instructions.Instructions> readonly load: (sessionID: SessionSchema.ID) => Effect.Effect<Instructions.List>
} }
export class Service extends Context.Service<Service, Interface>()("@opencode/InstructionBuiltIns") {} export class Service extends Context.Service<Service, Interface>()("@opencode/InstructionBuiltIns") {}
+7 -7
View File
@@ -53,7 +53,7 @@ export declare namespace Source {
} }
/** Ordered sources; identical values render identical bytes. */ /** Ordered sources; identical values render identical bytes. */
export type Instructions = ReadonlyArray<Source> export type List = ReadonlyArray<Source>
export type ReadResult = ReadonlyArray<{ export type ReadResult = ReadonlyArray<{
readonly key: Key readonly key: Key
@@ -82,10 +82,10 @@ export class DuplicateKeyError extends Schema.TaggedErrorClass<DuplicateKeyError
} }
} }
export const empty: Instructions = [] export const empty: List = []
/** Closes a typed definition into one `Source`, so differently typed sources compose. */ /** Closes a typed definition into one `Source`, so differently typed sources compose. */
export function make<A>(source: Source.Definition<A>): Instructions { export function make<A>(source: Source.Definition<A>): List {
const decode = Schema.decodeUnknownOption(source.codec) const decode = Schema.decodeUnknownOption(source.codec)
const encode = Schema.encodeSync(source.codec) const encode = Schema.encodeSync(source.codec)
const initial = (value: A) => requireText(source.key, "initial", source.render.initial(value)) const initial = (value: A) => requireText(source.key, "initial", source.render.initial(value))
@@ -121,7 +121,7 @@ export function make<A>(source: Source.Definition<A>): Instructions {
] ]
} }
export function combine(values: ReadonlyArray<Instructions>): Instructions { export function combine(values: ReadonlyArray<List>): List {
const sources = values.flat() const sources = values.flat()
const keys = new Set<Key>() const keys = new Set<Key>()
for (const source of sources) { for (const source of sources) {
@@ -131,7 +131,7 @@ export function combine(values: ReadonlyArray<Instructions>): Instructions {
return sources return sources
} }
export function read(value: Instructions): Effect.Effect<ReadResult> { export function read(value: List): Effect.Effect<ReadResult> {
return Effect.forEach( return Effect.forEach(
value, value,
(source) => source.read.pipe(Effect.map((observed) => ({ key: source.key, value: observed }))), (source) => source.read.pipe(Effect.map((observed) => ({ key: source.key, value: observed }))),
@@ -158,7 +158,7 @@ export function diff(observed: ReadResult, previous?: Values): Effect.Effect<Adm
return Effect.succeed({ delta, blobs }) return Effect.succeed({ delta, blobs })
} }
export function renderInitial(value: Instructions, values: Readonly<Record<string, Schema.Json>>) { export function renderInitial(value: List, values: Readonly<Record<string, Schema.Json>>) {
return render( return render(
value.flatMap((source) => { value.flatMap((source) => {
if (!Object.hasOwn(values, source.key)) return [] if (!Object.hasOwn(values, source.key)) return []
@@ -169,7 +169,7 @@ export function renderInitial(value: Instructions, values: Readonly<Record<strin
} }
export function renderUpdate( export function renderUpdate(
value: Instructions, value: List,
previous: Readonly<Record<string, Schema.Json>>, previous: Readonly<Record<string, Schema.Json>>,
delta: Readonly<Record<string, Option.Option<Schema.Json>>>, delta: Readonly<Record<string, Option.Option<Schema.Json>>>,
) { ) {
+1 -1
View File
@@ -55,7 +55,7 @@ const update = (previous: ReadonlyArray<Summary>, current: ReadonlyArray<Summary
} }
export interface Interface { export interface Interface {
readonly load: (agent: Agent.Selection) => Effect.Effect<Instructions.Instructions> readonly load: (agent: Agent.Selection) => Effect.Effect<Instructions.List>
} }
export class Service extends Context.Service<Service, Interface>()("@opencode/McpInstructions") {} export class Service extends Context.Service<Service, Interface>()("@opencode/McpInstructions") {}
+1 -1
View File
@@ -54,7 +54,7 @@ const update = (previous: ReadonlyArray<typeof Summary.Type>, current: ReadonlyA
} }
export interface Interface { export interface Interface {
readonly load: () => Effect.Effect<Instructions.Instructions> readonly load: () => Effect.Effect<Instructions.List>
} }
export class Service extends Context.Service<Service, Interface>()("@opencode/ReferenceInstructions") {} export class Service extends Context.Service<Service, Interface>()("@opencode/ReferenceInstructions") {}
+2
View File
@@ -11,6 +11,7 @@ import { llmClient } from "../effect/app-node-platform"
import { SessionEvent } from "./event" import { SessionEvent } from "./event"
import type { SessionMessage } from "./message" import type { SessionMessage } from "./message"
import { SessionModelHeaders } from "./model-headers" import { SessionModelHeaders } from "./model-headers"
import { SessionPromptCacheKey } from "./prompt-cache-key"
import { App } from "../app" import { App } from "../app"
import { SessionRunnerModel } from "./runner/model" import { SessionRunnerModel } from "./runner/model"
import { SessionSchema } from "./schema" import { SessionSchema } from "./schema"
@@ -258,6 +259,7 @@ const make = (dependencies: Dependencies) => {
.stream( .stream(
LLM.request({ LLM.request({
model: plan.model, model: plan.model,
promptCacheKey: SessionPromptCacheKey.make(plan.session.id),
http: { headers: SessionModelHeaders.make(plan.session, dependencies.app) }, http: { headers: SessionModelHeaders.make(plan.session, dependencies.app) },
messages: [Message.user(plan.prompt)], messages: [Message.user(plan.prompt)],
tools: [], tools: [],
+1 -1
View File
@@ -25,7 +25,7 @@ import { SessionStore } from "./store"
export interface Selection { export interface Selection {
readonly session: SessionSchema.Info readonly session: SessionSchema.Info
readonly agent: Agent.Selection & { readonly info: Agent.Info } readonly agent: Agent.Selection & { readonly info: Agent.Info }
readonly instructions: Instructions.Instructions readonly instructions: Instructions.List
readonly tools: Tool.Snapshot readonly tools: Tool.Snapshot
} }
+2 -4
View File
@@ -11,6 +11,7 @@ import { SessionContext } from "./context"
import { SessionGenerate } from "./generate" import { SessionGenerate } from "./generate"
import { SessionHistory } from "./history" import { SessionHistory } from "./history"
import { SessionModelHeaders } from "./model-headers" import { SessionModelHeaders } from "./model-headers"
import { SessionPromptCacheKey } from "./prompt-cache-key"
import { SessionRunnerModel } from "./runner/model" import { SessionRunnerModel } from "./runner/model"
import PROMPT_DEFAULT from "./runner/prompt/base.txt" import PROMPT_DEFAULT from "./runner/prompt/base.txt"
import { toLLMMessages } from "./runner/to-llm-message" import { toLLMMessages } from "./runner/to-llm-message"
@@ -31,9 +32,6 @@ export const layer = Layer.effect(
const model = yield* models.resolve(selection.session) const model = yield* models.resolve(selection.session)
const history = yield* SessionHistory.preview(database.db, selection.session.id, selection.instructions) const history = yield* SessionHistory.preview(database.db, selection.session.id, selection.instructions)
const providerMetadataKey = model.model.route.providerMetadataKey ?? model.model.provider const providerMetadataKey = model.model.route.providerMetadataKey ?? model.model.provider
const promptCacheKey = /^ses_[0-9a-f]{64}$/.test(selection.session.id)
? selection.session.id.slice(4)
: selection.session.id
const tools = selection.tools const tools = selection.tools
const toolDefinitions = tools.definitions const toolDefinitions = tools.definitions
const toolsByName = new Map(toolDefinitions.map((tool) => [tool.name, tool])) const toolsByName = new Map(toolDefinitions.map((tool) => [tool.name, tool]))
@@ -71,7 +69,7 @@ export const layer = Layer.effect(
LLM.request({ LLM.request({
model: model.model, model: model.model,
http: { headers: SessionModelHeaders.make(selection.session, app) }, http: { headers: SessionModelHeaders.make(selection.session, app) },
providerOptions: { [providerMetadataKey]: { promptCacheKey } }, promptCacheKey: SessionPromptCacheKey.make(selection.session.id),
system: contextEvent.system, system: contextEvent.system,
messages: contextEvent.messages, messages: contextEvent.messages,
tools: hookedTools, tools: hookedTools,
+2 -2
View File
@@ -74,7 +74,7 @@ export const load = Effect.fn("SessionHistory.load")(function* (db: DatabaseServ
export const entriesForRunner = Effect.fn("SessionHistory.entriesForRunner")(function* ( export const entriesForRunner = Effect.fn("SessionHistory.entriesForRunner")(function* (
db: DatabaseService, db: DatabaseService,
sessionID: SessionSchema.ID, sessionID: SessionSchema.ID,
instructions: Instructions.Instructions, instructions: Instructions.List,
) { ) {
return yield* db return yield* db
.transaction(() => .transaction(() =>
@@ -92,7 +92,7 @@ export const entriesForRunner = Effect.fn("SessionHistory.entriesForRunner")(fun
export const preview = Effect.fn("SessionHistory.preview")(function* ( export const preview = Effect.fn("SessionHistory.preview")(function* (
db: DatabaseService, db: DatabaseService,
sessionID: SessionSchema.ID, sessionID: SessionSchema.ID,
instructions: Instructions.Instructions, instructions: Instructions.List,
) { ) {
const observed = yield* Instructions.read(instructions) const observed = yield* Instructions.read(instructions)
return yield* db return yield* db
@@ -25,7 +25,7 @@ export interface Interface {
}) => Effect.Effect<void, InstructionEntry.ValueTooLargeError> }) => Effect.Effect<void, InstructionEntry.ValueTooLargeError>
readonly remove: (input: { readonly sessionID: SessionSchema.ID; readonly key: Key }) => Effect.Effect<void> readonly remove: (input: { readonly sessionID: SessionSchema.ID; readonly key: Key }) => Effect.Effect<void>
/** Produces one Instructions source per stored entry, keyed `api/<key>`. */ /** Produces one Instructions source per stored entry, keyed `api/<key>`. */
readonly load: (sessionID: SessionSchema.ID) => Effect.Effect<Instructions.Instructions> readonly load: (sessionID: SessionSchema.ID) => Effect.Effect<Instructions.List>
} }
export class Service extends Context.Service<Service, Interface>()("@opencode/InstructionEntry") {} export class Service extends Context.Service<Service, Interface>()("@opencode/InstructionEntry") {}
@@ -20,7 +20,7 @@ export interface Observation extends Instructions.Admission {
export const observe = Effect.fn("InstructionState.observe")(function* ( export const observe = Effect.fn("InstructionState.observe")(function* (
db: DatabaseService, db: DatabaseService,
instructions: Instructions.Instructions, instructions: Instructions.List,
sessionID: SessionSchema.ID, sessionID: SessionSchema.ID,
): Effect.fn.Return<Observation, Instructions.InitializationBlocked> { ): Effect.fn.Return<Observation, Instructions.InitializationBlocked> {
const [observed, stored] = yield* Effect.all([Instructions.read(instructions), find(db, sessionID)], { const [observed, stored] = yield* Effect.all([Instructions.read(instructions), find(db, sessionID)], {
@@ -38,7 +38,7 @@ export const observe = Effect.fn("InstructionState.observe")(function* (
export const commit = Effect.fn("InstructionState.commit")(function* ( export const commit = Effect.fn("InstructionState.commit")(function* (
db: DatabaseService, db: DatabaseService,
bus: Bus.Interface, bus: Bus.Interface,
instructions: Instructions.Instructions, instructions: Instructions.List,
observation: Observation, observation: Observation,
) { ) {
if (!observation.initial && Object.keys(observation.delta).length === 0) return if (!observation.initial && Object.keys(observation.delta).length === 0) return
@@ -62,7 +62,7 @@ export const commit = Effect.fn("InstructionState.commit")(function* (
const renderUpdateText = Effect.fnUntraced(function* ( const renderUpdateText = Effect.fnUntraced(function* (
db: DatabaseService, db: DatabaseService,
instructions: Instructions.Instructions, instructions: Instructions.List,
observation: Observation, observation: Observation,
) { ) {
const replaced = Object.entries(observation.previous).filter(([key]) => Object.hasOwn(observation.delta, key)) const replaced = Object.entries(observation.previous).filter(([key]) => Object.hasOwn(observation.delta, key))
@@ -77,7 +77,7 @@ const renderUpdateText = Effect.fnUntraced(function* (
export const prepare = Effect.fn("InstructionState.prepare")(function* ( export const prepare = Effect.fn("InstructionState.prepare")(function* (
db: DatabaseService, db: DatabaseService,
bus: Bus.Interface, bus: Bus.Interface,
instructions: Instructions.Instructions, instructions: Instructions.List,
sessionID: SessionSchema.ID, sessionID: SessionSchema.ID,
) { ) {
yield* commit(db, bus, instructions, yield* observe(db, instructions, sessionID)) yield* commit(db, bus, instructions, yield* observe(db, instructions, sessionID))
@@ -162,7 +162,7 @@ export const reset = Effect.fn("InstructionState.reset")(function* (db: Database
export const initial = Effect.fn("InstructionState.initial")(function* ( export const initial = Effect.fn("InstructionState.initial")(function* (
db: DatabaseService, db: DatabaseService,
sessionID: SessionSchema.ID, sessionID: SessionSchema.ID,
instructions: Instructions.Instructions, instructions: Instructions.List,
) { ) {
const state = yield* find(db, sessionID) const state = yield* find(db, sessionID)
if (!state) return yield* Effect.die(new Error(`Instruction state not found during assembly: ${sessionID}`)) if (!state) return yield* Effect.die(new Error(`Instruction state not found during assembly: ${sessionID}`))
@@ -181,7 +181,7 @@ export const current = Effect.fn("InstructionState.current")(function* (
export const preview = Effect.fn("InstructionState.preview")(function* ( export const preview = Effect.fn("InstructionState.preview")(function* (
db: DatabaseService, db: DatabaseService,
sessionID: SessionSchema.ID, sessionID: SessionSchema.ID,
instructions: Instructions.Instructions, instructions: Instructions.List,
observed: Instructions.ReadResult, observed: Instructions.ReadResult,
) { ) {
const state = yield* find(db, sessionID) const state = yield* find(db, sessionID)
+2 -2
View File
@@ -15,6 +15,7 @@ import { QuestionTool } from "../tool/plugin/question"
import { Tool } from "../tool" import { Tool } from "../tool"
import { SessionContext } from "./context" import { SessionContext } from "./context"
import { SessionModelHeaders } from "./model-headers" import { SessionModelHeaders } from "./model-headers"
import { SessionPromptCacheKey } from "./prompt-cache-key"
import { PromptCacheDiagnostics } from "./prompt-cache-diagnostics" import { PromptCacheDiagnostics } from "./prompt-cache-diagnostics"
import { MAX_STEPS_PROMPT } from "./runner/max-steps" import { MAX_STEPS_PROMPT } from "./runner/max-steps"
import PROMPT_DEFAULT from "./runner/prompt/base.txt" import PROMPT_DEFAULT from "./runner/prompt/base.txt"
@@ -181,7 +182,6 @@ export const layer = Layer.effect(
// The final Step keeps definitions available to protocols with native "none", // The final Step keeps definitions available to protocols with native "none",
// preserving their prompt cache prefix. Calls are still rejected at execution. // preserving their prompt cache prefix. Calls are still rejected at execution.
const tools = input.context.tools const tools = input.context.tools
const promptCacheKey = /^ses_[0-9a-f]{64}$/.test(session.id) ? session.id.slice(4) : session.id
const system = [agent.info.system ? agent.info.system : PROMPT_DEFAULT, input.context.initial] const system = [agent.info.system ? agent.info.system : PROMPT_DEFAULT, input.context.initial]
.filter((part) => part.length > 0) .filter((part) => part.length > 0)
.map(SystemPart.make) .map(SystemPart.make)
@@ -220,7 +220,7 @@ export const layer = Layer.effect(
http: { http: {
headers: SessionModelHeaders.make(session, app), headers: SessionModelHeaders.make(session, app),
}, },
providerOptions: { [providerMetadataKey]: { promptCacheKey } }, promptCacheKey: SessionPromptCacheKey.make(session.id),
system: context.system, system: context.system,
messages: boundImages(unsupportedParts(context.messages, resolved.capabilities)), messages: boundImages(unsupportedParts(context.messages, resolved.capabilities)),
tools: Array.from(hooked, ([name, tool]) => ({ ...tool, name })), tools: Array.from(hooked, ([name, tool]) => ({ ...tool, name })),
@@ -0,0 +1,6 @@
export * as SessionPromptCacheKey from "./prompt-cache-key"
import { SessionSchema } from "./schema"
export const make = (sessionID: SessionSchema.ID) =>
/^ses_[0-9a-f]{64}$/.test(sessionID) ? sessionID.slice(4) : sessionID
+1 -1
View File
@@ -57,7 +57,7 @@ const update = (previous: ReadonlyArray<Summary>, current: ReadonlyArray<Summary
} }
export interface Interface { export interface Interface {
readonly load: (agent: Agent.Selection) => Effect.Effect<Instructions.Instructions> readonly load: (agent: Agent.Selection) => Effect.Effect<Instructions.List>
} }
export class Service extends Context.Service<Service, Interface>()("@opencode/SkillInstructions") {} export class Service extends Context.Service<Service, Interface>()("@opencode/SkillInstructions") {}
+1 -1
View File
@@ -22,7 +22,7 @@ export abstract class NamedError extends Error {
return NamedError.createSchemaClass(name, Schema.isSchema(data) ? data : Schema.Struct(data)) return NamedError.createSchemaClass(name, Schema.isSchema(data) ? data : Schema.Struct(data))
} }
private static createSchemaClass<Name extends string, DataSchema extends Schema.Top>(name: Name, data: DataSchema) { public static createSchemaClass<Name extends string, DataSchema extends Schema.Top>(name: Name, data: DataSchema) {
const schema = Schema.Struct({ const schema = Schema.Struct({
name: Schema.Literal(name), name: Schema.Literal(name),
data, data,
-4
View File
@@ -179,7 +179,6 @@ describe("AISDKNative", () => {
models: ["anthropic/claude-sonnet-4.6"], models: ["anthropic/claude-sonnet-4.6"],
provider: { only: ["anthropic"], require_parameters: true }, provider: { only: ["anthropic"], require_parameters: true },
reasoning: { effort: "high" }, reasoning: { effort: "high" },
promptCacheKey: "session_123",
future_option: { enabled: true }, future_option: { enabled: true },
}), }),
).toEqual({ ).toEqual({
@@ -190,7 +189,6 @@ describe("AISDKNative", () => {
models: ["anthropic/claude-sonnet-4.6"], models: ["anthropic/claude-sonnet-4.6"],
provider: { only: ["anthropic"], require_parameters: true }, provider: { only: ["anthropic"], require_parameters: true },
reasoning: { effort: "high" }, reasoning: { effort: "high" },
promptCacheKey: "session_123",
future_option: { enabled: true }, future_option: { enabled: true },
}, },
}, },
@@ -271,7 +269,6 @@ describe("AISDKNative", () => {
baseURL: "https://xai.example/v1", baseURL: "https://xai.example/v1",
reasoningEffort: "custom", reasoningEffort: "custom",
store: true, store: true,
promptCacheKey: "cache-key",
}), }),
).toEqual({ ).toEqual({
package: "@opencode-ai/ai/providers/xai", package: "@opencode-ai/ai/providers/xai",
@@ -282,7 +279,6 @@ describe("AISDKNative", () => {
xai: { xai: {
reasoningEffort: "custom", reasoningEffort: "custom",
store: true, store: true,
promptCacheKey: "cache-key",
}, },
}, },
}, },
+1 -1
View File
@@ -68,7 +68,7 @@ const instructionEvents = (db: Database.Interface["db"], sessionID: SessionSchem
.all() .all()
.pipe(Effect.orDie) .pipe(Effect.orDie)
const preview = (db: Database.Interface["db"], sessionID: SessionSchema.ID, instructions: Instructions.Instructions) => const preview = (db: Database.Interface["db"], sessionID: SessionSchema.ID, instructions: Instructions.List) =>
Instructions.read(instructions).pipe( Instructions.read(instructions).pipe(
Effect.flatMap((observed) => InstructionState.preview(db, sessionID, instructions, observed)), Effect.flatMap((observed) => InstructionState.preview(db, sessionID, instructions, observed)),
) )
+2 -2
View File
@@ -10,7 +10,7 @@ export const state = (values: Readonly<Record<string, Schema.Json>>): State => (
const hashes = (values: Readonly<Record<string, Schema.Json>>): Instructions.Values => const hashes = (values: Readonly<Record<string, Schema.Json>>): Instructions.Values =>
Object.fromEntries(Object.entries(values).map(([key, value]) => [key, Instructions.hash(value)])) Object.fromEntries(Object.entries(values).map(([key, value]) => [key, Instructions.hash(value)]))
export const readInitial = (instructions: Instructions.Instructions) => export const readInitial = (instructions: Instructions.List) =>
Effect.gen(function* () { Effect.gen(function* () {
const admission = yield* Instructions.read(instructions).pipe(Effect.flatMap(Instructions.diff)) const admission = yield* Instructions.read(instructions).pipe(Effect.flatMap(Instructions.diff))
const current = state( const current = state(
@@ -23,7 +23,7 @@ export const readInitial = (instructions: Instructions.Instructions) =>
return { ...current, text: Instructions.renderInitial(instructions, current.values) } return { ...current, text: Instructions.renderInitial(instructions, current.values) }
}) })
export const readUpdate = (instructions: Instructions.Instructions, previous: State) => export const readUpdate = (instructions: Instructions.List, previous: State) =>
Effect.gen(function* () { Effect.gen(function* () {
const admission = yield* Instructions.read(instructions).pipe( const admission = yield* Instructions.read(instructions).pipe(
Effect.flatMap((observed) => Instructions.diff(observed, hashes(previous.values))), Effect.flatMap((observed) => Instructions.diff(observed, hashes(previous.values))),
@@ -236,6 +236,7 @@ it.effect("manual compaction summarizes short context instead of no-op", () =>
expect(Array.from(yield* Fiber.join(delta)).map((event) => event.data.text)).toEqual(["manual summary"]) expect(Array.from(yield* Fiber.join(delta)).map((event) => event.data.text)).toEqual(["manual summary"])
expect(requests).toHaveLength(1) expect(requests).toHaveLength(1)
expect(requests[0]?.promptCacheKey).toBe(sessionID)
expect(requests[0]?.http?.headers).toEqual({ expect(requests[0]?.http?.headers).toEqual({
"x-session-affinity": sessionID, "x-session-affinity": sessionID,
"X-Session-Id": sessionID, "X-Session-Id": sessionID,
+1 -1
View File
@@ -296,7 +296,7 @@ it.effect("generates from fresh settled Session context without durable mutation
expect(requests[0]?.system[0]?.text).toBe("Hooked system") expect(requests[0]?.system[0]?.text).toBe("Hooked system")
expect(requests[0]?.system.map((part) => part.text)).toContain("Initial context") expect(requests[0]?.system.map((part) => part.text)).toContain("Initial context")
expect(requests[0]?.http?.headers).toMatchObject({ "X-Session-Id": sessionID }) expect(requests[0]?.http?.headers).toMatchObject({ "X-Session-Id": sessionID })
expect(requests[0]?.providerOptions).toMatchObject({ openai: { promptCacheKey: sessionID } }) expect(requests[0]?.promptCacheKey).toBe(sessionID)
const instructionUpdates = requests[0]?.messages.flatMap((message) => const instructionUpdates = requests[0]?.messages.flatMap((message) =>
message.role === "system" message.role === "system"
? message.content.flatMap((content) => (content.type === "text" ? [content.text] : [])) ? message.content.flatMap((content) => (content.type === "text" ? [content.text] : []))
+2 -2
View File
@@ -3254,7 +3254,7 @@ describe("SessionRunnerLLM", () => {
yield* stream.started yield* stream.started
expect(requests).toHaveLength(2) expect(requests).toHaveLength(2)
expect(requests.map((request) => request.providerOptions?.openai?.promptCacheKey)).toEqual([ expect(requests.map((request) => request.promptCacheKey)).toEqual([
sessionID, sessionID,
otherSessionID, otherSessionID,
]) ])
@@ -3285,7 +3285,7 @@ describe("SessionRunnerLLM", () => {
yield* session.resume(longSessionID) yield* session.resume(longSessionID)
yield* session.resume(otherLongSessionID) yield* session.resume(otherLongSessionID)
const keys = requests.map((request) => request.providerOptions?.openai?.promptCacheKey) const keys = requests.map((request) => request.promptCacheKey)
expect(keys).toEqual([longSessionID.slice(4), otherLongSessionID.slice(4)]) expect(keys).toEqual([longSessionID.slice(4), otherLongSessionID.slice(4)])
expect(keys.every((key) => typeof key === "string" && key.length === 64)).toBe(true) expect(keys.every((key) => typeof key === "string" && key.length === 64)).toBe(true)
expect(keys[0]).not.toBe(keys[1]) expect(keys[0]).not.toBe(keys[1])
+11 -2
View File
@@ -2,6 +2,15 @@
"$schema": "https://json.schemastore.org/tsconfig", "$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/bun/tsconfig.json", "extends": "@tsconfig/bun/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"noUncheckedIndexedAccess": false "composite": true,
} "declaration": true,
"emitDeclarationOnly": true,
"incremental": true,
"noEmit": false,
"noUncheckedIndexedAccess": false,
"outDir": "node_modules/.ts-dist/source",
"rootDir": "src",
"tsBuildInfoFile": "node_modules/.ts-dist/source.tsbuildinfo"
},
"include": ["src"]
} }
+11
View File
@@ -0,0 +1,11 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/bun/tsconfig.json",
"compilerOptions": {
"incremental": true,
"noUncheckedIndexedAccess": false,
"tsBuildInfoFile": "node_modules/.ts-dist/tests.tsbuildinfo"
},
"include": ["drizzle.config.ts", "script", "test"],
"references": [{ "path": "./tsconfig.json" }]
}
@@ -110,12 +110,12 @@ export type SQLiteEffectDelete<
export type AnySQLiteEffectDelete = SQLiteEffectDeleteBase<any, any, any, any, any, any> export type AnySQLiteEffectDelete = SQLiteEffectDeleteBase<any, any, any, any, any, any>
export interface SQLiteEffectDeleteBase< export interface SQLiteEffectDeleteBase<
TTable extends SQLiteTable, out TTable extends SQLiteTable,
TRunResult, out TRunResult,
TReturning extends Record<string, unknown> | undefined = undefined, out TReturning extends Record<string, unknown> | undefined = undefined,
TDynamic extends boolean = false, out TDynamic extends boolean = false,
_TExcludedMethods extends string = never, _TExcludedMethods extends string = never,
TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase, out TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
> extends RunnableQuery<TReturning extends undefined ? TRunResult : TReturning[], "sqlite">, > extends RunnableQuery<TReturning extends undefined ? TRunResult : TReturning[], "sqlite">,
SQLWrapper, SQLWrapper,
Effect.Effect< Effect.Effect<
@@ -137,12 +137,12 @@ export interface SQLiteEffectDeleteBase<
} }
export class SQLiteEffectDeleteBase< export class SQLiteEffectDeleteBase<
TTable extends SQLiteTable, out TTable extends SQLiteTable,
TRunResult, out TRunResult,
TReturning extends Record<string, unknown> | undefined = undefined, out TReturning extends Record<string, unknown> | undefined = undefined,
TDynamic extends boolean = false, out TDynamic extends boolean = false,
_TExcludedMethods extends string = never, _TExcludedMethods extends string = never,
TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase, out TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
> >
implements RunnableQuery<TReturning extends undefined ? TRunResult : TReturning[], "sqlite">, SQLWrapper implements RunnableQuery<TReturning extends undefined ? TRunResult : TReturning[], "sqlite">, SQLWrapper
{ {
@@ -126,9 +126,9 @@ export type SQLiteEffectInsert<
export type AnySQLiteEffectInsert = SQLiteEffectInsertBase<any, any, any, any, any, any> export type AnySQLiteEffectInsert = SQLiteEffectInsertBase<any, any, any, any, any, any>
export class SQLiteEffectInsertBuilder< export class SQLiteEffectInsertBuilder<
TTable extends SQLiteTable, in out TTable extends SQLiteTable,
TRunResult, out TRunResult,
TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase, out TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
> { > {
static readonly [entityKind]: string = "SQLiteEffectInsertBuilder" static readonly [entityKind]: string = "SQLiteEffectInsertBuilder"
@@ -194,12 +194,12 @@ export class SQLiteEffectInsertBuilder<
} }
export interface SQLiteEffectInsertBase< export interface SQLiteEffectInsertBase<
TTable extends SQLiteTable, in out TTable extends SQLiteTable,
TRunResult, out TRunResult,
TReturning = undefined, out TReturning = undefined,
TDynamic extends boolean = false, out TDynamic extends boolean = false,
_TExcludedMethods extends string = never, _TExcludedMethods extends string = never,
TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase, out TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
> extends SQLWrapper, > extends SQLWrapper,
RunnableQuery<TReturning extends undefined ? TRunResult : TReturning[], "sqlite">, RunnableQuery<TReturning extends undefined ? TRunResult : TReturning[], "sqlite">,
Effect.Effect< Effect.Effect<
@@ -221,12 +221,12 @@ export interface SQLiteEffectInsertBase<
} }
export class SQLiteEffectInsertBase< export class SQLiteEffectInsertBase<
TTable extends SQLiteTable, in out TTable extends SQLiteTable,
TRunResult, out TRunResult,
TReturning = undefined, out TReturning = undefined,
TDynamic extends boolean = false, out TDynamic extends boolean = false,
_TExcludedMethods extends string = never, _TExcludedMethods extends string = never,
TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase, out TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
> >
implements RunnableQuery<TReturning extends undefined ? TRunResult : TReturning[], "sqlite">, SQLWrapper implements RunnableQuery<TReturning extends undefined ? TRunResult : TReturning[], "sqlite">, SQLWrapper
{ {
@@ -19,9 +19,9 @@ import type { SQLiteTable } from "drizzle-orm/sqlite-core/table"
import type { SQLiteEffectPreparedQuery, SQLiteEffectSession } from "./session" import type { SQLiteEffectPreparedQuery, SQLiteEffectSession } from "./session"
export class SQLiteEffectRelationalQueryBuilder< export class SQLiteEffectRelationalQueryBuilder<
TSchema extends TablesRelationalConfig, out TSchema extends TablesRelationalConfig,
TFields extends TableRelationalConfig, TFields extends TableRelationalConfig,
TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase, out TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
> { > {
static readonly [entityKind]: string = "SQLiteEffectRelationalQueryBuilderV2" static readonly [entityKind]: string = "SQLiteEffectRelationalQueryBuilderV2"
@@ -152,18 +152,18 @@ export interface SQLiteEffectSelectHKT<TEffectHKT extends QueryEffectHKTBase = Q
} }
export interface SQLiteEffectSelectBase< export interface SQLiteEffectSelectBase<
TTableName extends string | undefined, out TTableName extends string | undefined,
TRunResult, out TRunResult,
TSelection extends ColumnsSelection, out TSelection extends ColumnsSelection,
TSelectMode extends SelectMode = "single", out TSelectMode extends SelectMode = "single",
TNullabilityMap extends Record<string, JoinNullability> = TTableName extends string out TNullabilityMap extends Record<string, JoinNullability> = TTableName extends string
? Record<TTableName, "not-null"> ? Record<TTableName, "not-null">
: {}, : {},
TDynamic extends boolean = false, out TDynamic extends boolean = false,
TExcludedMethods extends string = never, TExcludedMethods extends string = never,
TResult extends any[] = SelectResult<TSelection, TSelectMode, TNullabilityMap>[], out TResult extends any[] = SelectResult<TSelection, TSelectMode, TNullabilityMap>[],
TSelectedFields extends ColumnsSelection = BuildSubquerySelection<TSelection, TNullabilityMap>, out TSelectedFields extends ColumnsSelection = BuildSubquerySelection<TSelection, TNullabilityMap>,
TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase, out TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
> extends SQLiteSelectQueryBuilderBase< > extends SQLiteSelectQueryBuilderBase<
SQLiteEffectSelectHKT<TEffectHKT>, SQLiteEffectSelectHKT<TEffectHKT>,
TTableName, TTableName,
@@ -180,18 +180,18 @@ export interface SQLiteEffectSelectBase<
Effect.Effect<TResult, TEffectHKT["error"], TEffectHKT["context"]> {} Effect.Effect<TResult, TEffectHKT["error"], TEffectHKT["context"]> {}
export class SQLiteEffectSelectBase< export class SQLiteEffectSelectBase<
TTableName extends string | undefined, out TTableName extends string | undefined,
TRunResult, out TRunResult,
TSelection extends ColumnsSelection, out TSelection extends ColumnsSelection,
TSelectMode extends SelectMode = "single", out TSelectMode extends SelectMode = "single",
TNullabilityMap extends Record<string, JoinNullability> = TTableName extends string out TNullabilityMap extends Record<string, JoinNullability> = TTableName extends string
? Record<TTableName, "not-null"> ? Record<TTableName, "not-null">
: {}, : {},
TDynamic extends boolean = false, out TDynamic extends boolean = false,
TExcludedMethods extends string = never, TExcludedMethods extends string = never,
TResult extends any[] = SelectResult<TSelection, TSelectMode, TNullabilityMap>[], out TResult extends any[] = SelectResult<TSelection, TSelectMode, TNullabilityMap>[],
TSelectedFields extends ColumnsSelection = BuildSubquerySelection<TSelection, TNullabilityMap>, out TSelectedFields extends ColumnsSelection = BuildSubquerySelection<TSelection, TNullabilityMap>,
TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase, out TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
> >
extends SQLiteSelectQueryBuilderBase< extends SQLiteSelectQueryBuilderBase<
SQLiteEffectSelectHKT<TEffectHKT>, SQLiteEffectSelectHKT<TEffectHKT>,
@@ -158,9 +158,9 @@ export type SQLiteEffectUpdateJoinFn<T extends AnySQLiteEffectUpdate> = <
) => T ) => T
export class SQLiteEffectUpdateBuilder< export class SQLiteEffectUpdateBuilder<
TTable extends SQLiteTable, in out TTable extends SQLiteTable,
TRunResult, out TRunResult,
TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase, out TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
> { > {
static readonly [entityKind]: string = "SQLiteEffectUpdateBuilder" static readonly [entityKind]: string = "SQLiteEffectUpdateBuilder"
@@ -193,13 +193,13 @@ export class SQLiteEffectUpdateBuilder<
} }
export interface SQLiteEffectUpdateBase< export interface SQLiteEffectUpdateBase<
TTable extends SQLiteTable = SQLiteTable, out TTable extends SQLiteTable = SQLiteTable,
TRunResult = unknown, out TRunResult = unknown,
TFrom extends SQLiteTable | Subquery | SQLiteViewBase | SQL | undefined = undefined, out TFrom extends SQLiteTable | Subquery | SQLiteViewBase | SQL | undefined = undefined,
TReturning = undefined, out TReturning = undefined,
TDynamic extends boolean = false, out TDynamic extends boolean = false,
_TExcludedMethods extends string = never, _TExcludedMethods extends string = never,
TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase, out TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
> extends SQLWrapper, > extends SQLWrapper,
RunnableQuery<TReturning extends undefined ? TRunResult : TReturning[], "sqlite">, RunnableQuery<TReturning extends undefined ? TRunResult : TReturning[], "sqlite">,
Effect.Effect< Effect.Effect<
@@ -222,13 +222,13 @@ export interface SQLiteEffectUpdateBase<
} }
export class SQLiteEffectUpdateBase< export class SQLiteEffectUpdateBase<
TTable extends SQLiteTable = SQLiteTable, out TTable extends SQLiteTable = SQLiteTable,
TRunResult = unknown, out TRunResult = unknown,
TFrom extends SQLiteTable | Subquery | SQLiteViewBase | SQL | undefined = undefined, out TFrom extends SQLiteTable | Subquery | SQLiteViewBase | SQL | undefined = undefined,
TReturning = undefined, out TReturning = undefined,
TDynamic extends boolean = false, out TDynamic extends boolean = false,
_TExcludedMethods extends string = never, _TExcludedMethods extends string = never,
TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase, out TEffectHKT extends QueryEffectHKTBase = QueryEffectHKTBase,
> >
implements RunnableQuery<TReturning extends undefined ? TRunResult : TReturning[], "sqlite">, SQLWrapper implements RunnableQuery<TReturning extends undefined ? TRunResult : TReturning[], "sqlite">, SQLWrapper
{ {
+1 -1
View File
@@ -5,7 +5,7 @@
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"typecheck": "tsgo --noEmit", "typecheck": "tsgo -b",
"dev": "vite dev", "dev": "vite dev",
"build": "vite build", "build": "vite build",
"build:cloudflare": "OPENCODE_DEPLOYMENT_TARGET=cloudflare vite build", "build:cloudflare": "OPENCODE_DEPLOYMENT_TARGET=cloudflare vite build",
+2 -1
View File
@@ -16,5 +16,6 @@
"paths": { "paths": {
"~/*": ["./src/*"] "~/*": ["./src/*"]
} }
} },
"references": [{ "path": "../core" }]
} }
+1 -1
View File
@@ -4,7 +4,7 @@ import type { Session } from "@opencode-ai/schema/session"
import type { SessionMessage } from "@opencode-ai/schema/session-message" import type { SessionMessage } from "@opencode-ai/schema/session-message"
import type { Hooks, Transform } from "./registration.js" import type { Hooks, Transform } from "./registration.js"
interface ToolDraft { export interface ToolDraft {
add< add<
Input extends Tool.ValueSchema<any>, Input extends Tool.ValueSchema<any>,
Output extends Tool.ValueSchema<any> | undefined, Output extends Tool.ValueSchema<any> | undefined,
+1 -1
View File
@@ -9,7 +9,7 @@
}, },
"scripts": { "scripts": {
"test": "bun test --timeout 5000", "test": "bun test --timeout 5000",
"typecheck": "tsgo --noEmit" "typecheck": "tsgo -b"
}, },
"dependencies": { "dependencies": {
"@opencode-ai/client": "workspace:*", "@opencode-ai/client": "workspace:*",
+2 -1
View File
@@ -4,5 +4,6 @@
"compilerOptions": { "compilerOptions": {
"lib": ["ESNext", "DOM", "DOM.Iterable"], "lib": ["ESNext", "DOM", "DOM.Iterable"],
"noUncheckedIndexedAccess": false "noUncheckedIndexedAccess": false
} },
"references": [{ "path": "../core" }]
} }
+1 -1
View File
@@ -10,7 +10,7 @@
}, },
"scripts": { "scripts": {
"test": "bun test --only-failures", "test": "bun test --only-failures",
"typecheck": "tsgo --noEmit" "typecheck": "tsgo -b"
}, },
"dependencies": { "dependencies": {
"@effect/platform-node": "catalog:", "@effect/platform-node": "catalog:",
+2 -1
View File
@@ -4,5 +4,6 @@
"compilerOptions": { "compilerOptions": {
"lib": ["ESNext", "DOM", "DOM.Iterable"], "lib": ["ESNext", "DOM", "DOM.Iterable"],
"noUncheckedIndexedAccess": false "noUncheckedIndexedAccess": false
} },
"references": [{ "path": "../core" }]
} }
+1 -1
View File
@@ -25,7 +25,7 @@
"./v2/prompt-input/types": "./src/v2/components/prompt-input/types.ts" "./v2/prompt-input/types": "./src/v2/components/prompt-input/types.ts"
}, },
"scripts": { "scripts": {
"typecheck": "tsgo --noEmit", "typecheck": "tsgo -b",
"test": "bun test src --only-failures" "test": "bun test src --only-failures"
}, },
"devDependencies": { "devDependencies": {
+2 -1
View File
@@ -15,5 +15,6 @@
"strict": true, "strict": true,
"types": ["vite/client", "bun"] "types": ["vite/client", "bun"]
}, },
"exclude": ["**/*.stories.*", "**/*.mdx"] "exclude": ["**/*.stories.*", "**/*.mdx"],
"references": [{ "path": "../core" }]
} }
+1 -1
View File
@@ -14,7 +14,7 @@
"./recording": "./src/recording.ts" "./recording": "./src/recording.ts"
}, },
"scripts": { "scripts": {
"typecheck": "tsgo --noEmit" "typecheck": "tsgo -b"
}, },
"dependencies": { "dependencies": {
"@fontsource/commit-mono": "5.2.5", "@fontsource/commit-mono": "5.2.5",

Some files were not shown because too many files have changed in this diff Show More