mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-10-30 19:32:39 +08:00
Merge remote-tracking branch 'origin/main' into markijbema/kilo-opencode-v1.14.29
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Warn when a model hits its output limit before finishing a response.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Remember the main auto-approve toggle preference across VS Code reloads and extension restarts.
|
||||
@@ -33,6 +33,10 @@ The underlying models behind each Auto Model tier are updated server-side as bet
|
||||
- **Balanced** — Routes to a cost-effective model for all modes. The specific model is selected based on the API interface in use, but does not vary by mode. A good default for most developers who want strong AI assistance without paying frontier prices.
|
||||
- **Free** — Routes to the best available free models on OpenRouter, splitting traffic across them. Because free model availability shifts over time as providers change promotional periods, the mapping is updated server-side — you always get the best free option without having to track what's currently available. Quality will be lower than paid tiers, and the models may change over time.
|
||||
|
||||
{% callout type="warning" title="Data handling for Auto Free" %}
|
||||
Auto Free may route your requests to providers that log prompts and outputs and use them to improve their services. In particular, it may route to NVIDIA's free endpoints, which are provided under the [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) — trial use only, not for production or sensitive data. Do not submit personal or confidential data when using Auto Free.
|
||||
{% /callout %}
|
||||
|
||||
## Benefits
|
||||
|
||||
### Cost Optimization
|
||||
|
||||
@@ -27,7 +27,7 @@ While the specifics change constantly, some principles stay consistent:
|
||||
- Use the **model selector** in the chat prompt area to pick a model for the current session. You can also type `/models` to open the model picker.
|
||||
- When the selected model supports variants, type `/variant` to open the reasoning effort selector.
|
||||
- Set per-agent defaults and a global default in the **Settings** panel (Models tab), or directly in the `kilo.jsonc` config file.
|
||||
- **Model precedence:** Session override → Last picked per agent → Per-agent config → Global config → Kilo Auto (free).
|
||||
- **Model precedence:** Session override → Last picked per agent → Per-agent config → Global config → [Auto Free](/docs/code-with-ai/agents/auto-model#tiers) (note: Auto Free may route to providers that log prompts — see the Auto Model page for details).
|
||||
- The model selector remembers the last model you picked for each agent — switching agents restores your previous choice. A manual pick always beats config settings; use the **reset button** (visible when your active model differs from config) to go back to the config default.
|
||||
|
||||
{% /tab %}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
---
|
||||
title: "Auto Model Tiers"
|
||||
description: "Architecture of Kilo Auto — a family of smart model tiers that match users to the right models without requiring AI expertise"
|
||||
description: "Architecture of the Auto Model tiers — a family of smart model tiers that match users to the right models without requiring AI expertise"
|
||||
---
|
||||
|
||||
# Auto Model Tiers
|
||||
|
||||
## Overview
|
||||
|
||||
Kilo Auto is a model routing system that automatically selects the optimal AI model based on the user's current mode (Code, Architect, Debug, etc.). It comes in multiple tiers so that every user — regardless of budget, preference, or expertise — gets a "just works" experience without needing to understand the AI model landscape.
|
||||
Auto Model is a routing system that automatically selects the optimal AI model based on the user's current mode (Code, Architect, Debug, etc.). It comes in multiple tiers so that every user — regardless of budget, preference, or expertise — gets a "just works" experience without needing to understand the AI model landscape.
|
||||
|
||||
Three tiers are user-facing, and one is internal:
|
||||
|
||||
@@ -68,6 +68,8 @@ For the current mode-to-model mappings, see the [Auto Model user docs](/docs/cod
|
||||
|
||||
**Constraints**: Free models do not vary by mode — the same model is used for every mode within a session. Quality will be lower than Frontier or Balanced tiers — this is a tradeoff users accept by choosing free.
|
||||
|
||||
**Data handling**: Auto Free may route to providers that log prompts and outputs and use them to improve their services, including NVIDIA's free endpoints (governed by the [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf)). This is surfaced to users alongside Auto Free mentions in the user-facing docs.
|
||||
|
||||
### Auto: Small (internal)
|
||||
|
||||
**Who it's for**: Not user-facing. Used internally by Kilo for lightweight background tasks (session titles, commit messages, conversation summaries).
|
||||
@@ -170,7 +172,8 @@ The client-side chain works as follows:
|
||||
## Data and compliance
|
||||
|
||||
- **Frontier**: Uses Anthropic models with no training on user data.
|
||||
- **Balanced and Free**: The underlying models may have different data handling policies depending on the provider. This should be documented per-tier so enterprise users can make informed choices.
|
||||
- **Balanced**: As a paid tier, underlying providers are selected with data-handling policies suitable for professional use. Prefer providers with stronger privacy posture when updating the routing.
|
||||
- **Free**: May route to providers that log prompts and outputs and use them to improve their services, including NVIDIA's free endpoints (see [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf)). Users should avoid submitting personal or confidential data. Surface this disclosure in proximity to every user-facing Auto Free mention.
|
||||
- **Small**: Same concern as Balanced/Free — the model selected depends on credit status, which may route to providers with different policies.
|
||||
|
||||
## Features for the future
|
||||
|
||||
@@ -60,7 +60,6 @@ Several models are available at no cost, subject to rate limits:
|
||||
|
||||
| Model ID | Description |
|
||||
|---|---|
|
||||
| `bytedance-seed/dola-seed-2.0-pro:free` | ByteDance Dola Seed 2.0 Pro |
|
||||
| `x-ai/grok-code-fast-1:optimized:free` | xAI Grok Code Fast 1 Optimized |
|
||||
| `nvidia/nemotron-3-super-120b-a12b:free` | NVIDIA Nemotron 3 Super 120B |
|
||||
| `arcee-ai/trinity-large-thinking:free` | Arcee Trinity Large |
|
||||
@@ -74,7 +73,7 @@ Provided under the [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia
|
||||
|
||||
## Auto models
|
||||
|
||||
Kilo Auto virtual models automatically select the best underlying model based on the task type. The selection is controlled by the `x-kilocode-mode` request header.
|
||||
Auto virtual models automatically select the best underlying model based on the task type. The selection is controlled by the `x-kilocode-mode` request header.
|
||||
|
||||
{% callout type="info" title="Underlying models can change" %}
|
||||
The mappings below reflect the current routing. The underlying models behind each `kilo-auto/*` tier are updated server-side as better options become available or as providers change pricing and availability — the tier IDs themselves remain stable.
|
||||
@@ -104,6 +103,10 @@ Great balance of price and capability. The resolved model depends on the API int
|
||||
|
||||
Free with limited capability. No credits required. The resolved model is selected dynamically per session from a curated set of available free models; the mapping updates server-side as free model availability shifts.
|
||||
|
||||
{% callout type="warning" title="Data handling for Auto Free" %}
|
||||
Auto Free may route your requests to providers that log prompts and outputs and use them to improve their services — including NVIDIA's free endpoints, which are provided under the [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) (trial use only, not for production or sensitive data). Do not submit personal or confidential data when using Auto Free.
|
||||
{% /callout %}
|
||||
|
||||
### `kilo-auto/small`
|
||||
|
||||
Automatically routes to a small, fast model for lightweight background tasks (session titles, commit messages, summaries).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: "Using Kilo for Free"
|
||||
description: "How to use Kilo Code for free — Auto Model Free, finding free models, free autocomplete, and free background tasks"
|
||||
description: "How to use Kilo Code for free — Auto Free, finding free models, free autocomplete, and free background tasks"
|
||||
---
|
||||
|
||||
# Using Kilo for Free
|
||||
@@ -19,9 +19,13 @@ Each of these consumes credits by default. **To use Kilo entirely for free, conf
|
||||
|
||||
Kilo provides free models for coding tasks through the Kilo Gateway and partner providers.
|
||||
|
||||
### Auto Model Free
|
||||
### Auto Free
|
||||
|
||||
The easiest way to get started is [**Auto Model Free**](/docs/code-with-ai/agents/auto-model) (`kilo-auto/free`). This is a Kilo-provided model tier that automatically routes your requests to the best available free models — no configuration needed.
|
||||
The easiest way to get started is [**Auto Free**](/docs/code-with-ai/agents/auto-model) (`kilo-auto/free`). This is a Kilo-provided model tier that automatically routes your requests to the best available free models — no configuration needed.
|
||||
|
||||
{% callout type="warning" title="Data handling for Auto Free" %}
|
||||
Auto Free may route your requests to providers that log prompts and outputs and use them to improve their services — including NVIDIA's free endpoints, which are provided under the [NVIDIA API Trial Terms of Service](https://assets.ngc.nvidia.com/products/api-catalog/legal/NVIDIA%20API%20Trial%20Terms%20of%20Service.pdf) (trial use only, not for production or sensitive data). Do not submit personal or confidential data when using Auto Free.
|
||||
{% /callout %}
|
||||
|
||||
### Finding Other Free Models
|
||||
|
||||
@@ -65,7 +69,7 @@ See the [Mistral Setup Guide](/docs/code-with-ai/features/autocomplete/mistral-s
|
||||
|
||||
## Free Background Tasks
|
||||
|
||||
Kilo uses a small model in the background for tasks like session titling. By default this is Kilo Auto Small, which consumes credits. If the small model is unavailable, Kilo falls back to your primary model — which may also consume credits if it's a paid model.
|
||||
Kilo uses a small model in the background for tasks like session titling. By default this is Auto Small, which consumes credits. If the small model is unavailable, Kilo falls back to your primary model — which may also consume credits if it's a paid model.
|
||||
|
||||
To avoid credit usage for background tasks, set the small model to a free model:
|
||||
|
||||
|
||||
@@ -781,6 +781,11 @@
|
||||
"default": false,
|
||||
"description": "Load CLAUDE.md instructions and skills from your Claude Code configuration directory into Kilo sessions. Enable this if you want Kilo to use your Claude Code instructions and skills."
|
||||
},
|
||||
"kilo-code.new.autoApprove.enabled": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Start Kilo Code with the main auto-approve toggle enabled. When enabled, permission prompts are approved automatically."
|
||||
},
|
||||
"kilo-code.new.browserAutomation.enabled": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
|
||||
@@ -20,6 +20,9 @@ export interface AutoApproveController {
|
||||
onChange(listener: (active: boolean) => void): { dispose(): void }
|
||||
}
|
||||
|
||||
const CONFIG = "kilo-code.new.autoApprove"
|
||||
const KEY = "enabled"
|
||||
|
||||
/**
|
||||
* Runtime auto-accept toggle for permissions.
|
||||
*
|
||||
@@ -34,7 +37,7 @@ export function registerToggleAutoApprove(
|
||||
resolve: DirectoryResolver,
|
||||
directories: AllDirectories,
|
||||
): AutoApproveController {
|
||||
let active = false
|
||||
let active = readActive()
|
||||
// Bumped on disable to invalidate in-flight enable drains
|
||||
let generation = 0
|
||||
const listeners = new Set<(active: boolean) => void>()
|
||||
@@ -43,10 +46,15 @@ export function registerToggleAutoApprove(
|
||||
for (const listener of listeners) listener(active)
|
||||
}
|
||||
|
||||
const toggle = async () => {
|
||||
active = !active
|
||||
const setActive = async (next: boolean) => {
|
||||
active = next
|
||||
generation++
|
||||
notify()
|
||||
await vscode.workspace.getConfiguration(CONFIG).update(KEY, active, target())
|
||||
}
|
||||
|
||||
const toggle = async () => {
|
||||
await setActive(!active)
|
||||
const snapshot = generation
|
||||
|
||||
if (!active) {
|
||||
@@ -88,6 +96,16 @@ export function registerToggleAutoApprove(
|
||||
})
|
||||
|
||||
context.subscriptions.push({ dispose: unsubscribe })
|
||||
context.subscriptions.push(
|
||||
vscode.workspace.onDidChangeConfiguration((event) => {
|
||||
if (!event.affectsConfiguration(`${CONFIG}.${KEY}`)) return
|
||||
const next = readActive()
|
||||
if (next === active) return
|
||||
active = next
|
||||
generation++
|
||||
notify()
|
||||
}),
|
||||
)
|
||||
|
||||
context.subscriptions.push(vscode.commands.registerCommand("kilo-code.new.toggleAutoApprove", toggle))
|
||||
|
||||
@@ -108,6 +126,17 @@ export function registerToggleAutoApprove(
|
||||
}
|
||||
}
|
||||
|
||||
function readActive(): boolean {
|
||||
return vscode.workspace.getConfiguration(CONFIG).get(KEY, false)
|
||||
}
|
||||
|
||||
function target(): vscode.ConfigurationTarget {
|
||||
const info = vscode.workspace.getConfiguration(CONFIG).inspect<boolean>(KEY)
|
||||
if (info?.workspaceFolderValue !== undefined) return vscode.ConfigurationTarget.WorkspaceFolder
|
||||
if (info?.workspaceValue !== undefined) return vscode.ConfigurationTarget.Workspace
|
||||
return vscode.ConfigurationTarget.Global
|
||||
}
|
||||
|
||||
function tryGetClient(connectionService: KiloConnectionService): KiloClient | undefined {
|
||||
try {
|
||||
return connectionService.getClient()
|
||||
|
||||
@@ -1017,6 +1017,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
? language.t("prompt.action.autoApprove.disable")
|
||||
: language.t("prompt.action.autoApprove.enable")
|
||||
}
|
||||
aria-pressed={autoApprove()}
|
||||
class={`prompt-auto-approve-button ${autoApprove() ? "prompt-auto-approve-button--active" : ""}`}
|
||||
>
|
||||
<Icon name="shield" size="small" />
|
||||
|
||||
@@ -10,6 +10,9 @@ import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
|
||||
export namespace KiloSessionProcessor {
|
||||
const log = Log.create({ service: "session.processor.kilo" })
|
||||
export const OUTPUT_LENGTH_WARNING = "The model hit its output limit, so this response may be incomplete."
|
||||
export const REASONING_LENGTH_WARNING =
|
||||
"The model hit its output limit while reasoning and produced no actionable output. Try disabling reasoning or increasing the output limit."
|
||||
|
||||
/**
|
||||
* Track LLM completion telemetry for a finished step.
|
||||
@@ -122,4 +125,18 @@ export namespace KiloSessionProcessor {
|
||||
msg.finish = "stop"
|
||||
}
|
||||
}
|
||||
|
||||
export function lengthWarning(input: {
|
||||
msg: MessageV2.Assistant
|
||||
step: { reasoning: boolean; text: boolean; tool: boolean }
|
||||
}) {
|
||||
if (input.msg.summary) return
|
||||
if (input.msg.finish !== "length") return
|
||||
if (input.step.reasoning && !input.step.text && !input.step.tool) {
|
||||
log.warn("reasoning-only length stop", { messageID: input.msg.id })
|
||||
return REASONING_LENGTH_WARNING
|
||||
}
|
||||
log.warn("length stop", { messageID: input.msg.id })
|
||||
return OUTPUT_LENGTH_WARNING
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export const BUILTIN_SKILLS: BuiltinSkill[] = [
|
||||
{
|
||||
name: "kilo-config",
|
||||
description:
|
||||
"Guide for configuring Kilo CLI and locating config, command, agent, and skill paths (global, project, legacy), plus MCP servers, permissions, instructions, plugins, providers, kilo.json fields, and TUI settings (themes, appearance, keybinds, ctrl+p commands). Use when the user asks about configuring Kilo, where it loads things from, or how to change settings.",
|
||||
"Guide for Kilo configuration: config paths, kilo.json fields, commands, agents, skills, permissions, MCPs, providers, TUI settings, plus Agent Manager worktree setup/run scripts, workflows, and state. Use for Kilo config questions, locating loaded config, changing settings, or Agent Manager questions about run/setup scripts, worktree setup/workflows, apply/merge/PR/conflicts, missing sessions/worktrees, and agent-manager.json recovery.",
|
||||
content: KILO_CONFIG,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
All config lives in `kilo.json` (or `kilo.jsonc`). Precedence low-to-high: remote well-known, global (`~/.config/kilo/kilo.json`), env `KILO_CONFIG`, project `./kilo.json`, `.kilo/kilo.json`, `KILO_CONFIG_CONTENT`, managed (see Config File Locations). Deep-merged; later wins.
|
||||
|
||||
This also covers where Kilo looks for config files, commands, agents, and skills across project, global, and legacy paths such as `.kilo/`, `.kilocode/`, `.opencode/`, and `~/.config/kilo/`.
|
||||
This also covers where Kilo looks for config files, commands, agents, and skills across project, global, and legacy paths such as `.kilo/`, `.kilocode/`, `.opencode/`, and `~/.config/kilo/`, plus Agent Manager setup/run scripts in the VS Code extension.
|
||||
|
||||
## Commands (`.kilo/command/*.md`)
|
||||
|
||||
@@ -67,6 +67,58 @@ System prompt for this agent.
|
||||
|
||||
Markdown files in `.kilo/workflows/` or `.kilocode/workflows/` (project-level) and `~/.kilo/workflows/` or `~/.kilocode/workflows/` (global). These are automatically converted to commands at startup. The filename (minus `.md`) becomes the command name. Project workflows override global ones with the same name.
|
||||
|
||||
## Agent Manager Setup And Run Scripts
|
||||
|
||||
Agent Manager setup/run scripts are project files in the main repository's `.kilo/` directory. They are not `kilo.json` settings and should not be configured inside generated `.kilo/worktrees/<name>/` checkouts.
|
||||
|
||||
Agent Manager worktrees usually live under `.kilo/worktrees/`. Think of each worktree as a separate checkout on its own branch: it enables parallel edits, but dependencies, build output, caches, databases, and generated files can consume significant disk space across many worktrees.
|
||||
|
||||
### Worktree workflow and conflicts
|
||||
|
||||
To bring changes back, choose one path: Agent Manager Apply for selected changes, merge the worktree branch into the target branch, or create/update a PR from the worktree. Agent Manager has native PR support and shows PR status on worktrees.
|
||||
|
||||
For conflict-heavy work, resolve inside the worktree before integration: merge or rebase the original/base branch into that worktree, ask the agent there to resolve conflicts and run checks, then apply, merge, or update the PR. Do not use `git stash` or autostash because stashes are shared across worktrees.
|
||||
|
||||
Agent Manager does not fully orchestrate dependencies across worktrees; users usually choose and sequence worktrees themselves. For larger parallel efforts, suggest stabilizing shared contracts, schemas, interfaces, routes, file layout, or test shape on the original/base branch before creating separate worktrees.
|
||||
|
||||
### Setup script
|
||||
|
||||
Setup scripts run once when a managed worktree is created, imported, or promoted, before the agent session starts. Use them for nested env files, local config, dependencies, databases, certificates, or other per-worktree setup.
|
||||
|
||||
| Platform | Filenames checked in order |
|
||||
|---|---|
|
||||
| macOS / Linux | `.kilo/setup-script`, `.kilo/setup-script.sh` |
|
||||
| Windows | `.kilo/setup-script.ps1`, `.kilo/setup-script.cmd`, `.kilo/setup-script.bat` |
|
||||
|
||||
Behavior: runs from the worktree directory with `WORKTREE_PATH` set to the absolute worktree path and `REPO_PATH` set to the main repository root. Agent Manager copies root-level `.env` and `.env.*` files before setup without overwriting existing files; nested env files or other project-specific local files need setup script handling. Setup has a 5 minute timeout, and failures leave the worktree available for inspection.
|
||||
|
||||
### Run script
|
||||
|
||||
Run scripts start or stop the user's project for the selected Agent Manager context. Use them for dev servers, watchers, emulators, queues, or other commands behind the Run button.
|
||||
|
||||
| Platform | Filenames checked in order |
|
||||
|---|---|
|
||||
| macOS / Linux | `.kilo/run-script`, `.kilo/run-script.sh` |
|
||||
| Windows | `.kilo/run-script.ps1`, `.kilo/run-script.cmd`, `.kilo/run-script.bat` |
|
||||
|
||||
Behavior: runs from the selected worktree directory, or the main repo root when `LOCAL` is selected. Receives `WORKTREE_PATH` as the current run directory and `REPO_PATH` as the main repository root. If no valid run script exists, Run opens or creates the default script template instead of running. Run status is in memory only and is not persisted in `.kilo/agent-manager.json`.
|
||||
|
||||
When the project supports it, avoid fixed global resources across worktrees by deriving ports, caches, Docker Compose project names, emulators, or databases from `WORKTREE_PATH` or the branch.
|
||||
|
||||
### Troubleshooting scripts
|
||||
|
||||
- If Run opens configuration instead of running, no valid run script exists for the current platform.
|
||||
- If a script is ignored, verify the platform-specific filename from the tables above.
|
||||
- For port conflicts (`EADDRINUSE`, browser/tests hitting the wrong worktree), inspect the app's dev-server config as well as `.kilo/run-script`. If fixing it requires application changes, ask whether the user wants the app made configurable or only wants a run-script workaround.
|
||||
- If commands are missing, inspect how VS Code was launched. Run scripts load the user shell environment, but setup scripts only receive explicit `WORKTREE_PATH` and `REPO_PATH` from the task adapter, so `PATH` can differ.
|
||||
- If setup times out, keep setup under 5 minutes or move long-running work into the run script or manual setup.
|
||||
- If output is not visible in the Agent Manager chat terminal, explain that setup/run scripts write to VS Code task terminals. Ask the user for that output if it is needed for debugging.
|
||||
- If unrelated conflicts appear, check for `git stash` usage. If stash caused it, suggest adding `AGENTS.md`, skill, or prompt guidance to avoid stash-based worktree merge flows.
|
||||
|
||||
### `agent-manager.json`
|
||||
|
||||
Agent Manager persists UI, worktree, and session state in `.kilo/agent-manager.json`. Treat this file as diagnostic or recovery state for lost sessions, stale worktrees, missing UI state, or external worktree deletion/movement. It can be large, so inspect it selectively. It does not store script contents, run status, live tasks, or terminal mappings, and should not be edited to configure run/setup behavior.
|
||||
|
||||
## Permissions
|
||||
|
||||
Scalar form applies to all patterns. Object form maps glob patterns to actions. Evaluated top-to-bottom; first match wins.
|
||||
|
||||
@@ -916,7 +916,7 @@ export const toModelMessagesEffect = Effect.fnUntraced(function* (
|
||||
parts: [],
|
||||
}
|
||||
for (const part of msg.parts) {
|
||||
if (part.type === "text")
|
||||
if (part.type === "text" && !part.ignored) // kilocode_change - keep local UI warnings out of future prompts
|
||||
assistantMessage.parts.push({
|
||||
type: "text",
|
||||
text: part.text,
|
||||
|
||||
@@ -75,6 +75,7 @@ interface ProcessorContext extends Input {
|
||||
currentText: MessageV2.TextPart | undefined
|
||||
reasoningMap: Record<string, MessageV2.ReasoningPart>
|
||||
stepStart: number // kilocode_change
|
||||
step: { reasoning: boolean; text: boolean; tool: boolean } // kilocode_change
|
||||
}
|
||||
|
||||
type StreamEvent = Event
|
||||
@@ -126,6 +127,7 @@ export const layer: Layer.Layer<
|
||||
currentText: undefined,
|
||||
reasoningMap: {},
|
||||
stepStart: 0, // kilocode_change
|
||||
step: { reasoning: false, text: false, tool: false }, // kilocode_change
|
||||
}
|
||||
let aborted = false
|
||||
const ac = new AbortController() // kilocode_change — abort controller for offline handler
|
||||
@@ -249,6 +251,7 @@ export const layer: Layer.Layer<
|
||||
|
||||
case "reasoning-start":
|
||||
if (value.id in ctx.reasoningMap) return
|
||||
ctx.step.reasoning = true // kilocode_change
|
||||
ctx.reasoningMap[value.id] = {
|
||||
id: PartID.ascending(),
|
||||
messageID: ctx.assistantMessage.id,
|
||||
@@ -288,6 +291,7 @@ export const layer: Layer.Layer<
|
||||
if (ctx.assistantMessage.summary) {
|
||||
throw new Error(`Tool call not allowed while generating summary: ${value.toolName}`)
|
||||
}
|
||||
ctx.step.tool = true // kilocode_change
|
||||
const part = yield* session.updatePart({
|
||||
id: ctx.toolcalls[value.id]?.partID ?? PartID.ascending(),
|
||||
messageID: ctx.assistantMessage.id,
|
||||
@@ -316,6 +320,7 @@ export const layer: Layer.Layer<
|
||||
if (ctx.assistantMessage.summary) {
|
||||
throw new Error(`Tool call not allowed while generating summary: ${value.toolName}`)
|
||||
}
|
||||
ctx.step.tool = true // kilocode_change
|
||||
// kilocode_change start — create tool part if tool-input-start was never emitted
|
||||
if (!ctx.toolcalls[value.toolCallId]) {
|
||||
log.warn("tool-call without prior tool-input-start", {
|
||||
@@ -401,6 +406,7 @@ export const layer: Layer.Layer<
|
||||
|
||||
case "start-step":
|
||||
ctx.stepStart = performance.now() // kilocode_change
|
||||
ctx.step = { reasoning: false, text: false, tool: false } // kilocode_change
|
||||
if (!ctx.snapshot) ctx.snapshot = yield* snapshot.track()
|
||||
yield* session.updatePart({
|
||||
id: PartID.ascending(),
|
||||
@@ -442,6 +448,19 @@ export const layer: Layer.Layer<
|
||||
tokens: usage.tokens,
|
||||
cost: usage.cost,
|
||||
})
|
||||
// kilocode_change start - surface output limit stops, with a stronger message for reasoning-only stops
|
||||
const warn = KiloSessionProcessor.lengthWarning({ msg: ctx.assistantMessage, step: ctx.step })
|
||||
if (warn) {
|
||||
yield* session.updatePart({
|
||||
id: PartID.ascending(),
|
||||
messageID: ctx.assistantMessage.id,
|
||||
sessionID: ctx.assistantMessage.sessionID,
|
||||
type: "text",
|
||||
text: warn,
|
||||
ignored: true,
|
||||
})
|
||||
}
|
||||
// kilocode_change end
|
||||
yield* session.updateMessage(ctx.assistantMessage)
|
||||
if (ctx.snapshot) {
|
||||
const patch = yield* snapshot.patch(ctx.snapshot)
|
||||
@@ -488,6 +507,7 @@ export const layer: Layer.Layer<
|
||||
case "text-delta":
|
||||
if (!ctx.currentText) return
|
||||
ctx.currentText.text += value.text
|
||||
if (value.text.trim()) ctx.step.text = true // kilocode_change
|
||||
if (value.providerMetadata) ctx.currentText.metadata = value.providerMetadata
|
||||
yield* session.updatePartDelta({
|
||||
sessionID: ctx.currentText.sessionID,
|
||||
@@ -511,6 +531,7 @@ export const layer: Layer.Layer<
|
||||
},
|
||||
{ text: ctx.currentText.text },
|
||||
)).text
|
||||
if (ctx.currentText.text.trim()) ctx.step.text = true // kilocode_change
|
||||
{
|
||||
const end = Date.now()
|
||||
ctx.currentText.time = { start: ctx.currentText.time?.start ?? end, end }
|
||||
@@ -618,6 +639,7 @@ export const layer: Layer.Layer<
|
||||
yield* Effect.gen(function* () {
|
||||
ctx.currentText = undefined
|
||||
ctx.reasoningMap = {}
|
||||
ctx.step = { reasoning: false, text: false, tool: false } // kilocode_change
|
||||
const stream = llm.stream(streamInput)
|
||||
|
||||
yield* stream.pipe(
|
||||
|
||||
@@ -37,24 +37,30 @@ export function soul() {
|
||||
|
||||
export function provider(model: Provider.Model) {
|
||||
// kilocode_change start
|
||||
switch (model.prompt) {
|
||||
case "anthropic":
|
||||
return [PROMPT_ANTHROPIC]
|
||||
case "anthropic_without_todo":
|
||||
return [PROMPT_DEFAULT]
|
||||
case "beast":
|
||||
return [PROMPT_BEAST]
|
||||
case "codex":
|
||||
return [PROMPT_CODEX]
|
||||
case "gemini":
|
||||
return [PROMPT_GEMINI]
|
||||
case "gpt55":
|
||||
return [PROMPT_GPT55]
|
||||
case "ling":
|
||||
return [PROMPT_LING]
|
||||
case "trinity":
|
||||
return [PROMPT_TRINITY]
|
||||
function prompt() {
|
||||
switch (model.prompt) {
|
||||
case "anthropic":
|
||||
return [PROMPT_ANTHROPIC]
|
||||
case "anthropic_without_todo":
|
||||
return [PROMPT_DEFAULT]
|
||||
case "beast":
|
||||
return [PROMPT_BEAST]
|
||||
case "codex":
|
||||
return [PROMPT_CODEX]
|
||||
case "gemini":
|
||||
return [PROMPT_GEMINI]
|
||||
case "gpt55":
|
||||
return [PROMPT_GPT55]
|
||||
case "ling":
|
||||
return [PROMPT_LING]
|
||||
case "trinity":
|
||||
return [PROMPT_TRINITY]
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
const kilo = prompt()
|
||||
if (kilo) return kilo
|
||||
// kilocode_change end
|
||||
|
||||
if (model.api.id.includes("gpt-4") || model.api.id.includes("o1") || model.api.id.includes("o3"))
|
||||
|
||||
@@ -18,6 +18,7 @@ import { MessageID, PartID, SessionID } from "../../src/session/schema"
|
||||
import { SessionStatus } from "../../src/session/status"
|
||||
import { SessionSummary } from "../../src/session/summary"
|
||||
import { Snapshot } from "../../src/snapshot"
|
||||
import { KiloSessionProcessor } from "../../src/kilocode/session/processor"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import * as CrossSpawnSpawner from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { provideTmpdirInstance } from "../fixture/fixture"
|
||||
@@ -183,6 +184,170 @@ describe("session processor empty tool-calls", () => {
|
||||
),
|
||||
)
|
||||
|
||||
it.effect("adds warning when model stops after reasoning-only length finish", () =>
|
||||
provideTmpdirInstance(
|
||||
(dir) =>
|
||||
Effect.gen(function* () {
|
||||
const test = yield* TestLLM
|
||||
const processors = yield* SessionProcessor.Service
|
||||
const session = yield* Session.Service
|
||||
|
||||
yield* test.reply(
|
||||
{ type: "start" },
|
||||
{ type: "start-step" } as LLM.Event,
|
||||
{ type: "reasoning-start", id: "reasoning", providerMetadata: undefined } as LLM.Event,
|
||||
{ type: "reasoning-delta", id: "reasoning", text: "thinking", providerMetadata: undefined } as LLM.Event,
|
||||
{ type: "reasoning-end", id: "reasoning", providerMetadata: undefined } as LLM.Event,
|
||||
{
|
||||
type: "finish-step",
|
||||
finishReason: "length",
|
||||
usage: usage(),
|
||||
providerMetadata: undefined,
|
||||
} as LLM.Event,
|
||||
{ type: "finish" } as LLM.Event,
|
||||
)
|
||||
|
||||
const chat = yield* session.create({})
|
||||
const parent = yield* session.updateMessage({
|
||||
id: MessageID.ascending(),
|
||||
role: "user",
|
||||
sessionID: chat.id,
|
||||
agent: "code",
|
||||
model: ref,
|
||||
time: { created: Date.now() },
|
||||
})
|
||||
const msg: MessageV2.Assistant = {
|
||||
id: MessageID.ascending(),
|
||||
role: "assistant",
|
||||
sessionID: chat.id,
|
||||
parentID: parent.id,
|
||||
mode: "code",
|
||||
agent: "code",
|
||||
path: { cwd: path.resolve(dir), root: path.resolve(dir) },
|
||||
cost: 0,
|
||||
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
|
||||
modelID: ref.modelID,
|
||||
providerID: ref.providerID,
|
||||
time: { created: Date.now() },
|
||||
}
|
||||
yield* session.updateMessage(msg)
|
||||
|
||||
const mdl = model()
|
||||
const handle = yield* processors.create({
|
||||
assistantMessage: msg,
|
||||
sessionID: chat.id,
|
||||
model: mdl,
|
||||
})
|
||||
|
||||
const input: LLM.StreamInput = {
|
||||
user: parent as MessageV2.User,
|
||||
sessionID: chat.id,
|
||||
model: mdl,
|
||||
agent: { name: "code", mode: "primary", permission: [], options: {} } as any,
|
||||
system: [],
|
||||
messages: [],
|
||||
tools: {},
|
||||
}
|
||||
|
||||
yield* handle.process(input)
|
||||
const parts = MessageV2.parts(msg.id)
|
||||
const warning = parts.find(
|
||||
(part): part is MessageV2.TextPart =>
|
||||
part.type === "text" && part.text === KiloSessionProcessor.REASONING_LENGTH_WARNING,
|
||||
)
|
||||
|
||||
expect(warning?.ignored).toBe(true)
|
||||
|
||||
const modelMsgs = yield* MessageV2.toModelMessagesEffect([{ info: handle.message, parts }], mdl)
|
||||
expect(JSON.stringify(modelMsgs)).not.toContain(KiloSessionProcessor.REASONING_LENGTH_WARNING)
|
||||
}),
|
||||
{ git: true },
|
||||
),
|
||||
)
|
||||
|
||||
it.effect("adds generic warning when model stops after text length finish", () =>
|
||||
provideTmpdirInstance(
|
||||
(dir) =>
|
||||
Effect.gen(function* () {
|
||||
const test = yield* TestLLM
|
||||
const processors = yield* SessionProcessor.Service
|
||||
const session = yield* Session.Service
|
||||
|
||||
yield* test.reply(
|
||||
{ type: "start" },
|
||||
{ type: "start-step" } as LLM.Event,
|
||||
{ type: "text-start", id: "text", providerMetadata: undefined } as LLM.Event,
|
||||
{ type: "text-delta", id: "text", text: "partial answer", providerMetadata: undefined } as LLM.Event,
|
||||
{ type: "text-end", id: "text", providerMetadata: undefined } as LLM.Event,
|
||||
{
|
||||
type: "finish-step",
|
||||
finishReason: "length",
|
||||
usage: usage(),
|
||||
providerMetadata: undefined,
|
||||
} as LLM.Event,
|
||||
{ type: "finish" } as LLM.Event,
|
||||
)
|
||||
|
||||
const chat = yield* session.create({})
|
||||
const parent = yield* session.updateMessage({
|
||||
id: MessageID.ascending(),
|
||||
role: "user",
|
||||
sessionID: chat.id,
|
||||
agent: "code",
|
||||
model: ref,
|
||||
time: { created: Date.now() },
|
||||
})
|
||||
const msg: MessageV2.Assistant = {
|
||||
id: MessageID.ascending(),
|
||||
role: "assistant",
|
||||
sessionID: chat.id,
|
||||
parentID: parent.id,
|
||||
mode: "code",
|
||||
agent: "code",
|
||||
path: { cwd: path.resolve(dir), root: path.resolve(dir) },
|
||||
cost: 0,
|
||||
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
|
||||
modelID: ref.modelID,
|
||||
providerID: ref.providerID,
|
||||
time: { created: Date.now() },
|
||||
}
|
||||
yield* session.updateMessage(msg)
|
||||
|
||||
const mdl = model()
|
||||
const handle = yield* processors.create({
|
||||
assistantMessage: msg,
|
||||
sessionID: chat.id,
|
||||
model: mdl,
|
||||
})
|
||||
|
||||
const input: LLM.StreamInput = {
|
||||
user: parent as MessageV2.User,
|
||||
sessionID: chat.id,
|
||||
model: mdl,
|
||||
agent: { name: "code", mode: "primary", permission: [], options: {} } as any,
|
||||
system: [],
|
||||
messages: [],
|
||||
tools: {},
|
||||
}
|
||||
|
||||
yield* handle.process(input)
|
||||
const parts = MessageV2.parts(msg.id)
|
||||
const warning = parts.find(
|
||||
(part): part is MessageV2.TextPart =>
|
||||
part.type === "text" && part.text === KiloSessionProcessor.OUTPUT_LENGTH_WARNING,
|
||||
)
|
||||
|
||||
expect(warning?.ignored).toBe(true)
|
||||
|
||||
const modelMsgs = yield* MessageV2.toModelMessagesEffect([{ info: handle.message, parts }], mdl)
|
||||
const json = JSON.stringify(modelMsgs)
|
||||
expect(json).toContain("partial answer")
|
||||
expect(json).not.toContain(KiloSessionProcessor.OUTPUT_LENGTH_WARNING)
|
||||
}),
|
||||
{ git: true },
|
||||
),
|
||||
)
|
||||
|
||||
it.live("ignores deleted session during cost reconciliation", () =>
|
||||
provideTmpdirInstance(
|
||||
(dir) =>
|
||||
|
||||
@@ -9,6 +9,7 @@ import PROMPT_CODEX from "../../src/session/prompt/codex.txt"
|
||||
import PROMPT_GEMINI from "../../src/session/prompt/gemini.txt"
|
||||
import PROMPT_GPT from "../../src/session/prompt/gpt.txt"
|
||||
import PROMPT_GPT55 from "../../src/session/prompt/kilocode-gpt-5.5.txt"
|
||||
import PROMPT_LING from "../../src/session/prompt/ling.txt"
|
||||
import PROMPT_TRINITY from "../../src/session/prompt/trinity.txt"
|
||||
|
||||
describe("SystemPrompt.provider", () => {
|
||||
@@ -78,6 +79,24 @@ describe("SystemPrompt.provider", () => {
|
||||
expect(result).toEqual([PROMPT_ANTHROPIC])
|
||||
})
|
||||
|
||||
test("Ling fallback runs after upstream model id heuristics", () => {
|
||||
const model = ProviderTest.model({
|
||||
prompt: undefined,
|
||||
api: { id: "gpt-5-ling", url: "https://example.com", npm: "@ai-sdk/openai" },
|
||||
})
|
||||
const result = SystemPrompt.provider(model)
|
||||
expect(result).toEqual([PROMPT_GPT])
|
||||
})
|
||||
|
||||
test("Ling fallback is selected after upstream heuristics miss", () => {
|
||||
const model = ProviderTest.model({
|
||||
prompt: undefined,
|
||||
api: { id: "ling-2", url: "https://example.com", npm: "@ai-sdk/openai" },
|
||||
})
|
||||
const result = SystemPrompt.provider(model)
|
||||
expect(result).toEqual([PROMPT_LING])
|
||||
})
|
||||
|
||||
test("GPT-5.5 model ids are not prompt-special without metadata", () => {
|
||||
const model = ProviderTest.model({
|
||||
prompt: undefined,
|
||||
|
||||
Reference in New Issue
Block a user