From 163a9e85b583b6a2cf533216eca00b453fbef6f5 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 5 Apr 2026 17:56:01 -0400 Subject: [PATCH] more explorations --- packages/opencode/src/session/compaction.ts | 27 +++++++++--- packages/opencode/src/session/llm.ts | 47 +++++++++------------ packages/sdk/js/src/v2/client.ts | 3 +- packages/sdk/js/src/v2/data.ts | 32 ++++++++++++++ packages/sdk/js/src/v2/index.ts | 3 ++ 5 files changed, 79 insertions(+), 33 deletions(-) create mode 100644 packages/sdk/js/src/v2/data.ts diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index 3158393f11..511d028e50 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -2,7 +2,6 @@ import { BusEvent } from "@/bus/bus-event" import { Bus } from "@/bus" import { Session } from "." import { SessionID, MessageID, PartID } from "./schema" -import { Instance } from "../project/instance" import { Provider } from "../provider/provider" import { MessageV2 } from "./message-v2" import z from "zod" @@ -219,7 +218,6 @@ When constructing the summary, try to stick to this template: const prompt = compacting.prompt ?? [defaultPrompt, ...compacting.context].join("\n\n") const msgs = structuredClone(messages) yield* plugin.trigger("experimental.chat.messages.transform", {}, { messages: msgs }) - const modelMessages = yield* MessageV2.toModelMessagesEffect(msgs, model, { stripMedia: true }) const ctx = yield* InstanceState.context const msg: MessageV2.Assistant = { id: MessageID.ascending(), @@ -261,10 +259,29 @@ When constructing the summary, try to stick to this template: tools: {}, system: [], messages: [ - ...modelMessages, + ...msgs, { - role: "user", - content: [{ type: "text", text: prompt }], + info: { + role: "user", + sessionID: input.sessionID, + id: MessageID.ascending(), + time: { created: Date.now() }, + agent: agent.name, + model: { + modelID: model.id, + providerID: model.providerID, + }, + }, + parts: [ + { + type: "text", + text: prompt, + sessionID: input.sessionID, + messageID: MessageID.ascending(), + id: PartID.ascending(), + synthetic: true, + }, + ], }, ], model, diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index 1813346cdc..a25e7a10a2 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -1,7 +1,6 @@ import { Provider } from "@/provider/provider" import { Log } from "@/util/log" -import { Cause, Effect, Layer, Record, ServiceMap } from "effect" -import * as Queue from "effect/Queue" +import { Effect, Layer, Record, ServiceMap } from "effect" import * as Stream from "effect/Stream" import { streamText, wrapLanguageModel, type ModelMessage, type Tool, tool, jsonSchema } from "ai" import { mergeDeep, pipe } from "remeda" @@ -10,13 +9,14 @@ import { ProviderTransform } from "@/provider/transform" import { Config } from "@/config/config" import { Instance } from "@/project/instance" import type { Agent } from "@/agent/agent" -import type { MessageV2 } from "./message-v2" +import { MessageV2 } from "./message-v2" import { Plugin } from "@/plugin" import { SystemPrompt } from "./system" import { Flag } from "@/flag/flag" import { Permission } from "@/permission" import { Auth } from "@/auth" import { Installation } from "@/installation" +import { iife } from "@/util/iife" export namespace LLM { const log = Log.create({ service: "llm" }) @@ -30,7 +30,7 @@ export namespace LLM { agent: Agent.Info permission?: Permission.Ruleset system: string[] - messages: ModelMessage[] + messages: MessageV2.WithParts[] small?: boolean tools: Record retries?: number @@ -146,19 +146,18 @@ export namespace LLM { } const isWorkflow = language instanceof GitLabWorkflowLanguageModel - const messages = isOpenaiOauth - ? input.messages - : isWorkflow - ? input.messages - : [ - ...system.map( - (x): ModelMessage => ({ - role: "system", - content: x, - }), - ), - ...input.messages, - ] + const messages = await iife(async () => { + if (isOpenaiOauth || isWorkflow) return MessageV2.toModelMessages(input.messages, input.model) + return [ + ...system.map( + (x): ModelMessage => ({ + role: "system", + content: x, + }), + ), + ...(await MessageV2.toModelMessages(input.messages, input.model)), + ] + }) const params = await Plugin.trigger( "chat.params", @@ -198,7 +197,7 @@ export namespace LLM { ? undefined : ProviderTransform.maxOutputTokens(input.model) - const tools = await resolveTools(input) + const tools = resolveTools(input) // LiteLLM and some Anthropic proxies require the tools parameter to be present // when message history contains tool calls, even if no tools are being used. @@ -242,7 +241,7 @@ export namespace LLM { try { const result = await t.execute!(JSON.parse(argsJson), { toolCallId: _requestID, - messages: input.messages, + messages: [], abortSignal: input.abort, }) const output = typeof result === "string" ? result : (result?.output ?? JSON.stringify(result)) @@ -346,13 +345,7 @@ export namespace LLM { // Check if messages contain any tool-call content // Used to determine if a dummy tool should be added for LiteLLM proxy compatibility - export function hasToolCalls(messages: ModelMessage[]): boolean { - for (const msg of messages) { - if (!Array.isArray(msg.content)) continue - for (const part of msg.content) { - if (part.type === "tool-call" || part.type === "tool-result") return true - } - } - return false + export function hasToolCalls(messages: MessageV2.WithParts[]): boolean { + return messages.some((msg) => msg.parts.some((part) => part.type === "tool")) } } diff --git a/packages/sdk/js/src/v2/client.ts b/packages/sdk/js/src/v2/client.ts index e230a4b5dc..67fe1de32f 100644 --- a/packages/sdk/js/src/v2/client.ts +++ b/packages/sdk/js/src/v2/client.ts @@ -77,5 +77,6 @@ export function createOpencodeClient(config?: Config & { directory?: string; exp workspace: config?.experimental_workspaceID, }), ) - return new OpencodeClient({ client }) + const result = new OpencodeClient({ client }) + return result } diff --git a/packages/sdk/js/src/v2/data.ts b/packages/sdk/js/src/v2/data.ts new file mode 100644 index 0000000000..baae6f278d --- /dev/null +++ b/packages/sdk/js/src/v2/data.ts @@ -0,0 +1,32 @@ +import type { Part, UserMessage } from "./client.js" + +export const message = { + user(input: Omit & { parts: Omit[] }): { + info: UserMessage + parts: Part[] + } { + const { parts, ...rest } = input + + const info: UserMessage = { + ...rest, + id: "asdasd", + time: { + created: Date.now(), + }, + role: "user", + } + + return { + info, + parts: input.parts.map( + (part) => + ({ + ...part, + id: "asdasd", + messageID: info.id, + sessionID: info.sessionID, + }) as Part, + ), + } + }, +} diff --git a/packages/sdk/js/src/v2/index.ts b/packages/sdk/js/src/v2/index.ts index d044f5ad66..d514784bc2 100644 --- a/packages/sdk/js/src/v2/index.ts +++ b/packages/sdk/js/src/v2/index.ts @@ -5,6 +5,9 @@ import { createOpencodeClient } from "./client.js" import { createOpencodeServer } from "./server.js" import type { ServerOptions } from "./server.js" +export * as data from "./data.js" +import * as data from "./data.js" + export async function createOpencode(options?: ServerOptions) { const server = await createOpencodeServer({ ...options,