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
2 changes: 1 addition & 1 deletion jsr.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nilenso/ask-forge",
"version": "0.0.14",
"version": "0.0.15",
"license": "MIT",
"exports": "./src/index.ts",
"exclude": [
Expand Down
9 changes: 7 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,17 +141,22 @@ export class AskForgeClient {
*
* @param repoUrl - The URL of the repository to connect to
* @param options - Git connection options (token, forge, commitish)
* @param onProgress - Optional callback for clone progress messages (useful for long clones)
* @returns A Session for asking questions about the repository
*/
async connect(repoUrl: string, options: ConnectOptions = {}): Promise<Session> {
async connect(
repoUrl: string,
options: ConnectOptions = {},
onProgress?: (message: string) => void,
): Promise<Session> {
const { config } = this;

// getModel has strict generics tying provider to model IDs - cast for flexibility
const model = (getModel as (p: string, m: string) => ReturnType<typeof getModel>)(config.provider, config.model);

if (this.#sandboxClient) {
// Sandbox mode: clone and execute tools in isolated container
const cloneResult = await this.#sandboxClient.clone(repoUrl, options.commitish);
const cloneResult = await this.#sandboxClient.clone(repoUrl, options.commitish, onProgress);

// Create a Repo-like object with sandbox metadata
const repo: Repo = {
Expand Down
10 changes: 8 additions & 2 deletions src/sandbox/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,9 @@ export class SandboxClient {
/**
* Clone a repository inside the sandbox.
* Kicks off an async clone and polls until ready (up to 20 minutes).
* @param onProgress - Optional callback invoked with status messages during polling
*/
async clone(url: string, commitish?: string): Promise<CloneResult> {
async clone(url: string, commitish?: string, onProgress?: (message: string) => void): Promise<CloneResult> {
const commit = commitish ?? "HEAD";
this.logger.debug("sandbox:client", `POST /clone url=${url} commitish=${commit}`);
const t0 = Date.now();
Expand Down Expand Up @@ -113,6 +114,7 @@ export class SandboxClient {
"sandbox:client",
`POST /clone → ready (cached) (${duration}ms) slug=${startBody.slug} sha=${startBody.sha.slice(0, 12)}`,
);
onProgress?.("Repository ready");
return { slug: startBody.slug, sha: startBody.sha, worktree: startBody.worktree };
}

Expand All @@ -123,6 +125,7 @@ export class SandboxClient {

// Step 2: Poll until ready or failed
this.logger.debug("sandbox:client", `clone started for ${url}, polling status...`);
onProgress?.("Cloning repository…");

const deadline = Date.now() + CLONE_POLL_TIMEOUT_MS;
while (Date.now() < deadline) {
Expand Down Expand Up @@ -152,6 +155,7 @@ export class SandboxClient {
"sandbox:client",
`clone ready (${duration}ms) slug=${slug} sha=${statusBody.sha.slice(0, 12)}`,
);
onProgress?.("Repository ready");
return { slug: statusBody.slug ?? slug, sha: statusBody.sha, worktree: statusBody.worktree };
}

Expand All @@ -163,7 +167,9 @@ export class SandboxClient {

// Still cloning — log progress
const elapsed = statusBody.elapsedMs ?? Date.now() - t0;
this.logger.debug("sandbox:client", `clone in progress for ${url} (${Math.round(elapsed / 1000)}s elapsed)`);
const elapsedSec = Math.round(elapsed / 1000);
this.logger.debug("sandbox:client", `clone in progress for ${url} (${elapsedSec}s elapsed)`);
onProgress?.(`Cloning repository… ${elapsedSec}s`);
}

throw new Error(`Sandbox clone timed out after ${CLONE_POLL_TIMEOUT_MS / 1000}s for ${url}`);
Expand Down
Loading