Skip to content
Merged
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
56 changes: 8 additions & 48 deletions packages/agents/claude/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@ import {
runCliJsonCommand,
type SessionEnvironment as RuntimeSessionEnvironment,
} from "../runtime/base";
import { getOrCreateThreadSession } from "../runtime/thread-session";
import { createCliThreadSessionManager } from "../runtime/cli-session";
import type {
OpenCodeMessage,
OpenCodeMessageContext,
OpenCodeOptions,
OpenCodeSessionInfo,
} from "../types";

export type SessionEnvironment = RuntimeSessionEnvironment;
Expand Down Expand Up @@ -50,52 +49,13 @@ function isValidUuid(value: string): boolean {
return uuidRegex.test(value);
}

export async function createSession(workingPath: string, env?: SessionEnvironment): Promise<string> {
const sessionId = crypto.randomUUID();
runtime.setSessionEnvironment(sessionId, env ?? {});
newSessions.add(sessionId);
log.info("Created Claude session", { sessionId, workingPath });
return sessionId;
}

export async function getOrCreateSession(
channelId: string,
threadId: string,
workingPath: string,
env: SessionEnvironment = {}
): Promise<OpenCodeSessionInfo> {
return getOrCreateThreadSession({
channelId,
threadId,
providerId: "claudecode",
workingPath,
env,
createSession,
getSessionEnvironment: (sessionId) => runtime.getSessionEnvironment(sessionId),
setSessionEnvironment: (sessionId, nextEnv) => {
runtime.setSessionEnvironment(sessionId, nextEnv);
},
validateSessionId: isValidUuid,
onInvalidSessionId: (existingSession) => {
log.info("Invalid Claude session id found; generating new session", {
channelId,
threadId,
workingPath,
existingSession,
});
},
onEnvironmentChanged: () => {
log.info("Claude session environment changed; creating new session", {
channelId,
threadId,
workingPath,
});
},
onCreatingSession: () => {
log.info("Creating new Claude session for thread", { channelId, threadId, workingPath });
},
});
}
export const { createSession, getOrCreateSession } = createCliThreadSessionManager({
providerId: "claudecode",
providerName: "Claude",
runtime,
newSessions,
validateSessionId: isValidUuid,
});

