11export * as Session from "./session.js"
22export * from "./session/schema.js"
33
4- import { DateTime , Effect , Layer , Context , Stream } from "effect"
4+ import { DateTime , Effect , Layer , Context , Option , Stream } from "effect"
55import { LLMClient } from "@opencode/ai/effect"
66import { ListAnchor , ListCursor , ListFilter } from "@opencode/schema/session"
77import { and , desc , eq } from "drizzle-orm"
@@ -16,6 +16,7 @@ import { Instance } from "./instance/service.js"
1616import { Database } from "./database/database.js"
1717import { SessionProjector } from "./session/projector.js"
1818import { SessionMessageTable } from "./session/sql.js"
19+ import { SessionBlob } from "./session/blob.js"
1920import { SessionSchema } from "./session/schema.js"
2021import { RelativePath } from "./schema.js"
2122import { Agent } from "@opencode/schema/agent"
@@ -147,6 +148,15 @@ export interface Interface {
147148 sessionID : SessionSchema . ID
148149 messageID : SessionMessage . ID
149150 } ) => Effect . Effect < SessionMessage . Info | undefined >
151+ /** Experimental: {@link Interface.messages} with files stored in `blob` left as `media:` refs when the session's location enables `experimental.media_blob`. */
152+ readonly messagesWithRefs : (
153+ input : SessionStore . MessagesInput ,
154+ ) => Effect . Effect < SessionMessage . MediaInfo [ ] , NotFoundError | MessageDecodeError >
155+ /** Experimental: {@link Interface.message} with files stored in `blob` left as `media:` refs when the session's location enables `experimental.media_blob`. */
156+ readonly messageWithRefs : ( input : {
157+ sessionID : SessionSchema . ID
158+ messageID : SessionMessage . ID
159+ } ) => Effect . Effect < SessionMessage . MediaInfo | undefined >
150160 readonly context : (
151161 sessionID : SessionSchema . ID ,
152162 ) => Effect . Effect < SessionMessage . Info [ ] , NotFoundError | MessageDecodeError >
@@ -166,6 +176,8 @@ export interface Interface {
166176 * unhandled compaction barriers.
167177 */
168178 readonly inbox : ( sessionID : SessionSchema . ID ) => Effect . Effect < SessionInbox . Info [ ] , NotFoundError >
179+ /** Experimental: {@link Interface.inbox} with files stored in `blob` left as `media:` refs when the session's location enables `experimental.media_blob`. */
180+ readonly inboxWithRefs : ( sessionID : SessionSchema . ID ) => Effect . Effect < SessionInbox . MediaInfo [ ] , NotFoundError >
169181 readonly cancelInbox : ( input : InboxItemRef ) => Effect . Effect < void , NotFoundError | InboxConflictError >
170182 readonly steerInbox : ( input : InboxItemRef ) => Effect . Effect < void , NotFoundError | InboxConflictError >
171183 readonly queueInbox : ( input : InboxItemRef ) => Effect . Effect < void , NotFoundError | InboxConflictError >
@@ -268,6 +280,16 @@ const layer = Layer.effect(
268280 const environments = yield * SessionEnvironment . Service
269281 const locations = yield * LocationServiceMap . Service
270282 const sessions = yield * Session . make ( )
283+ // `experimental.media_blob` as the session's own location configures it, where its media is written.
284+ const keepsMediaRefs = ( session : SessionSchema . Info ) =>
285+ SessionBlob . enabled . pipe (
286+ instances . provide ( session ) ,
287+ Effect . catch ( ( cause ) =>
288+ Effect . logWarning ( "could not resolve experimental.media_blob; serving media inline" , { cause } ) . pipe (
289+ Effect . as ( false ) ,
290+ ) ,
291+ ) ,
292+ )
271293 const isDurableSessionEvent = ( item : Bus . LogItem ) : item is SessionEvent . DurableEvent =>
272294 Object . hasOwn ( SessionEvent . Durable . cases , item . type )
273295
@@ -413,6 +435,18 @@ const layer = Layer.effect(
413435 return yield * store . messages ( input )
414436 } ) ,
415437 message : ( input ) => sessions . forSession ( input . sessionID ) . message ( input . messageID ) ,
438+ messagesWithRefs : Effect . fn ( "Session.messagesWithRefs" ) ( function * ( input ) {
439+ const session = yield * result . get ( input . sessionID )
440+ if ( ! ( yield * keepsMediaRefs ( session ) ) ) return yield * store . messages ( input )
441+ return yield * store . messagesWithRefs ( input )
442+ } ) ,
443+ messageWithRefs : Effect . fn ( "Session.messageWithRefs" ) ( function * ( input ) {
444+ const session = yield * result . get ( input . sessionID ) . pipe ( Effect . option )
445+ if ( Option . isNone ( session ) ) return undefined
446+ if ( ! ( yield * keepsMediaRefs ( session . value ) ) ) return yield * result . message ( input )
447+ const stored = yield * store . messageWithRefs ( input . messageID )
448+ return stored ?. sessionID === input . sessionID ? stored . message : undefined
449+ } ) ,
416450 context : Effect . fn ( "Session.context" ) ( function * ( sessionID ) {
417451 yield * result . get ( sessionID )
418452 return yield * store . context ( sessionID )
@@ -429,6 +463,11 @@ const layer = Layer.effect(
429463 } )
430464 } ) ,
431465 inbox : ( sessionID ) => sessions . forSession ( sessionID ) . inbox ( ) ,
466+ inboxWithRefs : Effect . fn ( "Session.inboxWithRefs" ) ( function * ( sessionID ) {
467+ const session = yield * result . get ( sessionID )
468+ if ( ! ( yield * keepsMediaRefs ( session ) ) ) return yield * result . inbox ( sessionID )
469+ return yield * SessionInbox . listWithRefs ( db , sessionID )
470+ } ) ,
432471 cancelInbox : ( input ) => sessions . forSession ( input . sessionID ) . cancelInbox ( input . inboxID ) ,
433472 steerInbox : ( input ) => sessions . forSession ( input . sessionID ) . steerInbox ( input . inboxID ) ,
434473 queueInbox : ( input ) => sessions . forSession ( input . sessionID ) . queueInbox ( input . inboxID ) ,
0 commit comments