Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/core/src/plugin/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,7 @@ export const make = Effect.fn("PluginHost.make")(function* (
prompt: sessions.prompt,
generate: (input) => sessions.generate(input).pipe(Effect.map((text) => ({ text }))),
command: (input) => sessions.command({ ...input, command: input.name }),
compact: sessions.compact,
update: Effect.fn(function* (input) {
yield* sessions.get(input.sessionID)
if (input.title !== undefined) yield* sessions.rename({ sessionID: input.sessionID, title: input.title })
Expand Down
1 change: 1 addition & 0 deletions packages/core/test/plugin/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ export function host(overrides: Overrides = {}): Plugin.Context {
prompt: overrides.session?.prompt ?? (() => Effect.die("unused session.prompt")),
generate: overrides.session?.generate ?? (() => Effect.die("unused session.generate")),
command: overrides.session?.command ?? (() => Effect.die("unused session.command")),
compact: overrides.session?.compact ?? (() => Effect.die("unused session.compact")),
update: overrides.session?.update ?? (() => Effect.die("unused session.update")),
move: overrides.session?.move ?? (() => Effect.die("unused session.move")),
synthetic: overrides.session?.synthetic ?? (() => Effect.die("unused session.synthetic")),
Expand Down
52 changes: 52 additions & 0 deletions packages/core/test/plugin/session-compact.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { expect } from "bun:test"
import { Plugin } from "@opencode/core/plugin"
import { PluginHost } from "@opencode/core/plugin/host"
import { PluginPromise } from "@opencode/core/plugin/promise"
import { Session } from "@opencode/core/session"
import { SessionMessage } from "@opencode/schema/session-message"
import { Effect } from "effect"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"

const it = testEffect(PluginTestLayer)

it.live("Effect plugins can admit session compaction", () =>
Effect.gen(function* () {
const plugins = yield* Plugin.Service
const sessions = yield* Session.Service
const context = yield* PluginHost.make(plugins)
const created = yield* context.session.create({ title: "Plugin compaction" })
const id = SessionMessage.ID.create()

const admitted = yield* context.session.compact({ sessionID: created.id, id, delivery: "queue" })

expect(admitted).toMatchObject({ id, sessionID: created.id, type: "compaction", delivery: "queue" })
expect(yield* sessions.inbox(created.id)).toContainEqual(admitted)
expect(yield* context.session.compact({ sessionID: Session.ID.create() }).pipe(Effect.flip)).toMatchObject({
_tag: "Session.NotFoundError",
})
}),
)

it.live("Promise plugins can admit session compaction", () =>
Effect.gen(function* () {
const plugins = yield* Plugin.Service
const sessions = yield* Session.Service
const context = yield* PluginHost.make(plugins)
const created = yield* context.session.create({ title: "Promise plugin compaction" })
const id = SessionMessage.ID.create()

yield* PluginPromise.fromPromise({
id: "test.session-compact",
async setup(ctx) {
const admitted = await ctx.session.compact({ sessionID: created.id, id })
expect(admitted).toMatchObject({ id, sessionID: created.id, type: "compaction", delivery: "steer" })
await expect(ctx.session.compact({ sessionID: Session.ID.create() })).rejects.toThrow()
},
}).effect(context)

expect(yield* sessions.inbox(created.id)).toContainEqual(
expect.objectContaining({ id, type: "compaction", delivery: "steer" }),
)
}),
)
1 change: 1 addition & 0 deletions packages/plugin/src/effect/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ export type SessionDomain = Pick<
| "prompt"
| "generate"
| "command"
| "compact"
| "synthetic"
| "interrupt"
| "update"
Expand Down
1 change: 1 addition & 0 deletions packages/plugin/src/promise/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,7 @@ export function fromPromise(plugin: Plugin) {
prompt: adaptApiMethod(SessionEndpoints["session.prompt"], host.session.prompt),
generate: adaptApiMethod(SessionEndpoints["session.generate"], host.session.generate),
command: adaptApiMethod(SessionEndpoints["session.command"], host.session.command),
compact: adaptApiMethod(SessionEndpoints["session.compact"], host.session.compact),
synthetic: adaptApiMethod(SessionEndpoints["session.synthetic"], host.session.synthetic),
interrupt: adaptApiMethod(SessionEndpoints["session.interrupt"], host.session.interrupt),
update: adaptApiMethod(SessionEndpoints["session.update"], host.session.update),
Expand Down
1 change: 1 addition & 0 deletions packages/plugin/src/promise/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ export type SessionDomain = Pick<
| "prompt"
| "generate"
| "command"
| "compact"
| "synthetic"
| "interrupt"
| "update"
Expand Down
12 changes: 12 additions & 0 deletions services/www/src/docs/content/build/plugins/effect.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,17 @@ effect: (ctx) =>
}),
```

Request compaction with `compact`. It returns the admitted inbox item, not a completed summary; compaction runs at
the next step boundary by default. Pass `delivery: "queue"` to preserve queue order instead.

```ts
effect: (ctx) =>
Effect.gen(function* () {
const created = yield* ctx.session.create({ title: "Review" }).pipe(Effect.orDie)
const compaction = yield* ctx.session.compact({ sessionID: created.id }).pipe(Effect.orDie)
}),
```

Schemas: [`Session.Info`](/api#schema-Session.Info), [`Model.Ref`](/api#schema-Model.Ref),
[`Session.Inbox.User`](/api#schema-Session.Inbox.User),
[`Session.Inbox.Synthetic`](/api#schema-Session.Inbox.Synthetic).
Expand All @@ -846,6 +857,7 @@ type SessionDomain = Pick<
| "prompt"
| "generate"
| "command"
| "compact"
| "synthetic"
| "interrupt"
| "rename"
Expand Down
8 changes: 8 additions & 0 deletions services/www/src/docs/content/build/plugins/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,13 @@ Remove a temporary session with `remove`. Removal stops active execution and rec
await ctx.session.remove({ sessionID })
```

Request compaction with `compact`. It returns the admitted inbox item, not a completed summary; compaction runs at
the next step boundary by default. Pass `delivery: "queue"` to preserve queue order instead.

```ts
const compaction = await ctx.session.compact({ sessionID })
```

#### Reference

Schemas: [`Session.Info`](/api#schema-Session.Info), [`Model.Ref`](/api#schema-Model.Ref),
Expand All @@ -796,6 +803,7 @@ interface SessionContext {
prompt(input: SessionPromptInput, requestOptions?: RequestOptions): Promise<SessionInboxUser>
generate(input: SessionGenerateInput, requestOptions?: RequestOptions): Promise<{ text: string }>
command(input: SessionCommandInput, requestOptions?: RequestOptions): Promise<SessionInboxUser>
compact(input: SessionCompactInput, requestOptions?: RequestOptions): Promise<SessionInboxCompaction>
synthetic(input: SessionSyntheticInput, requestOptions?: RequestOptions): Promise<SessionInboxSynthetic>
interrupt(input: SessionInterruptInput, requestOptions?: RequestOptions): Promise<void>
rename(input: SessionRenameInput, requestOptions?: RequestOptions): Promise<void>
Expand Down
Loading