Skip to content
Open
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
25 changes: 19 additions & 6 deletions src/api/ApiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ export interface AgentRun {
web_url?: string;
github_pull_requests?: any[];
source_type?: string;
repository?: {
id: number;
name: string;
full_name: string;
owner: string;
};
}

export interface AgentRunsResponse {
Expand Down Expand Up @@ -62,19 +68,26 @@ export class ApiClient {
);
}

async getAgentRuns(page: number = 1, perPage: number = 10): Promise<AgentRunsResponse> {
async getAgentRuns(page: number = 1, perPage: number = 10, repositoryName?: string): Promise<AgentRunsResponse> {
const orgId = this.authManager.getOrgId();
if (!orgId) {
throw new Error('No organization ID found. Please login again.');
}

try {
const params: any = {
page,
per_page: perPage,
source_type: 'API' // Filter to API source type like the CLI does
};

// Add repository filter if provided
if (repositoryName) {
params.repository = repositoryName;
}

const response = await this.client.get(`/v1/organizations/${orgId}/agent/runs`, {
params: {
page,
per_page: perPage,
source_type: 'API' // Filter to API source type like the CLI does
}
params
});

return response.data;
Expand Down
30 changes: 29 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as vscode from 'vscode';
import { AgentRunsProvider } from './providers/AgentRunsProvider';
import { AuthManager } from './auth/AuthManager';
import { ApiClient } from './api/ApiClient';
import { PrUtils } from './utils/PrUtils';

export function activate(context: vscode.ExtensionContext) {
console.log('Codegen IDE extension is now active!');
Expand Down Expand Up @@ -62,7 +63,11 @@ export function activate(context: vscode.ExtensionContext) {

if (prompt) {
try {
const agentRun = await apiClient.createAgentRun(prompt);
// Get current repository for context
const currentRepo = agentRunsProvider.getCurrentRepository();
const repoId = currentRepo ? undefined : undefined; // TODO: Map repository name to ID

const agentRun = await apiClient.createAgentRun(prompt, undefined, repoId);
vscode.window.showInformationMessage(
`Agent run created successfully! ID: ${agentRun.id}`,
'View in Browser'
Expand All @@ -86,6 +91,29 @@ export function activate(context: vscode.ExtensionContext) {
if (agentRun.web_url) {
vscode.env.openExternal(vscode.Uri.parse(agentRun.web_url));
}
}),

vscode.commands.registerCommand('codegen.openAgentRunOrPr', async (agentRun) => {
// First, try to open the PR if it exists
const prInfo = PrUtils.extractPrInfo(agentRun);

if (prInfo) {
// Get current repository for local path context
const currentRepo = agentRunsProvider.getCurrentRepository();

if (currentRepo && currentRepo.fullName === prInfo.repository) {
// We're in the same repository, try to open PR diff view
await PrUtils.openPrDiffView(prInfo, currentRepo.localPath);
} else {
// Different repository or no local repo, open PR in browser
await PrUtils.openPrDiff(prInfo);
}
} else {
// No PR, fall back to opening the agent run in browser
if (agentRun.web_url) {
vscode.env.openExternal(vscode.Uri.parse(agentRun.web_url));
}
}
})
];

Expand Down
57 changes: 49 additions & 8 deletions src/providers/AgentRunsProvider.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import * as vscode from 'vscode';
import { ApiClient, AgentRun } from '../api/ApiClient';
import { AuthManager } from '../auth/AuthManager';
import { GitUtils, GitRepository } from '../utils/GitUtils';
import { PrUtils, PullRequest } from '../utils/PrUtils';

export class AgentRunItem extends vscode.TreeItem {
constructor(
Expand All @@ -14,10 +16,10 @@ export class AgentRunItem extends vscode.TreeItem {
this.iconPath = this.getIcon();
this.contextValue = 'agentRun';

// Make it clickable to open in browser
// Make it clickable to open PR diff or agent run
this.command = {
command: 'codegen.openAgentRun',
title: 'Open Agent Run',
command: 'codegen.openAgentRunOrPr',
title: 'Open Agent Run or PR',
arguments: [agentRun]
};
}
Expand Down Expand Up @@ -49,10 +51,10 @@ export class AgentRunItem extends vscode.TreeItem {
timeAgo = `${diffMinutes}m ago`;
}

const prCount = this.agentRun.github_pull_requests?.length || 0;
const prText = prCount > 0 ? ` β€’ ${prCount} PR${prCount > 1 ? 's' : ''}` : '';
const prText = PrUtils.getPrStatusDescription(this.agentRun);
const repoText = this.agentRun.repository ? ` β€’ ${this.agentRun.repository.name}` : '';

return `${status} β€’ ${timeAgo}${prText}`;
return `${status} β€’ ${timeAgo} β€’ ${prText}${repoText}`;
}

private getIcon(): vscode.ThemeIcon {
Expand Down Expand Up @@ -82,16 +84,36 @@ export class AgentRunsProvider implements vscode.TreeDataProvider<AgentRunItem>
readonly onDidChangeTreeData: vscode.Event<AgentRunItem | undefined | null | void> = this._onDidChangeTreeData.event;

private agentRuns: AgentRun[] = [];
private currentRepository: GitRepository | null = null;

constructor(
private apiClient: ApiClient,
private authManager: AuthManager
) {}
) {
// Listen for workspace changes to update repository context
vscode.workspace.onDidChangeWorkspaceFolders(() => {
this.updateRepositoryContext();
});

// Initialize repository context
this.updateRepositoryContext();
}

refresh(): void {
this.updateRepositoryContext();
this._onDidChangeTreeData.fire();
}

private async updateRepositoryContext(): Promise<void> {
try {
this.currentRepository = await GitUtils.getCurrentRepository();
console.log('Current repository:', this.currentRepository?.fullName || 'None');
} catch (error) {
console.error('Failed to get current repository:', error);
this.currentRepository = null;
}
}

getTreeItem(element: AgentRunItem): vscode.TreeItem {
return element;
}
Expand All @@ -104,9 +126,24 @@ export class AgentRunsProvider implements vscode.TreeDataProvider<AgentRunItem>
if (!element) {
// Root level - return agent runs
try {
const response = await this.apiClient.getAgentRuns(1, 20); // Get first 20 runs
// Filter by current repository if available
const repositoryName = this.currentRepository?.fullName;
const response = await this.apiClient.getAgentRuns(1, 20, repositoryName);
this.agentRuns = response.items;

// If we have a repository context but no results, show a helpful message
if (this.agentRuns.length === 0 && repositoryName) {
// Return a placeholder item to show the user what's happening
const placeholderItem = new vscode.TreeItem(
`No agents found for ${repositoryName}`,
vscode.TreeItemCollapsibleState.None
);
placeholderItem.description = 'Try creating a new agent or check a different repository';
placeholderItem.iconPath = new vscode.ThemeIcon('info');
placeholderItem.contextValue = 'placeholder';
return [placeholderItem];
}

return this.agentRuns.map(agentRun =>
new AgentRunItem(agentRun, vscode.TreeItemCollapsibleState.None)
);
Expand All @@ -119,4 +156,8 @@ export class AgentRunsProvider implements vscode.TreeDataProvider<AgentRunItem>

return [];
}

getCurrentRepository(): GitRepository | null {
return this.currentRepository;
}
}
125 changes: 125 additions & 0 deletions src/utils/GitUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import * as vscode from 'vscode';
import * as path from 'path';

export interface GitRepository {
name: string;
owner: string;
fullName: string;
localPath: string;
}

export class GitUtils {
/**
* Get the current git repository information from the active workspace
*/
static async getCurrentRepository(): Promise<GitRepository | null> {
const workspaceFolders = vscode.workspace.workspaceFolders;
if (!workspaceFolders || workspaceFolders.length === 0) {
return null;
}

// Try to get the repository from the first workspace folder
const workspaceFolder = workspaceFolders[0];
const gitExtension = vscode.extensions.getExtension('vscode.git');

if (!gitExtension) {
console.warn('Git extension not found');
return null;
}

if (!gitExtension.isActive) {
await gitExtension.activate();
}

const git = gitExtension.exports.getAPI(1);
if (!git) {
console.warn('Git API not available');
return null;
}

// Find the repository for the current workspace
const repository = git.repositories.find((repo: any) =>
repo.rootUri.fsPath === workspaceFolder.uri.fsPath
);

if (!repository) {
console.warn('No git repository found in workspace');
return null;
}

try {
// Get remote origin URL
const remotes = repository.state.remotes;
const origin = remotes.find((remote: any) => remote.name === 'origin');

if (!origin || !origin.fetchUrl) {
console.warn('No origin remote found');
return null;
}

const repoInfo = this.parseGitUrl(origin.fetchUrl);
if (!repoInfo) {
console.warn('Could not parse git URL:', origin.fetchUrl);
return null;
}

return {
...repoInfo,
localPath: repository.rootUri.fsPath
};
} catch (error) {
console.error('Error getting repository info:', error);
return null;
}
}

/**
* Parse a git URL to extract owner and repository name
*/
private static parseGitUrl(url: string): { name: string; owner: string; fullName: string } | null {
// Handle different Git URL formats
const patterns = [
// SSH format: git@github.com:owner/repo.git
/^git@([^:]+):([^\/]+)\/(.+?)(?:\.git)?$/,
// HTTPS format: https://github.com/owner/repo.git
/^https?:\/\/([^\/]+)\/([^\/]+)\/(.+?)(?:\.git)?$/,
// HTTP format: http://github.com/owner/repo.git
/^https?:\/\/([^\/]+)\/([^\/]+)\/(.+?)(?:\.git)?$/
];

for (const pattern of patterns) {
const match = url.match(pattern);
if (match) {
const [, , owner, name] = match;
return {
name: name.replace(/\.git$/, ''),
owner,
fullName: `${owner}/${name.replace(/\.git$/, '')}`
};
}
}

return null;
}

/**
* Check if the current workspace has a git repository
*/
static async hasGitRepository(): Promise<boolean> {
const repo = await this.getCurrentRepository();
return repo !== null;
}

/**
* Get repository ID from the Codegen API based on the repository name
* This would need to be implemented based on the API structure
*/
static getRepositoryId(repoFullName: string): number | null {
// This is a placeholder - in a real implementation, you'd need to:
// 1. Call the Codegen API to get a list of repositories
// 2. Find the matching repository by name
// 3. Return its ID
// For now, we'll return null and filter by repository name in the API call
return null;
}
}
Loading