refactor: simplify cloud package state (#43970)

This commit is contained in:
Kit Langton 2026-08-21 15:25:45 -04:00 committed by GitHub
parent d633d794c2
commit 88788941df
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 136 additions and 73 deletions

View file

@ -1,5 +1,4 @@
import { Message, Model, Part, Session, SnapshotFileDiff } from "@opencode-ai/sdk/v2"
import { iife } from "@opencode-ai/core/util/iife"
import type { SessionInfo, SessionMessageInfo } from "@opencode-ai/client/promise"
import z from "zod"
import { Storage } from "./storage"
@ -210,38 +209,7 @@ export namespace Share {
const share = await get(input.share.id)
if (!share) throw new Errors.NotFound(input.share.id)
if (share.secret !== input.share.secret) throw new Errors.InvalidSecret(input.share.id)
const promises = []
for (const item of input.data) {
promises.push(
iife(async () => {
switch (item.type) {
case "session":
await Storage.write(["share_data", input.share.id, "session"], item.data)
break
case "message": {
const data = item.data as Message
await Storage.write(["share_data", input.share.id, "message", data.id], item.data)
break
}
case "messages":
await Storage.write(["share_data", input.share.id, "messages", item.data.sessionID], item.data)
break
case "part": {
const data = item.data as Part
await Storage.write(["share_data", input.share.id, "part", data.messageID, data.id], item.data)
break
}
case "session_diff":
await Storage.write(["share_data", input.share.id, "session_diff"], item.data)
break
case "model":
await Storage.write(["share_data", input.share.id, "model"], item.data)
break
}
}),
)
}
await Promise.all(promises)
await Promise.all(input.data.map((item) => Storage.write(["share_data", input.share.id, key(item)], item.data)))
},
)

View file

@ -289,4 +289,52 @@ describe.concurrent("core.share", () => {
await Share.remove({ id: share.id, secret: share.secret })
})
test("should sync all legacy data variants to their canonical paths", async () => {
const sessionID = Identifier.descending()
const share = await Share.create({ sessionID })
const data: Share.Data[] = [
{
type: "session",
data: {
id: sessionID,
slug: "session",
projectID: "project",
directory: "/",
title: "Session",
version: "1",
time: { created: 1, updated: 1 },
},
},
{
type: "message",
data: {
id: "msg1",
sessionID,
role: "user",
time: { created: 1 },
agent: "build",
model: { providerID: "provider", modelID: "model" },
},
},
{ type: "messages", data: { sessionID, messages: [] } },
{
type: "part",
data: { id: "part1", sessionID, messageID: "msg1", type: "text", text: "Hello" },
},
{ type: "session_diff", data: [] },
{ type: "model", data: [] },
]
await Share.syncOld({
share: { id: share.id, secret: share.secret },
data,
})
const paths = ["session", "message/msg1", `messages/${sessionID}`, "part/msg1/part1", "session_diff", "model"]
const stored = await Promise.all(paths.map((path) => Storage.read(["share_data", share.id, ...path.split("/")])))
expect(stored).toEqual(data.map((item) => item.data))
await Share.remove({ id: share.id, secret: share.secret })
})
})

View file

