| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- import { Bus } from "@/bus"
- import { Config } from "@/config/config"
- import { ulid } from "ulid"
- import { Provider } from "@/provider/provider"
- import { Session } from "@/session"
- import { MessageV2 } from "@/session/message-v2"
- import { Storage } from "@/storage/storage"
- import { Log } from "@/util/log"
- import type * as SDK from "@opencode-ai/sdk"
- export namespace ShareNext {
- const log = Log.create({ service: "share-next" })
- export async function init() {
- const config = await Config.get()
- if (!config.enterprise) return
- Bus.subscribe(Session.Event.Updated, async (evt) => {
- await sync(evt.properties.info.id, [
- {
- type: "session",
- data: evt.properties.info,
- },
- ])
- })
- Bus.subscribe(MessageV2.Event.Updated, async (evt) => {
- await sync(evt.properties.info.sessionID, [
- {
- type: "message",
- data: evt.properties.info,
- },
- ])
- if (evt.properties.info.role === "user") {
- await sync(evt.properties.info.sessionID, [
- {
- type: "model",
- data: [
- await Provider.getModel(evt.properties.info.model.providerID, evt.properties.info.model.modelID).then(
- (m) => m,
- ),
- ],
- },
- ])
- }
- })
- Bus.subscribe(MessageV2.Event.PartUpdated, async (evt) => {
- await sync(evt.properties.part.sessionID, [
- {
- type: "part",
- data: evt.properties.part,
- },
- ])
- })
- Bus.subscribe(Session.Event.Diff, async (evt) => {
- await sync(evt.properties.sessionID, [
- {
- type: "session_diff",
- data: evt.properties.diff,
- },
- ])
- })
- }
- export async function create(sessionID: string) {
- log.info("creating share", { sessionID })
- const url = await Config.get().then((x) => x.enterprise!.url)
- const result = await fetch(`${url}/api/share`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({ sessionID: sessionID }),
- })
- .then((x) => x.json())
- .then((x) => x as { id: string; url: string; secret: string })
- await Storage.write(["session_share", sessionID], result)
- fullSync(sessionID)
- return result
- }
- function get(sessionID: string) {
- return Storage.read<{
- id: string
- secret: string
- url: string
- }>(["session_share", sessionID])
- }
- type Data =
- | {
- type: "session"
- data: SDK.Session
- }
- | {
- type: "message"
- data: SDK.Message
- }
- | {
- type: "part"
- data: SDK.Part
- }
- | {
- type: "session_diff"
- data: SDK.FileDiff[]
- }
- | {
- type: "model"
- data: SDK.Model[]
- }
- const queue = new Map<string, { timeout: NodeJS.Timeout; data: Map<string, Data> }>()
- async function sync(sessionID: string, data: Data[]) {
- const existing = queue.get(sessionID)
- if (existing) {
- for (const item of data) {
- existing.data.set("id" in item ? (item.id as string) : ulid(), item)
- }
- return
- }
- const dataMap = new Map<string, Data>()
- for (const item of data) {
- dataMap.set("id" in item ? (item.id as string) : ulid(), item)
- }
- const timeout = setTimeout(async () => {
- const queued = queue.get(sessionID)
- if (!queued) return
- queue.delete(sessionID)
- const url = await Config.get().then((x) => x.enterprise!.url)
- const share = await get(sessionID)
- if (!share) return
- await fetch(`${url}/api/share/${share.id}/sync`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- secret: share.secret,
- data: Array.from(queued.data.values()),
- }),
- })
- }, 1000)
- queue.set(sessionID, { timeout, data: dataMap })
- }
- export async function remove(sessionID: string) {
- log.info("removing share", { sessionID })
- const url = await Config.get().then((x) => x.enterprise!.url)
- const share = await get(sessionID)
- if (!share) return
- await fetch(`${url}/api/share/${share.id}`, {
- method: "DELETE",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- secret: share.secret,
- }),
- })
- await Storage.remove(["session_share", share.id])
- }
- async function fullSync(sessionID: string) {
- log.info("full sync", { sessionID })
- const session = await Session.get(sessionID)
- const diffs = await Session.diff(sessionID)
- const messages = await Array.fromAsync(MessageV2.stream(sessionID))
- const models = await Promise.all(
- messages
- .filter((m) => m.info.role === "user")
- .map((m) => (m.info as SDK.UserMessage).model)
- .map((m) => Provider.getModel(m.providerID, m.modelID).then((m) => m)),
- )
- await sync(sessionID, [
- {
- type: "session",
- data: session,
- },
- ...messages.map((x) => ({
- type: "message" as const,
- data: x.info,
- })),
- ...messages.flatMap((x) => x.parts.map((y) => ({ type: "part" as const, data: y }))),
- {
- type: "session_diff",
- data: diffs,
- },
- {
- type: "model",
- data: models,
- },
- ])
- }
- }
|