Skip to content
Closed
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
5 changes: 5 additions & 0 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ async function createLLMConfigWithVendors(
);
}
return {
id: `${vendorId}/${modelId}`,
type: "vendor",
useToolCallMiddleware: options.useToolCallMiddleware,
getModel: () =>
Expand All @@ -358,6 +359,7 @@ async function createLLMConfigWithPochi(
if (pochiModelOptions) {
const vendorId = "pochi";
return {
id: `${vendorId}/${model}`,
type: "vendor",
useToolCallMiddleware: pochiModelOptions.useToolCallMiddleware,
getModel: () =>
Expand Down Expand Up @@ -389,6 +391,7 @@ async function createLLMConfigWithProviders(

if (modelProvider.kind === "ai-gateway") {
return {
id: `${providerId}/${modelId}`,
type: "ai-gateway",
modelId,
apiKey: modelProvider.apiKey,
Expand All @@ -401,6 +404,7 @@ async function createLLMConfigWithProviders(

if (modelProvider.kind === "google-vertex-tuning") {
return {
id: `${providerId}/${modelId}`,
type: "google-vertex-tuning",
modelId,
vertex: modelProvider.vertex,
Expand All @@ -419,6 +423,7 @@ async function createLLMConfigWithProviders(
modelProvider.kind === "anthropic"
) {
return {
id: `${providerId}/${modelId}`,
type: modelProvider.kind || "openai",
modelId,
baseURL: modelProvider.baseURL,
Expand Down
7 changes: 7 additions & 0 deletions packages/common/src/base/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ export const Environment = z.object({
recentCommits: z
.array(z.string())
.describe("A list of recent git commits."),
worktree: z
.object({
gitdir: z
.string()
.describe("The gitdir path stored in worktree .git file."),
})
.optional(),
})
.optional()
.describe("Git information for the current workspace."),
Expand Down
1 change: 1 addition & 0 deletions packages/common/src/base/prompts/__tests__/prompt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ test("environment", () => {
'02b50f727 feat(chat): add environment property to prepareRequestBody',
'962185adb feat(webui): add new task link and pending component',
],
worktree: {gitdir: '/Users/username/repo/.git/worktrees/add-environment-to-chat-request-body'},
},
terminals: [
{
Expand Down
20 changes: 20 additions & 0 deletions packages/common/src/tool-utils/__tests__/git-status.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { afterEach, describe, expect, it, vi } from "vitest";
import { GitStatusReader } from "../git-status";

const execMocks = vi.hoisted(() => new Map<string, string>());
const fsMocks = vi.hoisted(() => ({
stat: vi.fn(),
readFile: vi.fn(),
}));

vi.mock("node:child_process", () => ({
exec: vi.fn((command: string, _, callback) => {
Expand All @@ -14,9 +18,19 @@ vi.mock("node:child_process", () => ({
}),
}));

vi.mock("node:fs/promises", async (importOriginal) => {
const original = await importOriginal<typeof import("node:fs/promises")>();
return {
...original,
stat: fsMocks.stat,
readFile: fsMocks.readFile,
};
});

describe("GitStatusReader", () => {
afterEach(() => {
execMocks.clear();
vi.resetAllMocks();
});

it("should correctly read and parse git status", async () => {
Expand All @@ -33,6 +47,11 @@ describe("GitStatusReader", () => {
);
execMocks.set("config user.name", "Test User");
execMocks.set("config user.email", "[email protected]");
execMocks.set("rev-parse --path-format=absolute --git-common-dir", "/test/repo/.git");
execMocks.set("rev-parse --path-format=absolute --show-toplevel", "/test/repo");

fsMocks.stat.mockResolvedValue({ isFile: () => true });
fsMocks.readFile.mockResolvedValue("gitdir: /test/repo/.git/worktrees/feature-branch");

const reader = new GitStatusReader({ cwd: "/test/repo" });
const status = await reader.readGitStatus();
Expand All @@ -45,6 +64,7 @@ describe("GitStatusReader", () => {
recentCommits: ["abc1234 feat: new feature", "def5678 fix: a bug"],
userName: "Test User",
userEmail: "[email protected]",
worktree: {gitdir: "/test/repo/.git/worktrees/feature-branch"},
});
});

Expand Down
27 changes: 27 additions & 0 deletions packages/common/src/tool-utils/git-status.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { exec } from "node:child_process";
import { readFile, stat } from "node:fs/promises";
import { join } from "node:path";
import { promisify } from "node:util";
import { type GitStatus, getLogger } from "../base";
import { parseGitOriginUrl } from "../git-utils";
Expand Down Expand Up @@ -132,6 +134,8 @@ export class GitStatusReader {
recentCommits,
userName,
userEmail,
worktreeGitdir,
worktreeDir,
] = await Promise.all([
this.execGit("remote get-url origin").catch(() => undefined),
this.execGit("rev-parse --abbrev-ref HEAD").catch(() => "unknown"),
Expand All @@ -142,6 +146,10 @@ export class GitStatusReader {
.catch(() => []),
this.execGit("config user.name").catch(() => undefined),
this.execGit("config user.email").catch(() => undefined),
parseWorktreeGitdir(this.cwd),
this.execGit("rev-parse --path-format=absolute --show-toplevel").catch(
() => "",
),
]);

const origin = this.sanitizeOriginUrl(rawOrigin);
Expand All @@ -154,6 +162,10 @@ export class GitStatusReader {
recentCommits,
userName,
userEmail,
worktree:
this.cwd === worktreeDir && worktreeGitdir
? { gitdir: worktreeGitdir }
: undefined,
};
}

Expand All @@ -178,3 +190,18 @@ export class GitStatusReader {
}
}
}

async function parseWorktreeGitdir(cwd: string): Promise<string | undefined> {
try {
const gitFilePath = join(cwd, ".git");
const fileStat = await stat(gitFilePath);
if (!fileStat.isFile()) {
return undefined;
}
const content = await readFile(gitFilePath, "utf8");
const match = content.trim().match(/^gitdir:\s*(.+)$/);
return match ? match[1] : undefined;
} catch (error) {
return undefined;
}
}
8 changes: 7 additions & 1 deletion packages/common/src/vscode-webui-bridge/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@ export type {
} from "./types/execution";
export type { ResourceURI } from "./types/common";
export type { SessionState, WorkspaceState } from "./types/session";
export type { TaskIdParams, NewTaskParams } from "./types/task-params";
export type {
TaskIdParams,
NewTaskParams,
TaskDataParams,
TaskData,
} from "./types/task";
export { getTaskWorktreeName } from "./types/task";
export type {
VSCodeLmModel,
VSCodeLmRequestCallback,
Expand Down
15 changes: 0 additions & 15 deletions packages/common/src/vscode-webui-bridge/types/task-params.ts

This file was deleted.

40 changes: 40 additions & 0 deletions packages/common/src/vscode-webui-bridge/types/task.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
export type FileUIPart = {
name: string;
contentType: string;
url: string;
};

export interface TaskIdParams {
uid: string;
prompt?: string;
files?: FileUIPart[];
}

export interface NewTaskParams {
uid: undefined;
}

export interface TaskDataParams<T extends TaskData = TaskData> {
uid: string;
/**
* @link packages/vscode-webui/src/livestore-provider.tsx#TaskSyncData
*/
task: T;
}

/**
* only include fields that are used in the webview and node process
*/
export interface TaskData {
id: string;
cwd?: string | null;
git?: {
worktree?: { gitdir?: string } | null;
} | null;
}

export const getTaskWorktreeName = (
task: TaskData | undefined,
): string | undefined => {
return task?.git?.worktree?.gitdir?.split(/[\/\\]/).pop();
};
3 changes: 1 addition & 2 deletions packages/common/src/vscode-webui-bridge/webview-stub.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { ThreadAbortSignalSerialization } from "@quilted/threads";
import type { ThreadSignalSerialization } from "@quilted/threads/signals";
import type { Environment } from "../base";

import type { UserInfo } from "../configuration";
import type {
CaptureEvent,
Expand Down Expand Up @@ -221,7 +220,7 @@ const VSCodeHostStub = {
return "test-machine-id";
},

openPochiInNewTab: async (): Promise<void> => {},
openTaskInPanel: async (): Promise<void> => {},

bridgeStoreEvent: async (): Promise<void> => {},
} satisfies VSCodeHostApi;
Expand Down
7 changes: 5 additions & 2 deletions packages/common/src/vscode-webui-bridge/webview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {
} from "./index";
import type { DisplayModel } from "./types/model";
import type { PochiCredentials } from "./types/pochi";
import type { TaskDataParams } from "./types/task";

export interface VSCodeHostApi {
readResourceURI(): Promise<ResourceURI>;
Expand Down Expand Up @@ -271,7 +272,9 @@ export interface VSCodeHostApi {
ThreadSignalSerialization<Record<string, UserInfo>>
>;

openPochiInNewTab(): Promise<void>;
openTaskInPanel(
task: unknown /** @link packages/vscode-webui/src/livestore-provider.tsx#TaskSyncData */,
): Promise<void>;

bridgeStoreEvent(
webviewKind: "sidebar" | "pane",
Expand All @@ -283,7 +286,7 @@ export interface WebviewHostApi {
/**
* @param params - Existing task id or new task params.
*/
openTask(params: TaskIdParams | NewTaskParams): void;
openTask(params: TaskIdParams | NewTaskParams | TaskDataParams): void;

openTaskList(): void;

Expand Down
17 changes: 6 additions & 11 deletions packages/livekit/src/chat/live-chat-kit.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { getLogger } from "@getpochi/common";

import type { CustomAgent } from "@getpochi/tools";
import type { Store } from "@livestore/livestore";
import type { ChatInit, ChatOnErrorCallback, ChatOnFinishCallback } from "ai";
import type z from "zod/v4";
import { makeMessagesQuery, makeTaskQuery } from "../livestore/queries";
import { events, tables } from "../livestore/schema";
import { toTaskError, toTaskStatus } from "../task";
import { toTaskError, toTaskGitInfo, toTaskStatus } from "../task";
import type { Message } from "../types";
import { scheduleGenerateTitleJob } from "./background-job";
import {
Expand Down Expand Up @@ -152,6 +151,7 @@ export class LiveChatKit<
events.taskInited({
id: taskId,
cwd: this.task?.cwd || undefined,
modelId: this.task?.modelId || undefined,
createdAt: new Date(),
initMessage: {
id: crypto.randomUUID(),
Expand Down Expand Up @@ -257,28 +257,23 @@ export class LiveChatKit<
throw new Error("Task not found");
}

const getModel = () => createModel({ llm: getters.getLLM() });
const llm = getters.getLLM();
const getModel = () => createModel({ llm });
scheduleGenerateTitleJob({
taskId: this.taskId,
store,
messages,
getModel,
});

const { gitStatus } = environment?.workspace || {};

store.commit(
events.chatStreamStarted({
id: this.taskId,
data: lastMessage,
todos: environment?.todos || [],
git: gitStatus
? {
origin: gitStatus.origin,
branch: gitStatus.currentBranch,
}
: undefined,
git: toTaskGitInfo(environment?.workspace.gitStatus),
updatedAt: new Date(),
modelId: llm.id,
}),
);
}
Expand Down
11 changes: 5 additions & 6 deletions packages/livekit/src/livestore/queries.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { queryDb } from "@livestore/livestore";
import { Schema, queryDb, sql } from "@livestore/livestore";
import { tables } from "./schema";

export const makeTaskQuery = (taskId: string) =>
Expand Down Expand Up @@ -26,11 +26,10 @@ export const tasks$ = queryDb(

export const makeTasksQuery = (cwd: string) =>
queryDb(
() =>
tables.tasks
.where("parentId", "=", null)
.where("cwd", "=", cwd)
.orderBy("updatedAt", "desc"),
{
query: sql`select * from tasks where parentId is null and (cwd = '${cwd}' or git->>'$.worktree.gitdir' like '${cwd}/.git/worktrees%') order by updatedAt desc`,
schema: Schema.Array(tables.tasks.rowSchema),
},
{
label: "tasks.cwd",
deps: [cwd],
Expand Down
Loading