diff options
| author | Dax Raad <[email protected]> | 2025-05-18 02:43:01 -0400 |
|---|---|---|
| committer | Dax Raad <[email protected]> | 2025-05-26 12:40:17 -0400 |
| commit | d0d67029f4baad7389b5ba072379c2ff44a22dc4 (patch) | |
| tree | cb81e86662c14c20687bf5bac488dda911a9855e /js/src/session/session.ts | |
| parent | a34d020bc6b252e842f042d935c7a0e6444460cf (diff) | |
| download | opencode-d0d67029f4baad7389b5ba072379c2ff44a22dc4.tar.gz opencode-d0d67029f4baad7389b5ba072379c2ff44a22dc4.zip | |
process
Diffstat (limited to 'js/src/session/session.ts')
| -rw-r--r-- | js/src/session/session.ts | 176 |
1 files changed, 175 insertions, 1 deletions
diff --git a/js/src/session/session.ts b/js/src/session/session.ts index 0925584de..4ad40f32a 100644 --- a/js/src/session/session.ts +++ b/js/src/session/session.ts @@ -1,6 +1,18 @@ +import path from "path"; +import { z } from "zod"; +import { App } from "../app/"; import { Identifier } from "../id/id"; +import { LLM } from "../llm/llm"; import { Storage } from "../storage/storage"; import { Log } from "../util/log"; +import { + convertToModelMessages, + streamText, + tool, + type TextUIPart, + type ToolInvocationUIPart, + type UIMessage, +} from "ai"; export namespace Session { const log = Log.create({ service: "session" }); @@ -10,13 +22,175 @@ export namespace Session { title: string; } + const state = App.state("session", () => { + const sessions = new Map<string, Info>(); + const messages = new Map<string, UIMessage[]>(); + + return { + sessions, + messages, + }; + }); + export async function create() { const result: Info = { id: Identifier.create("session"), title: "New Session - " + new Date().toISOString(), }; log.info("created", result); - await Storage.write("session/info/" + result.id, JSON.stringify(result)); + await Storage.write( + "session/info/" + result.id + ".json", + JSON.stringify(result), + ); + state().sessions.set(result.id, result); return result; } + + export async function get(id: string) { + const result = state().sessions.get(id); + if (result) { + return result; + } + const read = JSON.parse(await Storage.readToString("session/info/" + id)); + state().sessions.set(id, read); + return read; + } + + export async function messages(sessionID: string) { + const result = state().messages.get(sessionID); + if (result) { + return result; + } + const read = JSON.parse( + await Storage.readToString( + "session/message/" + sessionID + ".json", + ).catch(() => "[]"), + ); + state().messages.set(sessionID, read); + return read; + } + + export async function* list() { + try { + const result = await Storage.list("session/info"); + for await (const item of result) { + yield path.basename(item.path, ".json"); + } + } catch { + return; + } + } + + export async function chat(sessionID: string, msg: UIMessage) { + const l = log.clone().tag("session", sessionID); + l.info("chatting"); + const msgs = (await messages(sessionID)) ?? [ + { + id: Identifier.create("message"), + role: "system", + parts: [ + { + type: "text", + text: "You are a helpful assistant called opencode", + }, + ], + } as UIMessage, + ]; + msgs.push(msg); + state().messages.set(sessionID, msgs); + async function write() { + return Storage.write( + "session/message/" + sessionID + ".json", + JSON.stringify(msgs), + ); + } + await write(); + + const model = await LLM.findModel("claude-3-7-sonnet-20250219"); + const result = streamText({ + messages: convertToModelMessages(msgs), + temperature: 0, + tools: { + test: tool({ + id: "opencode.test" as const, + parameters: z.object({ + feeling: z.string(), + }), + execute: async () => { + return `Hello`; + }, + description: "call this tool to get a greeting", + }), + }, + model, + }); + const next: UIMessage = { + id: Identifier.create("message"), + role: "assistant", + parts: [], + }; + msgs.push(next); + let text: TextUIPart | undefined; + const reader = result.toUIMessageStream().getReader(); + while (true) { + const { done, value } = await reader.read(); + if (done) break; + l.info("part", value); + switch (value.type) { + case "start": + break; + case "start-step": + next.parts.push({ + type: "step-start", + }); + break; + case "text": + if (!text) { + text = value; + next.parts.push(value); + break; + } + text.text += value.text; + break; + + case "tool-call": + next.parts.push({ + type: "tool-invocation", + toolInvocation: { + state: "call", + ...value, + }, + }); + break; + + case "tool-result": + const match = next.parts.find( + (p) => + p.type === "tool-invocation" && + p.toolInvocation.toolCallId === value.toolCallId, + ) as ToolInvocationUIPart | undefined; + if (match) { + match.toolInvocation = { + ...match.toolInvocation, + state: "result", + result: value.result, + }; + await write(); + } + break; + + case "finish": + await write(); + break; + case "finish-step": + await write(); + break; + + default: + l.info("unhandled", { + type: value.type, + }); + } + } + } } |
