diff --git a/jsr.json b/jsr.json index 864b20d..395bd3c 100644 --- a/jsr.json +++ b/jsr.json @@ -1,6 +1,6 @@ { "name": "@nilenso/ask-forge", - "version": "0.0.14", + "version": "0.0.15", "license": "MIT", "exports": "./src/index.ts", "exclude": [ diff --git a/src/index.ts b/src/index.ts index e491093..c28b54b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -141,9 +141,14 @@ 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 { + async connect( + repoUrl: string, + options: ConnectOptions = {}, + onProgress?: (message: string) => void, + ): Promise { const { config } = this; // getModel has strict generics tying provider to model IDs - cast for flexibility @@ -151,7 +156,7 @@ export class AskForgeClient { 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 = { diff --git a/src/sandbox/client.ts b/src/sandbox/client.ts index a5535bf..7b7b4d2 100644 --- a/src/sandbox/client.ts +++ b/src/sandbox/client.ts @@ -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 { + async clone(url: string, commitish?: string, onProgress?: (message: string) => void): Promise { const commit = commitish ?? "HEAD"; this.logger.debug("sandbox:client", `POST /clone url=${url} commitish=${commit}`); const t0 = Date.now(); @@ -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 }; } @@ -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) { @@ -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 }; } @@ -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}`);