@ -19,7 +19,7 @@ const opencode = await createOpencode({
})
console.log("✅ Opencode server ready")
const sessions = new Map<string, { client: any; server: any; sessionId: string; channel: string; thread: string }>()
const sessions = new Map<string, { sessionId: string; channel: string; thread: string }>()
void (async () => {
const events = await opencode.client.event.subscribe()
for await (const event of events.stream) {
@ -27,7 +27,7 @@ void (async () => {
const part = event.properties.part
if (part.type === "tool") {
// Find the session for this tool update
for (const [_sessionKey, session] of sessions.entries()) {
for (const session of sessions.values()) {
if (session.sessionId === part.sessionID) {
void handleToolUpdate(part, session.channel, session.thread)
break
@ -66,16 +66,14 @@ app.message(async ({ message, say }) => {
console.log("✅ Processing message:", message.text)
const channel = message.channel
const thread = (message as any).thread_ts || message.ts
const thread = ("thread_ts" in message && typeof message.thread_ts === "string" && message.thread_ts) || message.ts
const sessionKey = `${channel}-${thread}`
let session = sessions.get(sessionKey)
if (!session) {
console.log("🆕 Creating new opencode session...")
const { client, server } = opencode
const createResult = await client.session.create({
const createResult = await opencode.client.session.create({
body: { title: `Slack thread ${thread}` },
})
@ -90,10 +88,10 @@ app.message(async ({ message, say }) => {
console.log("✅ Created opencode session:", createResult.data.id)
session = { client, server, sessionId: createResult.data.id, channel, thread }
session = { sessionId: createResult.data.id, channel, thread }
sessions.set(sessionKey, session)
const shareResult = await client.session.share({ path: { id: createResult.data.id } })
const shareResult = await opencode.client.session.share({ path: { id: createResult.data.id } })
if (!shareResult.error && shareResult.data) {
const sessionUrl = shareResult.data.share?.url
console.log("🔗 Session shared:", sessionUrl)
@ -102,7 +100,7 @@ app.message(async ({ message, say }) => {
}
console.log("📝 Sending to opencode:", message.text)
const result = await session.client.session.prompt({
const result = await opencode.client.session.prompt({
path: { id: session.sessionId },
body: { parts: [{ type: "text", text: message.text }] },
})
@ -118,16 +116,12 @@ app.message(async ({ message, say }) => {
return
}
const response = result.data
// Build response text
const responseText =
response.info?.content ||
response.parts
?.filter((p: any) => p.type === "text")
.map((p: any) => p.text)
.join("\n") ||
"I received your message but didn't have a response."
result.data.parts
.filter((part) => part.type === "text")
.map((part) => part.text)
.join("\n") || "I received your message but didn't have a response."
console.log("💬 Sending response:", responseText)

View file

@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test"
import { channelsForRef, resolveChannel, validGitHubClaims } from "./index"
import worker, { channelsForRef, resolveChannel, validGitHubClaims } from "./index"
const claims = {
repository: "anomalyco/opencode",
@ -42,3 +42,69 @@ test("routes the retired next channel to beta", () => {
expect(resolveChannel("next")).toBe("beta")
expect(resolveChannel("dev")).toBe("dev")
})
const artifact = {
channel: "beta",
name: "opencode",
distribution: "darwin-arm64",
version: "1.0.0",
metadata: "{}",
active: 1,
time_created: 1,
time_updated: 2,
}
test.each([
["/api/next", ["beta"], { channel: "beta", artifacts: [{ ...artifact, metadata: {}, active: true }] }],
[
"/api/next/opencode",
["beta", "opencode"],
{ channel: "beta", name: "opencode", artifacts: [{ ...artifact, metadata: {}, active: true }] },
],
[
"/api/next/opencode/darwin-arm64",
["beta", "opencode", "darwin-arm64"],
{ ...artifact, metadata: {}, active: true },
],
])("routes GET %s", async (path, expectedBindings, expectedBody) => {
const bindings: unknown[][] = []
const statement = {
bind(...values: unknown[]) {
bindings.push(values)
return statement
},
async all() {
return { results: [artifact] }
},
async first() {
return artifact
},
}
const db = {
prepare() {
return statement
},
} as unknown as D1Database
const response = await worker.fetch(new Request(`https://update.opencode.ai${path}`), { DB: db })
expect(response.status).toBe(200)
expect(await response.text()).toBe(JSON.stringify(expectedBody))
expect(bindings).toEqual([expectedBindings])
})
test.each(["/api", "/v1/dev", "/api/dev/opencode/darwin-arm64/extra", "/api/dev/opencode/darwin$arm64"])(
"returns 404 for GET %s",
async (path) => {
const db = {
prepare() {
throw new Error("Invalid routes must not query the database")
},
} as unknown as D1Database
const response = await worker.fetch(new Request(`https://update.opencode.ai${path}`), { DB: db })
expect(response.status).toBe(404)
expect(await response.text()).toBe("Not found")
},
)

View file

@ -31,7 +31,7 @@ const audience = "https://update.opencode.ai"
const githubKeys = createRemoteJWKSet(new URL("https://token.actions.githubusercontent.com/.well-known/jwks"))
export default {
async fetch(request, env): Promise<Response> {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url)
if (url.pathname === "/") return json({ service: "opencode-updates" })
@ -40,28 +40,15 @@ export default {
if (url.pathname === "/api/publish" && request.method === "POST") return publishArtifact(request, env)
if (request.method !== "GET") return new Response("Method not allowed", { status: 405 })
const segments = url.pathname.split("/").filter(Boolean)
if (segments.length === 2 && segments[0] === "api" && validIdentifier(segments[1])) {
return channel(env.DB, resolveChannel(segments[1]))
const [root, ...path] = url.pathname.split("/").filter(Boolean)
if (root !== "api" || path.length < 1 || path.length > 3 || !path.every(validIdentifier)) {
return new Response("Not found", { status: 404 })
}
if (
segments.length === 3 &&
segments[0] === "api" &&
validIdentifier(segments[1]) &&
validIdentifier(segments[2])
) {
return artifactName(env.DB, resolveChannel(segments[1]), segments[2])
}
if (
segments.length === 4 &&
segments[0] === "api" &&
validIdentifier(segments[1]) &&
validIdentifier(segments[2]) &&
validIdentifier(segments[3])
) {
return artifactDistribution(env.DB, resolveChannel(segments[1]), segments[2], segments[3])
}
return new Response("Not found", { status: 404 })
const resolved = resolveChannel(path[0])
if (path.length === 1) return channel(env.DB, resolved)
if (path.length === 2) return artifactName(env.DB, resolved, path[1])
return artifactDistribution(env.DB, resolved, path[1], path[2])
},
} satisfies ExportedHandler<Env>