Skip to content
Open
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
2 changes: 0 additions & 2 deletions .github/workflows/stats.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,3 @@ jobs:
git add STATS.md
git diff --staged --quiet || git commit -m "ignore: update download stats $(date -I)"
git push
env:
POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
9 changes: 9 additions & 0 deletions .opencode/agent/gemini.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
description: Delegates tasks to the local Gemini CLI agent
color: "#8E75FF"
tools:
- gemini
---

You are a proxy agent for the Gemini CLI.
When the user gives you a task, you MUST use the `gemini` tool to delegate the task to the Gemini CLI. Do not attempt to complete the task yourself using other tools. Pass the user's prompt directly into the tool. Wait for the tool to complete and then summarize the results for the user.
30 changes: 30 additions & 0 deletions .opencode/tool/gemini.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/// <reference path="../env.d.ts" />
import { tool } from "@opencode-ai/plugin"
import { spawn } from "child_process"

export default tool({
description: "Delegates a task to the Gemini CLI agent. Gemini will autonomously research, edit files, and run commands to complete the task.",
args: {
prompt: tool.schema.string().describe("The complete task description to send to Gemini."),
},
async execute(args) {
return new Promise((resolve, reject) => {
// Spawns the gemini CLI
const proc = spawn("gemini", ["-p", args.prompt], {
stdio: "pipe",
env: process.env,
})

let output = ""
proc.stdout.on("data", (data) => (output += data.toString()))
proc.stderr.on("data", (data) => (output += data.toString()))

proc.on("close", (code) => {
resolve(`Gemini exited with code ${code}.\n\nOutput:\n${output}`)
})
proc.on("error", (err) => {
reject(`Failed to start Gemini CLI: ${err.message}`)
})
})
},
})
13 changes: 0 additions & 13 deletions packages/console/app/src/component/email-signup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,6 @@ import { Show } from "solid-js"

const emailSignup = action(async (formData: FormData) => {
"use server"
const emailAddress = formData.get("email")!
const listId = "8b9bb82c-9d5f-11f0-975f-0df6fd1e4945"
const response = await fetch(`https://api.emailoctopus.com/lists/${listId}/contacts`, {
method: "PUT",
headers: {
Authorization: `Bearer ${Resource.EMAILOCTOPUS_API_KEY.value}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
email_address: emailAddress,
}),
})
console.log(response)
return true
})

Expand Down
12 changes: 0 additions & 12 deletions packages/console/function/src/log-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,6 @@ export default {
}
}
console.log(JSON.stringify(metrics, null, 2))

const ret = await fetch("https://api.honeycomb.io/1/events/zen", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Honeycomb-Event-Time": (event.eventTimestamp ?? Date.now()).toString(),
"X-Honeycomb-Team": Resource.HONEYCOMB_API_KEY.value,
},
body: JSON.stringify(metrics),
})
console.log(ret.status)
console.log(await ret.text())
}
},
}
254 changes: 0 additions & 254 deletions packages/function/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,262 +12,8 @@ type Env = {
WEB_DOMAIN: string
}

async function getFeishuTenantToken(): Promise<string> {
const response = await fetch("https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
app_id: Resource.FEISHU_APP_ID.value,
app_secret: Resource.FEISHU_APP_SECRET.value,
}),
})
const data = (await response.json()) as { tenant_access_token?: string }
if (!data.tenant_access_token) throw new Error("Failed to get Feishu tenant token")
return data.tenant_access_token
}

export class SyncServer extends DurableObject<Env> {
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env)
}
async fetch() {
console.log("SyncServer subscribe")

const webSocketPair = new WebSocketPair()
const [client, server] = Object.values(webSocketPair)

this.ctx.acceptWebSocket(server)

const data = await this.ctx.storage.list()
Array.from(data.entries())
.filter(([key, _]) => key.startsWith("session/"))
.map(([key, content]) => server.send(JSON.stringify({ key, content })))

return new Response(null, {
status: 101,
webSocket: client,
})
}

async webSocketMessage(ws, message) {}

async webSocketClose(ws, code, reason, wasClean) {
ws.close(code, "Durable Object is closing WebSocket")
}

async publish(key: string, content: any) {
const sessionID = await this.getSessionID()
if (
!key.startsWith(`session/info/${sessionID}`) &&
!key.startsWith(`session/message/${sessionID}/`) &&
!key.startsWith(`session/part/${sessionID}/`)
)
return new Response("Error: Invalid key", { status: 400 })

// store message
await this.env.Bucket.put(`share/${key}.json`, JSON.stringify(content), {
httpMetadata: {
contentType: "application/json",
},
})
await this.ctx.storage.put(key, content)
const clients = this.ctx.getWebSockets()
console.log("SyncServer publish", key, "to", clients.length, "subscribers")
for (const client of clients) {
client.send(JSON.stringify({ key, content }))
}
}

public async share(sessionID: string) {
let secret = await this.getSecret()
if (secret) return secret
secret = randomUUID()

await this.ctx.storage.put("secret", secret)
await this.ctx.storage.put("sessionID", sessionID)

return secret
}

public async getData() {
const data = (await this.ctx.storage.list()) as Map<string, any>
return Array.from(data.entries())
.filter(([key, _]) => key.startsWith("session/"))
.map(([key, content]) => ({ key, content }))
}

public async assertSecret(secret: string) {
if (secret !== (await this.getSecret())) throw new Error("Invalid secret")
}

private async getSecret() {
return this.ctx.storage.get<string>("secret")
}

private async getSessionID() {
return this.ctx.storage.get<string>("sessionID")
}

async clear() {
const sessionID = await this.getSessionID()
const list = await this.env.Bucket.list({
prefix: `session/message/${sessionID}/`,
limit: 1000,
})
for (const item of list.objects) {
await this.env.Bucket.delete(item.key)
}
await this.env.Bucket.delete(`session/info/${sessionID}`)
await this.ctx.storage.deleteAll()
}

static shortName(id: string) {
return id.substring(id.length - 8)
}
}

export default new Hono<{ Bindings: Env }>()
.get("/", (c) => c.text("Hello, world!"))
.post("/share_create", async (c) => {
const body = await c.req.json<{ sessionID: string }>()
const sessionID = body.sessionID
const short = SyncServer.shortName(sessionID)
const id = c.env.SYNC_SERVER.idFromName(short)
const stub = c.env.SYNC_SERVER.get(id)
const secret = await stub.share(sessionID)
return c.json({
secret,
url: `https://${c.env.WEB_DOMAIN}/s/${short}`,
})
})
.post("/share_delete", async (c) => {
const body = await c.req.json<{ sessionID: string; secret: string }>()
const sessionID = body.sessionID
const secret = body.secret
const id = c.env.SYNC_SERVER.idFromName(SyncServer.shortName(sessionID))
const stub = c.env.SYNC_SERVER.get(id)
await stub.assertSecret(secret)
await stub.clear()
return c.json({})
})
.post("/share_delete_admin", async (c) => {
const body = await c.req.json<{ sessionShortName: string; adminSecret: string }>()
const sessionShortName = body.sessionShortName
const adminSecret = body.adminSecret
if (adminSecret !== Resource.ADMIN_SECRET.value) throw new Error("Invalid admin secret")
const id = c.env.SYNC_SERVER.idFromName(sessionShortName)
const stub = c.env.SYNC_SERVER.get(id)
await stub.clear()
return c.json({})
})
.post("/share_sync", async (c) => {
const body = await c.req.json<{
sessionID: string
secret: string
key: string
content: any
}>()
const name = SyncServer.shortName(body.sessionID)
const id = c.env.SYNC_SERVER.idFromName(name)
const stub = c.env.SYNC_SERVER.get(id)
await stub.assertSecret(body.secret)
await stub.publish(body.key, body.content)
return c.json({})
})
.get("/share_poll", async (c) => {
const upgradeHeader = c.req.header("Upgrade")
if (!upgradeHeader || upgradeHeader !== "websocket") {
return c.text("Error: Upgrade header is required", { status: 426 })
}
const id = c.req.query("id")
console.log("share_poll", id)
if (!id) return c.text("Error: Share ID is required", { status: 400 })
const stub = c.env.SYNC_SERVER.get(c.env.SYNC_SERVER.idFromName(id))
return stub.fetch(c.req.raw)
})
.get("/share_data", async (c) => {
const id = c.req.query("id")
console.log("share_data", id)
if (!id) return c.text("Error: Share ID is required", { status: 400 })
const stub = c.env.SYNC_SERVER.get(c.env.SYNC_SERVER.idFromName(id))
const data = await stub.getData()

let info
const messages: Record<string, any> = {}
data.forEach((d) => {
const [root, type, ...splits] = d.key.split("/")
if (root !== "session") return
if (type === "info") {
info = d.content
return
}
if (type === "message") {
messages[d.content.id] = {
parts: [],
...d.content,
}
}
if (type === "part") {
messages[d.content.messageID].parts.push(d.content)
}
})

return c.json({ info, messages })
})
.post("/feishu", async (c) => {
const body = (await c.req.json()) as {
challenge?: string
event?: {
message?: {
message_id?: string
root_id?: string
parent_id?: string
chat_id?: string
content?: string
}
}
}
console.log(JSON.stringify(body, null, 2))
const challenge = body.challenge
if (challenge) return c.json({ challenge })

const content = body.event?.message?.content
const parsed =
typeof content === "string" && content.trim().startsWith("{")
? (JSON.parse(content) as {
text?: string
})
: undefined
const text = typeof parsed?.text === "string" ? parsed.text : typeof content === "string" ? content : ""

let message = text.trim().replace(/^@_user_\d+\s*/, "")
message = message.replace(/^aiden,?\s*/i, "<@759257817772851260> ")
if (!message) return c.json({ ok: true })

const threadId = body.event?.message?.root_id || body.event?.message?.message_id
if (threadId) message = `${message} [${threadId}]`

const response = await fetch(
`https://discord.com/api/v10/channels/${Resource.DISCORD_SUPPORT_CHANNEL_ID.value}/messages`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bot ${Resource.DISCORD_SUPPORT_BOT_TOKEN.value}`,
},
body: JSON.stringify({
content: `${message}`,
}),
},
)

if (!response.ok) {
console.error(await response.text())
return c.json({ error: "Discord bot message failed" }, { status: 502 })
}

return c.json({ ok: true })
})
/**
* Used by the GitHub action to get GitHub installation access token given the OIDC token
*/
Expand Down
6 changes: 0 additions & 6 deletions packages/opencode/src/agent/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,12 +284,6 @@ export namespace Agent {
const existing = await list()

const params = {
experimental_telemetry: {
isEnabled: cfg.experimental?.openTelemetry,
metadata: {
userId: cfg.username ?? "unknown",
},
},
temperature: 0.3,
messages: [
...system.map(
Expand Down
Loading
Loading