Skip to content

Commit ee2e3a3

Browse files
committed
fix(ollama): propagate supportsTools, disable idle watchdog, fix thinking and keep_alive
- buildOllamaModelDefinition: compat always includes supportsTools, defaulting to true when capabilities are unknown (Ollama's /api/chat universally supports tool calls) - AgentModelEntryConfig: add optional compat override path - resolveLlmIdleTimeoutMs: move isLocalProviderBaseUrl check to top so local providers always get idleTimeout=0 (network silence during prompt evaluation is not a hang signal) - createConfiguredOllamaCompatStreamWrapper: guard think parameter with model.reasoning check; non-reasoning models get think:false forced - buildOllamaChatRequest: add keep_alive: "1h" default with requestParams spread override so model-level config can customize
1 parent 656fb80 commit ee2e3a3

7 files changed

Lines changed: 45 additions & 32 deletions

File tree

extensions/ollama/src/provider-models.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -247,13 +247,10 @@ export function buildOllamaModelDefinition(
247247
capabilities === undefined
248248
? isReasoningModelHeuristic(modelId)
249249
: capabilities.includes("thinking");
250-
const compat =
251-
capabilities === undefined
252-
? { supportsUsageInStreaming: true }
253-
: {
254-
supportsTools: capabilities.includes("tools"),
255-
supportsUsageInStreaming: true,
256-
};
250+
const compat = {
251+
supportsTools: capabilities === undefined ? true : capabilities.includes("tools"),
252+
supportsUsageInStreaming: true,
253+
};
257254
return {
258255
id: modelId,
259256
name: modelId,

extensions/ollama/src/stream.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -394,14 +394,25 @@ export function createConfiguredOllamaCompatStreamWrapper(
394394
const runtimeThinkValue = isNativeOllamaTransport
395395
? resolveOllamaThinkValue(ctx.thinkingLevel)
396396
: undefined;
397-
// "off" is also the implicit agent default. Preserve explicit native Ollama
398-
// model config unless the active run requests a non-off thinking level.
399-
const ollamaThinkValue =
400-
runtimeThinkValue === false && configuredThinkValue !== undefined
401-
? undefined
402-
: runtimeThinkValue;
403-
if (ollamaThinkValue !== undefined) {
404-
streamFn = createOllamaThinkingWrapper(streamFn, ollamaThinkValue);
397+
// Non-reasoning Ollama models cannot accept a truthy thinking/think
398+
// parameter — Ollama returns 400 "\"<model>\" does not support thinking".
399+
// Force think: false for these models regardless of runtime think level
400+
// or model-level config to prevent the error on fallback/think propagation.
401+
const modelSupportsThinking = model?.reasoning === true;
402+
if (!modelSupportsThinking) {
403+
if (runtimeThinkValue !== false || configuredThinkValue !== undefined) {
404+
streamFn = createOllamaThinkingWrapper(streamFn, false);
405+
}
406+
} else {
407+
// "off" is also the implicit agent default. Preserve explicit native Ollama
408+
// model config unless the active run requests a non-off thinking level.
409+
const ollamaThinkValue =
410+
runtimeThinkValue === false && configuredThinkValue !== undefined
411+
? undefined
412+
: runtimeThinkValue;
413+
if (ollamaThinkValue !== undefined) {
414+
streamFn = createOllamaThinkingWrapper(streamFn, ollamaThinkValue);
415+
}
405416
}
406417

407418
if (normalizeProviderId(ctx.provider) === "ollama" && isOllamaCloudKimiModelRef(ctx.modelId)) {
@@ -433,6 +444,7 @@ export function buildOllamaChatRequest(params: {
433444
stream: params.stream ?? true,
434445
...(params.tools && params.tools.length > 0 ? { tools: params.tools } : {}),
435446
...(params.options ? { options: params.options } : {}),
447+
keep_alive: "1h",
436448
...params.requestParams,
437449
};
438450
}

src/agents/pi-embedded-runner/run/llm-idle-timeout.test.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -189,20 +189,18 @@ describe("resolveLlmIdleTimeoutMs", () => {
189189
expect(resolveLlmIdleTimeoutMs({ model: { baseUrl: "" } })).toBe(DEFAULT_LLM_IDLE_TIMEOUT_MS);
190190
});
191191

192-
it("still honors an explicit provider request timeout for local providers", () => {
192+
it("disables idle watchdog for local providers even with explicit modelRequestTimeoutMs", () => {
193193
expect(
194194
resolveLlmIdleTimeoutMs({
195195
model: { baseUrl: "http://127.0.0.1:11434" },
196196
modelRequestTimeoutMs: 600_000,
197197
}),
198-
).toBe(600_000);
198+
).toBe(0);
199199
});
200200

201-
it("still applies agents.defaults.timeoutSeconds cap for local providers", () => {
201+
it("disables idle watchdog for local providers even with agents.defaults.timeoutSeconds", () => {
202202
const cfg = { agents: { defaults: { timeoutSeconds: 30 } } } as OpenClawConfig;
203-
expect(resolveLlmIdleTimeoutMs({ cfg, model: { baseUrl: "http://127.0.0.1:11434" } })).toBe(
204-
30_000,
205-
);
203+
expect(resolveLlmIdleTimeoutMs({ cfg, model: { baseUrl: "http://127.0.0.1:11434" } })).toBe(0);
206204
});
207205
});
208206

src/agents/pi-embedded-runner/run/llm-idle-timeout.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,17 @@ export function resolveLlmIdleTimeoutMs(params?: {
102102
modelRequestTimeoutMs?: number;
103103
model?: { baseUrl?: string };
104104
}): number {
105+
// The default watchdog is a network-silence-as-hang guard for cloud providers.
106+
// Local providers can legitimately stream nothing for many minutes during
107+
// prompt evaluation or thinking. Disable idle timeout early, before any
108+
// configured timeout clamping, so that local models (Ollama, LM Studio,
109+
// llama.cpp) are never subject to the network-silence heuristic regardless
110+
// of agent-level timeoutSeconds.
111+
const baseUrl = params?.model?.baseUrl;
112+
if (typeof baseUrl === "string" && baseUrl.length > 0 && isLocalProviderBaseUrl(baseUrl)) {
113+
return 0;
114+
}
115+
105116
const clampTimeoutMs = (valueMs: number) => Math.min(Math.floor(valueMs), MAX_SAFE_TIMEOUT_MS);
106117
const clampImplicitTimeoutMs = (valueMs: number) =>
107118
clampTimeoutMs(Math.min(valueMs, DEFAULT_LLM_IDLE_TIMEOUT_MS));
@@ -152,16 +163,6 @@ export function resolveLlmIdleTimeoutMs(params?: {
152163
return 0;
153164
}
154165

155-
// The default watchdog is a network-silence-as-hang guard for cloud providers.
156-
// Local providers can legitimately stream nothing for many minutes during
157-
// prompt evaluation or thinking, so falling back to the default would abort
158-
// valid local runs. Honor it only when the user has not opted out via the
159-
// baseUrl pointing at loopback / private-network / `.local`.
160-
const baseUrl = params?.model?.baseUrl;
161-
if (typeof baseUrl === "string" && baseUrl.length > 0 && isLocalProviderBaseUrl(baseUrl)) {
162-
return 0;
163-
}
164-
165166
return DEFAULT_LLM_IDLE_TIMEOUT_MS;
166167
}
167168

src/config/types.agent-defaults.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ export type AgentModelEntryConfig = {
3838
agentRuntime?: AgentRuntimePolicyConfig;
3939
/** Enable streaming for this model (default: true, false for Ollama to avoid SDK issue #1205). */
4040
streaming?: boolean;
41+
/** Runtime compatibility overrides (e.g., supportsTools for models that support tool calls). */
42+
compat?: import("./types.models.js").ModelCompatConfig;
4143
};
4244

4345
export type AgentModelListConfig = {

src/config/zod-schema.agent-defaults.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
BlockStreamingCoalesceSchema,
1515
CliBackendSchema,
1616
HumanDelaySchema,
17+
ModelCompatSchema,
1718
TypingModeSchema,
1819
} from "./zod-schema.core.js";
1920

@@ -73,6 +74,8 @@ export const AgentDefaultsSchema = z
7374
agentRuntime: AgentRuntimePolicySchema,
7475
/** Enable streaming for this model (default: true, false for Ollama to avoid SDK issue #1205). */
7576
streaming: z.boolean().optional(),
77+
/** Runtime compatibility overrides (e.g., supportsTools for models that support tool calls). */
78+
compat: ModelCompatSchema.optional(),
7679
})
7780
.strict(),
7881
)

src/config/zod-schema.core.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ export const SecretsConfigSchema = z
184184

185185
const ModelApiSchema = z.enum(MODEL_APIS);
186186

187-
const ModelCompatSchema = z
187+
export const ModelCompatSchema = z
188188
.object({
189189
supportsStore: z.boolean().optional(),
190190
supportsPromptCacheKey: z.boolean().optional(),

0 commit comments

Comments
 (0)