mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-22 03:23:27 +00:00
refactor: simplify cloud package state (#43970)
This commit is contained in:
parent
d633d794c2
commit
88788941df
5 changed files with 136 additions and 73 deletions
|
|
@ -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)))
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue