Skip to content

Commit 2f5df54

Browse files
committed
feat(server): POST/PUT /todo endpoints for plugin todo management
Adds create (POST) and bulk-update (PUT) routes for session todos. The new Todo.add() provides O(1) single-item insertion instead of the previous delete-all + insert-all pattern. Todo.update() is exported for the PUT bulk-replace route. Old todo routes (at the end of the chain) are replaced with improved versions placed after the GET route.
1 parent 3a505a0 commit 2f5df54

2 files changed

Lines changed: 82 additions & 49 deletions

File tree

packages/opencode/src/server/routes/session.ts

Lines changed: 45 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,51 @@ export const SessionRoutes = lazy(() =>
214214
return c.json(todos)
215215
},
216216
)
217+
.post(
218+
"/:sessionID/todo",
219+
describeRoute({
220+
summary: "Create session todo",
221+
description: "Create a new todo item for the session.",
222+
operationId: "session.todo.create",
223+
responses: {
224+
200: { description: "Created todo", content: { "application/json": { schema: resolver(Todo.Info) } } },
225+
...errors(400, 404),
226+
},
227+
}),
228+
validator("param", z.object({ sessionID: SessionID.zod })),
229+
validator(
230+
"json",
231+
z.object({ content: z.string(), status: z.string().optional(), priority: z.string().optional() }),
232+
),
233+
async (c) => {
234+
const sessionID = c.req.valid("param").sessionID
235+
const body = c.req.valid("json")
236+
const todo = Todo.add(sessionID, body)
237+
return c.json(todo)
238+
},
239+
)
240+
.put(
241+
"/:sessionID/todo",
242+
describeRoute({
243+
summary: "Update session todos",
244+
description: "Replace all todos for a session (bulk update).",
245+
operationId: "session.todo.update",
246+
responses: {
247+
200: {
248+
description: "Updated todos",
249+
content: { "application/json": { schema: resolver(Todo.Info.array()) } },
250+
},
251+
...errors(400, 404),
252+
},
253+
}),
254+
validator("param", z.object({ sessionID: SessionID.zod })),
255+
validator("json", z.object({ todos: z.array(Todo.Info) })),
256+
async (c) => {
257+
const sessionID = c.req.valid("param").sessionID
258+
const body = c.req.valid("json")
259+
await Todo.update({ sessionID, todos: body.todos })
260+
return c.json(body.todos)
261+
},
217262
)
218263
.post(
219264
"/",
@@ -1348,54 +1393,5 @@ export const SessionRoutes = lazy(() =>
13481393
}
13491394
})
13501395
},
1351-
)
1352-
// Todo CRUD — create and bulk update
1353-
.post(
1354-
"/:sessionID/todo",
1355-
describeRoute({
1356-
summary: "Create session todo",
1357-
operationId: "session.todo.create",
1358-
responses: {
1359-
200: {
1360-
description: "Updated todo list",
1361-
content: { "application/json": { schema: resolver(Todo.Info.array()) } },
1362-
},
1363-
...errors(400, 404),
1364-
},
1365-
}),
1366-
validator("param", z.object({ sessionID: SessionID.zod })),
1367-
validator("json", Todo.Info),
1368-
async (c) => {
1369-
const sessionID = c.req.valid("param").sessionID
1370-
await Session.get(sessionID)
1371-
const todo = c.req.valid("json")
1372-
const existing = await Todo.get(sessionID)
1373-
const todos = [...existing, todo]
1374-
await Todo.update({ sessionID, todos })
1375-
return c.json(todos)
1376-
},
1377-
)
1378-
.put(
1379-
"/:sessionID/todo",
1380-
describeRoute({
1381-
summary: "Update session todos",
1382-
operationId: "session.todo.update",
1383-
responses: {
1384-
200: {
1385-
description: "Updated todo list",
1386-
content: { "application/json": { schema: resolver(Todo.Info.array()) } },
1387-
},
1388-
...errors(400, 404),
1389-
},
1390-
}),
1391-
validator("param", z.object({ sessionID: SessionID.zod })),
1392-
validator("json", z.object({ todos: Todo.Info.array() })),
1393-
async (c) => {
1394-
const sessionID = c.req.valid("param").sessionID
1395-
await Session.get(sessionID)
1396-
const body = c.req.valid("json")
1397-
await Todo.update({ sessionID, todos: body.todos })
1398-
return c.json(body.todos)
1399-
},
14001396
),
14011397
)

packages/opencode/src/session/todo.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,41 @@ export namespace Todo {
8888
export async function get(sessionID: SessionID) {
8989
return runPromise((svc) => svc.get(sessionID))
9090
}
91+
92+
export async function update(input: { sessionID: SessionID; todos: Info[] }) {
93+
return runPromise((svc) => svc.update(input))
94+
}
95+
96+
/** O(1) add — direct INSERT instead of delete-all + insert-all */
97+
export function add(sessionID: SessionID, todo: Partial<Info> & { content: string }) {
98+
const current = Database.use((db) =>
99+
db.select().from(TodoTable).where(eq(TodoTable.session_id, sessionID)).orderBy(asc(TodoTable.position)).all(),
100+
)
101+
const newTodo: Info = {
102+
content: todo.content,
103+
status: todo.status ?? "pending",
104+
priority: todo.priority ?? "medium",
105+
}
106+
Database.use((db) => {
107+
db.insert(TodoTable)
108+
.values({
109+
session_id: sessionID,
110+
content: newTodo.content,
111+
status: newTodo.status,
112+
priority: newTodo.priority,
113+
position: current.length,
114+
})
115+
.run()
116+
})
117+
const updated = [
118+
...current.map((row) => ({
119+
content: row.content,
120+
status: row.status,
121+
priority: row.priority,
122+
})),
123+
newTodo,
124+
]
125+
void Bus.publish(Event.Updated, { sessionID, todos: updated })
126+
return newTodo
127+
}
91128
}

0 commit comments

Comments
 (0)