summaryrefslogtreecommitdiffhomepage
path: root/js/src/session/session.ts
diff options
context:
space:
mode:
authorDax Raad <[email protected]>2025-05-18 02:43:01 -0400
committerDax Raad <[email protected]>2025-05-26 12:40:17 -0400
commitd0d67029f4baad7389b5ba072379c2ff44a22dc4 (patch)
treecb81e86662c14c20687bf5bac488dda911a9855e /js/src/session/session.ts
parenta34d020bc6b252e842f042d935c7a0e6444460cf (diff)
downloadopencode-d0d67029f4baad7389b5ba072379c2ff44a22dc4.tar.gz
opencode-d0d67029f4baad7389b5ba072379c2ff44a22dc4.zip
process
Diffstat (limited to 'js/src/session/session.ts')
-rw-r--r--js/src/session/session.ts176
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,
+ });
+ }
+ }
+ }
}