Skip to content
Draft
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
36 changes: 26 additions & 10 deletions src/adapters/standalone/llm-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export interface StandaloneLLMConfig {
maxTokens?: number;
/** Request timeout in milliseconds (default: 120_000). */
timeoutMs?: number;
/** Disable thinking/reasoning mode (DeepSeek: sets thinking.type=disabled in request body). */
disableThinking?: boolean;
}

// ============================
Expand Down Expand Up @@ -149,12 +151,6 @@ function createSandboxedTools(workspaceDir: string, logger?: Logger) {
};
}

/** Read-only tool subset — used when enableTools=false to avoid empty tools rejection. */
function createReadOnlyTools(workspaceDir: string, logger?: Logger) {
const all = createSandboxedTools(workspaceDir, logger);
return { read_file: all.read_file };
}

// ============================
// StandaloneLLMRunner
// ============================
Expand Down Expand Up @@ -185,29 +181,49 @@ export class StandaloneLLMRunner implements LLMRunner {

this.logger?.debug?.(
`${TAG} run() start: taskId=${params.taskId}, model=${this.model}, ` +
`tools=${this.enableTools}, timeout=${timeoutMs}ms`,
`tools=${this.enableTools}, disableThinking=${this.config.disableThinking === true}, timeout=${timeoutMs}ms`,
);

// Create OpenAI-compatible provider via AI SDK
// Use "compatible" mode to call /chat/completions (not Responses API),
// which works with all OpenAI-compatible backends (DeepSeek, Qwen, etc.)
const disableThinking = this.config.disableThinking === true;
const provider = createOpenAI({
baseURL: this.config.baseUrl,
apiKey: this.config.apiKey,
compatibility: "compatible",
...(disableThinking
? {
fetch: async (url, init) => {
if (typeof init?.body === "string") {
try {
const body = JSON.parse(init.body);
body.thinking = { type: "disabled" };
init = { ...init, body: JSON.stringify(body) };
} catch (error) {
const errMsg = error instanceof Error ? error.message : String(error);
this.logger?.warn?.(`${TAG} failed to inject thinking=disabled: ${errMsg}`);
}
}
return fetch(url, init);
},
}
: {}),
});

// Select tools based on mode
// For pure text tasks like L1 extraction, avoid exposing any tools.
// DeepSeek may opportunistically issue tool calls even when the prompt
// asks for plain JSON, which can lead to empty/non-JSON output.
const tools = this.enableTools
? createSandboxedTools(workspaceDir, this.logger)
: createReadOnlyTools(workspaceDir, this.logger);
: undefined;

try {
const result = await generateText({
model: provider.chat(this.model),
system: params.systemPrompt,
prompt: params.prompt,
tools,
...(tools ? { tools } : {}),
stopWhen: stepCountIs(this.enableTools ? MAX_TOOL_ITERATIONS : 1),
maxOutputTokens: maxTokens,
abortSignal: AbortSignal.timeout(timeoutMs),
Expand Down
12 changes: 12 additions & 0 deletions src/gateway/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export function loadGatewayConfig(overrides?: Partial<GatewayConfig>): GatewayCo
model: env("TDAI_LLM_MODEL") ?? str(llmConfig, "model") ?? "gpt-4o",
maxTokens: envInt("TDAI_LLM_MAX_TOKENS") ?? num(llmConfig, "maxTokens") ?? 4096,
timeoutMs: envInt("TDAI_LLM_TIMEOUT_MS") ?? num(llmConfig, "timeoutMs") ?? 120_000,
disableThinking: envBool("TDAI_LLM_DISABLE_THINKING") ?? bool(llmConfig, "disableThinking") ?? false,
};

// Memory config (reuse the plugin's parseConfig for full compatibility)
Expand Down Expand Up @@ -184,6 +185,17 @@ function envInt(key: string): number | undefined {
return Number.isFinite(n) ? n : undefined;
}

function envBool(key: string): boolean | undefined {
const v = env(key);
if (!v) return undefined;
return v === "1" || v.toLowerCase() === "true";
}

function bool(src: Record<string, unknown>, key: string): boolean | undefined {
const v = src[key];
return typeof v === "boolean" ? v : undefined;
}

function obj(c: Record<string, unknown>, key: string): Record<string, unknown> {
const v = c[key];
return v && typeof v === "object" && !Array.isArray(v) ? v as Record<string, unknown> : {};
Expand Down