function extractJsonPayload(output: string): string {
const trimmed = output.trim();
Expand Down
45 changes: 6 additions & 39 deletions packages/agents/codex/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,19 @@ import {
runCliJsonCommand,
type SessionEnvironment as RuntimeSessionEnvironment,
} from "../runtime/base";
import { getOrCreateThreadSession } from "../runtime/thread-session";
import { createCliThreadSessionManager } from "../runtime/cli-session";
import type {
OpenCodeMessage,
OpenCodeMessageContext,
OpenCodeOptions,
OpenCodeSessionInfo,
} from "../types";

const runtime = new CliAgentRuntime("Codex");
export const { createSession, getOrCreateSession } = createCliThreadSessionManager({
providerId: "codex",
providerName: "Codex",
runtime,
});

export type SessionEnvironment = RuntimeSessionEnvironment;

Expand Down Expand Up @@ -153,43 +157,6 @@ function parseCodexResponse(output: string): {
return { text, threadId };
}

export async function createSession(workingPath: string, env?: SessionEnvironment): Promise<string> {
const sessionId = crypto.randomUUID();
runtime.setSessionEnvironment(sessionId, env ?? {});
log.info("Created Codex session", { sessionId, workingPath });
return sessionId;
}

export async function getOrCreateSession(
channelId: string,
threadId: string,
workingPath: string,
env: SessionEnvironment = {}
): Promise<OpenCodeSessionInfo> {
return getOrCreateThreadSession({
channelId,
threadId,
providerId: "codex",
workingPath,
env,
createSession,
getSessionEnvironment: (sessionId) => runtime.getSessionEnvironment(sessionId),
setSessionEnvironment: (sessionId, nextEnv) => {
runtime.setSessionEnvironment(sessionId, nextEnv);
},
onEnvironmentChanged: () => {
log.info("Codex session environment changed; creating new session", {
channelId,
threadId,
workingPath,
});
},
onCreatingSession: () => {
log.info("Creating new Codex session for thread", { channelId, threadId, workingPath });
},
});
}

export async function sendMessage(
channelId: string,
sessionId: string,
Expand Down
47 changes: 7 additions & 40 deletions packages/agents/gemini/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ import {
runCliJsonCommand,
type SessionEnvironment as RuntimeSessionEnvironment,
} from "../runtime/base";
import { getOrCreateThreadSession } from "../runtime/thread-session";
import { createCliThreadSessionManager } from "../runtime/cli-session";
import type {
OpenCodeMessage,
OpenCodeMessageContext,
OpenCodeOptions,
OpenCodeSessionInfo,
} from "../types";

export type SessionEnvironment = RuntimeSessionEnvironment;
Expand All @@ -39,6 +38,12 @@ type GeminiJsonRecord = {

const runtime = new CliAgentRuntime("Gemini");
const newSessions = new Set<string>();
export const { createSession, getOrCreateSession } = createCliThreadSessionManager({
providerId: "gemini",
providerName: "Gemini",
runtime,
newSessions,
});

function resolveGeminiBinary(): string {
if (typeof Bun !== "undefined" && Bun.which("gemini")) return "gemini";
Expand Down Expand Up @@ -158,44 +163,6 @@ function parseGeminiResponse(output: string): {
return { text, sessionId };
}

export async function createSession(workingPath: string, env?: SessionEnvironment): Promise<string> {
const sessionId = crypto.randomUUID();
runtime.setSessionEnvironment(sessionId, env ?? {});
newSessions.add(sessionId);
log.info("Created Gemini session", { sessionId, workingPath });
return sessionId;
}

export async function getOrCreateSession(
channelId: string,
threadId: string,
workingPath: string,
env: SessionEnvironment = {}
): Promise<OpenCodeSessionInfo> {
return getOrCreateThreadSession({
channelId,
threadId,
providerId: "gemini",
workingPath,
env,
createSession,
getSessionEnvironment: (sessionId) => runtime.getSessionEnvironment(sessionId),
setSessionEnvironment: (sessionId, nextEnv) => {
runtime.setSessionEnvironment(sessionId, nextEnv);
},
onEnvironmentChanged: () => {
log.info("Gemini session environment changed; creating new session", {
channelId,
threadId,
workingPath,
});
},
onCreatingSession: () => {
log.info("Creating new Gemini session for thread", { channelId, threadId, workingPath });
},
});
}

export async function sendMessage(
channelId: string,
sessionId: string,
Expand Down
47 changes: 7 additions & 40 deletions packages/agents/goose/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ import {
runCliJsonCommand,
type SessionEnvironment as RuntimeSessionEnvironment,
} from "../runtime/base";
import { getOrCreateThreadSession } from "../runtime/thread-session";
import { createCliThreadSessionManager } from "../runtime/cli-session";
import type {
OpenCodeMessage,
OpenCodeMessageContext,
OpenCodeOptions,
OpenCodeSessionInfo,
} from "../types";

export type SessionEnvironment = RuntimeSessionEnvironment;
Expand Down Expand Up @@ -43,6 +42,12 @@ type GooseJsonRecord = {

const runtime = new CliAgentRuntime("Goose");
const newSessions = new Set<string>();
export const { createSession, getOrCreateSession } = createCliThreadSessionManager({
providerId: "goose",
providerName: "Goose",
runtime,
newSessions,
});

function resolveGooseBinary(): string {
if (typeof Bun !== "undefined" && Bun.which("goose")) return "goose";
Expand Down Expand Up @@ -192,44 +197,6 @@ export function parseGooseResponse(output: string): {
return { text, sessionId };
}

export async function createSession(workingPath: string, env?: SessionEnvironment): Promise<string> {
const sessionId = crypto.randomUUID();
runtime.setSessionEnvironment(sessionId, env ?? {});
newSessions.add(sessionId);
log.info("Created Goose session", { sessionId, workingPath });
return sessionId;
}

export async function getOrCreateSession(
channelId: string,
threadId: string,
workingPath: string,
env: SessionEnvironment = {}
): Promise<OpenCodeSessionInfo> {
return getOrCreateThreadSession({
channelId,
threadId,
providerId: "goose",
workingPath,
env,
createSession,
getSessionEnvironment: (sessionId) => runtime.getSessionEnvironment(sessionId),
setSessionEnvironment: (sessionId, nextEnv) => {
runtime.setSessionEnvironment(sessionId, nextEnv);
},
onEnvironmentChanged: () => {
log.info("Goose session environment changed; creating new session", {
channelId,
threadId,
workingPath,
});
},
onCreatingSession: () => {
log.info("Creating new Goose session for thread", { channelId, threadId, workingPath });
},
});
}

export async function sendMessage(
channelId: string,
sessionId: string,
Expand Down
52 changes: 45 additions & 7 deletions packages/agents/goose/session-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,40 @@ export type GooseInspectorToolState = StreamToolState;

export type GooseStreamStateMaps = StreamStateMaps<GooseInspectorToolState>;

function resolveGooseToolResponseId(block: {
id?: string;
tool_use_id?: string;
}): string {
if (typeof block.id === "string" && block.id.trim()) {
return block.id;
}
if (typeof block.tool_use_id === "string" && block.tool_use_id.trim()) {
return block.tool_use_id;
}
return "";
}

function extractGooseToolResponseText(value: unknown): string {
if (typeof value === "string") {
return value.trim();
}
if (!Array.isArray(value)) {
return "";
}
return value
.filter((entry) => entry && typeof entry === "object")
.map((entry) => {
const record = entry as { type?: string; text?: string };
if (record.type === "text" && typeof record.text === "string") {
return record.text;
}
return "";
})
.filter((text) => text.length > 0)
.join("\n")
.trim();
}

function parseTodosFromGooseToolInput(toolName: string, input: Record<string, unknown> | undefined): SessionTodo[] | undefined {
const direct = parseTodosFromToolInput(toolName, input);
if (direct) return direct;
Expand Down Expand Up @@ -97,6 +131,9 @@ export function applyGooseRecordToState(
const blocks = record.message?.content ?? [];

if (role === "assistant") {
const messageCreatedAtMs = typeof record.message?.created === "number"
? record.message.created * 1000
: Date.now();
for (const block of blocks) {
if (block?.type === "text") {
const chunk = typeof block.text === "string" ? block.text : "";
Expand Down Expand Up @@ -138,7 +175,12 @@ export function applyGooseRecordToState(
output: existing?.output,
error: existing?.error,
title: existing?.title,
metadata: existing?.metadata,
metadata: {
...(existing?.metadata ?? {}),
startedAtMs: typeof existing?.metadata?.startedAtMs === "number"
? existing.metadata.startedAtMs
: messageCreatedAtMs,
},
};
toolById.set(callId, tool);
updateTool(state, tool);
Expand All @@ -150,16 +192,12 @@ export function applyGooseRecordToState(
if (role === "user") {
for (const block of blocks) {
if (block?.type !== "toolResponse") continue;
const callId = typeof block.id === "string" && block.id.trim() ? block.id : "";
const callId = resolveGooseToolResponseId(block);
if (!callId) continue;
const existing = toolById.get(callId);
if (!existing) continue;
const result = block.toolResult?.value;
const output = (result?.content ?? [])
.filter((entry) => entry?.type === "text")
.map((entry) => entry.text ?? "")
.join("\n")
.trim();
const output = extractGooseToolResponseText(result?.content);
const hasError = result?.isError === true || block.toolResult?.status === "error";
const updated: GooseInspectorToolState = {
...existing,
Expand Down
Loading