Configuration
Connect Options
The connect() method accepts git-related options:
// Connect to a specific commit, branch, or tagconst session = await client.connect("https://github.com/owner/repo", { commitish: "v1.0.0",});
// Connect to a private repository with a tokenconst session = await client.connect("https://github.com/owner/repo", { token: process.env.GITHUB_TOKEN,});
// Explicitly specify the forge (auto-detected for github.com and gitlab.com)const session = await client.connect("https://gitlab.example.com/owner/repo", { forge: "gitlab", token: process.env.GITLAB_TOKEN,});Model and Provider
The AskForgeClient accepts a ForgeConfig object that controls the AI model and behavior.
By default, the client uses OpenRouter with anthropic/claude-sonnet-4.6. You can override both provider and model (they must be specified together). The corresponding API key environment variable is resolved automatically (e.g. OPENROUTER_API_KEY, ANTHROPIC_API_KEY).
Available providers and model IDs are defined in @mariozechner/pi-ai.
import { AskForgeClient, type ForgeConfig } from "@nilenso/ask-forge";
// Use defaults (openrouter + anthropic/claude-sonnet-4.6)const client = new AskForgeClient();
// Or specify a different provider/modelconst client = new AskForgeClient({ provider: "anthropic", model: "claude-sonnet-4.6",});
// Full configurationconst client = new AskForgeClient({ provider: "openrouter", model: "anthropic/claude-sonnet-4.6", maxIterations: 10, // Optional: default is 20});System Prompt
By default, ask-forge builds a system prompt that includes the repository URL and commit SHA. You can override it to customize the assistant’s behavior:
const client = new AskForgeClient({ systemPrompt: "You are a security auditor. Focus on identifying vulnerabilities, insecure patterns, and potential attack vectors in the codebase.",});You can also build the default prompt yourself and extend it:
import { AskForgeClient, buildDefaultSystemPrompt } from "@nilenso/ask-forge";
const base = buildDefaultSystemPrompt("https://github.com/owner/repo", "abc123");const client = new AskForgeClient({ systemPrompt: `${base}\n\nAlways respond in Spanish.`,});Logging
The second parameter to the constructor controls logging:
import { AskForgeClient, consoleLogger, nullLogger, type Logger,} from "@nilenso/ask-forge";
// Use console logger (default)const client = new AskForgeClient(config, consoleLogger);
// Silence all loggingconst client = new AskForgeClient(config, nullLogger);
// Custom loggerconst customLogger: Logger = { log: (label, content) => myLogSystem.info(`${label}: ${content}`), error: (label, error) => myLogSystem.error(label, error),};const client = new AskForgeClient(config, customLogger);Sandboxing
For production deployments or untrusted repositories, enable sandbox mode to run all operations in an isolated container:
const client = new AskForgeClient({ sandbox: { baseUrl: "http://localhost:8080", timeoutMs: 120_000, secret: "optional-auth-secret", },});When enabled, repository cloning and all tool execution (file reads, searches, git operations) happen inside the sandbox. The host filesystem is never accessed directly.
See the Sandboxed Execution guide for security layers, architecture, and how to run the sandbox server.
Thinking
Control the model’s reasoning/thinking behavior via the thinking field. Ask Forge supports two modes:
- Effort-based (cross-provider): Set an effort level that pi-ai maps to each provider’s native format (
reasoning.effortfor OpenAI,thinkingfor Anthropic, etc.) - Adaptive (Anthropic 4.6 only): The model decides when and how much to think per request
// OpenAI — effort-based reasoningconst client = new AskForgeClient({ provider: "openai", model: "o3", thinking: { effort: "low" },});
// Anthropic 4.5 — effort-based (older model, no adaptive support)const client = new AskForgeClient({ provider: "anthropic", model: "claude-sonnet-4-5-20251022", thinking: { effort: "high", budgetOverrides: { high: 10000 } },});
// Anthropic 4.6 — adaptive (model decides when/how much to think)const client = new AskForgeClient({ provider: "anthropic", model: "claude-sonnet-4-6", thinking: { type: "adaptive" },});
// Anthropic 4.6 — adaptive with explicit effort guidanceconst client = new AskForgeClient({ provider: "anthropic", model: "claude-sonnet-4-6", thinking: { type: "adaptive", effort: "medium" },});
// No thinking (default)const client = new AskForgeClient();| Field | Type | Description |
|---|---|---|
type | "adaptive" | Anthropic 4.6 only. Omit for effort-based mode. |
effort | "minimal" | "low" | "medium" | "high" | "xhigh" | Required for effort-based, optional for adaptive. |
budgetOverrides | ThinkingBudgets | Custom token budgets per level (effort-based only). |
Context Compaction
When conversations grow long, ask-forge can automatically summarize older messages to stay within the model’s context window. This is enabled by default.
const client = new AskForgeClient({ compaction: { enabled: true, // default: true contextWindow: 200_000, // default: 200K tokens reserveTokens: 16_384, // tokens reserved for the response keepRecentTokens: 20_000, // recent messages to keep unsummarized },});Tracing
ask-forge emits OpenTelemetry spans following the GenAI semantic conventions. The library depends only on @opentelemetry/api — if no OTel SDK is installed, all tracing is a zero-overhead no-op.
To send traces to any OTel-compatible backend (Jaeger, Honeycomb, Langfuse, etc.):
- Install
@opentelemetry/sdk-nodeand your backend’s exporter or span processor - Create and start a
NodeSDKinstance before creating anyAskForgeClient - All
session.ask()calls will automatically emit spans to your backend
Console (development)
import { NodeSDK } from "@opentelemetry/sdk-node";import { ConsoleSpanExporter } from "@opentelemetry/sdk-trace-base";
const sdk = new NodeSDK({ traceExporter: new ConsoleSpanExporter() });sdk.start();Langfuse
import { NodeSDK } from "@opentelemetry/sdk-node";import { LangfuseSpanProcessor } from "@langfuse/otel";
const sdk = new NodeSDK({ spanProcessors: [new LangfuseSpanProcessor()],});sdk.start();See the Observability guide for the full trace structure and captured metrics.
Streaming Progress
Use the onProgress callback to receive real-time events during inference:
const result = await session.ask("Find all API endpoints", { onProgress: (event) => { switch (event.type) { case "tool_call": console.log(`Using tool: ${event.name}`); break; case "text_delta": process.stdout.write(event.text); break; } },});